Merged heads.

This commit is contained in:
Greyscale 2009-05-11 04:47:56 +02:00
commit 67ec1f8433
15 changed files with 907 additions and 147 deletions

View File

@ -36,8 +36,8 @@
// kill_bonus number How many points to give per kill. Humans only.
// speed decimal The player speed.
// knockback decimal Force of the knockback when shot at. Zombies only.
// jump_height decimal Extra upwards jump boost.
// jump_distance decimal Extra forwards jump boost.
// jump_height decimal Extra upwards jump boost in units. 0.0 for no extra boost.
// jump_distance decimal Extra forwards jump boost multiplier. 0.2 is normal distance.
"classes"
{
@ -75,16 +75,16 @@
"immunity_amount" "0.0"
"no_fall_damage" "1"
"health" "3000"
"health" "2500"
"health_regen_interval" "0.0"
"health_regen_amount" "0"
"health_infect_gain" "800"
"health_infect_gain" "700"
"kill_bonus" "2"
"speed" "350"
"knockback" "3"
"jump_height" "40.0"
"jump_distance" "1.5"
"knockback" "4"
"jump_height" "10.0"
"jump_distance" "0.3"
}
"fast"
@ -123,9 +123,9 @@
"kill_bonus" "2"
"speed" "380"
"knockback" "3.5"
"jump_height" "60.0"
"jump_distance" "2.0"
"knockback" "4.5"
"jump_height" "30.0"
"jump_distance" "0.4"
}
"mutated"
@ -157,16 +157,16 @@
"immunity_amount" "0.0"
"no_fall_damage" "1"
"health" "5000"
"health" "3500"
"health_regen_interval" "0.0"
"health_regen_amount" "0"
"health_infect_gain" "1000"
"health_infect_gain" "850"
"kill_bonus" "2"
"speed" "275"
"knockback" "3.5"
"jump_height" "40.0"
"jump_distance" "1.3"
"jump_height" "20.0"
"jump_distance" "0.4"
}
"heavy"
@ -198,16 +198,16 @@
"immunity_amount" "0.0"
"no_fall_damage" "1"
"health" "5000"
"health" "4000"
"health_regen_interval" "0.0"
"health_regen_amount" "0"
"health_infect_gain" "1000"
"kill_bonus" "2"
"speed" "280"
"knockback" "2.0"
"knockback" "2.5"
"jump_height" "0.0"
"jump_distance" "0.8"
"jump_distance" "0.2"
}
// ------------------------------------------
@ -254,7 +254,7 @@
"speed" "300"
"knockback" "0"
"jump_height" "0.0"
"jump_distance" "1.0"
"jump_distance" "0.2"
}
"human_speedy"
@ -295,7 +295,7 @@
"speed" "380"
"knockback" "0"
"jump_height" "0.0"
"jump_distance" "1.0"
"jump_distance" "0.2"
}
"human_light"
@ -335,7 +335,7 @@
"speed" "300"
"knockback" "0"
"jump_height" "64.0"
"jump_distance" "2.0"
"jump_height" "30.0"
"jump_distance" "0.4"
}
}

View File

@ -200,6 +200,9 @@ public OnConfigsExecuted()
VEffectsLoad();
SEffectsLoad();
ClassLoad();
ConfigOnModulesLoaded();
ClassOnModulesLoaded();
}
/**

View File

@ -26,7 +26,9 @@ CreateCommands()
RegAdminCmd("zr_anticamp_list", Command_AnticampList, ADMFLAG_GENERIC, "List current volumes.");
RegConsoleCmd("zr_log_flags", Command_LogFlags, "List available logging flags.");
RegConsoleCmd("zr_class_dump", Command_ClassDump, "Dumps class data at a specified index in the specified cache. Usage: zr_class_dump <cachetype> <index|targetname>");
RegAdminCmd("zr_class_modify", Command_ClassModify, ADMFLAG_GENERIC, "Modify class data on one or more classes. Usage: zr_class_modify <classname|\"zombies\"|\"humans\"|\"admins\"> <attribute> <value> [is_multiplier]");
}
public Action:Command_Infect(client, argc)
@ -229,60 +231,6 @@ public Action:Command_Unrestrict(client, argc)
return Plugin_Handled;
}
/*public Action:Command_SetClassKnockback(client, argc)
{
if (argc < 2)
{
ReplyToCommand(client, "Sets the specified class knockback. Usage: zr_set_class_knockback <classname> <knockback>");
return Plugin_Handled;
}
decl String:classname[64];
decl String:knockback_arg[8];
new classindex;
new Float:knockback;
GetCmdArg(1, classname, sizeof(classname));
GetCmdArg(2, knockback_arg, sizeof(knockback_arg));
classindex = GetClassIndex(classname);
knockback = StringToFloat(knockback_arg);
if (classindex < 0)
{
ReplyToCommand(client, "Could not find the class %s.", classname);
return Plugin_Handled;
}
arrayClasses[classindex][data_knockback] = knockback;
return Plugin_Handled;
}
public Action:Command_GetClassKnockback(client, argc)
{
if (argc < 1)
{
ReplyToCommand(client, "Gets the specified class knockback. Usage: zr_get_class_knockback <classname>");
return Plugin_Handled;
}
decl String:classname[64];
new classindex;
new Float:knockback;
GetCmdArg(1, classname, sizeof(classname));
classindex = GetClassIndex(classname);
if (classindex < 0)
{
ReplyToCommand(client, "Could not find the class %s.", classname);
return Plugin_Handled;
}
knockback = arrayClasses[classindex][data_knockback];
ReplyToCommand(client, "Current knockback for %s: %f", classname, knockback);
return Plugin_Handled;
}*/
public Action:Command_AdminMenu(client, argc)
{

View File

@ -41,6 +41,9 @@
*/
#define CONFIG_OPTION_MAX_LENGTH 32
/**
* Actions to use when working on key/values.
*/
enum ConfigKeyvalueAction
{
Create, /** Create a key. */
@ -48,6 +51,9 @@ enum ConfigKeyvalueAction
Set, /** Modify setting of a key. */
Get, /** Get setting of a key. */
}
/**
* @endsection
*/
/**
* @section Global data handle initializations.
@ -57,6 +63,7 @@ new Handle:kvClassData = INVALID_HANDLE;
new Handle:kvWeapons = INVALID_HANDLE;
new Handle:kvWeaponGroups = INVALID_HANDLE;
new Handle:kvHitgroups = INVALID_HANDLE;
/**
* Load plugin configs.
*/
@ -88,6 +95,48 @@ ConfigLoad()
}
}
/**
* Executed when modules are done loading. After all init calls in
* OnConfigsExecuted.
*
* Executes post map configs if they exist.
*/
ConfigOnModulesLoaded()
{
decl String:mapname[256];
decl String:mapconfig[PLATFORM_MAX_PATH];
decl String:path[PLATFORM_MAX_PATH];
new bool:cfgexists;
// Get map name and format into config path.
GetCurrentMap(mapname, sizeof(mapname));
Format(mapconfig, sizeof(mapconfig), "sourcemod/zombiereloaded/%s.post.cfg", mapname);
// Prepend cfg to path.
Format(path, sizeof(path), "cfg/%s", mapconfig);
// Workaround for bug 3083 in SourceMod compiler. Having FileExist directly
// in the if in this function makes it crash. Storing the result in a
// boolean first works.
// Check if the file exist.
cfgexists = FileExists(path);
if (!cfgexists)
{
// File doesn't exist, then stop.
return;
}
// Execute config file.
ServerCommand("exec %s", mapconfig);
// Log action.
if (LogCheckFlag(LOG_CORE_EVENTS))
{
LogMessageFormatted(-1, "", "", "Executed post map config file: %s.", LOG_FORMAT_TYPE_SIMPLE, mapconfig);
}
}
/**
* Load config file.
*
@ -106,6 +155,7 @@ bool:ConfigGetFilePath(CvarsList:cvar, String:path[])
return FileExists(path);
}
/**
* Creates, deletes, sets, or gets any key/setting of any ZR config keyvalue file in memory.
* Only use when interacting with a command or manipulating single keys/values,

View File

@ -28,13 +28,13 @@ JumpBoostOnClientJump(client)
// Apply jump values.
vecVelocity[0] *= distance;
vecVelocity[1] *= distance;
vecVelocity[2] = height;
vecVelocity[2] += height;
JumpBoostSetClientVelocity(client, vecVelocity);
}
/**
* Apply jump boost force on client. (Special method separate from ToolsClientVelocity)
* Set new velocity on client. (Special method separate from ToolsClientVelocity)
*
* @param client The client index.
* @param vecVelocity Velocity to set on client.

View File

@ -92,6 +92,7 @@ LogInit()
* LOG_FORMAT_TYPE_SIMPLE - Simple, no module or block info.
* LOG_FORMAT_TYPE_FULL - Full, with module and block info, printed in normal log.
* LOG_FORMAT_TYPE_ERROR - Full, printed in error log.
* LOG_FORMAT_TYPE_FATALERROR - Full, stops the plugin.
* @param any... Formatting parameters.
*/
LogMessageFormatted(client, const String:module[], const String:block[], const String:message[], type = LOG_FORMAT_TYPE_FULL, any:...)

View File

@ -24,7 +24,7 @@ MenuMain(client)
SetGlobalTransTarget(client);
// Set menu title.
SetMenuTitle(menu_main, "%t\n ", "!zmenu title");
SetMenuTitle(menu_main, "%t\n ", "Menu main title");
// Initialize menu lines.
decl String:zadmin[64];

View File

@ -40,6 +40,9 @@ ModelsLoad()
*/
ModelsPrepModels()
{
// Initialize log boolean.
new bool:enablelog = LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE);
// Get models file path.
decl String:pathmodels[PLATFORM_MAX_PATH];
new bool:exists = ConfigGetFilePath(CVAR_CONFIG_PATH_MODELS, pathmodels);
@ -48,10 +51,7 @@ ModelsPrepModels()
if (!exists)
{
// Log failure and stop plugin.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE))
{
LogMessageFormatted(-1, "Models", "Config Validation", "Missing models file: %s", LOG_FORMAT_TYPE_FATALERROR, pathmodels);
}
LogMessageFormatted(-1, "Models", "Config Validation", "Fatal error: Missing models file: \"%s\"", LOG_FORMAT_TYPE_FATALERROR, pathmodels);
}
// If model array exists, then destroy it.
@ -65,7 +65,7 @@ ModelsPrepModels()
// If array couldn't be created, then fail.
if (arrayModelsList == INVALID_HANDLE)
{
LogMessageFormatted(-1, "Models", "Config Validation", "Error parsing %s", LOG_FORMAT_TYPE_FATALERROR, pathmodels);
LogMessageFormatted(-1, "Models", "Config Validation", "Fatal error: Error parsing \"%s\"", LOG_FORMAT_TYPE_FATALERROR, pathmodels);
}
new modelcount;
@ -155,7 +155,7 @@ ModelsPrepModels()
x--;
// Log missing model files.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE))
if (enablelog)
{
LogMessageFormatted(-1, "Models", "Config Validation", "Missing model files on server (%s)", LOG_FORMAT_TYPE_ERROR, modelbase);
}
@ -163,12 +163,15 @@ ModelsPrepModels()
}
// Log model validation info.
if (enablelog)
{
LogMessageFormatted(-1, "Models", "Config Validation", "Total: %d | Successful: %d | Unsuccessful: %d", LOG_FORMAT_TYPE_FULL, modelcount, modelvalidcount, modelcount - modelvalidcount);
}
// If none of the model paths are valid, then log and fail.
if (!modelvalidcount)
{
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE))
if (enablelog)
{
LogMessageFormatted(-1, "Models", "Config Validation", "No usable model paths in %s", LOG_FORMAT_TYPE_FATALERROR, pathmodels);
}
@ -180,6 +183,9 @@ ModelsPrepModels()
*/
ModelsPrepDownloads()
{
// Initialize log boolean.
new bool:enablelog = LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE);
// Get downloads file path.
decl String:pathdownloads[PLATFORM_MAX_PATH];
new bool:exists = ConfigGetFilePath(CVAR_CONFIG_PATH_DOWNLOADS, pathdownloads);
@ -188,9 +194,9 @@ ModelsPrepDownloads()
if (!exists)
{
// Log error, then stop.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE))
if (enablelog)
{
LogMessageFormatted(-1, "Downloads", "Config Validation", "Missing downloads file: %s", LOG_FORMAT_TYPE_ERROR, pathdownloads);
LogMessageFormatted(-1, "Downloads", "Config Validation", "Missing downloads file: \"%s\"", LOG_FORMAT_TYPE_ERROR, pathdownloads);
}
return;
@ -201,9 +207,9 @@ ModelsPrepDownloads()
// If array couldn't be created, then fail.
if (arrayModelsList == INVALID_HANDLE)
{
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE))
if (enablelog)
{
LogMessageFormatted(-1, "Downloads", "Config Validation", "Error parsing %s", LOG_FORMAT_TYPE_ERROR, pathdownloads);
LogMessageFormatted(-1, "Downloads", "Config Validation", "Error parsing \"%s\"", LOG_FORMAT_TYPE_ERROR, pathdownloads);
}
}
@ -232,9 +238,9 @@ ModelsPrepDownloads()
// Backtrack one index, because we deleted it out from under the loop.
x--;
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CORE))
if (enablelog)
{
LogMessageFormatted(-1, "Downloads", "Config Validation", "Missing file (%s)", LOG_FORMAT_TYPE_ERROR, downloadpath);
LogMessageFormatted(-1, "Downloads", "Config Validation", "Missing file \"%s\"", LOG_FORMAT_TYPE_ERROR, downloadpath);
}
continue;
@ -248,5 +254,8 @@ ModelsPrepDownloads()
}
// Log model validation info.
if (enablelog)
{
LogMessageFormatted(-1, "Downloads", "Config Validation", "Total: %d | Successful: %d | Unsuccessful: %d", LOG_FORMAT_TYPE_FULL, downloadcount, downloadvalidcount, downloadcount - downloadvalidcount);
}
}

View File

@ -875,3 +875,177 @@ Float:ClassGetJumpDistance(index, cachetype = ZR_CLASS_CACHE_PLAYER)
}
return -1.0;
}
/**
* Gets the attribute flag that represent the specified attribute.
*
* @param attributename The attribute name.
* @return The flag that reporesent the specified attribute.
* -1 on error.
*/
ClassAttributeNameToFlag(const String:attributename[])
{
// Check attribute names.
if (StrEqual(attributename, "enabled", false))
{
return ZR_CLASS_FLAG_ENABLED;
}
else if (StrEqual(attributename, "team", false))
{
return ZR_CLASS_FLAG_TEAM;
}
else if (StrEqual(attributename, "team_default", false))
{
return ZR_CLASS_FLAG_TEAM_DEFAULT;
}
else if (StrEqual(attributename, "name", false))
{
return ZR_CLASS_FLAG_NAME;
}
else if (StrEqual(attributename, "description", false))
{
return ZR_CLASS_FLAG_DESCRIPTION;
}
else if (StrEqual(attributename, "model_path", false))
{
return ZR_CLASS_FLAG_MODEL_PATH;
}
else if (StrEqual(attributename, "alpha_initial", false))
{
return ZR_CLASS_FLAG_ALPHA_INITIAL;
}
else if (StrEqual(attributename, "alpha_damaged", false))
{
return ZR_CLASS_FLAG_ALPHA_DAMAGED;
}
else if (StrEqual(attributename, "alpha_damage", false))
{
return ZR_CLASS_FLAG_ALPHA_DAMAGE;
}
else if (StrEqual(attributename, "overlay_path", false))
{
return ZR_CLASS_FLAG_OVERLAY_PATH;
}
else if (StrEqual(attributename, "nvgs", false))
{
return ZR_CLASS_FLAG_NVGS;
}
else if (StrEqual(attributename, "fov", false))
{
return ZR_CLASS_FLAG_FOV;
}
else if (StrEqual(attributename, "napalm_time", false))
{
return ZR_CLASS_FLAG_NAPALM_TIME;
}
else if (StrEqual(attributename, "immunity_mode", false))
{
return ZR_CLASS_FLAG_IMMUNITY_MODE;
}
else if (StrEqual(attributename, "immunity_amount", false))
{
return ZR_CLASS_FLAG_IMMUNITY_AMOUNT;
}
else if (StrEqual(attributename, "no_fall_damage", false))
{
return ZR_CLASS_FLAG_NO_FALL_DAMAGE;
}
else if (StrEqual(attributename, "health", false))
{
return ZR_CLASS_FLAG_HEALTH;
}
else if (StrEqual(attributename, "health_regen_interval", false))
{
return ZR_CLASS_FLAG_HEALTH_REGEN_INTERVAL;
}
else if (StrEqual(attributename, "health_regen_amount", false))
{
return ZR_CLASS_FLAG_HEALTH_REGEN_AMOUNT;
}
else if (StrEqual(attributename, "health_infect_gain", false))
{
return ZR_CLASS_FLAG_HEALTH_INFECT_GAIN;
}
else if (StrEqual(attributename, "kill_bonus", false))
{
return ZR_CLASS_FLAG_KILL_BONUS;
}
else if (StrEqual(attributename, "speed", false))
{
return ZR_CLASS_FLAG_SPEED;
}
else if (StrEqual(attributename, "knockback", false))
{
return ZR_CLASS_FLAG_KNOCKBACK;
}
else if (StrEqual(attributename, "jump_height", false))
{
return ZR_CLASS_FLAG_JUMP_HEIGHT;
}
else if (StrEqual(attributename, "jump_distance", false))
{
return ZR_CLASS_FLAG_JUMP_DISTANCE;
}
// Invalid attribute name.
return -1;
}
/**
* Returns the datatype used in the specified attribute.
*
* @param attributeflag A flag specifying the attribute to check.
* @return The data type used in the specified attribute, or
* ClassType_InvalidType if failed.
*/
ClassDataTypes:ClassGetAttributeType(attributeflag)
{
switch (attributeflag)
{
// Boolean.
case ZR_CLASS_FLAG_ENABLED,
ZR_CLASS_FLAG_NVGS,
ZR_CLASS_FLAG_NO_FALL_DAMAGE:
{
return ClassDataType_Boolean;
}
// Integer.
case ZR_CLASS_FLAG_ALPHA_INITIAL,
ZR_CLASS_FLAG_ALPHA_DAMAGED,
ZR_CLASS_FLAG_ALPHA_DAMAGE,
ZR_CLASS_FLAG_FOV,
ZR_CLASS_FLAG_IMMUNITY_MODE,
ZR_CLASS_FLAG_HEALTH,
ZR_CLASS_FLAG_HEALTH_REGEN_AMOUNT,
ZR_CLASS_FLAG_HEALTH_INFECT_GAIN,
ZR_CLASS_FLAG_KILL_BONUS:
{
return ClassDataType_Integer;
}
// Float.
case ZR_CLASS_FLAG_NAPALM_TIME,
ZR_CLASS_FLAG_IMMUNITY_AMOUNT,
ZR_CLASS_FLAG_HEALTH_REGEN_INTERVAL,
ZR_CLASS_FLAG_SPEED,
ZR_CLASS_FLAG_KNOCKBACK,
ZR_CLASS_FLAG_JUMP_HEIGHT,
ZR_CLASS_FLAG_JUMP_DISTANCE:
{
return ClassDataType_Float;
}
// String.
case ZR_CLASS_FLAG_NAME,
ZR_CLASS_FLAG_DESCRIPTION,
ZR_CLASS_FLAG_MODEL_PATH,
ZR_CLASS_FLAG_OVERLAY_PATH:
{
return ClassDataType_String;
}
}
// Invalid flag or multiple flags combined.
return ClassDataType_InvalidType;
}

View File

@ -97,3 +97,499 @@ public Action:Command_ClassDump(client, argc)
return Plugin_Handled;
}
/**
* Modifies class data on one or more classes.
*
* Syntax: zr_class_modify <class> <attribute> <value> [is_multiplier]
*
* class: The class to modify. Can be any class name, or one of the
* following team names; "all", "humans", "zombies" or
* "admins".
* attribute: The name of the class attribute.
* value: Value to set. Use quotes if value is a string.
* is_multiplier: Optional. specifies wether the original value should be
* multiplied by the specified value. Defaults to false.
*
* Note: Original values are retrieved from the original class cache, not the
* modified class cache.
*/
public Action:Command_ClassModify(client, argc)
{
decl String:syntax[1024];
syntax[0] = 0;
if (argc < 3)
{
// Write syntax info.
StrCat(syntax, sizeof(syntax), "Modifies class data on one or more classes. Usage: zr_class_modify <class> <attribute> <value> [is_multiplier]\n\n");
StrCat(syntax, sizeof(syntax), "class: The class to modify. Can be any class name, or one of the following team names; all, humans, zombies or admins.\n");
StrCat(syntax, sizeof(syntax), "attribute: The name of the class attribute.\n");
StrCat(syntax, sizeof(syntax), "value: Value to set. Use quotes if value is a string.\n");
StrCat(syntax, sizeof(syntax), "is_multiplier: Optional. specifies wether the original value should be multiplied by the specified value. Not all attributes support multiplying. Defaults to false.\n\n");
StrCat(syntax, sizeof(syntax), "Note: Original values are retrieved from the original class cache, not the modified class cache.");
ReplyToCommand(client, syntax);
return Plugin_Handled;
}
decl String:classname[64];
decl String:attributename[128];
decl String:value[256];
decl String:ismultiplier[4];
new attributeflag;
new ClassDataTypes:attributetype;
new bool:isgroup;
new bool:hasmultiplier;
new Handle:classlist;
new classindex;
new bool:listresult;
classlist = CreateArray();
// Get command arguments.
GetCmdArg(1, classname, sizeof(classname));
GetCmdArg(2, attributename, sizeof(attributename));
GetCmdArg(3, value, sizeof(value));
// Get last command argument if specified.
if (argc == 4)
{
GetCmdArg(4, ismultiplier, sizeof(ismultiplier));
if (StringToInt(ismultiplier))
{
hasmultiplier = true;
}
}
// Get attribute flag.
attributeflag = ClassAttributeNameToFlag(attributename);
// Validate attribute flag.
if (attributeflag < 0)
{
ReplyToCommand(client, "Invalid class attribute specified.");
return Plugin_Handled;
}
// Get attribute data type.
attributetype = ClassGetAttributeType(attributeflag);
// Check if classname is a group. Add classes to the class list
// and use the specified team filter.
if (StrEqual(classname, "all", false))
{
listresult = ClassAddToArray(classlist);
isgroup = true;
}
else if (StrEqual(classname, "humans", false))
{
listresult = ClassAddToArray(classlist, ZR_CLASS_TEAM_HUMANS);
isgroup = true;
}
else if (StrEqual(classname, "zombies", false))
{
listresult = ClassAddToArray(classlist, ZR_CLASS_TEAM_ZOMBIES);
isgroup = true;
}
else if (StrEqual(classname, "admins", false))
{
listresult = ClassAddToArray(classlist, ZR_CLASS_TEAM_ADMINS);
isgroup = true;
}
// Check if classname is a group.
if (isgroup)
{
// Check if the list is valid.
if (!listresult)
{
ReplyToCommand(client, "Failed to get classes in the specified team: \"%s\".", classname);
return Plugin_Handled;
}
// Loop through all classes in the list.
new listsize = GetArraySize(classlist);
for (new i = 0; i < listsize; i++)
{
classindex = GetArrayCell(classlist, i);
switch (attributetype)
{
case ClassDataType_Boolean:
{
if (!ClassModifyBoolean(classindex, attributeflag, bool:StringToInt(value)))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
case ClassDataType_Integer:
{
if (hasmultiplier)
{
if (!ClassModifyInteger(classindex, attributeflag, StringToInt(value), StringToFloat(value)))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
else
{
if (!ClassModifyInteger(classindex, attributeflag, StringToInt(value)))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
}
case ClassDataType_Float:
{
if (!ClassModifyFloat(classindex, attributeflag, StringToFloat(value), hasmultiplier))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
case ClassDataType_String:
{
if (!ClassModifyString(classindex, attributeflag, value))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
}
}
}
else
{
// It's a single class.
classindex = ClassGetIndex(classname);
// Validate classindex.
if (!ClassValidateIndex(classindex))
{
ReplyToCommand(client, "Invalid class name specified.");
return Plugin_Handled;
}
switch (attributetype)
{
case ClassDataType_Boolean:
{
if (!ClassModifyBoolean(classindex, attributeflag, bool:StringToInt(value)))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
case ClassDataType_Integer:
{
if (hasmultiplier)
{
if (!ClassModifyInteger(classindex, attributeflag, StringToInt(value), StringToFloat(value)))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
else
{
if (!ClassModifyInteger(classindex, attributeflag, StringToInt(value)))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
}
case ClassDataType_Float:
{
if (!ClassModifyFloat(classindex, attributeflag, StringToFloat(value)), hasmultiplier)
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
case ClassDataType_String:
{
if (!ClassModifyString(classindex, attributeflag, value))
{
ReplyToCommand(client, "Failed to set \"%s\" to \"%s\" in class \"%d\".", attributename, value, classindex);
}
}
}
}
return Plugin_Handled;
}
/**
* Modify class boolean attribute on a class.
*
* @param classindex The class index.
* @param attributeflag Attribute to modify (a single attribute flag).
* @param value New value to set.
* @return True on success, false otherwise.
*/
bool:ClassModifyBoolean(classindex, attributeflag, bool:value)
{
// Validate class index.
if (!ClassValidateIndex(classindex))
{
return false;
}
switch (attributeflag)
{
case ZR_CLASS_FLAG_ENABLED:
{
ClassDataCache[classindex][class_enabled] = bool:value;
return true;
}
case ZR_CLASS_FLAG_NVGS:
{
ClassDataCache[classindex][class_nvgs] = bool:value;
return true;
}
case ZR_CLASS_FLAG_NO_FALL_DAMAGE:
{
ClassDataCache[classindex][class_no_fall_damage] = bool:value;
return true;
}
}
// Invalid flag or multiple flags combined.
return false;
}
/**
* Modify class integer attribute on a class.
*
* @param classindex The class index.
* @param attributeflag Attribute to modify (a single attribute flag).
* @param value New value to set, or multiply with.
* @param multiplier Optional. Use a multiplier instead of the value,
* that multiplies with the original class value.
* Not all attributes support multipliers. 0.0 to
* disable. Value is ignored if this is non-zero.
* @return True on success, false otherwise.
*/
ClassModifyInteger(classindex, attributeflag, value, Float:multiplier = 0.0)
{
// Validate class index.
if (!ClassValidateIndex(classindex))
{
return false;
}
// Check if multiplier is specified.
new bool:ismultiplier = (multiplier != 0.0) ? true : false;
switch (attributeflag)
{
case ZR_CLASS_FLAG_ALPHA_INITIAL:
{
if (ismultiplier)
{
value = RoundToNearest(float(ClassData[classindex][class_alpha_initial]) * multiplier);
}
ClassDataCache[classindex][class_alpha_initial] = value;
return true;
}
case ZR_CLASS_FLAG_ALPHA_DAMAGED:
{
if (ismultiplier)
{
value = RoundToNearest(float(ClassData[classindex][class_alpha_damaged]) * multiplier);
}
ClassDataCache[classindex][class_alpha_damaged] = value;
return true;
}
case ZR_CLASS_FLAG_ALPHA_DAMAGE:
{
if (ismultiplier)
{
value = RoundToNearest(float(ClassData[classindex][class_alpha_damage]) * multiplier);
}
ClassDataCache[classindex][class_alpha_damage] = value;
return true;
}
case ZR_CLASS_FLAG_FOV:
{
ClassDataCache[classindex][class_fov] = value;
return true;
}
case ZR_CLASS_FLAG_IMMUNITY_MODE:
{
ClassDataCache[classindex][class_fov] = value;
return true;
}
case ZR_CLASS_FLAG_HEALTH:
{
if (ismultiplier)
{
value = RoundToNearest(float(ClassData[classindex][class_health]) * multiplier);
}
ClassDataCache[classindex][class_health] = value;
return true;
}
case ZR_CLASS_FLAG_HEALTH_REGEN_AMOUNT:
{
if (ismultiplier)
{
value = RoundToNearest(float(ClassData[classindex][class_health_regen_amount]) * multiplier);
}
ClassDataCache[classindex][class_health_regen_amount] = value;
return true;
}
case ZR_CLASS_FLAG_HEALTH_INFECT_GAIN:
{
if (ismultiplier)
{
value = RoundToNearest(float(ClassData[classindex][class_health_infect_gain]) * multiplier);
}
ClassDataCache[classindex][class_health_infect_gain] = value;
return true;
}
case ZR_CLASS_FLAG_KILL_BONUS:
{
if (ismultiplier)
{
value = RoundToNearest(float(ClassData[classindex][class_kill_bonus]) * multiplier);
}
ClassDataCache[classindex][class_kill_bonus] = value;
return true;
}
}
// Invalid flag or multiple flags combined.
return false;
}
/**
* Modify class float attribute on a class.
*
* @param classindex The class index.
* @param attributeflag Attribute to modify (a single attribute flag).
* @param value New value to set, or multiply with.
* @param ismultiplier Optional. Specifies wether to value as a multiplier
* that multiplies with the original class value.
* Not all attributes support multipliers.
* @return True on success, false otherwise.
*/
ClassModifyFloat(classindex, attributeflag, Float:value, bool:ismultiplier = false)
{
// Validate class index.
if (!ClassValidateIndex(classindex))
{
return false;
}
switch (attributeflag)
{
case ZR_CLASS_FLAG_NAPALM_TIME:
{
if (ismultiplier)
{
value = ClassData[classindex][class_napalm_time] * value;
}
ClassDataCache[classindex][class_napalm_time] = value;
return true;
}
case ZR_CLASS_FLAG_IMMUNITY_AMOUNT:
{
if (ismultiplier)
{
value = ClassData[classindex][class_immunity_amount] * value;
}
ClassDataCache[classindex][class_immunity_amount] = value;
return true;
}
case ZR_CLASS_FLAG_HEALTH_REGEN_INTERVAL:
{
if (ismultiplier)
{
value = ClassData[classindex][class_health_regen_interval] * value;
}
ClassDataCache[classindex][class_health_regen_interval] = value;
return true;
}
case ZR_CLASS_FLAG_SPEED:
{
if (ismultiplier)
{
value = ClassData[classindex][class_speed] * value;
}
ClassDataCache[classindex][class_speed] = value;
return true;
}
case ZR_CLASS_FLAG_KNOCKBACK:
{
if (ismultiplier)
{
value = ClassData[classindex][class_knockback] * value;
}
ClassDataCache[classindex][class_knockback] = value;
return true;
}
case ZR_CLASS_FLAG_JUMP_HEIGHT:
{
if (ismultiplier)
{
value = ClassData[classindex][class_jump_height] * value;
}
ClassDataCache[classindex][class_jump_height] = value;
return true;
}
case ZR_CLASS_FLAG_JUMP_DISTANCE:
{
if (ismultiplier)
{
value = ClassData[classindex][class_jump_distance] * value;
}
ClassDataCache[classindex][class_jump_distance] = value;
return true;
}
}
// Invalid flag or multiple flags combined.
return false;
}
/**
* Modify class string attribute on a class.
*
* @param classindex The class index.
* @param attributeflag Attribute to modify (a single attribute flag).
* @param value New value to set.
* @return True on success, false otherwise.
*/
ClassModifyString(classindex, attributeflag, const String:value[])
{
// Validate class index.
if (!ClassValidateIndex(classindex))
{
return false;
}
switch (attributeflag)
{
case ZR_CLASS_FLAG_NAME:
{
strcopy(ClassDataCache[classindex][class_name], 64, value);
return true;
}
case ZR_CLASS_FLAG_DESCRIPTION:
{
strcopy(ClassDataCache[classindex][class_description], 256, value);
return true;
}
case ZR_CLASS_FLAG_MODEL_PATH:
{
strcopy(ClassDataCache[classindex][class_model_path], PLATFORM_MAX_PATH, value);
return true;
}
case ZR_CLASS_FLAG_OVERLAY_PATH:
{
strcopy(ClassDataCache[classindex][class_overlay_path], PLATFORM_MAX_PATH, value);
return true;
}
}
// Invalid flag or multiple flags combined.
return false;
}

View File

@ -23,24 +23,42 @@
*/
ClassClientInit(client)
{
if (ZRIsClientValid(client))
// Check if there are valid classes and the client is valid.
if (ClassValidated && ZRIsClientValid(client))
{
// Set default class indexes on the player.
ClassClientSetDefaultIndexes(client);
}
}
/**
* Called when all modules are done loading.
*/
ClassOnModulesLoaded()
{
// Set default classes on all player slots.
ClassClientSetDefaultIndexes();
}
ClassOnClientDisconnect(client)
{
// Stop timers related to class attributes.
// Disable class attributes with timers.
ClassHealthRegenStop(client);
ClassOverlayStop(client);
}
ClassOnClientSpawn(client)
{
// Check if the player is alive.
if (!IsPlayerAlive(client))
{
// The client isn't alive.
return;
}
// Check if there are valid classes. Block this event if classes aren't
// done loading.
if (!ClassValidated)
{
return;
}
@ -97,7 +115,7 @@ ClassOnClientSpawn(client)
ClassOnClientDeath(client)
{
// Reset certain attributes to not make spectating disorted.
// Disable class attributes with timers.
ClassHealthRegenStop(client);
ClassOverlayStop(client);
@ -109,17 +127,13 @@ ClassOnClientInfected(client, bool:motherzombie = false)
{
new classindex = ClassGetActiveIndex(client);
// Disable class attributes with timers.
ClassHealthRegenStop(client);
ClassOverlayStop(client);
// Update the players cache with zombie attributes.
ClassReloadPlayerCache(client, classindex);
// Apply the new attributes.
ClassApplyAttributes(client, motherzombie);
}
/* ------------------------------------
*
* PLAYER COMMANDS
*
* ------------------------------------
*/

View File

@ -96,6 +96,20 @@ ClassValidateAttributes(classindex)
{
flags += ZR_CLASS_FLAG_NAME;
}
else
{
decl String:name[64];
strcopy(name, sizeof(name), ClassData[classindex][class_name]);
// Check for reserved name keyworks.
if (StrEqual(name, "all", false) ||
StrEqual(name, "humans", false) ||
StrEqual(name, "zombies", false) ||
StrEqual(name, "admins", false))
{
flags += ZR_CLASS_FLAG_NAME;
}
}
// Description.
if (strlen(ClassData[classindex][class_description]) < ZR_CLASS_DESCRIPTION_MIN)
@ -195,7 +209,7 @@ ClassValidateAttributes(classindex)
new infect_gain = ClassData[classindex][class_health_infect_gain];
if (!(infect_gain >= ZR_CLASS_HEALTH_INFECT_GAIN_MIN && infect_gain <= ZR_CLASS_HEALTH_INFECT_GAIN_MAX))
{
flags += ZR_CLASS_FLAG_INFECT_GAIN;
flags += ZR_CLASS_FLAG_HEALTH_INFECT_GAIN;
}
// Kill bonus.
@ -660,6 +674,9 @@ ClassGetDefaultSpawnClass(teamid, cachetype = ZR_CLASS_CACHE_MODIFIED)
decl String:classname[64];
new classindex;
// Initialize log boolean.
new bool:enablelog = LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CLASSES);
// Get the default class name from the correct CVAR depending on teamid.
switch (teamid)
{
@ -723,7 +740,7 @@ ClassGetDefaultSpawnClass(teamid, cachetype = ZR_CLASS_CACHE_MODIFIED)
// in the specified team, and log a warning.
classindex = ClassGetFirstClass(teamid, _, cachetype);
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CLASSES))
if (enablelog)
{
LogMessageFormatted(-1, "Classes", "DefaultSpawnClass", "Warning: Failed to set \"%s\" as default spawn class for team %d. The class doesn't exist or the team IDs doesn't match. Falling back to the first class in the team.", _, classname, teamid);
}
@ -732,7 +749,7 @@ ClassGetDefaultSpawnClass(teamid, cachetype = ZR_CLASS_CACHE_MODIFIED)
if (ClassValidateIndex(classindex))
{
// Log a warning.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CLASSES))
if (enablelog)
{
LogMessageFormatted(-1, "Classes", "DefaultSpawnClass", "Warning: The default class name \"%s\" does not exist or matches the team ID.", _, classname);
}

View File

@ -156,40 +156,44 @@
#define ZR_CLASS_KNOCKBACK_MAX 30.0
#define ZR_CLASS_JUMP_HEIGHT_MIN -500.0
#define ZR_CLASS_JUMP_HEIGHT_MAX 500.0
#define ZR_CLASS_JUMP_DISTANCE_MIN -500.0
#define ZR_CLASS_JUMP_DISTANCE_MAX 500.0
#define ZR_CLASS_JUMP_DISTANCE_MIN -5.0
#define ZR_CLASS_JUMP_DISTANCE_MAX 5.0
/**
* @endsection
*/
/**
* @section Error flags for invalid class attributes.
* @section Flags used for specifying one or more attributes.
*/
#define ZR_CLASS_FLAG_NAME (1<<0)
#define ZR_CLASS_FLAG_DESCRIPTION (1<<1)
#define ZR_CLASS_FLAG_MODEL_PATH (1<<2)
#define ZR_CLASS_FLAG_ALPHA_INITIAL (1<<3)
#define ZR_CLASS_FLAG_ALPHA_DAMAGED (1<<4)
#define ZR_CLASS_FLAG_ALPHA_DAMAGE (1<<5)
#define ZR_CLASS_FLAG_OVERLAY_PATH (1<<6)
#define ZR_CLASS_FLAG_FOV (1<<7)
#define ZR_CLASS_FLAG_NAPALM_TIME (1<<8)
#define ZR_CLASS_FLAG_IMMUNITY_MODE (1<<9)
#define ZR_CLASS_FLAG_IMMUNITY_AMOUNT (1<<10)
#define ZR_CLASS_FLAG_HEALTH (1<<11)
#define ZR_CLASS_FLAG_HEALTH_REGEN_INTERVAL (1<<12)
#define ZR_CLASS_FLAG_HEALTH_REGEN_AMOUNT (1<<13)
#define ZR_CLASS_FLAG_INFECT_GAIN (1<<14)
#define ZR_CLASS_FLAG_KILL_BONUS (1<<15)
#define ZR_CLASS_FLAG_SPEED (1<<16)
#define ZR_CLASS_FLAG_KNOCKBACK (1<<17)
#define ZR_CLASS_FLAG_JUMP_HEIGHT (1<<18)
#define ZR_CLASS_FLAG_JUMP_DISTANCE (1<<19)
#define ZR_CLASS_FLAG_ENABLED (1<<0)
#define ZR_CLASS_FLAG_TEAM (1<<1)
#define ZR_CLASS_FLAG_TEAM_DEFAULT (1<<2)
#define ZR_CLASS_FLAG_NAME (1<<3)
#define ZR_CLASS_FLAG_DESCRIPTION (1<<4)
#define ZR_CLASS_FLAG_MODEL_PATH (1<<5)
#define ZR_CLASS_FLAG_ALPHA_INITIAL (1<<6)
#define ZR_CLASS_FLAG_ALPHA_DAMAGED (1<<7)
#define ZR_CLASS_FLAG_ALPHA_DAMAGE (1<<8)
#define ZR_CLASS_FLAG_OVERLAY_PATH (1<<9)
#define ZR_CLASS_FLAG_NVGS (1<<10)
#define ZR_CLASS_FLAG_FOV (1<<11)
#define ZR_CLASS_FLAG_NAPALM_TIME (1<<12)
#define ZR_CLASS_FLAG_IMMUNITY_MODE (1<<13)
#define ZR_CLASS_FLAG_IMMUNITY_AMOUNT (1<<14)
#define ZR_CLASS_FLAG_NO_FALL_DAMAGE (1<<15)
#define ZR_CLASS_FLAG_HEALTH (1<<16)
#define ZR_CLASS_FLAG_HEALTH_REGEN_INTERVAL (1<<17)
#define ZR_CLASS_FLAG_HEALTH_REGEN_AMOUNT (1<<18)
#define ZR_CLASS_FLAG_HEALTH_INFECT_GAIN (1<<19)
#define ZR_CLASS_FLAG_KILL_BONUS (1<<20)
#define ZR_CLASS_FLAG_SPEED (1<<21)
#define ZR_CLASS_FLAG_KNOCKBACK (1<<22)
#define ZR_CLASS_FLAG_JUMP_HEIGHT (1<<23)
#define ZR_CLASS_FLAG_JUMP_DISTANCE (1<<24)
/**
* @endsection
*/
/**
* Generic player attributes.
*/
@ -231,7 +235,19 @@ enum ClassAttributes
Float:class_speed,
Float:class_knockback,
Float:class_jump_height,
Float:class_jump_distance,
Float:class_jump_distance
}
/**
* Data types used in class attributes.
*/
enum ClassDataTypes
{
ClassDataType_InvalidType, /** Invalid type */
ClassDataType_Boolean, /** Boolean value */
ClassDataType_Integer, /** Integer value */
ClassDataType_Float, /** Floating point value */
ClassDataType_String /** String value */
}
/**
@ -264,6 +280,12 @@ new ClassPlayerCache[MAXPLAYERS + 1][ClassAttributes];
*/
new ClassCount;
/**
* Specifies wether the class team requirement and attributes are valid or not.
* Used to block events that happend before the module is done loading.
*/
new bool:ClassValidated;
/**
* Stores what class that the player have selected, for each team.
*/
@ -300,6 +322,9 @@ new ClassPlayerNextAdminClass[MAXPLAYERS + 1];
*/
ClassLoad()
{
// Initialize log boolean.
new bool:enablelog = LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_WEAPONS);
// Make sure kvClassData is ready to use.
if (kvClassData != INVALID_HANDLE)
{
@ -314,15 +339,17 @@ ClassLoad()
// If file doesn't exist, then log and stop.
if (!exists)
{
// Log failure.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_WEAPONS))
{
LogMessageFormatted(-1, "Classes", "Config Validation", "Missing playerclasses config file: %s", LOG_FORMAT_TYPE_FATALERROR, pathclasses);
}
LogMessageFormatted(-1, "Classes", "Load", "Fatal error: Missing playerclasses config file \"%s\"", LOG_FORMAT_TYPE_FATALERROR, pathclasses);
return;
}
// Log what class file that is loaded.
if (enablelog)
{
LogMessageFormatted(-1, "Classes", "Load", "Loading classes from file \"%s\".", LOG_FORMAT_TYPE_SIMPLE, pathclasses);
}
// Put file data into memory.
FileToKeyValues(kvClassData, pathclasses);
@ -330,7 +357,7 @@ ClassLoad()
KvRewind(kvClassData);
if (!KvGotoFirstSubKey(kvClassData))
{
LogMessageFormatted(-1, "Classes", "Config Validation", "Can't find any classes in %s", LOG_FORMAT_TYPE_FATALERROR, pathclasses);
LogMessageFormatted(-1, "Classes", "Load", "Fatal error: Can't find any classes in \"%s\"", LOG_FORMAT_TYPE_FATALERROR, pathclasses);
}
decl String:name[64];
@ -339,6 +366,7 @@ ClassLoad()
decl String:overlay_path[PLATFORM_MAX_PATH];
ClassCount = 0;
new failedcount;
new ClassErrorFlags;
// Loop through all classes and store attributes in the ClassData array.
@ -347,7 +375,7 @@ ClassLoad()
if (ClassCount > ZR_CLASS_MAX)
{
// Maximum classes reached. Write a warning and exit the loop.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CLASSES))
if (enablelog)
{
LogMessageFormatted(-1, "Classes", "Load", "Warning: Maximum classes reached (%d). Skipping other classes.", _, ZR_CLASS_MAX + 1);
}
@ -411,10 +439,12 @@ ClassLoad()
// There's one or more invalid class attributes. Disable the class
// and log an error message.
ClassData[ClassCount][class_enabled] = false;
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_CLASSES))
if (enablelog)
{
LogMessageFormatted(-1, "Classes", "Config Validation", "Warning: Invalid class at index %d, disabled class. Class error flags: %d.", LOG_FORMAT_TYPE_ERROR, ClassCount, ClassErrorFlags);
}
failedcount++;
}
// Update the counter.
@ -424,17 +454,26 @@ ClassLoad()
// Validate team requirements.
if (!ClassValidateTeamRequirements())
{
LogMessageFormatted(-1, "Classes", "Config Validation", "The class configuration doesn't match the team requirements.", LOG_FORMAT_TYPE_FATALERROR);
LogMessageFormatted(-1, "Classes", "Config Validation", "Fatal error: The class configuration doesn't match the team requirements.", LOG_FORMAT_TYPE_FATALERROR);
}
// Validate team default requirements.
if (!ClassValidateTeamDefaults())
{
LogMessageFormatted(-1, "Classes", "Config Validation", "Couldn't find a default class for one or more teams. At least one class per team must be marked as default.", LOG_FORMAT_TYPE_FATALERROR);
LogMessageFormatted(-1, "Classes", "Config Validation", "Fatal error: Couldn't find a default class for one or more teams. At least one class per team must be marked as default.", LOG_FORMAT_TYPE_FATALERROR);
}
// Cache class data.
ClassReloadDataCache();
// Mark classes as valid.
ClassValidated = true;
// Log summary.
if (enablelog)
{
LogMessageFormatted(-1, "Classes", "Config Validation", "Total: %d | Successful: %d | Unsuccessful: %d", _, ClassCount, ClassCount - failedcount, failedcount);
}
}
/**

View File

@ -57,6 +57,9 @@ bool:AmbientSoundsValidateConfig()
return false;
}
// Initialize log boolean.
new bool:enablelog = LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_AMBIENTSOUNDS);
// Get ambient sound file.
decl String:sound[SOUND_MAX_PATH];
GetConVarString(g_hCvarsList[CVAR_AMBIENTSOUNDS_FILE], sound, sizeof(sound));
@ -66,7 +69,7 @@ bool:AmbientSoundsValidateConfig()
if (!FileExists(sound, true))
{
// Log invalid sound file error.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_AMBIENTSOUNDS))
if (enablelog)
{
LogMessageFormatted(-1, "Ambient Sounds", "Config Validation", "Invalid sound file specified in zr_ambientsounds_file.", LOG_FORMAT_TYPE_ERROR);
}
@ -79,7 +82,7 @@ bool:AmbientSoundsValidateConfig()
if (ambientvolume <= 0.0)
{
// Log invalid ambient sound volume error.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_AMBIENTSOUNDS))
if (enablelog)
{
LogMessageFormatted(-1, "Ambient Sounds", "Config Validation", "Ambient sound is either muted or invalid.", LOG_FORMAT_TYPE_ERROR);
}
@ -92,9 +95,9 @@ bool:AmbientSoundsValidateConfig()
if (ambientlength <= 0.0)
{
// Log invalid ambient sound length error.
if (LogCheckFlag(LOG_CORE_EVENTS, LOG_MODULE_AMBIENTSOUNDS))
if (enablelog)
{
LogMessageFormatted(-1, "Ambient Sounds", "Config Validation", "Ambient sound length is invalid.", LOG_FORMAT_TYPE_ERROR);
LogMessageFormatted(-1, "Ambient Sounds", "Config Validation", "Specified ambient sound length is invalid.", LOG_FORMAT_TYPE_ERROR);
}
return false;

View File

@ -36,6 +36,12 @@ ZSpawnOnMapStart()
*/
ZSpawnOnClientDisconnect(client)
{
// Check if client is a bot.
if (IsFakeClient(client))
{
return;
}
// Get client's unique serial number.
new serial = GetClientSerial(client);