Merged heads.

This commit is contained in:
Greyscale 2009-07-16 18:36:01 -07:00
commit 162bd1e107
20 changed files with 1892 additions and 189 deletions

Binary file not shown.

View File

@ -1092,16 +1092,18 @@
"ru" "Здоровье: {1}" "ru" "Здоровье: {1}"
} }
// ===========================
// Volumetric features (module)
// ===========================
"Vol Anticamp Message"
"Unfair camping"
{ {
"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}" "#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})."
} }
} }

View File

@ -80,7 +80,7 @@ warning is logged in the SourceMod error logs.
The validation prevents unexpected or invalid behaviour in the plugin. Dealing The validation prevents unexpected or invalid behaviour in the plugin. Dealing
with errors or warnings in error logs helps a lot troubleshooting eventual with errors or warnings in error logs helps a lot troubleshooting eventual
eventual issues in the plugin caused by incorrect configurations. issues in the plugin caused by incorrect configurations.
It's also possible to specify the path of configuration files. This can be used It's also possible to specify the path of configuration files. This can be used
in combination with map configs, so it's possible to have different in combination with map configs, so it's possible to have different

View File

@ -63,6 +63,10 @@ public const MaxClients; /**< Maximum number of players the server supports (dyn
* If you return false (or return nothing), the client will be rejected. If the client is * If you return false (or return nothing), the client will be rejected. If the client is
* rejected by this forward or any other, OnClientDisconnect will not be called. * rejected by this forward or any other, OnClientDisconnect will not be called.
* *
* Note: Do not write to rejectmsg if you plan on returning true. If multiple plugins write
* to the string buffer, it is not defined which plugin's string will be shown to the client,
* but it is guaranteed one of them will.
*
* @param client Client index. * @param client Client index.
* @param rejectmsg Buffer to store the rejection message when the connection is refused. * @param rejectmsg Buffer to store the rejection message when the connection is refused.
* @param maxlen Maximum number of characters for rejection buffer. * @param maxlen Maximum number of characters for rejection buffer.

View File

@ -817,9 +817,11 @@ native bool:FindNextConCommand(Handle:search, String:buffer[], max_size, &bool:i
native bool:SendConVarValue(client, Handle:convar, const String:value[]); native bool:SendConVarValue(client, Handle:convar, const String:value[]);
/** /**
* Appends a string to Valve's sv_tags convar and makes sure it remains after mapchanges. * Adds an informational string to the server's public "tags".
* This string should be a short, unique identifier.
* *
* Note: Tags are automatically removed on plugin unload * Note: Tags are automatically removed when a plugin unloads.
* Note: Currently, this function does nothing because of bugs in the Valve master.
* *
* @param tag Tag string to append. * @param tag Tag string to append.
* @noreturn * @noreturn
@ -827,9 +829,7 @@ native bool:SendConVarValue(client, Handle:convar, const String:value[]);
native AddServerTag(const String:tag[]); native AddServerTag(const String:tag[]);
/** /**
* Removes a string from valve's sv_tags convar. * Removes a tag previously added by the calling plugin.
*
* Note: You can only remove tags created by you.
* *
* @param tag Tag string to remove. * @param tag Tag string to remove.
* @noreturn * @noreturn

View File

@ -94,6 +94,8 @@ enum MenuAction
#define VOTEINFO_ITEM_INDEX 0 /**< Item index */ #define VOTEINFO_ITEM_INDEX 0 /**< Item index */
#define VOTEINFO_ITEM_VOTES 1 /**< Number of votes for the item */ #define VOTEINFO_ITEM_VOTES 1 /**< Number of votes for the item */
#define VOTEFLAG_NO_REVOTES (1<<0) /**< Players cannot change their votes */
/** /**
* Reasons a menu can be cancelled (MenuAction_Cancel). * Reasons a menu can be cancelled (MenuAction_Cancel).
*/ */
@ -469,22 +471,24 @@ native CancelVote();
* @param clients Array of clients to broadcast to. * @param clients Array of clients to broadcast to.
* @param numClients Number of clients in the array. * @param numClients Number of clients in the array.
* @param time Maximum time to leave menu on the screen. * @param time Maximum time to leave menu on the screen.
* @param flags Optional voting flags.
* @return True on success, false if this menu already has a vote session * @return True on success, false if this menu already has a vote session
* in progress. * in progress.
* @error Invalid Handle, or a vote is already in progress. * @error Invalid Handle, or a vote is already in progress.
*/ */
native bool:VoteMenu(Handle:menu, clients[], numClients, time); native bool:VoteMenu(Handle:menu, clients[], numClients, time, flags=0);
/** /**
* Sends a vote menu to all clients. See VoteMenu() for more information. * Sends a vote menu to all clients. See VoteMenu() for more information.
* *
* @param menu Menu Handle. * @param menu Menu Handle.
* @param time Maximum time to leave menu on the screen. * @param time Maximum time to leave menu on the screen.
* @param flags Optional voting flags.
* @return True on success, false if this menu already has a vote session * @return True on success, false if this menu already has a vote session
* in progress. * in progress.
* @error Invalid Handle. * @error Invalid Handle.
*/ */
stock VoteMenuToAll(Handle:menu, time) stock VoteMenuToAll(Handle:menu, time, flags=0)
{ {
new num = GetMaxClients(); new num = GetMaxClients();
new total; new total;
@ -499,7 +503,7 @@ stock VoteMenuToAll(Handle:menu, time)
players[total++] = i; players[total++] = i;
} }
return VoteMenu(menu, players, total, time); return VoteMenu(menu, players, total, time, flags);
} }
/** /**
* Callback for when a vote has ended and results are available. * Callback for when a vote has ended and results are available.
@ -543,7 +547,7 @@ native CheckVoteDelay();
/** /**
* Returns whether a client is in the pool of clients allowed * Returns whether a client is in the pool of clients allowed
* to participate in the current vote. This is determined by * to participate in the current vote. This is determined by
* the client list passed to StartVote(). * the client list passed to VoteMenu().
* *
* @param client Client index. * @param client Client index.
* @return True if client is allowed to vote, false otherwise. * @return True if client is allowed to vote, false otherwise.
@ -555,12 +559,13 @@ native bool:IsClientInVotePool(client);
* Redraws the current vote menu to a client in the voting pool. * Redraws the current vote menu to a client in the voting pool.
* *
* @param client Client index. * @param client Client index.
* @param revotes True to allow revotes, false otherwise.
* @return True on success, false if the client is in the vote pool * @return True on success, false if the client is in the vote pool
* but cannot vote again. * but cannot vote again.
* @error No vote in progress, client is not in the voting pool, * @error No vote in progress, client is not in the voting pool,
* or client index is invalid. * or client index is invalid.
*/ */
native bool:RedrawClientVoteMenu(client); native bool:RedrawClientVoteMenu(client, bool:revotes=true);
/** /**
* Returns a style's global Handle. * Returns a style's global Handle.
@ -807,3 +812,4 @@ stock bool:IsNewVoteAllowed()
return true; return true;
} }

View File

@ -57,6 +57,14 @@ enum TFTeam
TFTeam_Blue = 3 TFTeam_Blue = 3
}; };
/**
* Sets a client on fire for 10 seconds.
*
* @param client Player's index.
* @noreturn
* @error Invalid client index, client not in game, or no mod support.
*/
native TF2_IgnitePlayer(client, target);
/** /**
* Respawns a client * Respawns a client

View File

@ -37,6 +37,6 @@
#define SOURCEMOD_V_MAJOR 1 /**< SourceMod Major version */ #define SOURCEMOD_V_MAJOR 1 /**< SourceMod Major version */
#define SOURCEMOD_V_MINOR 2 /**< SourceMod Minor version */ #define SOURCEMOD_V_MINOR 2 /**< SourceMod Minor version */
#define SOURCEMOD_V_RELEASE 0 /**< SourceMod Release version */ #define SOURCEMOD_V_RELEASE 1 /**< SourceMod Release version */
#define SOURCEMOD_VERSION "1.2.0" /**< SourceMod version string (major.minor.release.build) */ #define SOURCEMOD_VERSION "1.2.1" /**< SourceMod version string (major.minor.release.build) */

View File

@ -80,6 +80,7 @@
#include "zr/zcookies" #include "zr/zcookies"
#include "zr/jumpboost" #include "zr/jumpboost"
#include "zr/volfeatures/volfeatures" #include "zr/volfeatures/volfeatures"
#include "zr/debugtools"
/** /**
* Record plugin info. * Record plugin info.
@ -136,6 +137,7 @@ public OnMapStart()
InfectOnMapStart(); InfectOnMapStart();
SEffectsOnMapStart(); SEffectsOnMapStart();
ZSpawnOnMapStart(); ZSpawnOnMapStart();
} }
/** /**

View File

@ -46,6 +46,8 @@ CommandsInit()
ZHPOnCommandsCreate(); ZHPOnCommandsCreate();
VolOnCommandsCreate(); VolOnCommandsCreate();
DebugOnCommandsCreate();
// Forward event to modules. (hook commands) // Forward event to modules. (hook commands)
ClassOnCommandsHook(); ClassOnCommandsHook();
DamageOnCommandsHook(); DamageOnCommandsHook();

View File

@ -429,8 +429,8 @@ CvarsCreate()
// Volumetric Features (module) // Volumetric Features (module)
// =========================== // ===========================
g_hCvarsList[CVAR_VOL] = CreateConVar("zr_vol", "1", "Enables volumetric features."); 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_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", "1.0", "How often to check for delayed events, in seconds. Use lower values for more precise delays."); 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.");
// =========================== // ===========================

105
src/zr/debugtools.inc Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* ============================================================================
*/
DebugOnCommandsCreate()
{
// Custom test commands:
RegConsoleCmd("zr_getparametercount", Command_GetParameterCount, "Test GetParameterCount function in paramtools.inc. Usage: zr_getparametercount <paramstring>");
RegConsoleCmd("zr_getparametervalue", Command_GetParameterValue, "Test GetParameterValue function in paramtools.inc. Usage: zr_getparametervalue <paramname> <paramstring>");
}
public Action:Command_GetParameterCount(client, argc)
{
if (argc < 1)
{
ReplyToCommand(client, "No parameter string passed. Usage: zr_getparametercount <paramstring>");
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 <paramname> <paramstring>");
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;
}

View File

@ -8,6 +8,8 @@
* Description: Provides functions for parsing strings with parameters in * Description: Provides functions for parsing strings with parameters in
* key=value format. * key=value format.
* *
* Note: Does not support spaces or quoted strings.
*
* Copyright (C) 2009 Greyscale, Richard Helgeby * Copyright (C) 2009 Greyscale, Richard Helgeby
* *
* This program is free software: you can redistribute it and/or modify * 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 lenRawString = strlen(rawString);
new paramCount; new paramCount;
new searchPos;
new splitPos;
// Check if the raw string is empty. // Check if the raw string is empty.
if (lenRawString == 0) if (lenRawString == 0)
@ -42,20 +46,26 @@ stock GetParameterCount(const String:rawString[])
} }
// Count number of "=". // Count number of "=".
for (new searchPos = 0; searchPos < lenRawString; searchPos++) for (splitPos = 0; splitPos < lenRawString; splitPos++)
{ {
// Set searchPos and check if "=" was found. // Find next "=".
searchPos = StrContains(rawString[searchPos], "="); searchPos = StrContains(rawString[splitPos], "=", false);
if (searchPos)
// 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++; paramCount++;
// Increment one position so we dont find the same "=" again. // Update split position.
searchPos++; splitPos += searchPos;
} }
else else
{ {
// Exit loop. // No need to continue. "=" not found.
break; break;
} }
} }
@ -86,9 +96,11 @@ stock GetParameterValue(String:buffer[], maxlen, const String:rawString[], const
new valuePos; new valuePos;
new valueLen; new valueLen;
new nextPos; new nextPos;
new splitPos;
// Get the position of parameter. // Get the position of parameter.
paramPos = StrContains(rawString, parameter, false); paramPos = StrContains(rawString, parameter, false);
splitPos = paramPos;
// Check if found. // Check if found.
if (paramPos >= 0) if (paramPos >= 0)
@ -96,8 +108,8 @@ stock GetParameterValue(String:buffer[], maxlen, const String:rawString[], const
// Get parameter length. // Get parameter length.
paramLen = strlen(parameter); paramLen = strlen(parameter);
// Get the position of the next parameter. // Get the position of the next parameter by finding the next space.
nextPos = StrContains(rawString[paramPos + 1], " "); nextPos = StrContains(rawString[splitPos], " ");
// Check if the next parameter was found. // Check if the next parameter was found.
if (nextPos >= 0) if (nextPos >= 0)
@ -105,8 +117,9 @@ stock GetParameterValue(String:buffer[], maxlen, const String:rawString[], const
// Calculate value starting position. // Calculate value starting position.
valuePos = paramPos + paramLen + 1; valuePos = paramPos + paramLen + 1;
// Calculate value length. // Calculate value length. Note: Adding 1 for space to the null
valueLen = nextPos - valuePos; // terminator.
valueLen = nextPos + splitPos - valuePos + 1;
// Check if value length is longer than buffer size. // Check if value length is longer than buffer size.
if (valueLen > maxlen) if (valueLen > maxlen)
@ -145,6 +158,7 @@ stock GetParameterName(String:buffer[], maxlen, const String:rawString[], parame
new paramPos; new paramPos;
new valuePos; new valuePos;
new nextValuePos; new nextValuePos;
new splitPos;
// Check if the raw string is empty. // Check if the raw string is empty.
if (strlen(rawString) == 0) 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. // Get the value starting position for the previous index.
for (new index = 0; index < parameterIndex - 1; 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. // 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. // of the parameter name.
nextValuePos = StrContains(rawString[paramPos], "="); nextValuePos = StrContains(rawString[splitPos], "=") + splitPos;
// Check if a value is specified. // Check if a value is specified.
if (nextValuePos > 0) if (nextValuePos > 0)
{ {
// Return the parameter name between paramPos and nextValuePos. // 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 else
{ {
@ -189,7 +207,7 @@ stock GetParameterName(String:buffer[], maxlen, const String:rawString[], parame
if (valuePos > 0) if (valuePos > 0)
{ {
// Return the parameter name. // Return the parameter name.
return strcopy(buffer, valuePos, rawString); return strcopy(buffer, valuePos + 1, rawString); // + 1 to include null terminator.
} }
else else
{ {

View File

@ -384,7 +384,7 @@ stock ClassGetIndex(const String:name[], cachetype = ZR_CLASS_CACHE_MODIFIED)
*/ */
stock ClassGetActiveIndex(client) stock ClassGetActiveIndex(client)
{ {
new teamid = GetClientTeam(client); new teamid;
if (!ZRIsClientOnTeam(client)) if (!ZRIsClientOnTeam(client))
{ {

View File

@ -56,6 +56,7 @@ TranslationInit()
{ {
// Load translations phrases used by plugin. // Load translations phrases used by plugin.
LoadTranslations("common.phrases.txt"); LoadTranslations("common.phrases.txt");
LoadTranslations("core.phrases.txt");
LoadTranslations("zombiereloaded.phrases.txt"); LoadTranslations("zombiereloaded.phrases.txt");
} }

View File

@ -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. * Actions to do with players in anti-camp volumes.
*/ */
enum VolAnticampAction enum VolAnticampAction
{ {
anticamp_no_action, /** Do nothing but give a warning. */ Anticamp_NoAction, /** Do nothing but give a warning. */
anticamp_damage, /** Give damage to player. */ Anticamp_Damage, /** Give damage to player. */
anticamp_slay, /** Slay player. */ Anticamp_Slay, /** Slay player. */
anticamp_drug, /** Drug player. */ Anticamp_Drug, /** Drug player. */
anticamp_fire /** Ignite player. */ Anticamp_Ignite /** Ignite player. */
} }
/** /**
@ -57,13 +42,797 @@ enum VolAnticampAction
*/ */
enum VolAnticampeWarningType enum VolAnticampeWarningType
{ {
anticamp_no_warning, /** No warning messages. */ Anticamp_NoWarning, /** No warning messages. */
anticamp_chat, /** Print warning in chat area. */ Anticamp_Chat, /** Print warning in chat area. */
anticamp_center, /** Print centered warning message. */ Anticamp_Center, /** Print centered warning message. */
anticamp_menu /** Print a menu-like warning message with close option. */ 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. * Anti-camp data.
*/ */
new AnticampData[ZR_VOLUMES_MAX][VolTypeAnticamp]; 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;
}

View File

@ -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 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 <x1> <y1> <z1> <x2> <y2> <z2> <type> [params]");
RegAdminCmd("zr_vol_remove", VolRemoveVolumeCommand, ADMFLAG_CONFIG, "Removes an existing volume in the map. Usage: zr_vol_remove <volume index>");
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 <index|targetname>");
}
/**
* Command callback for creating a new volume.
*/
public Action:VolAddVolumeCommand(client, argc) 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 <x1> <y1> <z1> <x2> <y2> <z2> <type> [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 <volume index>");
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 <index|targetname>");
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. * 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 locMin Minimum x, y and z values.
* @param locMax Maximum x, y and z values. * @param locMax Maximum x, y and z values.
* @param volumeType Specifies the volumetric feature type. * @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. * @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; if (volumeIndex < 0)
{
// Get a free volume index. // Get a free volume index.
volumeIndex = VolGetFreeVolume(); volumeIndex = VolGetFreeVolume();
}
// Validate index. // Validate index.
if (volumeIndex >= 0) if (VolIsValidIndex(volumeIndex))
{ {
// Mark volume as enabled and in use. // Mark volume as enabled and in use.
Volumes[volumeIndex][vol_enabled] = true; Volumes[volumeIndex][Vol_Enabled] = true;
Volumes[volumeIndex][vol_in_use] = true; Volumes[volumeIndex][Vol_InUse] = true;
// Set location data. // Set location data.
Volumes[volumeIndex][vol_x_min] = locMin[0]; Volumes[volumeIndex][Vol_xMin] = locMin[0];
Volumes[volumeIndex][vol_y_min] = locMin[1]; Volumes[volumeIndex][Vol_yMin] = locMin[1];
Volumes[volumeIndex][vol_z_min] = locMin[2]; Volumes[volumeIndex][Vol_zMin] = locMin[2];
Volumes[volumeIndex][vol_x_max] = locMax[0]; Volumes[volumeIndex][Vol_xMax] = locMax[0];
Volumes[volumeIndex][vol_y_max] = locMax[1]; Volumes[volumeIndex][Vol_yMax] = locMax[1];
Volumes[volumeIndex][vol_z_max] = locMax[2]; Volumes[volumeIndex][Vol_zMax] = locMax[2];
// Set type. // Set type.
Volumes[volumeIndex][vol_type] = volumeType; Volumes[volumeIndex][Vol_Type] = volumeType;
Volumes[volumeIndex][vol_data_index] = dataIndex; Volumes[volumeIndex][Vol_DataIndex] = dataIndex;
// Update number of volumes.
VolumeCount++;
// Return the new index. // Return the new index.
return volumeIndex; return volumeIndex;
} }
else else
{ {
// No free volumes. // No free volumes or invalid index.
return -1; return -1;
} }
} }
@ -112,14 +541,22 @@ VolAdd(Float:locMin[3], Float:locMax[3], VolumeFeatureTypes:volumeType, dataInde
bool:VolRemove(volumeIndex) bool:VolRemove(volumeIndex)
{ {
// Validate index. // Validate index.
if (volumeIndex >= 0) if (VolIsValidIndex(volumeIndex))
{ {
// Trigger event. // Trigger event to clean up data and stop timers.
VolOnVolumeDisabled(volumeIndex); VolOnDisabled(volumeIndex);
// Mark volume as disabled and unused. // Clear feature data.
Volumes[volumeIndex][vol_enabled] = false; switch (Volumes[volumeIndex][Vol_Type])
Volumes[volumeIndex][vol_in_use] = false; {
case VolFeature_Anticamp:
{
VolAnticampReset(Volumes[volumeIndex][Vol_DataIndex]);
}
}
// Clear volume data.
VolClear(volumeIndex);
return true; 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. * Sets extra attributes on a volume.
* *
@ -142,6 +604,8 @@ VolSetAttributes(volumeIndex, const String:attributes[])
{ {
new attribCount; new attribCount;
new successfulCount; new successfulCount;
new VolumeFeatureTypes:voltype;
new dataindex;
decl String:attribName[64]; decl String:attribName[64];
decl String:attribValue[256]; decl String:attribValue[256];
@ -160,8 +624,14 @@ VolSetAttributes(volumeIndex, const String:attributes[])
return -1; return -1;
} }
// Get volumetric feature type.
voltype = Volumes[volumeIndex][Vol_Type];
// Get feature data index.
dataindex = Volumes[volumeIndex][Vol_DataIndex];
// Loop through all attributes. // Loop through all attributes.
for (new attrib = 0; attrib > attribCount; attrib++) for (new attrib = 0; attrib < attribCount; attrib++)
{ {
// Get attribute name. // Get attribute name.
GetParameterName(attribName, sizeof(attribName), attributes, attrib); GetParameterName(attribName, sizeof(attribName), attributes, attrib);
@ -169,8 +639,10 @@ VolSetAttributes(volumeIndex, const String:attributes[])
// Get attribute value. // Get attribute value.
GetParameterValue(attribValue, sizeof(attribValue), attributes, attribName); GetParameterValue(attribValue, sizeof(attribValue), attributes, attribName);
// Check names and set volume attributes. LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Set attribute", "Got parameter: \"%s\" = \"%s\"", attribName, attribValue);
if (strcmp(attribName, "team", false))
// Check generic attributes.
if (StrEqual(attribName, "teamfilter", false))
{ {
// Parse team string value. // Parse team string value.
if (VolSetTeamString(volumeIndex, attribValue)) if (VolSetTeamString(volumeIndex, attribValue))
@ -178,7 +650,7 @@ VolSetAttributes(volumeIndex, const String:attributes[])
successfulCount++; successfulCount++;
} }
} }
else if (strcmp(attribName, "delay", false)) else if (StrEqual(attribName, "delay", false))
{ {
// Parse delay string value. // Parse delay string value.
if (VolSetDelayString(volumeIndex, attribValue)) if (VolSetDelayString(volumeIndex, attribValue))
@ -186,7 +658,7 @@ VolSetAttributes(volumeIndex, const String:attributes[])
successfulCount++; successfulCount++;
} }
} }
else if (strcmp(attribName, "effect", false)) else if (StrEqual(attribName, "effect", false))
{ {
// Parse effect string value. // Parse effect string value.
if (VolSetEffectString(volumeIndex, attribValue)) if (VolSetEffectString(volumeIndex, attribValue))
@ -194,7 +666,7 @@ VolSetAttributes(volumeIndex, const String:attributes[])
successfulCount++; successfulCount++;
} }
} }
else if (strcmp(attribName, "effect_color", false)) else if (StrEqual(attribName, "effect_color", false))
{ {
// Parse effect color string value. // Parse effect color string value.
if (VolSetEffectColorString(volumeIndex, attribValue)) if (VolSetEffectColorString(volumeIndex, attribValue))
@ -202,7 +674,7 @@ VolSetAttributes(volumeIndex, const String:attributes[])
successfulCount++; successfulCount++;
} }
} }
else if (strcmp(attribName, "enabled", false)) else if (StrEqual(attribName, "enabled", false))
{ {
// Parse enabled string value. // Parse enabled string value.
if (VolSetEnabledString(volumeIndex, attribValue)) if (VolSetEnabledString(volumeIndex, attribValue))
@ -210,16 +682,23 @@ VolSetAttributes(volumeIndex, const String:attributes[])
successfulCount++; 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 number of successfully attributes set.
return successfulCount; return successfulCount;
} }
/**
* Creates commands for managing volumes.
*/
VolOnCommandsCreate()
{
RegAdminCmd("zr_vol_add", VolAddVolumeCommand, ADMFLAG_GENERIC, "Adds a new volume. Usage: zr_vol_add <x1> <y1> <z1> <x2> <y2> <z2> <type> [params]");
}

View File

@ -40,8 +40,10 @@ VolOnPlayerEnter(client, volumeIndex)
return; return;
} }
LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Event", "Player %N entered volume %d.", client, volumeIndex);
// Forward event to features. // Forward event to features.
// VolAnticampStart(client, volume);
} }
/** /**
@ -59,8 +61,10 @@ VolOnPlayerLeave(client, volumeIndex)
return; return;
} }
LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Event", "Player %N left volume %d.", client, volumeIndex);
// Forward event to features. // Forward event to features.
// VolAnticampStop(client, volume);
} }
/** /**
@ -106,6 +110,9 @@ VolOnRoundStart()
// Start main timer. // Start main timer.
VolStartUpdateTimer(); VolStartUpdateTimer();
// Start volumes.
VolEnableVolumes();
} }
/** /**
@ -116,26 +123,42 @@ VolOnRoundEnd()
// Stop main timer. // Stop main timer.
VolStopUpdateTimer(); VolStopUpdateTimer();
// Forward stop event to features. // Stop volumes.
// VolAnticampStop(); VolDisableVolumes();
} }
/** /**
* Called when a volume is disabled. * Called when a volume is disabled.
* @param volumeIndex The volume index. * @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. * Called when a volume is enabled.
* @param volumeIndex The volume index. * @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);
}
}
} }

View File

@ -36,30 +36,30 @@
enum VolumeAttributes enum VolumeAttributes
{ {
/* General */ /* General */
bool:vol_enabled, /** Volume state. */ bool:Vol_Enabled, /** Volume state. */
bool:vol_in_use, /** Marks if the volume is used. */ bool:Vol_InUse, /** Marks if the volume is used. */
/* Location */ /* Location */
Float:vol_x_min, /** Minimum x position. */ Float:Vol_xMin, /** Minimum x position. */
Float:vol_x_max, /** Maximum x position. */ Float:Vol_xMax, /** Maximum x position. */
Float:vol_y_min, /** Minimum y position. */ Float:Vol_yMin, /** Minimum y position. */
Float:vol_y_max, /** Maximum y position. */ Float:Vol_yMax, /** Maximum y position. */
Float:vol_z_min, /** Minimum z position. */ Float:Vol_zMin, /** Minimum z position. */
Float:vol_z_max, /** Maximum z position. */ Float:Vol_zMax, /** Maximum z position. */
/* Style */ /* Style */
VolumeEffects:vol_effect, /** Visual effect to apply on the volume. */ VolumeEffects:Vol_Effect, /** Visual effect to apply on the volume. */
vol_effect_color[3], /** Render color of the effect. RGB colors. */ Vol_EffectColor[3], /** Render color of the effect. RGB colors. */
/* Data */ /* Data */
VolumeFeatureTypes:vol_type, /** The volumetric feature type. */ VolumeFeatureTypes:Vol_Type, /** The volumetric feature type. */
vol_data_index, /** Index in remote feature array. */ Vol_DataIndex, /** Index in remote feature array. */
/* Behaviour */ /* Behaviour */
VolumeTeamFilters:vol_team_filter, /** Team filtering. Trigger by certain teams, or all. */ VolumeTeamFilters:Vol_TeamFilter, /** 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. */ Float:Vol_TriggerDelay /** Trigger delay. How many seconds players have to stay to trigger volume events. */
} }
/** /**
@ -67,7 +67,8 @@ enum VolumeAttributes
*/ */
enum VolumeFeatureTypes enum VolumeFeatureTypes
{ {
VolFeature_Anticamp = 0, VolFeature_Invalid = 0,
VolFeature_Anticamp,
VolFeature_Knockback VolFeature_Knockback
} }
@ -106,6 +107,11 @@ new VolumeCount;
*/ */
new Float:VolPlayerLoc[MAXPLAYERS + 1][3]; 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 * Specifies whether the volumetric features module is enabled or not. Synced
* with zr_vol CVAR. * 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 * 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. * 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; new Handle:hVolUpdateTimer;
@ -138,6 +144,8 @@ new Float:VolTriggerInterval;
#include "zr/volfeatures/volevents" #include "zr/volfeatures/volevents"
#include "zr/volfeatures/volgenericattributes" #include "zr/volfeatures/volgenericattributes"
#include "zr/volfeatures/volcommands" #include "zr/volfeatures/volcommands"
// Sub features.
#include "zr/volfeatures/volanticamp" #include "zr/volfeatures/volanticamp"
@ -148,6 +156,9 @@ VolLoad()
{ {
// Cache CVAR value. // Cache CVAR value.
VolEnabled = GetConVarBool(g_hCvarsList[CVAR_VOL]); VolEnabled = GetConVarBool(g_hCvarsList[CVAR_VOL]);
// Initialize sub features.
VolAnticampInit();
} }
/** /**
@ -157,8 +168,51 @@ VolDisable()
{ {
VolEnabled = false; VolEnabled = false;
VolStopUpdateTimer(); 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);
}
}
} }
/** /**
@ -196,6 +250,7 @@ bool:VolStartUpdateTimer()
else 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; return false;
} }
} }
@ -236,7 +291,7 @@ bool:VolStartTriggerTimer()
if (VolTriggerInterval > 0.0) if (VolTriggerInterval > 0.0)
{ {
// Start the timer. // Start the timer.
hVolTriggerTimer = CreateTimer(VolTriggerInterval, Event_VolUpdateTimer, _, TIMER_REPEAT); hVolTriggerTimer = CreateTimer(VolTriggerInterval, Event_VolTriggerTimer, _, TIMER_REPEAT);
// Trigger timer started. // Trigger timer started.
return true; return true;
@ -244,6 +299,7 @@ bool:VolStartTriggerTimer()
else else
{ {
// Trigger timer not running. Either disabled or invalid interval. // 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; return false;
} }
} }
@ -302,7 +358,7 @@ VolResetCountDown(client = -1)
*/ */
VolUpdatePlayerLocation(client = -1) VolUpdatePlayerLocation(client = -1)
{ {
if (client <= 0) if (client > 0)
{ {
// Assume the client is valid and save location in array. // Assume the client is valid and save location in array.
GetClientAbsOrigin(client, VolPlayerLoc[client]); GetClientAbsOrigin(client, VolPlayerLoc[client]);
@ -343,10 +399,11 @@ VolUpdatePlayerChanges()
// Check if client is in game and alive. // Check if client is in game and alive.
if (!IsClientConnected(client) || !IsClientInGame(client) || !IsPlayerAlive(client)) 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)); VolGetPlayerStates(client, volumeStates, sizeof(volumeStates));
// Update player location cache. // Update player location cache.
@ -356,23 +413,37 @@ VolUpdatePlayerChanges()
VolGetPlayerStates(client, volumeNewStates, sizeof(volumeNewStates)); VolGetPlayerStates(client, volumeNewStates, sizeof(volumeNewStates));
// Loop through each volume and compare states. // 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]; newState = volumeNewStates[volumeIndex];
oldState = volumeStates[volumeIndex]; oldState = volumeStates[volumeIndex];
// Compare new states with old states. // Check for no change.
if (newState == oldState) if (newState == oldState)
{ {
// No change. Skip to next volume. // No change. Skip to next volume.
break; continue;
} }
// Check if client entered the volume. // Check if client entered the volume.
else if (!newState && oldState) if (newState && !oldState)
{ {
// Get trigger delay value. // 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. // Check if the volume has a trigger delay.
if (trigger_delay > 0.0) if (trigger_delay > 0.0)
@ -382,17 +453,23 @@ VolUpdatePlayerChanges()
} }
else else
{ {
// Update cache.
VolPlayerInVolume[client][volumeIndex] = true;
// No trigger delay, trigger event instantly. // No trigger delay, trigger event instantly.
VolOnPlayerEnter(client, volumeIndex); VolOnPlayerEnter(client, volumeIndex);
} }
} }
// Check if client left the volume. // Check if client left the volume.
else if (newState && !oldState) else if (!newState && oldState)
{ {
// Make sure count down value is reset. // Make sure count down value is reset.
VolPlayerCountDown[client][volumeIndex] = -1.0; VolPlayerCountDown[client][volumeIndex] = -1.0;
// Update cache.
VolPlayerInVolume[client][volumeIndex] = false;
// Trigger event. // Trigger event.
VolOnPlayerLeave(client, volumeIndex); 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 min Minimum x, y and z values of the location.
* @param max Maximum 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 * @return True if the position is within min and max values. False
* otherwise. * 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. // Cache to avoid re-indexing arrays.
new Float:posX = pos[0]; new Float:posX = point[0];
new Float:posY = pos[1]; new Float:posY = point[1];
new Float:posZ = pos[2]; new Float:posZ = point[2];
// Check if within x boundaries. // Check if within x boundaries.
if ((posX >= min[0]) && (posX <= max[0])) 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. // Check if within x boundaries.
if ((posZ >= min[2]) && (posZ <= max[2])) if ((posZ >= min[2]) && (posZ <= max[2]))
{ {
// The player is within the location boundaries. // The point is within the location boundaries.
return true; return true;
} }
} }
} }
// The player is outside the location boundaries. // The point is outside the location boundaries.
return false; return false;
} }
@ -443,9 +520,22 @@ bool:IsPositionInLocation(Float:pos[3], Float:min[3], Float:max[3])
* @param volumeIndex The volume index. * @param volumeIndex The volume index.
* @return True if in use, false otherwise. * @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++) for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++)
{ {
// Check if it's free. // Check if it's free.
if (!VolIsInUse(volumeIndex)) if (!VolInUse(volumeIndex))
{ {
return volumeIndex; return volumeIndex;
} }
@ -488,6 +578,69 @@ VolGetFreeVolume()
return -1; 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 * Gets wether a client is within volumes or not. Result is stored in a boolean
* array. * array.
@ -506,25 +659,25 @@ VolGetPlayerStates(client, bool:buffer[], maxlen)
new Float:volMaxBuffer[3]; new Float:volMaxBuffer[3];
// Loop through all available volumes. // 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. // Chache volume to avoid re-indexing.
volumeBuffer = Volumes[volumeIndex]; volumeBuffer = Volumes[volumeIndex];
// Get min positions. // Get min positions.
volMinBuffer[0] = volumeBuffer[vol_x_min]; volMinBuffer[0] = volumeBuffer[Vol_xMin];
volMinBuffer[1] = volumeBuffer[vol_y_min]; volMinBuffer[1] = volumeBuffer[Vol_yMin];
volMinBuffer[2] = volumeBuffer[vol_z_min]; volMinBuffer[2] = volumeBuffer[Vol_zMin];
// Get max positions. // Get max positions.
volMaxBuffer[0] = volumeBuffer[vol_x_min]; volMaxBuffer[0] = volumeBuffer[Vol_xMax];
volMaxBuffer[1] = volumeBuffer[vol_y_min]; volMaxBuffer[1] = volumeBuffer[Vol_yMax];
volMaxBuffer[2] = volumeBuffer[vol_z_min]; volMaxBuffer[2] = volumeBuffer[Vol_zMax];
// Check the cached player location. // Check the cached player location.
if (IsPositionInLocation(VolPlayerLoc[client], volMinBuffer, volMaxBuffer)) if (IsPointInLocation(VolPlayerLoc[client], volMinBuffer, volMaxBuffer))
{ {
// Mark player as in volume. // Mark player as in volume.
buffer[volumeIndex] = true; buffer[volumeIndex] = true;
@ -541,6 +694,65 @@ VolGetPlayerStates(client, bool:buffer[], maxlen)
return volCount; 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. * 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. // Loop through all volumes.
for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) 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. // Get count down value.
countDown = VolPlayerCountDown[client][volumeIndex]; countDown = VolPlayerCountDown[client][volumeIndex];
@ -571,15 +790,21 @@ public Action:Event_VolTriggerTimer(Handle:timer)
// Substract by trigger interval. // Substract by trigger interval.
countDown -= VolTriggerInterval; countDown -= VolTriggerInterval;
// Check if zero or below. // Check if time is up.
if (countDown <= 0.0) if (countDown <= 0.0)
{ {
// Update cache.
VolPlayerInVolume[client][volumeIndex] = true;
// Trigger volume enter event. // Trigger volume enter event.
VolOnPlayerEnter(client, volumeIndex); VolOnPlayerEnter(client, volumeIndex);
// Reset count down value. // Reset count down value.
VolPlayerCountDown[client][volumeIndex] = -1.0; 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) if (isEnabled)
{ {
// Volumetric features is enabled. // Volumetric features is enabled.
VolEnabled = true; VolEnable();
// Start timers.
VolStartUpdateTimer();
} }
else else
{ {

View File

@ -44,21 +44,21 @@ stock bool:VolSetTeamString(volumeIndex, const String:team[])
} }
// Convert value. // Convert value.
if (strcmp(team, "all", false)) if (StrEqual(team, "all", false))
{ {
teamfilter = VolTeam_All; teamfilter = VolTeam_All;
} }
else if (strcmp(team, "humans", false)) else if (StrEqual(team, "humans", false))
{ {
teamfilter = VolTeam_Humans; teamfilter = VolTeam_Humans;
} }
else if (strcmp(team, "zombies", false)) else if (StrEqual(team, "zombies", false))
{ {
teamfilter = VolTeam_Zombies; teamfilter = VolTeam_Zombies;
} }
// Apply value. // Apply value.
Volumes[volumeIndex][vol_team_filter] = teamfilter; Volumes[volumeIndex][Vol_TeamFilter] = teamfilter;
return true; return true;
} }
@ -71,7 +71,38 @@ stock bool:VolSetTeamString(volumeIndex, const String:team[])
*/ */
stock VolSetTeam(volumeIndex, VolumeTeamFilters: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); triggerdelay = StringToFloat(delay);
// Apply value. // Apply value.
Volumes[volumeIndex][vol_trigger_delay] = triggerdelay; Volumes[volumeIndex][Vol_TriggerDelay] = triggerdelay;
return true; return true;
} }
@ -108,7 +139,7 @@ stock bool:VolSetDelayString(volumeIndex, const String:delay[])
*/ */
stock VolSetDelay(volumeIndex, Float: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. // 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; 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; 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; return true;
} }
@ -156,7 +187,38 @@ stock bool:VolSetEffectString(volumeIndex, const String:effect[])
*/ */
stock VolSetEffect(volumeIndex, VolumeEffects: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]); blue = StringToInt(colors[2]);
// Apply values. // Apply values.
Volumes[volumeIndex][vol_effect_color][0] = red; Volumes[volumeIndex][Vol_EffectColor][0] = red;
Volumes[volumeIndex][vol_effect_color][1] = green; Volumes[volumeIndex][Vol_EffectColor][1] = green;
Volumes[volumeIndex][vol_effect_color][2] = blue; Volumes[volumeIndex][Vol_EffectColor][2] = blue;
return true; return true;
} }
@ -227,31 +289,31 @@ stock bool:VolSetEnabledString(volumeIndex, const String:enabled[])
new bool:val = bool:StringToInt(enabled); new bool:val = bool:StringToInt(enabled);
// Check yes or no values first. // Check yes or no values first.
if (strcmp(enabled, "yes", false) == 0) if (StrEqual(enabled, "yes", false))
{ {
val = true; val = true;
} }
else if (strcmp(enabled, "no", false) == 0) else if (StrEqual(enabled, "no", false))
{ {
val = false; val = false;
} }
// Check if the new value is different from the current. // Check if the new value is different from the current.
if (Volumes[volumeIndex][vol_enabled] != val) if (Volumes[volumeIndex][Vol_Enabled] != val)
{ {
// Forward event. // Forward event.
if (val) if (val)
{ {
VolOnVolumeEnabled(volumeIndex); VolOnEnabled(volumeIndex);
} }
else else
{ {
VolOnVolumeDisabled(volumeIndex); VolOnDisabled(volumeIndex);
} }
} }
// Apply converted value. // Apply converted value.
Volumes[volumeIndex][vol_enabled] = val; Volumes[volumeIndex][Vol_Enabled] = val;
return true; return true;
} }
@ -264,7 +326,7 @@ stock bool:VolSetEnabledString(volumeIndex, const String:enabled[])
stock VolSetEnabled(volumeIndex, bool:enabled) stock VolSetEnabled(volumeIndex, bool:enabled)
{ {
// Check if the new value is different from the current. // Check if the new value is different from the current.
if (Volumes[volumeIndex][vol_enabled] != enabled) if (Volumes[volumeIndex][Vol_Enabled] != enabled)
{ {
// Forward event. // Forward event.
if (enabled) if (enabled)
@ -277,5 +339,5 @@ stock VolSetEnabled(volumeIndex, bool:enabled)
} }
} }
Volumes[volumeIndex][vol_enabled] = enabled; Volumes[volumeIndex][Vol_Enabled] = enabled;
} }