/* * ============================================================================ * * Zombie:Reloaded * * File: volanticamp.inc * Type: Module * Description: Anti-camp handler. * * Copyright (C) 2009-2013 Greyscale, Richard Helgeby * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ============================================================================ */ /** * Actions to do with players in anti-camp volumes. */ enum VolAnticampAction { 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. */ } /** * Warning types. */ enum VolAnticampeWarningType { 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; new Handle:timer; // 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]) { // Kill timer if it exist. timer = AnticampData[dataindex][Anticamp_Timer]; if (timer != INVALID_HANDLE) { KillTimer(timer); 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, volumeIndex, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Vol state", "Enabled anticamp volume %d.", volumeIndex); } else { LogEvent(_, LogType_Error, LOG_CORE_EVENTS, LogModule_Volfeatures, "Config Validation", "Warning: Invalid interval %.2f in anticamp volume %d.", interval, volumeIndex); } } } /** * Starts all existing anticamp timers. * * TODO: Reuse code! This is almost duplicate of VolAnticampEnable. */ stock VolAnticampEnableAll() { new Float:interval; new dataindex; // Loop through all volumes. for (new volumeindex = 0; volumeindex < ZR_VOLUMES_MAX; volumeindex++) { // Check if unused. if (!VolInUse(volumeindex)) { // Volume not in use, skip it. continue; } // Check if it's a anticamp volume. if (VolIsType(volumeindex, VolFeature_Anticamp)) { // Get data index. dataindex = Volumes[volumeindex][Vol_DataIndex]; // Kill timer if it exist. timer = AnticampData[dataindex][Anticamp_Timer]; if (timer != INVALID_HANDLE) { KillTimer(timer); 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, volumeindex, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "Vol state", "Enabled anticamp volume %d.", volumeIndex); } else { LogEvent(_, LogType_Error, LOG_CORE_EVENTS, LogModule_Volfeatures, "Config Validation", "Warning: Invalid interval %.2f in anticamp volume %d.", interval, volumeIndex); } } } } /** * 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); } } /** * Called when a player leave a anticamp volume. * * @param client The client index. * @param volumeIndex Index of volume the player left. */ VolAnticampOnPlayerLeave(client, volumeIndex) { new dataindex = Volumes[volumeIndex][Vol_DataIndex]; switch (AnticampData[dataindex][Anticamp_Action]) { case Anticamp_Drug: { // TODO: Un-drug player. } case Anticamp_Ignite: { ExtinguishEntity(client); } } } /** * Timer callback for anticamp volumes. Applies actions on players in volumes. */ public Action:Event_VolAnticampTrigger(Handle:timer, any:volumeIndex) { // Loop through all players. for (new client = 1; client <= MaxClients; client++) { // Validate client's connection state. if (!IsClientConnected(client) || !IsClientInGame(client) || !IsPlayerAlive(client)) { continue; } // Check if the volume is unused. if (!VolInUse(volumeIndex)) { continue; } // Check if the volume is disabled. if (!VolIsEnabled(volumeIndex)) { continue; } // Check if it's a anticamp volume. if (VolIsType(volumeIndex, VolFeature_Anticamp)) { // Check if player is in the volume. if (VolPlayerInVolume[client][volumeIndex]) { // 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]; // Set client language. SetGlobalTransTarget(client); // Get player name. decl String:name[64]; GetClientName(client, name, sizeof(name)); // 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 or below. new damage = RoundToNearest(amount); new health = GetClientHealth(client) - damage; if (health > 0) { SetEntityHealth(client, health); } else { // Health is zero or below. Kill player. ForcePlayerSuicide(client); // Log event. LogEvent(false, LogType_Normal, LOG_GAME_EVENTS, LogModule_Volfeatures, "Anti-camp", "%t", "Vol Slay", name, volumeIndex); } } case Anticamp_Slay: { // Instantly kill the player. ForcePlayerSuicide(client); // Log event. LogEvent(false, LogType_Normal, LOG_GAME_EVENTS, LogModule_Volfeatures, "Anti-camp", "%t", "Vol Slay", name, volumeIndex); } case Anticamp_Drug: { // TODO: Trigger sm_drug on client some how. } case Anticamp_Ignite: { // Validate amount. if (amount > 0.0) { // Extinguish player first. ExtinguishEntity(client); // Ignite player for "amount" seconds. IgniteEntity(client, amount); // Log event. LogEvent(false, LogType_Normal, LOG_GAME_EVENTS, LogModule_Volfeatures, "Anti-camp", "%t", "Vol Ignite", name, volumeIndex); } } } } /** * 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 default anticamp 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]) { // Mark as in use. AnticampData[dataindex][Anticamp_InUse] = true; // Return the new index. 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_Slay: { return shortName ? strcopy(buffer, maxlen, "slay") : strcopy(buffer, maxlen, "Kill 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, "slay", false)) { return Anticamp_Slay; } 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, "menu") : 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; }