diff --git a/plugins/l4d2_editor.smx b/plugins/l4d2_editor.smx index 1c99968..95bfac7 100644 Binary files a/plugins/l4d2_editor.smx and b/plugins/l4d2_editor.smx differ diff --git a/plugins/l4d2_hideandseek.smx b/plugins/l4d2_hideandseek.smx index 7c3f18b..9a0de36 100644 Binary files a/plugins/l4d2_hideandseek.smx and b/plugins/l4d2_hideandseek.smx differ diff --git a/plugins/l4d2_randomizer.smx b/plugins/l4d2_randomizer.smx index 297d2a9..836ff1b 100644 Binary files a/plugins/l4d2_randomizer.smx and b/plugins/l4d2_randomizer.smx differ diff --git a/scripting/include/editor/editor.sp b/scripting/include/editor/editor.sp index e49f546..a1c3bf6 100644 --- a/scripting/include/editor/editor.sp +++ b/scripting/include/editor/editor.sp @@ -30,8 +30,9 @@ enum { Edit_None, Edit_Copy = 1, Edit_Preview = 2, - Edit_WallCreator = 4, - Edit_Manager = 8 + Edit_WallCreator = 4, + Edit_Manager = 8, // Edit started via manager + Edit_Grab = 16 // Edit started via +editor grab command } enum buildType { @@ -141,7 +142,6 @@ enum struct EditorData { this.angles[0] = RoundToNearestInterval(this.angles[0], this.snapAngle); this.angles[1] = RoundToNearestInterval(this.angles[1], this.snapAngle); this.angles[2] = RoundToNearestInterval(this.angles[2], this.snapAngle); - } TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR); } @@ -342,7 +342,7 @@ enum struct EditorData { // Is edit, do nothing, just reset PrintHintText(this.client, "Edit Complete"); this.Reset(); - entity = 0; + this.entity = 0; type = Complete_EditSuccess; } @@ -921,6 +921,35 @@ Action Command_Editor(int client, int args) { return Plugin_Handled; } + +Action Cmd_EditorGrab(int client, int args) { + int entity = GetLookingEntity(client, Filter_ValidHats); + if(entity > 0) { + int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); + if(parent > 0) { + entity = parent; + } + if(!CheckBlacklist(entity)) { + return Plugin_Handled; + } + Editor[client].ImportEntity(entity, view_as(Edit_Grab), MOVE_ORIGIN); + char classname[64]; + char targetname[32]; + GetEntityClassname(entity, classname, sizeof(classname)); + GetEntPropString(entity, Prop_Data, "m_target", targetname, sizeof(targetname)); + PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d (%s) [%s]\x01. End with \x05/edit done\x01", entity, classname, targetname); + } + return Plugin_Handled; +} + +Action Cmd_EditorRelease(int client, int args) { + if(Editor[client].IsActive() && Editor[client].flags & Edit_Grab) { + int entity; + Editor[client].Done(entity); + } + return Plugin_Handled; +} + int GetWallId(int client, const char[] arg) { int id; if(StringToIntEx(arg, id) > 0 && id > 0 && id <= createdWalls.Length) { diff --git a/scripting/include/editor/props/methods.sp b/scripting/include/editor/props/methods.sp index cef42f1..f322028 100644 --- a/scripting/include/editor/props/methods.sp +++ b/scripting/include/editor/props/methods.sp @@ -415,7 +415,7 @@ int GetSpawnedIndex(int client, int index) { } return -1; } -#define MAX_SEARCH_RESULTS 10 +#define MAX_SEARCH_RESULTS 30 ArrayList SearchItems(const char[] query) { // We have to put it into SearchData enum struct, then convert it back to ItemResult LoadCategories(); diff --git a/scripting/include/gamemodes/base.inc b/scripting/include/gamemodes/base.inc index c32813b..3a98ac2 100644 --- a/scripting/include/gamemodes/base.inc +++ b/scripting/include/gamemodes/base.inc @@ -1,295 +1,297 @@ -#include -char GAMEMODE_NAME[30] = "_UNINITIALIZED_GAMEMODE_"; -char GAMEMODE_PREFIX[32]; - - -// Meta -char gamemode[32]; -bool isEnabled, lateLoaded; - -char g_currentSet[16]; -char g_currentMap[64]; -char nextRoundMap[64]; - -// Legacy: -bool isNavBlockersEnabled = true, isPropsEnabled = true, isPortalsEnabled = true - -static int _debugFlags = BaseDebug_Server; - -enum { - BaseDebug_None, - BaseDebug_Server = 1, - BaseDebug_ConsoleAll = 2, - BaseDebug_ChatAll = 4 -} - -static bool ents_NavBlockers = true; -static bool ents_Props = true; -static bool ents_Portals = true; - -int g_iLaserIndex; - -// Gamemode state -bool isPendingPlay[MAXPLAYERS+1]; -static char buffer[256]; - -methodmap BaseGame { - property int DebugFlags { - public get() { return _debugFlags; } - public set(int flags) { - _debugFlags = flags; - } - } - - public void Init(const char[] name, const char[] prefix = "") { - strcopy(GAMEMODE_NAME, sizeof(GAMEMODE_NAME), name); - if(StrEqual(prefix, "")) - Format(GAMEMODE_PREFIX, sizeof(GAMEMODE_PREFIX), "[%s]", name); - else - Format(GAMEMODE_PREFIX, sizeof(GAMEMODE_PREFIX), "[%s]", prefix); - } - - public void Cleanup() { - ClearPortalData(); - } - - public void PrintToServer(const char[] format, any ...) { - VFormat(buffer, sizeof(buffer), format, 3); - PrintToServer("[%s] %s", GAMEMODE_NAME, buffer); - } - - public void Warn(const char[] format, any ...) { - VFormat(buffer, sizeof(buffer), format, 3); - PrintToServer("[%s::WARN] %s", GAMEMODE_NAME, buffer); - } - - public void Broadcast(const char[] format, any ...) { - VFormat(buffer, sizeof(buffer), format, 3); - PrintToChatAll("[%s] %s", GAMEMODE_NAME, buffer); - } - - public void Debug(const char[] format, any ...) { - if(_debugFlags == BaseDebug_None) return; - VFormat(buffer, sizeof(buffer), format, 3); - if(_debugFlags & BaseDebug_Server) - PrintToServer("[%s/debug] %s", GAMEMODE_NAME, buffer); - if(_debugFlags & BaseDebug_ChatAll) - PrintToChatAll("[%s/debug] %s", GAMEMODE_NAME, buffer); - if(_debugFlags & BaseDebug_ConsoleAll) - PrintToConsoleAll("[%s/debug] %s", GAMEMODE_NAME, buffer); - } - - public void DebugConsole(const char[] format, any ...) { - if(_debugFlags == BaseDebug_None) return; - VFormat(buffer, sizeof(buffer), format, 3); - PrintToConsoleAll("[%s/debug] %s", GAMEMODE_NAME, buffer); - } - - public void DebugCustom(const char[] format, int flags, any ...) { - if(flags == BaseDebug_None) return; - VFormat(buffer, sizeof(buffer), format, 3); - if(flags & BaseDebug_Server) - PrintToServer("[%s/debug] %s", GAMEMODE_NAME, buffer); - if(flags & BaseDebug_ChatAll) - PrintToChatAll("[%s/debug] %s", GAMEMODE_NAME, buffer); - if(flags & BaseDebug_ConsoleAll) - PrintToConsoleAll("[%s/debug] %s", GAMEMODE_NAME, buffer); - } - - property bool Enabled { - public get() { - return isEnabled - } - public set(bool value) { - isEnabled = value; - } - } - - property bool Blockers { - public get() { - return ents_NavBlockers; - } - public set(bool value) { - ents_NavBlockers = value; - } - } - - property bool Props { - public get() { - return ents_Props; - } - public set(bool value) { - ents_Props = value; - } - } - - property bool Portals { - public get() { - return ents_Portals; - } - public set(bool value) { - ents_Portals = value; - } - } - -} - - -// PeekCam specifics -PeekCamera PeekCam; -static int seekerCam = INVALID_ENT_REFERENCE; -static bool isViewingCam[MAXPLAYERS+1]; -static int seekerTarget; - -enum PeekPerspective { - Cam_Unknown, - Cam_FirstPerson, - Cam_ThirdPerson -} - -methodmap PeekCamera { - property int Target { - public get() { - return GetClientOfUserId(seekerTarget); - } - public set(int client) { - this.Create(); - seekerTarget = GetClientUserId(client); - - AcceptEntityInput(seekerCam, "ClearParent"); - AcceptEntityInput(seekerCam, "Disable"); - } - } - - property PeekPerspective Perspective { - public set(PeekPerspective perspective) { - this.SetPerspective(perspective); - } - } - - public void SetPerspective(PeekPerspective perspective) { - float pos[3], ang[3]; - int client = this.Target; - GetClientEyePosition(this.Target, pos); - GetClientEyeAngles(client, ang); - - if(perspective == Cam_FirstPerson) { - - TeleportEntity(seekerCam, pos, ang, NULL_VECTOR); - SetParent(seekerCam, client); - SetParentAttachment(seekerCam, "primary", false); - } else if(perspective == Cam_ThirdPerson) { - float endPos[3]; - TR_TraceRayFilter(pos, ang, CONTENTS_PLAYERCLIP | MASK_SOLID | MASK_VISIBLE, RayType_Infinite, Filter_IgnoreAll); - if(TR_DidHit()) { - TR_GetEndPosition(endPos); - } - endPos[2] += 50.0; - - ang[0] = 0.0; - float deltaA = endPos[0] - pos[0]; - float deltaB = endPos[1] - pos[1]; - float deltaC = endPos[2] - pos[2]; - ang[0] = RadToDeg(ArcTangent(deltaC / GetVectorDistance(endPos, pos, false) )); - ang[1] = RadToDeg(ArcTangent2(deltaA, deltaB)); - TeleportEntity(seekerCam, endPos, ang, NULL_VECTOR); - } - } - - public void SetViewing(int client, bool active) { - if(seekerCam != INVALID_ENT_REFERENCE) { - AcceptEntityInput(seekerCam, "Enable", client); // Need to always activate before deactivating to fix a semi-common bug - if(!active) { - AcceptEntityInput(seekerCam, "Disable", client); - } - } else { - PrintToServer("WARN: SetPeekCamActive(%d, %b) when seekerCam invalid", client, active); - } - isViewingCam[client] = active; - } - - public void Enable(int client = -1) { - AcceptEntityInput(seekerCam, "Enable", client); - if(client > 0) { - isViewingCam[client] = true; - } - } - public void Disable(int client = -1) { - AcceptEntityInput(seekerCam, "Disable", client); - if(client > 0) { - isViewingCam[client] = false; - } - } - - public bool IsViewing(int client) { - return isViewingCam[client]; - } - - public void Create() { - if(seekerCam == INVALID_ENT_REFERENCE || !IsValidEntity(seekerCam)) { - seekerCam = CreateEntityByName("point_viewcontrol_survivor"); - DispatchKeyValue(seekerCam, "targetname", "hscam"); - DispatchSpawn(seekerCam); - for(int i = 0; i <= MaxClients; i++) { - isViewingCam[i] = false; - } - } - } - - public bool Exists() { - return seekerCam != INVALID_ENT_REFERENCE && IsValidEntity(seekerCam) - } - - public void Destroy() { - if(seekerCam != INVALID_ENT_REFERENCE && IsValidEntity(seekerCam)) { - AcceptEntityInput(seekerCam, "Disable"); - AcceptEntityInput(seekerCam, "Kill"); - seekerCam = INVALID_ENT_REFERENCE - } - seekerTarget = 0; - } -} - -bool Filter_IgnoreAll(int entity, int mask) { - return false; -} - -enum struct EntityConfig { - float origin[3]; - float rotation[3]; - char type[32]; - char model[64]; - float scale[3]; - float offset[3]; -} - -enum struct MapConfig { - ArrayList entities; - ArrayList inputs; - float spawnpoint[3]; - bool hasSpawnpoint; - int mapTime; - bool canClimb; - bool pressButtons; -} - -MapConfig mapConfig; -ArrayList validMaps; -ArrayList validSets; - -stock void SetParent(int child, int parent) { - SetVariantString("!activator"); - AcceptEntityInput(child, "SetParent", parent); -} - -stock void SetParentAttachment(int child, const char[] attachment, bool withOffset = false) { - SetVariantString(attachment); - if(withOffset) - AcceptEntityInput(child, "SetParentAttachmentMaintainOffset"); - else - AcceptEntityInput(child, "SetParentAttachment"); -} - -stock void ClearParent(int child) { - AcceptEntityInput(child, "ClearParent"); -} +#include +char GAMEMODE_NAME[30] = "_UNINITIALIZED_GAMEMODE_"; +char GAMEMODE_PREFIX[32]; + + +// Meta +char gamemode[32]; +bool isEnabled, lateLoaded; + +char g_currentSet[16]; +char g_currentMap[64]; +char nextRoundMap[64]; + +// Legacy: +bool isNavBlockersEnabled = true, isPropsEnabled = true, isPortalsEnabled = true + +static int _debugFlags = BaseDebug_Server; + +enum { + BaseDebug_None, + BaseDebug_Server = 1, + BaseDebug_ConsoleAll = 2, + BaseDebug_ChatAll = 4 +} + +static bool ents_NavBlockers = true; +static bool ents_Props = true; +static bool ents_Portals = true; + +int g_iLaserIndex; + +// Gamemode state +bool isPendingPlay[MAXPLAYERS+1]; +static char buffer[256]; + +methodmap BaseGame { + property int DebugFlags { + public get() { return _debugFlags; } + public set(int flags) { + _debugFlags = flags; + } + } + + public void Init(const char[] name, const char[] prefix = "") { + strcopy(GAMEMODE_NAME, sizeof(GAMEMODE_NAME), name); + if(StrEqual(prefix, "")) + Format(GAMEMODE_PREFIX, sizeof(GAMEMODE_PREFIX), "[%s]", name); + else + Format(GAMEMODE_PREFIX, sizeof(GAMEMODE_PREFIX), "[%s]", prefix); + } + + public void Cleanup() { + ClearPortalData(); + } + + public void PrintToServer(const char[] format, any ...) { + VFormat(buffer, sizeof(buffer), format, 3); + PrintToServer("[%s] %s", GAMEMODE_NAME, buffer); + } + + public void Warn(const char[] format, any ...) { + VFormat(buffer, sizeof(buffer), format, 3); + PrintToServer("[%s::WARN] %s", GAMEMODE_NAME, buffer); + } + + public void Broadcast(const char[] format, any ...) { + VFormat(buffer, sizeof(buffer), format, 3); + PrintToChatAll("[%s] %s", GAMEMODE_NAME, buffer); + } + + public void Debug(const char[] format, any ...) { + if(_debugFlags == BaseDebug_None) return; + VFormat(buffer, sizeof(buffer), format, 3); + if(_debugFlags & BaseDebug_Server) + PrintToServer("[%s/debug] %s", GAMEMODE_NAME, buffer); + if(_debugFlags & BaseDebug_ChatAll) + PrintToChatAll("[%s/debug] %s", GAMEMODE_NAME, buffer); + if(_debugFlags & BaseDebug_ConsoleAll) + PrintToConsoleAll("[%s/debug] %s", GAMEMODE_NAME, buffer); + } + + public void DebugConsole(const char[] format, any ...) { + if(_debugFlags == BaseDebug_None) return; + VFormat(buffer, sizeof(buffer), format, 3); + PrintToConsoleAll("[%s/debug] %s", GAMEMODE_NAME, buffer); + } + + public void DebugCustom(const char[] format, int flags, any ...) { + if(flags == BaseDebug_None) return; + VFormat(buffer, sizeof(buffer), format, 3); + if(flags & BaseDebug_Server) + PrintToServer("[%s/debug] %s", GAMEMODE_NAME, buffer); + if(flags & BaseDebug_ChatAll) + PrintToChatAll("[%s/debug] %s", GAMEMODE_NAME, buffer); + if(flags & BaseDebug_ConsoleAll) + PrintToConsoleAll("[%s/debug] %s", GAMEMODE_NAME, buffer); + } + + property bool Enabled { + public get() { + return isEnabled + } + public set(bool value) { + isEnabled = value; + } + } + + property bool Blockers { + public get() { + return ents_NavBlockers; + } + public set(bool value) { + ents_NavBlockers = value; + } + } + + property bool Props { + public get() { + return ents_Props; + } + public set(bool value) { + ents_Props = value; + } + } + + property bool Portals { + public get() { + return ents_Portals; + } + public set(bool value) { + ents_Portals = value; + } + } + +} + + +// PeekCam specifics +PeekCamera PeekCam; +static int seekerCam = INVALID_ENT_REFERENCE; +static bool isViewingCam[MAXPLAYERS+1]; +static int seekerTarget; + +enum PeekPerspective { + Cam_Unknown, + Cam_FirstPerson, + Cam_ThirdPerson +} + +methodmap PeekCamera { + property int Target { + public get() { + return GetClientOfUserId(seekerTarget); + } + public set(int client) { + this.Create(); + seekerTarget = GetClientUserId(client); + + AcceptEntityInput(seekerCam, "ClearParent"); + AcceptEntityInput(seekerCam, "Disable"); + } + } + + property PeekPerspective Perspective { + public set(PeekPerspective perspective) { + this.SetPerspective(perspective); + } + } + + public void SetPerspective(PeekPerspective perspective) { + float pos[3], ang[3]; + int client = this.Target; + GetClientEyePosition(this.Target, pos); + GetClientEyeAngles(client, ang); + + if(perspective == Cam_FirstPerson) { + + TeleportEntity(seekerCam, pos, ang, NULL_VECTOR); + SetParent(seekerCam, client); + SetParentAttachment(seekerCam, "primary", false); + } else if(perspective == Cam_ThirdPerson) { + float endPos[3]; + TR_TraceRayFilter(pos, ang, CONTENTS_PLAYERCLIP | MASK_SOLID | MASK_VISIBLE, RayType_Infinite, Filter_IgnoreAll); + if(TR_DidHit()) { + TR_GetEndPosition(endPos); + } + endPos[2] += 50.0; + + ang[0] = 0.0; + float deltaA = endPos[0] - pos[0]; + float deltaB = endPos[1] - pos[1]; + float deltaC = endPos[2] - pos[2]; + ang[0] = RadToDeg(ArcTangent(deltaC / GetVectorDistance(endPos, pos, false) )); + ang[1] = RadToDeg(ArcTangent2(deltaA, deltaB)); + TeleportEntity(seekerCam, endPos, ang, NULL_VECTOR); + } + } + + public void SetViewing(int client, bool active) { + if(seekerCam != INVALID_ENT_REFERENCE) { + AcceptEntityInput(seekerCam, "Enable", client); // Need to always activate before deactivating to fix a semi-common bug + if(!active) { + AcceptEntityInput(seekerCam, "Disable", client); + } + } else { + PrintToServer("WARN: SetPeekCamActive(%d, %b) when seekerCam invalid", client, active); + } + isViewingCam[client] = active; + } + + public void Enable(int client = -1) { + if(seekerCam == -1) return; + AcceptEntityInput(seekerCam, "Enable", client); + if(client > 0) { + isViewingCam[client] = true; + } + } + public void Disable(int client = -1) { + if(seekerCam == -1) return; + AcceptEntityInput(seekerCam, "Disable", client); + if(client > 0) { + isViewingCam[client] = false; + } + } + + public bool IsViewing(int client) { + return isViewingCam[client]; + } + + public void Create() { + if(seekerCam == INVALID_ENT_REFERENCE || !IsValidEntity(seekerCam)) { + seekerCam = CreateEntityByName("point_viewcontrol_survivor"); + DispatchKeyValue(seekerCam, "targetname", "hscam"); + DispatchSpawn(seekerCam); + for(int i = 0; i <= MaxClients; i++) { + isViewingCam[i] = false; + } + } + } + + public bool Exists() { + return seekerCam != INVALID_ENT_REFERENCE && IsValidEntity(seekerCam) + } + + public void Destroy() { + if(seekerCam != INVALID_ENT_REFERENCE && IsValidEntity(seekerCam)) { + AcceptEntityInput(seekerCam, "Disable"); + AcceptEntityInput(seekerCam, "Kill"); + seekerCam = INVALID_ENT_REFERENCE + } + seekerTarget = 0; + } +} + +bool Filter_IgnoreAll(int entity, int mask) { + return false; +} + +enum struct EntityConfig { + float origin[3]; + float rotation[3]; + char type[32]; + char model[64]; + float scale[3]; + float offset[3]; +} + +enum struct MapConfig { + ArrayList entities; + ArrayList inputs; + float spawnpoint[3]; + bool hasSpawnpoint; + int mapTime; + bool canClimb; + bool pressButtons; +} + +MapConfig mapConfig; +ArrayList validMaps; +ArrayList validSets; + +stock void SetParent(int child, int parent) { + SetVariantString("!activator"); + AcceptEntityInput(child, "SetParent", parent); +} + +stock void SetParentAttachment(int child, const char[] attachment, bool withOffset = false) { + SetVariantString(attachment); + if(withOffset) + AcceptEntityInput(child, "SetParentAttachmentMaintainOffset"); + else + AcceptEntityInput(child, "SetParentAttachment"); +} + +stock void ClearParent(int child) { + AcceptEntityInput(child, "ClearParent"); +} diff --git a/scripting/l4d2_editor.sp b/scripting/l4d2_editor.sp index 54238b8..3ce8848 100644 --- a/scripting/l4d2_editor.sp +++ b/scripting/l4d2_editor.sp @@ -84,6 +84,8 @@ public void OnPluginStart() { RegAdminCmd("sm_edit", Command_Editor, ADMFLAG_CUSTOM2); RegAdminCmd("sm_wall", Command_Editor, ADMFLAG_CUSTOM2); RegAdminCmd("sm_prop", Command_Props, ADMFLAG_CUSTOM2); + RegAdminCmd("+editor", Cmd_EditorGrab, ADMFLAG_CHEATS, "Grab the entity in your crosshair."); + RegAdminCmd("-editor", Cmd_EditorRelease, ADMFLAG_CHEATS, "Releases the entity you grabbed."); if(SQL_CheckConfig(DATABASE_CONFIG_NAME)) { if(!ConnectDB()) { @@ -219,7 +221,6 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 switch(Editor[client].mode) { case MOVE_ORIGIN: { SetWeaponDelay(client, 0.5); - bool isRotate; int flags = GetEntityFlags(client); if(buttons & IN_RELOAD) { @@ -267,8 +268,11 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 if(g_inRotate[client]) { g_inRotate[client] = false; } - // Move position - float moveAmount = (buttons & IN_SPEED) ? 2.0 : 1.0; + float moveAmount = 1.0; + if(buttons & IN_SPEED) { + // If holding shift, move distance expoentionally faster the farther away + moveAmount += Pow(2.0, Editor[client].moveDistance / 200.0); + } if(buttons & IN_ATTACK) Editor[client].moveDistance += moveAmount; else if(buttons & IN_ATTACK2) Editor[client].moveDistance -= moveAmount; } @@ -278,8 +282,10 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 flags = flags & ~FL_FROZEN; SetEntityFlags(client, flags); } - if(Editor[client].stackerDirection == Stack_Off) + // Update the position of entity to cursor + if(Editor[client].stackerDirection == Stack_Off) { CalculateEditorPosition(client, Filter_IgnorePlayerAndWall); + } } case SCALE: { SetWeaponDelay(client, 0.5); @@ -563,6 +569,21 @@ stock float SnapTo(const float value, const float degree) { return float(RoundFloat(value / degree)) * degree; } +// Taken from GrabEnt +stock bool GetInitialRayPosition(int client, float endPos[3]) { + if (client > 0 && client <= MaxClients && IsClientInGame(client)) { + float clientEye[3], clientAngle[3]; + GetClientEyePosition(client, clientEye); + GetClientEyeAngles(client, clientAngle); + + TR_TraceRayFilter(clientEye, clientAngle, MASK_SOLID, RayType_Infinite, Filter_IgnorePlayer, client); + if (TR_DidHit(INVALID_HANDLE)) + TR_GetEndPosition(endPos); + return true; + } + return false; +} + stock bool CalculateEditorPosition(int client, TraceEntityFilter filter) { if (client > 0 && client <= MaxClients && IsClientInGame(client)) { float clientEye[3], clientAngle[3], direction[3]; diff --git a/scripting/l4d2_hideandseek.sp b/scripting/l4d2_hideandseek.sp index 8e5c4e2..0978ffc 100644 --- a/scripting/l4d2_hideandseek.sp +++ b/scripting/l4d2_hideandseek.sp @@ -1,746 +1,746 @@ -#pragma semicolon 1 -#pragma newdecls required - -//#define DEBUG - -#define PLUGIN_VERSION "1.0" -#define DEBUG_BLOCKERS 1 -// #define FORCE_ENABLED 1 -#define DEBUG_LOG_MAPSTART 1 - -#include -#include -#include -#include -#include -#if defined DEBUG_BLOCKERS -#include -#endif - -#define PARTICLE_ELMOS "st_elmos_fire_cp0" -#define PARTICLE_TES1 "electrical_arc_01" - -public Plugin myinfo = -{ - name = "L4D2 Hide & Seek", - author = "jackzmc", - description = "", - version = PLUGIN_VERSION, - url = "https://github.com/Jackzmc/sourcemod-plugins" -}; - -/*TODO: -2. Seeker helping -3. flare on hunted -*/ - -/* -script g_ModeScript.DeepPrintTable(g_ModeScript.MutationState) -{ - MapName = "c6m2_bedlam" - StartActive = true - CurrentSlasher = ([1] player) - MapTime = 480 - StateTick = 464 - LastGiveAdren = 10 - ModeName = "hideandseek" - Tick = 484 - CurrentStage = 3 - SlasherLastStumbled = 0 -} -*/ - -#define SOUND_SUSPENSE_1 "custom/suspense1.mp3" -#define SOUND_SUSPENSE_1_FAST "custom/suspense1fast.mp3" -#define SOUND_SHAKE "doors/gate_move1.wav" - -enum GameState { - State_Unknown = -1, - State_Startup, - State_Hiding, - State_Restarting, - State_Hunting -} -char GAME_STATE_DEBUG[4][] = { - "Startup", - "Hiding", - "Restarting", - "Hunting" -}; - -bool isNearbyPlaying[MAXPLAYERS+1]; -bool wasThirdPersonVomitted[MAXPLAYERS+1]; -bool gameOver; -int currentSeeker; -int currentPlayers = 0; - -Handle suspenseTimer, thirdPersonTimer, peekCamStopTimer, hiderCheckTimer; -Handle startupTimer; - - -int g_BeamSprite; -int g_HaloSprite; - -bool hasBeenSeeker[MAXPLAYERS+1]; -float lastPos[MAXPLAYERS+1][3]; -float distanceTraveled[MAXPLAYERS+1]; -bool ignoreSeekerBalance; - -ConVar cvar_peekCam; -ConVar cvar_seekerBalance; -ConVar cvar_abm_autohard; -ConVar cvar_seekerHelpers; - -BaseGame Game; -#include -#include - - -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { - lateLoaded = late; - return APLRes_Success; -} - -public void OnPluginStart() { - EngineVersion g_Game = GetEngineVersion(); - if(g_Game != Engine_Left4Dead2) { - SetFailState("This plugin is for L4D2 only."); - } - - Game.Init("Hide&Seek", "H&S"); - - validMaps = new ArrayList(ByteCountToCells(64)); - validSets = new ArrayList(ByteCountToCells(16)); - mapConfigs = new StringMap(); - - if(!ReloadMapDB()) { - SetFailState("Could not load entity config from data/hideandseek.cfg"); - } - - cvar_peekCam = CreateConVar("hs_peekcam", "0", "Controls the peek camera on events. Set bits\n0 = OFF, 1 = On Game End, 2 = Any death", FCVAR_NONE, true, 0.0, true, 3.0); - cvar_seekerBalance = CreateConVar("hs_seekerbalance", "1", "Enable or disable ensuring every player has played as seeker", FCVAR_NONE, true, 0.0, true, 1.0); - cvar_seekerHelpers = CreateConVar("hs_seekerhelper", "1", "Dev. 0 = off, 1 = Glow", FCVAR_NONE, true, 0.0); - - ConVar hGamemode = FindConVar("mp_gamemode"); - hGamemode.AddChangeHook(Event_GamemodeChange); - Event_GamemodeChange(hGamemode, gamemode, gamemode); - - cvar_abm_autohard = FindConVar("cvar_abm_autohard"); - - RegConsoleCmd("sm_joingame", Command_Join, "Joins or joins someone else"); - RegAdminCmd("sm_hs", Command_HideAndSeek, ADMFLAG_CHEATS, "The main command. see /hs help"); - -} - -public void OnPluginEnd() { - Cleanup(); -} - -public void OnClientConnected(int client) { - hasBeenSeeker[client] = false; - if(!IsFakeClient(client)) { - currentPlayers++; - if(isEnabled) { - // Once first player joins, wait for others to join - if(currentPlayers == 1) { - StartWaiting(); - } - } - } -} - -void StartWaiting(bool fireInstantly = false) { - if(GetState() == State_Startup && startupTimer != null) { - startupTimer = CreateTimer(10.0, Timer_KeepWaiting, _, TIMER_REPEAT); - if(fireInstantly) - TriggerTimer(startupTimer); - } -} - -public void OnClientPutInServer(int client) { - if(isEnabled && !IsFakeClient(client)) { - ChangeClientTeam(client, 1); - isPendingPlay[client] = true; - isNearbyPlaying[client] = false; - PrintToChatAll("%N will play next round", client); - float pos[3]; - // Try to get spawn point, if not, just let them spawn normally - if(GetSpawnPosition(pos)) - TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); - } -} - -public Action Timer_KeepWaiting(Handle h) { - SetTick(-40); - SetState(State_Startup); - PrintHintTextToAll("Waiting for players to join..."); - if(IsGameSoloOrPlayersLoading()) { - return Plugin_Continue; - } else { - startupTimer = null; - return Plugin_Handled; - } -} - -public void OnClientDisconnect(int client) { - distanceTraveled[client] = 0.0; - if(!IsFakeClient(client)) { - currentPlayers--; - } -} - -public void OnMapStart() { - if(!isEnabled) return; - - currentSeeker = 0; - - if(cvar_abm_autohard != null) { - cvar_abm_autohard.IntValue = 0; - } - char map[64]; - GetCurrentMap(map, sizeof(map)); - - if(!StrEqual(g_currentMap, map)) { - PrintToServer("[H&S] Map changed, loading fresh config"); - strcopy(g_currentMap, sizeof(g_currentMap), map); - if(!mapConfigs.GetArray(map, mapConfig, sizeof(MapConfig))) { - LoadConfigForMap(map); - } - strcopy(g_currentSet, sizeof(g_currentSet), "default"); - if(IsGameSoloOrPlayersLoading()) { - StartWaiting(true); - PrintToServer("[H&S] Player(s) are connecting, or solo. Waiting..."); - SetState(State_Startup); - } - } - - #if defined DEBUG_BLOCKERS - g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt"); - #endif - PrecacheSound(SOUND_SUSPENSE_1); - PrecacheSound(SOUND_SUSPENSE_1_FAST); - PrecacheSound(SOUND_SHAKE); - PrecacheSound("custom/xen_teleport.mp3"); - AddFileToDownloadsTable("sound/custom/suspense1.mp3"); - AddFileToDownloadsTable("sound/custom/suspense1fast.mp3"); - AddFileToDownloadsTable("sound/custom/xen_teleport.mp3"); - - PrecacheParticle(PARTICLE_ELMOS); - PrecacheParticle(PARTICLE_TES1); - - g_BeamSprite = PrecacheModel("sprites/laser.vmt"); - g_HaloSprite = PrecacheModel("sprites/halo01.vmt"); - PrecacheSound("buttons/button17.wav", true); - - if(lateLoaded) { - SetupEntities(); - int seeker = GetSlasher(); - if(seeker > -1) { - currentSeeker = seeker; - PrintToServer("[H&S] Late load, found seeker %N", currentSeeker); - } - if(IsGameSoloOrPlayersLoading()) { - Handle timer = CreateTimer(10.0, Timer_KeepWaiting, _, TIMER_REPEAT); - TriggerTimer(timer); - PrintToServer("[H&S] Late load, player(s) are connecting, or solo. Waiting..."); - SetState(State_Startup); - } - lateLoaded = false; - } -} -public void OnMapEnd() { - Game.Cleanup(); -} - -public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) { - if(isEnabled) { - finaleType = 0; - return Plugin_Changed; - } - return Plugin_Continue; -} - -public void OnSceneStageChanged(int scene, SceneStages stage) { - if(isEnabled && stage == SceneStage_Spawned) { - int activator = GetSceneInitiator(scene); - if(activator != GetActorFromScene(scene)) { - CancelScene(scene); - } - } -} - -bool wasEnabled; -public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[] newValue) { - cvar.GetString(gamemode, sizeof(gamemode)); - #if defined FORCE_ENABLED - isEnabled = true; - PrintToServer("[H&S] Force-enabled debug"); - #else - isEnabled = StrEqual(gamemode, "hideandseek", false); - #endif - if(isEnabled) { - PrintToChatAll("[H&S] Hide and seek gamemode activated, starting"); - HookEvent("round_end", Event_RoundEnd); - HookEvent("round_start", Event_RoundStart); - HookEvent("player_spawn", Event_PlayerSpawn); - HookEvent("item_pickup", Event_ItemPickup); - HookEvent("player_death", Event_PlayerDeath); - // OnMapStart(); // Don't use, need to track OnMapStart - if(lateLoaded) - CreateTimer(2.0, Timer_RoundStart); - else - CreateTimer(10.0, Timer_RoundStart); - if(suspenseTimer != null) - delete suspenseTimer; - suspenseTimer = CreateTimer(20.0, Timer_Music, _, TIMER_REPEAT); - if(hiderCheckTimer != null) - delete hiderCheckTimer; - hiderCheckTimer = CreateTimer(30.0, Timer_CheckHiders, _, TIMER_REPEAT); - if(thirdPersonTimer != null) - delete thirdPersonTimer; - thirdPersonTimer = CreateTimer(2.0, Timer_CheckPlayers, _, TIMER_REPEAT); - if(!lateLoaded) { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i)) { - ForcePlayerSuicide(i); - } - } - } - wasEnabled = true; - } else if(wasEnabled) { - wasEnabled = false; - UnhookEvent("round_end", Event_RoundEnd); - UnhookEvent("round_start", Event_RoundStart); - UnhookEvent("item_pickup", Event_ItemPickup); - UnhookEvent("player_death", Event_PlayerDeath); - UnhookEvent("player_spawn", Event_PlayerSpawn); - Cleanup(); - delete suspenseTimer; - delete thirdPersonTimer; - delete hiderCheckTimer; - } -} - -const float DEATH_CAM_MIN_DIST = 150.0; -public Action Timer_StopPeekCam(Handle h) { - for(int i = 1; i <= MaxClients; i++) { - PeekCam.Disable(i); - } - PeekCam.Disable(); - RequestFrame(Frame_StopPeekCam); - peekCamStopTimer = null; - return Plugin_Stop; -} - -void Frame_StopPeekCam() { - PeekCam.Destroy(); -} - -public void OnEntityCreated(int entity, const char[] classname) { - if(isEnabled) { - if(StrEqual(classname, "infected")) - AcceptEntityInput(entity, "Kill"); - } -} - -public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { - if(gameOver) return; - - int client = GetClientOfUserId(event.GetInt("userid")); - int attacker = GetClientOfUserId(event.GetInt("attacker")); - - if(!gameOver && client && GetClientTeam(client) == 2) { - PeekCam.Create(); - PeekCam.Target = attacker > 0 ? attacker : client; - PeekCam.Perspective = Cam_FirstPerson; - - float seekerPos[3], pos[3]; - // If player died on their own, set attacker to themselves for viewing purposes - if(attacker <= 0) attacker = client; - GetClientAbsOrigin(attacker, seekerPos); - PeekCam.Enable(attacker); - - int alive = 0; - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i)) { - // If death was by a seeker: - if(attacker == currentSeeker && cvar_peekCam.IntValue & 2) { - GetClientAbsOrigin(i, pos); - if(GetVectorDistance(seekerPos, pos) > DEATH_CAM_MIN_DIST) { - PeekCam.Enable(i); - } - } - if(IsPlayerAlive(i) && GetClientTeam(i) == 2) alive++; - } - } - - PrintToConsoleAll("attacker: %N | victim: %N | alive: %d", client, attacker, alive); - if(client == currentSeeker && alive == 1) { - // If seeker died - CPrintToChatAll("{green}Hiders win!"); - gameOver = true; - } else { - if(alive == 2) { - CPrintToChatAll("{green}One hider remains."); - } else if(alive <= 0) { - // Player died and not seeker, therefore seeker killed em - // If who died was not the seeker - if(attacker == 0) { - CPrintToChatAll("Seeker has left, game over."); - } - if(client == currentSeeker) { - CPrintToChatAll("{green}Hiders win! The seeker has perished."); - } else { - CPrintToChatAll("{green}The seeker %N won!", currentSeeker); - } - - if(cvar_peekCam.IntValue & 1) { - PeekCam.Target = client; - PeekCam.Perspective = Cam_ThirdPerson; - } - - gameOver = true; - return; - } else if(alive > 2 && client != currentSeeker) { - CPrintToChatAll("{green}%d hiders remain", alive - 1); - } - } - if(peekCamStopTimer != null && IsValidHandle(peekCamStopTimer)) delete peekCamStopTimer; - peekCamStopTimer = CreateTimer(2.0, Timer_StopPeekCam); - } -} - - - - -public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - if(client && client > 0 && currentSeeker != client) { - static char item[32]; - event.GetString("item", item, sizeof(item)); - if(StrEqual(item, "melee")) { - int entity = GetPlayerWeaponSlot(client, 1); - GetEntPropString(entity, Prop_Data, "m_strMapSetScriptName", item, sizeof(item)); - if(StrEqual(item, "fireaxe")) { - gameOver = false; - currentSeeker = GetSlasher(); - if(currentSeeker <= 0) { - PrintToServer("[H&S] ERROR: GetSlasher() returned invalid seeker"); - currentSeeker = client; - } else if(currentSeeker != client) { - PrintToConsoleAll("[H&S] Seeker does not equal axe-receiver. Possible seeker: %N", client); - } - PeekCam.Target = currentSeeker; - if(!ignoreSeekerBalance && cvar_seekerBalance.BoolValue) { - if(hasBeenSeeker[currentSeeker]) { - ArrayList notPlayedSeekers = new ArrayList(1); - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && !hasBeenSeeker[i]) { - notPlayedSeekers.Push(i); - } - } - if(notPlayedSeekers.Length > 0) { - int newSlasher = notPlayedSeekers.Get(GetURandomInt() % notPlayedSeekers.Length); - PrintToServer("[H&S] Switching seeker to a new random seeker: %N", newSlasher); - SetSlasher(newSlasher); - delete notPlayedSeekers; - return; - } else { - PrintToServer("[H&S] All players have played as seeker, resetting"); - PrintToChatAll("[H&S] Everyone has played as a seeker once"); - for(int i = 1; i <= MaxClients; i++) { - hasBeenSeeker[i] = false; - } - hasBeenSeeker[currentSeeker] = true; - } - delete notPlayedSeekers; - } else { - hasBeenSeeker[currentSeeker] = true; - } - } - ignoreSeekerBalance = false; - CPrintToChatAll("{green}%N{yellow} is the seeker", currentSeeker); - } - } - } -} - -public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - if(client > 0 && mapConfig.hasSpawnpoint) { - TeleportEntity(client, mapConfig.spawnpoint, NULL_VECTOR, NULL_VECTOR); - } - PeekCam.Disable(client); -} - -public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { - if(mapConfig.hasSpawnpoint) { - PrintToServer("[H&S] Using provided spawnpoint: %.1f %.1f %.1f", mapConfig.spawnpoint[0], mapConfig.spawnpoint[1], mapConfig.spawnpoint[2]); - } - for(int i = 1; i <= MaxClients; i++) { - if(IsClientInGame(i)) { - if(isPendingPlay[i]) { - JoinGame(i); - } - if(mapConfig.hasSpawnpoint) - TeleportEntity(i, mapConfig.spawnpoint, NULL_VECTOR, NULL_VECTOR); - } - } - EntFire("relay_intro_start", "Kill"); - EntFire("outro", "Kill"); - CreateTimer(1.0, Timer_SetupEntities); - CreateTimer(15.0, Timer_RoundStart); -} - -public Action Timer_SetupEntities(Handle h) { - SetupEntities(); - return Plugin_Handled; -} - -public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { - if(nextRoundMap[0] != '\0') { - ForceChangeLevel(nextRoundMap, "SetMapSelect"); - nextRoundMap[0] = '\0'; - return; - } - currentSeeker = 0; - for(int i = 1; i <= MaxClients; i++) { - isNearbyPlaying[i] = false; - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 1) { - distanceTraveled[i] = 0.0; - PeekCam.Disable(i); - if(isPendingPlay[i]) { - JoinGame(i); - } - } - } - PeekCam.Destroy(); -} - -public Action Timer_CheckPlayers(Handle h) { - for(int i = 1; i <= MaxClients; i++) { - if(!IsClientConnected(i) || !IsClientInGame(i)) continue; - - if(GetClientTeam(i) == 2 && IsPlayerAlive(i) && i != currentSeeker) { - float pos[3]; - GetClientAbsOrigin(i, pos); - distanceTraveled[i] = FloatAbs(Get2DVectorDistance(pos, lastPos[i])); - - lastPos[i] = pos; - QueryClientConVar(i, "cam_collision", QueryClientConVarCallback); - } - SetEntProp(i, Prop_Send, "m_audio.soundscapeIndex", -1); - - } - // Rare Crashes: - // ServerCommand("soundscape_flush"); - return Plugin_Continue; -} - -public void QueryClientConVarCallback(QueryCookie cookie, int client, ConVarQueryResult result, const char[] sCvarName, const char[] bCvarValue) { - int value = 0; - if (result == ConVarQuery_Okay && StringToIntEx(bCvarValue, value) > 0 && value == 0) { - wasThirdPersonVomitted[client] = true; - PrintHintText(client, "Third person is disabled in this mode"); - // L4D_OnITExpired(client); - // L4D_CTerrorPlayer_OnVomitedUpon(client, client); - float random = GetRandomFloat(); - if(random < 0.3) - PerformScene(client, "Playerareaclear"); - else if(random <= 0.6) - PerformScene(client, "PlayerLaugh"); - else - PerformScene(client, "PlayerDeath"); - } else if(wasThirdPersonVomitted[client]) { - wasThirdPersonVomitted[client] = false; - L4D_OnITExpired(client); - } -} - -int PLAYER_GLOW_COLOR[3] = { 0, 255, 0 }; -public Action Timer_CheckHiders(Handle h) { - static float seekerLoc[3]; - static float playerLoc[3]; - if(cvar_seekerHelpers.IntValue != 0) return Plugin_Continue; - if(currentSeeker > 0) { - GetClientAbsOrigin(currentSeeker, seekerLoc); - } - for(int i = 1; i <= MaxClients; i++) { - if(i != currentSeeker && IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) { - GetClientAbsOrigin(i, playerLoc); - float dist = GetVectorDistance(seekerLoc, playerLoc, true); - if(dist > 290000.0) { - L4D2_SetPlayerSurvivorGlowState(i, true); - L4D2_SetEntityGlow(i, L4D2Glow_Constant, 0, 10, PLAYER_GLOW_COLOR, true); - CreateTimer(2.0, Timer_RemoveGlow, GetClientUserId(i)); - } - } - } - return Plugin_Continue; -} - -public Action Timer_RemoveGlow(Handle h, int userid) { - int client = GetClientOfUserId(userid); - if(client) { - L4D2_SetPlayerSurvivorGlowState(client, false); - L4D2_RemoveEntityGlow(client); - } - return Plugin_Handled; -} -GameState prevState; - -// #define HEARTBEAT_NEAR_DIST 1_000_000 -// #define HEARTBEAT_CLOSE_DIST 250_000 - -#define HEARTBEAT_NEAR_DIST 1000 -#define HEARTBEAT_CLOSE_DIST 300 - -public Action Timer_Music(Handle h) { - static float seekerLoc[3]; - static float playerLoc[3]; - if(currentSeeker > 0) { - GetClientAbsOrigin(currentSeeker, seekerLoc); - GameState state = GetState(); - if(state == State_Hunting) { - if(prevState == State_Hiding) { - ShowBeacon(currentSeeker); - PeekCam.Target = currentSeeker; - PeekCam.Create(); - PeekCam.Enable(); - CreateTimer(2.2, Timer_StopPeekCam); - } - EmitSoundToClient(currentSeeker, SOUND_SUSPENSE_1, currentSeeker, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_CHANGEPITCH, 0.2, 90, currentSeeker, seekerLoc, seekerLoc, true); - } - prevState = state; - } - int playerCount; - - for(int i = 1; i <= MaxClients; i++) { - if(IsClientInGame(i) && i != currentSeeker) { - playerCount++; - // GetClientAbsOrigin(i, playerLoc); - // float dist = GetVectorDistance(seekerLoc, playerLoc, true); - if(currentSeeker == 0) continue; - float dist = GetFlowDistance(currentSeeker, i); - if(dist <= HEARTBEAT_CLOSE_DIST) { - StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1); - EmitSoundToClient(i, SOUND_SUSPENSE_1_FAST, i, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_CHANGEPITCH, 1.0, 100, currentSeeker, seekerLoc, playerLoc, true); - isNearbyPlaying[i] = true; - } else if(dist <= HEARTBEAT_CLOSE_DIST) { - EmitSoundToClient(i, SOUND_SUSPENSE_1, i, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_CHANGEPITCH, 0.4, 90, currentSeeker, seekerLoc, playerLoc, true); - isNearbyPlaying[i] = true; - StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1_FAST); - } else if(isNearbyPlaying[i]) { - isNearbyPlaying[i] = false; - StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1_FAST); - StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1); - } - } - } - return Plugin_Continue; -} - - -float GetFlowDistance(int survivorA, int survivorB) { - return FloatAbs(L4D2Direct_GetFlowDistance(survivorA) - L4D2Direct_GetFlowDistance(survivorB)); -} - -public Action Timer_RoundStart(Handle h) { - PrintToServer("[H&S] Running round entity tweaks"); - CreateTimer(0.1, Timer_CheckWeapons); - CreateTimer(10.0, Timer_CheckWeapons); - int entity = INVALID_ENT_REFERENCE; - if(mapConfig.pressButtons) { - while ((entity = FindEntityByClassname(entity, "func_button")) != INVALID_ENT_REFERENCE) { - AcceptEntityInput(entity, "Press"); - } - } - entity = INVALID_ENT_REFERENCE; - while ((entity = FindEntityByClassname(entity, "infected")) != INVALID_ENT_REFERENCE) { - AcceptEntityInput(entity, "Kill"); - } - entity = INVALID_ENT_REFERENCE; - while ((entity = FindEntityByClassname(entity, "prop_door_rotating")) != INVALID_ENT_REFERENCE) { - AcceptEntityInput(entity, "Unlock"); - if(GetURandomFloat() > 0.5) - AcceptEntityInput(entity, "Toggle"); - } - if(mapConfig.canClimb) { - while ((entity = FindEntityByClassname(entity, "func_simpleladder")) != INVALID_ENT_REFERENCE) { - SDKHook(entity, SDKHook_TraceAttackPost, Hook_OnAttackPost); - SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); - } - } - entity = INVALID_ENT_REFERENCE; - while ((entity = FindEntityByClassname(entity, "env_soundscape")) != INVALID_ENT_REFERENCE) { - AcceptEntityInput(entity, "Disable"); - AcceptEntityInput(entity, "Kill"); - } - entity = INVALID_ENT_REFERENCE; - while ((entity = FindEntityByClassname(entity, "env_soundscape_triggerable")) != INVALID_ENT_REFERENCE) { - AcceptEntityInput(entity, "Disable"); - AcceptEntityInput(entity, "Kill"); - } - entity = INVALID_ENT_REFERENCE; - while ((entity = FindEntityByClassname(entity, "env_soundscape_proxy")) != INVALID_ENT_REFERENCE) { - AcceptEntityInput(entity, "Disable"); - AcceptEntityInput(entity, "Kill"); - } - if(mapConfig.mapTime > 0) { - SetMapTime(mapConfig.mapTime); - } - - PrintToServer("[H&S] Map time is %d seconds", GetMapTime()); - return Plugin_Continue; -} - -static float lastShakeTime; -public void Hook_OnAttackPost(int entity, int attacker, int inflictor, float damage, int damagetype, int ammotype, int hitbox, int hitgroup) { - if(attacker == currentSeeker && attacker > 0 && GetGameTime() - lastShakeTime > 2.0) { - lastShakeTime = GetGameTime(); - float min[3], max[3], origin[3], top[3]; - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); - GetEntPropVector(entity, Prop_Send, "m_vecMins", min); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", max); - PrintHintText(attacker, "Shaking ladder"); - top = origin; - top[2] = max[2]; - origin[2] = min[2]; - TR_EnumerateEntitiesHull(origin, top, min, max, PARTITION_SOLID_EDICTS, Ray_Enumerator, attacker); - EmitAmbientSound(SOUND_SHAKE, top, SOUND_FROM_WORLD, SNDLEVEL_SCREAMING, SND_NOFLAGS, SNDVOL_NORMAL, SNDPITCH_NORMAL); - } -} -bool Ray_Enumerator(int entity, int attacker) { - if(entity > 0 && entity <= MaxClients && entity != attacker) { - L4D_StaggerPlayer(entity, attacker, NULL_VECTOR); - } - return true; -} -public Action Timer_CheckWeapons(Handle h) { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { - // Check if has no melee: - if(GetPlayerWeaponSlot(i, 1) == -1) { - CheatCommand(i, "give", "knife"); - } - int item = GetPlayerWeaponSlot(i, 0); - if(item != -1) AcceptEntityInput(item, "Kill"); - } - } - if(currentSeeker) { - int wpn = GetPlayerWeaponSlot(currentSeeker, 1); - if(wpn > 0) { - char buffer[32]; - GetEntityClassname(wpn, buffer, sizeof(buffer)); - if(StrEqual(buffer, "melee")) { - GetEntPropString(wpn, Prop_Data, "m_strMapSetScriptName", buffer, sizeof(buffer)); - if(StrEqual(buffer, "fireaxe")) { - return Plugin_Continue; - } - } - AcceptEntityInput(wpn, "Kill"); - } - CheatCommand(currentSeeker, "give", "fireaxe"); - } - return Plugin_Continue; +#pragma semicolon 1 +#pragma newdecls required + +//#define DEBUG + +#define PLUGIN_VERSION "1.0" +#define DEBUG_BLOCKERS 1 +// #define FORCE_ENABLED 1 +#define DEBUG_LOG_MAPSTART 1 + +#include +#include +#include +#include +#include +#if defined DEBUG_BLOCKERS +#include +#endif + +#define PARTICLE_ELMOS "st_elmos_fire_cp0" +#define PARTICLE_TES1 "electrical_arc_01" + +public Plugin myinfo = +{ + name = "L4D2 Hide & Seek", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +/*TODO: +2. Seeker helping +3. flare on hunted +*/ + +/* +script g_ModeScript.DeepPrintTable(g_ModeScript.MutationState) +{ + MapName = "c6m2_bedlam" + StartActive = true + CurrentSlasher = ([1] player) + MapTime = 480 + StateTick = 464 + LastGiveAdren = 10 + ModeName = "hideandseek" + Tick = 484 + CurrentStage = 3 + SlasherLastStumbled = 0 +} +*/ + +#define SOUND_SUSPENSE_1 "custom/suspense1.mp3" +#define SOUND_SUSPENSE_1_FAST "custom/suspense1fast.mp3" +#define SOUND_SHAKE "doors/gate_move1.wav" + +enum GameState { + State_Unknown = -1, + State_Startup, + State_Hiding, + State_Restarting, + State_Hunting +} +char GAME_STATE_DEBUG[4][] = { + "Startup", + "Hiding", + "Restarting", + "Hunting" +}; + +bool isNearbyPlaying[MAXPLAYERS+1]; +bool wasThirdPersonVomitted[MAXPLAYERS+1]; +bool gameOver; +int currentSeeker; +int currentPlayers = 0; + +Handle suspenseTimer, thirdPersonTimer, peekCamStopTimer, hiderCheckTimer; +Handle startupTimer; + + +int g_BeamSprite; +int g_HaloSprite; + +bool hasBeenSeeker[MAXPLAYERS+1]; +float lastPos[MAXPLAYERS+1][3]; +float distanceTraveled[MAXPLAYERS+1]; +bool ignoreSeekerBalance; + +ConVar cvar_peekCam; +ConVar cvar_seekerBalance; +ConVar cvar_abm_autohard; +ConVar cvar_seekerHelpers; + +BaseGame Game; +#include +#include + + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + lateLoaded = late; + return APLRes_Success; +} + +public void OnPluginStart() { + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D2 only."); + } + + Game.Init("Hide&Seek", "H&S"); + + validMaps = new ArrayList(ByteCountToCells(64)); + validSets = new ArrayList(ByteCountToCells(16)); + mapConfigs = new StringMap(); + + if(!ReloadMapDB()) { + SetFailState("Could not load entity config from data/hideandseek.cfg"); + } + + cvar_peekCam = CreateConVar("hs_peekcam", "0", "Controls the peek camera on events. Set bits\n0 = OFF, 1 = On Game End, 2 = Any death", FCVAR_NONE, true, 0.0, true, 3.0); + cvar_seekerBalance = CreateConVar("hs_seekerbalance", "1", "Enable or disable ensuring every player has played as seeker", FCVAR_NONE, true, 0.0, true, 1.0); + cvar_seekerHelpers = CreateConVar("hs_seekerhelper", "1", "Dev. 0 = off, 1 = Glow", FCVAR_NONE, true, 0.0); + + ConVar hGamemode = FindConVar("mp_gamemode"); + hGamemode.AddChangeHook(Event_GamemodeChange); + Event_GamemodeChange(hGamemode, gamemode, gamemode); + + cvar_abm_autohard = FindConVar("cvar_abm_autohard"); + + RegConsoleCmd("sm_joingame", Command_Join, "Joins or joins someone else"); + RegAdminCmd("sm_hs", Command_HideAndSeek, ADMFLAG_CHEATS, "The main command. see /hs help"); + +} + +public void OnPluginEnd() { + Cleanup(); +} + +public void OnClientConnected(int client) { + hasBeenSeeker[client] = false; + if(!IsFakeClient(client)) { + currentPlayers++; + if(isEnabled) { + // Once first player joins, wait for others to join + if(currentPlayers == 1) { + StartWaiting(); + } + } + } +} + +void StartWaiting(bool fireInstantly = false) { + if(GetState() == State_Startup && startupTimer != null) { + startupTimer = CreateTimer(10.0, Timer_KeepWaiting, _, TIMER_REPEAT); + if(fireInstantly) + TriggerTimer(startupTimer); + } +} + +public void OnClientPutInServer(int client) { + if(isEnabled && !IsFakeClient(client)) { + ChangeClientTeam(client, 1); + isPendingPlay[client] = true; + isNearbyPlaying[client] = false; + PrintToChatAll("%N will play next round", client); + float pos[3]; + // Try to get spawn point, if not, just let them spawn normally + if(GetSpawnPosition(pos)) + TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); + } +} + +public Action Timer_KeepWaiting(Handle h) { + SetTick(-40); + SetState(State_Startup); + PrintHintTextToAll("Waiting for players to join..."); + if(IsGameSoloOrPlayersLoading()) { + return Plugin_Continue; + } else { + startupTimer = null; + return Plugin_Handled; + } +} + +public void OnClientDisconnect(int client) { + distanceTraveled[client] = 0.0; + if(!IsFakeClient(client)) { + currentPlayers--; + } +} + +public void OnMapStart() { + if(!isEnabled) return; + + currentSeeker = 0; + + if(cvar_abm_autohard != null) { + cvar_abm_autohard.IntValue = 0; + } + char map[64]; + GetCurrentMap(map, sizeof(map)); + + if(!StrEqual(g_currentMap, map)) { + PrintToServer("[H&S] Map changed, loading fresh config"); + strcopy(g_currentMap, sizeof(g_currentMap), map); + if(!mapConfigs.GetArray(map, mapConfig, sizeof(MapConfig))) { + LoadConfigForMap(map); + } + strcopy(g_currentSet, sizeof(g_currentSet), "default"); + if(IsGameSoloOrPlayersLoading()) { + StartWaiting(true); + PrintToServer("[H&S] Player(s) are connecting, or solo. Waiting..."); + SetState(State_Startup); + } + } + + #if defined DEBUG_BLOCKERS + g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt"); + #endif + PrecacheSound(SOUND_SUSPENSE_1); + PrecacheSound(SOUND_SUSPENSE_1_FAST); + PrecacheSound(SOUND_SHAKE); + PrecacheSound("custom/xen_teleport.mp3"); + AddFileToDownloadsTable("sound/custom/suspense1.mp3"); + AddFileToDownloadsTable("sound/custom/suspense1fast.mp3"); + AddFileToDownloadsTable("sound/custom/xen_teleport.mp3"); + + PrecacheParticle(PARTICLE_ELMOS); + PrecacheParticle(PARTICLE_TES1); + + g_BeamSprite = PrecacheModel("sprites/laser.vmt"); + g_HaloSprite = PrecacheModel("sprites/halo01.vmt"); + PrecacheSound("buttons/button17.wav", true); + + if(lateLoaded) { + SetupEntities(); + int seeker = GetSlasher(); + if(seeker > -1) { + currentSeeker = seeker; + PrintToServer("[H&S] Late load, found seeker %N", currentSeeker); + } + if(IsGameSoloOrPlayersLoading()) { + Handle timer = CreateTimer(10.0, Timer_KeepWaiting, _, TIMER_REPEAT); + TriggerTimer(timer); + PrintToServer("[H&S] Late load, player(s) are connecting, or solo. Waiting..."); + SetState(State_Startup); + } + lateLoaded = false; + } +} +public void OnMapEnd() { + Game.Cleanup(); +} + +public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) { + if(isEnabled) { + finaleType = 0; + return Plugin_Changed; + } + return Plugin_Continue; +} + +public void OnSceneStageChanged(int scene, SceneStages stage) { + if(isEnabled && stage == SceneStage_Spawned) { + int activator = GetSceneInitiator(scene); + if(activator != GetActorFromScene(scene)) { + CancelScene(scene); + } + } +} + +bool wasEnabled; +public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[] newValue) { + cvar.GetString(gamemode, sizeof(gamemode)); + #if defined FORCE_ENABLED + isEnabled = true; + PrintToServer("[H&S] Force-enabled debug"); + #else + isEnabled = StrEqual(gamemode, "hideandseek", false); + #endif + if(isEnabled) { + PrintToChatAll("[H&S] Hide and seek gamemode activated, starting"); + HookEvent("round_end", Event_RoundEnd); + HookEvent("round_start", Event_RoundStart); + HookEvent("player_spawn", Event_PlayerSpawn); + HookEvent("item_pickup", Event_ItemPickup); + HookEvent("player_death", Event_PlayerDeath); + // OnMapStart(); // Don't use, need to track OnMapStart + if(lateLoaded) + CreateTimer(2.0, Timer_RoundStart); + else + CreateTimer(10.0, Timer_RoundStart); + if(suspenseTimer != null) + delete suspenseTimer; + suspenseTimer = CreateTimer(20.0, Timer_Music, _, TIMER_REPEAT); + if(hiderCheckTimer != null) + delete hiderCheckTimer; + hiderCheckTimer = CreateTimer(30.0, Timer_CheckHiders, _, TIMER_REPEAT); + if(thirdPersonTimer != null) + delete thirdPersonTimer; + thirdPersonTimer = CreateTimer(2.0, Timer_CheckPlayers, _, TIMER_REPEAT); + if(!lateLoaded) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + ForcePlayerSuicide(i); + } + } + } + wasEnabled = true; + } else if(wasEnabled) { + wasEnabled = false; + UnhookEvent("round_end", Event_RoundEnd); + UnhookEvent("round_start", Event_RoundStart); + UnhookEvent("item_pickup", Event_ItemPickup); + UnhookEvent("player_death", Event_PlayerDeath); + UnhookEvent("player_spawn", Event_PlayerSpawn); + Cleanup(); + delete suspenseTimer; + delete thirdPersonTimer; + delete hiderCheckTimer; + } +} + +const float DEATH_CAM_MIN_DIST = 150.0; +public Action Timer_StopPeekCam(Handle h) { + for(int i = 1; i <= MaxClients; i++) { + PeekCam.Disable(i); + } + PeekCam.Disable(); + RequestFrame(Frame_StopPeekCam); + peekCamStopTimer = null; + return Plugin_Stop; +} + +void Frame_StopPeekCam() { + PeekCam.Destroy(); +} + +public void OnEntityCreated(int entity, const char[] classname) { + if(isEnabled) { + if(StrEqual(classname, "infected")) + AcceptEntityInput(entity, "Kill"); + } +} + +public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { + if(gameOver) return; + + int client = GetClientOfUserId(event.GetInt("userid")); + int attacker = GetClientOfUserId(event.GetInt("attacker")); + + if(!gameOver && client && GetClientTeam(client) == 2) { + PeekCam.Create(); + PeekCam.Target = attacker > 0 ? attacker : client; + PeekCam.Perspective = Cam_FirstPerson; + + float seekerPos[3], pos[3]; + // If player died on their own, set attacker to themselves for viewing purposes + if(attacker <= 0) attacker = client; + GetClientAbsOrigin(attacker, seekerPos); + PeekCam.Enable(attacker); + + int alive = 0; + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + // If death was by a seeker: + if(attacker == currentSeeker && cvar_peekCam.IntValue & 2) { + GetClientAbsOrigin(i, pos); + if(GetVectorDistance(seekerPos, pos) > DEATH_CAM_MIN_DIST) { + PeekCam.Enable(i); + } + } + if(IsPlayerAlive(i) && GetClientTeam(i) == 2) alive++; + } + } + + PrintToConsoleAll("attacker: %N | victim: %N | alive: %d", client, attacker, alive); + if(client == currentSeeker && alive == 1) { + // If seeker died + CPrintToChatAll("{green}Hiders win!"); + gameOver = true; + } else { + if(alive == 2) { + CPrintToChatAll("{green}One hider remains."); + } else if(alive <= 0) { + // Player died and not seeker, therefore seeker killed em + // If who died was not the seeker + if(attacker == 0) { + CPrintToChatAll("Seeker has left, game over."); + } + if(client == currentSeeker) { + CPrintToChatAll("{green}Hiders win! The seeker has perished."); + } else { + CPrintToChatAll("{green}The seeker %N won!", currentSeeker); + } + + if(cvar_peekCam.IntValue & 1) { + PeekCam.Target = client; + PeekCam.Perspective = Cam_ThirdPerson; + } + + gameOver = true; + return; + } else if(alive > 2 && client != currentSeeker) { + CPrintToChatAll("{green}%d hiders remain", alive - 1); + } + } + if(peekCamStopTimer != null && IsValidHandle(peekCamStopTimer)) delete peekCamStopTimer; + peekCamStopTimer = CreateTimer(2.0, Timer_StopPeekCam); + } +} + + + + +public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client && client > 0 && currentSeeker != client) { + static char item[32]; + event.GetString("item", item, sizeof(item)); + if(StrEqual(item, "melee")) { + int entity = GetPlayerWeaponSlot(client, 1); + GetEntPropString(entity, Prop_Data, "m_strMapSetScriptName", item, sizeof(item)); + if(StrEqual(item, "fireaxe")) { + gameOver = false; + currentSeeker = GetSlasher(); + if(currentSeeker <= 0) { + PrintToServer("[H&S] ERROR: GetSlasher() returned invalid seeker"); + currentSeeker = client; + } else if(currentSeeker != client) { + PrintToConsoleAll("[H&S] Seeker does not equal axe-receiver. Possible seeker: %N", client); + } + PeekCam.Target = currentSeeker; + if(!ignoreSeekerBalance && cvar_seekerBalance.BoolValue) { + if(hasBeenSeeker[currentSeeker]) { + ArrayList notPlayedSeekers = new ArrayList(1); + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && !hasBeenSeeker[i]) { + notPlayedSeekers.Push(i); + } + } + if(notPlayedSeekers.Length > 0) { + int newSlasher = notPlayedSeekers.Get(GetURandomInt() % notPlayedSeekers.Length); + PrintToServer("[H&S] Switching seeker to a new random seeker: %N", newSlasher); + SetSlasher(newSlasher); + delete notPlayedSeekers; + return; + } else { + PrintToServer("[H&S] All players have played as seeker, resetting"); + PrintToChatAll("[H&S] Everyone has played as a seeker once"); + for(int i = 1; i <= MaxClients; i++) { + hasBeenSeeker[i] = false; + } + hasBeenSeeker[currentSeeker] = true; + } + delete notPlayedSeekers; + } else { + hasBeenSeeker[currentSeeker] = true; + } + } + ignoreSeekerBalance = false; + CPrintToChatAll("{green}%N{yellow} is the seeker", currentSeeker); + } + } + } +} + +public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0 && mapConfig.hasSpawnpoint) { + TeleportEntity(client, mapConfig.spawnpoint, NULL_VECTOR, NULL_VECTOR); + } + PeekCam.Disable(client); +} + +public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { + if(mapConfig.hasSpawnpoint) { + PrintToServer("[H&S] Using provided spawnpoint: %.1f %.1f %.1f", mapConfig.spawnpoint[0], mapConfig.spawnpoint[1], mapConfig.spawnpoint[2]); + } + for(int i = 1; i <= MaxClients; i++) { + if(IsClientInGame(i)) { + if(isPendingPlay[i]) { + JoinGame(i); + } + if(mapConfig.hasSpawnpoint) + TeleportEntity(i, mapConfig.spawnpoint, NULL_VECTOR, NULL_VECTOR); + } + } + EntFire("relay_intro_start", "Kill"); + EntFire("outro", "Kill"); + CreateTimer(1.0, Timer_SetupEntities); + CreateTimer(15.0, Timer_RoundStart); +} + +public Action Timer_SetupEntities(Handle h) { + SetupEntities(); + return Plugin_Handled; +} + +public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { + if(nextRoundMap[0] != '\0') { + ForceChangeLevel(nextRoundMap, "SetMapSelect"); + nextRoundMap[0] = '\0'; + return; + } + currentSeeker = 0; + for(int i = 1; i <= MaxClients; i++) { + isNearbyPlaying[i] = false; + if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 1) { + distanceTraveled[i] = 0.0; + PeekCam.Disable(i); + if(isPendingPlay[i]) { + JoinGame(i); + } + } + } + PeekCam.Destroy(); +} + +public Action Timer_CheckPlayers(Handle h) { + for(int i = 1; i <= MaxClients; i++) { + if(!IsClientConnected(i) || !IsClientInGame(i)) continue; + + if(GetClientTeam(i) == 2 && IsPlayerAlive(i) && i != currentSeeker) { + float pos[3]; + GetClientAbsOrigin(i, pos); + distanceTraveled[i] = FloatAbs(Get2DVectorDistance(pos, lastPos[i])); + + lastPos[i] = pos; + QueryClientConVar(i, "cam_collision", QueryClientConVarCallback); + } + SetEntProp(i, Prop_Send, "m_audio.soundscapeIndex", -1); + + } + // Rare Crashes: + // ServerCommand("soundscape_flush"); + return Plugin_Continue; +} + +public void QueryClientConVarCallback(QueryCookie cookie, int client, ConVarQueryResult result, const char[] sCvarName, const char[] bCvarValue) { + int value = 0; + if (result == ConVarQuery_Okay && StringToIntEx(bCvarValue, value) > 0 && value == 0) { + wasThirdPersonVomitted[client] = true; + PrintHintText(client, "Third person is disabled in this mode"); + // L4D_OnITExpired(client); + // L4D_CTerrorPlayer_OnVomitedUpon(client, client); + float random = GetRandomFloat(); + if(random < 0.3) + PerformScene(client, "Playerareaclear"); + else if(random <= 0.6) + PerformScene(client, "PlayerLaugh"); + else + PerformScene(client, "PlayerDeath"); + } else if(wasThirdPersonVomitted[client]) { + wasThirdPersonVomitted[client] = false; + L4D_OnITExpired(client); + } +} + +int PLAYER_GLOW_COLOR[3] = { 0, 255, 0 }; +public Action Timer_CheckHiders(Handle h) { + static float seekerLoc[3]; + static float playerLoc[3]; + if(cvar_seekerHelpers.IntValue != 0) return Plugin_Continue; + if(currentSeeker > 0) { + GetClientAbsOrigin(currentSeeker, seekerLoc); + } + for(int i = 1; i <= MaxClients; i++) { + if(i != currentSeeker && IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) { + GetClientAbsOrigin(i, playerLoc); + float dist = GetVectorDistance(seekerLoc, playerLoc, true); + if(dist > 290000.0) { + L4D2_SetPlayerSurvivorGlowState(i, true); + L4D2_SetEntityGlow(i, L4D2Glow_Constant, 0, 10, PLAYER_GLOW_COLOR, true); + CreateTimer(2.0, Timer_RemoveGlow, GetClientUserId(i)); + } + } + } + return Plugin_Continue; +} + +public Action Timer_RemoveGlow(Handle h, int userid) { + int client = GetClientOfUserId(userid); + if(client) { + L4D2_SetPlayerSurvivorGlowState(client, false); + L4D2_RemoveEntityGlow(client); + } + return Plugin_Handled; +} +GameState prevState; + +// #define HEARTBEAT_NEAR_DIST 1_000_000 +// #define HEARTBEAT_CLOSE_DIST 250_000 + +#define HEARTBEAT_NEAR_DIST 1000 +#define HEARTBEAT_CLOSE_DIST 300 + +public Action Timer_Music(Handle h) { + static float seekerLoc[3]; + static float playerLoc[3]; + if(currentSeeker > 0) { + GetClientAbsOrigin(currentSeeker, seekerLoc); + GameState state = GetState(); + if(state == State_Hunting) { + if(prevState == State_Hiding) { + ShowBeacon(currentSeeker); + PeekCam.Target = currentSeeker; + PeekCam.Create(); + PeekCam.Enable(); + CreateTimer(2.2, Timer_StopPeekCam); + } + EmitSoundToClient(currentSeeker, SOUND_SUSPENSE_1, currentSeeker, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_CHANGEPITCH, 0.2, 90, currentSeeker, seekerLoc, seekerLoc, true); + } + prevState = state; + } + int playerCount; + + for(int i = 1; i <= MaxClients; i++) { + if(IsClientInGame(i) && i != currentSeeker) { + playerCount++; + // GetClientAbsOrigin(i, playerLoc); + // float dist = GetVectorDistance(seekerLoc, playerLoc, true); + if(currentSeeker == 0) continue; + float dist = GetFlowDistance(currentSeeker, i); + if(dist <= HEARTBEAT_CLOSE_DIST) { + StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1); + EmitSoundToClient(i, SOUND_SUSPENSE_1_FAST, i, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_CHANGEPITCH, 1.0, 100, currentSeeker, seekerLoc, playerLoc, true); + isNearbyPlaying[i] = true; + } else if(dist <= HEARTBEAT_CLOSE_DIST) { + EmitSoundToClient(i, SOUND_SUSPENSE_1, i, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_CHANGEPITCH, 0.4, 90, currentSeeker, seekerLoc, playerLoc, true); + isNearbyPlaying[i] = true; + StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1_FAST); + } else if(isNearbyPlaying[i]) { + isNearbyPlaying[i] = false; + StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1_FAST); + StopSound(i, SNDCHAN_AUTO, SOUND_SUSPENSE_1); + } + } + } + return Plugin_Continue; +} + + +float GetFlowDistance(int survivorA, int survivorB) { + return FloatAbs(L4D2Direct_GetFlowDistance(survivorA) - L4D2Direct_GetFlowDistance(survivorB)); +} + +public Action Timer_RoundStart(Handle h) { + PrintToServer("[H&S] Running round entity tweaks"); + CreateTimer(0.1, Timer_CheckWeapons); + CreateTimer(10.0, Timer_CheckWeapons); + int entity = INVALID_ENT_REFERENCE; + if(mapConfig.pressButtons) { + while ((entity = FindEntityByClassname(entity, "func_button")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Press"); + } + } + entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "infected")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Kill"); + } + entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "prop_door_rotating")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Unlock"); + if(GetURandomFloat() > 0.5) + AcceptEntityInput(entity, "Toggle"); + } + if(mapConfig.canClimb) { + while ((entity = FindEntityByClassname(entity, "func_simpleladder")) != INVALID_ENT_REFERENCE) { + SDKHook(entity, SDKHook_TraceAttackPost, Hook_OnAttackPost); + SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); + } + } + entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "env_soundscape")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Disable"); + AcceptEntityInput(entity, "Kill"); + } + entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "env_soundscape_triggerable")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Disable"); + AcceptEntityInput(entity, "Kill"); + } + entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "env_soundscape_proxy")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Disable"); + AcceptEntityInput(entity, "Kill"); + } + if(mapConfig.mapTime > 0) { + SetMapTime(mapConfig.mapTime); + } + + PrintToServer("[H&S] Map time is %d seconds", GetMapTime()); + return Plugin_Continue; +} + +static float lastShakeTime; +public void Hook_OnAttackPost(int entity, int attacker, int inflictor, float damage, int damagetype, int ammotype, int hitbox, int hitgroup) { + if(attacker == currentSeeker && attacker > 0 && GetGameTime() - lastShakeTime > 2.0) { + lastShakeTime = GetGameTime(); + float min[3], max[3], origin[3], top[3]; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(entity, Prop_Send, "m_vecMins", min); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", max); + PrintHintText(attacker, "Shaking ladder"); + top = origin; + top[2] = max[2]; + origin[2] = min[2]; + TR_EnumerateEntitiesHull(origin, top, min, max, PARTITION_SOLID_EDICTS, Ray_Enumerator, attacker); + EmitAmbientSound(SOUND_SHAKE, top, SOUND_FROM_WORLD, SNDLEVEL_SCREAMING, SND_NOFLAGS, SNDVOL_NORMAL, SNDPITCH_NORMAL); + } +} +bool Ray_Enumerator(int entity, int attacker) { + if(entity > 0 && entity <= MaxClients && entity != attacker) { + L4D_StaggerPlayer(entity, attacker, NULL_VECTOR); + } + return true; +} +public Action Timer_CheckWeapons(Handle h) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { + // Check if has no melee: + if(GetPlayerWeaponSlot(i, 1) == -1) { + CheatCommand(i, "give", "knife"); + } + int item = GetPlayerWeaponSlot(i, 0); + if(item != -1) AcceptEntityInput(item, "Kill"); + } + } + if(currentSeeker) { + int wpn = GetPlayerWeaponSlot(currentSeeker, 1); + if(wpn > 0) { + char buffer[32]; + GetEntityClassname(wpn, buffer, sizeof(buffer)); + if(StrEqual(buffer, "melee")) { + GetEntPropString(wpn, Prop_Data, "m_strMapSetScriptName", buffer, sizeof(buffer)); + if(StrEqual(buffer, "fireaxe")) { + return Plugin_Continue; + } + } + AcceptEntityInput(wpn, "Kill"); + } + CheatCommand(currentSeeker, "give", "fireaxe"); + } + return Plugin_Continue; } \ No newline at end of file diff --git a/scripting/l4d2_randomizer.sp b/scripting/l4d2_randomizer.sp index 165bc22..636c60a 100644 --- a/scripting/l4d2_randomizer.sp +++ b/scripting/l4d2_randomizer.sp @@ -1113,7 +1113,7 @@ void spawnVariant(SceneVariantData choice) { void spawnEntity(VariantEntityData entity) { if(StrEqual(entity.type, "env_fire")) { Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.type, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - CreateFire(entity.origin, 20.0, 100.0, 0.0); + CreateFire(entity.origin, 20.0, 100.0, 1.0); } else if(StrEqual(entity.type, "env_physics_blocker") || StrEqual(entity.type, "env_player_blocker")) { CreateEnvBlockerScaled(entity.type, entity.origin, entity.scale); } else if(StrEqual(entity.type, "infodecal")) {