From d666fbf7ce5eeead6d0c3a283f6d2928c3f6fd16 Mon Sep 17 00:00:00 2001 From: richard Date: Thu, 16 Jul 2009 10:05:40 +0200 Subject: [PATCH] More work on volfeatures. See details. Finished base event handler for volfeatures. Finished zr_voladd, zr_vol_remove, zr_vol_list and zr_vol_dumpstates console commands. Removed unused function call in class module. Fixed minior bugs in parameter parser (paramtools.inc). Made a debug tools module with console commands for directly testing certain functions. --- .../translations/zombiereloaded.phrases.txt | 12 +- src/zombiereloaded.sp | 2 + src/zr/commands.inc | 2 + src/zr/cvars.inc | 4 +- src/zr/debugtools.inc | 105 +++ src/zr/paramtools.inc | 54 +- src/zr/playerclasses/filtertools.inc | 2 +- src/zr/translation.inc | 1 + src/zr/volfeatures/volanticamp.inc | 817 +++++++++++++++++- src/zr/volfeatures/volcommands.inc | 555 +++++++++++- src/zr/volfeatures/volevents.inc | 41 +- src/zr/volfeatures/volfeatures.inc | 330 +++++-- src/zr/volfeatures/volgenericattributes.inc | 112 ++- 13 files changed, 1861 insertions(+), 176 deletions(-) create mode 100644 src/zr/debugtools.inc diff --git a/cstrike/addons/sourcemod/translations/zombiereloaded.phrases.txt b/cstrike/addons/sourcemod/translations/zombiereloaded.phrases.txt index 79871d0..a94bc35 100644 --- a/cstrike/addons/sourcemod/translations/zombiereloaded.phrases.txt +++ b/cstrike/addons/sourcemod/translations/zombiereloaded.phrases.txt @@ -932,17 +932,19 @@ "ru" "Здоровье: {1}" } + // =========================== + // Volumetric features (module) + // =========================== - - "Unfair camping" + "Vol Anticamp Message" { - "en" "An admin has marked this area as unfair, please move along, or die." + "en" "This area is restricted, please move along." } - "Unfair camper slayed" + "Vol Slay" { "#format" "{1:s},{2:d}" - "en" "Player {1} has been slayed for camping in a restricted area. (ID: {2})" + "en" "Player \"{1}\" has been slayed for camping in a restricted area (ID: {2})." } // =========================== diff --git a/src/zombiereloaded.sp b/src/zombiereloaded.sp index 5e923d9..aa8f4f0 100644 --- a/src/zombiereloaded.sp +++ b/src/zombiereloaded.sp @@ -80,6 +80,7 @@ #include "zr/zcookies" #include "zr/jumpboost" #include "zr/volfeatures/volfeatures" +#include "zr/debugtools" /** * Record plugin info. @@ -137,6 +138,7 @@ public OnMapStart() SEffectsOnMapStart(); AntiStickOnMapStart(); ZSpawnOnMapStart(); + } /** diff --git a/src/zr/commands.inc b/src/zr/commands.inc index 6f343e8..4dcb552 100644 --- a/src/zr/commands.inc +++ b/src/zr/commands.inc @@ -45,6 +45,8 @@ CommandsInit() ZHPOnCommandsCreate(); VolOnCommandsCreate(); + DebugOnCommandsCreate(); + // Forward event to modules. (hook commands) ClassOnCommandsHook(); DamageOnCommandsHook(); diff --git a/src/zr/cvars.inc b/src/zr/cvars.inc index 99b14de..4efcc38 100644 --- a/src/zr/cvars.inc +++ b/src/zr/cvars.inc @@ -423,8 +423,8 @@ CvarsCreate() // Volumetric Features (module) // =========================== g_hCvarsList[CVAR_VOL] = CreateConVar("zr_vol", "1", "Enables volumetric features."); - g_hCvarsList[CVAR_VOL_UPDATE_INTERVAL] = CreateConVar("zr_vol_update_interval", "1.0", "How often to update player positions and trigger events, in seconds."); - g_hCvarsList[CVAR_VOL_TRIGGER_INTERVAL] = CreateConVar("zr_vol_trigger_interval", "1.0", "How often to check for delayed events, in seconds. Use lower values for more precise delays."); + g_hCvarsList[CVAR_VOL_UPDATE_INTERVAL] = CreateConVar("zr_vol_update_interval", "0.5", "How often to update player positions and trigger events, in seconds."); + g_hCvarsList[CVAR_VOL_TRIGGER_INTERVAL] = CreateConVar("zr_vol_trigger_interval", "0.5", "How often to check for delayed events, in seconds. Use lower values for more precise delays."); // =========================== diff --git a/src/zr/debugtools.inc b/src/zr/debugtools.inc new file mode 100644 index 0000000..e8082ae --- /dev/null +++ b/src/zr/debugtools.inc @@ -0,0 +1,105 @@ +/* + * ============================================================================ + * + * Zombie:Reloaded + * + * File: debugtools.inc + * Type: Core + * Description: Place to put custom functions and test stuff. + * + * 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 . + * + * ============================================================================ + */ + +DebugOnCommandsCreate() +{ + // Custom test commands: + RegConsoleCmd("zr_getparametercount", Command_GetParameterCount, "Test GetParameterCount function in paramtools.inc. Usage: zr_getparametercount "); + RegConsoleCmd("zr_getparametervalue", Command_GetParameterValue, "Test GetParameterValue function in paramtools.inc. Usage: zr_getparametervalue "); +} + +public Action:Command_GetParameterCount(client, argc) +{ + if (argc < 1) + { + ReplyToCommand(client, "No parameter string passed. Usage: zr_getparametercount "); + return Plugin_Handled; + } + + decl String:argbuffer[256]; + decl String:paramstring[256]; + + paramstring[0] = 0; + + // Join the last parameters in a string. + for (new arg = 1; arg <= argc; arg++) + { + GetCmdArg(arg, argbuffer, sizeof(argbuffer)); + StrCat(paramstring, sizeof(paramstring), argbuffer); + + // Add space, except on the last parameter. + if (arg < argc) + { + StrCat(paramstring, sizeof(paramstring), " "); + } + } + + ReplyToCommand(client, "Parameter string: \"%s\"", paramstring); + new paramcount = GetParameterCount(paramstring); + ReplyToCommand(client, "Parameter count: %d", paramcount); + + return Plugin_Handled; +} + +public Action:Command_GetParameterValue(client, argc) +{ + if (argc < 2) + { + ReplyToCommand(client, "Missing parameters. Usage: zr_getparametervalue "); + return Plugin_Handled; + } + + decl String:paramname[256]; + decl String:paramstring[256]; + + decl String:valuebuffer[64]; + decl String:argbuffer[256]; + + paramstring[0] = 0; + valuebuffer[0] = 0; + + GetCmdArg(1, paramname, sizeof(paramname)); + + // Join the last parameters in a string. + for (new arg = 2; arg <= argc; arg++) + { + GetCmdArg(arg, argbuffer, sizeof(argbuffer)); + StrCat(paramstring, sizeof(paramstring), argbuffer); + + // Add space, except on the last parameter. + if (arg < argc) + { + StrCat(paramstring, sizeof(paramstring), " "); + } + } + + new retval = GetParameterValue(valuebuffer, sizeof(valuebuffer), paramstring, paramname); + + ReplyToCommand(client, "Return value: %d\nParameter string: \"%s\"\nParameter value: \"%s\"", retval, paramstring, valuebuffer); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/src/zr/paramtools.inc b/src/zr/paramtools.inc index cb61b20..701ab7a 100644 --- a/src/zr/paramtools.inc +++ b/src/zr/paramtools.inc @@ -8,6 +8,8 @@ * Description: Provides functions for parsing strings with parameters in * key=value format. * + * Note: Does not support spaces or quoted strings. + * * Copyright (C) 2009 Greyscale, Richard Helgeby * * This program is free software: you can redistribute it and/or modify @@ -34,6 +36,8 @@ stock GetParameterCount(const String:rawString[]) { new lenRawString = strlen(rawString); new paramCount; + new searchPos; + new splitPos; // Check if the raw string is empty. if (lenRawString == 0) @@ -42,20 +46,26 @@ stock GetParameterCount(const String:rawString[]) } // Count number of "=". - for (new searchPos = 0; searchPos < lenRawString; searchPos++) + for (splitPos = 0; splitPos < lenRawString; splitPos++) { - // Set searchPos and check if "=" was found. - searchPos = StrContains(rawString[searchPos], "="); - if (searchPos) + // Find next "=". + searchPos = StrContains(rawString[splitPos], "=", false); + + // Note: searchPos is an offset from rawString[splitPos]. If searchPos + // is 0, then the real position is splitPos. + + // Check if "=" was found. + if (searchPos >= 0) { + // Parameter found. paramCount++; - // Increment one position so we dont find the same "=" again. - searchPos++; + // Update split position. + splitPos += searchPos; } else { - // Exit loop. + // No need to continue. "=" not found. break; } } @@ -86,9 +96,11 @@ stock GetParameterValue(String:buffer[], maxlen, const String:rawString[], const new valuePos; new valueLen; new nextPos; + new splitPos; // Get the position of parameter. paramPos = StrContains(rawString, parameter, false); + splitPos = paramPos; // Check if found. if (paramPos >= 0) @@ -96,8 +108,8 @@ stock GetParameterValue(String:buffer[], maxlen, const String:rawString[], const // Get parameter length. paramLen = strlen(parameter); - // Get the position of the next parameter. - nextPos = StrContains(rawString[paramPos + 1], " "); + // Get the position of the next parameter by finding the next space. + nextPos = StrContains(rawString[splitPos], " "); // Check if the next parameter was found. if (nextPos >= 0) @@ -105,8 +117,9 @@ stock GetParameterValue(String:buffer[], maxlen, const String:rawString[], const // Calculate value starting position. valuePos = paramPos + paramLen + 1; - // Calculate value length. - valueLen = nextPos - valuePos; + // Calculate value length. Note: Adding 1 for space to the null + // terminator. + valueLen = nextPos + splitPos - valuePos + 1; // Check if value length is longer than buffer size. if (valueLen > maxlen) @@ -145,6 +158,7 @@ stock GetParameterName(String:buffer[], maxlen, const String:rawString[], parame new paramPos; new valuePos; new nextValuePos; + new splitPos; // Check if the raw string is empty. if (strlen(rawString) == 0) @@ -157,22 +171,26 @@ stock GetParameterName(String:buffer[], maxlen, const String:rawString[], parame // Get the value starting position for the previous index. for (new index = 0; index < parameterIndex - 1; index++) { - valuePos = StrContains(rawString[valuePos], "="); + valuePos = StrContains(rawString[splitPos], "="); + splitPos += valuePos; } - // Find the next space from valuePos where the specified parameter + // Find the next space from splitPos where the specified parameter // starts. - paramPos = StrContains(rawString[valuePos], " ") + 1; + paramPos = StrContains(rawString[splitPos], " ") + splitPos + 1; - // Find the next value position from paramPos to get the end position + // Update split position. + splitPos += paramPos; + + // Find the next value position from splitPos to get the end position // of the parameter name. - nextValuePos = StrContains(rawString[paramPos], "="); + nextValuePos = StrContains(rawString[splitPos], "=") + splitPos; // Check if a value is specified. if (nextValuePos > 0) { // Return the parameter name between paramPos and nextValuePos. - return strcopy(buffer, nextValuePos - paramPos, rawString[paramPos]); + return strcopy(buffer, nextValuePos - paramPos + 1, rawString[paramPos]); // + 1 to include null terminator. } else { @@ -189,7 +207,7 @@ stock GetParameterName(String:buffer[], maxlen, const String:rawString[], parame if (valuePos > 0) { // Return the parameter name. - return strcopy(buffer, valuePos, rawString); + return strcopy(buffer, valuePos + 1, rawString); // + 1 to include null terminator. } else { diff --git a/src/zr/playerclasses/filtertools.inc b/src/zr/playerclasses/filtertools.inc index aa86c85..d63ca73 100644 --- a/src/zr/playerclasses/filtertools.inc +++ b/src/zr/playerclasses/filtertools.inc @@ -384,7 +384,7 @@ stock ClassGetIndex(const String:name[], cachetype = ZR_CLASS_CACHE_MODIFIED) */ stock ClassGetActiveIndex(client) { - new teamid = GetClientTeam(client); + new teamid; if (!ZRIsClientOnTeam(client)) { diff --git a/src/zr/translation.inc b/src/zr/translation.inc index f2ae06e..1b8ccfa 100644 --- a/src/zr/translation.inc +++ b/src/zr/translation.inc @@ -61,6 +61,7 @@ TranslationInit() { // Load translations phrases used by plugin. LoadTranslations("common.phrases.txt"); + LoadTranslations("core.phrases.txt"); LoadTranslations("zombiereloaded.phrases.txt"); } diff --git a/src/zr/volfeatures/volanticamp.inc b/src/zr/volfeatures/volanticamp.inc index bc314c1..d99fe98 100644 --- a/src/zr/volfeatures/volanticamp.inc +++ b/src/zr/volfeatures/volanticamp.inc @@ -25,31 +25,16 @@ * ============================================================================ */ -/** - * Data structure for a anti-camp volume. - */ -enum VolTypeAnticamp -{ - Float:anticamp_interval, /** How often to trigger an action. */ - Handle:anticamp_timer, /** Action timer handle. */ - - VolAnticampAction:anticamp_action, /** What to do with players in anti-camp volumes */ - Float:anticamp_amount, /** Amount depending on action type. Usually time in seconds or damage amount. */ - - VolAnticampeWarningType:anticamp_warning, /** How to warn the player. */ - String:anticamp_message[256] /** Override warning message. Max 256 characters. */ -} - /** * Actions to do with players in anti-camp volumes. */ enum VolAnticampAction { - anticamp_no_action, /** Do nothing but give a warning. */ - anticamp_damage, /** Give damage to player. */ - anticamp_slay, /** Slay player. */ - anticamp_drug, /** Drug player. */ - anticamp_fire /** Ignite player. */ + Anticamp_NoAction, /** Do nothing but give a warning. */ + Anticamp_Damage, /** Give damage to player. */ + Anticamp_Slay, /** Slay player. */ + Anticamp_Drug, /** Drug player. */ + Anticamp_Ignite /** Ignite player. */ } /** @@ -57,13 +42,797 @@ enum VolAnticampAction */ enum VolAnticampeWarningType { - anticamp_no_warning, /** No warning messages. */ - anticamp_chat, /** Print warning in chat area. */ - anticamp_center, /** Print centered warning message. */ - anticamp_menu /** Print a menu-like warning message with close option. */ + Anticamp_NoWarning, /** No warning messages. */ + Anticamp_Chat, /** Print warning in chat area. */ + Anticamp_Center, /** Print centered warning message. */ + Anticamp_Menu /** Print a menu-like warning message with close option. */ +} + +/** + * Data structure for a anti-camp volume. + */ +enum VolTypeAnticamp +{ + bool:Anticamp_InUse, /** Specifies if the data index is used or not. */ + Float:Anticamp_Interval, /** How often to trigger an action. */ + Handle:Anticamp_Timer, /** Action timer handle. */ + + VolAnticampAction:Anticamp_Action, /** What to do with players in anti-camp volumes */ + Float:Anticamp_Amount, /** Amount depending on action type. Usually time in seconds or damage amount. */ + + VolAnticampeWarningType:Anticamp_Warning, /** How to warn the player. */ + String:Anticamp_Message[256] /** Override warning message. Max 256 characters. */ } /** * Anti-camp data. */ new AnticampData[ZR_VOLUMES_MAX][VolTypeAnticamp]; + +/** + * Event callback. Enables a anticamp volume. + * + * @param volumeIndex The volume index. + */ +VolAnticampEnable(volumeIndex) +{ + new Float:interval; + + // Validate index. + if (!VolIsValidIndex(volumeIndex)) + { + return; + } + + // Get data index. + new dataindex = Volumes[volumeIndex][Vol_DataIndex]; + + // Validate data index. + if (!VolAnticampValidateIndex(dataindex)) + { + return; + } + + // Check if in use. + if (AnticampData[dataindex][Anticamp_InUse]) + { + // Start timer if not running. + if (AnticampData[dataindex][Anticamp_Timer] == INVALID_HANDLE) + { + // Get interval. + interval = AnticampData[dataindex][Anticamp_Interval]; + + // Validate interval. + if (interval > 0.0) + { + AnticampData[dataindex][Anticamp_Timer] = CreateTimer(interval, Event_VolAnticampTrigger, _, TIMER_REPEAT); + } + } + + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Vol state", "Enabled anticamp volume %d.", volumeIndex); + } +} + +/** + * Starts all existing anticamp timers. + */ +stock VolAnticampEnableAll() +{ + new Float:interval; + + // Loop through all volumes. + for (new dataindex = 0; dataindex < ZR_VOLUMES_MAX; dataindex++) + { + // Check if in use. + if (AnticampData[dataindex][Anticamp_InUse]) + { + // Start timer if not running. + if (AnticampData[dataindex][Anticamp_Timer] == INVALID_HANDLE) + { + // Get interval. + interval = AnticampData[dataindex][Anticamp_Interval]; + + // Validate interval. + if (interval > 0.0) + { + AnticampData[dataindex][Anticamp_Timer] = CreateTimer(interval, Event_VolAnticampTrigger, _, TIMER_REPEAT); + } + } + } + } +} + +/** + * Event callback. Stops existing anticamp timer on a volume. + */ +VolAnticampDisable(volumeIndex) +{ + new Handle:timerbuffer; + + // Validate index. + if (!VolIsValidIndex(volumeIndex)) + { + return; + } + + // Get data index. + new dataindex = Volumes[volumeIndex][Vol_DataIndex]; + + // Validate data index. + if (!VolAnticampValidateIndex(dataindex)) + { + return; + } + + // Check if in use. + if (AnticampData[dataindex][Anticamp_InUse]) + { + // Stop timer. + timerbuffer = AnticampData[dataindex][Anticamp_Timer]; + if (timerbuffer != INVALID_HANDLE) + { + KillTimer(timerbuffer); + AnticampData[dataindex][Anticamp_Timer] = INVALID_HANDLE; + } + + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Vol state", "Disabled anticamp volume %d.", volumeIndex); + } + +} + +/** + * Stops all existing anticamp timers. + */ +stock VolAnticampDisableAll() +{ + new Handle:timerbuffer; + + // Loop through all volumes. + for (new dataindex = 0; dataindex < ZR_VOLUMES_MAX; dataindex++) + { + // Check if in use. + if (AnticampData[dataindex][Anticamp_InUse]) + { + // Stop timer. + timerbuffer = AnticampData[dataindex][Anticamp_Timer]; + if (timerbuffer != INVALID_HANDLE) + { + KillTimer(timerbuffer); + AnticampData[dataindex][Anticamp_Timer] = INVALID_HANDLE; + } + } + } +} + +/** + * Disables feature and resets data to defaults at the specified index. + * + * @param dataIndex Local data index. + */ +VolAnticampReset(dataIndex) +{ + AnticampData[dataIndex][Anticamp_InUse] = false; + + AnticampData[dataIndex][Anticamp_Interval] = 1.0; + if (AnticampData[dataIndex][Anticamp_Timer] != INVALID_HANDLE) + { + KillTimer(AnticampData[dataIndex][Anticamp_Timer]); + AnticampData[dataIndex][Anticamp_Timer] = INVALID_HANDLE; + } + + AnticampData[dataIndex][Anticamp_Action] = Anticamp_Damage; + AnticampData[dataIndex][Anticamp_Amount] = 5.0; + + AnticampData[dataIndex][Anticamp_Warning] = Anticamp_Chat; + Format(String:AnticampData[dataIndex][Anticamp_Message], 256, ""); +} + +/** + * Initialization event for anticamp feature. + */ +VolAnticampInit() +{ + // Set default attributes. + for (new dataindex = 0; dataindex < ZR_VOLUMES_MAX; dataindex++) + { + VolAnticampReset(dataindex); + } +} + +/** + * Timer callback for anticamp volumes. Applies actions on players in volumes. + */ +public Action:Event_VolAnticampTrigger(Handle:timer) +{ + // Loop through all players. + for (new client = 1; client <= MaxClients; client++) + { + // Loop through all volumes. + for (new volumeindex = 0; volumeindex < ZR_VOLUMES_MAX; volumeindex++) + { + // Check if the volume is enabled and in use. + if (VolIsEnabled(volumeindex) && VolInUse(volumeindex)) + { + // Check if player is in a anticamp volume. + if (VolPlayerInVolume[client][volumeindex] && Volumes[volumeindex][Vol_Type] == VolFeature_Anticamp) + { + // Apply action. + VolAnticampApplyAction(client, Volumes[volumeindex][Vol_DataIndex], volumeindex); + } + } + } + } +} + +/** + * Applies action on a client for the specified volume. + * + * @param client The client index. + * @param dataIndex Local data index. + * @param volumeIndex The volume index. + */ +VolAnticampApplyAction(client, dataIndex, volumeIndex) +{ + new Float:amount = AnticampData[dataIndex][Anticamp_Amount]; + + // Send warning message. + VolAnticampWarnPlayer(client, dataIndex); + + switch (AnticampData[dataIndex][Anticamp_Action]) + { + case Anticamp_NoAction: + { + // Do nothing. + } + case Anticamp_Damage: + { + // Give damage to player. Kill if zero HP. + new damage = RoundToNearest(amount); + new health = GetClientHealth(client) - damage; + decl String:name[64]; + decl String:buffer[256]; + + if (health > 0) + { + SetEntityHealth(client, health); + } + else + { + // Health is zero or below. Kill player. + ForcePlayerSuicide(client); + + // Log event. + GetClientName(client, name, sizeof(name)); + SetGlobalTransTarget(client); + Format(buffer, sizeof(buffer), "%t", "Vol Slay", name, volumeIndex); + + /*if (LogFlagCheck(LOG_GAME_EVENTS, LOG_MODULE_ANTICAMP)) ZR_LogMessageFormatted(client, "anticamp", "kill", "%s", true, buffer); + if (anticamp_echo) + { + FormatTextString(buffer, sizeof(buffer)); + ZR_PrintToAdminChat(buffer); + }*/ + } + } + case Anticamp_Slay: + { + + } + case Anticamp_Drug: + { + + } + case Anticamp_Ignite: + { + + } + } +} + +/** + * Gives a warning to the specified player for the specified volume. + * + * @param client The client index. + * @param dataIndex Local data index. + */ +VolAnticampWarnPlayer(client, dataIndex) +{ + decl String:buffer[256]; + new bool:custommessage = (strlen(AnticampData[dataIndex][Anticamp_Message]) > 0) ? true : false; + + // Set language. + SetGlobalTransTarget(client); + + // Format message. + if (custommessage) + { + // Use custom message. + strcopy(buffer, sizeof(buffer), AnticampData[dataIndex][Anticamp_Message]); + } + else + { + // Use message in translations file. + Format(buffer, sizeof(buffer), "%t", "Vol Anticamp Message"); + } + + switch (AnticampData[dataIndex][Anticamp_Warning]) + { + case Anticamp_NoWarning: + { + // Do nothing. + } + case Anticamp_Chat: + { + // Apply ZR formatting and print chat message. + TranslationPluginFormatString(buffer, sizeof(buffer)); + PrintToChat(client, buffer); + } + case Anticamp_Center: + { + // Print centered message. + PrintCenterText(client, buffer); + } + case Anticamp_Menu: + { + // Display the message in a menu panel. + new Handle:panel = CreatePanel(); + + SetPanelTitle(panel, "Zombie:Reloaded"); + DrawPanelItem(panel, "", ITEMDRAW_SPACER); + DrawPanelItem(panel, buffer); + DrawPanelItem(panel, "", ITEMDRAW_SPACER); + + SetPanelCurrentKey(panel, 10); + + Format(buffer, sizeof(buffer), "%t", "Exit"); + DrawPanelItem(panel, buffer, ITEMDRAW_CONTROL); + + SendPanelToClient(panel, client, Handler_AnitcampDummy, 10); + CloseHandle(panel); + } + } +} + +/** + * Dummy handler for panel messages. + */ +public Handler_AnitcampDummy(Handle:menu, MenuAction:action, param1, param2) +{ + // Do nothing. +} + +/** + * Gets the first free anticamp data index. + * + * @return The first free anticamp data index if successful, or -1 if + * there are no free volumes. + */ +VolAnticampGetFreeIndex() +{ + // Loop through all indexes. + for (new dataindex = 0; dataindex < ZR_VOLUMES_MAX; dataindex++) + { + // Check if it's free. + if (!AnticampData[dataindex][Anticamp_InUse]) + { + return dataindex; + } + } + + // No free index found. + return -1; +} + +/** + * Validates local data index. + * + * @param dataIndex Index to validate. + * @return True if valid, false otherwise. + */ +bool:VolAnticampValidateIndex(dataIndex) +{ + if (dataIndex >= 0 && dataIndex < ZR_VOLUMES_MAX) + { + return true; + } + else + { + return false; + } +} + +/** + * Dumps data to be used by zr_vol_list command. + * + * @param dataIndex Index in anticamp data array. + * @param buffer Destination string buffer. + * @param maxlen Size of destination buffer. + * @return Number of cells written. + */ +VolAnticampDumpData(dataIndex, String:buffer[], maxlen) +{ + decl String:linebuffer[128]; + decl String:valuebuffer[256]; + new anticampcache[VolTypeAnticamp]; + new cellswritten; + + // Validate index. + if (!VolAnticampValidateIndex(dataIndex)) + { + return 0; + } + + // Initialize and clear buffer. + buffer[0] = 0; + + // Cache data. + anticampcache = AnticampData[dataIndex]; + + Format(linebuffer, sizeof(linebuffer), "Interval: %.2f\n", anticampcache[Anticamp_Interval]); + cellswritten += StrCat(buffer, maxlen, linebuffer); + + VolAnticampActionToString(anticampcache[Anticamp_Action], valuebuffer, sizeof(valuebuffer)); + Format(linebuffer, sizeof(linebuffer), "Action: %s\n", valuebuffer); + cellswritten += StrCat(buffer, maxlen, linebuffer); + + Format(linebuffer, sizeof(linebuffer), "Action amount: %.2f\n", anticampcache[Anticamp_Amount]); + cellswritten += StrCat(buffer, maxlen, linebuffer); + + VolAnticampWarningToString(anticampcache[Anticamp_Warning], valuebuffer, sizeof(valuebuffer)); + Format(linebuffer, sizeof(linebuffer), "Warning type: %s\n", valuebuffer); + cellswritten += StrCat(buffer, maxlen, linebuffer); + + strcopy(valuebuffer, sizeof(valuebuffer), anticampcache[Anticamp_Message]); + Format(linebuffer, sizeof(linebuffer), "Warning message: \"%s\"\n", valuebuffer); + cellswritten += StrCat(buffer, maxlen, linebuffer); + + return cellswritten; +} + + +/************************************** + * * + * CONVERTING FUNCTIONS * + * * + **************************************/ + +/** + * Converts a action type to a string. + * + * @param actionType Action type to convert. + * @param buffer Destination string buffer. + * @param maxlen Size of destination buffer. + * @param shortName Optional. Write short name or human readable name. + * Default is human readable (false). + * @return Number of cells written. + */ +VolAnticampActionToString(VolAnticampAction:actionType, String:buffer[], maxlen, bool:shortName = false) +{ + switch (actionType) + { + case Anticamp_NoAction: + { + return shortName ? strcopy(buffer, maxlen, "none") : strcopy(buffer, maxlen, "No action"); + } + case Anticamp_Damage: + { + return shortName ? strcopy(buffer, maxlen, "damage") : strcopy(buffer, maxlen, "Damage player"); + } + case Anticamp_Drug: + { + return shortName ? strcopy(buffer, maxlen, "drug") : strcopy(buffer, maxlen, "Drug player "); + } + case Anticamp_Ignite: + { + return shortName ? strcopy(buffer, maxlen, "ignite") : strcopy(buffer, maxlen, "Ignite player"); + } + } + + return 0; +} + +/** + * Converts a action string type to a action type. + * + * @param action Action string type to convert. + * @return Action type or Anticamp_NoAction if failed. + */ +stock VolAnticampAction:VolAnticampStringToAction(const String:action[]) +{ + // Check if empty. + if (strlen(action) == 0) + { + return Anticamp_NoAction; + } + + if (StrEqual(action, "none", false)) + { + return Anticamp_NoWarning; + } + else if (StrEqual(action, "damage", false)) + { + return Anticamp_Damage; + } + else if (StrEqual(action, "drug", false)) + { + return Anticamp_Drug; + } + else if (StrEqual(action, "ignite", false)) + { + return Anticamp_Ignite; + } + + // No match. + return Anticamp_NoAction; +} + +/** + * Converts a warning type to a string. + * + * @param warningType Warning type to convert. + * @param buffer Destination string buffer. + * @param maxlen Size of destination buffer. + * @param shortName Optional. Write short name or human readable name. + * Default is human readable (false). + * @return Number of cells written. + */ +VolAnticampWarningToString(VolAnticampeWarningType:warningType, String:buffer[], maxlen, bool:shortName = false) +{ + switch (warningType) + { + case Anticamp_NoWarning: + { + return shortName ? strcopy(buffer, maxlen, "none") : strcopy(buffer, maxlen, "No warning"); + } + case Anticamp_Chat: + { + return shortName ? strcopy(buffer, maxlen, "chat") : strcopy(buffer, maxlen, "Chat area"); + } + case Anticamp_Center: + { + return shortName ? strcopy(buffer, maxlen, "center") : strcopy(buffer, maxlen, "Centered message"); + } + case Anticamp_Menu: + { + return shortName ? strcopy(buffer, maxlen, "meny") : strcopy(buffer, maxlen, "Message in menu panel"); + } + } + + return 0; +} + +/** + * Converts a warning string type to a warning type. + * + * @param warning Warning string type to convert. + * @return Warning type, or Anticamp_NoWarning if failed. + */ +stock VolAnticampeWarningType:VolAnticampStringToWarning(const String:warning[]) +{ + // Check if empty. + if (strlen(warning) == 0) + { + return Anticamp_NoWarning; + } + + if (StrEqual(warning, "none", false)) + { + return Anticamp_NoWarning; + } + else if (StrEqual(warning, "chat", false)) + { + return Anticamp_Chat; + } + else if (StrEqual(warning, "center", false)) + { + return Anticamp_Center; + } + else if (StrEqual(warning, "menu", false)) + { + return Anticamp_Menu; + } + + // No match. + return Anticamp_NoWarning; +} + + +/************************************** + * * + * ATTRIBUTE FUNCTIONS * + * * + **************************************/ + +/** + * Sets anticamp spesific attributes on a anticamp volume. + * + * @param dataIndex Local data index. + * @param attribName Attribute to modify. + * @param attribVlue Attribute value to set. + * @return True if successfully set, false otherwise. + */ +bool:VolAnticampSetAttribute(dataIndex, const String:attribName[], const String:attribValue[]) +{ + // Validate data index. + if (!VolAnticampValidateIndex(dataIndex)) + { + return false; + } + + // Check attribute names. + if (StrEqual(attribName, "interval", false)) + { + if (VolAnticampSetIntervalString(dataIndex, attribValue)) + { + return true; + } + } + else if (StrEqual(attribName, "action", false)) + { + if (VolAnticampSetActionString(dataIndex, attribValue)) + { + return true; + } + } + else if (StrEqual(attribName, "amount", false)) + { + if (VolAnticampSetAmountString(dataIndex, attribValue)) + { + return true; + } + } + else if (StrEqual(attribName, "warning", false)) + { + if (VolAnticampSetWarningString(dataIndex, attribValue)) + { + return true; + } + } + else if (StrEqual(attribName, "message", false)) + { + // Unsupported because of technical limits in command parser. Spaces + // and quoted strings aren't supported yet. + } + + return false; +} + +/** + * Parses a interval string value and applies it to the specified volume. + * + * @param dataIndex Local data index. + * @param interval Interval to set. A floating point number formatted as a + * string. + * @return True if successfully set, false otherwise. + */ +bool:VolAnticampSetIntervalString(dataIndex, const String:interval[]) +{ + new Float:anticampinterval; + + // Check if string value is empty. + if (strlen(interval) == 0) + { + return false; + } + + // Convert value. + anticampinterval = StringToFloat(interval); + + // Apply value. + AnticampData[dataIndex][Anticamp_Interval] = anticampinterval; + return true; +} + +/** + * Parses a action type string value and applies it to the specified volume. + * + * @param dataIndex Local data index. + * @param action Action type to set. + * @return True if successfully set, false otherwise. + */ +bool:VolAnticampSetActionString(dataIndex, const String:action[]) +{ + // Check if string value is empty. + if (strlen(action) == 0) + { + return false; + } + + // Check effect string values and apply them to the volume. + if (StrEqual(action, "none", false)) + { + AnticampData[dataIndex][Anticamp_Action] = Anticamp_NoAction; + return true; + } + else if (StrEqual(action, "damage", false)) + { + AnticampData[dataIndex][Anticamp_Action] = Anticamp_Damage; + return true; + } + else if (StrEqual(action, "slay", false)) + { + AnticampData[dataIndex][Anticamp_Action] = Anticamp_Slay; + return true; + } + else if (StrEqual(action, "drug", false)) + { + AnticampData[dataIndex][Anticamp_Action] = Anticamp_Drug; + return true; + } + else if (StrEqual(action, "ignite", false)) + { + AnticampData[dataIndex][Anticamp_Action] = Anticamp_Ignite; + return true; + } + + // The string value didn't match any valid action types. + return false; +} + +/** + * Parses a action amount string value and applies it to the specified volume. + * + * @param dataIndex Local data index. + * @param amount Amount to set. A floating point number formatted as a + * string. + * @return True if successfully set, false otherwise. + */ +bool:VolAnticampSetAmountString(dataIndex, const String:amount[]) +{ + new Float:actionamount; + + // Check if string value is empty. + if (strlen(amount) == 0) + { + return false; + } + + // Convert value. + actionamount = StringToFloat(amount); + + // Apply value. + AnticampData[dataIndex][Anticamp_Amount] = actionamount; + return true; +} + +/** + * Parses a warning type string value and applies it to the specified volume. + * + * @param dataIndex Local data index. + * @param warning warning type to set. + * @return True if successfully set, false otherwise. + */ +bool:VolAnticampSetWarningString(dataIndex, const String:warning[]) +{ + + // Check if string value is empty. + if (strlen(warning) == 0) + { + return false; + } + + // Check effect string values and apply them to the volume. + if (StrEqual(warning, "none", false)) + { + AnticampData[dataIndex][Anticamp_Warning] = Anticamp_NoWarning; + return true; + } + else if (StrEqual(warning, "chat", false)) + { + AnticampData[dataIndex][Anticamp_Warning] = Anticamp_Chat; + return true; + } + else if (StrEqual(warning, "center", false)) + { + AnticampData[dataIndex][Anticamp_Warning] = Anticamp_Center; + return true; + } + else if (StrEqual(warning, "menu", false)) + { + AnticampData[dataIndex][Anticamp_Warning] = Anticamp_Menu; + return true; + } + + // The string value didn't match any valid action types. + return false; +} diff --git a/src/zr/volfeatures/volcommands.inc b/src/zr/volfeatures/volcommands.inc index de40f3d..4488ba8 100644 --- a/src/zr/volfeatures/volcommands.inc +++ b/src/zr/volfeatures/volcommands.inc @@ -49,9 +49,433 @@ Example: zr_vol_add 0 0 0 100 200 300 anticamp team=humans delay=5 effect=wireframe effect_color=255,0,0 */ + +/** + * Creates commands for managing volumes. + */ +VolOnCommandsCreate() +{ + RegAdminCmd("zr_vol_add", VolAddVolumeCommand, ADMFLAG_CONFIG, "Creates a rectangular volume in the map. Usage: zr_vol_add [params]"); + RegAdminCmd("zr_vol_remove", VolRemoveVolumeCommand, ADMFLAG_CONFIG, "Removes an existing volume in the map. Usage: zr_vol_remove "); + RegConsoleCmd("zr_vol_list", VolListCommand, "Lists existing volumes in the map, or dumps detail data to the specified volume. Usage: zr_vol_list [volume index]"); + RegConsoleCmd("zr_vol_dumpstates", VolDumpStatesCommand, "Dumps volume states for the specified player. Usage: zr_vol_dumpstates "); +} + +/** + * Command callback for creating a new volume. + */ public Action:VolAddVolumeCommand(client, argc) { + decl String:buffer[640]; + buffer[0] = 0; + if (argc < 7) + { + // Write syntax info. + StrCat(buffer, sizeof(buffer), "Creates a rectangular volume in the map. Usage: zr_vol_add [params]\n\n"); + StrCat(buffer, sizeof(buffer), "Parameters:\n"); + StrCat(buffer, sizeof(buffer), "x1, y1, z1 Coordinates to first corner (any corner)\n"); + StrCat(buffer, sizeof(buffer), "x2, y2, z2 Coordinates to oposite corner (diagonally to oposite height)\n"); + StrCat(buffer, sizeof(buffer), "type Volumetric feature type:\n"); + StrCat(buffer, sizeof(buffer), " anticamp\n"); + StrCat(buffer, sizeof(buffer), "params Parameter string with additional volume data. Generic parameters:\n"); + StrCat(buffer, sizeof(buffer), " teamfilter=all|humans|zombies\n"); + StrCat(buffer, sizeof(buffer), " delay=0\n"); + StrCat(buffer, sizeof(buffer), " effect=none|wireframe|smoke\n"); + StrCat(buffer, sizeof(buffer), " effect_color=0,0,0\n"); + StrCat(buffer, sizeof(buffer), " enabled=1"); + + ReplyToCommand(client, buffer); + return Plugin_Handled; + } + + new Float:x1; + new Float:y1; + new Float:z1; + new Float:x2; + new Float:y2; + new Float:z2; + new Float:min[3]; + new Float:max[3]; + new VolumeFeatureTypes:voltype; + new Float:floatbuffer; + new volindex; + new dataindex; + new paramcount; + + decl String:params[256]; + decl String:argbuffer[256]; + + params[0] = 0; + + // Get a free volume index. + volindex = VolGetFreeVolume(); + + // Validate index. + if (!VolIsValidIndex(volindex)) + { + ReplyToCommand(client, "Cannot add volume. Maximum number of volumes reached."); + return Plugin_Handled; + } + + // Get positions. + GetCmdArg(1, argbuffer, sizeof(argbuffer)); + x1 = StringToFloat(argbuffer); + + GetCmdArg(2, argbuffer, sizeof(argbuffer)); + y1 = StringToFloat(argbuffer); + + GetCmdArg(3, argbuffer, sizeof(argbuffer)); + z1 = StringToFloat(argbuffer); + + GetCmdArg(4, argbuffer, sizeof(argbuffer)); + x2 = StringToFloat(argbuffer); + + GetCmdArg(5, argbuffer, sizeof(argbuffer)); + y2 = StringToFloat(argbuffer); + + GetCmdArg(6, argbuffer, sizeof(argbuffer)); + z2 = StringToFloat(argbuffer); + + // Check if both locations are equal. + if (FloatCompare(x1, x2) == 0) + { + if (FloatCompare(y1, y2) == 0) + { + if (FloatCompare(z1, z2) == 0) + { + ReplyToCommand(client, "Cannot add volume. Both locations are equal."); + return Plugin_Handled; + } + } + } + + // Sort out max and min values so 1-values are smaller. + if (FloatCompare(x1, x2) == 1) + { + // x1 is bigger than x2. Swap values. + floatbuffer = x1; + x1 = x2; + x2 = floatbuffer; + } + if (FloatCompare(y1, y2) == 1) + { + // y1 is bigger than y2. Swap values. + floatbuffer = y1; + y1 = y2; + y2 = floatbuffer; + } + if (FloatCompare(z1, z2) == 1) + { + // z1 is bigger than z2. Swap values. + floatbuffer = z1; + z1 = z2; + z2 = floatbuffer; + } + + // Copy coordinates to location vectors. + min[0] = x1; + min[1] = y1; + min[2] = z1; + max[0] = x2; + max[1] = y2; + max[2] = z2; + + // Get volume type. + GetCmdArg(7, argbuffer, sizeof(argbuffer)); + voltype = VolGetTypeFromString(argbuffer); + + // Validate volume type. + if (voltype == VolFeature_Invalid) + { + ReplyToCommand(client, "Cannot add volume. Invalid volume type: %s", argbuffer); + return Plugin_Handled; + } + + // Get free data index for the specified type. + dataindex = VolGetFreeDataIndex(voltype); + + // Validate data index. + if (dataindex < 0) + { + ReplyToCommand(client, "Cannot add volume. Out of free data indexes for type \"%s\"", argbuffer); + return Plugin_Handled; + } + + // Add volume. + volindex = VolAdd(volindex, min, max, voltype, dataindex); + + // Get additional parameters if they exist. + if (argc >= 8) + { + // Join the last parameters in a string. + for (new arg = 8; arg <= argc; arg++) + { + GetCmdArg(arg, argbuffer, sizeof(argbuffer)); + StrCat(params, sizeof(params), argbuffer); + + // Add space, except on the last parameter. + if (arg < argc) + { + StrCat(params, sizeof(params), " "); + } + } + + // Set attributes. + paramcount = VolSetAttributes(volindex, params); + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Add volume", "Parsing parameter string. Param count = \"%d\". String = \"%s\"", paramcount, params); + } + else + { + // No attributes set. + paramcount = 0; + } + + if (paramcount < 1) + { + Format(buffer, sizeof(buffer), "No additional attributes set."); + } + else + { + Format(buffer, sizeof(buffer), "Additional attributes set: %d", paramcount); + } + + ReplyToCommand(client, "Added volume at index %d. %s", volindex, buffer); + return Plugin_Handled; +} + +/** + * Command callback for removing a volume. + */ +public Action:VolRemoveVolumeCommand(client, argc) +{ + decl String:arg[16]; + new volindex; + + if (argc < 1) + { + // Write syntax info. + ReplyToCommand(client, "Removes an existing volume in the map. Usage: zr_vol_remove "); + return Plugin_Handled; + } + + // Get volume index. + GetCmdArg(1, arg, sizeof(arg)); + volindex = StringToInt(arg); + + // Validate index. + if (!VolIsValidIndex(volindex)) + { + ReplyToCommand(client, "Invalid volume index."); + return Plugin_Handled; + } + + // Check if volume exist. + if (!Volumes[volindex][Vol_InUse]) + { + ReplyToCommand(client, "Volume %d doesn't exist.", volindex); + return Plugin_Handled; + } + + // Remove volume. + VolRemove(volindex); + + ReplyToCommand(client, "Successfully disabled and removed volume %d.", volindex); + return Plugin_Handled; +} + +/** + * Command callback for listing volumes or dumping data. + */ +public Action:VolListCommand(client, argc) +{ + decl String:buffer[1022]; // Two chars reserved for newline and null terminator. + decl String:linebuffer[128]; + decl String:valuebuffer[32]; + decl String:arg[16]; + + buffer[0] = 0; + linebuffer[0] = 0; + + new volindex; + new volcount; + new volcache[VolumeAttributes]; + + if (argc < 1) + { + // No volume specified. Display syntax and list volumes. + StrCat(buffer, sizeof(buffer), "Lists existing volumes in the map, or dumps detail data to the specified volume. Usage: zr_vol_list [volume index]\n\n"); + StrCat(buffer, sizeof(buffer), "ID: Type: Min loc: Max loc:\n"); + StrCat(buffer, sizeof(buffer), "--------------------------------------------------------------------------------"); + ReplyToCommand(client, buffer); + + // Loop through all indexes. + for (volindex = 0; volindex < ZR_VOLUMES_MAX; volindex++) + { + // Check if in use. + if (Volumes[volindex][Vol_InUse]) + { + // Cache volume data. + volcache = Volumes[volindex]; + + // Add to list. + VolTypeToString(volcache[Vol_Type], valuebuffer, sizeof(valuebuffer), true); + Format(linebuffer, sizeof(linebuffer), "%-4d %-15s %-8.2f %-8.2f %-8.2f %-8.2f %-8.2f %-8.2f", + volindex, + valuebuffer, + volcache[Vol_xMin], + volcache[Vol_yMin], + volcache[Vol_zMin], + volcache[Vol_xMax], + volcache[Vol_yMax], + volcache[Vol_zMax]); + + ReplyToCommand(client, linebuffer); + volcount++; + } + } + + Format(linebuffer, sizeof(linebuffer), "\nTotal volumes: %d", volcount); + ReplyToCommand(client, linebuffer); + return Plugin_Handled; + } + else + { + // Dump data for the specified volume. + + // Get volume index. + GetCmdArg(1, arg, sizeof(arg)); + volindex = StringToInt(arg); + + // Validate index. + if (!VolIsValidIndex(volindex)) + { + ReplyToCommand(client, "The specified volume index is invalid: %d", volindex); + return Plugin_Handled; + } + + // Check if unused. + if (!VolInUse(volindex)) + { + ReplyToCommand(client, "The specified volume doesn't exist: %d.", volindex); + return Plugin_Handled; + } + + // Cache volume data. + volcache = Volumes[volindex]; + + // Dump generic volume data. + Format(linebuffer, sizeof(linebuffer), "Volume data at index %d:\n", volindex); + StrCat(buffer, sizeof(buffer), linebuffer); + StrCat(buffer, sizeof(buffer), "--------------------------------------------------------------------------------"); + ReplyToCommand(client, buffer); + + // Clear buffer. + buffer[0] = 0; + + Format(linebuffer, sizeof(linebuffer), "ID: %d\n", volindex); + StrCat(buffer, sizeof(buffer), linebuffer); + + Format(linebuffer, sizeof(linebuffer), "Enabled: %d\n", volcache[Vol_Enabled]); + StrCat(buffer, sizeof(buffer), linebuffer); + + VolTypeToString(volcache[Vol_Type], valuebuffer, sizeof(valuebuffer)); + Format(linebuffer, sizeof(linebuffer), "Type: %s\n", valuebuffer); + StrCat(buffer, sizeof(buffer), linebuffer); + + Format(linebuffer, sizeof(linebuffer), "Min loc: %-8.2f %-8.2f %-8.2f\n", volcache[Vol_xMin], volcache[Vol_yMin], volcache[Vol_zMin]); + StrCat(buffer, sizeof(buffer), linebuffer); + + Format(linebuffer, sizeof(linebuffer), "Max loc: %-8.2f %-8.2f %-8.2f\n", volcache[Vol_xMax], volcache[Vol_yMax], volcache[Vol_zMax]); + StrCat(buffer, sizeof(buffer), linebuffer); + + VolEffectToString(volcache[Vol_Effect], valuebuffer, sizeof(valuebuffer)); + Format(linebuffer, sizeof(linebuffer), "Effect: %s\n", valuebuffer); + StrCat(buffer, sizeof(buffer), linebuffer); + + Format(linebuffer, sizeof(linebuffer), "Effect color: %d, %d, %d\n", volcache[Vol_EffectColor][0], volcache[Vol_EffectColor][1], volcache[Vol_EffectColor][2]); + StrCat(buffer, sizeof(buffer), linebuffer); + + VolTeamToString(volcache[Vol_TeamFilter], valuebuffer, sizeof(valuebuffer)); + Format(linebuffer, sizeof(linebuffer), "Team filter: %s\n", valuebuffer); + StrCat(buffer, sizeof(buffer), linebuffer); + + Format(linebuffer, sizeof(linebuffer), "Trigger delay: %.2f", volcache[Vol_TriggerDelay]); + StrCat(buffer, sizeof(buffer), linebuffer); + + // Print generic attributes. + ReplyToCommand(client, buffer); + + // Clear buffer. + buffer[0] = 0; + + // Get type spesific attributes. + switch (volcache[Vol_Type]) + { + case VolFeature_Anticamp: + { + VolAnticampDumpData(volcache[Vol_DataIndex], buffer, sizeof(buffer)); + } + case VolFeature_Knockback: + { + + } + } + + // Print type spesific attributes if any. + if (strlen(buffer) > 0) + { + ReplyToCommand(client, buffer); + } + + return Plugin_Handled; + } +} + +public Action:VolDumpStatesCommand(client, argc) +{ + decl String:target[64]; + new targetclient; + + if (argc < 1) + { + ReplyToCommand(client, "Dumps volume states for the specified player. Usage: zr_vol_dumpstates "); + return Plugin_Handled; + } + + // Get target. + GetCmdArg(1, target, sizeof(target)); + targetclient = FindTarget(client, target); + + // Validate target. + if (targetclient <= 0) + { + // Note: FindTarget automatically print error messages. + return Plugin_Handled; + } + + // Print header. + ReplyToCommand(client, "Volume ID: Player in volume:\n----------------------------------------"); + + // Get player states. + new bool:statebuffer[ZR_VOLUMES_MAX]; + VolGetPlayerStates(targetclient, statebuffer, sizeof(statebuffer)); + + // Set language. + SetGlobalTransTarget(client); + + // Loop through each volume. + for (new volumeindex = 0; volumeindex < ZR_VOLUMES_MAX; volumeindex++) + { + // Check if volume is in use. + if (VolInUse(volumeindex)) + { + // Dump state. + ReplyToCommand(client, "%-11d %t", volumeindex, statebuffer[volumeindex] ? "Yes" : "No"); + } + } + + return Plugin_Handled; } /** @@ -59,6 +483,7 @@ public Action:VolAddVolumeCommand(client, argc) * * Note: Extra volume attributes must be set using VolSetAttributes. * + * @param index Optional. Add volume at the specified index. * @param locMin Minimum x, y and z values. * @param locMax Maximum x, y and z values. * @param volumeType Specifies the volumetric feature type. @@ -66,39 +491,43 @@ public Action:VolAddVolumeCommand(client, argc) * * @return The new volume index, or -1 if failed. */ -VolAdd(Float:locMin[3], Float:locMax[3], VolumeFeatureTypes:volumeType, dataIndex) +VolAdd(volumeIndex = -1, Float:locMin[3], Float:locMax[3], VolumeFeatureTypes:volumeType, dataIndex) { - new volumeIndex; - - // Get a free volume index. - volumeIndex = VolGetFreeVolume(); + if (volumeIndex < 0) + { + // Get a free volume index. + volumeIndex = VolGetFreeVolume(); + } // Validate index. - if (volumeIndex >= 0) + if (VolIsValidIndex(volumeIndex)) { // Mark volume as enabled and in use. - Volumes[volumeIndex][vol_enabled] = true; - Volumes[volumeIndex][vol_in_use] = true; + Volumes[volumeIndex][Vol_Enabled] = true; + Volumes[volumeIndex][Vol_InUse] = true; // Set location data. - Volumes[volumeIndex][vol_x_min] = locMin[0]; - Volumes[volumeIndex][vol_y_min] = locMin[1]; - Volumes[volumeIndex][vol_z_min] = locMin[2]; + Volumes[volumeIndex][Vol_xMin] = locMin[0]; + Volumes[volumeIndex][Vol_yMin] = locMin[1]; + Volumes[volumeIndex][Vol_zMin] = locMin[2]; - Volumes[volumeIndex][vol_x_max] = locMax[0]; - Volumes[volumeIndex][vol_y_max] = locMax[1]; - Volumes[volumeIndex][vol_z_max] = locMax[2]; + Volumes[volumeIndex][Vol_xMax] = locMax[0]; + Volumes[volumeIndex][Vol_yMax] = locMax[1]; + Volumes[volumeIndex][Vol_zMax] = locMax[2]; // Set type. - Volumes[volumeIndex][vol_type] = volumeType; - Volumes[volumeIndex][vol_data_index] = dataIndex; + Volumes[volumeIndex][Vol_Type] = volumeType; + Volumes[volumeIndex][Vol_DataIndex] = dataIndex; + + // Update number of volumes. + VolumeCount++; // Return the new index. return volumeIndex; } else { - // No free volumes. + // No free volumes or invalid index. return -1; } } @@ -112,14 +541,22 @@ VolAdd(Float:locMin[3], Float:locMax[3], VolumeFeatureTypes:volumeType, dataInde bool:VolRemove(volumeIndex) { // Validate index. - if (volumeIndex >= 0) + if (VolIsValidIndex(volumeIndex)) { - // Trigger event. - VolOnVolumeDisabled(volumeIndex); + // Trigger event to clean up data and stop timers. + VolOnDisabled(volumeIndex); - // Mark volume as disabled and unused. - Volumes[volumeIndex][vol_enabled] = false; - Volumes[volumeIndex][vol_in_use] = false; + // Clear feature data. + switch (Volumes[volumeIndex][Vol_Type]) + { + case VolFeature_Anticamp: + { + VolAnticampReset(Volumes[volumeIndex][Vol_DataIndex]); + } + } + + // Clear volume data. + VolClear(volumeIndex); return true; } @@ -130,6 +567,31 @@ bool:VolRemove(volumeIndex) } } +/** + * Clears volume data at the specified index. + * + * @param volumeIndex The volume index. + */ +VolClear(volumeIndex) +{ + Volumes[volumeIndex][Vol_Enabled] = false; + Volumes[volumeIndex][Vol_InUse] = false; + + Volumes[volumeIndex][Vol_xMin] = 0.0; + Volumes[volumeIndex][Vol_yMin] = 0.0; + Volumes[volumeIndex][Vol_zMin] = 0.0; + + Volumes[volumeIndex][Vol_xMax] = 0.0; + Volumes[volumeIndex][Vol_yMax] = 0.0; + Volumes[volumeIndex][Vol_zMax] = 0.0; + + Volumes[volumeIndex][Vol_Type] = VolFeature_Invalid; + Volumes[volumeIndex][Vol_DataIndex] = -1; + + Volumes[volumeIndex][Vol_TeamFilter] = VolTeam_All; + Volumes[volumeIndex][Vol_TriggerDelay] = 0.0; +} + /** * Sets extra attributes on a volume. * @@ -142,6 +604,8 @@ VolSetAttributes(volumeIndex, const String:attributes[]) { new attribCount; new successfulCount; + new VolumeFeatureTypes:voltype; + new dataindex; decl String:attribName[64]; decl String:attribValue[256]; @@ -160,8 +624,14 @@ VolSetAttributes(volumeIndex, const String:attributes[]) return -1; } + // Get volumetric feature type. + voltype = Volumes[volumeIndex][Vol_Type]; + + // Get feature data index. + dataindex = Volumes[volumeIndex][Vol_DataIndex]; + // Loop through all attributes. - for (new attrib = 0; attrib > attribCount; attrib++) + for (new attrib = 0; attrib < attribCount; attrib++) { // Get attribute name. GetParameterName(attribName, sizeof(attribName), attributes, attrib); @@ -169,8 +639,10 @@ VolSetAttributes(volumeIndex, const String:attributes[]) // Get attribute value. GetParameterValue(attribValue, sizeof(attribValue), attributes, attribName); - // Check names and set volume attributes. - if (strcmp(attribName, "team", false)) + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Set attribute", "Got parameter: \"%s\" = \"%s\"", attribName, attribValue); + + // Check generic attributes. + if (StrEqual(attribName, "teamfilter", false)) { // Parse team string value. if (VolSetTeamString(volumeIndex, attribValue)) @@ -178,7 +650,7 @@ VolSetAttributes(volumeIndex, const String:attributes[]) successfulCount++; } } - else if (strcmp(attribName, "delay", false)) + else if (StrEqual(attribName, "delay", false)) { // Parse delay string value. if (VolSetDelayString(volumeIndex, attribValue)) @@ -186,7 +658,7 @@ VolSetAttributes(volumeIndex, const String:attributes[]) successfulCount++; } } - else if (strcmp(attribName, "effect", false)) + else if (StrEqual(attribName, "effect", false)) { // Parse effect string value. if (VolSetEffectString(volumeIndex, attribValue)) @@ -194,7 +666,7 @@ VolSetAttributes(volumeIndex, const String:attributes[]) successfulCount++; } } - else if (strcmp(attribName, "effect_color", false)) + else if (StrEqual(attribName, "effect_color", false)) { // Parse effect color string value. if (VolSetEffectColorString(volumeIndex, attribValue)) @@ -202,7 +674,7 @@ VolSetAttributes(volumeIndex, const String:attributes[]) successfulCount++; } } - else if (strcmp(attribName, "enabled", false)) + else if (StrEqual(attribName, "enabled", false)) { // Parse enabled string value. if (VolSetEnabledString(volumeIndex, attribValue)) @@ -210,16 +682,23 @@ VolSetAttributes(volumeIndex, const String:attributes[]) successfulCount++; } } + + // Pass attribute onto the volumetric feature attribute handler. + else + { + switch (voltype) + { + case VolFeature_Anticamp: + { + if (VolAnticampSetAttribute(dataindex, attribName, attribValue)) + { + successfulCount++; + } + } + } + } } // Return number of successfully attributes set. return successfulCount; } - -/** - * Creates commands for managing volumes. - */ -VolOnCommandsCreate() -{ - RegAdminCmd("zr_vol_add", VolAddVolumeCommand, ADMFLAG_GENERIC, "Adds a new volume. Usage: zr_vol_add [params]"); -} diff --git a/src/zr/volfeatures/volevents.inc b/src/zr/volfeatures/volevents.inc index 3531c3d..33f1398 100644 --- a/src/zr/volfeatures/volevents.inc +++ b/src/zr/volfeatures/volevents.inc @@ -39,9 +39,11 @@ VolOnPlayerEnter(client, volumeIndex) // Volumetric features disabled. return; } - + + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Event", "Player %N entered volume %d.", client, volumeIndex); + // Forward event to features. - // VolAnticampStart(client, volume); + } /** @@ -59,8 +61,10 @@ VolOnPlayerLeave(client, volumeIndex) return; } + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Event", "Player %N left volume %d.", client, volumeIndex); + // Forward event to features. - // VolAnticampStop(client, volume); + } /** @@ -106,6 +110,9 @@ VolOnRoundStart() // Start main timer. VolStartUpdateTimer(); + + // Start volumes. + VolEnableVolumes(); } /** @@ -116,26 +123,42 @@ VolOnRoundEnd() // Stop main timer. VolStopUpdateTimer(); - // Forward stop event to features. - // VolAnticampStop(); + // Stop volumes. + VolDisableVolumes(); } /** * Called when a volume is disabled. * @param volumeIndex The volume index. */ -VolOnVolumeDisabled(volumeIndex) +VolOnDisabled(volumeIndex) { - // Forward stop event to features. + new VolumeFeatureTypes:voltype = Volumes[volumeIndex][Vol_Type]; + // Forward stop event to features. + switch (voltype) + { + case VolFeature_Anticamp: + { + VolAnticampDisable(volumeIndex); + } + } } /** * Called when a volume is enabled. * @param volumeIndex The volume index. */ -VolOnVolumeEnabled(volumeIndex) +VolOnEnabled(volumeIndex) { - // Forward start event to features. + new VolumeFeatureTypes:voltype = Volumes[volumeIndex][Vol_Type]; + // Forward stop event to features. + switch (voltype) + { + case VolFeature_Anticamp: + { + VolAnticampEnable(volumeIndex); + } + } } diff --git a/src/zr/volfeatures/volfeatures.inc b/src/zr/volfeatures/volfeatures.inc index f0192d7..1f67dfd 100644 --- a/src/zr/volfeatures/volfeatures.inc +++ b/src/zr/volfeatures/volfeatures.inc @@ -36,30 +36,30 @@ enum VolumeAttributes { /* General */ - bool:vol_enabled, /** Volume state. */ - bool:vol_in_use, /** Marks if the volume is used. */ + bool:Vol_Enabled, /** Volume state. */ + bool:Vol_InUse, /** Marks if the volume is used. */ /* Location */ - Float:vol_x_min, /** Minimum x position. */ - Float:vol_x_max, /** Maximum x position. */ + Float:Vol_xMin, /** Minimum x position. */ + Float:Vol_xMax, /** Maximum x position. */ - Float:vol_y_min, /** Minimum y position. */ - Float:vol_y_max, /** Maximum y position. */ + Float:Vol_yMin, /** Minimum y position. */ + Float:Vol_yMax, /** Maximum y position. */ - Float:vol_z_min, /** Minimum z position. */ - Float:vol_z_max, /** Maximum z position. */ + Float:Vol_zMin, /** Minimum z position. */ + Float:Vol_zMax, /** Maximum z position. */ /* Style */ - VolumeEffects:vol_effect, /** Visual effect to apply on the volume. */ - vol_effect_color[3], /** Render color of the effect. RGB colors. */ + VolumeEffects:Vol_Effect, /** Visual effect to apply on the volume. */ + Vol_EffectColor[3], /** Render color of the effect. RGB colors. */ /* Data */ - VolumeFeatureTypes:vol_type, /** The volumetric feature type. */ - vol_data_index, /** Index in remote feature array. */ + VolumeFeatureTypes:Vol_Type, /** The volumetric feature type. */ + Vol_DataIndex, /** Index in remote feature array. */ /* Behaviour */ - VolumeTeamFilters:vol_team_filter, /** Team filtering. Trigger by certain teams, or all. */ - Float:vol_trigger_delay /** Trigger delay. How many seconds players have to stay to trigger volume events. */ + VolumeTeamFilters:Vol_TeamFilter, /** Team filtering. Trigger by certain teams, or all. */ + Float:Vol_TriggerDelay /** Trigger delay. How many seconds players have to stay to trigger volume events. */ } /** @@ -67,7 +67,8 @@ enum VolumeAttributes */ enum VolumeFeatureTypes { - VolFeature_Anticamp = 0, + VolFeature_Invalid = 0, + VolFeature_Anticamp, VolFeature_Knockback } @@ -106,6 +107,11 @@ new VolumeCount; */ new Float:VolPlayerLoc[MAXPLAYERS + 1][3]; +/** + * Cache that specifies if a player is in a volume or not. + */ +new bool:VolPlayerInVolume[MAXPLAYERS + 1][ZR_VOLUMES_MAX]; + /** * Specifies whether the volumetric features module is enabled or not. Synced * with zr_vol CVAR. @@ -121,7 +127,7 @@ new Float:VolPlayerCountDown[MAXPLAYERS + 1][ZR_VOLUMES_MAX]; * The handle for a timer that updates player locations. This is the main timer * and any feature events can't be updated faster than this interval. * - * Note: Some features may have its own timer. + * Note: Some features may have its own timer for actions on players. */ new Handle:hVolUpdateTimer; @@ -138,6 +144,8 @@ new Float:VolTriggerInterval; #include "zr/volfeatures/volevents" #include "zr/volfeatures/volgenericattributes" #include "zr/volfeatures/volcommands" + +// Sub features. #include "zr/volfeatures/volanticamp" @@ -148,6 +156,9 @@ VolLoad() { // Cache CVAR value. VolEnabled = GetConVarBool(g_hCvarsList[CVAR_VOL]); + + // Initialize sub features. + VolAnticampInit(); } /** @@ -157,8 +168,51 @@ VolDisable() { VolEnabled = false; VolStopUpdateTimer(); + VolDisableVolumes(); - // TODO: Send disable/stop event to volumes with their own timers. + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Disabled", "Volfeatures disabled."); +} + +/** + * Function alias for starting volumetric features. + */ +VolEnable() +{ + VolEnabled = true; + VolStartUpdateTimer(); + VolEnableVolumes(); + + LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Enabled", "Volfeatures enabled."); +} + +/** + * Disables all enabled volumes. + */ +VolDisableVolumes() +{ + // Trigger disable event on all enabled volumes in use. + for (new volindex = 0; volindex < ZR_VOLUMES_MAX; volindex++) + { + if (Volumes[volindex][Vol_InUse] && Volumes[volindex][Vol_Enabled]) + { + VolOnDisabled(volindex); + } + } +} + +/** + * Enables all disabled volumes. + */ +VolEnableVolumes() +{ + // Trigger enable event on all volumes in use. + for (new volindex = 0; volindex < ZR_VOLUMES_MAX; volindex++) + { + if (Volumes[volindex][Vol_InUse] && !Volumes[volindex][Vol_Enabled]) + { + VolOnEnabled(volindex); + } + } } /** @@ -195,7 +249,8 @@ bool:VolStartUpdateTimer() } else { - // Volumetric features disabled. + // Volumetric features disabled. + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Volfeatures, "Config Validation", "Warning: Console variable \"zr_vol_update_interval\" is set to zero or negative. Must be positive."); return false; } } @@ -236,7 +291,7 @@ bool:VolStartTriggerTimer() if (VolTriggerInterval > 0.0) { // Start the timer. - hVolTriggerTimer = CreateTimer(VolTriggerInterval, Event_VolUpdateTimer, _, TIMER_REPEAT); + hVolTriggerTimer = CreateTimer(VolTriggerInterval, Event_VolTriggerTimer, _, TIMER_REPEAT); // Trigger timer started. return true; @@ -244,6 +299,7 @@ bool:VolStartTriggerTimer() else { // Trigger timer not running. Either disabled or invalid interval. + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Volfeatures, "Config Validation", "Warning: Console variable \"zr_vol_trigger_interval\" is set to zero or negative. Must be positive."); return false; } } @@ -302,7 +358,7 @@ VolResetCountDown(client = -1) */ VolUpdatePlayerLocation(client = -1) { - if (client <= 0) + if (client > 0) { // Assume the client is valid and save location in array. GetClientAbsOrigin(client, VolPlayerLoc[client]); @@ -343,10 +399,11 @@ VolUpdatePlayerChanges() // Check if client is in game and alive. if (!IsClientConnected(client) || !IsClientInGame(client) || !IsPlayerAlive(client)) { - return; + // Skip client. + continue; } - // Get the current volume states. + // Get the current volume states based on player location cache. VolGetPlayerStates(client, volumeStates, sizeof(volumeStates)); // Update player location cache. @@ -356,23 +413,37 @@ VolUpdatePlayerChanges() VolGetPlayerStates(client, volumeNewStates, sizeof(volumeNewStates)); // Loop through each volume and compare states. - for (new volumeIndex = 0; volumeIndex < VolumeCount; volumeIndex++) + for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) { + // Check if the volume is disabled and unused. + if (!VolInUse(volumeIndex) || !VolIsEnabled(volumeIndex)) + { + // Skip volume. + continue; + } + + // Check team filtering on the volume. + if (!VolTeamFilterMatch(client, volumeIndex)) + { + // Team filter mismatch. + continue; + } + newState = volumeNewStates[volumeIndex]; oldState = volumeStates[volumeIndex]; - // Compare new states with old states. + // Check for no change. if (newState == oldState) { // No change. Skip to next volume. - break; + continue; } // Check if client entered the volume. - else if (!newState && oldState) + if (newState && !oldState) { // Get trigger delay value. - trigger_delay = Volumes[volumeIndex][vol_trigger_delay]; + trigger_delay = Volumes[volumeIndex][Vol_TriggerDelay]; // Check if the volume has a trigger delay. if (trigger_delay > 0.0) @@ -382,17 +453,23 @@ VolUpdatePlayerChanges() } else { + // Update cache. + VolPlayerInVolume[client][volumeIndex] = true; + // No trigger delay, trigger event instantly. VolOnPlayerEnter(client, volumeIndex); } } // Check if client left the volume. - else if (newState && !oldState) + else if (!newState && oldState) { // Make sure count down value is reset. VolPlayerCountDown[client][volumeIndex] = -1.0; + // Update cache. + VolPlayerInVolume[client][volumeIndex] = false; + // Trigger event. VolOnPlayerLeave(client, volumeIndex); } @@ -401,20 +478,20 @@ VolUpdatePlayerChanges() } /** - * Returns wether a position is within a certain location. + * Returns wether a point is within a certain location. * - * @param loc The position to check. + * @param point The point to check. * @param min Minimum x, y and z values of the location. * @param max Maximum x, y and z values of the location. * @return True if the position is within min and max values. False * otherwise. */ -bool:IsPositionInLocation(Float:pos[3], Float:min[3], Float:max[3]) +bool:IsPointInLocation(Float:point[3], Float:min[3], Float:max[3]) { - // Cache location to avoid re-indexing arrays. - new Float:posX = pos[0]; - new Float:posY = pos[1]; - new Float:posZ = pos[2]; + // Cache to avoid re-indexing arrays. + new Float:posX = point[0]; + new Float:posY = point[1]; + new Float:posZ = point[2]; // Check if within x boundaries. if ((posX >= min[0]) && (posX <= max[0])) @@ -425,13 +502,13 @@ bool:IsPositionInLocation(Float:pos[3], Float:min[3], Float:max[3]) // Check if within x boundaries. if ((posZ >= min[2]) && (posZ <= max[2])) { - // The player is within the location boundaries. + // The point is within the location boundaries. return true; } } } - // The player is outside the location boundaries. + // The point is outside the location boundaries. return false; } @@ -443,9 +520,22 @@ bool:IsPositionInLocation(Float:pos[3], Float:min[3], Float:max[3]) * @param volumeIndex The volume index. * @return True if in use, false otherwise. */ -bool:VolIsInUse(volumeIndex) +bool:VolInUse(volumeIndex) { - return Volumes[volumeIndex][vol_in_use]; + return Volumes[volumeIndex][Vol_InUse]; +} + +/** + * Returns wether a volume is enabled or not. + * + * Note: Does not validate index. + * + * @param volumeIndex The volume index. + * @return True if enabled, false otherwise. + */ +bool:VolIsEnabled(volumeIndex) +{ + return Volumes[volumeIndex][Vol_Enabled]; } /** @@ -478,7 +568,7 @@ VolGetFreeVolume() for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) { // Check if it's free. - if (!VolIsInUse(volumeIndex)) + if (!VolInUse(volumeIndex)) { return volumeIndex; } @@ -488,6 +578,69 @@ VolGetFreeVolume() return -1; } +/** + * Gets a free index in the data array for the specified volume type. + * + * @param volumeType Volumetric feature type. + * @return Data index, or -1 on error. + */ +VolGetFreeDataIndex(VolumeFeatureTypes:volumeType) +{ + switch (volumeType) + { + case VolFeature_Anticamp: + { + return VolAnticampGetFreeIndex(); + } + case VolFeature_Knockback: + { + // TOTO: Finish incomplete feature. + return -1; + } + } + + // No match. + return -1; +} + +/** + * Checks if the specified client match the team filtering for the specified + * volume. + * + * @param client The client index. + * @param volumeIndex The volume to check team filtering on. + * @return True if client pass the team filtering, false otherwise. + */ +bool:VolTeamFilterMatch(client, volumeIndex) +{ + new VolumeTeamFilters:filter; + + // Chache filter value. + filter = Volumes[volumeIndex][Vol_TeamFilter]; + + switch (filter) + { + case VolTeam_All: + { + // All maches everyone. + return true; + } + case VolTeam_Humans: + { + // Check if client is a human. + return InfectIsClientHuman(client); + } + case VolTeam_Zombies: + { + // Check if client is a zombie. + return InfectIsClientInfected(client); + } + } + + // Invalid filter value. + return false; +} + /** * Gets wether a client is within volumes or not. Result is stored in a boolean * array. @@ -506,25 +659,25 @@ VolGetPlayerStates(client, bool:buffer[], maxlen) new Float:volMaxBuffer[3]; // Loop through all available volumes. - for (new volumeIndex = 0; volumeIndex < VolumeCount && volumeIndex < maxlen; volumeIndex++) + for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX && volumeIndex < maxlen; volumeIndex++) { - if (VolIsInUse(volumeIndex)) + if (VolInUse(volumeIndex)) { // Chache volume to avoid re-indexing. volumeBuffer = Volumes[volumeIndex]; // Get min positions. - volMinBuffer[0] = volumeBuffer[vol_x_min]; - volMinBuffer[1] = volumeBuffer[vol_y_min]; - volMinBuffer[2] = volumeBuffer[vol_z_min]; + volMinBuffer[0] = volumeBuffer[Vol_xMin]; + volMinBuffer[1] = volumeBuffer[Vol_yMin]; + volMinBuffer[2] = volumeBuffer[Vol_zMin]; // Get max positions. - volMaxBuffer[0] = volumeBuffer[vol_x_min]; - volMaxBuffer[1] = volumeBuffer[vol_y_min]; - volMaxBuffer[2] = volumeBuffer[vol_z_min]; + volMaxBuffer[0] = volumeBuffer[Vol_xMax]; + volMaxBuffer[1] = volumeBuffer[Vol_yMax]; + volMaxBuffer[2] = volumeBuffer[Vol_zMax]; // Check the cached player location. - if (IsPositionInLocation(VolPlayerLoc[client], volMinBuffer, volMaxBuffer)) + if (IsPointInLocation(VolPlayerLoc[client], volMinBuffer, volMaxBuffer)) { // Mark player as in volume. buffer[volumeIndex] = true; @@ -541,6 +694,65 @@ VolGetPlayerStates(client, bool:buffer[], maxlen) return volCount; } +/** + * Converts a string into a volumetric feature type. + * + * @param volType String to convert. Name of type. + * @return Volumetric feature type or VolFeature_Invalid on error. + */ +VolumeFeatureTypes:VolGetTypeFromString(const String:volType[]) +{ + // Check if empty. + if (strlen(volType) == 0) + { + return VolFeature_Invalid; + } + + // Match types. + if (StrEqual(volType, "anticamp", false)) + { + return VolFeature_Anticamp; + } + else if (StrEqual(volType, "knockback", false)) + { + return VolFeature_Knockback; + } + + // No match. + return VolFeature_Invalid; +} + +/** + * Converts a volume type to a string. + * + * @param volType Volume type to convert. + * @param buffer Destination string buffer. + * @param maxlen Size of destination buffer. + * @param shortName Optional. Write short name or human readable name. + * Default is human readable (false). + * @return Number of cells written. + */ +VolTypeToString(VolumeFeatureTypes:volType, String:buffer[], maxlen, bool:shortName = false) +{ + switch (volType) + { + case VolFeature_Invalid: + { + return shortName ? strcopy(buffer, maxlen, "") : strcopy(buffer, maxlen, "(none)"); + } + case VolFeature_Anticamp: + { + return shortName ? strcopy(buffer, maxlen, "anticamp") : strcopy(buffer, maxlen, "Anti camp"); + } + case VolFeature_Knockback: + { + return shortName ? strcopy(buffer, maxlen, "knockback") : strcopy(buffer, maxlen, "Knock back modifier"); + } + } + + return 0; +} + /** * Callback for update timer. This is the main timer in volumetric features. */ @@ -562,6 +774,13 @@ public Action:Event_VolTriggerTimer(Handle:timer) // Loop through all volumes. for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) { + // Check if volume is in use and enabled. + if (!VolInUse(volumeIndex) || !VolIsEnabled(volumeIndex)) + { + // Not in use or enabled, skip volume. + continue; + } + // Get count down value. countDown = VolPlayerCountDown[client][volumeIndex]; @@ -571,15 +790,21 @@ public Action:Event_VolTriggerTimer(Handle:timer) // Substract by trigger interval. countDown -= VolTriggerInterval; - // Check if zero or below. + // Check if time is up. if (countDown <= 0.0) { + // Update cache. + VolPlayerInVolume[client][volumeIndex] = true; + // Trigger volume enter event. VolOnPlayerEnter(client, volumeIndex); // Reset count down value. VolPlayerCountDown[client][volumeIndex] = -1.0; } + + // Update count down value and continue. + VolPlayerCountDown[client][volumeIndex] = countDown; } } } @@ -595,10 +820,7 @@ public VolEnabledChanged(Handle:cvar, const String:oldvalue[], const String:newv if (isEnabled) { // Volumetric features is enabled. - VolEnabled = true; - - // Start timers. - VolStartUpdateTimer(); + VolEnable(); } else { diff --git a/src/zr/volfeatures/volgenericattributes.inc b/src/zr/volfeatures/volgenericattributes.inc index 461665a..3632b1d 100644 --- a/src/zr/volfeatures/volgenericattributes.inc +++ b/src/zr/volfeatures/volgenericattributes.inc @@ -44,21 +44,21 @@ stock bool:VolSetTeamString(volumeIndex, const String:team[]) } // Convert value. - if (strcmp(team, "all", false)) + if (StrEqual(team, "all", false)) { teamfilter = VolTeam_All; } - else if (strcmp(team, "humans", false)) + else if (StrEqual(team, "humans", false)) { teamfilter = VolTeam_Humans; } - else if (strcmp(team, "zombies", false)) + else if (StrEqual(team, "zombies", false)) { teamfilter = VolTeam_Zombies; } // Apply value. - Volumes[volumeIndex][vol_team_filter] = teamfilter; + Volumes[volumeIndex][Vol_TeamFilter] = teamfilter; return true; } @@ -71,7 +71,38 @@ stock bool:VolSetTeamString(volumeIndex, const String:team[]) */ stock VolSetTeam(volumeIndex, VolumeTeamFilters:team[]) { - Volumes[volumeIndex][vol_team_filter] = team; + Volumes[volumeIndex][Vol_TeamFilter] = team; +} + +/** + * Converts a team name type to a string. + * + * @param team Team type to convert. + * @param buffer Destination string buffer. + * @param maxlen Size of destination buffer. + * @param shortName Optional. Write short name or human readable name. + * Default is human readable (false). + * @return Number of cells written. + */ +stock VolTeamToString(VolumeTeamFilters:team, String:buffer[], maxlen, bool:shortName = false) +{ + switch (team) + { + case VolTeam_All: + { + return shortName ? strcopy(buffer, maxlen, "all") : strcopy(buffer, maxlen, "All"); + } + case VolTeam_Humans: + { + return shortName ? strcopy(buffer, maxlen, "humans") : strcopy(buffer, maxlen, "Humans"); + } + case VolTeam_Zombies: + { + return shortName ? strcopy(buffer, maxlen, "zombies") : strcopy(buffer, maxlen, "Zombies"); + } + } + + return 0; } /** @@ -96,7 +127,7 @@ stock bool:VolSetDelayString(volumeIndex, const String:delay[]) triggerdelay = StringToFloat(delay); // Apply value. - Volumes[volumeIndex][vol_trigger_delay] = triggerdelay; + Volumes[volumeIndex][Vol_TriggerDelay] = triggerdelay; return true; } @@ -108,7 +139,7 @@ stock bool:VolSetDelayString(volumeIndex, const String:delay[]) */ stock VolSetDelay(volumeIndex, Float:delay) { - Volumes[volumeIndex][vol_trigger_delay] = delay; + Volumes[volumeIndex][Vol_TriggerDelay] = delay; } /** @@ -128,19 +159,19 @@ stock bool:VolSetEffectString(volumeIndex, const String:effect[]) } // Check effect string values and apply them to the volume. - if (strcmp(effect, "none", false)) + if (StrEqual(effect, "none", false)) { - Volumes[volumeIndex][vol_effect] = VolEffect_None; + Volumes[volumeIndex][Vol_Effect] = VolEffect_None; return true; } - else if (strcmp(effect, "wireframe", false)) + else if (StrEqual(effect, "wireframe", false)) { - Volumes[volumeIndex][vol_effect] = VolEffect_Wireframe; + Volumes[volumeIndex][Vol_Effect] = VolEffect_Wireframe; return true; } - else if (strcmp(effect, "smoke", false)) + else if (StrEqual(effect, "smoke", false)) { - Volumes[volumeIndex][vol_effect] = VolEffect_Smoke; + Volumes[volumeIndex][Vol_Effect] = VolEffect_Smoke; return true; } @@ -156,7 +187,38 @@ stock bool:VolSetEffectString(volumeIndex, const String:effect[]) */ stock VolSetEffect(volumeIndex, VolumeEffects:effect) { - Volumes[volumeIndex][vol_effect] = effect; + Volumes[volumeIndex][Vol_Effect] = effect; +} + +/** + * Converts a volume effect type to a string. + * + * @param effect Effect type to convert. + * @param buffer Destination string buffer. + * @param maxlen Size of destination buffer. + * @param shortName Optional. Write short name or human readable name. + * Default is human readable (false). + * @return Number of cells written. + */ +stock VolEffectToString(VolumeEffects:effect, String:buffer[], maxlen, bool:shortName = false) +{ + switch (effect) + { + case VolEffect_None: + { + return shortName ? strcopy(buffer, maxlen, "none") : strcopy(buffer, maxlen, "No effect"); + } + case VolEffect_Wireframe: + { + return shortName ? strcopy(buffer, maxlen, "wireframe") : strcopy(buffer, maxlen, "Wire frame"); + } + case VolEffect_Smoke: + { + return shortName ? strcopy(buffer, maxlen, "smoke") : strcopy(buffer, maxlen, "Smoke"); + } + } + + return 0; } /** @@ -187,9 +249,9 @@ stock bool:VolSetEffectColorString(volumeIndex, const String:effect_color[]) blue = StringToInt(colors[2]); // Apply values. - Volumes[volumeIndex][vol_effect_color][0] = red; - Volumes[volumeIndex][vol_effect_color][1] = green; - Volumes[volumeIndex][vol_effect_color][2] = blue; + Volumes[volumeIndex][Vol_EffectColor][0] = red; + Volumes[volumeIndex][Vol_EffectColor][1] = green; + Volumes[volumeIndex][Vol_EffectColor][2] = blue; return true; } @@ -227,31 +289,31 @@ stock bool:VolSetEnabledString(volumeIndex, const String:enabled[]) new bool:val = bool:StringToInt(enabled); // Check yes or no values first. - if (strcmp(enabled, "yes", false) == 0) + if (StrEqual(enabled, "yes", false)) { val = true; } - else if (strcmp(enabled, "no", false) == 0) + else if (StrEqual(enabled, "no", false)) { val = false; } // Check if the new value is different from the current. - if (Volumes[volumeIndex][vol_enabled] != val) + if (Volumes[volumeIndex][Vol_Enabled] != val) { // Forward event. if (val) { - VolOnVolumeEnabled(volumeIndex); + VolOnEnabled(volumeIndex); } else { - VolOnVolumeDisabled(volumeIndex); + VolOnDisabled(volumeIndex); } } // Apply converted value. - Volumes[volumeIndex][vol_enabled] = val; + Volumes[volumeIndex][Vol_Enabled] = val; return true; } @@ -264,7 +326,7 @@ stock bool:VolSetEnabledString(volumeIndex, const String:enabled[]) stock VolSetEnabled(volumeIndex, bool:enabled) { // Check if the new value is different from the current. - if (Volumes[volumeIndex][vol_enabled] != enabled) + if (Volumes[volumeIndex][Vol_Enabled] != enabled) { // Forward event. if (enabled) @@ -277,5 +339,5 @@ stock VolSetEnabled(volumeIndex, bool:enabled) } } - Volumes[volumeIndex][vol_enabled] = enabled; + Volumes[volumeIndex][Vol_Enabled] = enabled; }