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 @@
-
+
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 Flags
+ Model Attributes
+
+
- public |
- Everyone can use the model. |
+ Attribute: |
+ Value type: |
+
+
+
+ name |
+ text |
- adminonly |
- (Incomplete) Can only be used by admins. |
+
+ File name of model file, without extension.
+ |
+
+
+
+ path |
+ text |
- 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".
+ |
+
+
+
+ team |
+ text |
- hidden |
- (Incomplete) Is not included in random selections. |
+
+ What team the model belongs to.
+ Options:
+
+ zombies | Zombie players. Includes mother zombies. |
+ humans | Human players. |
+
+ |
+
+
+
+ access |
+ text |
- motherzombie |
- (Incomplete) Can only be used on mother zombies. |
+
+ Access mode of the model.
+ Options:
+
+ public | Everyone can use the model. Included in
+ public random selections. |
+ admins | Model can only be used by admins. Included
+ in public random selections but only applied to admins. |
+ hidden | Model is excluded from public random selections. |
+ motherzombies | Model can only be used by mother zombies. |
+ group | Use group authentication. See "group" attribute. |
+
+ |
+
+
+
+ group |
+ text |
+
+
+
+ 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:
default |
@@ -1020,12 +1062,31 @@ the admin-only flag in the flags attribute.
random |
- Selects 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. |
- nochange |
- Don't change model. To be used in combination with other plugins that
- change model on players. |
+ random_public |
+ Selects a random public model for the current team. |
+
+
+ random_admin |
+ Selects a random admin model for the current team. Model permissions will
+ be ignored. |
+
+
+ random_hidden |
+ Selects a random hidden model for the current team. |
+
+
+ random_mother_zombie |
+ Zombies only. Selects a random mother zombie model. |
+
+
+ no_change |
+ Don'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.