/* * ============================================================================ * * Zombie:Reloaded * * File: playerclasses.inc * Type: Core * Description: Provides functions for managing classes. * * 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 . * * ============================================================================ */ /* Ideas for immunity modes for humans: - Zombies have to stab x times to infect. - Zombies have to hurt humans so they loose hp. When the hp reach zero (or below) they turn into zombies. - Fully imune to all damage. Can't take or give damage. Sould only be used on admin mode classes. TODO: Make class attributes for for changing model render mode and colors. TODO: Make class attributes for fancy effects, like a glow (if possible). TODO: Make immunity settings suitable for both teams, or certain zombie- only/human-only settings. */ /** * Total number of classes that can be stored in each cache. A total of 32 * classes should be enough. Too many classes will confuse players. */ #define ZR_CLASS_MAX 32 /** * @section Class cache types. Specifies what data array to use. */ #define ZR_CLASS_CACHE_ORIGINAL 0 /** Points ClassData array. A cache that is never changed after loading. */ #define ZR_CLASS_CACHE_MODIFIED 1 /** Points to ClassDataCache array. Modified by admins or overrides. */ #define ZR_CLASS_CACHE_PLAYER 2 /** Points to ClassPlayerCache array. Current player attributes. */ /** * @endsection */ /** * Number of available class teams. */ #define ZR_CLASS_TEAMCOUNT 3 /** * @section Available class teams. The admin team is optional and not required * in class configs. */ #define ZR_CLASS_TEAM_ZOMBIES 0 #define ZR_CLASS_TEAM_HUMANS 1 #define ZR_CLASS_TEAM_ADMINS 2 /** Note: Will set you in a special mode where you don't really participates in the game, but just walk around. */ /** * @endsection */ /** * @section Damage immunity modes. The mode effects will vary depending on the * class team. */ #define ZR_CLASS_IMMUNITY_DISABLED 0 /** No immunity. */ #define ZR_CLASS_IMMUNITY_CONSTANT 1 /** Always imune. Should only be used in special cases like on admin classes. */ #define ZR_CLASS_IMMUNITY_TIMED 2 /** Imune to damage for n seconds. The time is specified in a class as immunity amount. */ /** * @endsection */ /** * @section Flags for special classes. */ #define ZR_CLASS_FLAG_ADMIN_ONLY (1<<0) /** Class is usable by admins only. */ #define ZR_CLASS_FLAG_MOTHER_ZOMBIE (1<<1) /** Class is usable by mother zombies only. */ /** A combination of special class flags. Used to exclude special classes. */ #define ZR_CLASS_SPECIALFLAGS ZR_CLASS_FLAG_ADMIN_ONLY + ZR_CLASS_FLAG_MOTHER_ZOMBIE /** * @endsection */ /** * @section Overall default class settings. Since this is a zombie plugin the * default values represent a zombie. */ #define ZR_CLASS_DEFAULT_ENABLED "yes" #define ZR_CLASS_DEFAULT_TEAM ZR_CLASS_TEAM_ZOMBIES #define ZR_CLASS_DEFAULT_TEAM_DEFAULT "yes" #define ZR_CLASS_DEFAULT_FLAGS 0 #define ZR_CLASS_DEFAULT_GROUP "" #define ZR_CLASS_DEFAULT_NAME "classic" #define ZR_CLASS_DEFAULT_DESCRIPTION "Need brains!!! Arrrrggghh!" #define ZR_CLASS_DEFAULT_MODEL_PATH "models/player/zh/zh_zombie003.mdl" #define ZR_CLASS_DEFAULT_ALPHA_INITIAL 255 #define ZR_CLASS_DEFAULT_ALPHA_DAMAGED 255 #define ZR_CLASS_DEFAULT_ALPHA_DAMAGE 0 #define ZR_CLASS_DEFAULT_OVERLAY_PATH "overlays/zr/zvision" #define ZR_CLASS_DEFAULT_NVGS "no" #define ZR_CLASS_DEFAULT_FOV 90 #define ZR_CLASS_DEFAULT_HAS_NAPALM "no" #define ZR_CLASS_DEFAULT_NAPALM_TIME 10.0 #define ZR_CLASS_DEFAULT_IMMUNITY_MODE ZR_CLASS_IMMUNITY_DISABLED #define ZR_CLASS_DEFAULT_IMMUNITY_AMOUNT 0.0 #define ZR_CLASS_DEFAULT_NO_FALL_DAMAGE "yes" #define ZR_CLASS_DEFAULT_HEALTH 6000 #define ZR_CLASS_DEFAULT_HEALTH_REGEN_INTERVAL 0.0 #define ZR_CLASS_DEFAULT_HEALTH_REGEN_AMOUNT 2 #define ZR_CLASS_DEFAULT_HEALTH_INFECT_GAIN 800 #define ZR_CLASS_DEFAULT_KILL_BONUS 2 #define ZR_CLASS_DEFAULT_SPEED 360.0 #define ZR_CLASS_DEFAULT_KNOCKBACK 2.0 #define ZR_CLASS_DEFAULT_JUMP_HEIGHT 10.0 #define ZR_CLASS_DEFAULT_JUMP_DISTANCE 0.2 /** * @endsection */ /** * @section Attribute limit values. Used when validating. */ #define ZR_CLASS_TEAM_MIN 0 #define ZR_CLASS_TEAM_MAX 2 #define ZR_CLASS_FLAGS_MIN 0 #define ZR_CLASS_FLAGS_MAX 3 #define ZR_CLASS_NAME_MIN 1 #define ZR_CLASS_DESCRIPTION_MIN 1 /** Model path is checked for existance. */ #define ZR_CLASS_ALPHA_INITIAL_MIN 0 #define ZR_CLASS_ALPHA_INITIAL_MAX 255 #define ZR_CLASS_ALPHA_DAMAGED_MIN 0 #define ZR_CLASS_ALPHA_DAMAGED_MAX 255 #define ZR_CLASS_ALPHA_DAMAGE_MIN 0 #define ZR_CLASS_ALPHA_DAMAGE_MAX 20000 /** Overlay path is optional, and file is checked for existance if specified. */ #define ZR_CLASS_FOV_MIN 15 #define ZR_CLASS_FOV_MAX 165 #define ZR_CLASS_NAPALM_TIME_MIN 0.0 #define ZR_CLASS_NAPALM_TIME_MAX 600.0 #define ZR_CLASS_HEALTH_MIN 1 #define ZR_CLASS_HEALTH_MAX 20000 #define ZR_CLASS_REGEN_INTERVAL_MIN 0.0 #define ZR_CLASS_REGEN_INTERVAL_MAX 900.0 #define ZR_CLASS_REGEN_AMOUNT_MIN 0 #define ZR_CLASS_REGEN_AMOUNT_MAX 10000 #define ZR_CLASS_HEALTH_INFECT_GAIN_MIN 0 #define ZR_CLASS_HEALTH_INFECT_GAIN_MAX 20000 #define ZR_CLASS_KILL_BONUS_MIN 0 #define ZR_CLASS_KILL_BONUS_MAX 16 #define ZR_CLASS_SPEED_MIN 10.0 #define ZR_CLASS_SPEED_MAX 2000.0 #define ZR_CLASS_KNOCKBACK_MIN -30.0 #define ZR_CLASS_KNOCKBACK_MAX 30.0 #define ZR_CLASS_KNOCKBACK_IGNORE -31.0 /** Used by class editor volumetric feature. */ #define ZR_CLASS_JUMP_HEIGHT_MIN 0.0 #define ZR_CLASS_JUMP_HEIGHT_MAX 5.0 #define ZR_CLASS_JUMP_DISTANCE_MIN 0.0 #define ZR_CLASS_JUMP_DISTANCE_MAX 5.0 /** * @endsection */ /** * @section Class attribute flags. */ #define ZR_CLASS_ENABLED (1<<0) #define ZR_CLASS_TEAM (1<<1) #define ZR_CLASS_TEAM_DEFAULT (1<<2) #define ZR_CLASS_FLAGS (1<<3) #define ZR_CLASS_GROUP (1<<4) #define ZR_CLASS_NAME (1<<5) #define ZR_CLASS_DESCRIPTION (1<<6) #define ZR_CLASS_MODEL_PATH (1<<7) #define ZR_CLASS_ALPHA_INITIAL (1<<8) #define ZR_CLASS_ALPHA_DAMAGED (1<<9) #define ZR_CLASS_ALPHA_DAMAGE (1<<10) #define ZR_CLASS_OVERLAY_PATH (1<<11) #define ZR_CLASS_NVGS (1<<12) #define ZR_CLASS_FOV (1<<13) #define ZR_CLASS_HAS_NAPALM (1<<14) #define ZR_CLASS_NAPALM_TIME (1<<15) #define ZR_CLASS_IMMUNITY_MODE (1<<16) #define ZR_CLASS_IMMUNITY_AMOUNT (1<<17) #define ZR_CLASS_NO_FALL_DAMAGE (1<<18) #define ZR_CLASS_HEALTH (1<<19) #define ZR_CLASS_HEALTH_REGEN_INTERVAL (1<<20) #define ZR_CLASS_HEALTH_REGEN_AMOUNT (1<<21) #define ZR_CLASS_HEALTH_INFECT_GAIN (1<<22) #define ZR_CLASS_KILL_BONUS (1<<23) #define ZR_CLASS_SPEED (1<<24) #define ZR_CLASS_KNOCKBACK (1<<25) #define ZR_CLASS_JUMP_HEIGHT (1<<26) #define ZR_CLASS_JUMP_DISTANCE (1<<27) /** * @endsection */ /** * Generic player attributes. * * Stuff that must be updated when new attributes are added: * ZR_CLASS_DEFAULT_... define * ZR_CLASS_..._MAX/MIN defines * ZR_CLASS_... define (place in same order as listed in ClassAttributes, bump bit numbers + update numbers in docs) * ClassLoad * ClassReloadDataCache * ClassReloadPlayerCache * ClassDumpData * attributes.inc - Add new Get-function * ClassAttributeNameToFlag * ClassGetAttributeType * ClassValidateAttributes * ClassModify* in classcommands.inc * Update docs with detailed attribute description */ enum ClassAttributes { /* General */ bool:Class_Enabled, Class_Team, bool:Class_TeamDefault, Class_Flags, String:Class_Group[64], String:Class_Name[64], String:Class_Description[256], /* Model */ String:Class_ModelPath[PLATFORM_MAX_PATH], Class_AlphaInitial, Class_AlphaDamaged, Class_AlphaDamage, /* Hud */ String:Class_OverlayPath[PLATFORM_MAX_PATH], bool:Class_Nvgs, Class_Fov, /* Effects */ bool:Class_HasNapalm, Float:Class_NapalmTime, /* Player behaviour */ Class_ImmunityMode, Float:Class_ImmunityAmount, bool:Class_NoFallDamage, Class_Health, Float:Class_HealthRegenInterval, Class_HealthRegenAmount, Class_HealthInfectGain, Class_KillBonus, Float:Class_Speed, Float:Class_KnockBack, Float:Class_JumpHeight, Float:Class_JumpDistance } /** * Structure of class attributes that are allowed to be modified directly, * while the player is alive. * * Note: This structure is also used as a mask to tell if a individual * attribute should be ignored or not. Negative valueas usually indicate * ignored attributes. Booleans are now ints so they can be negative. * Strings have reserved keywords like "nochange" that indicate a ignored * attribute. */ enum ClassEditableAttributes { /* Model */ ClassEdit_AlphaInitial = 0, ClassEdit_AlphaDamaged, ClassEdit_AlphaDamage, /* Hud */ String:ClassEdit_OverlayPath[PLATFORM_MAX_PATH], ClassEdit_Nvgs, ClassEdit_Fov, /* Effects */ ClassEdit_HasNapalm, Float:ClassEdit_NapalmTime, /* Player behaviour */ ClassEdit_ImmunityMode, Float:ClassEdit_ImmunityAmount, ClassEdit_NoFallDamage, Float:ClassEdit_RegenInterval, ClassEdit_RegenAmount, ClassEdit_InfectGain, ClassEdit_KillBonus, Float:ClassEdit_Speed, Float:ClassEdit_KnockBack, Float:ClassEdit_JumpHeight, Float:ClassEdit_JumpDistance } /** * Class attributes that support multipliers. */ enum ClassMultipliers { ClassM_Invalid = 0, Float:ClassM_NapalmTime, Float:ClassM_Health, Float:ClassM_HealthRegenInterval, Float:ClassM_HealthRegenAmount, Float:ClassM_HealthInfectGain, Float:ClassM_Speed, Float:ClassM_Knockback, Float:ClassM_JumpHeight, Float:ClassM_JumpDistance } /** * Available class teams, used to specify targets. */ enum ClassTeams { ClassTeam_Zombies = 0, ClassTeam_Humans, ClassTeam_Admins, ClassTeam_All } /** * 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 */ } /** * Structure for class filter settings passed to various functions. */ enum ClassFilter { bool:ClassFilter_IgnoreEnabled, /** Ignore whether the class is disabled or not. */ ClassFilter_RequireFlags, /** Flags the classes must have set. */ ClassFilter_DenyFlags, /** Flags the classes cannot have set. */ ClassFilter_Client /** The client to check for class group permissions. Use 0 to ignore group filter and negative to exclude classes with groups set. */ } /** * Empty filter structure. */ new ClassNoFilter[ClassFilter]; /** * Filter structure for excluding special classes. */ new ClassNoSpecialClasses[ClassFilter] = {false, 0, ZR_CLASS_SPECIALFLAGS, -1}; /** * Keyvalue handle to store class data. */ new Handle:kvClassData = INVALID_HANDLE; /** * The original class data. This array only changed when class data is loaded. * ZR_CLASS_CACHE_ORIGINAL is the cache type to this array. */ new ClassData[ZR_CLASS_MAX][ClassAttributes]; /** * The class thata cache that can be modified. ZR_CLASS_CACHE_MODIFIED is the * cache type to this array. */ new ClassDataCache[ZR_CLASS_MAX][ClassAttributes]; /** * Cache for player attributes. Makes it possible for one or more players to * have custom attributes. ZR_CLASS_CACHE_PLAYER is the cache type to this * array. */ new ClassPlayerCache[MAXPLAYERS + 1][ClassAttributes]; /** * Cache for storing global multipliers, per team and per attribute when * possible. Only attributes that support multipliers will be used, others are * ignored. */ new Float:ClassMultiplierCache[ZR_CLASS_TEAMCOUNT][ClassMultipliers]; /** * Number of classes loaded. */ 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 the player has selected, for each team. */ new ClassSelected[MAXPLAYERS + 1][ZR_CLASS_TEAMCOUNT]; /** * Stores what class to be restored on next spawn, if available. */ new ClassSelectedNext[MAXPLAYERS + 1][ZR_CLASS_TEAMCOUNT]; /** * Cache for the currently selected team (admin menus). */ new ClassAdminTeamSelected[MAXPLAYERS + 1]; /** * Cookies for storing class indexes. */ new Handle:g_hClassCookieClassSelected[ZR_CLASS_TEAMCOUNT]; /** * Cache for the currently selected attribute multiplier (admin menus). */ new ClassMultipliers:ClassAdminAttributeSelected[MAXPLAYERS + 1]; /** * Specifies whether a player is currently in admin mode. */ new bool:ClassPlayerInAdminMode[MAXPLAYERS + 1]; /** * Specifies whether a player is allowed to change class with instant effect. * This is only used on human classes, and in combination with the * zr_classes_change_timelimit time limit, but could be used other places too. * The class menu will automatically check this setting and apply attributes if * set to true. */ new bool:ClassAllowInstantChange[MAXPLAYERS + 1]; /** * Cache for storing original model path before applying custom models. Used * when restoring to old model. */ new String:ClassOriginalPlayerModel[MAXPLAYERS + 1][PLATFORM_MAX_PATH]; #include "zr/playerclasses/filtertools" #include "zr/playerclasses/attributes" #include "zr/playerclasses/apply" #include "zr/playerclasses/clientoverlays" #include "zr/playerclasses/clientalpha" #include "zr/playerclasses/healthregen" #include "zr/playerclasses/classevents" #include "zr/playerclasses/classmenus" #include "zr/playerclasses/classcommands" /** * Loads class attributes from the class file into ClassData array. If any * error occur the plugin load will fail, and errors will be logged. * * @param keepMultipliers Optional. Don't reset multipliers. Default is * false. */ ClassLoad(bool:keepMultipliers = false) { // Register config file. ConfigRegisterConfig(File_Classes, Structure_Keyvalue, CONFIG_FILE_ALIAS_CLASSES); // Make sure kvClassData is ready to use. if (kvClassData != INVALID_HANDLE) { CloseHandle(kvClassData); } kvClassData = CreateKeyValues(CONFIG_FILE_ALIAS_CLASSES); // Get weapons config path. decl String:pathclasses[PLATFORM_MAX_PATH]; new bool:exists = ConfigGetCvarFilePath(CVAR_CONFIG_PATH_CLASSES, pathclasses); // If file doesn't exist, then log and stop. if (!exists) { LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "Missing playerclasses config file \"%s\"", pathclasses); // Remove key/value cache. CloseHandle(kvClassData); kvClassData = INVALID_HANDLE; return; } // Log what class file that is loaded. LogEvent(false, LogType_Normal, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "Loading classes from file \"%s\".", pathclasses); // Put file data into memory. FileToKeyValues(kvClassData, pathclasses); // Try to find the first class. KvRewind(kvClassData); if (!KvGotoFirstSubKey(kvClassData)) { LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "Can't find any classes in \"%s\"", pathclasses); } decl String:name[64]; decl String:group[64]; decl String:description[256]; decl String:model_path[PLATFORM_MAX_PATH]; decl String:overlay_path[PLATFORM_MAX_PATH]; ClassCount = 0; new failedcount; new ClassErrorFlags; // Loop through all classes and store attributes in the ClassData array. do { if (ClassCount > ZR_CLASS_MAX) { // Maximum classes reached. Write a warning and exit the loop. LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "Warning: Maximum classes reached (%d). Skipping other classes.", ZR_CLASS_MAX + 1); break; } /* General */ ClassData[ClassCount][Class_Enabled] = ConfigKvGetStringBool(kvClassData, "enabled", ZR_CLASS_DEFAULT_ENABLED); ClassData[ClassCount][Class_Team] = KvGetNum(kvClassData, "team", ZR_CLASS_DEFAULT_TEAM); ClassData[ClassCount][Class_TeamDefault] = ConfigKvGetStringBool(kvClassData, "team_default", ZR_CLASS_DEFAULT_TEAM_DEFAULT); ClassData[ClassCount][Class_Flags] = KvGetNum(kvClassData, "flags", ZR_CLASS_DEFAULT_FLAGS); KvGetString(kvClassData, "group", group, sizeof(group), ZR_CLASS_DEFAULT_GROUP); strcopy(ClassData[ClassCount][Class_Group], 64, group); KvGetString(kvClassData, "name", name, sizeof(name), ZR_CLASS_DEFAULT_NAME); strcopy(ClassData[ClassCount][Class_Name], 64, name); KvGetString(kvClassData, "description", description, sizeof(description), ZR_CLASS_DEFAULT_DESCRIPTION); strcopy(ClassData[ClassCount][Class_Description], 256, description); /* Model */ KvGetString(kvClassData, "model_path", model_path, sizeof(model_path), ZR_CLASS_DEFAULT_MODEL_PATH); strcopy(ClassData[ClassCount][Class_ModelPath], PLATFORM_MAX_PATH, model_path); ClassData[ClassCount][Class_AlphaInitial] = KvGetNum(kvClassData, "alpha_initial", ZR_CLASS_DEFAULT_ALPHA_INITIAL); ClassData[ClassCount][Class_AlphaDamaged] = KvGetNum(kvClassData, "alpha_damaged", ZR_CLASS_DEFAULT_ALPHA_DAMAGED); ClassData[ClassCount][Class_AlphaDamage] = KvGetNum(kvClassData, "alpha_damage", ZR_CLASS_DEFAULT_ALPHA_DAMAGE); /* Hud */ KvGetString(kvClassData, "overlay_path", overlay_path, sizeof(overlay_path), ZR_CLASS_DEFAULT_OVERLAY_PATH); strcopy(ClassData[ClassCount][Class_OverlayPath], PLATFORM_MAX_PATH, overlay_path); ClassData[ClassCount][Class_Nvgs] = ConfigKvGetStringBool(kvClassData, "nvgs", ZR_CLASS_DEFAULT_NVGS); ClassData[ClassCount][Class_Fov] = KvGetNum(kvClassData, "fov", ZR_CLASS_DEFAULT_FOV); /* Effects */ ClassData[ClassCount][Class_HasNapalm] = ConfigKvGetStringBool(kvClassData, "has_napalm", ZR_CLASS_DEFAULT_HAS_NAPALM); ClassData[ClassCount][Class_NapalmTime] = KvGetFloat(kvClassData, "napalm_time", ZR_CLASS_DEFAULT_NAPALM_TIME); /* Player behaviour */ ClassData[ClassCount][Class_ImmunityMode] = KvGetNum(kvClassData, "immunity_mode", ZR_CLASS_DEFAULT_IMMUNITY_MODE); ClassData[ClassCount][Class_ImmunityAmount] = KvGetFloat(kvClassData, "immunity_amount", ZR_CLASS_DEFAULT_IMMUNITY_AMOUNT); ClassData[ClassCount][Class_NoFallDamage] = ConfigKvGetStringBool(kvClassData, "no_fall_damage", ZR_CLASS_DEFAULT_NO_FALL_DAMAGE); ClassData[ClassCount][Class_Health] = KvGetNum(kvClassData, "health", ZR_CLASS_DEFAULT_HEALTH); ClassData[ClassCount][Class_HealthRegenInterval] = KvGetFloat(kvClassData, "health_regen_interval", ZR_CLASS_DEFAULT_HEALTH_REGEN_INTERVAL); ClassData[ClassCount][Class_HealthRegenAmount] = KvGetNum(kvClassData, "health_regen_amount", ZR_CLASS_DEFAULT_HEALTH_REGEN_AMOUNT); ClassData[ClassCount][Class_HealthInfectGain] = KvGetNum(kvClassData, "health_infect_gain", ZR_CLASS_DEFAULT_HEALTH_INFECT_GAIN); ClassData[ClassCount][Class_KillBonus] = KvGetNum(kvClassData, "kill_bonus", ZR_CLASS_DEFAULT_KILL_BONUS); ClassData[ClassCount][Class_Speed] = KvGetFloat(kvClassData, "speed", ZR_CLASS_DEFAULT_SPEED); ClassData[ClassCount][Class_KnockBack] = KvGetFloat(kvClassData, "knockback", ZR_CLASS_DEFAULT_KNOCKBACK); ClassData[ClassCount][Class_JumpHeight] = KvGetFloat(kvClassData, "jump_height", ZR_CLASS_DEFAULT_JUMP_HEIGHT); ClassData[ClassCount][Class_JumpDistance] = KvGetFloat(kvClassData, "jump_distance", ZR_CLASS_DEFAULT_JUMP_DISTANCE); // Validate the class attributes. ClassErrorFlags = ClassValidateAttributes(ClassCount); if (ClassErrorFlags > 0) { // There's one or more invalid class attributes. Disable the class // and log an error message. ClassData[ClassCount][Class_Enabled] = false; LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "Warning: Invalid class at index %d, disabled class. Class error flags: %d.", ClassCount, ClassErrorFlags); failedcount++; } // Update the counter. ClassCount++; } while (KvGotoNextKey(kvClassData)); // Validate team requirements. if (!ClassValidateTeamRequirements()) { LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "The class configuration doesn't match the team requirements."); } // Validate team default requirements. if (!ClassValidateTeamDefaults()) { LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "Couldn't find a default class for one or more teams. At least one class per team must be marked as default."); } // Cache class data. ClassReloadDataCache(); // Reset selected class indexes for next spawn. ClassResetNextIndexes(); // Reset attribute multipliers, if not keeping. if (!keepMultipliers) { ClassResetMultiplierCache(); } // Mark classes as valid. ClassValidated = true; // Log summary. LogEvent(false, LogType_Normal, LOG_CORE_EVENTS, LogModule_Playerclasses, "Config Validation", "Total: %d | Successful: %d | Unsuccessful: %d", ClassCount, ClassCount - failedcount, failedcount); // Set config data. ConfigSetConfigLoaded(File_Classes, true); ConfigSetConfigReloadFunc(File_Classes, GetFunctionByName(GetMyHandle(), "ClassOnConfigReload")); ConfigSetConfigPath(File_Classes, pathclasses); // Remove key/value cache. CloseHandle(kvClassData); kvClassData = INVALID_HANDLE; } /** * Called when configs are being reloaded. * * @param config The config being reloaded. (only if 'all' is false) */ public ClassOnConfigReload(ConfigFile:config) { // Reload class config. ClassLoad(true); } /** * Updates the class data cache. Original values are retrieved from ClassData. * * @return True on success, false otherwise. */ bool:ClassReloadDataCache() { // Check if there are no classes. if (ClassCount == 0) { return false; } // Loop through all classes. for (new classindex = 0; classindex < ClassCount; classindex++) { /* General */ ClassDataCache[classindex][Class_Enabled] = ClassData[classindex][Class_Enabled]; ClassDataCache[classindex][Class_Team] = ClassData[classindex][Class_Team]; ClassDataCache[classindex][Class_TeamDefault] = ClassData[classindex][Class_TeamDefault]; ClassDataCache[classindex][Class_Flags] = ClassData[classindex][Class_Flags]; strcopy(ClassDataCache[classindex][Class_Group], 64, ClassData[classindex][Class_Group]); strcopy(ClassDataCache[classindex][Class_Name], 64, ClassData[classindex][Class_Name]); strcopy(ClassDataCache[classindex][Class_Description], 256, ClassData[classindex][Class_Description]); /* Model */ strcopy(ClassDataCache[classindex][Class_ModelPath], PLATFORM_MAX_PATH, ClassData[classindex][Class_ModelPath]); ClassDataCache[classindex][Class_AlphaInitial] = ClassData[classindex][Class_AlphaInitial]; ClassDataCache[classindex][Class_AlphaDamaged] = ClassData[classindex][Class_AlphaDamaged]; ClassDataCache[classindex][Class_AlphaDamage] = ClassData[classindex][Class_AlphaDamage]; /* Hud */ strcopy(ClassDataCache[classindex][Class_OverlayPath], PLATFORM_MAX_PATH, ClassData[classindex][Class_OverlayPath]); ClassDataCache[classindex][Class_Nvgs] = ClassData[classindex][Class_Nvgs]; ClassDataCache[classindex][Class_Fov] = ClassData[classindex][Class_Fov]; /* Effects */ ClassDataCache[classindex][Class_HasNapalm] = ClassData[classindex][Class_HasNapalm]; ClassDataCache[classindex][Class_NapalmTime] = ClassData[classindex][Class_NapalmTime]; /* Player behaviour */ ClassDataCache[classindex][Class_ImmunityMode] = ClassData[classindex][Class_ImmunityMode]; ClassDataCache[classindex][Class_ImmunityAmount] = ClassData[classindex][Class_ImmunityAmount]; ClassDataCache[classindex][Class_NoFallDamage] = ClassData[classindex][Class_NoFallDamage]; ClassDataCache[classindex][Class_Health] = ClassData[classindex][Class_Health]; ClassDataCache[classindex][Class_HealthRegenInterval] = ClassData[classindex][Class_HealthRegenInterval]; ClassDataCache[classindex][Class_HealthRegenAmount] = ClassData[classindex][Class_HealthRegenAmount]; ClassDataCache[classindex][Class_HealthInfectGain] = ClassData[classindex][Class_HealthInfectGain]; ClassDataCache[classindex][Class_KillBonus] = ClassData[classindex][Class_KillBonus]; ClassDataCache[classindex][Class_Speed] = ClassData[classindex][Class_Speed]; ClassDataCache[classindex][Class_KnockBack] = ClassData[classindex][Class_KnockBack]; ClassDataCache[classindex][Class_JumpHeight] = ClassData[classindex][Class_JumpHeight]; ClassDataCache[classindex][Class_JumpDistance] = ClassData[classindex][Class_JumpDistance]; } return true; } /** * Refresh the specified player's cache from the specified class data cache. * * @param client The client index. * @param classindex The index of the class to read from. * @param cachetype Optional. Specifies what class cache to read from. * Options: ZR_CLASS_CACHE_ORIGINAL (unchanged class * data), ZR_CLASS_CACHE_MODIFIED (default, modified class * data). * @return True if successful, false otherwise. */ bool:ClassReloadPlayerCache(client, classindex, cachetype = ZR_CLASS_CACHE_MODIFIED) { // Validate indexes. if (!ClassValidateIndex(classindex) || !ZRIsClientValid(client)) { return false; } switch (cachetype) { case ZR_CLASS_CACHE_ORIGINAL: { /* General */ ClassPlayerCache[client][Class_Enabled] = ClassData[classindex][Class_Enabled]; ClassPlayerCache[client][Class_Team] = ClassData[classindex][Class_Team]; ClassPlayerCache[client][Class_TeamDefault] = ClassData[classindex][Class_TeamDefault]; ClassPlayerCache[client][Class_Flags] = ClassData[classindex][Class_Flags]; strcopy(ClassPlayerCache[client][Class_Group], 64, ClassData[classindex][Class_Group]); strcopy(ClassPlayerCache[client][Class_Name], 64, ClassData[classindex][Class_Name]); strcopy(ClassPlayerCache[client][Class_Description], 256, ClassData[classindex][Class_Description]); /* Model */ strcopy(ClassPlayerCache[client][Class_ModelPath], PLATFORM_MAX_PATH, ClassData[classindex][Class_ModelPath]); ClassPlayerCache[client][Class_AlphaInitial] = ClassData[classindex][Class_AlphaInitial]; ClassPlayerCache[client][Class_AlphaDamaged] = ClassData[classindex][Class_AlphaDamaged]; ClassPlayerCache[client][Class_AlphaDamage] = ClassData[classindex][Class_AlphaDamage]; /* Hud */ strcopy(ClassPlayerCache[client][Class_OverlayPath], PLATFORM_MAX_PATH, ClassData[classindex][Class_OverlayPath]); ClassPlayerCache[client][Class_Nvgs] = ClassData[classindex][Class_Nvgs]; ClassPlayerCache[client][Class_Fov] = ClassData[classindex][Class_Fov]; /* Effects */ ClassPlayerCache[client][Class_HasNapalm] = ClassData[classindex][Class_HasNapalm]; ClassPlayerCache[client][Class_NapalmTime] = ClassData[classindex][Class_NapalmTime]; /* Player behaviour */ ClassPlayerCache[client][Class_ImmunityMode] = ClassData[classindex][Class_ImmunityMode]; ClassPlayerCache[client][Class_ImmunityAmount] = ClassData[classindex][Class_ImmunityAmount]; ClassPlayerCache[client][Class_NoFallDamage] = ClassData[classindex][Class_NoFallDamage]; ClassPlayerCache[client][Class_Health] = ClassData[classindex][Class_Health]; ClassPlayerCache[client][Class_HealthRegenInterval] = ClassData[classindex][Class_HealthRegenInterval]; ClassPlayerCache[client][Class_HealthRegenAmount] = ClassData[classindex][Class_HealthRegenAmount]; ClassPlayerCache[client][Class_HealthInfectGain] = ClassData[classindex][Class_HealthInfectGain]; ClassPlayerCache[client][Class_KillBonus] = ClassData[classindex][Class_KillBonus]; ClassPlayerCache[client][Class_Speed] = ClassData[classindex][Class_Speed]; ClassPlayerCache[client][Class_KnockBack] = ClassData[classindex][Class_KnockBack]; ClassPlayerCache[client][Class_JumpHeight] = ClassData[classindex][Class_JumpHeight]; ClassPlayerCache[client][Class_JumpDistance] = ClassData[classindex][Class_JumpDistance]; } case ZR_CLASS_CACHE_MODIFIED: { /* General */ ClassPlayerCache[client][Class_Enabled] = ClassDataCache[classindex][Class_Enabled]; ClassPlayerCache[client][Class_Team] = ClassDataCache[classindex][Class_Team]; ClassPlayerCache[client][Class_TeamDefault] = ClassDataCache[classindex][Class_TeamDefault]; ClassPlayerCache[client][Class_Flags] = ClassDataCache[classindex][Class_Flags]; strcopy(ClassPlayerCache[client][Class_Group], 64, ClassDataCache[classindex][Class_Group]); strcopy(ClassPlayerCache[client][Class_Name], 64, ClassDataCache[classindex][Class_Name]); strcopy(ClassPlayerCache[client][Class_Description], 256, ClassDataCache[classindex][Class_Description]); /* Model */ strcopy(ClassPlayerCache[client][Class_ModelPath], PLATFORM_MAX_PATH, ClassDataCache[classindex][Class_ModelPath]); ClassPlayerCache[client][Class_AlphaInitial] = ClassDataCache[classindex][Class_AlphaInitial]; ClassPlayerCache[client][Class_AlphaDamaged] = ClassDataCache[classindex][Class_AlphaDamaged]; ClassPlayerCache[client][Class_AlphaDamage] = ClassDataCache[classindex][Class_AlphaDamage]; /* Hud */ strcopy(ClassPlayerCache[client][Class_OverlayPath], PLATFORM_MAX_PATH, ClassDataCache[classindex][Class_OverlayPath]); ClassPlayerCache[client][Class_Nvgs] = ClassDataCache[classindex][Class_Nvgs]; ClassPlayerCache[client][Class_Fov] = ClassDataCache[classindex][Class_Fov]; /* Effects */ ClassPlayerCache[client][Class_HasNapalm] = ClassDataCache[classindex][Class_HasNapalm]; ClassPlayerCache[client][Class_NapalmTime] = ClassDataCache[classindex][Class_NapalmTime]; /* Player behaviour */ ClassPlayerCache[client][Class_ImmunityMode] = ClassDataCache[classindex][Class_ImmunityMode]; ClassPlayerCache[client][Class_ImmunityAmount] = ClassDataCache[classindex][Class_ImmunityAmount]; ClassPlayerCache[client][Class_NoFallDamage] = ClassDataCache[classindex][Class_NoFallDamage]; ClassPlayerCache[client][Class_Health] = ClassDataCache[classindex][Class_Health]; ClassPlayerCache[client][Class_HealthRegenInterval] = ClassDataCache[classindex][Class_HealthRegenInterval]; ClassPlayerCache[client][Class_HealthRegenAmount] = ClassDataCache[classindex][Class_HealthRegenAmount]; ClassPlayerCache[client][Class_HealthInfectGain] = ClassDataCache[classindex][Class_HealthInfectGain]; ClassPlayerCache[client][Class_KillBonus] = ClassDataCache[classindex][Class_KillBonus]; ClassPlayerCache[client][Class_Speed] = ClassDataCache[classindex][Class_Speed]; ClassPlayerCache[client][Class_KnockBack] = ClassDataCache[classindex][Class_KnockBack]; ClassPlayerCache[client][Class_JumpHeight] = ClassDataCache[classindex][Class_JumpHeight]; ClassPlayerCache[client][Class_JumpDistance] = ClassDataCache[classindex][Class_JumpDistance]; } default: { // Invalid cache specified. return false; } } return true; } /** * Refresh the specified player's cache and re-apply attributes. * * @param client The client index. * @return True if successful, false otherwise. */ bool:ClassReloadPlayer(client) { new activeclass; // Get active class index. activeclass = ClassGetActiveIndex(client); // Validate index. if (activeclass < 0) { return false; } // Refresh cache and re-apply attributes. ClassOnClientDeath(client); // Dummy event to clean up and turn off stuff. ClassReloadPlayerCache(client, activeclass); ClassApplyAttributes(client); return true; } /** * Reset all class attribute multipliers to 1.0. */ ClassResetMultiplierCache() { // Loop through all teams. for (new teamid = 0; teamid < ZR_CLASS_TEAMCOUNT; teamid++) { ClassMultiplierCache[teamid][ClassM_NapalmTime] = 1.0; ClassMultiplierCache[teamid][ClassM_Health] = 1.0; ClassMultiplierCache[teamid][ClassM_HealthRegenInterval] = 1.0; ClassMultiplierCache[teamid][ClassM_HealthRegenAmount] = 1.0; ClassMultiplierCache[teamid][ClassM_HealthInfectGain] = 1.0; ClassMultiplierCache[teamid][ClassM_Speed] = 1.0; ClassMultiplierCache[teamid][ClassM_Knockback] = 1.0; ClassMultiplierCache[teamid][ClassM_JumpHeight] = 1.0; ClassMultiplierCache[teamid][ClassM_JumpDistance] = 1.0; } } /** * Resets the selected class indexes for next spawn on one or all clients. * * @param client Optional. Specify client to reset. Default is all. */ ClassResetNextIndexes(client = -1) { new teamid; if (client > 0) { for (teamid = 0; teamid < ZR_CLASS_TEAMCOUNT; teamid++) { ClassSelectedNext[client][teamid] = -1; } } else { for (client = 1; client <= MAXPLAYERS; client++) { for (teamid = 0; teamid < ZR_CLASS_TEAMCOUNT; teamid++) { ClassSelectedNext[client][teamid] = -1; } } } } /** * Restores next class indexes on a player, if available. * Note: Does not apply attributes. The classes are only marked as selected. * * @param client The client index. * @param excludeTeam Optional. Do not restore the specified team. */ ClassRestoreNextIndexes(client, excludeTeam = -1) { // Get next class indexes. new zombie = ClassSelectedNext[client][ZR_CLASS_TEAM_ZOMBIES]; new human = ClassSelectedNext[client][ZR_CLASS_TEAM_HUMANS]; new admin = ClassSelectedNext[client][ZR_CLASS_TEAM_ADMINS]; // Check if the zombie team should be excluded. if (excludeTeam != ZR_CLASS_TEAM_ZOMBIES) { // Validate zombie class index. if (ClassValidateIndex(zombie)) { // Mark next zombie class as selected. ClassSelected[client][ZR_CLASS_TEAM_ZOMBIES] = zombie; } // Reset index. ClassSelectedNext[client][ZR_CLASS_TEAM_ZOMBIES] = -1; } // Check if the human team should be excluded. if (excludeTeam != ZR_CLASS_TEAM_HUMANS) { // Validate human class index. if (ClassValidateIndex(human)) { // Mark next zombie class as selected. ClassSelected[client][ZR_CLASS_TEAM_HUMANS] = human; } // Reset index. ClassSelectedNext[client][ZR_CLASS_TEAM_HUMANS] = -1; } // Check if the human team should be excluded. if (excludeTeam != ZR_CLASS_TEAM_ADMINS) { // Validate admin class index. if (ClassValidateIndex(admin)) { // Mark next zombie class as selected. ClassSelected[client][ZR_CLASS_TEAM_ADMINS] = admin; } // Reset index. ClassSelectedNext[client][ZR_CLASS_TEAM_ADMINS] = -1; } } /** * Sets default class indexes for each team on all players, or a single player * if specified. * * @param client Optional. The client index. If specified, cookies are used. */ ClassClientSetDefaultIndexes(client = -1) { new bool:clientvalid = ZRIsClientValid(client); new filter[ClassFilter]; new bool:saveclasses = GetConVarBool(g_hCvarsList[CVAR_CLASSES_SAVE]); new zombieindex; new humanindex; new adminindex; new bool:haszombie; new bool:hashuman; new bool:hasadmin; // Check if a client is specified. if (clientvalid) { // Get cookie indexes if enabled. if (saveclasses) { zombieindex = CookiesGetInt(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ZOMBIES]); humanindex = CookiesGetInt(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_HUMANS]); adminindex = CookiesGetInt(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ADMINS]); } else { // Do not use indexes in cookies. Set invalid values so it will // fall back to default class. zombieindex = 0; humanindex = 0; adminindex = 0; } // Note: When class indexes are set on cookies, they're incremented by // one so zero means no class set and will result in a invalid // class index when restored. // Setup filtering so group permission is checked on player first. filter[ClassFilter_Client] = client; // Check if class indexes are set and that the client pass group // permissions. If not, fall back to default class indexes. Otherwise // substract index by one. if (zombieindex <= 0 || !ClassFilterMatch(zombieindex, filter)) { zombieindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ZOMBIES, filter); } else { zombieindex--; haszombie = true; } if (humanindex <= 0 || !ClassFilterMatch(humanindex, filter)) { humanindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_HUMANS, filter); } else { humanindex--; hashuman = true; } if (adminindex <= 0 || !ClassFilterMatch(adminindex, filter)) { adminindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ADMINS, filter); } else { adminindex--; hasadmin = true; } } else { // Setup filtering so classes with groups set are excluded. filter[ClassFilter_Client] = -1; // Get default class indexes. zombieindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ZOMBIES, filter); humanindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_HUMANS, filter); adminindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ADMINS, filter); } // Validate indexes. if (!ClassValidateIndex(zombieindex)) { // Invalid class index. Fall back to default class in class config and // log a warning. LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Playerclasses, "Set Default Indexes", "Warning: Failed to get specified zombie class, falling back to default class in class config. Check spelling in \"zr_classes_default_zombie\"."); // Use default class. zombieindex = ClassGetDefaultClass(ZR_CLASS_TEAM_ZOMBIES, filter); } // Get human class index. if (!ClassValidateIndex(humanindex)) { // Invalid class index. Fall back to default class in class config and // log a warning. LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Playerclasses, "Set Default Indexes", "Warning: Failed to get specified human class, falling back to default class in class config. Check spelling in \"zr_classes_default_human\"."); // Use default class. humanindex = ClassGetDefaultClass(ZR_CLASS_TEAM_HUMANS, filter); } // Get admin class index. if (!ClassValidateIndex(adminindex)) { // Invalid class index. Fall back to default class in class config if // possible. A invalid class index (-1) can also be stored if there are // no admin classes at all. adminindex = ClassGetDefaultClass(ZR_CLASS_TEAM_ADMINS, filter); } // Check if a client is specified. if (clientvalid) { // Set selected class idexes. ClassSelected[client][ZR_CLASS_TEAM_ZOMBIES] = zombieindex; ClassSelected[client][ZR_CLASS_TEAM_HUMANS] = humanindex; ClassSelected[client][ZR_CLASS_TEAM_ADMINS] = adminindex; // Copy human class data to player cache. ClassReloadPlayerCache(client, humanindex); // Save indexes in cookies if enabled, and not already saved. if (saveclasses) { if (!haszombie) { CookiesSetInt(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ZOMBIES], zombieindex + 1); } if (!hashuman) { CookiesSetInt(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_HUMANS], humanindex + 1); } if (!hasadmin) { CookiesSetInt(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ADMINS], adminindex + 1); } } } else { // No client specified. Loop through all players. for (new clientindex = 1; clientindex <= MaxClients; clientindex++) { // Set selected class idexes. ClassSelected[clientindex][ZR_CLASS_TEAM_ZOMBIES] = zombieindex; ClassSelected[clientindex][ZR_CLASS_TEAM_HUMANS] = humanindex; ClassSelected[clientindex][ZR_CLASS_TEAM_ADMINS] = adminindex; // Copy human class data to player cache. ClassReloadPlayerCache(client, humanindex); } } } /** * Dump class data into a string. String buffer length should be at about 2048 * cells. * * @param index Index of the class in a class cache or a client index, * depending on the cache type specified. * @param cachetype Optional. Specifies what class cache to read from. Options: * ZR_CLASS_CACHE_ORIGINAL - Unchanced class data. * ZR_CLASS_CACHE_MODIFIED (default) - Changed/newest class * data. * ZR_CLASS_CACHE_PLAYER - Player cache. If this one is used, * index will be used as a client index. * @return Number of cells written. */ ClassDumpData(index, cachetype, String:buffer[], maxlen) { new cellcount; decl String:attribute[320]; decl String:format_buffer[256]; if (maxlen == 0) { return 0; } Format(format_buffer, sizeof(format_buffer), "Class data at index %d:\n", index); cellcount += StrCat(buffer, maxlen, format_buffer); cellcount += StrCat(buffer, maxlen, "-------------------------------------------------------------------------------\n"); Format(attribute, sizeof(attribute), "enabled: \"%d\"\n", ClassIsEnabled(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "team: \"%d\"\n", ClassGetTeamID(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "team_default: \"%d\"\n", ClassGetTeamDefault(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "flags: \"%d\"\n", ClassGetFlags(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); ClassGetGroup(index, format_buffer, sizeof(format_buffer), cachetype); Format(attribute, sizeof(attribute), "group: \"%s\"\n", format_buffer); cellcount += StrCat(buffer, maxlen, attribute); ClassGetName(index, format_buffer, sizeof(format_buffer), cachetype); Format(attribute, sizeof(attribute), "name: \"%s\"\n", format_buffer); cellcount += StrCat(buffer, maxlen, attribute); ClassGetDescription(index, format_buffer, sizeof(format_buffer), cachetype); Format(attribute, sizeof(attribute), "description: \"%s\"\n", format_buffer); cellcount += StrCat(buffer, maxlen, attribute); ClassGetModelPath(index, format_buffer, sizeof(format_buffer), cachetype); Format(attribute, sizeof(attribute), "model_path: \"%s\"\n", format_buffer); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "alpha_initial: \"%d\"\n", ClassGetAlphaInitial(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "alpha_damaged: \"%d\"\n", ClassGetAlphaDamaged(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "alpha_damage: \"%d\"\n", ClassGetAlphaDamage(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); ClassGetOverlayPath(index, format_buffer, sizeof(format_buffer), cachetype); Format(attribute, sizeof(attribute), "overlay_path: \"%s\"\n", format_buffer); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "nvgs: \"%d\"\n", ClassGetNvgs(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "fov: \"%d\"\n", ClassGetFOV(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "has_napalm: \"%d\"\n", ClassGetHasNapalm(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "napalm_time: \"%f\"\n", ClassGetNapalmTime(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "immunity_mode: \"%d\"\n", ClassGetImmunityMode(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "immunity_amount: \"%f\"\n", ClassGetImmunityAmount(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "no_fall_damage: \"%d\"\n", ClassGetNoFallDamage(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "health: \"%d\"\n", ClassGetHealth(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "health_regen_interval: \"%f\"\n", ClassGetHealthRegenInterval(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "health_regen_amount: \"%d\"\n", ClassGetHealthRegenAmount(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "health_infect_gain: \"%d\"\n", ClassGetHealthInfectGain(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "kill_bonus: \"%d\"\n", ClassGetKillBonus(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "speed: \"%f\"\n", ClassGetSpeed(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "knockback: \"%f\"\n", ClassGetKnockback(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "jump_height: \"%f\"\n", ClassGetJumpHeight(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); Format(attribute, sizeof(attribute), "jump_distance: \"%f\"\n", ClassGetJumpDistance(index, cachetype)); cellcount += StrCat(buffer, maxlen, attribute); return cellcount; } /** * Converts a multiplier attribute to a human readable string. * * @param client The client index to translate correct language. * @param attribute Attribute to convert. * @param buffer Destination string buffer. * @param maxlen Size of buffer. * @return Number of cells written. */ ClassMultiplierToString(client, ClassMultipliers:attribute, String:buffer[], maxlen) { decl String:phrase[48]; SetGlobalTransTarget(client); switch (attribute) { case ClassM_NapalmTime: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Napalm Time"); return strcopy(buffer, maxlen, phrase); } case ClassM_Health: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Health"); return strcopy(buffer, maxlen, phrase); } case ClassM_HealthRegenInterval: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Regen Interval"); return strcopy(buffer, maxlen, phrase); } case ClassM_HealthRegenAmount: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Regen Amount"); return strcopy(buffer, maxlen, phrase); } case ClassM_HealthInfectGain: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Infect Gain"); return strcopy(buffer, maxlen, phrase); } case ClassM_Speed: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Speed"); return strcopy(buffer, maxlen, phrase); } case ClassM_Knockback: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Knockback"); return strcopy(buffer, maxlen, phrase); } case ClassM_JumpHeight: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Jump Height"); return strcopy(buffer, maxlen, phrase); } case ClassM_JumpDistance: { Format(phrase, sizeof(phrase), "%t", "Classes Attrib Jump Distance"); return strcopy(buffer, maxlen, phrase); } } return 0; }