/* * ============================================================================ * * Zombie:Reloaded * * File: volfeatures.inc * Type: Module * Description: Provides functions for managing volumetric features. * * 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 . * * ============================================================================ */ /** * Total volumes that can be created in a map. */ #define ZR_VOLUMES_MAX 64 /** * Represent a rectangular volume. */ enum VolumeAttributes { /* General */ bool:Vol_Enabled, /** Volume state. */ bool:Vol_InUse, /** Marks if the volume is used. */ /* Location */ Float:Vol_xMin, /** Minimum x position. */ Float:Vol_xMax, /** Maximum x position. */ Float:Vol_yMin, /** Minimum y position. */ Float:Vol_yMax, /** Maximum y position. */ Float:Vol_zMin, /** Minimum z position. */ Float:Vol_zMax, /** Maximum z position. */ /* Style */ VolumeEffects:Vol_Effect, /** Visual effect to apply on the volume. */ Vol_EffectColor[3], /** Render color of the effect. RGB colors. */ /* Data */ VolumeFeatureTypes:Vol_Type, /** The volumetric feature type. */ Vol_DataIndex, /** Index in remote feature array. */ /* Behaviour */ VolumeTeamFilters:Vol_TeamFilter, /** Team filtering. Trigger by certain teams, or all. */ Float:Vol_TriggerDelay /** Trigger delay. How many seconds players have to stay to trigger volume events. */ } /** * Available volumetric feature types. */ enum VolumeFeatureTypes { VolFeature_Invalid = 0, VolFeature_Anticamp, VolFeature_Knockback } /** * Effects that can be applied on a volume. (Currently no effects.) */ enum VolumeEffects { VolEffect_None = 0, VolEffect_Wireframe, VolEffect_Smoke } /** * Available team filter settings. */ enum VolumeTeamFilters { VolTeam_All = 0, VolTeam_Humans, VolTeam_Zombies } /** * Volumes. */ new Volumes[ZR_VOLUMES_MAX][VolumeAttributes]; /** * Total number of volumes. */ new VolumeCount; /** * List of player locations. Updated by a timer. */ new Float:VolPlayerLoc[MAXPLAYERS + 1][3]; /** * Cache that specifies if a player is in a volume or not. */ new bool:VolPlayerInVolume[MAXPLAYERS + 1][ZR_VOLUMES_MAX]; /** * Specifies whether the volumetric features module is enabled or not. Synced * with zr_vol CVAR. */ new bool:VolEnabled; /** * Counter for trigger delay. */ new Float:VolPlayerCountDown[MAXPLAYERS + 1][ZR_VOLUMES_MAX]; /** * The handle for a timer that updates player locations. This is the main timer * and any feature events can't be updated faster than this interval. * * Note: Some features may have its own timer for actions on players. */ new Handle:hVolUpdateTimer; /** * The handle for a timer that do count down on trigger delays. */ new Handle:hVolTriggerTimer; /** * Cached interval value for trigger timer. */ new Float:VolTriggerInterval; #include "zr/volfeatures/volevents" #include "zr/volfeatures/volgenericattributes" #include "zr/volfeatures/volcommands" // Sub features. #include "zr/volfeatures/volanticamp" /** * Initialize volumetric features. */ VolLoad() { // Cache CVAR value. VolEnabled = GetConVarBool(g_hCvarsList[CVAR_VOL]); // Initialize sub features. VolAnticampInit(); } /** * Function alias for fully stopping volumetric features. */ VolDisable() { VolEnabled = false; VolStopUpdateTimer(); VolDisableVolumes(); 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]) { // Mark as disabled. Volumes[volindex][Vol_Enabled] = false; // Trigger player left volume event if inside a volume. for (new client = 1; client <= MaxClients; client++) { // Validate client's connection state. if (!IsClientConnected(client) || !IsClientInGame(client)) { continue; } // Check if player is inside the volume. if (VolPlayerInVolume[client][volindex]) { // Mark as not in the volume and trigger event. VolPlayerInVolume[client][volindex] = false; VolOnPlayerLeave(client, volindex); } } // Trigger disabled event. 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]) { Volumes[volindex][Vol_Enabled] = true; VolOnEnabled(volindex); } } } /** * Starts the update timer. * * @return True if timer is started, false otherwise. */ bool:VolStartUpdateTimer() { // Check if volumetric features is enabled. if (!VolEnabled) { // Volumetric features disabled. return false; } // Stop timer if it exist. VolStopUpdateTimer(); // Get update interval. new Float:interval = GetConVarFloat(g_hCvarsList[CVAR_VOL_UPDATE_INTERVAL]); // Validate interval. if (interval > 0.0) { // Create a new timer. hVolUpdateTimer = CreateTimer(interval, Event_VolUpdateTimer, _, TIMER_REPEAT); // Also start the trigger delay timer. VolStartTriggerTimer(); // Volumetric features started. return true; } else { // 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; } } /** * Kills the update timer if it exists. */ VolStopUpdateTimer() { // Kill the timer if it's running. if (hVolUpdateTimer != INVALID_HANDLE) { KillTimer(hVolUpdateTimer); hVolUpdateTimer = INVALID_HANDLE; } // Also stop trigger delay timer. VolStopTriggerTimer(); // Reset all trigger delay counters. VolResetCountDown(); } /** * Starts the update timer if it exists. * * @return True if timer is started, false otherwise. */ bool:VolStartTriggerTimer() { // Make sure existing timer is killed. VolStopTriggerTimer(); // Get trigger interval and cache it. VolTriggerInterval = GetConVarFloat(g_hCvarsList[CVAR_VOL_TRIGGER_INTERVAL]); // Validate interval. if (VolTriggerInterval > 0.0) { // Start the timer. hVolTriggerTimer = CreateTimer(VolTriggerInterval, Event_VolTriggerTimer, _, TIMER_REPEAT); // Trigger timer started. return true; } else { // Trigger timer not running. Either disabled or invalid interval. LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Volfeatures, "Config Validation", "Warning: Console variable \"zr_vol_trigger_interval\" is set to zero or negative. Must be positive."); return false; } } /** * Kills the trigger delay timer if it exists. */ VolStopTriggerTimer() { // Kill the timer if it's running. if (hVolTriggerTimer != INVALID_HANDLE) { KillTimer(hVolTriggerTimer); hVolTriggerTimer = INVALID_HANDLE; } } /** * Resets volume trigger delay counters on one or more players. * * @param client Optional. Specifies a single player to reset. Default is * -1, all players. */ VolResetCountDown(client = -1) { // Check if a client is specified. if (client > -1) { // Reset volume counters. for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) { VolPlayerCountDown[client][volumeIndex] = -1.0; } } else { // Reset all volume counters. for (new clientIndex = 0; clientIndex < MAXPLAYERS + 1; clientIndex++) { for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) { VolPlayerCountDown[clientIndex][volumeIndex] = -1.0; } } } } /** * Updates all player locations. Used for initialization. * * Note: If a client is specified, it's NOT validated. This function assumes * the specified client is in game and alive. * * @param client Optional. Specify single client to be updated. Default is * -1. */ VolUpdatePlayerLocation(client = -1) { if (client > 0) { // Assume the client is valid and save location in array. GetClientAbsOrigin(client, VolPlayerLoc[client]); } else { for (client = 1; client <= MaxClients; client++) { // Validate client's connection state. if (!IsClientConnected(client) || !IsClientInGame(client) || !IsPlayerAlive(client)) { continue; } // Save location in array. GetClientAbsOrigin(client, VolPlayerLoc[client]); } } } /** * Updates player locations and trigger events for each player that enter or * leave a volume. */ VolUpdatePlayerChanges() { new bool:volumeStates[ZR_VOLUMES_MAX]; new bool:volumeNewStates[ZR_VOLUMES_MAX]; new bool:newState; new bool:oldState; new Float:trigger_delay; // Loop through all players. for (new client = 1; client <= MaxClients; client++) { // Validate client's connection state. if (!IsClientConnected(client) || !IsClientInGame(client) || !IsPlayerAlive(client)) { // Skip client. continue; } // Get the current volume states based on player location cache. VolGetPlayerStates(client, volumeStates, sizeof(volumeStates)); // Update player location cache. GetClientAbsOrigin(client, VolPlayerLoc[client]); // Get new volume states. VolGetPlayerStates(client, volumeNewStates, sizeof(volumeNewStates)); // Loop through each volume and compare states. for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) { // Check if the volume is disabled and unused. if (!VolInUse(volumeIndex) || !VolIsEnabled(volumeIndex)) { // Skip volume. continue; } // Check team filtering on the volume. if (!VolTeamFilterMatch(client, volumeIndex)) { // Team filter mismatch. continue; } newState = volumeNewStates[volumeIndex]; oldState = volumeStates[volumeIndex]; // Check for no change. if (newState == oldState) { // No change. Skip to next volume. continue; } // Check if client entered the volume. if (newState && !oldState) { // Get trigger delay value. trigger_delay = Volumes[volumeIndex][Vol_TriggerDelay]; // Check if the volume has a trigger delay. if (trigger_delay > 0.0) { // Set count down value. VolPlayerCountDown[client][volumeIndex] = trigger_delay; } else { // Update cache. VolPlayerInVolume[client][volumeIndex] = true; // No trigger delay, trigger event instantly. VolOnPlayerEnter(client, volumeIndex); } } // Check if client left the volume. else if (!newState && oldState) { // Make sure count down value is reset. VolPlayerCountDown[client][volumeIndex] = -1.0; // Only trigger left volume event if player already is in the // volume, so volumes with trigger delay won't get a left event // before the enter event. if (VolPlayerInVolume[client][volumeIndex]) { // Update cache. VolPlayerInVolume[client][volumeIndex] = false; // Trigger event. VolOnPlayerLeave(client, volumeIndex); } } } } } /** * Returns wether a point is within a certain location. * * @param point The point to check. * @param min Minimum x, y and z values of the location. * @param max Maximum x, y and z values of the location. * @return True if the position is within min and max values. False * otherwise. */ bool:IsPointInLocation(Float:point[3], Float:min[3], Float:max[3]) { // Cache to avoid re-indexing arrays. new Float:posX = point[0]; new Float:posY = point[1]; new Float:posZ = point[2]; // Check if within x boundaries. if ((posX >= min[0]) && (posX <= max[0])) { // Check if within y boundaries. if ((posY >= min[1]) && (posY <= max[1])) { // Check if within x boundaries. if ((posZ >= min[2]) && (posZ <= max[2])) { // The point is within the location boundaries. return true; } } } // The point is outside the location boundaries. return false; } /** * Returns wether a volume is marked as in use. * * Note: Does not validate index. * * @param volumeIndex The volume index. * @return True if in use, false otherwise. */ bool:VolInUse(volumeIndex) { 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]; } /** * Validates a volume index. * * @param volumeIndex The volume index. * @return True if valid, false otherwise. */ bool:VolIsValidIndex(volumeIndex) { if (volumeIndex >= 0 && volumeIndex < ZR_VOLUMES_MAX) { return true; } else { return false; } } /** * Gets the first free volume index. * * @return The first free volume index if successful, or -1 if there are * no free volumes. */ VolGetFreeVolume() { // Loop through all volumes. for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX; volumeIndex++) { // Check if it's free. if (!VolInUse(volumeIndex)) { return volumeIndex; } } // No free volumes found. 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; } /** * Checs if a volume is a certain type. * * @param volumeIndex Volume to check. * @param volType Type to match. * @return True if the types match, false otherwise. */ bool:VolIsType(volumeIndex, VolumeFeatureTypes:volType) { return Volumes[volumeIndex][Vol_Type] == volType; } /** * Gets wether a client is within volumes or not. Result is stored in a boolean * array. * * @param client The client index. * @param buffer Destination buffer. * @param maxlen Size of destination buffer. * @return Number of volumes the client is within. */ VolGetPlayerStates(client, bool:buffer[], maxlen) { new volumeBuffer[VolumeAttributes]; new volCount; new Float:volMinBuffer[3]; new Float:volMaxBuffer[3]; // Loop through all available volumes. for (new volumeIndex = 0; volumeIndex < ZR_VOLUMES_MAX && volumeIndex < maxlen; volumeIndex++) { if (VolInUse(volumeIndex)) { // Chache volume to avoid re-indexing. volumeBuffer = Volumes[volumeIndex]; // Get min positions. volMinBuffer[0] = volumeBuffer[Vol_xMin]; volMinBuffer[1] = volumeBuffer[Vol_yMin]; volMinBuffer[2] = volumeBuffer[Vol_zMin]; // Get max positions. volMaxBuffer[0] = volumeBuffer[Vol_xMax]; volMaxBuffer[1] = volumeBuffer[Vol_yMax]; volMaxBuffer[2] = volumeBuffer[Vol_zMax]; // Check the cached player location. if (IsPointInLocation(VolPlayerLoc[client], volMinBuffer, volMaxBuffer)) { // Mark player as in volume. buffer[volumeIndex] = true; volCount++; } else { // Do explicit reset. buffer[volumeIndex] = false; } } } 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. */ public Action:Event_VolUpdateTimer(Handle:timer) { VolUpdatePlayerChanges(); } /** * Callback for trigger delay timer. */ public Action:Event_VolTriggerTimer(Handle:timer) { new Float:countDown; // 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 volume is in use and enabled. if (!VolInUse(volumeIndex) || !VolIsEnabled(volumeIndex)) { // Not in use or enabled, skip volume. continue; } // Get count down value. countDown = VolPlayerCountDown[client][volumeIndex]; // Check if volume trigger delay is enabled. if (countDown > 0.0) { // Substract by trigger interval. countDown -= VolTriggerInterval; // Check if time is up. if (countDown <= 0.0) { // Update cache. VolPlayerInVolume[client][volumeIndex] = true; // Trigger volume enter event. VolOnPlayerEnter(client, volumeIndex); // Reset count down value. VolPlayerCountDown[client][volumeIndex] = -1.0; } // Update count down value and continue. VolPlayerCountDown[client][volumeIndex] = countDown; } } } } /** * Called when zr_vol CVAR is changed. */ public VolEnabledChanged(Handle:cvar, const String:oldvalue[], const String:newvalue[]) { new bool:isEnabled = bool:StringToInt(newvalue); if (isEnabled) { // Volumetric features is enabled. VolEnable(); } else { // Volumetric features is disabled. VolDisable(); } }