Changed model config to key/value format and improved model module features. Model access isn't tested yet, but base stuff works. Minior fixes.

Added more random model selection presets: public, admins, hidden and mother zombies.
Simplified ClassFlagFilterMatch logic.
This commit is contained in:
richard 2009-11-17 08:47:39 +01:00
parent 8c1a549239
commit 66ed43dd48
8 changed files with 824 additions and 309 deletions

View File

@ -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 ;<string> 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
"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" ""
//}
}

View File

@ -16,7 +16,7 @@
<p class="headerinfo">Targets plugin version 3.0.0 Beta 2, (not released)<br />
Written by Richard Helgeby</p>
<p class="headerinfo">Manual last modified: 2009.10.31</p>
<p class="headerinfo">Manual last modified: 2009.11.17</p>
<h2>Index</h2>
@ -747,6 +747,9 @@ configuration files are optional.</p>
certain maps. That could be scaling knockback, restricting certain weapons, changing class
attributes or changing ambience sound.</p>
<p>Other map configuration plugins should also work if certain features that doesn't exist in
Zombie:Reloaded map configurations is needed.</p>
<h4><a name="3.4.1" />1. Types</h4>
<p>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
<h3><a name="3.5" />3.5 Model Configuration</h3>
<p><strong>Note:</strong> 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.</p>
<p>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.</p>
<p>The model configuration file is a list of models used on the server stored in Valve's key/value
format.</p>
<p>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.</p>
<p>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.</p>
<p>In addition models can be restricted to certain groups using the "access" attribute.</p>
<p>Each line is separated into two fields with ";". The last field is optional.</p>
<p>If no flag is specified it's treated as a regular public model.</p>
<p>Model line syntax:</p>
<blockquote><p><code>&lt;model path&gt;[; flag]</code></p></blockquote>
<p>List of available model attributes:</p>
<blockquote><table>
<caption>Model Flags</caption>
<caption>Model Attributes</caption>
<tr>
<td class="valueoption">public</td>
<td>Everyone can use the model.</td>
<th class="namewidth">Attribute:</th>
<th class="mediumwidth">Value type:</th>
</tr>
<tr>
<td class="commandheader">name</td>
<td class="commandheader">text</td>
</tr>
<tr>
<td class="valueoption">adminonly</td>
<td>(Incomplete) Can only be used by admins.</td>
<td class="indent" colspan="2">
<p>File name of model file, without extension.</p>
</td>
</tr>
<tr>
<td class="commandheader">path</td>
<td class="commandheader">text</td>
</tr>
<tr>
<td class="valueoption">donator</td>
<td>(Incomplete) Can only be used by donators.</td>
<td class="indent" colspan="2">
<p>Path to model files. <strong>Must</strong> end with "/". Windows servers can use "\"
in paths, but they also work with "/".</p>
<p>The path is relative to "cstrike".</p>
</td>
</tr>
<tr>
<td class="commandheader">team</td>
<td class="commandheader">text</td>
</tr>
<tr>
<td class="valueoption">hidden</td>
<td>(Incomplete) Is not included in random selections.</td>
<td class="indent" colspan="2">
<p>What team the model belongs to.</p>
<p>Options:</p>
<blockquote><table>
<tr><td class="valueoption">zombies</td><td>Zombie players. Includes mother zombies.</td></tr>
<tr><td class="valueoption">humans</td><td>Human players.</td></tr>
</table></blockquote>
</td>
</tr>
<tr>
<td class="commandheader">access</td>
<td class="commandheader">text</td>
</tr>
<tr>
<td class="valueoption">motherzombie</td>
<td>(Incomplete) Can only be used on mother zombies.</td>
<td class="indent" colspan="2">
<p>Access mode of the model.</p>
<p>Options:</p>
<blockquote><table>
<tr><td class="valueoption">public</td><td>Everyone can use the model. Included in
public random selections.</td></tr>
<tr><td class="valueoption">admins</td><td>Model can only be used by admins. Included
in public random selections but only applied to admins.</td></tr>
<tr><td class="valueoption">hidden</td><td>Model is excluded from public random selections.</td></tr>
<tr><td class="valueoption">motherzombies</td><td>Model can only be used by mother zombies.</td></tr>
<tr><td class="valueoption">group</td><td>Use group authentication. See "group" attribute.</td></tr>
</table></blockquote>
</td>
</tr>
<tr>
<td class="commandheader">group</td>
<td class="commandheader">text</td>
</tr>
<tr>
<td class="indent" colspan="2">
<p>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 ("").</p>
</td>
</tr>
</table></blockquote>
<p>Example usages:</p>
<blockquote><p><code>models/player/zh/zh_charple001<br />
models/player/zh/zh_corpse002; adminonly<br />
models/player/zh/zh_zombie003; hidden<br />
models/player/ics/hellknight_red/t_guerilla; motherzombie</code></p></blockquote>
<p>For example usages see examples in default model configuration.</p>
<p>Put the list of models in:</p>
@ -837,7 +879,7 @@ admins/donators, hidden from random selection or only for mother zombies.</p>
<h3><a name="3.6" />3.6 Download List</h3>
<p>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.</p>
download them. Use one line per file, with paths relative to the "cstrike" folder.</p>
<p>List files to be downloaded in the following file:</p>
@ -1011,8 +1053,8 @@ the admin-only flag in the <span class="code">flags</span> attribute.</p>
</tr>
<tr>
<td class="indent" colspan="3">
<p>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:</p>
<p>Model file path to use on the player, relative to the "cstrike" folder. There are a
few presets supported by this attribute:</p>
<blockquote><table>
<tr>
<td class="valueoption">default</td>
@ -1020,12 +1062,31 @@ the admin-only flag in the <span class="code">flags</span> attribute.</p>
</tr>
<tr>
<td class="valueoption">random</td>
<td>Selects a random model for the current team.</td>
<td>Selects a random public or admin model for the current team. Admin models
are only applied to admins.</td>
</tr>
<tr>
<td class="valueoption">nochange</td>
<td>Don't change model. To be used in combination with other plugins that
change model on players.</td>
<td class="valueoption">random_public</td>
<td>Selects a random public model for the current team.</td>
</tr>
<tr>
<td class="valueoption">random_admin</td>
<td>Selects a random admin model for the current team. Model permissions will
be ignored.</td>
</tr>
<tr>
<td class="valueoption">random_hidden</td>
<td>Selects a random hidden model for the current team.</td>
</tr>
<tr>
<td class="valueoption">random_mother_zombie</td>
<td><strong>Zombies only.</strong> Selects a random mother zombie model.</td>
</tr>
<tr>
<td class="valueoption">no_change</td>
<td>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.</td>
</tr>
</table></blockquote>
</td>

View File

@ -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"

88
src/zr/models.h.inc Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* ============================================================================
*/
/**
* 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). */
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
/**

View File

@ -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.