diff --git a/plugins/l4d2_hideandseek.smx b/plugins/l4d2_hideandseek.smx index 8626063..5858e9f 100644 Binary files a/plugins/l4d2_hideandseek.smx and b/plugins/l4d2_hideandseek.smx differ diff --git a/scripting/include/gamemodes/ents.inc b/scripting/include/gamemodes/ents.inc index 4179296..6bee0c9 100644 --- a/scripting/include/gamemodes/ents.inc +++ b/scripting/include/gamemodes/ents.inc @@ -11,11 +11,31 @@ #define ENT_ENV_NAME "cenv" #endif -void DeleteCustomEnts() { - EntFire(ENT_PROP_NAME, "Kill"); - EntFire(ENT_BLOCKER_NAME, "Kill"); - EntFire(ENT_PORTAL_NAME, "Kill"); - EntFire(ENT_ENV_NAME, "Kill"); +stock void DeleteCustomEnts() { + EntFireTarget(ENT_PROP_NAME, "Kill"); + EntFireTarget(ENT_BLOCKER_NAME, "Kill"); + EntFireTarget(ENT_PORTAL_NAME, "Kill"); + EntFireTarget(ENT_ENV_NAME, "Kill"); +} + +stock void EntFireTarget(const char[] name, const char[] input) { + static char targetname[64]; + static char cmd[32]; + #if defined DEBUG_LOG_MAPSTART + PrintToServer("EntFireTarget: %s \"%s\"", name, input); + #endif + int len = SplitString(input, " ", cmd, sizeof(cmd)); + if(len > -1) SetVariantString(input[len]); + + for(int i = MaxClients + 1; i <= 4096; i++) { + if(IsValidEntity(i) && (IsValidEdict(i) || EntIndexToEntRef(i) != -1)) { + GetEntPropString(i, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrEqual(targetname, name, false)) { + if(len > -1) AcceptEntityInput(i, cmd); + else AcceptEntityInput(i, input); + } + } + } } stock int CreateEnvBlockerScaled(const char[] entClass, const float pos[3], const float scale[3] = { 5.0, 5.0, 5.0 }, bool enabled = true) { @@ -120,6 +140,9 @@ void OnPortalTouch(const char[] output, int caller, int activator, float delay) } else { TeleportEntity(activator, entData[caller].portalOffsets, NULL_VECTOR, NULL_VECTOR); } + #if defined PORTAL_ENTER_SOUND + EmitSoundToClient(activator, PORTAL_ENTER_SOUND, activator); + #endif } stock int StartPropCreate(const char[] entClass, const char[] model, const float pos[3], const float ang[3]) { @@ -199,7 +222,65 @@ stock int CreateDynamicLight(float vOrigin[3], float vAngles[3], int color, floa return -1; } +// From l4d_anomaly +stock int CreateParticle(const char[] sParticle, const float vPos[3], const float vAng[3], int client = 0) +{ + int entity = CreateEntityByName("info_particle_system"); + + if( entity != -1 ) + { + DispatchKeyValue(entity, "effect_name", sParticle); + DispatchKeyValue(entity, "targetname", ENT_PORTAL_NAME) + DispatchSpawn(entity); + ActivateEntity(entity); + AcceptEntityInput(entity, "start"); + + if( client ) + { + // Attach to survivor + SetVariantString("!activator"); + AcceptEntityInput(entity, "SetParent", client); + } + + TeleportEntity(entity, vPos, vAng, NULL_VECTOR); + + // Refire + float refire = 0.2; + static char sTemp[64]; + Format(sTemp, sizeof(sTemp), "OnUser1 !self:Stop::%f:-1", refire - 0.05); + SetVariantString(sTemp); + AcceptEntityInput(entity, "AddOutput"); + Format(sTemp, sizeof(sTemp), "OnUser1 !self:FireUser2::%f:-1", refire); + SetVariantString(sTemp); + AcceptEntityInput(entity, "AddOutput"); + AcceptEntityInput(entity, "FireUser1"); + + SetVariantString("OnUser2 !self:Start::0:-1"); + AcceptEntityInput(entity, "AddOutput"); + SetVariantString("OnUser2 !self:FireUser1::0:-1"); + AcceptEntityInput(entity, "AddOutput"); + + return entity; + } + + return 0; +} + +// From l4d_anomaly +stock void PrecacheParticle(const char[] sEffectName) +{ + static int table = INVALID_STRING_TABLE; + if( table == INVALID_STRING_TABLE ) + { + table = FindStringTable("ParticleEffectNames"); + } + + if( FindStringIndex(table, sEffectName) == INVALID_STRING_INDEX ) + { + bool save = LockStringTables(false); + AddToStringTable(table, sEffectName); + LockStringTables(save); + } +} + -int GetHammerId(int entity) { - return HasEntProp(entity, Prop_Data, "m_iHammerID") ? GetEntProp(entity, Prop_Data, "m_iHammerID") : -1; -} \ No newline at end of file diff --git a/scripting/include/hideandseek/hsents.inc b/scripting/include/hideandseek/hsents.inc index 20ce382..962f13f 100644 --- a/scripting/include/hideandseek/hsents.inc +++ b/scripting/include/hideandseek/hsents.inc @@ -2,6 +2,7 @@ #define ENT_BLOCKER_NAME "hsblocker" #define ENT_PORTAL_NAME "hsportal" #define ENT_ENV_NAME "hsenv" +#define PORTAL_ENTER_SOUND "custom/xen_teleport.mp3" #include stock void CheatCommand(int client, const char[] command, const char[] argument1) { @@ -28,7 +29,7 @@ stock void EntFire(const char[] name, const char[] input) { for(int i = MaxClients + 1; i <= 4096; i++) { if(IsValidEntity(i) && (IsValidEdict(i) || EntIndexToEntRef(i) != -1)) { if(hammerId > 0) { - if(hammerId == GetHammerId(i)) { + if(hammerId == Entity_GetHammerId(i)) { if(setTeam) { SDKHook(i, SDKHook_TraceAttackPost, Hook_OnAttackPost); SetEntProp(i, Prop_Send, "m_iTeamNum", 0); @@ -87,6 +88,13 @@ void SetupEntities(bool blockers = true, bool props = true, bool portals = true) if(portals && CreatePortal(Portal_Teleport, config.model, config.origin, config.offset, config.scale) == -1) { PrintToServer("[H&S:WARN] Failed to spawn portal at (%.1f,%.1f, %.1f)", config.origin[0], config.origin[1], config.origin[2]); } + } else if(StrEqual(config.type, "_portal_xen")) { + if(portals) { + if(CreatePortal(Portal_Teleport, config.model, config.origin, config.offset, config.scale) == -1) { + PrintToServer("[H&S:WARN] Failed to spawn portal at (%.1f,%.1f, %.1f)", config.origin[0], config.origin[1], config.origin[2]); + } + CreateParticle(PARTICLE_ELMOS, config.origin, NULL_VECTOR); // Pulsating + } } else if(StrEqual(config.type, "_lantern")) { int parent = CreateProp("prop_dynamic", config.model, config.origin, config.rotation); if(parent == -1) { diff --git a/scripting/include/hideandseek/hsgame.inc b/scripting/include/hideandseek/hsgame.inc index ddb2ed4..0d93236 100644 --- a/scripting/include/hideandseek/hsgame.inc +++ b/scripting/include/hideandseek/hsgame.inc @@ -63,23 +63,28 @@ void SetSlasher(int client, bool ignoreBalance = false) { } GameState state = GetState(); char buf[128]; + ArrayList hiders = new ArrayList(); for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && i != client) { + if(IsClientConnected(i) && IsClientInGame(i) && i != client && GetClientTeam(i) == 2) { for(int s = 0; s < 6; s++) { int ent = GetPlayerWeaponSlot(i, s); if(ent > 0) AcceptEntityInput(ent, "Kill"); } + hiders.Push(i); if(state == State_Hunting) CheatCommand(i, "give", "pistol_magnum"); else CheatCommand(i, "give", "knife"); } } + int defibHolder = hiders.Get(GetRandomInt(0, hiders.Length - 1)); + CheatCommand(defibHolder, "give", "defibrillator"); Format(buf, sizeof(buf), "g_ModeScript.MutationState.CurrentSlasher = GetPlayerFromUserID(%d); g_ModeScript.GiveSeekerItem(GetPlayerFromUserID(%d))", GetClientUserId(client), GetClientUserId(client)); L4D2_ExecVScriptCode(buf); currentSeeker = client; - // CheatCommand(client, "give", "fireaxe"); + CheatCommand(client, "give", "fireaxe"); CheatCommand(client, "give", "adrenaline"); + delete hiders; } int GetTick() { diff --git a/scripting/l4d2_hideandseek.sp b/scripting/l4d2_hideandseek.sp index 74dd033..adda143 100644 --- a/scripting/l4d2_hideandseek.sp +++ b/scripting/l4d2_hideandseek.sp @@ -17,6 +17,9 @@ #include #endif +#define PARTICLE_ELMOS "st_elmos_fire_cp0" +#define PARTICLE_TES1 "electrical_arc_01" + public Plugin myinfo = { name = "L4D2 Hide & Seek", @@ -65,7 +68,8 @@ bool gameOver; int currentSeeker; int currentPlayers = 0; -Handle suspenseTimer, thirdPersonTimer; +Handle suspenseTimer, thirdPersonTimer, peekCamStopTimer, hiderCheckTimer; + int g_BeamSprite; int g_HaloSprite; @@ -76,6 +80,7 @@ bool ignoreSeekerBalance; ConVar cvar_peekCam; ConVar cvar_seekerBalance; ConVar cvar_abm_autohard; +ConVar cvar_seekerHelpers; BaseGame Game; #include @@ -105,6 +110,7 @@ public void OnPluginStart() { cvar_peekCam = CreateConVar("hs_peekcam", "3", "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); @@ -178,8 +184,13 @@ public void OnMapStart() { 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"); @@ -244,9 +255,12 @@ public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[ 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(1.0, Timer_CheckPlayers, _, TIMER_REPEAT); + thirdPersonTimer = CreateTimer(2.0, Timer_CheckPlayers, _, TIMER_REPEAT); if(!lateLoaded) { for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i)) { @@ -265,6 +279,7 @@ public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[ Cleanup(); delete suspenseTimer; delete thirdPersonTimer; + delete hiderCheckTimer; } } @@ -275,11 +290,25 @@ public Action Timer_StopPeekCam(Handle h) { PeekCam.SetViewing(i, false); } } + RequestFrame(Frame_StopPeekCam); + return Plugin_Stop; +} + +void Frame_StopPeekCam() { PeekCam.Destroy(); - return Plugin_Handled; + peekCamStopTimer = null; +} + +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")); @@ -287,22 +316,23 @@ public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast PeekCam.Target = attacker > 0 ? attacker : client; PeekCam.Perspective = Cam_FirstPerson; - int alive = 0; float pos[3], checkPos[3]; + // If player died on their own, set attacker to themselves for viewing purposes if(attacker <= 0) attacker = client; GetClientAbsOrigin(attacker, pos); PeekCam.SetViewing(attacker, true); + + int alive = 0; for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { - if(attacker > 0 && attacker != client) { - if(cvar_peekCam.IntValue & 2) { - GetClientAbsOrigin(i, checkPos); - if(GetVectorDistance(checkPos, pos) > DEATH_CAM_MIN_DIST) { - PeekCam.SetViewing(i, true); - } + if(IsClientConnected(i) && IsClientInGame(i)) { + // If death was by a seeker: + if(attacker == currentSeeker && cvar_peekCam.IntValue & 2) { + GetClientAbsOrigin(i, checkPos); + if(GetVectorDistance(checkPos, pos) > DEATH_CAM_MIN_DIST) { + PeekCam.SetViewing(i, true); } - alive++; } + if(IsPlayerAlive(i) && GetClientTeam(i) == 2) alive++; } } @@ -321,17 +351,20 @@ public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast } 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); } } - CreateTimer(2.0, Timer_StopPeekCam); + if(peekCamStopTimer != null && IsValidHandle(peekCamStopTimer)) delete peekCamStopTimer; + peekCamStopTimer = CreateTimer(2.0, Timer_StopPeekCam); } } @@ -347,6 +380,7 @@ public void OnClientPutInServer(int client) { } } + public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(client && client > 0 && currentSeeker != client) { @@ -417,10 +451,15 @@ public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) } EntFire("relay_intro_start", "Kill"); EntFire("outro", "Kill"); - SetupEntities(); + 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"); @@ -447,9 +486,13 @@ public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { public Action Timer_CheckPlayers(Handle h) { for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) && i != currentSeeker) + if(!IsClientConnected(i) || !IsClientInGame(i)) continue; + if(GetClientTeam(i) == 2 && IsPlayerAlive(i) && i != currentSeeker) QueryClientConVar(i, "cam_collision", QueryClientConVarCallback); + SetEntProp(i, Prop_Send, "m_audio.soundscapeIndex", -1); + } + ServerCommand("soundscape_flush"); return Plugin_Continue; } @@ -473,6 +516,36 @@ public void QueryClientConVarCallback(QueryCookie cookie, int client, ConVarQuer } } +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; public Action Timer_Music(Handle h) { static float seekerLoc[3]; @@ -504,7 +577,7 @@ public Action Timer_Music(Handle h) { 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 <= 1000000.0) { - EmitSoundToClient(i, SOUND_SUSPENSE_1, i, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_CHANGEPITCH, 0.2, 90, currentSeeker, seekerLoc, playerLoc, true); + 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]) { @@ -545,10 +618,21 @@ public Action Timer_RoundStart(Handle h) { 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); }