diff --git a/cstrike/addons/sourcemod/configs/zr/models.txt b/cstrike/addons/sourcemod/configs/zr/models.txt index 461a903..ac8f5ab 100644 --- a/cstrike/addons/sourcemod/configs/zr/models.txt +++ b/cstrike/addons/sourcemod/configs/zr/models.txt @@ -3,28 +3,96 @@ // ZOMBIE:RELOADED // Model configuration // -// Check the weapon configuration section in the manual for detailed info. +// See Model Configuration (3.5) section in the manual for detailed info. // // ============================================================================ -// Format: -// ---------------------------------------------------------------------------- -// the/path/to/the/model ;public/hidden/adminonly/etc -// * ;public - The model will be treated as a model that any client has access to. -// * ;hidden - The model can only be accessed through explicit use of a player class. -// E.g. If a class uses the "random" setting for model, then any non-public -// models will not be chosen. -// ============================================================================ -// * Each uncommented line will be used as a model path for clients to download, -// and classes to utilize. -// * If no ; is specified, the model will be assumed as public. -// ---------------------------------------------------------------------------- -// Defaults: +// +// SHORT DESCRIPTIONS +// +// Attribute: Description: // ---------------------------------------------------------------------------- +// name Name of model file, without extension. +// path Path to model files. MUST end with "/". +// team Model type: +// "zombies" +// "humans" +// access Access type: +// "public" - Everyone can use the model. +// "admins" - Model can only be used by admins. +// "hidden" - Model is excluded from public random selections. +// "motherzombies" - Model can only be used by mother zombies. +// "group" - Use group authentication. +// group If access is "group": A SourceMod group name. Otherwise blank (""). -models/player/zh/zh_charple001 ;public -models/player/zh/zh_zombie003 ;public -models/player/zh/zh_corpse002 ;public -models/player/ics/hellknight_red/t_guerilla ;public -// models/player/adminmodels/1337model ;adminonly // None of these models will be randomly chosen. -// models/player/donatormodels/donatormodel ;donator -// models/player/hiddenmodels/myhiddenmodel ;non-public \ No newline at end of file +"models" +{ + "zh_charple001" + { + "name" "zh_charple001" + "path" "models/player/zh/" + "team" "zombies" + "access" "public" + "group" "" + } + + "zh_zombie003" + { + "name" "zh_zombie003" + "path" "models/player/zh/" + "team" "zombies" + "access" "public" + "group" "" + } + + "zh_corpse002" + { + "name" "zh_corpse002" + "path" "models/player/zh/" + "team" "zombies" + "access" "public" + "group" "" + } + + "t_guerilla" + { + "name" "t_guerilla" + "path" "models/player/ics/hellknight_red/" + "team" "zombies" + "access" "public" + "group" "" + } + + // Special model examples: + // ----------------------- + + // Only admins can use this zombie model. + //"admin_zombie" + //{ + // "name" "1337model" + // "path" "models/player/adminmodels/" + // "team" "zombies" + // "access" "admins" + // "group" "" + //} + + // Only members of the zr_vip group in SourceMod can use this human model. + //"vip_human" + //{ + // "name" "vipmodel" + // "path" "models/player/vip/" + // "team" "humans" + // "access" "group" + // "group" "zr_vip" + //} + + // This model will be excluded from public random selections. Only classes + // that use "randomhidden" or explicit specify this model will be able to use it. + //"hidden" + //{ + // "name" "hiddenmodel" + // "path" "models/player/" + // "team" "humans" + // "access" "hidden" + // "group" "" + //} +} diff --git a/docs/zr_manual.htm b/docs/zr_manual.htm index 46764c6..9a463d7 100644 --- a/docs/zr_manual.htm +++ b/docs/zr_manual.htm @@ -16,7 +16,7 @@

Targets plugin version 3.0.0 Beta 2, (not released)
Written by Richard Helgeby

-

Manual last modified: 2009.10.31

+

Manual last modified: 2009.11.17

Index

@@ -747,6 +747,9 @@ configuration files are optional.

certain maps. That could be scaling knockback, restricting certain weapons, changing class attributes or changing ambience sound.

+

Other map configuration plugins should also work if certain features that doesn't exist in +Zombie:Reloaded map configurations is needed.

+

1. Types

There are two kinds of map configs; pre and post. Pre map configuration files are executed before the modules and data is loaded. They're useful for changing configuration sets for certain @@ -777,57 +780,96 @@ stuff have to be placed in this one to be effective, like changing class attribu

3.5 Model Configuration

-

Note: Work in progress. Model list data structure is about to be changed -with support for human models and model restrictions. Currently, assume all models to be public -zombie models that everyone can use.

- -

The model configuration file is a list of models used on the server. Each line contains the path -including the model name, but not the file extension.

+

The model configuration file is a list of models used on the server stored in Valve's key/value +format.

The models listed in this file are also precached when the server starts. Custom models used, but not listed in this file will cause a "model not precached" error on the server, so they must be listed in this file.

-

In addition certain flags can be added to mark the model as special, such as only for -admins/donators, hidden from random selection or only for mother zombies.

+

In addition models can be restricted to certain groups using the "access" attribute.

-

Each line is separated into two fields with ";". The last field is optional.

- -

If no flag is specified it's treated as a regular public model.

- -

Model line syntax:

-

<model path>[; flag]

+

List of available model attributes:

- + + + - - + + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + + + +
Model FlagsModel Attributes
publicEveryone can use the model.Attribute:Value type:
nametext
adminonly(Incomplete) Can only be used by admins. +

File name of model file, without extension.

+
pathtext
donator(Incomplete) Can only be used by donators. +

Path to model files. Must end with "/". Windows servers can use "\" + in paths, but they also work with "/".

+

The path is relative to "cstrike".

+
teamtext
hidden(Incomplete) Is not included in random selections. +

What team the model belongs to.

+

Options:

+
+ + +
zombiesZombie players. Includes mother zombies.
humansHuman players.
+
accesstext
motherzombie(Incomplete) Can only be used on mother zombies. +

Access mode of the model.

+

Options:

+
+ + + + + +
publicEveryone can use the model. Included in + public random selections.
adminsModel can only be used by admins. Included + in public random selections but only applied to admins.
hiddenModel is excluded from public random selections.
motherzombiesModel can only be used by mother zombies.
groupUse group authentication. See "group" attribute.
+
grouptext
+

Name of SourceMod group to use for model authentication if access is "group". If + access is anything else than "group" this setting is ignored and can be blank ("").

+
-

Example usages:

- -

models/player/zh/zh_charple001
- models/player/zh/zh_corpse002; adminonly
- models/player/zh/zh_zombie003; hidden
- models/player/ics/hellknight_red/t_guerilla; motherzombie

+

For example usages see examples in default model configuration.

Put the list of models in:

@@ -837,7 +879,7 @@ admins/donators, hidden from random selection or only for mother zombies.

3.6 Download List

Custom models, materials and overlays must be listed in the download list so clients will -download them. Use one line per file with paths relative to the "cstrike" folder.

+download them. Use one line per file, with paths relative to the "cstrike" folder.

List files to be downloaded in the following file:

@@ -1011,8 +1053,8 @@ the admin-only flag in the flags attribute.

-

The model file to use on the player, path is relative to the "cstrike" folder. There - are a few special values supported by this attribute:

+

Model file path to use on the player, relative to the "cstrike" folder. There are a + few presets supported by this attribute:

@@ -1020,12 +1062,31 @@ the admin-only flag in the flags attribute.

- + - - + + + + + + + + + + + + + + + + + +
default
randomSelects a random model for the current team.Selects a random public or admin model for the current team. Admin models + are only applied to admins.
nochangeDon't change model. To be used in combination with other plugins that - change model on players.random_publicSelects a random public model for the current team.
random_adminSelects a random admin model for the current team. Model permissions will + be ignored.
random_hiddenSelects a random hidden model for the current team.
random_mother_zombieZombies only. Selects a random mother zombie model.
no_changeDon't change model. Use this setting to keep default Counter-Strike: Source + model, or to fix a compatibility issue with other plugins that change model on + players.
diff --git a/src/zombiereloaded.sp b/src/zombiereloaded.sp index 7f26aba..8662769 100644 --- a/src/zombiereloaded.sp +++ b/src/zombiereloaded.sp @@ -54,6 +54,7 @@ // Header includes. #include "zr/log.h" +#include "zr/models.h" #if defined ADD_VERSION_INFO #include "zr/hgversion.h" @@ -79,10 +80,10 @@ #include "zr/paramtools" #include "zr/paramparser" #include "zr/shoppinglist" -#include "zr/models" #include "zr/downloads" #include "zr/overlays" #include "zr/playerclasses/playerclasses" +#include "zr/models" #include "zr/weapons/weapons" #include "zr/hitgroups" #include "zr/roundstart" diff --git a/src/zr/models.h.inc b/src/zr/models.h.inc new file mode 100644 index 0000000..a006414 --- /dev/null +++ b/src/zr/models.h.inc @@ -0,0 +1,88 @@ +/* + * ============================================================================ + * + * Zombie:Reloaded + * + * File: models.h.inc + * Type: Core + * Description: Model data structures and constants. + * + * Copyright (C) 2009 Greyscale, Richard Helgeby + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * ============================================================================ + */ + +/** + * Maximum number of models. + */ +#define MODELS_MAX 48 + +/** + * Maximum folder depth a model file can be located. + */ +#define MODELS_PATH_MAX_DEPTH 8 + +/** + * Maximum string length of a folder a model file is located under. + */ +#define MODELS_PATH_DIR_MAX_LENGTH 32 + +/** + * Model access settings. + */ +enum ModelAccess +{ + ModelAccess_Invalid = -1, /* Invalid access type. */ + ModelAccess_Public = 0, /* Everyone can use the model. */ + ModelAccess_Admins, /* Model can only be used by admins. */ + ModelAccess_Hidden, /* Model is excluded from public random selections. */ + ModelAccess_MotherZombies, /* Only mother zombies can use this model. */ + ModelAccess_Group /* Enables group authentication. */ +} + +/** + * @section Model access flags. + */ +#define MODEL_ACCESS_PUBLIC (1<<0) +#define MODEL_ACCESS_ADMINS (1<<1) +#define MODEL_ACCESS_HIDDEN (1<<2) +#define MODEL_ACCESS_MOTHER_ZOMBIES (1<<3) +#define MODEL_ACCESS_GROUP (1<<4) +/** + * @endsection + */ + +/** + * Avaliable teams for models. + */ +enum ModelTeam +{ + ModelTeam_Invalid = -1, + ModelTeam_Zombies = 0, + ModelTeam_Humans +} + +/** + * Model settings structure. + */ +enum ModelAttributes +{ + String:Model_Name[64], /* File name of model (no file extension). */ + String:Model_Path[PLATFORM_MAX_PATH], /* Path to model files. */ + ModelTeam:Model_Team, /* What team the model belongs to. */ + ModelAccess:Model_Access, /* Access settings. */ + String:Model_Group[64] /* Group authentication (if used). */ +} diff --git a/src/zr/models.inc b/src/zr/models.inc index e735f12..35e81ac 100644 --- a/src/zr/models.inc +++ b/src/zr/models.inc @@ -5,7 +5,7 @@ * * File: models.inc * Type: Core - * Description: Model validation. + * Description: Model manager. * * Copyright (C) 2009 Greyscale, Richard Helgeby * @@ -25,173 +25,208 @@ * ============================================================================ */ -/** - * Maximum folder depth a model file can be located. +/* + * Note: Data structures and constants defined in models.h.inc. */ -#define MODELS_PATH_MAX_DEPTH 8 /** - * Maximum string length of a folder a model file is located under. + * Parsed model data. */ -#define MODELS_PATH_DIR_MAX_LENGTH 32 +new ModelData[MODELS_MAX][ModelAttributes]; /** - * Array that stores a list of validated models. + * Number of valid models. */ -new Handle:arrayModels = INVALID_HANDLE; +new ModelCount; + /** * Prepare all model/download data. */ ModelsLoad() { + new Handle:kvModels = INVALID_HANDLE; + // Register config file. ConfigRegisterConfig(File_Models, Structure_List, CONFIG_FILE_ALIAS_MODELS); // Get models file path. - decl String:pathmodels[PLATFORM_MAX_PATH]; - new bool:exists = ConfigGetCvarFilePath(CVAR_CONFIG_PATH_MODELS, pathmodels); + decl String:modelPath[PLATFORM_MAX_PATH]; + new bool:exists = ConfigGetCvarFilePath(CVAR_CONFIG_PATH_MODELS, modelPath); // If file doesn't exist, then log and stop. if (!exists) { // Log failure and stop plugin. - LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Missing models file: \"%s\"", pathmodels); + LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Missing model list: \"%s\"", modelPath); } // Set the path to the config file. - ConfigSetConfigPath(File_Models, pathmodels); + ConfigSetConfigPath(File_Models, modelPath); - // Load config from file and create array structure. - new bool:success = ConfigLoadConfig(File_Models, arrayModels, PLATFORM_MAX_PATH); + // Prepare key/value structure. + kvModels = CreateKeyValues(CONFIG_FILE_ALIAS_MODELS); - // Unexpected error, stop plugin. - if (!success) + // Log what models file that is loaded. + LogEvent(false, LogType_Normal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Loading models from file \"%s\".", modelPath); + + // Load model data file. + FileToKeyValues(kvModels, modelPath); + + // Try to find the first model. + KvRewind(kvModels); + if (!KvGotoFirstSubKey(kvModels)) { - LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Unexpected error encountered loading: %s", pathmodels); + LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Can't find any models in \"%s\"", modelPath); } - new modelcount; - new modelpublicvalidcount; - new modelnonpublicvalidcount; - new modelfilecount; + decl String:buffer[256]; + decl String:name[64]; + decl String:path[PLATFORM_MAX_PATH]; + decl String:team[64]; + decl String:access[64]; + decl String:group[64]; - decl String:modelbase[PLATFORM_MAX_PATH]; - decl String:modelpath[PLATFORM_MAX_PATH]; - decl String:modelname[MODELS_PATH_DIR_MAX_LENGTH]; - decl String:modelfile[MODELS_PATH_DIR_MAX_LENGTH]; - decl String:modeldiskname[MODELS_PATH_DIR_MAX_LENGTH]; - decl String:modelfullpath[PLATFORM_MAX_PATH]; + ModelCount = 0; + new failedCount; + new publicCount; - new String:baseexploded[MODELS_PATH_MAX_DEPTH][MODELS_PATH_DIR_MAX_LENGTH]; - - new FileType:type; - - new models = modelcount = GetArraySize(arrayModels); - - // x = model array index. - for (new x = 0; x < models; x++) + // Loop through all models and store attributes in ModelData array. + do { - // Get base model path, excluding the public/non-public setting. - ModelReturnPath(x, modelbase, sizeof(modelbase)); - - // Explode path into pieces. (separated by "/") - new strings = ExplodeString(modelbase, "/", baseexploded, sizeof(baseexploded), sizeof(baseexploded[])); - - // Get model file name. - strcopy(modelname, sizeof(modelname), baseexploded[strings - 1]); - - // Get the path to the file. - // Works by truncating original path by the length of the file name. - strcopy(modelpath, strlen(modelbase) - strlen(modelname), modelbase); - - // Open dir containing model files. - new Handle:modeldir = OpenDirectory(modelpath); - - if (modeldir == INVALID_HANDLE) + if (ModelCount > MODELS_MAX) { - LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Error opening model path directory: %s", modelpath); + // Maximum number of models reached. Log a warning and exit the loop. + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Warning: Maximum number of models reached (%d). Skipping other models.", MODELS_MAX + 1); + + break; + } + + KvGetString(kvModels, "name", name, sizeof(name)); + strcopy(ModelData[ModelCount][Model_Name], 64, name); + + KvGetString(kvModels, "path", path, sizeof(path)); + strcopy(ModelData[ModelCount][Model_Path], 64, path); + + KvGetString(kvModels, "team", team, sizeof(team)); + ModelData[ModelCount][Model_Team] = ModelsStringToTeam(team); + + KvGetString(kvModels, "access", access, sizeof(access)); + ModelData[ModelCount][Model_Access] = ModelsStringToAccess(access); + + KvGetString(kvModels, "group", group, sizeof(group)); + strcopy(ModelData[ModelCount][Model_Group], 64, group); + + + // Validate model attributes. + + // Build path and check if model file exist. + strcopy(buffer, sizeof(buffer), path); + StrCat(buffer, sizeof(buffer), name); + StrCat(buffer, sizeof(buffer), ".mdl"); + if (!FileExists(buffer)) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Warning: Invalid model name/path setting at index %d. File not found: \"%s\".", ModelCount + failedCount, buffer); + failedCount++; continue; } - // Reset model file count. - modelfilecount = 0; - - while (ReadDirEntry(modeldir, modelfile, sizeof(modelfile), type)) + // Validate team. + if (ModelData[ModelCount][Model_Team] == ModelTeam_Invalid) { - // If entry isn't a file, then stop. + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Warning: Invalid model team setting at index %d: \"%s\".", ModelCount + failedCount, team); + failedCount++; + continue; + } + + // Validate access. + if (ModelData[ModelCount][Model_Access] == ModelAccess_Invalid) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Warning: Invalid model access setting at index %d: \"%s\".", ModelCount + failedCount, access); + failedCount++; + continue; + } + else + { + // Increment public model counter. + if (ModelData[ModelCount][Model_Access] == ModelAccess_Public) + { + publicCount++; + } + } + + // Validate group. + if (ModelData[ModelCount][Model_Access] == ModelAccess_Group && + FindAdmGroup(group) == INVALID_GROUP_ID) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Warning: Invalid model group setting at index %d. Couldn't find SourceMod group \"%s\".", ModelCount + failedCount, group); + failedCount++; + continue; + } + + // Open directory with model files. + new Handle:dir = OpenDirectory(path); + + // Check if failed. + if (dir == INVALID_HANDLE) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Error opening directory: %s", dir); + continue; + } + + new FileType:type; + decl String:file[64]; + decl String:fileShort[64]; + + // Search for model files with the specified name and add them to + // downloads table. + while (ReadDirEntry(dir, file, sizeof(file), type)) + { + // Skip if entry isn't a file. if (type != FileType_File) { continue; } // Find break point index in the string to get model name. - // Add one because it seems to break on the character before. - new breakpoint = FindCharInString(modelfile, '.') + 1; - strcopy(modeldiskname, breakpoint, modelfile); + // Add one to make space for null terminator. + new breakpoint = FindCharInString(file, '.') + 1; + strcopy(fileShort, breakpoint, file); - // If this file doesn't match, then stop. - if (!StrEqual(modelname, modeldiskname, false)) + // If this file doesn't match model name, then skip it. + if (!StrEqual(name, fileShort, false)) { continue; } // Format a full path string. - strcopy(modelfullpath, sizeof(modelfullpath), modelpath); - Format(modelfullpath, sizeof(modelfullpath), "%s/%s", modelfullpath, modelfile); + strcopy(buffer, sizeof(buffer), path); + Format(buffer, sizeof(buffer), "%s%s", buffer, file); // Precache model file and add to downloads table. - PrecacheModel(modelfullpath); - AddFileToDownloadsTable(modelfullpath); - - // Increment modelfilecount - modelfilecount++; + PrecacheModel(buffer); + AddFileToDownloadsTable(buffer); } - CloseHandle(modeldir); + CloseHandle(dir); - // Increment variable if model files are valid. - if (modelfilecount) - { - // Increment proper variable. - if (ModelIsPublic(x)) - { - modelpublicvalidcount++; - } - else - { - modelnonpublicvalidcount++; - } - } - else - { - // Remove client from array. - RemoveFromArray(arrayModels, x); - - // Subtract one from count. - models--; - - // Backtrack one index, because we deleted it out from under the loop. - x--; - - // Log missing model files. - LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Missing model files on server (%s)", modelbase); - } + ModelCount++; + } while (KvGotoNextKey(kvModels)); + + CloseHandle(kvModels); + + // Check if there are no public models. + if (!publicCount) + { + LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Couldn't find any public model in \"%s\". There must be at least one public model.", modelPath); } // Log model validation info. - LogEvent(false, LogType_Normal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Total: %d | Successful Public: %d | Successful Non-Public: %d | Unsuccessful: %d", modelcount, modelpublicvalidcount, modelnonpublicvalidcount, modelcount - (modelpublicvalidcount + modelnonpublicvalidcount)); - - // If none of the model paths are valid, then log and fail. - if (!modelpublicvalidcount) - { - LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "No usable (public) model paths in %s", pathmodels); - } + LogEvent(false, LogType_Normal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Successful: %d | Unsuccessful: %d", ModelCount, failedCount); // Set config data. ConfigSetConfigLoaded(File_Models, true); ConfigSetConfigReloadFunc(File_Models, GetFunctionByName(GetMyHandle(), "ModelsOnConfigReload")); - ConfigSetConfigHandle(File_Models, arrayModels); } /** @@ -204,120 +239,336 @@ public ModelsOnConfigReload(ConfigFile:config) } /** - * Checks if a model is public. - * - * @param modelindex The array index of the model to check. - * @return True if public, false if not. + * Returns a random model index according to the specified filter settings. + * + * @param client Optional. Client index used to check for + * permissions in "group" or "admins" access mode. + * Use negative index to disable permission check + * (default). + * @param teamFilter Optional. Team filtering settings. Use + * ModelTeam_Invalid to disable filter. Default is + * ModelTeam_Zombies. + * @param accessRequireFlags Optional. One or more required access flags. + * Default is MODEL_ACCESS_PUBLIC. + * @return Random model index according to filter, or -1 on error. */ -stock bool:ModelIsPublic(modelindex) +ModelsGetRandomModel(client = -1, ModelTeam:teamFilter = ModelTeam_Zombies, accessRequireFlags = MODEL_ACCESS_PUBLIC) { - // Get the entire model string to parse for what we need. - decl String:modelsetting[PLATFORM_MAX_PATH + 16]; - GetArrayString(arrayModels, modelindex, modelsetting, sizeof(modelsetting)); + decl modelIndexes[MODELS_MAX]; + new listCount; - // We define this to use as little memory as possible, because the value won't be used. - decl String:modelpath[1]; - decl String:strpublic[32]; - - if (StrContains(modelsetting, ";") > -1) + // Loop through all models. + for (new index = 0; index < ModelCount; index++) { - // Get string index of where the public setting starts. - new strindex = SplitString(modelsetting, ";", modelpath, sizeof(modelpath)); - - // Copy setting to new string - strcopy(strpublic, sizeof(strpublic), modelsetting[strindex]); - - // Trim the whitespace. - TrimString(strpublic); - - // If public, return true, non-public returns false. - return StrEqual(strpublic, "public", false); - } - - // If nothing is specified, assume public. - return true; -} - -/** - * Returns the path of a given model index. - * - * @param modelindex The array index of the model. - * @param modelpath The output string of the model path. - * @param maxlen The maximum length of the output string. - */ -stock ModelReturnPath(modelindex, String:modelpath[], maxlen) -{ - // Get the entire model string to parse for what we need. - decl String:modelsetting[PLATFORM_MAX_PATH + 16]; - GetArrayString(arrayModels, modelindex, modelsetting, sizeof(modelsetting)); - - // Copy to path before split just in case the string has no ";" - strcopy(modelpath, maxlen, modelsetting); - if (StrContains(modelsetting, ";") > -1) - { - SplitString(modelsetting, ";", modelpath, maxlen); - } - - // Trim whitespace. - TrimString(modelpath); -} - -/** - * Get a random model index in arrayModels, allows you to specify a filter. - * - * @param modelpath The output string of the model path. - * @param maxlen The maximum length of the output string. - * @param all True to choose any of the models in the file, false to use 'publicmodels' param. - * @param publicmodels True to find a random public model, false to find non-public. - */ -stock ModelsGetRandomModelIndex(String:modelpath[], maxlen, bool:all = true, bool:publicmodels = true) -{ - new modelindex = -1; - - // Return any random model. - if (all) - { - // Get random model index and return the string in it. - modelindex = GetRandomInt(0, GetArraySize(arrayModels) - 1); - } - else - { - new Handle:modelsarray = CreateArray(PLATFORM_MAX_PATH); - decl String:modelsetting[PLATFORM_MAX_PATH]; - - // x = Array index. - new size = GetArraySize(arrayModels); - for (new x = 0; x < size; x++) + // Check team filtering. Skip if no match. + if (teamFilter != ModelTeam_Invalid && + ModelsGetTeam(index) != teamFilter) { - if (publicmodels == ModelIsPublic(x)) + continue; + } + + // Cache current model access flag. + new ModelAccess:access = ModelsGetAccess(index); + new accessFlag = ModelsGetAccessFlag(access); + + // Check access filtering. Skip if no match. + if (accessRequireFlags > 0 && + !(accessRequireFlags & accessFlag)) + { + continue; + } + + // Do client group authentication if client is specified. + if (client > 0) + { + // Check if current model use group authentication. + if (access == ModelAccess_Group) { - // Transfer model to temp array. - GetArrayString(arrayModels, x, modelsetting, sizeof(modelsetting)); - PushArrayString(modelsarray, modelsetting); + decl String:group[64]; + ModelsGetGroup(index, group, sizeof(group)); + + if (!ZRIsClientInGroup(client, group)) + { + // Client not authorized to use this model. + continue; + } + } + else + { + // No group authentication. Do regular authentication if model + // is a admin model. + if (access == ModelAccess_Admins && + !ZRIsClientAdmin(client)) + { + // Client not authorized to use this model. + continue; + } } } - // y = Array index. - size = GetArraySize(modelsarray); - - // If there are no models then copy a blank string to the output. - if (size == 0) - { - strcopy(modelpath, maxlen, ""); - - // Destroy the handle. - CloseHandle(modelsarray); - - return; - } - - // Get random model index from the temp list, and return the string in it. - modelindex = GetRandomInt(0, GetArraySize(modelsarray) - 1); - - // Destroy the handle. - CloseHandle(modelsarray); + // Model passed filter tests. Add to list. + modelIndexes[listCount] = index; + listCount++; } - // Get the path to the selected model. - ModelReturnPath(modelindex, modelpath, maxlen); + // Check if any models passed the filter. + if (listCount) + { + return modelIndexes[GetRandomInt(0, listCount - 1)]; + } + else + { + return -1; + } +} + +/** + * Validates the specified index according to maximum number of models, and + * number of models in use. Unused indexes will fail validation by default. + * + * @param index Model index to validate. + * @param rangeOnly Optional. Do not check if the index is in use. Default + * is false, check if in use. + * @return True if valid, false otherwise. + */ +bool:ModelsIsValidIndex(index, bool:rangeOnly = false) +{ + new bool:rangeValid = (index >= 0 && index < MODELS_MAX); + + if (rangeOnly) + { + // Only check if the index is valid. + return rangeValid; + } + else + { + // Check if the index is valid, and if it's in use. + return rangeValid && (index < ModelCount); + } +} + +/** + * Gets the name for the specified model. + * + * @param index Model index. + * @param buffer Destination string buffer. + * @param maxlen Size of buffer. + * @return Number of cells written, or -1 on error. + */ +ModelsGetName(index, String:buffer[], maxlen) +{ + // Validate index. + if (!ModelsIsValidIndex(index)) + { + return -1; + } + + return strcopy(buffer, maxlen, ModelData[index][Model_Name]); +} + +/** + * Gets the path for the specified model. + * + * @param index Model index. + * @param buffer Destination string buffer. + * @param maxlen Size of buffer. + * @return Number of cells written, or -1 on error. + */ +ModelsGetPath(index, String:buffer[], maxlen) +{ + // Validate index. + if (!ModelsIsValidIndex(index)) + { + return -1; + } + + return strcopy(buffer, maxlen, ModelData[index][Model_Path]); +} + +/** + * Gets the team for the specified model. + * + * @param index Model index. + * @return Team for the specified model, ModelTeam_Invalid on error. + */ +ModelTeam:ModelsGetTeam(index) +{ + // Validate index. + if (!ModelsIsValidIndex(index)) + { + return ModelTeam_Invalid; + } + + return ModelData[index][Model_Team]; +} + +/** + * Gets the access setting for the specified model. + * + * @param index Model index. + * @return Access setting for the specified model, ModelAccess_Invalid + * on error. + */ +ModelAccess:ModelsGetAccess(index) +{ + // Validate index. + if (!ModelsIsValidIndex(index)) + { + return ModelAccess_Invalid; + } + + return ModelData[index][Model_Access]; +} + +/** + * Gets the access flag for the specified access setting. + * + * @param access Access setting to convert. + * @return Access flag, or 0 on error. + */ +ModelsGetAccessFlag(ModelAccess:access) +{ + switch (access) + { + case ModelAccess_Public: + { + return MODEL_ACCESS_PUBLIC; + } + case ModelAccess_Admins: + { + return MODEL_ACCESS_ADMINS; + } + case ModelAccess_Hidden: + { + return MODEL_ACCESS_HIDDEN; + } + case ModelAccess_MotherZombies: + { + return MODEL_ACCESS_MOTHER_ZOMBIES; + } + case ModelAccess_Group: + { + return MODEL_ACCESS_GROUP; + } + } + + // Invalid access flag. + return 0; +} + +/** + * Gets the group for the specified model. + * + * @param index Model index. + * @param buffer Destination string buffer. + * @param maxlen Size of buffer. + * @return Number of cells written, or -1 on error. + */ +ModelsGetGroup(index, String:buffer[], maxlen) +{ + // Validate index. + if (!ModelsIsValidIndex(index)) + { + return -1; + } + + return strcopy(buffer, maxlen, ModelData[index][Model_Group]); +} + +/** + * Gets the full model file path for the specified model. + * + * @param index Model index. + * @param buffer Destination string buffer. + * @param maxlen Size of buffer. + * @return Number of cells written, or -1 on error. + */ +ModelsGetFullPath(index, String:buffer[], maxlen) +{ + decl String:path[PLATFORM_MAX_PATH]; + decl String:name[64]; + + ModelsGetPath(index, path, sizeof(path)); + ModelsGetName(index, name, sizeof(name)); + + buffer[0] = 0; + + StrCat(buffer, maxlen, path); + StrCat(buffer, maxlen, name); + StrCat(buffer, maxlen, ".mdl"); +} + +/** + * Converts the specified string to a team setting. + * + * @param team String to convert. + * @return Team setting, or ModelTeam_Invalid on error. + */ +ModelTeam:ModelsStringToTeam(const String:team[]) +{ + if (StrEqual(team, "zombies", false)) + { + return ModelTeam_Zombies; + } + else if (StrEqual(team, "humans", false)) + { + return ModelTeam_Humans; + } + + return ModelTeam_Invalid; +} + +/** + * Converts the specified class team ID to a team setting. + * + * @param teamid Class team ID. + * @return Team setting, or ModelTeam_Invalid on error. + */ +ModelTeam:ModelsTeamIdToTeam(teamid) +{ + switch (teamid) + { + case ZR_CLASS_TEAM_ZOMBIES: + { + return ModelTeam_Zombies; + } + case ZR_CLASS_TEAM_HUMANS: + { + return ModelTeam_Humans; + } + } + + return ModelTeam_Invalid; +} + +/** + * Converts the specified string to a access setting. + * + * @param access String to convert. + * @return Access setting, or ModelAccess_Invalid on error. + */ +ModelAccess:ModelsStringToAccess(const String:access[]) +{ + if (StrEqual(access, "public", false)) + { + return ModelAccess_Public; + } + else if (StrEqual(access, "admins", false)) + { + return ModelAccess_Admins; + } + else if (StrEqual(access, "hidden", false)) + { + return ModelAccess_Hidden; + } + else if (StrEqual(access, "motherzombies", false)) + { + return ModelAccess_MotherZombies; + } + else if (StrEqual(access, "group", false)) + { + return ModelAccess_Group; + } + + return ModelAccess_Invalid; } diff --git a/src/zr/playerclasses/apply.inc b/src/zr/playerclasses/apply.inc index 2818651..b683d99 100644 --- a/src/zr/playerclasses/apply.inc +++ b/src/zr/playerclasses/apply.inc @@ -77,24 +77,64 @@ bool:ClassApplyAttributes(client, bool:improved = false) */ bool:ClassApplyModel(client, classindex, cachetype = ZR_CLASS_CACHE_PLAYER) { + new bool:isAttributePreset = false; decl String:modelpath[PLATFORM_MAX_PATH]; + new index; + new ModelTeam:team; + new access; + new model; - // Get the model path from the specified cache. + // Get correct index according to cache type. if (cachetype == ZR_CLASS_CACHE_PLAYER) { - ClassGetModelPath(client, modelpath, sizeof(modelpath), cachetype); + index = client; } else { - ClassGetModelPath(classindex, modelpath, sizeof(modelpath), cachetype); + index = classindex; } - // Check if the user specified a pre-defined model setting. + // Get the model path from the specified cache. + ClassGetModelPath(index, modelpath, sizeof(modelpath), cachetype); + + // Get model team setting from the specified cache. + team = ModelsTeamIdToTeam(ClassGetTeamID(index, cachetype)); + + // Check if the user specified a pre-defined model setting. If so, setup + // model filter settings. if (StrEqual(modelpath, "random", false)) { - // TODO: Make a function that gets a random model from the specified team. - ModelsGetRandomModelIndex(modelpath, sizeof(modelpath), false, true); - Format(modelpath, sizeof(modelpath), "%s.mdl", modelpath); + // Set access filter flags. + access = MODEL_ACCESS_PUBLIC | MODEL_ACCESS_ADMINS; + + // Specify client for including admin models if client is admin. + index = client; + + isAttributePreset = true; + } + else if (StrEqual(modelpath, "random_public", false)) + { + access = MODEL_ACCESS_PUBLIC; + index = -1; + isAttributePreset = true; + } + else if (StrEqual(modelpath, "random_hidden", false)) + { + access = MODEL_ACCESS_HIDDEN; + index = -1; + isAttributePreset = true; + } + else if (StrEqual(modelpath, "random_admin", false)) + { + access = MODEL_ACCESS_ADMINS; + index = -1; + isAttributePreset = true; + } + else if (StrEqual(modelpath, "random_mother_zombie", false)) + { + access = MODEL_ACCESS_MOTHER_ZOMBIES; + index = -1; + isAttributePreset = true; } else if (StrEqual(modelpath, "default", false)) { @@ -112,12 +152,33 @@ bool:ClassApplyModel(client, classindex, cachetype = ZR_CLASS_CACHE_PLAYER) return true; } } - else if (StrEqual(modelpath, "nochange", false)) + else if (StrEqual(modelpath, "no_change", false)) { // Do nothing. return true; } + // Check if model setting is a attribute preset. + if (isAttributePreset) + { + // Get model based on filter settings set up earlier. + model = ModelsGetRandomModel(index, team, access); + + // Check if found. + if (model >= 0) + { + // Get model path. + ModelsGetFullPath(model, modelpath, sizeof(modelpath)); + } + else + { + // Couldn't find any models based on filter. Fall back to a random + // public model. Then get its path. + model = ModelsGetRandomModel(-1, team, MODEL_ACCESS_PUBLIC); + ModelsGetFullPath(model, modelpath, sizeof(modelpath)); + } + } + SetEntityModel(client, modelpath); return true; } diff --git a/src/zr/playerclasses/filtertools.inc b/src/zr/playerclasses/filtertools.inc index e5b3cf7..b42eb55 100644 --- a/src/zr/playerclasses/filtertools.inc +++ b/src/zr/playerclasses/filtertools.inc @@ -162,8 +162,12 @@ stock ClassValidateAttributes(classindex) } else { - // Check if a model different from a pre-defined setting. + // Validate only if not a pre-defined setting. if (!StrEqual(model_path, "random", false) && + !StrEqual(model_path, "random_public", false) && + !StrEqual(model_path, "random_hidden", false) && + !StrEqual(model_path, "random_admins", false) && + !StrEqual(model_path, "random_mother_zombies", false) && !StrEqual(model_path, "default", false) && !StrEqual(model_path, "nochange", false)) { @@ -172,7 +176,7 @@ stock ClassValidateAttributes(classindex) { flags += ZR_CLASS_MODEL_PATH; } - } + } } // Alpha, initial. @@ -705,8 +709,8 @@ stock bool:ClassFilterMatch(index, filter[ClassFilter], cachetype = ZR_CLASS_CAC stock bool:ClassFlagFilterMatch(index, require, deny, cachetype) { new flags; - new bool:requirepassed; - new bool:denypassed; + new bool:requirepassed = false; + new bool:denypassed = false; // Do quick check for optimization reasons: Check if no flags are specified. if (require == 0 && deny == 0) @@ -723,11 +727,6 @@ stock bool:ClassFlagFilterMatch(index, require, deny, cachetype) // All required flags are set. requirepassed = true; } - else - { - // Not all required flags are set. - requirepassed = false; - } // Match deny filter. if (deny == 0 || !(flags & deny)) @@ -735,11 +734,6 @@ stock bool:ClassFlagFilterMatch(index, require, deny, cachetype) // No denied flags are set. denypassed = true; } - else - { - // It has denied flags set. - denypassed = false; - } // Check if required and denied flags passed the filter. if (requirepassed && denypassed) @@ -747,11 +741,9 @@ stock bool:ClassFlagFilterMatch(index, require, deny, cachetype) // The class pass the filter. return true; } - else - { - // The class didn't pass the filter. - return false; - } + + // The class didn't pass the filter. + return false; } /** diff --git a/src/zr/playerclasses/playerclasses.inc b/src/zr/playerclasses/playerclasses.inc index 9409981..7193003 100644 --- a/src/zr/playerclasses/playerclasses.inc +++ b/src/zr/playerclasses/playerclasses.inc @@ -376,11 +376,6 @@ new ClassNoFilter[ClassFilter]; */ new ClassNoSpecialClasses[ClassFilter] = {false, 0, ZR_CLASS_SPECIALFLAGS, -1}; -/** - * Keyvalue handle to store class data. - */ -new Handle:kvClassData = INVALID_HANDLE; - /** * The original class data. This array only changed when class data is loaded. * ZR_CLASS_CACHE_ORIGINAL is the cache type to this array. @@ -413,7 +408,7 @@ new Float:ClassMultiplierCache[ZR_CLASS_TEAMCOUNT][ClassMultipliers]; new ClassCount; /** - * Specifies wether the class team requirement and attributes are valid or not. + * Specifies whether the class team requirements and attributes are valid or not. * Used to block events that happend before the module is done loading. */ new bool:ClassValidated; @@ -485,11 +480,9 @@ ClassLoad() // Register config file. ConfigRegisterConfig(File_Classes, Structure_Keyvalue, CONFIG_FILE_ALIAS_CLASSES); + new Handle:kvClassData; + // Make sure kvClassData is ready to use. - if (kvClassData != INVALID_HANDLE) - { - CloseHandle(kvClassData); - } kvClassData = CreateKeyValues(CONFIG_FILE_ALIAS_CLASSES); // Get weapons config path.