sm-zombiereloaded-3/src/zr/playerclasses/playerclasses.inc

1555 lines
61 KiB
SourcePawn

/*
* ============================================================================
*
* Zombie:Reloaded
*
* File: playerclasses.inc
* Type: Core
* Description: Provides functions for managing classes.
*
* Copyright (C) 2009-2013 Greyscale, Richard Helgeby
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* ============================================================================
*/
/*
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.
*/
#define ZR_CLASS_MAX 64
/**
* @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_SM_FLAGS ""
#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_MODEL_SKIN_INDEX 0
#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 "none"
#define ZR_CLASS_DEFAULT_IMMUNITY_AMOUNT 1
#define ZR_CLASS_DEFAULT_IMMUNITY_COOLDOWN 60
#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 0.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_IDENTIFIER_MIN 1
#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_MODEL_SKIN_INDEX_MIN 0
#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_IMMUNITY_COOLDOWN_MIN 0
#define ZR_CLASS_IMMUNITY_COOLDOWN_MAX 600
#define ZR_CLASS_HEALTH_MIN 1
#define ZR_CLASS_HEALTH_MAX 100000
#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_LMV_MIN 10.0
#define ZR_CLASS_SPEED_LMV_MAX 2000.0
#define ZR_CLASS_SPEED_PROP_MIN -200.0
#define ZR_CLASS_SPEED_PROP_MAX 1750.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_IDENTIFIER (1<<0)
#define ZR_CLASS_ENABLED (1<<1)
#define ZR_CLASS_TEAM (1<<2)
#define ZR_CLASS_TEAM_DEFAULT (1<<3)
#define ZR_CLASS_FLAGS (1<<4)
#define ZR_CLASS_GROUP (1<<5)
#define ZR_CLASS_SM_FLAGS (1<<6)
#define ZR_CLASS_NAME (1<<7)
#define ZR_CLASS_DESCRIPTION (1<<8)
#define ZR_CLASS_MODEL_PATH (1<<9)
#define ZR_CLASS_MODEL_SKIN_INDEX (1<<10)
#define ZR_CLASS_ALPHA_INITIAL (1<<11)
#define ZR_CLASS_ALPHA_DAMAGED (1<<12)
#define ZR_CLASS_ALPHA_DAMAGE (1<<13)
#define ZR_CLASS_OVERLAY_PATH (1<<14)
#define ZR_CLASS_NVGS (1<<15)
#define ZR_CLASS_FOV (1<<16)
#define ZR_CLASS_HAS_NAPALM (1<<17)
#define ZR_CLASS_NAPALM_TIME (1<<18)
#define ZR_CLASS_IMMUNITY_MODE (1<<19)
#define ZR_CLASS_IMMUNITY_AMOUNT (1<<20)
#define ZR_CLASS_IMMUNITY_COOLDOWN (1<<21)
#define ZR_CLASS_NO_FALL_DAMAGE (1<<22)
#define ZR_CLASS_HEALTH (1<<23)
#define ZR_CLASS_HEALTH_REGEN_INTERVAL (1<<24)
#define ZR_CLASS_HEALTH_REGEN_AMOUNT (1<<25)
#define ZR_CLASS_HEALTH_INFECT_GAIN (1<<26)
#define ZR_CLASS_KILL_BONUS (1<<27)
#define ZR_CLASS_SPEED (1<<28)
#define ZR_CLASS_KNOCKBACK (1<<29)
#define ZR_CLASS_JUMP_HEIGHT (1<<30)
#define ZR_CLASS_JUMP_DISTANCE (1<<31)
/**
* @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
* VolEmptyAttributes
* Update docs with detailed attribute description
*/
enum ClassAttributes
{
/* General */
String:Class_Identifier[64],
bool:Class_Enabled,
Class_Team,
bool:Class_TeamDefault,
Class_Flags,
String:Class_Group[64],
String:Class_SM_Flags[64],
String:Class_Name[64],
String:Class_Description[256],
/* Model */
String:Class_ModelPath[PLATFORM_MAX_PATH],
Class_ModelSkinIndex,
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 */
ImmunityMode:Class_ImmunityMode,
Class_ImmunityAmount,
Class_ImmunityCooldown,
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_ModelSkinIndex = 0,
ClassEdit_AlphaInitial,
ClassEdit_AlphaDamaged,
ClassEdit_AlphaDamage,
/* Hud */
String:ClassEdit_OverlayPath[PLATFORM_MAX_PATH],
ClassEdit_Nvgs,
ClassEdit_Fov,
/* Effects */
ClassEdit_HasNapalm,
Float:ClassEdit_NapalmTime,
/* Player behavior */
ImmunityMode:ClassEdit_ImmunityMode,
ClassEdit_ImmunityAmount,
ClassEdit_ImmunityCooldown,
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. */
}
/**
* Speed methods for applying player speed.
*/
enum ClassSpeedMethods
{
ClassSpeed_Invalid = -1,
ClassSpeed_LMV, /** Modifies lagged movement value. */
ClassSpeed_Prop, /** Modifies players' max speed property(m_flMaxspeed). */
}
/**
* Results when selecting a class for a player.
*/
enum ClassSelectResult
{
ClassSelected_NoChange, /** No class change was necessary (class already selected). */
ClassSelected_Instant, /** Class was instantly changed. */
ClassSelected_NextSpawn /** Class will be used next spawn. */
}
/**
* Empty filter structure.
*/
new ClassNoFilter[ClassFilter];
/**
* Filter structure for excluding special classes.
*/
new ClassNoSpecialClasses[ClassFilter] = {false, 0, ZR_CLASS_SPECIALFLAGS, -1};
/**
* The original class data. This array is 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 data 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 whether the class team requirements and attributes are valid or not.
* Used to block events that happend before the module is done loading.
*/
new bool:ClassValidated;
/**
* 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];
/**
* Specifies whether a player has spawned.
*/
new bool:ClassPlayerSpawned[MAXPLAYERS + 1];
/**
* What method used to apply speed on players.
*/
new ClassSpeedMethods:ClassSpeedMethod = ClassSpeed_Prop;
#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()
{
// Register config file.
ConfigRegisterConfig(File_Classes, Structure_Keyvalue, CONFIG_FILE_ALIAS_CLASSES);
new Handle:kvClassData;
// Make sure kvClassData is ready to use.
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);
}
new String:section_name[64];
new String:name[64];
new String:group[64];
new String:sm_flags[64];
new String:description[256];
new String:model_path[PLATFORM_MAX_PATH];
new String:immunity_mode[32];
new 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;
}
/* Class identifier = section name */
KvGetSectionName(kvClassData, section_name, sizeof(section_name));
strcopy(ClassData[ClassCount][Class_Identifier], 64, section_name);
/* 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, "sm_flags", sm_flags, sizeof(sm_flags), ZR_CLASS_DEFAULT_SM_FLAGS);
strcopy(ClassData[ClassCount][Class_SM_Flags], 64, sm_flags);
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_ModelSkinIndex] = KvGetNum(kvClassData, "model_skin_index", ZR_CLASS_DEFAULT_MODEL_SKIN_INDEX);
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 */
KvGetString(kvClassData, "immunity_mode", immunity_mode, sizeof(immunity_mode), ZR_CLASS_DEFAULT_IMMUNITY_MODE);
ClassData[ClassCount][Class_ImmunityMode] = ImmunityStringToMode(immunity_mode);
ClassData[ClassCount][Class_ImmunityAmount] = KvGetNum(kvClassData, "immunity_amount", ZR_CLASS_DEFAULT_IMMUNITY_AMOUNT);
ClassData[ClassCount][Class_ImmunityCooldown] = KvGetNum(kvClassData, "immunity_cooldown", ZR_CLASS_DEFAULT_IMMUNITY_COOLDOWN);
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 class attributes if class is enabled.
if (ClassData[ClassCount][Class_Enabled])
{
ClassErrorFlags = ClassValidateAttributes(ClassCount, true);
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();
// 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();
}
/**
* Gets the speed method.
*
* @return Speed method, or ClassSpeed_Invalid on error.
*/
ClassSpeedMethods:ClassGetSpeedMethod()
{
decl String:speedMethod[16];
speedMethod[0] = 0;
GetConVarString(g_hCvarsList[CVAR_CLASSES_SPEED_METHOD], speedMethod, sizeof(speedMethod));
if (StrEqual(speedMethod, "lmv", false))
{
return ClassSpeed_LMV;
}
else if (StrEqual(speedMethod, "prop", false))
{
return ClassSpeed_Prop;
}
return ClassSpeed_Invalid;
}
/**
* 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 */
strcopy(ClassDataCache[classindex][Class_Identifier], 64, ClassData[classindex][Class_Identifier]);
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_SM_Flags], 64, ClassData[classindex][Class_SM_Flags]);
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_ModelSkinIndex] = ClassData[classindex][Class_ModelSkinIndex];
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 behavior */
ClassDataCache[classindex][Class_ImmunityMode] = ClassData[classindex][Class_ImmunityMode];
ClassDataCache[classindex][Class_ImmunityAmount] = ClassData[classindex][Class_ImmunityAmount];
ClassDataCache[classindex][Class_ImmunityCooldown] = ClassData[classindex][Class_ImmunityCooldown];
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 */
strcopy(ClassPlayerCache[client][Class_Identifier], 64, ClassData[classindex][Class_Identifier]);
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_SM_Flags], 64, ClassData[classindex][Class_SM_Flags]);
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_ModelSkinIndex] = ClassData[classindex][Class_ModelSkinIndex];
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 behavior */
ClassPlayerCache[client][Class_ImmunityMode] = ClassData[classindex][Class_ImmunityMode];
ClassPlayerCache[client][Class_ImmunityAmount] = ClassData[classindex][Class_ImmunityAmount];
ClassPlayerCache[client][Class_ImmunityCooldown] = ClassData[classindex][Class_ImmunityCooldown];
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 */
strcopy(ClassPlayerCache[client][Class_Identifier], 64, ClassDataCache[classindex][Class_Identifier]);
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_SM_Flags], 64, ClassDataCache[classindex][Class_SM_Flags]);
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_ModelSkinIndex] = ClassDataCache[classindex][Class_ModelSkinIndex];
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 behavior */
ClassPlayerCache[client][Class_ImmunityMode] = ClassDataCache[classindex][Class_ImmunityMode];
ClassPlayerCache[client][Class_ImmunityAmount] = ClassDataCache[classindex][Class_ImmunityAmount];
ClassPlayerCache[client][Class_ImmunityCooldown] = ClassDataCache[classindex][Class_ImmunityCooldown];
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 identifiers for each team on all players, or a single player
* if specified.
*
* @param client Optional. The client index. If specified, cookies are used.
*/
ClassClientSetDefaultIdentifiers(client = -1)
{
new bool:clientvalid = ZRIsClientValid(client);
new filter[ClassFilter];
new filter_valid[ClassFilter];
new bool:saveclasses = GetConVarBool(g_hCvarsList[CVAR_CLASSES_SAVE]);
char zombie_ident[64];
char human_ident[64];
char admin_ident[64];
new zombieindex = -1;
new humanindex = -1;
new adminindex = -1;
new bool:haszombie;
new bool:hashuman;
new bool:hasadmin;
/*
* SETUP CLASS FILTER
*/
// Do not require class to be enabled.
// Disabled classes should not be deleted from the client cookies.
// We'll manually check if the class is disabled later.
filter_valid[ClassFilter_IgnoreEnabled] = true;
// Do not require any class flags to be set.
filter[ClassFilter_RequireFlags] = 0;
filter_valid[ClassFilter_RequireFlags] = 0;
// Set filter to hide mother zombie classes.
filter[ClassFilter_DenyFlags] = ZR_CLASS_FLAG_MOTHER_ZOMBIE;
filter_valid[ClassFilter_DenyFlags] = ZR_CLASS_FLAG_MOTHER_ZOMBIE;
// Set filter to also hide admin-only classes if not admin.
filter[ClassFilter_DenyFlags] += !ZRIsClientAdmin(client) ? ZR_CLASS_FLAG_ADMIN_ONLY : 0;
filter_valid[ClassFilter_DenyFlags] += !ZRIsClientAdmin(client) ? ZR_CLASS_FLAG_ADMIN_ONLY : 0;
// Specify client so it can check group permissions.
filter[ClassFilter_Client] = client;
filter_valid[ClassFilter_Client] = client;
/*
* GET CLASS INDEXES
*/
// Check if a client is specified.
if (clientvalid)
{
// Get cookie indexes if enabled.
if (saveclasses)
{
GetClientCookie(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ZOMBIES], zombie_ident, sizeof(zombie_ident));
GetClientCookie(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_HUMANS], human_ident, sizeof(human_ident));
GetClientCookie(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ADMINS], admin_ident, sizeof(admin_ident));
zombieindex = ClassGetIndexByIdentifier(zombie_ident);
humanindex = ClassGetIndexByIdentifier(human_ident);
adminindex = ClassGetIndexByIdentifier(admin_ident);
}
// Check if class identifiers are set and that the client pass the filter.
// Also check that the saved class' team id match with the loaded class.
// If not, fall back to default class indexes.
if (zombieindex < 0 ||
!ClassTeamCompare(zombieindex, ZR_CLASS_TEAM_ZOMBIES) ||
!ClassFilterMatch(zombieindex, filter_valid))
{
zombieindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ZOMBIES, filter);
}
else
{
haszombie = true;
if (!ClassIsEnabled(zombieindex))
{
zombieindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ZOMBIES, filter);
}
}
if (humanindex < 0 ||
!ClassTeamCompare(humanindex, ZR_CLASS_TEAM_HUMANS) ||
!ClassFilterMatch(humanindex, filter_valid))
{
humanindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_HUMANS, filter);
}
else
{
hashuman = true;
if (!ClassIsEnabled(humanindex))
{
humanindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_HUMANS, filter);
}
}
if (adminindex < 0 ||
!ClassTeamCompare(adminindex, ZR_CLASS_TEAM_ADMINS) ||
!ClassFilterMatch(adminindex, filter_valid))
{
adminindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ADMINS, filter);
}
else
{
hasadmin = true;
if (!ClassIsEnabled(adminindex))
{
adminindex = ClassGetDefaultSpawnClass(ZR_CLASS_TEAM_ADMINS, filter);
}
}
}
else
{
// Set filter to exclude classes that require groups.
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 the 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);
}
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 the 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);
}
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);
}
/*
* MARK INDEXES AS SELECTED, UPDATE CACHE AND COOKIES
*/
// 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 class data to player cache.
if (InfectIsClientInfected(client))
ClassReloadPlayerCache(client, zombieindex);
else
ClassReloadPlayerCache(client, humanindex);
// Save indexes in cookies if enabled, and not already saved.
if (saveclasses)
{
if (!haszombie && zombieindex != -1)
{
ClassGetIdentifier(zombieindex, zombie_ident, sizeof(zombie_ident), ZR_CLASS_CACHE_MODIFIED);
SetClientCookie(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ZOMBIES], zombie_ident);
}
if (!hashuman && humanindex != -1)
{
ClassGetIdentifier(humanindex, human_ident, sizeof(human_ident), ZR_CLASS_CACHE_MODIFIED);
SetClientCookie(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_HUMANS], human_ident);
}
if (!hasadmin && adminindex != -1)
{
ClassGetIdentifier(adminindex, admin_ident, sizeof(admin_ident), ZR_CLASS_CACHE_MODIFIED);
SetClientCookie(client, g_hClassCookieClassSelected[ZR_CLASS_TEAM_ADMINS], admin_ident);
}
}
}
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);
}
}
}
/**
* Selects a class for a player.
*
* Human class attribute may be instantly applied if player is alive, human and
* instant class change is enabled. Otherwise only the selected index will be
* updated for next spawn.
*
* Class selection will be saved in client cookies if enabled.
*
* @param client Client index.
* @param classIndex Class index.
* @param applyIfPossible Optional. Apply class attributes if conditions allow
* it. Default is true.
* @param saveIfEnabled Optional. Save class selection in client cookies if
* enabled. Default is true.
*
* @return Class selection result. See enum ClassSelectResult.
*/
ClassSelectResult:ClassSelectClientClass(client, classIndex, bool:applyIfPossible = true, bool:saveIfEnabled = true)
{
new bool:iszombie = InfectIsClientInfected(client);
new teamid = ClassGetTeamID(classIndex, ZR_CLASS_CACHE_MODIFIED);
new ClassSelectResult:selectResult = ClassSelected_NoChange;
// Allow instant class change if enabled and both class and player is human.
if (applyIfPossible &&
ClassAllowInstantChange[client] &&
!iszombie && teamid == ZR_CLASS_TEAM_HUMANS)
{
// Update selected class index.
ClassSelected[client][teamid] = classIndex;
// Update cache and apply attributes.
ClassReloadPlayerCache(client, classIndex);
ClassApplyAttributes(client);
selectResult = ClassSelected_Instant;
}
else
{
// Set next spawn index if the player is changing the class on
// his active team.
if (IsPlayerAlive(client) &&
(iszombie && teamid == ZR_CLASS_TEAM_ZOMBIES) ||
(!iszombie && teamid == ZR_CLASS_TEAM_HUMANS) ||
(ClassPlayerInAdminMode[client] && teamid == ZR_CLASS_TEAM_ADMINS))
{
// Check if selecting the same class that the player already is.
if (ClassSelected[client][teamid] == classIndex)
{
// Player is already the specified class. No need to change
// class next spawn.
ClassSelectedNext[client][teamid] = -1;
selectResult = ClassSelected_NoChange;
}
else
{
// Set class to be used on next spawn.
ClassSelectedNext[client][teamid] = classIndex;
selectResult = ClassSelected_NextSpawn;
}
}
else
{
// Directly change the selected class index.
ClassSelected[client][teamid] = classIndex;
selectResult = ClassSelected_NextSpawn;
}
}
// Save selected class index in cookie if enabled.
// Note: Saved indexes are increased by one.
if (saveIfEnabled && GetConVarBool(g_hCvarsList[CVAR_CLASSES_SAVE]))
{
char classIdent[64];
ClassGetIdentifier(classIndex, classIdent, sizeof(classIdent), ZR_CLASS_CACHE_MODIFIED);
SetClientCookie(client, g_hClassCookieClassSelected[teamid], classIdent);
}
return selectResult;
}
/**
* 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");
ClassGetIdentifier(index, format_buffer, sizeof(format_buffer), cachetype);
Format(attribute, sizeof(attribute), "identifier: \"%s\"\n", format_buffer);
cellcount += StrCat(buffer, maxlen, attribute);
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);
ClassGetSM_Flags(index, format_buffer, sizeof(format_buffer), cachetype);
Format(attribute, sizeof(attribute), "sm_flags: \"%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), "model_skin_index: \"%d\"\n", ClassGetModelSkinIndex(index, cachetype));
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);
ImmunityModeToString(ClassGetImmunityMode(index, cachetype), format_buffer, sizeof(format_buffer));
Format(attribute, sizeof(attribute), "immunity_mode: \"%s\"\n", format_buffer);
cellcount += StrCat(buffer, maxlen, attribute);
Format(attribute, sizeof(attribute), "immunity_amount: \"%d\"\n", ClassGetImmunityAmount(index, cachetype));
cellcount += StrCat(buffer, maxlen, attribute);
Format(attribute, sizeof(attribute), "immunity_cooldown: \"%d\"\n", ClassGetImmunityCooldown(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;
}