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 // ZOMBIE:RELOADED
// Model configuration // 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: //
// ---------------------------------------------------------------------------- // SHORT DESCRIPTIONS
// the/path/to/the/model ;public/hidden/adminonly/etc //
// * ;public - The model will be treated as a model that any client has access to. // Attribute: Description:
// * ;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:
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// 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"
models/player/zh/zh_zombie003 ;public {
models/player/zh/zh_corpse002 ;public "zh_charple001"
models/player/ics/hellknight_red/t_guerilla ;public {
// models/player/adminmodels/1337model ;adminonly // None of these models will be randomly chosen. "name" "zh_charple001"
// models/player/donatormodels/donatormodel ;donator "path" "models/player/zh/"
// models/player/hiddenmodels/myhiddenmodel ;non-public "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 /> <p class="headerinfo">Targets plugin version 3.0.0 Beta 2, (not released)<br />
Written by Richard Helgeby</p> 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> <h2>Index</h2>
@ -747,6 +747,9 @@ configuration files are optional.</p>
certain maps. That could be scaling knockback, restricting certain weapons, changing class certain maps. That could be scaling knockback, restricting certain weapons, changing class
attributes or changing ambience sound.</p> 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> <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 <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 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> <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 <p>The model configuration file is a list of models used on the server stored in Valve's key/value
with support for human models and model restrictions. Currently, assume all models to be public format.</p>
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 models listed in this file are also precached when the server starts. Custom models used, <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 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> listed in this file.</p>
<p>In addition certain flags can be added to mark the model as special, such as only for <p>In addition models can be restricted to certain groups using the "access" attribute.</p>
admins/donators, hidden from random selection or only for mother zombies.</p>
<p>Each line is separated into two fields with ";". The last field is optional.</p> <p>List of available model attributes:</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>
<blockquote><table> <blockquote><table>
<caption>Model Flags</caption> <caption>Model Attributes</caption>
<tr> <tr>
<td class="valueoption">public</td> <th class="namewidth">Attribute:</th>
<td>Everyone can use the model.</td> <th class="mediumwidth">Value type:</th>
</tr>
<tr>
<td class="commandheader">name</td>
<td class="commandheader">text</td>
</tr> </tr>
<tr> <tr>
<td class="valueoption">adminonly</td> <td class="indent" colspan="2">
<td>(Incomplete) Can only be used by admins.</td> <p>File name of model file, without extension.</p>
</td>
</tr>
<tr>
<td class="commandheader">path</td>
<td class="commandheader">text</td>
</tr> </tr>
<tr> <tr>
<td class="valueoption">donator</td> <td class="indent" colspan="2">
<td>(Incomplete) Can only be used by donators.</td> <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>
<tr> <tr>
<td class="valueoption">hidden</td> <td class="indent" colspan="2">
<td>(Incomplete) Is not included in random selections.</td> <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>
<tr> <tr>
<td class="valueoption">motherzombie</td> <td class="indent" colspan="2">
<td>(Incomplete) Can only be used on mother zombies.</td> <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> </tr>
</table></blockquote> </table></blockquote>
<p>Example usages:</p> <p>For example usages see examples in default model configuration.</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>Put the list of models in:</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> <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 <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> <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>
<tr> <tr>
<td class="indent" colspan="3"> <td class="indent" colspan="3">
<p>The model file to use on the player, path is relative to the "cstrike" folder. There <p>Model file path to use on the player, relative to the "cstrike" folder. There are a
are a few special values supported by this attribute:</p> few presets supported by this attribute:</p>
<blockquote><table> <blockquote><table>
<tr> <tr>
<td class="valueoption">default</td> <td class="valueoption">default</td>
@ -1020,12 +1062,31 @@ the admin-only flag in the <span class="code">flags</span> attribute.</p>
</tr> </tr>
<tr> <tr>
<td class="valueoption">random</td> <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>
<tr> <tr>
<td class="valueoption">nochange</td> <td class="valueoption">random_public</td>
<td>Don't change model. To be used in combination with other plugins that <td>Selects a random public model for the current team.</td>
change model on players.</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> </tr>
</table></blockquote> </table></blockquote>
</td> </td>

View File

@ -54,6 +54,7 @@
// Header includes. // Header includes.
#include "zr/log.h" #include "zr/log.h"
#include "zr/models.h"
#if defined ADD_VERSION_INFO #if defined ADD_VERSION_INFO
#include "zr/hgversion.h" #include "zr/hgversion.h"
@ -79,10 +80,10 @@
#include "zr/paramtools" #include "zr/paramtools"
#include "zr/paramparser" #include "zr/paramparser"
#include "zr/shoppinglist" #include "zr/shoppinglist"
#include "zr/models"
#include "zr/downloads" #include "zr/downloads"
#include "zr/overlays" #include "zr/overlays"
#include "zr/playerclasses/playerclasses" #include "zr/playerclasses/playerclasses"
#include "zr/models"
#include "zr/weapons/weapons" #include "zr/weapons/weapons"
#include "zr/hitgroups" #include "zr/hitgroups"
#include "zr/roundstart" #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 * File: models.inc
* Type: Core * Type: Core
* Description: Model validation. * Description: Model manager.
* *
* Copyright (C) 2009 Greyscale, Richard Helgeby * 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. * Prepare all model/download data.
*/ */
ModelsLoad() ModelsLoad()
{ {
new Handle:kvModels = INVALID_HANDLE;
// Register config file. // Register config file.
ConfigRegisterConfig(File_Models, Structure_List, CONFIG_FILE_ALIAS_MODELS); ConfigRegisterConfig(File_Models, Structure_List, CONFIG_FILE_ALIAS_MODELS);
// Get models file path. // Get models file path.
decl String:pathmodels[PLATFORM_MAX_PATH]; decl String:modelPath[PLATFORM_MAX_PATH];
new bool:exists = ConfigGetCvarFilePath(CVAR_CONFIG_PATH_MODELS, pathmodels); new bool:exists = ConfigGetCvarFilePath(CVAR_CONFIG_PATH_MODELS, modelPath);
// If file doesn't exist, then log and stop. // If file doesn't exist, then log and stop.
if (!exists) if (!exists)
{ {
// Log failure and stop plugin. // 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. // Set the path to the config file.
ConfigSetConfigPath(File_Models, pathmodels); ConfigSetConfigPath(File_Models, modelPath);
// Load config from file and create array structure. // Prepare key/value structure.
new bool:success = ConfigLoadConfig(File_Models, arrayModels, PLATFORM_MAX_PATH); kvModels = CreateKeyValues(CONFIG_FILE_ALIAS_MODELS);
// Unexpected error, stop plugin. // Log what models file that is loaded.
if (!success) 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; decl String:buffer[256];
new modelpublicvalidcount; decl String:name[64];
new modelnonpublicvalidcount; decl String:path[PLATFORM_MAX_PATH];
new modelfilecount; decl String:team[64];
decl String:access[64];
decl String:group[64];
decl String:modelbase[PLATFORM_MAX_PATH]; ModelCount = 0;
decl String:modelpath[PLATFORM_MAX_PATH]; new failedCount;
decl String:modelname[MODELS_PATH_DIR_MAX_LENGTH]; new publicCount;
decl String:modelfile[MODELS_PATH_DIR_MAX_LENGTH];
decl String:modeldiskname[MODELS_PATH_DIR_MAX_LENGTH];
decl String:modelfullpath[PLATFORM_MAX_PATH];
new String:baseexploded[MODELS_PATH_MAX_DEPTH][MODELS_PATH_DIR_MAX_LENGTH]; // Loop through all models and store attributes in ModelData array.
do
new FileType:type;
new models = modelcount = GetArraySize(arrayModels);
// x = model array index.
for (new x = 0; x < models; x++)
{ {
// Get base model path, excluding the public/non-public setting. if (ModelCount > MODELS_MAX)
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)
{ {
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; continue;
} }
// Reset model file count. // Validate team.
modelfilecount = 0; if (ModelData[ModelCount][Model_Team] == ModelTeam_Invalid)
while (ReadDirEntry(modeldir, modelfile, sizeof(modelfile), type))
{ {
// 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) if (type != FileType_File)
{ {
continue; continue;
} }
// Find break point index in the string to get model name. // Find break point index in the string to get model name.
// Add one because it seems to break on the character before. // Add one to make space for null terminator.
new breakpoint = FindCharInString(modelfile, '.') + 1; new breakpoint = FindCharInString(file, '.') + 1;
strcopy(modeldiskname, breakpoint, modelfile); strcopy(fileShort, breakpoint, file);
// If this file doesn't match, then stop. // If this file doesn't match model name, then skip it.
if (!StrEqual(modelname, modeldiskname, false)) if (!StrEqual(name, fileShort, false))
{ {
continue; continue;
} }
// Format a full path string. // Format a full path string.
strcopy(modelfullpath, sizeof(modelfullpath), modelpath); strcopy(buffer, sizeof(buffer), path);
Format(modelfullpath, sizeof(modelfullpath), "%s/%s", modelfullpath, modelfile); Format(buffer, sizeof(buffer), "%s%s", buffer, file);
// Precache model file and add to downloads table. // Precache model file and add to downloads table.
PrecacheModel(modelfullpath); PrecacheModel(buffer);
AddFileToDownloadsTable(modelfullpath); AddFileToDownloadsTable(buffer);
// Increment modelfilecount
modelfilecount++;
} }
CloseHandle(modeldir); CloseHandle(dir);
// Increment variable if model files are valid. ModelCount++;
if (modelfilecount) } while (KvGotoNextKey(kvModels));
CloseHandle(kvModels);
// Check if there are no public models.
if (!publicCount)
{ {
// Increment proper variable. 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);
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);
}
} }
// Log model validation info. // 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)); LogEvent(false, LogType_Normal, LOG_CORE_EVENTS, LogModule_Models, "Config Validation", "Successful: %d | Unsuccessful: %d", ModelCount, failedCount);
// 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);
}
// Set config data. // Set config data.
ConfigSetConfigLoaded(File_Models, true); ConfigSetConfigLoaded(File_Models, true);
ConfigSetConfigReloadFunc(File_Models, GetFunctionByName(GetMyHandle(), "ModelsOnConfigReload")); ConfigSetConfigReloadFunc(File_Models, GetFunctionByName(GetMyHandle(), "ModelsOnConfigReload"));
ConfigSetConfigHandle(File_Models, arrayModels);
} }
/** /**
@ -204,120 +239,336 @@ public ModelsOnConfigReload(ConfigFile:config)
} }
/** /**
* Checks if a model is public. * Returns a random model index according to the specified filter settings.
* *
* @param modelindex The array index of the model to check. * @param client Optional. Client index used to check for
* @return True if public, false if not. * 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 modelIndexes[MODELS_MAX];
decl String:modelsetting[PLATFORM_MAX_PATH + 16]; new listCount;
GetArrayString(arrayModels, modelindex, modelsetting, sizeof(modelsetting));
// We define this to use as little memory as possible, because the value won't be used. // Loop through all models.
decl String:modelpath[1]; for (new index = 0; index < ModelCount; index++)
decl String:strpublic[32];
if (StrContains(modelsetting, ";") > -1)
{ {
// Get string index of where the public setting starts. // Check team filtering. Skip if no match.
new strindex = SplitString(modelsetting, ";", modelpath, sizeof(modelpath)); if (teamFilter != ModelTeam_Invalid &&
ModelsGetTeam(index) != teamFilter)
// Copy setting to new string {
strcopy(strpublic, sizeof(strpublic), modelsetting[strindex]); continue;
// Trim the whitespace.
TrimString(strpublic);
// If public, return true, non-public returns false.
return StrEqual(strpublic, "public", false);
} }
// If nothing is specified, assume public. // Cache current model access flag.
return true; new ModelAccess:access = ModelsGetAccess(index);
} new accessFlag = ModelsGetAccessFlag(access);
/** // Check access filtering. Skip if no match.
* Returns the path of a given model index. if (accessRequireFlags > 0 &&
* !(accessRequireFlags & accessFlag))
* @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); continue;
} }
// Trim whitespace. // Do client group authentication if client is specified.
TrimString(modelpath); if (client > 0)
}
/**
* 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. // Check if current model use group authentication.
modelindex = GetRandomInt(0, GetArraySize(arrayModels) - 1); if (access == ModelAccess_Group)
{
decl String:group[64];
ModelsGetGroup(index, group, sizeof(group));
if (!ZRIsClientInGroup(client, group))
{
// Client not authorized to use this model.
continue;
}
} }
else else
{ {
new Handle:modelsarray = CreateArray(PLATFORM_MAX_PATH); // No group authentication. Do regular authentication if model
decl String:modelsetting[PLATFORM_MAX_PATH]; // is a admin model.
if (access == ModelAccess_Admins &&
// x = Array index. !ZRIsClientAdmin(client))
new size = GetArraySize(arrayModels);
for (new x = 0; x < size; x++)
{ {
if (publicmodels == ModelIsPublic(x)) // Client not authorized to use this model.
continue;
}
}
}
// Model passed filter tests. Add to list.
modelIndexes[listCount] = index;
listCount++;
}
// Check if any models passed the filter.
if (listCount)
{ {
// Transfer model to temp array. return modelIndexes[GetRandomInt(0, listCount - 1)];
GetArrayString(arrayModels, x, modelsetting, sizeof(modelsetting));
PushArrayString(modelsarray, modelsetting);
} }
} else
// 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, ""); return -1;
// 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); /**
* Validates the specified index according to maximum number of models, and
// Destroy the handle. * number of models in use. Unused indexes will fail validation by default.
CloseHandle(modelsarray); *
} * @param index Model index to validate.
* @param rangeOnly Optional. Do not check if the index is in use. Default
// Get the path to the selected model. * is false, check if in use.
ModelReturnPath(modelindex, modelpath, maxlen); * @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) bool:ClassApplyModel(client, classindex, cachetype = ZR_CLASS_CACHE_PLAYER)
{ {
new bool:isAttributePreset = false;
decl String:modelpath[PLATFORM_MAX_PATH]; 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) if (cachetype == ZR_CLASS_CACHE_PLAYER)
{ {
ClassGetModelPath(client, modelpath, sizeof(modelpath), cachetype); index = client;
} }
else 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)) if (StrEqual(modelpath, "random", false))
{ {
// TODO: Make a function that gets a random model from the specified team. // Set access filter flags.
ModelsGetRandomModelIndex(modelpath, sizeof(modelpath), false, true); access = MODEL_ACCESS_PUBLIC | MODEL_ACCESS_ADMINS;
Format(modelpath, sizeof(modelpath), "%s.mdl", modelpath);
// 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)) else if (StrEqual(modelpath, "default", false))
{ {
@ -112,12 +152,33 @@ bool:ClassApplyModel(client, classindex, cachetype = ZR_CLASS_CACHE_PLAYER)
return true; return true;
} }
} }
else if (StrEqual(modelpath, "nochange", false)) else if (StrEqual(modelpath, "no_change", false))
{ {
// Do nothing. // Do nothing.
return true; 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); SetEntityModel(client, modelpath);
return true; return true;
} }

View File

@ -162,8 +162,12 @@ stock ClassValidateAttributes(classindex)
} }
else 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) && 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, "default", false) &&
!StrEqual(model_path, "nochange", false)) !StrEqual(model_path, "nochange", false))
{ {
@ -705,8 +709,8 @@ stock bool:ClassFilterMatch(index, filter[ClassFilter], cachetype = ZR_CLASS_CAC
stock bool:ClassFlagFilterMatch(index, require, deny, cachetype) stock bool:ClassFlagFilterMatch(index, require, deny, cachetype)
{ {
new flags; new flags;
new bool:requirepassed; new bool:requirepassed = false;
new bool:denypassed; new bool:denypassed = false;
// Do quick check for optimization reasons: Check if no flags are specified. // Do quick check for optimization reasons: Check if no flags are specified.
if (require == 0 && deny == 0) if (require == 0 && deny == 0)
@ -723,11 +727,6 @@ stock bool:ClassFlagFilterMatch(index, require, deny, cachetype)
// All required flags are set. // All required flags are set.
requirepassed = true; requirepassed = true;
} }
else
{
// Not all required flags are set.
requirepassed = false;
}
// Match deny filter. // Match deny filter.
if (deny == 0 || !(flags & deny)) if (deny == 0 || !(flags & deny))
@ -735,11 +734,6 @@ stock bool:ClassFlagFilterMatch(index, require, deny, cachetype)
// No denied flags are set. // No denied flags are set.
denypassed = true; denypassed = true;
} }
else
{
// It has denied flags set.
denypassed = false;
}
// Check if required and denied flags passed the filter. // Check if required and denied flags passed the filter.
if (requirepassed && denypassed) if (requirepassed && denypassed)
@ -747,11 +741,9 @@ stock bool:ClassFlagFilterMatch(index, require, deny, cachetype)
// The class pass the filter. // The class pass the filter.
return true; return true;
} }
else
{
// The class didn't pass the filter. // The class didn't pass the filter.
return false; return false;
}
} }
/** /**

View File

@ -376,11 +376,6 @@ new ClassNoFilter[ClassFilter];
*/ */
new ClassNoSpecialClasses[ClassFilter] = {false, 0, ZR_CLASS_SPECIALFLAGS, -1}; 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. * The original class data. This array only changed when class data is loaded.
* ZR_CLASS_CACHE_ORIGINAL is the cache type to this array. * ZR_CLASS_CACHE_ORIGINAL is the cache type to this array.
@ -413,7 +408,7 @@ new Float:ClassMultiplierCache[ZR_CLASS_TEAMCOUNT][ClassMultipliers];
new ClassCount; 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. * Used to block events that happend before the module is done loading.
*/ */
new bool:ClassValidated; new bool:ClassValidated;
@ -485,11 +480,9 @@ ClassLoad()
// Register config file. // Register config file.
ConfigRegisterConfig(File_Classes, Structure_Keyvalue, CONFIG_FILE_ALIAS_CLASSES); ConfigRegisterConfig(File_Classes, Structure_Keyvalue, CONFIG_FILE_ALIAS_CLASSES);
new Handle:kvClassData;
// Make sure kvClassData is ready to use. // Make sure kvClassData is ready to use.
if (kvClassData != INVALID_HANDLE)
{
CloseHandle(kvClassData);
}
kvClassData = CreateKeyValues(CONFIG_FILE_ALIAS_CLASSES); kvClassData = CreateKeyValues(CONFIG_FILE_ALIAS_CLASSES);
// Get weapons config path. // Get weapons config path.