diff --git a/cstrike/addons/sourcemod/configs/zr/playerclasses.txt b/cstrike/addons/sourcemod/configs/zr/playerclasses.txt index 6d50103..44144ea 100644 --- a/cstrike/addons/sourcemod/configs/zr/playerclasses.txt +++ b/cstrike/addons/sourcemod/configs/zr/playerclasses.txt @@ -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" } } diff --git a/src/zombiereloaded.sp b/src/zombiereloaded.sp index 798aa8c..e526886 100644 --- a/src/zombiereloaded.sp +++ b/src/zombiereloaded.sp @@ -200,6 +200,9 @@ public OnConfigsExecuted() VEffectsLoad(); SEffectsLoad(); ClassLoad(); + + ConfigOnModulesLoaded(); + ClassOnModulesLoaded(); } /** diff --git a/src/zr/commands.inc b/src/zr/commands.inc index 6cac4f1..a98183e 100644 --- a/src/zr/commands.inc +++ b/src/zr/commands.inc @@ -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 "); + RegAdminCmd("zr_class_modify", Command_ClassModify, ADMFLAG_GENERIC, "Modify class data on one or more classes. Usage: zr_class_modify [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 "); - 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 "); - 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) { diff --git a/src/zr/config.inc b/src/zr/config.inc index d582631..bdc7c4b 100644 --- a/src/zr/config.inc +++ b/src/zr/config.inc @@ -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, diff --git a/src/zr/jumpboost.inc b/src/zr/jumpboost.inc index 7105b57..5ac4cbf 100644 --- a/src/zr/jumpboost.inc +++ b/src/zr/jumpboost.inc @@ -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. diff --git a/src/zr/log.inc b/src/zr/log.inc index 5101de6..6544ae9 100644 --- a/src/zr/log.inc +++ b/src/zr/log.inc @@ -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:...) diff --git a/src/zr/menu.inc b/src/zr/menu.inc index 5c05bea..21b952d 100644 --- a/src/zr/menu.inc +++ b/src/zr/menu.inc @@ -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]; diff --git a/src/zr/models.inc b/src/zr/models.inc index 7b2da1c..2de1e33 100644 --- a/src/zr/models.inc +++ b/src/zr/models.inc @@ -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. - LogMessageFormatted(-1, "Models", "Config Validation", "Total: %d | Successful: %d | Unsuccessful: %d", LOG_FORMAT_TYPE_FULL, modelcount, modelvalidcount, modelcount - modelvalidcount); + 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. - LogMessageFormatted(-1, "Downloads", "Config Validation", "Total: %d | Successful: %d | Unsuccessful: %d", LOG_FORMAT_TYPE_FULL, downloadcount, downloadvalidcount, downloadcount - downloadvalidcount); + if (enablelog) + { + LogMessageFormatted(-1, "Downloads", "Config Validation", "Total: %d | Successful: %d | Unsuccessful: %d", LOG_FORMAT_TYPE_FULL, downloadcount, downloadvalidcount, downloadcount - downloadvalidcount); + } } diff --git a/src/zr/playerclasses/attributes.inc b/src/zr/playerclasses/attributes.inc index b5ba255..5f6fbb5 100644 --- a/src/zr/playerclasses/attributes.inc +++ b/src/zr/playerclasses/attributes.inc @@ -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; +} diff --git a/src/zr/playerclasses/classcommands.inc b/src/zr/playerclasses/classcommands.inc index 02e48f6..9048aef 100644 --- a/src/zr/playerclasses/classcommands.inc +++ b/src/zr/playerclasses/classcommands.inc @@ -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 [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 [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; +} diff --git a/src/zr/playerclasses/classevents.inc b/src/zr/playerclasses/classevents.inc index c13d63b..944b21d 100644 --- a/src/zr/playerclasses/classevents.inc +++ b/src/zr/playerclasses/classevents.inc @@ -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 - * - * ------------------------------------ - */ - diff --git a/src/zr/playerclasses/filtertools.inc b/src/zr/playerclasses/filtertools.inc index a84759f..d389ee7 100644 --- a/src/zr/playerclasses/filtertools.inc +++ b/src/zr/playerclasses/filtertools.inc @@ -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); } diff --git a/src/zr/playerclasses/playerclasses.inc b/src/zr/playerclasses/playerclasses.inc index 02b6ee1..24a06d0 100644 --- a/src/zr/playerclasses/playerclasses.inc +++ b/src/zr/playerclasses/playerclasses.inc @@ -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); + } } /** diff --git a/src/zr/soundeffects/ambientsounds.inc b/src/zr/soundeffects/ambientsounds.inc index e76b17b..2c201a1 100644 --- a/src/zr/soundeffects/ambientsounds.inc +++ b/src/zr/soundeffects/ambientsounds.inc @@ -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; diff --git a/src/zr/zspawn.inc b/src/zr/zspawn.inc index 904fc7a..a3e2e46 100644 --- a/src/zr/zspawn.inc +++ b/src/zr/zspawn.inc @@ -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);