From ae429b37025f8e458bd4a90181f7574381c92bd8 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Fri, 31 Mar 2023 20:30:12 -0700 Subject: [PATCH 01/86] Init --- addons/sourcemod/scripting/include/sf2.inc | 6 +- .../scripting/include/sf2/defines.inc | 54 +- .../include/sf2/profiles/profiles.inc | 36 +- addons/sourcemod/scripting/sf2.sp | 419 ++- addons/sourcemod/scripting/sf2/anticamping.sp | 229 ++ addons/sourcemod/scripting/sf2/client.sp | 2253 ++--------------- .../sourcemod/scripting/sf2/client/blink.sp | 272 ++ .../scripting/sf2/client/breathing.sp | 139 + .../scripting/sf2/client/deathcam.sp | 561 ++++ .../scripting/sf2/client/flashlight.sp | 882 +++++++ .../scripting/sf2/client/ghostmode.sp | 483 ++++ .../sourcemod/scripting/sf2/client/hints.sp | 84 + .../sourcemod/scripting/sf2/client/music.sp | 1777 +++++++++++++ addons/sourcemod/scripting/sf2/client/peek.sp | 27 + .../sourcemod/scripting/sf2/client/proxy.sp | 2052 +++++++++++++++ .../sourcemod/scripting/sf2/client/sprint.sp | 875 +++++++ .../sourcemod/scripting/sf2/client/static.sp | 439 ++++ .../sourcemod/scripting/sf2/client/think.sp | 958 +++++++ .../scripting/sf2/client/ultravision.sp | 284 +++ addons/sourcemod/scripting/sf2/debug.sp | 6 +- addons/sourcemod/scripting/sf2/effects.sp | 24 +- .../scripting/sf2/extras/commands.sp | 424 +++- .../scripting/sf2/extras/game_events.sp | 846 +------ .../sourcemod/scripting/sf2/extras/natives.sp | 132 +- .../scripting/sf2/extras/renevant_mode.sp | 55 +- addons/sourcemod/scripting/sf2/mapentities.sp | 36 +- .../sf2/mapentities/sf2_game_text.sp | 6 +- addons/sourcemod/scripting/sf2/menus.sp | 86 +- addons/sourcemod/scripting/sf2/methodmaps.sp | 155 +- addons/sourcemod/scripting/sf2/npc.sp | 398 ++- .../sf2/npc/entities/chaser/entity.sp | 1 + .../scripting/sf2/npc/entities/initialize.sp | 9 + .../sf2/npc/entities/statue/actions/main.sp | 281 ++ .../sf2/npc/entities/statue/entity.sp | 154 ++ .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 8 +- .../scripting/sf2/npc/npc_chaser_attacks.sp | 38 +- .../scripting/sf2/npc/npc_chaser_mind.sp | 39 +- .../sf2/npc/npc_chaser_projectiles.sp | 10 +- .../sf2/npc/npc_chaser_takedamage.sp | 2 +- .../scripting/sf2/npc/npc_creeper.sp | 314 +-- .../sourcemod/scripting/sf2/playergroups.sp | 20 +- .../scripting/sf2/playergroups/menus.sp | 8 +- addons/sourcemod/scripting/sf2/profiles.sp | 6 +- .../sf2/profiles/profile_chaser_precache.sp | 44 +- .../sf2/profiles/profiles_boss_functions.sp | 8 +- addons/sourcemod/scripting/sf2/pvp.sp | 186 +- .../sourcemod/scripting/sf2/specialround.sp | 297 ++- addons/sourcemod/scripting/sf2/stocks.sp | 169 +- addons/sourcemod/scripting/sf2/traps.sp | 54 +- 49 files changed, 11409 insertions(+), 4237 deletions(-) create mode 100644 addons/sourcemod/scripting/sf2/anticamping.sp create mode 100644 addons/sourcemod/scripting/sf2/client/blink.sp create mode 100644 addons/sourcemod/scripting/sf2/client/breathing.sp create mode 100644 addons/sourcemod/scripting/sf2/client/deathcam.sp create mode 100644 addons/sourcemod/scripting/sf2/client/flashlight.sp create mode 100644 addons/sourcemod/scripting/sf2/client/ghostmode.sp create mode 100644 addons/sourcemod/scripting/sf2/client/hints.sp create mode 100644 addons/sourcemod/scripting/sf2/client/music.sp create mode 100644 addons/sourcemod/scripting/sf2/client/peek.sp create mode 100644 addons/sourcemod/scripting/sf2/client/proxy.sp create mode 100644 addons/sourcemod/scripting/sf2/client/sprint.sp create mode 100644 addons/sourcemod/scripting/sf2/client/static.sp create mode 100644 addons/sourcemod/scripting/sf2/client/think.sp create mode 100644 addons/sourcemod/scripting/sf2/client/ultravision.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/initialize.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 9442ac8a..5a34fbde 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -3,8 +3,8 @@ #endif #define _sf2_included -#define PLUGIN_VERSION "1.7.5.10 M" -#define PLUGIN_VERSION_DISPLAY "1.7.5.10 M" +#define PLUGIN_VERSION "1.8.0 M Alpha" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 @@ -991,7 +991,7 @@ native bool SF2_IsClientInDeathCam(int client); * @param vecLookPos The position the player should look at. * @param antiCamp Whether the player was camping or not. */ -native void SF2_ClientStartDeathCam(int client, int bossIndex, const float vecLookPos[3], bool antiCamp = false); +native void SF2_ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false); /** * Returns whether or not a proxy can be spawned, returns a teleport position or spawn point if defined and the result is true, works well with SF2_ClientForceProxy. diff --git a/addons/sourcemod/scripting/include/sf2/defines.inc b/addons/sourcemod/scripting/include/sf2/defines.inc index eaf37f34..f1ca4841 100644 --- a/addons/sourcemod/scripting/include/sf2/defines.inc +++ b/addons/sourcemod/scripting/include/sf2/defines.inc @@ -15,16 +15,16 @@ #define CRIT_PARTICLENAME "crit_text" #define MINICRIT_SOUND "player/crit_hit_mini.wav" #define MINICRIT_PARTICLENAME "minicrit_text" -#define ZAP_SOUND "weapons/barret_arm_zap.wav" +#define ZAP_SOUND ")weapons/barret_arm_zap.wav" #define ZAP_PARTICLENAME "dxhr_arm_muzzleflash" #define FIREWORKSBLU_PARTICLENAME "utaunt_firework_teamcolor_blue" #define FIREWORKSRED_PARTICLENAME "utaunt_firework_teamcolor_red" #define TELEPORTEDINBLU_PARTICLENAME "teleported_red" -#define SOUND_THUNDER "ambient/explosions/explode_9.wav" +#define SOUND_THUNDER ")ambient/explosions/explode_9.wav" -#define EXPLOSIVEDANCE_EXPLOSION1 "weapons/explode1.wav" -#define EXPLOSIVEDANCE_EXPLOSION2 "weapons/explode2.wav" -#define EXPLOSIVEDANCE_EXPLOSION3 "weapons/explode3.wav" +#define EXPLOSIVEDANCE_EXPLOSION1 ")weapons/explode1.wav" +#define EXPLOSIVEDANCE_EXPLOSION2 ")weapons/explode2.wav" +#define EXPLOSIVEDANCE_EXPLOSION3 ")weapons/explode3.wav" #define SPECIAL1UPSOUND "mvm/mvm_revive.wav" @@ -36,8 +36,8 @@ #define PAGE_MODEL "models/slender/pickups/sheet.mdl" #define PAGE_MODELSCALE 1.0 -#define DEFAULT_CLOAKONSOUND "weapons/medi_shield_deploy.wav" -#define DEFAULT_CLOAKOFFSOUND "weapons/medi_shield_retract.wav" +#define DEFAULT_CLOAKONSOUND ")weapons/medi_shield_deploy.wav" +#define DEFAULT_CLOAKOFFSOUND ")weapons/medi_shield_retract.wav" #define SF_KEYMODEL "models/demani_sf/key_australium.mdl" @@ -45,7 +45,7 @@ #define FLASHLIGHT_CLICKSOUND_NIGHTVISION "slender/nightvision.mp3" #define FLASHLIGHT_BREAKSOUND "ambient/energy/spark6.wav" #define FLASHLIGHT_NOSOUND "player/suit_denydevice.wav" -#define PAGE_GRABSOUND "slender/slenderpagegrab.wav" +#define PAGE_GRABSOUND ")slender/slenderpagegrab.wav" #define MUSIC_CHAN SNDCHAN_AUTO #define MUSIC_GOTPAGES1_SOUND "slender/newambience_1.wav" @@ -58,32 +58,32 @@ #define PROXY_RAGE_MODE_SOUND "slender/proxyrage.mp3" -#define FIREBALL_SHOOT "misc/halloween/spell_fireball_cast.wav" -#define FIREBALL_IMPACT "misc/halloween/spell_fireball_impact.wav" +#define FIREBALL_SHOOT ")misc/halloween/spell_fireball_cast.wav" +#define FIREBALL_IMPACT ")misc/halloween/spell_fireball_impact.wav" #define FIREBALL_TRAIL "spell_fireball_small_red" -#define ICEBALL_IMPACT "weapons/icicle_freeze_victim_01.wav" +#define ICEBALL_IMPACT ")weapons/icicle_freeze_victim_01.wav" #define ICEBALL_TRAIL "spell_fireball_small_blue" -#define ROCKET_SHOOT "weapons/rocket_shoot.wav" -#define ROCKET_IMPACT "weapons/explode1.wav" +#define ROCKET_SHOOT ")weapons/rocket_shoot.wav" +#define ROCKET_IMPACT ")weapons/explode1.wav" #define ROCKET_MODEL "models/weapons/w_models/w_rocket.mdl" #define ROCKET_TRAIL "rockettrail" #define ROCKET_EXPLODE_PARTICLE "ExplosionCore_MidAir" -#define GRENADE_SHOOT "weapons/grenade_launcher_shoot.wav" -#define SENTRYROCKET_SHOOT "weapons/sentry_rocket.wav" -#define ARROW_SHOOT "weapons/bow_shoot.wav" +#define GRENADE_SHOOT ")weapons/grenade_launcher_shoot.wav" +#define SENTRYROCKET_SHOOT ")weapons/sentry_rocket.wav" +#define ARROW_SHOOT ")weapons/bow_shoot.wav" #define GRENADE_MODEL "models/weapons/w_models/w_grenade_grenadelauncher.mdl" -#define MANGLER_SHOOT "weapons/cow_mangler_main_shot.wav" -#define MANGLER_EXPLODE1 "weapons/cow_mangler_explosion_normal_01.wav" -#define MANGLER_EXPLODE2 "weapons/cow_mangler_explosion_normal_02.wav" -#define MANGLER_EXPLODE3 "weapons/cow_mangler_explosion_normal_03.wav" -#define BASEBALL_SHOOT "weapons/bat_baseball_hit1.wav" +#define MANGLER_SHOOT ")weapons/cow_mangler_main_shot.wav" +#define MANGLER_EXPLODE1 ")weapons/cow_mangler_explosion_normal_01.wav" +#define MANGLER_EXPLODE2 ")weapons/cow_mangler_explosion_normal_02.wav" +#define MANGLER_EXPLODE3 ")weapons/cow_mangler_explosion_normal_03.wav" +#define BASEBALL_SHOOT ")weapons/bat_baseball_hit1.wav" #define BASEBALL_MODEL "weapons/w_models/w_baseball.mdl" -#define JARATE_HITPLAYER "weapons/jar_single.wav" +#define JARATE_HITPLAYER ")weapons/jar_single.wav" #define JARATE_PARTICLE "peejar_impact" #define MILK_PARTICLE "peejar_impact_milk" #define GAS_PARTICLE "gas_can_impact_blue" -#define STUN_HITPLAYER "weapons/icicle_freeze_victim_01.wav" +#define STUN_HITPLAYER ")weapons/icicle_freeze_victim_01.wav" #define STUN_PARTICLE "xms_icicle_melt" #define ELECTRIC_RED_PARTICLE "electrocuted_gibbed_red" #define ELECTRIC_BLUE_PARTICLE "electrocuted_gibbed_red" @@ -98,10 +98,6 @@ #define JARATE_ROLL "weapons/jar_explode.wav" #define GAS_ROLL "weapons/gas_can_explode.wav" -#define FIREWORK_EXPLOSION "weapons/flare_detonator_explode.wav" -#define FIREWORK_START "weapons/flare_detonator_launch.wav" -#define FIREWORK_PARTICLE "burningplayer_rainbow_flame" - #define GENERIC_ROLL_TICK "ui/buttonrollover.wav" #define MINICRIT_BUFF "weapons/buff_banner_flag.wav" @@ -121,8 +117,8 @@ #define NINETYSMUSIC "slender/sf2modified_runninginthe90s_v2.wav" -#define TRAP_DEPLOY "slender/modified_traps/beartrap/trap_deploy.mp3" -#define TRAP_CLOSE "slender/modified_traps/beartrap/trap_close.mp3" +#define TRAP_DEPLOY ")slender/modified_traps/beartrap/trap_deploy.mp3" +#define TRAP_CLOSE ")slender/modified_traps/beartrap/trap_close.mp3" #define TRAP_MODEL "models/mentrillum/traps/beartrap.mdl" #define LASER_MODEL "sprites/laser.vmt" diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 04aed187..81632ffa 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -4,11 +4,18 @@ // FUNCTIONS // ====================================== -stock void PrecacheSound2(const char[] path, bool checkFile) +stock void PrecacheSound2(const char[] path, bool checkFile, bool replace = true) { - PrecacheSound(path, true); + char tempPath[PLATFORM_MAX_PATH]; + strcopy(tempPath, sizeof(tempPath), path); + PrecacheSound(tempPath, true); + if (replace) + { + ReplaceStringEx(tempPath, sizeof(tempPath), ")", ""); + ReplaceStringEx(tempPath, sizeof(tempPath), "#", ""); + } char buffer[PLATFORM_MAX_PATH]; - FormatEx(buffer, sizeof(buffer), "sound/%s", path); + FormatEx(buffer, sizeof(buffer), "sound/%s", tempPath); if (checkFile) { if (FileExists(buffer, true)) @@ -35,34 +42,35 @@ stock void PrecacheSound2(const char[] path, bool checkFile) * @param disableWarning Disables the warning if a file doesn't exist. * @param checkFile Determines if missing files should be checked. */ -stock void TryPrecacheBossProfileSoundPath(const char[] soundPath, bool disableWarning = false, bool checkFile) +stock void TryPrecacheBossProfileSoundPath(const char[] soundPath, bool checkFile) { if (soundPath[0] == '\0') { return; } - char fullPath[PLATFORM_MAX_PATH]; - FormatEx(fullPath, sizeof(fullPath), "sound/%s", soundPath); + char fullPath[PLATFORM_MAX_PATH], tempPath[PLATFORM_MAX_PATH]; + strcopy(tempPath, sizeof(tempPath), soundPath); + PrecacheSound(tempPath); + ReplaceStringEx(tempPath, sizeof(tempPath), ")", ""); + ReplaceStringEx(tempPath, sizeof(tempPath), "#", ""); + FormatEx(fullPath, sizeof(fullPath), "sound/%s", tempPath); if (checkFile) { if (FileExists(fullPath, false) || FileExists(fullPath, true)) { - PrecacheSound2(soundPath, checkFile); + PrecacheSound2(tempPath, checkFile, false); } else { - if (!disableWarning) - { - PrintToServer("[SF2] Sound file %s does not exist, excluding from downloads!", fullPath); - } - PrecacheSound(soundPath); + PrintToServer("[SF2] Sound file %s does not exist, excluding from downloads!", fullPath); + PrecacheSound(tempPath); } } else { - PrecacheSound2(soundPath, checkFile); + PrecacheSound2(tempPath, checkFile, false); } } @@ -728,7 +736,7 @@ enum struct SF2BossProfileSoundInfo break; } - TryPrecacheBossProfileSoundPath(soundPath, _, checkFile); + TryPrecacheBossProfileSoundPath(soundPath, checkFile); this.Paths.PushString(soundPath); } diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index ce6993d1..b5bc3d03 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -46,7 +46,7 @@ bool steamworks; public Plugin myinfo = { - name = "Slender Fortress", + name = "Slender Fortress Modified", author = "KitRifty, Kenzzer, Mentrillum, The Gaben", description = "Based on the game Slender: The Eight Pages.", version = PLUGIN_VERSION, @@ -105,15 +105,18 @@ char g_SoundNightmareMode[][] = static const char g_PageCollectDuckSounds[][] = { - "ambient/bumper_car_quack1.wav", - "ambient/bumper_car_quack2.wav", - "ambient/bumper_car_quack3.wav", - "ambient/bumper_car_quack4.wav", - "ambient/bumper_car_quack5.wav", - "ambient/bumper_car_quack9.wav", - "ambient/bumper_car_quack11.wav" + ")ambient/bumper_car_quack1.wav", + ")ambient/bumper_car_quack2.wav", + ")ambient/bumper_car_quack3.wav", + ")ambient/bumper_car_quack4.wav", + ")ambient/bumper_car_quack5.wav", + ")ambient/bumper_car_quack9.wav", + ")ambient/bumper_car_quack11.wav" }; +bool g_ClientInGame[MAXTF2PLAYERS] = { false, ... }; +bool g_ClientInCondition[MAXTF2PLAYERS][TFCond_PowerupModeDominant]; + //Update bool g_SeeUpdateMenu[MAXTF2PLAYERS] = { false, ... }; //Command @@ -429,25 +432,6 @@ bool g_PlayerFullyDied1Up[MAXTF2PLAYERS]; float g_PlayerLastChaseBossEncounterTime[MAXTF2PLAYERS][MAX_BOSSES]; -// Player static data. -int g_PlayerStaticMode[MAXTF2PLAYERS][MAX_BOSSES]; -float g_PlayerStaticIncreaseRate[MAXTF2PLAYERS]; -float g_PlayerStaticDecreaseRate[MAXTF2PLAYERS]; -Handle g_PlayerStaticTimer[MAXTF2PLAYERS]; -int g_PlayerStaticMaster[MAXTF2PLAYERS] = { -1, ... }; -char g_PlayerStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -char g_PlayerLastStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -float g_PlayerLastStaticTime[MAXTF2PLAYERS]; -float g_PlayerLastStaticVolume[MAXTF2PLAYERS]; -Handle g_PlayerLastStaticTimer[MAXTF2PLAYERS]; - -// Static shake data. -int g_PlayerStaticShakeMaster[MAXTF2PLAYERS]; -bool g_PlayerInStaticShake[MAXTF2PLAYERS]; -char g_PlayerStaticShakeSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -float g_PlayerStaticShakeMinVolume[MAXTF2PLAYERS]; -float g_PlayerStaticShakeMaxVolume[MAXTF2PLAYERS]; - float g_PlayerProxyNextVoiceSound[MAXTF2PLAYERS]; bool g_PlayerTrapped[MAXTF2PLAYERS]; @@ -663,13 +647,6 @@ ConVar g_GraceTimeConVar; ConVar g_AllChatConVar; ConVar g_MaxPlayersConVar; ConVar g_MaxPlayersOverrideConVar; -ConVar g_CampingEnabledConVar; -ConVar g_CampingMaxStrikesConVar; -ConVar g_CampingStrikesWarnConVar; -ConVar g_ExitCampingTimeAllowedConVar; -ConVar g_CampingMinDistanceConVar; -ConVar g_CampingNoStrikeSanityConVar; -ConVar g_CampingNoStrikeBossDistanceConVar; ConVar g_DifficultyConVar; ConVar g_CameraOverlayConVar; ConVar g_OverlayNoGrainConVar; @@ -815,6 +792,23 @@ GlobalForward g_OnClientEnterGameFwd; GlobalForward g_OnGroupEnterGameFwd; GlobalForward g_OnEverythingLoadedFwd; +// Private forwards +PrivateForward g_OnMapStartPFwd; +PrivateForward g_OnGameFramePFwd; +PrivateForward g_OnRoundStartPFwd; +PrivateForward g_OnRoundEndPFwd; +PrivateForward g_OnEntityCreatedPFwd; +PrivateForward g_OnEntityDestroyedPFwd; +PrivateForward g_OnPlayerJumpPFwd; +PrivateForward g_OnPlayerSpawnPFwd; +PrivateForward g_OnPlayerDeathPFwd; +PrivateForward g_OnPlayerPutInServerPFwd; +PrivateForward g_OnPlayerDisconnectedPFwd; +PrivateForward g_OnPlayerEscapePFwd; +PrivateForward g_OnPlayerTeamPFwd; +PrivateForward g_OnPlayerClassPFwd; +PrivateForward g_OnBossRemovedPFwd; + Handle g_SDKGetMaxHealth; Handle g_SDKEquipWearable; Handle g_SDKPlaySpecificSequence; @@ -876,6 +870,7 @@ int g_FlashlightHaloModel = -1; #include "sf2/pvp.sp" #include "sf2/client.sp" #include "sf2/specialround.sp" +#include "sf2/anticamping.sp" #include "sf2/adminmenu.sp" #include "sf2/traps.sp" #include "sf2/extras/renevant_mode.sp" @@ -925,7 +920,6 @@ public void OnLibraryRemoved(const char[] name) public void OnMapStart() { g_TimerFail = null; - PvP_OnMapStart(); FindHealthBar(); PrecacheSound(SOUND_THUNDER, true); PrecacheSound("weapons/teleporter_send.wav"); @@ -945,7 +939,10 @@ public void OnMapStart() PrecacheModel(SF2_FLASHLIGHT_BEAM_MATERIAL); g_FlashlightHaloModel = PrecacheModel(SF2_FLASHLIGHT_HALO_MATERIAL, true); - SF2MapEntity_OnMapStart(); + SetupEntityActions(); + + Call_StartForward(g_OnMapStartPFwd); + Call_Finish(); } public void OnConfigsExecuted() @@ -1049,14 +1046,14 @@ static void StartPlugin() #if defined _SteamWorks_Included if (steamworks) { - SteamWorks_SetGameDescription("Slender Fortress ("...PLUGIN_VERSION_DISPLAY...")"); + SteamWorks_SetGameDescription("SF2 Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); steamtools = false; } #endif #if defined _steamtools_included if (steamtools) { - Steam_SetGameDescription("Slender Fortress ("...PLUGIN_VERSION_DISPLAY...")"); + Steam_SetGameDescription("SF2 Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); steamworks = false; } #endif @@ -1141,9 +1138,9 @@ static void PrecacheStuff() g_Particles[FireworksBLU] = PrecacheParticleSystem(FIREWORKSBLU_PARTICLENAME); g_Particles[TeleportedInBlu] = PrecacheParticleSystem(TELEPORTEDINBLU_PARTICLENAME); - for (int i = 0; i < sizeof(g_SoundNightmareMode) - 1; i++) + for (int i = 0; i < sizeof(g_SoundNightmareMode); i++) { - PrecacheSound2(g_SoundNightmareMode[i], true); + PrecacheSound(g_SoundNightmareMode[i], true); } PrecacheSound("ui/itemcrate_smash_ultrarare_short.wav"); @@ -1165,8 +1162,6 @@ static void PrecacheStuff() PrecacheSound(BLEED_ROLL); PrecacheSound(GENERIC_ROLL_TICK); PrecacheSound(LOSE_SPRINT_ROLL); - PrecacheSound(FIREWORK_EXPLOSION); - PrecacheSound(FIREWORK_START); PrecacheSound(MINICRIT_BUFF); PrecacheSound(NULLSOUND); PrecacheSound(JARATE_ROLL); @@ -1397,6 +1392,12 @@ public void OnMapTimeLeftChanged() public void TF2_OnConditionAdded(int client, TFCond cond) { + g_ClientInCondition[client][cond] = true; + + if (!g_Enabled) + { + return; + } SF2_BasePlayer player = SF2_BasePlayer(client); if (cond == TFCond_Taunting) { @@ -1430,6 +1431,11 @@ public void TF2_OnConditionAdded(int client, TFCond cond) } } +public void TF2_OnConditionRemoved(int client, TFCond condition) +{ + g_ClientInCondition[client][condition] = false; +} + static Action Timer_GlobalGameFrame(Handle timer) { if (!g_Enabled) @@ -1471,7 +1477,7 @@ static Action Timer_GlobalGameFrame(Handle timer) } for (int client = 1; client <= MaxClients; client++) { - if (!IsClientInGame(client) || IsFakeClient(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client)) || DidClientEscape(client)) + if (!IsValidClient(client) || IsFakeClient(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client)) || DidClientEscape(client)) { continue; } @@ -1525,7 +1531,7 @@ static Action Timer_GlobalGameFrame(Handle timer) for (int client = 1; client <= MaxClients; client++) { - if (!IsClientInGame(client) || !g_PlayerEliminated[client]) + if (!IsValidClient(client) || !g_PlayerEliminated[client]) { continue; } @@ -1574,7 +1580,7 @@ static Action Timer_GlobalGameFrame(Handle timer) for (int client = 1; client <= MaxClients; client++) { - if (!IsClientInGame(client) || !g_PlayerEliminated[client] || GetClientTeam(client) == TFTeam_Red) + if (!IsValidClient(client) || !g_PlayerEliminated[client] || GetClientTeam(client) == TFTeam_Red) { continue; } @@ -1682,7 +1688,8 @@ static Action Timer_GlobalGameFrame(Handle timer) delete proxyCandidates; } - PvP_OnGameFrame(); + Call_StartForward(g_OnGameFramePFwd); + Call_Finish(); return Plugin_Continue; } @@ -2185,10 +2192,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { ArrayList selectableBossesAdmin = GetSelectableAdminBossProfileList().Clone(); ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - for (int i = 0; i < sizeof(g_SoundNightmareMode) - 1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); SpecialRoundGameText("Its Restart Session time!", "leaderboard_streak"); CPrintToChatAll("{royalblue}%t {default}Your thirst for blood continues? Very well, let the blood spill. Let the demons feed off your unfortunate soul... Difficulty set to {mediumslateblue}%t!", "SF2 Prefix", "SF2 Calamity Difficulty"); g_RestartSessionEnabled = true; @@ -2295,6 +2299,41 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) g_RoundDifficultyModifier = DIFFICULTYMODIFIER_APOLLYON; } } + else if (cvar == g_RaidMapConVar) + { + for (int i = 1; i < MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + if (client.IsEliminated && !client.IsInPvP) + { + ToggleWeaponCooldowns(client, true); + } + else + { + ToggleWeaponCooldowns(client, false); + } + } + } +} + +static void ToggleWeaponCooldowns(SF2_BasePlayer client, bool invert) +{ + for (int i = 0; i <= 5; i++) + { + int weapon = client.GetWeaponSlot(i); + if (!IsValidEntity(weapon)) + { + continue; + } + + CBaseEntity weaponEnt = CBaseEntity(weapon); + weaponEnt.SetPropFloat(Prop_Send, "m_flNextPrimaryAttack", invert ? 0.0 : 99999999.9); + weaponEnt.SetPropFloat(Prop_Send, "m_flNextSecondaryAttack", invert ? 0.0 : 99999999.9); + } } // ========================================================== @@ -2329,7 +2368,11 @@ public void OnEntityCreated(int ent, const char[] classname) { RemoveEntity(ent); } - PvP_OnEntityCreated(ent, classname); + + Call_StartForward(g_OnEntityCreatedPFwd); + Call_PushCell(CBaseEntity(ent)); + Call_PushString(classname); + Call_Finish(); } MRESReturn Hook_WeaponGetCustomDamageType(int weapon, DHookReturn returnHandle, DHookParam params) @@ -2413,7 +2456,10 @@ public void OnEntityDestroyed(int ent) } } - PvP_OnEntityDestroyed(ent, classname); + Call_StartForward(g_OnEntityDestroyedPFwd); + Call_PushCell(CBaseEntity(ent)); + Call_PushString(classname); + Call_Finish(); } Action Hook_BlockUserMessage(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init) @@ -3227,16 +3273,17 @@ static void EmitRollSound(int client) EmitSoundToClient(client, GENERIC_ROLL_TICK, client); g_PlayerPageRewardCycleCount[client] = 0; g_PlayerPageRewardCycleCooldown[client] = 0.0; - g_PlayerPageRewardCycleTimer[client] = CreateTimer(0.1, Timer_RollTick, client, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerPageRewardCycleTimer[client] = CreateTimer(0.1, Timer_RollTick, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } -static Action Timer_RollTick(Handle timer, any client) +static Action Timer_RollTick(Handle timer, any id) { if (!g_Enabled) { return Plugin_Stop; } + int client = GetClientOfUserId(id); if (!IsValidClient(client) || timer != g_PlayerPageRewardCycleTimer[client] || g_PlayerEliminated[client]) { return Plugin_Stop; @@ -3434,6 +3481,13 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 return Plugin_Continue; } + if (!IsValidClient(client)) + { + return Plugin_Continue; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + bool changed = false; // Check impulse (block spraying and built-in flashlight) @@ -3445,7 +3499,7 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 } case 201, 202: { - if (IsClientInGhostMode(client)) + if (player.IsInGhostMode) { impulse = 0; } @@ -3457,34 +3511,34 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 if ((buttons & button)) { - if (!(g_PlayerLastButtons[client] & button)) + if (!(player.LastButtons & button)) { - AFK_SetTime(client); - ClientOnButtonPress(client, button); + player.SetAFKTime(); + ClientOnButtonPress(player, button); if (button == IN_ATTACK2) { - if (IsClientInPvP(client) && !(buttons & IN_ATTACK)) + if (player.IsInPvP && !(buttons & IN_ATTACK)) { - if (TF2_GetPlayerClass(client) == TFClass_Medic) + if (player.Class == TFClass_Medic) { - int weaponEnt = GetPlayerWeaponSlot(client, 0); - if (weaponEnt > MaxClients) + CBaseEntity weaponEnt = CBaseEntity(player.GetWeaponSlot(0)); + if (weaponEnt.IsValid()) { char weaponClass[64]; - GetEdictClassname(weaponEnt, weaponClass, sizeof(weaponClass)); + weaponEnt.GetClassname(weaponClass, sizeof(weaponClass)); if (strcmp(weaponClass, "tf_weapon_crossbow") == 0) { - int clip = GetEntProp(weaponEnt, Prop_Send, "m_iClip1"); + int clip = weaponEnt.GetProp(Prop_Send, "m_iClip1"); if (clip > 0) { buttons |= IN_ATTACK; - g_PlayerLastButtons[client] = buttons; + player.LastButtons = buttons; buttons &= ~IN_ATTACK2; changed = true; - RequestFrame(Frame_ClientHealArrow, client); + RequestFrame(Frame_ClientHealArrow, player.index); - EmitSoundToAll(")weapons/crusaders_crossbow_shoot.wav", client, SNDCHAN_WEAPON, SNDLEVEL_MINIBIKE); //Fix client's predictions. + EmitSoundToAll(")weapons/crusaders_crossbow_shoot.wav", player.index, SNDCHAN_WEAPON, SNDLEVEL_MINIBIKE); //Fix client's predictions. } } } @@ -3494,11 +3548,11 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 } if (button == IN_ATTACK2) { - if (!g_PlayerEliminated[client]) + if (!player.IsEliminated) { - g_PlayerLastButtons[client] = buttons; - int weaponActive = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); - if (weaponActive > MaxClients && IsTauntWep(weaponActive)) + player.LastButtons = buttons; + CBaseEntity weaponActive = CBaseEntity(player.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (weaponActive.IsValid() && IsTauntWep(weaponActive.index)) { buttons &= ~IN_ATTACK2; //Tough break update made players able to taunt with secondary attack. Block this feature. changed = true; @@ -3506,17 +3560,17 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 } } } - else if ((g_PlayerLastButtons[client] & button)) + else if ((player.LastButtons & button)) { - ClientOnButtonRelease(client, button); + ClientOnButtonRelease(player, button); } } - AFK_CheckTime(client); + player.CheckAFKTime(); if (!changed) { - g_PlayerLastButtons[client] = buttons; + player.LastButtons = buttons; } return (changed) ? Plugin_Changed : Plugin_Continue; } @@ -3647,6 +3701,8 @@ public void OnClientPutInServer(int client) g_PlayerPageCount[client] = 0; g_PlayerDesiredFOV[client] = 90; + g_ClientInGame[client] = true; + SDKHook(client, SDKHook_PreThink, Hook_ClientPreThink); SDKHook(client, SDKHook_PreThinkPost, Hook_OnFlashlightThink); SDKHook(client, SDKHook_SetTransmit, Hook_ClientSetTransmit); @@ -3671,36 +3727,17 @@ public void OnClientPutInServer(int client) SetPlayerGroupInvitedPlayerTime(i, client, 0.0); } - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); - ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); ClientResetInteractiveGlow(client); ClientDisableConstantGlow(client); ClientSetScareBoostEndTime(client, -1.0); - ClientStartProxyAvailableTimer(client); - AFK_SetAFK(client); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) @@ -3729,7 +3766,9 @@ public void OnClientPutInServer(int client) QueryClientConVar(client, "fov_desired", OnClientGetDesiredFOV); } - PvP_OnClientPutInServer(client); + Call_StartForward(g_OnPlayerPutInServerPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); #if defined DEBUG g_PlayerDebugFlags[client] = 0; @@ -3823,14 +3862,9 @@ public void OnClientDisconnect(int client) // Reset any client functions that may be still active. ClientResetOverlay(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientSetGhostModeState(client, false); ClientResetInteractiveGlow(client); ClientDisableConstantGlow(client); - ClientStopProxyForce(client); - if (SF_IsBoxingMap() && IsRoundInEscapeObjective()) { CreateTimer(0.2, Timer_CheckAlivePlayers, _, TIMER_FLAG_NO_MAPCHANGE); @@ -3860,9 +3894,12 @@ public void OnClientDisconnect(int client) // Reset queue points global variable. g_PlayerQueuePoints[client] = 0; - PvP_OnClientDisconnect(client); AFK_SetTime(client, false); + Call_StartForward(g_OnPlayerDisconnectedPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) { @@ -3874,6 +3911,7 @@ public void OnClientDisconnect(int client) public void OnClientDisconnect_Post(int client) { g_PlayerLastButtons[client] = 0; + g_ClientInGame[client] = false; } public void TF2_OnWaitingForPlayersStart() @@ -3953,7 +3991,7 @@ void SetRoundState(SF2RoundState roundState) // Enable movement on players. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -3966,7 +4004,7 @@ void SetRoundState(SF2RoundState roundState) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -4072,6 +4110,7 @@ void SetRoundState(SF2RoundState roundState) g_RenevantBeaconEffect = false; g_Renevant90sEffect = false; g_RenevantMarkForDeath = false; + g_DifficultyConVar.SetInt(Difficulty_Normal); if (g_RestartSessionConVar.BoolValue) { g_RestartSessionEnabled = false; @@ -4105,7 +4144,7 @@ void SetRoundState(SF2RoundState roundState) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -4166,7 +4205,6 @@ void SetRoundState(SF2RoundState roundState) } if (SF_IsBoxingMap()) { - g_DifficultyConVar.IntValue = Difficulty_Normal; CPrintToChatAll("%t", "SF2 Boxing Initiate"); CreateTimer(0.2, Timer_CheckAlivePlayers, _, TIMER_FLAG_NO_MAPCHANGE); @@ -4195,7 +4233,7 @@ void SetRoundState(SF2RoundState roundState) // Teleport winning players to the escape point. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -4209,7 +4247,7 @@ void SetRoundState(SF2RoundState roundState) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -4279,7 +4317,7 @@ bool IsInfiniteSprintEnabled() return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1); } -stock bool IsClientParticipating(int client) +bool IsClientParticipating(int client) { if (!IsValidClient(client)) { @@ -4351,18 +4389,7 @@ ArrayList GetQueueList() return array; } -stock int GetOppositeTeam(int team) -{ - return team == 2 ? 3 : 2; -} - -stock int GetOppositeTeamOf(int client) -{ - int team = GetClientTeam(client); - return GetOppositeTeam(team); -} - -void SetClientPlayState(int client, bool state, bool enablePlay=true) +void SetClientPlayState(int client, bool state, bool enablePlay = true) { Handle message = StartMessageAll("PlayerTauntSoundLoopEnd", USERMSG_RELIABLE); BfWriteByte(message, client); @@ -4444,7 +4471,7 @@ void SetClientPlaySpecialRoundState(int client, bool state) void TeleportClientToEscapePoint(int client) { - if (!IsClientInGame(client)) + if (!IsValidClient(client)) { return; } @@ -4474,7 +4501,7 @@ void TeleportClientToEscapePoint(int client) delete spawnPoints; - if (ent && IsValidEntity(ent)) + if (ent && ent != INVALID_ENT_REFERENCE) { SF2PlayerEscapeSpawnEntity spawnPoint = SF2PlayerEscapeSpawnEntity(ent); @@ -4713,7 +4740,7 @@ void SlenderOnClientStressUpdate(int client) ArrayList proxyArray = new ArrayList(); for (int i = 1; i <= MaxClients; i++) { - if (IsClientInGame(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) + if (IsValidClient(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) { proxyArray.Push(i); } @@ -4770,7 +4797,7 @@ void SlenderOnClientStressUpdate(int client) for (int i = 1; i <= MaxClients; i++) { - if (IsClientInGame(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) + if (IsValidClient(i) && IsPlayerAlive(i) && !g_PlayerEliminated[i] && !IsClientInGhostMode(i) && !DidClientEscape(i)) { if (g_PlayerIsExitCamping[i]) { @@ -4834,7 +4861,7 @@ void GetPageEntities(ArrayList array) for (int i = g_Pages.Length - 1; i >= 0; i--) { int pageEntIndex = EntRefToEntIndex(g_Pages.Get(i, SF2PageEntityData::EntRef)); - if (pageEntIndex != INVALID_ENT_REFERENCE) + if (pageEntIndex && pageEntIndex != INVALID_ENT_REFERENCE) { array.Push(pageEntIndex); } @@ -5190,7 +5217,7 @@ void SetPageCount(int num) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -5393,14 +5420,9 @@ static bool TraceFilter_NotTeam(int entity, int contentsMask, int team) { return false; } - if (IsValidEdict(entity)) + if (IsValidEdict(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } + return false; } return true; } @@ -5532,7 +5554,7 @@ void DistributeQueuePointsToPlayers() for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -5578,7 +5600,7 @@ void DistributeQueuePointsToPlayers() */ bool HandlePlayerTeam(int client, bool respawn=true) { - if (!IsClientInGame(client) || !IsClientParticipating(client)) + if (!IsValidClient(client) || !IsClientParticipating(client)) { return false; } @@ -5589,10 +5611,10 @@ bool HandlePlayerTeam(int client, bool respawn=true) { if (respawn) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); TF2_RemoveCondition(client, TFCond_SpawnOutline); ChangeClientTeamNoSuicide(client, TFTeam_Red); } @@ -5610,10 +5632,10 @@ bool HandlePlayerTeam(int client, bool respawn=true) { if (respawn) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); TF2_RemoveCondition(client, TFCond_SpawnOutline); ChangeClientTeamNoSuicide(client, TFTeam_Blue); } @@ -5631,7 +5653,7 @@ bool HandlePlayerTeam(int client, bool respawn=true) void HandlePlayerIntroState(int client) { - if (!IsClientInGame(client) || !IsPlayerAlive(client) || !IsClientParticipating(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client) || !IsClientParticipating(client)) { return; } @@ -5833,7 +5855,7 @@ int GetClientForDeath(int exclude1, int exclude2 = 0) // Use AFKs first for (int i = 1; i <= MaxClients; i++) { - if (i != exclude1 && i != exclude2 && IsClientInGame(i) && GetClientTeam(i) > TFTeam_Spectator && g_PlayerNoPoints[i]) + if (i != exclude1 && i != exclude2 && IsValidClient(i) && GetClientTeam(i) > TFTeam_Spectator && g_PlayerNoPoints[i]) { return i; } @@ -5842,7 +5864,7 @@ int GetClientForDeath(int exclude1, int exclude2 = 0) // Use BLU second for (int i = 1; i <= MaxClients; i++) { - if (i != exclude1 && i != exclude2 && IsClientInGame(i) && GetClientTeam(i) == TFTeam_Blue) + if (i != exclude1 && i != exclude2 && IsValidClient(i) && GetClientTeam(i) == TFTeam_Blue) { return i; } @@ -5851,7 +5873,7 @@ int GetClientForDeath(int exclude1, int exclude2 = 0) // Anyone else last for (int i = 1; i <= MaxClients; i++) { - if (i != exclude1 && i != exclude2 && IsClientInGame(i)) + if (i != exclude1 && i != exclude2 && IsValidClient(i)) { return i; } @@ -5896,23 +5918,15 @@ Action Timer_SendDeath(Handle timer, Event event) int client = GetClientOfUserId(event.GetInt("userid")); if (client > 0) { - int ignore = event.GetInt("ignore"); - if (!ignore) - { - //Delay event until their name is correct - int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (attacker > 0 && attacker <= MaxClients && g_TimerChangeClientName[attacker]) - { - return Plugin_Continue; - } - } - //Send it to the clients - for (int i = 1; i<=MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { - if (i != ignore && IsValidClient(i)) + if (IsValidClient(i)) { - if (!g_PlayerEliminated[client] || g_PlayerEliminated[i] || GetClientTeam(client) == GetClientTeam(i)) event.FireToClient(i); + if (!g_PlayerEliminated[client] || g_PlayerEliminated[i] || GetClientTeam(client) == GetClientTeam(i)) + { + event.FireToClient(i); + } } } } @@ -5920,22 +5934,10 @@ Action Timer_SendDeath(Handle timer, Event event) return Plugin_Stop; } -Action Timer_SendDeathToSpecific(Handle timer, Event event) -{ - int client = GetClientOfUserId(event.GetInt("send")); - if (client > 0) - { - event.FireToClient(client); - } - - event.Cancel(); - return Plugin_Stop; -} - Action Timer_RevertClientName(Handle timer, int index) { g_TimerChangeClientName[index] = null; - if (IsClientInGame(index)) + if (IsValidClient(index)) { SetClientName(index, g_OldClientName[index]); SetEntPropString(index, Prop_Data, "m_szNetname", g_OldClientName[index]); @@ -5955,7 +5957,7 @@ Action Timer_CheckAlivePlayers(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i) || !IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -6452,10 +6454,9 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntPropVector(ent, Prop_Send, "m_vecRagdollOrigin", pos); if (g_SlenderHasPushRagdollOnKill[bossIndex]) { - float vectorForce[3]; - GetBossProfilePushRagdollForce(profile, vectorForce); - SetEntPropVector(ent, Prop_Send, "m_vecRagdollVelocity", vectorForce); - SetEntPropVector(ent, Prop_Send, "m_vecForce", vectorForce); + GetBossProfilePushRagdollForce(profile, force); + SetEntPropVector(ent, Prop_Send, "m_vecRagdollVelocity", force); + SetEntPropVector(ent, Prop_Send, "m_vecForce", force); } else { @@ -6652,8 +6653,8 @@ stock int AttachParticle(int entity, char[] particleType, float posOffset[3] = { DispatchKeyValue(particle, "effect_name", particleType); SetVariantString("!activator"); AcceptEntityInput(particle, "SetParent", entity, particle, 0); - float vec_start[3]; - TeleportEntity(particle, vec_start, NULL_VECTOR, NULL_VECTOR); + float start[3]; + TeleportEntity(particle, start, NULL_VECTOR, NULL_VECTOR); DispatchSpawn(particle); AcceptEntityInput(particle, "start"); @@ -6710,7 +6711,7 @@ static Action Timer_RoundStart(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || IsFakeClient(i) || g_PlayerEliminated[i]) { continue; } @@ -6725,10 +6726,7 @@ static Action Timer_RoundStart(Handle timer) { if (clientsNum) { - // Automatically set it to Normal. - g_DifficultyConVar.SetInt(Difficulty_Normal); - - g_VoteTimer = CreateTimer(1.0, Timer_VoteDifficulty, arrayClients, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_VoteTimer = CreateTimer(5.0, Timer_VoteDifficulty, arrayClients, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); TriggerTimer(g_VoteTimer, true); int gameText = -1; @@ -6805,7 +6803,7 @@ static Action Timer_RoundTime(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) { continue; } @@ -6854,7 +6852,7 @@ static Action Timer_RoundTime(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) + if (!IsValidClient(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) { continue; } @@ -6886,7 +6884,7 @@ static Action Timer_RoundTimeEscape(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i) || DidClientEscape(i)) + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i) || DidClientEscape(i)) { continue; } @@ -6940,7 +6938,7 @@ static Action Timer_RoundTimeEscape(Handle timer) int alivePlayer = 0; for (int client = 1; client <= MaxClients; client++) { - if (IsClientInGame(client) && IsPlayerAlive(client) && !g_PlayerEliminated[client]) + if (IsValidClient(client) && IsPlayerAlive(client) && !g_PlayerEliminated[client]) { alivePlayer++; } @@ -6971,7 +6969,7 @@ static Action Timer_RoundTimeEscape(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) + if (!IsValidClient(i) || IsFakeClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i))) { continue; } @@ -7033,7 +7031,7 @@ static Action Timer_VoteDifficulty(Handle timer, any data) return Plugin_Stop; } - if (IsVoteInProgress()) + if (NativeVotes_IsVoteInProgress()) { return Plugin_Continue; // There's another vote in progess. Wait. } @@ -7540,7 +7538,7 @@ static void SpawnPages() // Spawn all pages. array.Sort(Sort_Random, Sort_Integer); - float vecPos[3], vecAng[3]; + float pos[3], angle[3]; int page; char pageModel[PLATFORM_MAX_PATH]; @@ -7568,8 +7566,8 @@ static void SpawnPages() SF2PageSpawnEntity spawnPoint = SF2PageSpawnEntity(spawnPointEnt); - GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", vecPos); - GetEntPropVector(spawnPointEnt, Prop_Data, "m_angAbsRotation", vecAng); + GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(spawnPointEnt, Prop_Data, "m_angAbsRotation", angle); GetEntPropString(spawnPointEnt, Prop_Data, "m_iParent", pageParentName, sizeof(pageParentName)); // Get model, scale, skin, and animation. @@ -7616,7 +7614,7 @@ static void SpawnPages() DispatchKeyValue(page2, "parentname", pageParentName); DispatchKeyValue(page2, "solid", "0"); SetEntityModel(page2, pageModel); - TeleportEntity(page2, vecPos, vecAng, NULL_VECTOR); + TeleportEntity(page2, pos, angle, NULL_VECTOR); DispatchSpawn(page2); ActivateEntity(page2); SetVariantInt(pageSkin); @@ -7651,7 +7649,7 @@ static void SpawnPages() DispatchKeyValue(page, "parentname", pageParentName); DispatchKeyValue(page, "solid", "2"); SetEntityModel(page, pageModel); - TeleportEntity(page, vecPos, vecAng, NULL_VECTOR); + TeleportEntity(page, pos, angle, NULL_VECTOR); DispatchSpawn(page); ActivateEntity(page); SetVariantInt(pageSkin); @@ -7716,7 +7714,7 @@ static void SpawnPages() static Action Page_RemoveAlwaysTransmit(Handle timer, int ref) { int page = EntRefToEntIndex(ref); - if (page > MaxClients) + if (page && page != INVALID_ENT_REFERENCE) { //All the pages are now "registred" by the client, nuke the always transmit flag. CBaseEntity(page).DispatchUpdateTransmitState(); @@ -7747,7 +7745,7 @@ static bool HandleSpecialRoundState() // Check if there are players who haven't played the special round yet. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedSpecialRound[i] = true; continue; @@ -7874,7 +7872,7 @@ static void HandleNewBossRoundState() // Check if there are players who haven't played the boss round yet. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedNewBossRound[i] = true; continue; @@ -7969,7 +7967,7 @@ int GetActivePlayerCount() for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { continue; } @@ -8259,7 +8257,7 @@ void InitializeNewGame() // Initialize all players' values. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedSpecialRound[i] = true; continue; @@ -8306,7 +8304,7 @@ void InitializeNewGame() // Initialize all players' values. for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || !IsClientParticipating(i)) + if (!IsValidClient(i) || !IsClientParticipating(i)) { g_PlayerPlayedNewBossRound[i] = true; continue; @@ -8346,7 +8344,7 @@ void InitializeNewGame() { for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -8420,10 +8418,6 @@ static Action Timer_PlayIntroMusicToPlayer(Handle timer, any userid) { return Plugin_Stop; } - if (!IsClientInGame(client)) - { - return Plugin_Stop; - } if (timer != g_PlayerIntroMusicTimer[client]) { @@ -8481,7 +8475,10 @@ static Action Timer_NewIntroTextSequence(Handle timer, any data) for (int client = 1; client <= MaxClients; client++) { - if (!IsClientInGame(client) || g_PlayerEliminated[client]) continue; + if (!IsValidClient(client) || g_PlayerEliminated[client]) + { + continue; + } clients[clientsNum] = client; clientsNum++; @@ -8527,7 +8524,7 @@ static Action Timer_IntroTextSequence(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || g_PlayerEliminated[i]) + if (!IsValidClient(i) || g_PlayerEliminated[i]) { continue; } @@ -8660,7 +8657,7 @@ void CheckRoundWinConditions() for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } diff --git a/addons/sourcemod/scripting/sf2/anticamping.sp b/addons/sourcemod/scripting/sf2/anticamping.sp new file mode 100644 index 00000000..25dda4d2 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/anticamping.sp @@ -0,0 +1,229 @@ +#pragma semicolon 1 + +// Anti-camping data. +int g_PlayerCampingStrikes[MAXTF2PLAYERS] = { 0, ... }; +Handle g_PlayerCampingTimer[MAXTF2PLAYERS] = { null, ... }; +float g_PlayerCampingLastPosition[MAXTF2PLAYERS][3]; +bool g_IsPlayerCampingFirstTime[MAXTF2PLAYERS]; +static float g_ClientAllowedTimeNearEscape[MAXTF2PLAYERS]; + +static ConVar g_CampingEnabledConVar; +static ConVar g_CampingMaxStrikesConVar; +static ConVar g_CampingStrikesWarnConVar; +static ConVar g_ExitCampingTimeAllowedConVar; +static ConVar g_CampingMinDistanceConVar; +static ConVar g_CampingNoStrikeSanityConVar; +static ConVar g_CampingNoStrikeBossDistanceConVar; + +void SetupAntiCamping() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + + g_CampingEnabledConVar = CreateConVar("sf2_anticamping_enabled", "1", "Enable/Disable anti-camping system for RED.", _, true, 0.0, true, 1.0); + g_CampingMaxStrikesConVar = CreateConVar("sf2_anticamping_maxstrikes", "4", "How many 5-second intervals players are allowed to stay in one spot before he/she is forced to suicide.", _, true, 0.0); + g_CampingStrikesWarnConVar = CreateConVar("sf2_anticamping_strikeswarn", "2", "The amount of strikes left where the player will be warned of camping."); + g_ExitCampingTimeAllowedConVar = CreateConVar("sf2_exitcamping_allowedtime", "25.0", "The amount of time a player can stay near the exit before being flagged as camper."); + g_CampingMinDistanceConVar = CreateConVar("sf2_anticamping_mindistance", "128.0", "Every 5 seconds the player has to be at least this far away from his last position 5 seconds ago or else he'll get a strike."); + g_CampingNoStrikeSanityConVar = CreateConVar("sf2_anticamping_no_strike_sanity", "0.1", "The camping system will NOT give any strikes under any circumstances if the players's Sanity is missing at least this much of his maximum Sanity (max is 1.0)."); + g_CampingNoStrikeBossDistanceConVar = CreateConVar("sf2_anticamping_no_strike_boss_distance", "512.0", "The camping system will NOT give any strikes under any circumstances if the player is this close to a boss (ignoring LOS)."); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetCampingStats(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetCampingStats(client.index); + + if (!client.IsEliminated) + { + ClientStartCampingTimer(client.index); + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetCampingStats(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetCampingStats(client.index); +} + +static void ClientResetCampingStats(int client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetCampingStats(%d)", client); + } + #endif + + g_PlayerCampingStrikes[client] = 0; + g_PlayerIsExitCamping[client] = false; + g_PlayerCampingTimer[client] = null; + g_IsPlayerCampingFirstTime[client] = true; + g_PlayerCampingLastPosition[client][0] = 0.0; + g_PlayerCampingLastPosition[client][1] = 0.0; + g_PlayerCampingLastPosition[client][2] = 0.0; + g_ClientAllowedTimeNearEscape[client] = g_ExitCampingTimeAllowedConVar.FloatValue; + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetCampingStats(%d)", client); + } + #endif +} + +static void ClientStartCampingTimer(int client) +{ + g_PlayerCampingTimer[client] = CreateTimer(5.0, Timer_ClientCheckCamp, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); +} + +/*void ClientNavAreaUpdate(int client, CNavArea newArea, CNavArea oldArea) +{ + if (GetRoundState() != SF2RoundState_Active) return; + + if (g_PlayerEliminated[client]) return; + + if (newArea == INVALID_NAV_AREA) return; + + if ((oldArea != INVALID_NAV_AREA && oldArea.Attributes & NAV_MESH_DONT_HIDE) || newArea.Attributes & NAV_MESH_DONT_HIDE) + { + g_ClientAllowedTimeNearEscape[client] -= 0.3;//Remove 0.3sec this function is called every ~0.3sec + } + else + { + g_ClientAllowedTimeNearEscape[client] += 0.1;//Forgive the player of 0.1 + } + if (g_ClientAllowedTimeNearEscape[client] <= 0.0 && SF_IsSurvivalMap()) + { + g_PlayerIsExitCamping[client] = true; + } + else + { + g_PlayerIsExitCamping[client] = false; + } + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_NAV, 1, "Old area: %i DHF:%s, New area: %i DHF:%s, is considered as exit camper: %s", oldArea.Index, (oldArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", newArea.Index, (newArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", (g_PlayerIsExitCamping[client]) ? "true" : "false" ); + #endif +}*/ + +static Action Timer_ClientCheckCamp(Handle timer, any userid) +{ + if (IsRoundInWarmup()) + { + return Plugin_Stop; + } + + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerCampingTimer[client]) + { + return Plugin_Stop; + } + + if (IsRoundEnding() || !IsPlayerAlive(client) || g_PlayerEliminated[client] || DidClientEscape(client)) + { + return Plugin_Stop; + } + + if (!g_IsPlayerCampingFirstTime[client]) + { + bool isCamping = false; + float pos[3], maxs[3], mins[3]; + GetClientAbsOrigin(client, pos); + GetEntPropVector(client, Prop_Send, "m_vecMins", mins); + GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); + + // Only do something if the player is NOT stuck. + float distFromLastPosition = GetVectorSquareMagnitude(g_PlayerCampingLastPosition[client], pos); + float distFromClosestBoss = 9999999.0; + int closestBoss = -1; + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + int slender = NPCGetEntIndex(i); + if (!slender || slender == INVALID_ENT_REFERENCE) + { + continue; + } + + float slenderPos[3]; + SlenderGetAbsOrigin(i, slenderPos); + + float dist = GetVectorSquareMagnitude(slenderPos, pos); + if (dist < distFromClosestBoss) + { + closestBoss = i; + distFromClosestBoss = dist; + } + } + /*if (IsSpaceOccupiedIgnorePlayers(pos, mins, maxs, client)) + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is stuck, no actions taken", client, client);*/ + if (!SF_IsBoxingMap() && g_CampingEnabledConVar.BoolValue && + IsRoundPlaying() && + g_PlayerStaticAmount[client] <= g_CampingNoStrikeSanityConVar.FloatValue && + (closestBoss == -1 || distFromClosestBoss >= g_CampingNoStrikeBossDistanceConVar.FloatValue) && + distFromLastPosition <= SquareFloat(g_CampingMinDistanceConVar.FloatValue)) + { + isCamping = true; + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk, or camping", client, client); + } + if (isCamping) + { + g_PlayerCampingStrikes[client]++; + if (g_PlayerCampingStrikes[client] < g_CampingMaxStrikesConVar.IntValue) + { + if (g_PlayerCampingStrikes[client] >= g_CampingStrikesWarnConVar.IntValue) + { + CPrintToChat(client, "{red}%T", "SF2 Camping System Warning", client, (g_CampingMaxStrikesConVar.IntValue - g_PlayerCampingStrikes[client]) * 5); + } + } + else + { + g_PlayerCampingStrikes[client] = 0; + ClientStartDeathCam(client, 0, pos, true); + } + } + else + { + // Forgiveness. + if (g_PlayerCampingStrikes[client] > 0) + { + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is forgiven of one strike.", client, client); + g_PlayerCampingStrikes[client]--; + } + } + + g_PlayerCampingLastPosition[client][0] = pos[0]; + g_PlayerCampingLastPosition[client][1] = pos[1]; + g_PlayerCampingLastPosition[client][2] = pos[2]; + } + else + { + g_IsPlayerCampingFirstTime[client] = false; + //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk/camping for the 1st time since the reset, don't take any actions for now.....", client, client); + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index c9d03ac7..5c0d50c2 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -7,65 +7,10 @@ #define SF2_OVERLAY_DEFAULT "overlays/slender/newcamerahud_3" #define SF2_OVERLAY_DEFAULT_NO_FILMGRAIN "overlays/slender/nofilmgrain" -#define SF2_OVERLAY_GHOST "overlays/slender/ghostcamera" - -#define SF2_ULTRAVISION_WIDTH 800.0 -#define SF2_ULTRAVISION_LENGTH 800.0 -#define SF2_ULTRAVISION_BRIGHTNESS -4 // Intensity of Ultravision. - -#define SF2_PLAYER_BREATH_COOLDOWN_MIN 0.8 -#define SF2_PLAYER_BREATH_COOLDOWN_MAX 2.0 - -#define SF2_FLASHLIGHT_WIDTH 512.0 // How wide the player's Flashlight should be in world units. -#define SF2_FLASHLIGHT_BRIGHTNESS 0 // Intensity of the players' Flashlight. -#define SF2_FLASHLIGHT_DRAIN_RATE 0.65 // How long (in seconds) each bar on the player's Flashlight meter lasts. -#define SF2_FLASHLIGHT_RECHARGE_RATE 0.68 // How long (in seconds) it takes each bar on the player's Flashlight meter to recharge. -#define SF2_FLASHLIGHT_FLICKERAT 0.25 // The percentage of the Flashlight battery where the Flashlight will start to blink. -#define SF2_FLASHLIGHT_ENABLEAT 0.3 // The percentage of the Flashlight battery where the Flashlight will be able to be used again (if the player shortens out the Flashlight from excessive use). -#define SF2_FLASHLIGHT_COOLDOWN 0.4 // How much time players have to wait before being able to switch their flashlight on again after turning it off. - -char g_PlayerBreathSounds[][] = -{ - "slender/fastbreath1.wav" -}; //Client Special Round Timer static Handle g_ClientSpecialRoundTimer[MAXTF2PLAYERS]; -// Deathcam data. -static int g_PlayerDeathCamBoss[MAXTF2PLAYERS] = { -1, ... }; -static bool g_PlayerDeathCam[MAXTF2PLAYERS] = { false, ... }; -static bool g_PlayerDeathCamShowOverlay[MAXTF2PLAYERS] = { false, ... }; -int g_PlayerDeathCamEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerDeathCamEnt2[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerDeathCamTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static Handle g_PlayerDeathCamTimer[MAXTF2PLAYERS] = { null, ... }; -bool g_CameraInDeathCamAdvanced[2049] = { false, ... }; -float g_CameraPlayerOffsetBackward[2049] = { 0.0, ... }; -float g_CameraPlayerOffsetDownward[2049] = { 0.0, ... }; -static float g_vecPlayerOriginalDeathcamPosition[MAXTF2PLAYERS][3]; - -// Ultravision data. -bool g_PlayerHasUltravision[MAXTF2PLAYERS] = { false, ... }; -int g_PlayerUltravisionEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; - -// Sprint data. -static bool g_PlayerSprint[MAXTF2PLAYERS] = { false, ... }; -int g_PlayerSprintPoints[MAXTF2PLAYERS] = { 100, ... }; -Handle g_PlayerSprintTimer[MAXTF2PLAYERS] = { null, ... }; - -// Blink data. -static Handle g_PlayerBlinkTimer[MAXTF2PLAYERS] = { null, ... }; -static bool g_PlayerBlink[MAXTF2PLAYERS] = { false, ... }; -bool g_PlayerHoldingBlink[MAXTF2PLAYERS] = { false, ... }; -static float g_PlayerBlinkMeter[MAXTF2PLAYERS] = { 0.0, ... }; -static float g_TimeUntilUnblink[MAXTF2PLAYERS] = { 0.0, ... }; -static int g_PlayerBlinkCount[MAXTF2PLAYERS] = { 0, ... }; - -// Breathing data. -bool g_PlayerBreath[MAXTF2PLAYERS] = { false, ... }; -static Handle g_PlayerBreathTimer[MAXTF2PLAYERS] = { null, ... }; - // Interactive glow data. static int g_PlayerInteractiveGlowEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; static int g_PlayerInteractiveGlowTargetEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; @@ -80,12 +25,6 @@ static float g_PlayerJumpScareLifeTime[MAXTF2PLAYERS] = { -1.0, ... }; static float g_PlayerScareBoostEndTime[MAXTF2PLAYERS] = { -1.0, ... }; -// Anti-camping data. -int g_PlayerCampingStrikes[MAXTF2PLAYERS] = { 0, ... }; -Handle g_PlayerCampingTimer[MAXTF2PLAYERS] = { null, ... }; -float g_PlayerCampingLastPosition[MAXTF2PLAYERS][3]; -bool g_IsPlayerCampingFirstTime[MAXTF2PLAYERS]; - // Frame data int g_ClientMaxFrameDeathAnim[MAXTF2PLAYERS]; int g_ClientFrame[MAXTF2PLAYERS]; @@ -93,9 +32,20 @@ int g_ClientFrame[MAXTF2PLAYERS]; //Nav Data //static CNavArea g_lastNavArea[MAXTF2PLAYERS]; -static float g_ClientAllowedTimeNearEscape[MAXTF2PLAYERS]; -//Peeking Data -static bool g_PlayerPeeking[MAXTF2PLAYERS] = { false, ... }; +#include "sf2/client/hints.sp" +#include "sf2/client/think.sp" +#include "sf2/client/static.sp" +#include "sf2/client/blink.sp" +#include "sf2/client/deathcam.sp" +#include "sf2/client/ultravision.sp" +#include "sf2/client/flashlight.sp" +#include "sf2/client/sprint.sp" +#include "sf2/client/breathing.sp" +#include "sf2/client/peek.sp" +#include "sf2/client/ghostmode.sp" +#include "sf2/client/music.sp" +#include "sf2/client/proxy.sp" +#include "sf2/methodmaps.sp" // ========================================================== // GENERAL CLIENT HOOK FUNCTIONS @@ -117,33 +67,6 @@ MRESReturn Hook_ClientWantsLagCompensationOnEntity(int client, DHookReturn retur return MRES_Supercede; } -public Action CH_ShouldCollide(int ent1,int ent2, bool &result) -{ - SF2RoundState state = GetRoundState(); - if (state == SF2RoundState_Intro || state == SF2RoundState_Outro) - { - return Plugin_Continue; - } - - if (MaxClients >= ent1 > 0) - { - if (IsClientInGhostMode(ent1)) - { - result = false; - return Plugin_Changed; - } - } - if (MaxClients >= ent2 > 0) - { - if (IsClientInGhostMode(ent2)) - { - result = false; - return Plugin_Changed; - } - } - return Plugin_Continue; -} - public Action CH_PassFilter(int ent1,int ent2, bool &result) { SF2RoundState state = GetRoundState(); @@ -183,7 +106,7 @@ void ClientSetScareBoostEndTime(int client, float time) Action Hook_HealthKitOnTouch(int healthKit, int client) { - if (MaxClients >= client > 0 && IsClientInGame(client)) + if (IsValidClient(client)) { TFClassType class = TF2_GetPlayerClass(client); int classToInt = view_as(class); @@ -296,7 +219,7 @@ public Action TF2_CalcIsAttackCritical(int client, int weapon, char[] weaponName void Hook_ClientWeaponEquipPost(int client, int weapon) { - if (!IsValidClient(client) || !IsClientInGame(client) || !IsValidEdict(weapon)) + if (!IsValidClient(client) || !IsValidEdict(weapon)) { return; } @@ -319,42 +242,6 @@ Action Hook_TEFireBullets(const char[] te_name,const int[] players,int numClient return Plugin_Continue; } -void ClientResetStatic(int client) -{ - g_PlayerStaticMaster[client] = -1; - g_PlayerStaticTimer[client] = null; - g_PlayerStaticIncreaseRate[client] = 0.0; - g_PlayerStaticDecreaseRate[client] = 0.0; - g_PlayerLastStaticTimer[client] = null; - g_PlayerLastStaticTime[client] = 0.0; - g_PlayerLastStaticVolume[client] = 0.0; - g_PlayerInStaticShake[client] = false; - g_PlayerStaticShakeMaster[client] = -1; - g_PlayerStaticShakeMinVolume[client] = 0.0; - g_PlayerStaticShakeMaxVolume[client] = 0.0; - g_PlayerStaticAmount[client] = 0.0; - - if (IsClientInGame(client)) - { - if (g_PlayerStaticSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticSound[client]); - } - if (g_PlayerLastStaticSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); - } - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); - } - } - - g_PlayerStaticSound[client][0] = '\0'; - g_PlayerLastStaticSound[client][0] = '\0'; - g_PlayerStaticShakeSound[client][0] = '\0'; -} - bool DidClientEscape(int client) { return g_PlayerEscaped[client]; @@ -378,34 +265,17 @@ void ClientEscape(int client) g_PlayerPageCount[client] = 0; - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); ClientResetInteractiveGlow(client); ClientDisableConstantGlow(client); - ClientHandleGhostMode(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -463,6 +333,10 @@ void ClientEscape(int client) Call_PushCell(client); Call_Finish(); + Call_StartForward(g_OnPlayerEscapePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 1) { @@ -491,7 +365,7 @@ Action Timer_TeleportPlayerToEscapePoint(Handle timer, any userid) return Plugin_Stop; } -float ClientGetDistanceFromEntity(int client,int entity) +float ClientGetDistanceFromEntity(int client, int entity) { float startPos[3], endPos[3]; GetClientAbsOrigin(client, startPos); @@ -566,231 +440,9 @@ float ClientGetDefaultWalkSpeed(int client, TFClassType class = TFClass_Unknown) return returnFloat; } -float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknown) -{ - float returnFloat = 340.0; - float returnFloat2 = returnFloat; - Action action = Plugin_Continue; - if (IsValidClient(client)) - { - class = TF2_GetPlayerClass(client); - } - - switch (class) - { - case TFClass_Scout: - { - returnFloat = 305.0; - } - case TFClass_Sniper: - { - returnFloat = 295.0; - } - case TFClass_Soldier: - { - returnFloat = 280.0; - } - case TFClass_DemoMan: - { - returnFloat = 280.0; - } - case TFClass_Heavy: - { - returnFloat = 280.0; - } - case TFClass_Medic: - { - returnFloat = 290.0; - } - case TFClass_Pyro: - { - returnFloat = 290.0; - } - case TFClass_Spy: - { - returnFloat = 300.0; - } - case TFClass_Engineer: - { - returnFloat = 295.0; - } - } - - if (IsValidClient(client)) - { - // Call our forward. - Call_StartForward(g_OnClientGetDefaultSprintSpeedFwd); - Call_PushCell(client); - Call_PushCellRef(returnFloat2); - Call_Finish(action); - } - - if (action == Plugin_Changed) - { - returnFloat = returnFloat2; - } - - return returnFloat; -} - -// Static shaking should only affect the x, y portion of the player's view, not roll. -// This is purely for cosmetic effect. - -void ClientProcessStaticShake(int client) -{ - if (!IsClientInGame(client) || !IsPlayerAlive(client)) - { - return; - } - - bool oldStaticShake = g_PlayerInStaticShake[client]; - int oldStaticShakeMaster = NPCGetFromUniqueID(g_PlayerStaticShakeMaster[client]); - int newStaticShakeMaster = -1; - - float oldPunchAng[3], oldPunchAngVel[3]; - GetEntDataVector(client, g_PlayerPunchAngleOffset, oldPunchAng); - GetEntDataVector(client, g_PlayerPunchAngleOffsetVel, oldPunchAngVel); - - float newPunchAng[3], newPunchAngVel[3]; - - for (int i = 0; i < 3; i++) - { - newPunchAng[i] = oldPunchAng[i]; - newPunchAngVel[i] = oldPunchAngVel[i]; - } - - if (newStaticShakeMaster != -1) - { - g_PlayerStaticShakeMaster[client] = NPCGetUniqueID(newStaticShakeMaster); - - if (newStaticShakeMaster != oldStaticShakeMaster) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(newStaticShakeMaster, profile, sizeof(profile)); - - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); - } - - g_PlayerStaticShakeMinVolume[client] = GetBossProfileStaticShakeLocalVolumeMin(profile); - g_PlayerStaticShakeMaxVolume[client] = GetBossProfileStaticShakeLocalVolumeMax(profile); - - char staticSound[PLATFORM_MAX_PATH]; - GetBossProfileStaticShakeSound(profile, staticSound, sizeof(staticSound)); - if (staticSound[0] != '\0') - { - strcopy(g_PlayerStaticShakeSound[client], sizeof(g_PlayerStaticShakeSound[]), staticSound); - } - else - { - g_PlayerStaticShakeSound[client][0] = '\0'; - } - } - } - - if (g_PlayerInStaticShake[client]) - { - if (g_PlayerStaticAmount[client] <= 0.0) - { - g_PlayerInStaticShake[client] = false; - } - } - else - { - if (newStaticShakeMaster != -1) - { - g_PlayerInStaticShake[client] = true; - } - } - - if (g_PlayerInStaticShake[client] && !oldStaticShake) - { - for (int i = 0; i < 2; i++) - { - newPunchAng[i] = 0.0; - newPunchAngVel[i] = 0.0; - } - - SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); - SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); - } - else if (!g_PlayerInStaticShake[client] && oldStaticShake) - { - for (int i = 0; i < 2; i++) - { - newPunchAng[i] = 0.0; - newPunchAngVel[i] = 0.0; - } - - g_PlayerStaticShakeMaster[client] = -1; - - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); - } - - g_PlayerStaticShakeSound[client][0] = '\0'; - - g_PlayerStaticShakeMinVolume[client] = 0.0; - g_PlayerStaticShakeMaxVolume[client] = 0.0; - - SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); - SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); - } - - if (g_PlayerInStaticShake[client]) - { - if (g_PlayerStaticShakeSound[client][0] != '\0') - { - float volume = g_PlayerStaticAmount[client]; - if (GetRandomFloat(0.0, 1.0) <= 0.35) - { - volume = 0.0; - } - else - { - if (volume < g_PlayerStaticShakeMinVolume[client]) - { - volume = g_PlayerStaticShakeMinVolume[client]; - } - - if (volume > g_PlayerStaticShakeMaxVolume[client]) - { - volume = g_PlayerStaticShakeMaxVolume[client]; - } - } - - EmitSoundToClient(client, g_PlayerStaticShakeSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL | SND_STOP, volume); - } - - // Spazz our view all over the place. - for (int i = 0; i < 2; i++) - { - newPunchAng[i] = AngleNormalize(GetRandomFloat(0.0, 360.0)); - } - NormalizeVector(newPunchAng, newPunchAng); - - float angVelocityScalar = 5.0 * g_PlayerStaticAmount[client]; - if (angVelocityScalar < 1.0) - { - angVelocityScalar = 1.0; - } - ScaleVector(newPunchAng, angVelocityScalar); - - for (int i = 0; i < 2; i++) - { - newPunchAngVel[i] = 0.0; - } - - SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); - SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); - } -} - void ClientProcessVisibility(int client) { - if (!IsClientInGame(client) || !IsPlayerAlive(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client)) { return; } @@ -1326,200 +978,66 @@ void ClientProcessViewAngles(int client) } } -static Action Timer_ClientIncreaseStatic(Handle timer, any userid) +// ========================================================== +// SPECIAL ROUND FUNCTIONS +// ========================================================== + +void ClientSetSpecialRoundTimer(int client, float time, Timer callback, any data, int flags=0) +{ + g_ClientSpecialRoundTimer[client] = CreateTimer(time, callback, data, flags); +} + +Action Timer_ClientPageDetector(Handle timer, int userid) { - int client = GetClientOfUserId(userid); - if (client <= 0) + if (!SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) { return Plugin_Stop; } - - if (timer != g_PlayerStaticTimer[client]) + if (GetRoundState() == SF2RoundState_Escape) { return Plugin_Stop; } - g_PlayerStaticAmount[client] += 0.05; - if (g_PlayerStaticAmount[client] > 1.0) - { - g_PlayerStaticAmount[client] = 1.0; - } - - if (g_PlayerStaticSound[client][0] != '\0') - { - EmitSoundToClient(client, g_PlayerStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerStaticAmount[client]); - - if (g_PlayerStaticAmount[client] >= 0.5) - { - ClientAddStress(client, 0.03); - } - else - { - ClientAddStress(client, 0.02); - } - } - - return Plugin_Continue; -} - -Action Timer_ClientDecreaseStatic(Handle timer, any userid) -{ int client = GetClientOfUserId(userid); - if (client <= 0) + if (!IsValidClient(client)) { return Plugin_Stop; } - if (timer != g_PlayerStaticTimer[client]) + if (g_ClientSpecialRoundTimer[client] != timer) { return Plugin_Stop; } - g_PlayerStaticAmount[client] -= 0.05; - if (g_PlayerStaticAmount[client] < 0.0) + if (g_PlayerEliminated[client]) { - g_PlayerStaticAmount[client] = 0.0; + return Plugin_Stop; } - if (g_PlayerLastStaticSound[client][0] != '\0') - { - float volume = g_PlayerStaticAmount[client]; - if (volume > 0.0) - { - EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); - } - } + int closestPageEntIndex = -1; - if (g_PlayerStaticAmount[client] <= 0.0) + float distance = SquareFloat(99999.0); + float clientPos[3], pagePos[3]; + GetClientAbsOrigin(client, clientPos); + + ArrayList pageEntities = new ArrayList(); + GetPageEntities(pageEntities); + + for (int i = 0; i < pageEntities.Length; i++) { - // I've done my job; no point to keep on doing it. - if (g_PlayerLastStaticSound[client][0] != '\0') + CBaseEntity pageEnt = CBaseEntity(pageEntities.Get(i)); + pageEnt.GetAbsOrigin(pagePos); + + float squareDistance = GetVectorSquareMagnitude(clientPos, pagePos); + + if (closestPageEntIndex == -1 || squareDistance < distance) { - StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + closestPageEntIndex = pageEnt.index; + distance = squareDistance; } - g_PlayerStaticTimer[client] = null; - return Plugin_Stop; } - return Plugin_Continue; -} - -static Action Timer_ClientFadeOutLastStaticSound(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerLastStaticTimer[client]) - { - return Plugin_Stop; - } - - if (strcmp(g_PlayerLastStaticSound[client], g_PlayerStaticSound[client], false) == 0) - { - // Wait, the player's current static sound is the same one we're stopping. Abort! - g_PlayerLastStaticTimer[client] = null; - return Plugin_Stop; - } - - if (g_PlayerLastStaticSound[client][0] != '\0') - { - float diff = (GetGameTime() - g_PlayerLastStaticTime[client]) / 1.0; - if (diff > 1.0) - { - diff = 1.0; - } - - float volume = g_PlayerLastStaticVolume[client] - diff; - if (volume < 0.0) - { - volume = 0.0; - } - - if (volume <= 0.0) - { - // I've done my job; no point to keep on doing it. - StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); - g_PlayerLastStaticTimer[client] = null; - return Plugin_Stop; - } - else - { - EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); - } - } - else - { - // I've done my job; no point to keep on doing it. - g_PlayerLastStaticTimer[client] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -// ========================================================== -// SPECIAL ROUND FUNCTIONS -// ========================================================== - -void ClientSetSpecialRoundTimer(int client, float time, Timer callback, any data, int flags=0) -{ - g_ClientSpecialRoundTimer[client] = CreateTimer(time, callback, data, flags); -} - -Action Timer_ClientPageDetector(Handle timer, int userid) -{ - if (!SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) - { - return Plugin_Stop; - } - if (GetRoundState() == SF2RoundState_Escape) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - if (g_ClientSpecialRoundTimer[client] != timer) - { - return Plugin_Stop; - } - - if (!IsValidClient(client)) - { - return Plugin_Stop; - } - - if (g_PlayerEliminated[client]) - { - return Plugin_Stop; - } - - int closestPageEntIndex = -1; - - float distance = SquareFloat(99999.0); - float clientPos[3], pagePos[3]; - GetClientAbsOrigin(client, clientPos); - - ArrayList pageEntities = new ArrayList(); - GetPageEntities(pageEntities); - - for (int i = 0; i < pageEntities.Length; i++) - { - CBaseEntity pageEnt = CBaseEntity(pageEntities.Get(i)); - pageEnt.GetAbsOrigin(pagePos); - - float squareDistance = GetVectorSquareMagnitude(clientPos, pagePos); - - if (closestPageEntIndex == -1 || squareDistance < distance) - { - closestPageEntIndex = pageEnt.index; - distance = squareDistance; - } - } - - delete pageEntities; + delete pageEntities; float nextBeepTime = distance/SquareFloat(800.0); @@ -1543,7 +1061,7 @@ Action Timer_ClientPageDetector(Handle timer, int userid) void ClientProcessInteractiveGlow(int client) { - if (!IsClientInGame(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !g_PlayerProxy[client]) || IsClientInGhostMode(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !g_PlayerProxy[client]) || IsClientInGhostMode(client)) { return; } @@ -1625,7 +1143,7 @@ static bool ClientCreateInteractiveGlow(int client,int entity, const char[] atta { ClientRemoveInteractiveGlow(client); - if (!IsClientInGame(client)) + if (!IsValidClient(client)) { return false; } @@ -1715,1372 +1233,151 @@ static Action Hook_InterativeGlowSetTransmit(int ent,int other) } // ========================================================== -// BREATHING FUNCTIONS +// GHOST AND GLOW FUNCTIONS // ========================================================== -void ClientResetBreathing(int client) -{ - g_PlayerBreath[client] = false; - g_PlayerBreathTimer[client] = null; -} - -static float ClientCalculateBreathingCooldown(int client) -{ - float average = 0.0; - int averageNum = 0; - - // Sprinting only, for now. - average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow((float(g_PlayerSprintPoints[client]) / 100.0), 1.65)); - averageNum++; - - average /= float(averageNum); - - if (average < SF2_PLAYER_BREATH_COOLDOWN_MIN) - { - average = SF2_PLAYER_BREATH_COOLDOWN_MIN; - } - - return average; -} - -void ClientStartBreathing(int client) -{ - g_PlayerBreath[client] = true; - g_PlayerBreathTimer[client] = CreateTimer(ClientCalculateBreathingCooldown(client), Timer_ClientBreath, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); -} - -static void ClientStopBreathing(int client) -{ - g_PlayerBreath[client] = false; - g_PlayerBreathTimer[client] = null; -} - -bool ClientCanBreath(int client) -{ - return ClientCalculateBreathingCooldown(client) < SF2_PLAYER_BREATH_COOLDOWN_MAX; -} - -static Action Timer_ClientBreath(Handle timer, any userid) +Action Timer_ClassScramblePlayer(Handle timer, any userid) { - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerBreathTimer[client]) + if (!g_Enabled) { return Plugin_Stop; } - if (!g_PlayerBreath[client]) - { - return Plugin_Stop; - } + int client = GetClientOfUserId(userid); - if (ClientCanBreath(client)) + if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) { - EmitSoundToAll(g_PlayerBreathSounds[GetRandomInt(0, sizeof(g_PlayerBreathSounds) - 1)], client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - - ClientStartBreathing(client); return Plugin_Stop; } + g_PlayerRandomClassNumber[client] = GetRandomInt(1, 9); - ClientStopBreathing(client); + // Regenerate player but keep health the same. + int health = GetEntProp(client, Prop_Send, "m_iHealth"); + TF2_RegeneratePlayer(client); + SetEntProp(client, Prop_Data, "m_iHealth", health); + SetEntProp(client, Prop_Send, "m_iHealth", health); return Plugin_Stop; } -// ========================================================== -// SPRINTING FUNCTIONS -// ========================================================== - -bool IsClientSprinting(int client) -{ - return g_PlayerSprint[client]; -} - -int ClientGetSprintPoints(int client) -{ - return g_PlayerSprintPoints[client]; -} - -void ClientResetSprint(int client) +Action Timer_ClassScramblePlayer2(Handle timer, any userid) { - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) + if (!g_Enabled) { - DebugMessage("START ClientResetSprint(%d)", client); + return Plugin_Stop; } - #endif - - bool wasSprinting = IsClientSprinting(client); - g_PlayerSprint[client] = false; - g_PlayerSprintPoints[client] = 100; - g_PlayerSprintTimer[client] = null; + int client = GetClientOfUserId(userid); - if (IsValidClient(client)) + if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - - ClientSetFOV(client, g_PlayerDesiredFOV[client]); + return Plugin_Stop; } - if (wasSprinting) - { - Call_StartForward(g_OnClientStopSprintingFwd); - Call_PushCell(client); - Call_Finish(); - } + // Regenerate player but keep health the same. + int health = GetEntProp(client, Prop_Send, "m_iHealth"); + TF2_RegeneratePlayer(client); + SetEntProp(client, Prop_Data, "m_iHealth", health); + SetEntProp(client, Prop_Send, "m_iHealth", health); - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetSprint(%d)", client); - } - #endif + return Plugin_Stop; } -void ClientStartSprint(int client) +bool DoesClientHaveConstantGlow(int client) { - if (IsClientSprinting(client)) - { - return; - } - - g_PlayerSprint[client] = true; - g_PlayerSprintTimer[client] = null; - ClientSprintTimer(client); - TriggerTimer(g_PlayerSprintTimer[client], true); - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) - { - Client90sMusicStart(client); - } - - SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - - Call_StartForward(g_OnClientStartSprintingFwd); - Call_PushCell(client); - Call_Finish(); + return g_PlayerConstantGlowEnabled[client]; } -void ClientSprintTimer(int client, bool recharge=false) +void ClientDisableConstantGlow(int client) { - float rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 0.38 : 0.28; - if (recharge) + if (!DoesClientHaveConstantGlow(client)) { - rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 1.4 : 0.8; + return; } - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - float velocity[3]; - GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); - - if (recharge) - { - if (!(GetEntityFlags(client) & FL_ONGROUND)) - { - rate *= 0.75; - } - else if (GetVectorLength(velocity, true) == 0.0) - { - if (GetEntProp(client, Prop_Send, "m_bDucked")) - { - rate *= 0.66; - } - else - { - rate *= 0.75; - } - } - } - else + int glow = EntRefToEntIndex(g_PlayerGlowEntity[client]); + if (glow && glow != INVALID_ENT_REFERENCE) { - if (!IsClassConfigsValid()) - { - if (class == TFClass_DemoMan) - { - rate *= 1.15; - } - else if (class == TFClass_Medic || class == TFClass_Spy) - { - rate *= 1.05; - } - else if (class == TFClass_Scout) - { - rate *= 1.08; - } - } - else - { - rate *= g_ClassSprintDurationMultipler[classToInt]; - } + RemoveEntity(glow); + g_PlayerGlowEntity[client] = INVALID_ENT_REFERENCE; } - if (recharge) - { - g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientRechargeSprint, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientSprinting, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } + g_PlayerConstantGlowEnabled[client] = false; } -void ClientStopSprint(int client) +void ClientEnableConstantGlow(int client, int color[4] = {255, 255, 255, 255}) { - if (!IsClientSprinting(client)) + if (!IsValidClient(client)) { return; } - g_PlayerSprint[client] = false; - g_PlayerSprintTimer[client] = null; - ClientSprintTimer(client, true); - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) - { - Client90sMusicStop(client); - } - - SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - - Call_StartForward(g_OnClientStopSprintingFwd); - Call_PushCell(client); - Call_Finish(); -} - -bool IsClientReallySprinting(int client) -{ - if (!IsClientSprinting(client)) - { - return false; - } - if (!(GetEntityFlags(client) & FL_ONGROUND)) - { - return false; - } - float velocity[3]; - GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); - if (GetVectorLength(velocity, true) < SquareFloat(30.0)) + if (DoesClientHaveConstantGlow(client) || g_PlayerGlowEntity[client] != INVALID_ENT_REFERENCE) { - return false; + return; } - return true; -} - -// ========================================================== -// GHOST AND GLOW FUNCTIONS -// ========================================================== + char model[PLATFORM_MAX_PATH]; + GetEntPropString(client, Prop_Data, "m_ModelName", model, sizeof(model)); -Action Timer_ClassScramblePlayer(Handle timer, any userid) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - - if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) - { - return Plugin_Stop; - } - g_PlayerRandomClassNumber[client] = GetRandomInt(1, 9); - - // Regenerate player but keep health the same. - int health = GetEntProp(client, Prop_Send, "m_iHealth"); - TF2_RegeneratePlayer(client); - SetEntProp(client, Prop_Data, "m_iHealth", health); - SetEntProp(client, Prop_Send, "m_iHealth", health); - - return Plugin_Stop; -} - -Action Timer_ClassScramblePlayer2(Handle timer, any userid) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - - if (client <= 0 || DidClientEscape(client) || g_PlayerEliminated[client] || !IsPlayerAlive(client) || IsClientInGhostMode(client) || g_PlayerProxy[client]) - { - return Plugin_Stop; - } - - // Regenerate player but keep health the same. - int health = GetEntProp(client, Prop_Send, "m_iHealth"); - TF2_RegeneratePlayer(client); - SetEntProp(client, Prop_Data, "m_iHealth", health); - SetEntProp(client, Prop_Send, "m_iHealth", health); - - return Plugin_Stop; -} - -bool DoesClientHaveConstantGlow(int client) -{ - return g_PlayerConstantGlowEnabled[client]; -} - -void ClientDisableConstantGlow(int client) -{ - if (!DoesClientHaveConstantGlow(client)) - { - return; - } - - int glow = EntRefToEntIndex(g_PlayerGlowEntity[client]); - if (glow != INVALID_ENT_REFERENCE) - { - RemoveEntity(glow); - g_PlayerGlowEntity[client] = INVALID_ENT_REFERENCE; - } - - g_PlayerConstantGlowEnabled[client] = false; -} - -void ClientEnableConstantGlow(int client, int color[4] = {255, 255, 255, 255}) -{ - if (!IsValidClient(client)) - { - return; - } - - if (DoesClientHaveConstantGlow(client) || g_PlayerGlowEntity[client] != INVALID_ENT_REFERENCE) - { - return; - } - - char model[PLATFORM_MAX_PATH]; - GetEntPropString(client, Prop_Data, "m_ModelName", model, sizeof(model)); - - if (model[0] == '\0') + if (model[0] == '\0') { // For some reason the model couldn't be found, so no. - return; - } - - int glow = TF2_CreateGlow(client); - if (IsValidEntity(glow)) - { - g_PlayerConstantGlowEnabled[client] = true; - - SDKHook(glow, SDKHook_SetTransmit, Hook_ConstantGlowSetTransmit); - - g_PlayerGlowEntity[client] = EntIndexToEntRef(glow); - //Set our desired glow color - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); - g_DHookShouldTransmit.HookEntity(Hook_Pre, glow, Hook_EntityShouldTransmit); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow, Hook_GlowUpdateTransmitState); - } -} - -void ClientResetJumpScare(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetJumpScare(%d)", client); - } - #endif - - g_PlayerJumpScareBoss[client] = -1; - g_PlayerJumpScareLifeTime[client] = -1.0; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetJumpScare(%d)", client); - } - #endif -} - -void ClientDoJumpScare(int client,int bossIndex, float flLifeTime) -{ - g_PlayerJumpScareBoss[client] = NPCGetUniqueID(bossIndex); - g_PlayerJumpScareLifeTime[client] = GetGameTime() + flLifeTime; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char buffer[PLATFORM_MAX_PATH]; - GetBossProfileJumpscareSound(profile, buffer, sizeof(buffer)); - - if (buffer[0] != '\0') - { - EmitSoundToClient(client, buffer, _, MUSIC_CHAN); - } -} - - /** - * Handles sprinting upon player input. - */ -void ClientHandleSprint(int client, bool sprint) -{ - if (!IsPlayerAlive(client) || - g_PlayerEliminated[client] || - DidClientEscape(client) || - g_PlayerProxy[client] || - IsClientInGhostMode(client)) - { - return; - } - - if (sprint) - { - if (g_PlayerSprintPoints[client] > 0) - { - ClientStartSprint(client); - } - else - { - EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); - } - } - else - { - if (IsClientSprinting(client)) - { - ClientStopSprint(client); - } - } -} -/** - * Handles thirdperson peeking - */ -bool ClientStartPeeking(int client) -{ - if (!g_PlayerPeeking[client] && g_AllowPlayerPeekingConVar.BoolValue && !TF2_IsPlayerInCondition(client, TFCond_Dazed) && GetClientButtons(client) & IN_DUCK) - { - TF2_StunPlayer(client, 999.9, 1.0, TF_STUNFLAGS_LOSERSTATE); - g_PlayerPeeking[client] = true; - return true; - } - return false; -} - -void ClientEndPeeking(int client) -{ - if (g_PlayerPeeking[client]) - { - TF2_RemoveCondition(client, TFCond_Dazed); - g_PlayerPeeking[client] = false; - } -} - -// ========================================================== -// DEATH CAM FUNCTIONS -// ========================================================== - -bool IsClientInDeathCam(int client) -{ - return g_PlayerDeathCam[client]; -} - -static Action Hook_DeathCamSetTransmit(int slender,int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - if (EntRefToEntIndex(g_PlayerDeathCamEnt2[other]) != slender) - { - return Plugin_Handled; - } - return Plugin_Continue; -} - -void ClientResetDeathCam(int client) -{ - if (!IsClientInDeathCam(client)) - { - return; // no really need to reset if it wasn't set. - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetDeathCam(%d)", client); - } - #endif - - int deathCamBoss = NPCGetFromUniqueID(g_PlayerDeathCamBoss[client]); - - g_PlayerDeathCamBoss[client] = -1; - g_PlayerDeathCam[client] = false; - g_PlayerDeathCamShowOverlay[client] = false; - g_PlayerDeathCamTimer[client] = null; - - int ent = EntRefToEntIndex(g_PlayerDeathCamEnt[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - g_CameraInDeathCamAdvanced[ent] = false; - AcceptEntityInput(ent, "Disable"); - RemoveEntity(ent); - } - - ent = EntRefToEntIndex(g_PlayerDeathCamEnt2[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - AcceptEntityInput(ent, "Kill"); - } - - ent = EntRefToEntIndex(g_PlayerDeathCamTarget[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - RemoveEntity(ent); - } - - g_PlayerDeathCamEnt[client] = INVALID_ENT_REFERENCE; - g_PlayerDeathCamEnt2[client] = INVALID_ENT_REFERENCE; - g_PlayerDeathCamTarget[client] = INVALID_ENT_REFERENCE; - - if (IsClientInGame(client)) - { - SetClientViewEntity(client, client); - } - - Call_StartForward(g_OnClientEndDeathCamFwd); - Call_PushCell(client); - Call_PushCell(deathCamBoss); - Call_Finish(); - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetDeathCam(%d)", client); - } - #endif -} - -void ClientStartDeathCam(int client,int bossIndex, const float vecLookPos[3], bool antiCamp = false) -{ - if (IsClientInDeathCam(client)) - { - return; - } - if (!NPCIsValid(bossIndex)) - { - return; - } - - for (int npcIndex; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - switch (NPCGetType(npcIndex)) - { - case SF2BossType_Chaser: - { - if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderGiveUp[npcIndex] = true; - } - } - } - } - - GetClientAbsOrigin(client, g_vecPlayerOriginalDeathcamPosition[client]); - - char buffer[PLATFORM_MAX_PATH]; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - if (g_SlenderDeathCamScareSound[bossIndex]) - { - GetBossProfileScareSounds(profile, soundInfo); - soundInfo.EmitSound(true, client); - } - - GetBossProfileClientDeathCamSounds(profile, soundInfo); - soundInfo.EmitSound(true, client); - - GetBossProfileGlobalDeathCamSounds(profile, soundInfo); - for (int i = 0; i < MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - soundInfo.EmitSound(true, i); - } - - // Call our forward. - Call_StartForward(g_OnClientCaughtByBossFwd); - Call_PushCell(client); - Call_PushCell(bossIndex); - Call_Finish(); - - if (!NPCHasDeathCamEnabled(bossIndex) && !(NPCGetFlags(bossIndex) & SFF_FAKE)) - { - SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. - - float value = NPCGetAttributeValue(bossIndex, SF2Attribute_IgnitePlayerOnDeath); - if (value > 0.0) - { - TF2_IgnitePlayer(client, client); - } - - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt > MaxClients) - { - SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - } - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. - KillClient(client); - return; - } - else if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - } - - g_PlayerDeathCamBoss[client] = NPCGetUniqueID(bossIndex); - g_PlayerDeathCam[client] = true; - g_PlayerDeathCamShowOverlay[client] = false; - - float eyePos[3], eyeAng[3], vecAng[3]; - GetClientEyePosition(client, eyePos); - GetClientEyeAngles(client, eyeAng); - SubtractVectors(eyePos, vecLookPos, vecAng); - GetVectorAngles(vecAng, vecAng); - vecAng[0] = 0.0; - vecAng[2] = 0.0; - - // Create fake model. - int slender = SpawnSlenderModel(bossIndex, vecLookPos, true); - TeleportEntity(slender, vecLookPos, vecAng, NULL_VECTOR); - g_PlayerDeathCamEnt2[client] = EntIndexToEntRef(slender); - if (!g_SlenderPublicDeathCam[bossIndex]) - { - SDKHook(slender, SDKHook_SetTransmit, Hook_DeathCamSetTransmit); - } - else - { - GetBossProfileLocalDeathCamSounds(profile, soundInfo); - soundInfo.EmitSound(_, slender); - SetEntityMoveType(client, MOVETYPE_NOCLIP); - if (!antiCamp) - { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderInDeathcam[bossIndex] = true; - SetEntityRenderMode(slenderEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(slenderEnt, g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], 0); - NPCChaserUpdateBossAnimation(bossIndex, slenderEnt, STATE_IDLE); - g_SlenderDeathCamTarget[bossIndex] = EntIndexToEntRef(client); - if (g_SlenderEntityThink[bossIndex] != null) - { - KillTimer(g_SlenderEntityThink[bossIndex]); - } - g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderPublicDeathCamThink, EntIndexToEntRef(slenderEnt), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - } - } - - // Create camera look point. - char name[64]; - FormatEx(name, sizeof(name), "sf2_boss_%d", EntIndexToEntRef(slender)); - - float offsetPos[3]; - int target = CreateEntityByName("info_target"); - if (!g_SlenderPublicDeathCam[bossIndex]) - { - GetBossProfileDeathCamPosition(profile, offsetPos); - AddVectors(vecLookPos, offsetPos, offsetPos); - TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); - DispatchKeyValue(target, "targetname", name); - SetVariantString("!activator"); - AcceptEntityInput(target, "SetParent", slender); - } - else - { - char boneName[PLATFORM_MAX_PATH]; - AddVectors(vecLookPos, offsetPos, offsetPos); - TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); - DispatchKeyValue(target, "targetname", name); - SetVariantString("!activator"); - AcceptEntityInput(target, "SetParent", slender); - GetBossProfilePublicDeathCamTargetAttachment(profile, boneName, sizeof(boneName)); - if (boneName[0] != '\0') - { - SetVariantString(boneName); - AcceptEntityInput(target, "SetParentAttachment"); - } - } - g_PlayerDeathCamTarget[client] = EntIndexToEntRef(target); - - // Create the camera itself. - int camera = CreateEntityByName("point_viewcontrol"); - TeleportEntity(camera, eyePos, eyeAng, NULL_VECTOR); - DispatchKeyValue(camera, "spawnflags", "12"); - DispatchKeyValue(camera, "target", name); - DispatchSpawn(camera); - AcceptEntityInput(camera, "Enable", client); - g_PlayerDeathCamEnt[client] = EntIndexToEntRef(camera); - if (g_SlenderPublicDeathCam[bossIndex]) - { - float camSpeed, camAcceleration, camDeceleration; - - camSpeed = g_SlenderPublicDeathCamSpeed[bossIndex]; - camAcceleration = g_SlenderPublicDeathCamAcceleration[bossIndex]; - camDeceleration = g_SlenderPublicDeathCamDeceleration[bossIndex]; - FloatToString(camSpeed, buffer, sizeof(buffer)); - DispatchKeyValue(camera, "acceleration", buffer); - FloatToString(camAcceleration, buffer, sizeof(buffer)); - DispatchKeyValue(camera, "deceleration", buffer); - FloatToString(camDeceleration, buffer, sizeof(buffer)); - DispatchKeyValue(camera, "speed", buffer); - - SetVariantString("!activator"); - AcceptEntityInput(camera, "SetParent", slender); - char attachmentName[PLATFORM_MAX_PATH]; - GetBossProfilePublicDeathCamAttachment(profile, attachmentName, sizeof(attachmentName)); - if (attachmentName[0] != '\0') - { - SetVariantString(attachmentName); - AcceptEntityInput(camera, "SetParentAttachment"); - } - - g_CameraInDeathCamAdvanced[camera] = true; - g_CameraPlayerOffsetBackward[camera] = g_SlenderPublicDeathCamBackwardOffset[bossIndex]; - g_CameraPlayerOffsetDownward[camera] = g_SlenderPublicDeathCamDownwardOffset[bossIndex]; - RequestFrame(Frame_PublicDeathCam, camera); //Resend taunt sound to eliminated players only - } - - if (g_SlenderDeathCamOverlay[bossIndex] && g_SlenderDeathCamOverlayTimeStart[bossIndex] >= 0.0) - { - if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) - { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_BossDeathCamDelay, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); - } - } - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_ClientResetDeathCam1, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) - { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_BossDeathCamDuration, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); - } - } - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_ClientResetDeathCamEnd, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - - TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); - - Call_StartForward(g_OnClientStartDeathCamFwd); - Call_PushCell(client); - Call_PushCell(bossIndex); - Call_Finish(); -} - -static void Frame_PublicDeathCam(int cameraRef) -{ - int camera = EntRefToEntIndex(cameraRef); - if (IsValidEntity(camera)) - { - int slender = GetEntPropEnt(camera, Prop_Data, "m_hOwnerEntity"); - int client = GetEntPropEnt(camera, Prop_Data, "m_hPlayer"); - if (IsValidEntity(slender) && IsValidClient(client)) - { - float camPos[3], camAngs[3]; - GetEntPropVector(camera, Prop_Data, "m_angAbsRotation", camAngs); - GetEntPropVector(camera, Prop_Data, "m_vecAbsOrigin", camPos); - - camPos[0] -= g_CameraPlayerOffsetBackward[camera]; - camPos[2] -= g_CameraPlayerOffsetDownward[camera]; - - TeleportEntity(client, camPos, camAngs, NULL_VECTOR); - - RequestFrame(Frame_PublicDeathCam, EntIndexToEntRef(cameraRef)); - } - } -} - -static Action Timer_ClientResetDeathCam1(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerDeathCamTimer[client]) - { - return Plugin_Stop; - } - - SF2NPC_BaseNPC Npc = view_as(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - if (Npc.IsValid()) - { - g_PlayerDeathCamShowOverlay[client] = true; - Npc.GetProfile(profile, sizeof(profile)); - SF2BossProfileSoundInfo soundInfo; - GetBossProfilePlayerDeathcamOverlaySounds(profile, soundInfo); - soundInfo.EmitSound(true, client); - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_ClientResetDeathCamEnd, userid, TIMER_FLAG_NO_MAPCHANGE); - } - - return Plugin_Stop; -} - -static Action Timer_BossDeathCamDelay(Handle timer, any entref) -{ - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(slender)); - - if (timer != g_SlenderDeathCamTimer[Npc.Index]) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.GetProfile(profile, sizeof(profile)); - - g_SlenderDeathCamTimer[Npc.Index] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_BossDeathCamDuration, slender, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Stop; -} - -static Action Timer_BossDeathCamDuration(Handle timer, any entref) -{ - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_Chaser Npc = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); - - if (timer != g_SlenderDeathCamTimer[Npc.Index]) - { - return Plugin_Stop; - } - - if (g_SlenderInDeathcam[Npc.Index]) - { - SetEntityRenderMode(slender, RENDER_NORMAL); - if (!Npc.CloakEnabled) - { - SetEntityRenderColor(slender, Npc.GetRenderColor(0), Npc.GetRenderColor(1), Npc.GetRenderColor(2), Npc.GetRenderColor(3)); - } - g_SlenderEntityThink[Npc.Index] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - if (!(Npc.Flags & SFF_FAKE)) - { - g_SlenderInDeathcam[Npc.Index] = false; - } - Npc.UpdateAnimation(slender, Npc.State); - } - if ((Npc.Flags & SFF_FAKE)) - { - if (g_SlenderInDeathcam[Npc.Index]) - { - g_SlenderInDeathcam[Npc.Index] = false; - } - Npc.MarkAsFake(); - } - g_SlenderDeathCamTimer[Npc.Index] = null; - - return Plugin_Stop; -} - -static Action Timer_ClientResetDeathCamEnd(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerDeathCamTimer[client]) - { - return Plugin_Stop; - } - - SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol entity changes our damage state. - - SF2NPC_BaseNPC deathCamBoss = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); - if (deathCamBoss != SF2_INVALID_NPC) - { - float value = deathCamBoss.GetAttributeValue(SF2Attribute_IgnitePlayerOnDeath); - if (value > 0.0) - { - TF2_IgnitePlayer(client, client); - } - if (!(deathCamBoss.Flags & SFF_FAKE)) - { - int slenderEnt = deathCamBoss.EntIndex; - if (slenderEnt > MaxClients) - { - SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - } - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. - KillClient(client); - } - else - { - SetEntityMoveType(client, MOVETYPE_WALK); - TeleportEntity(client, g_vecPlayerOriginalDeathcamPosition[client], NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); - } - } - else//The boss is invalid? But the player got a death cam? - { - //Then kill him anyways. - KillClient(client); - ForcePlayerSuicide(client); - } - ClientResetDeathCam(client); - - return Plugin_Stop; -} -// ========================================================== -// NAV AREA FUNCTIONS -// ========================================================== -/*void ClientNavAreaUpdate(int client, CNavArea newArea, CNavArea oldArea) -{ - if (GetRoundState() != SF2RoundState_Active) return; - - if (g_PlayerEliminated[client]) return; - - if (newArea == INVALID_NAV_AREA) return; - - if ((oldArea != INVALID_NAV_AREA && oldArea.Attributes & NAV_MESH_DONT_HIDE) || newArea.Attributes & NAV_MESH_DONT_HIDE) - { - g_ClientAllowedTimeNearEscape[client] -= 0.3;//Remove 0.3sec this function is called every ~0.3sec - } - else - { - g_ClientAllowedTimeNearEscape[client] += 0.1;//Forgive the player of 0.1 - } - if (g_ClientAllowedTimeNearEscape[client] <= 0.0 && SF_IsSurvivalMap()) - { - g_PlayerIsExitCamping[client] = true; - } - else - { - g_PlayerIsExitCamping[client] = false; - } - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_NAV, 1, "Old area: %i DHF:%s, New area: %i DHF:%s, is considered as exit camper: %s", oldArea.Index, (oldArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", newArea.Index, (newArea.Attributes & NAV_MESH_DONT_HIDE) ? "true" : "false", (g_PlayerIsExitCamping[client]) ? "true" : "false" ); - #endif -}*/ - -// ========================================================== -// GHOST MODE FUNCTIONS -// ========================================================== - -static bool g_PlayerInGhostMode[MAXTF2PLAYERS] = { false, ... }; -static int g_PlayerGhostModeTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerGhostModeBossTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -Handle g_PlayerGhostModeConnectionCheckTimer[MAXTF2PLAYERS] = { null, ... }; -float g_PlayerGhostModeConnectionTimeOutTime[MAXTF2PLAYERS] = { -1.0, ... }; -float g_PlayerGhostModeConnectionBootTime[MAXTF2PLAYERS] = { -1.0, ... }; - -/** - * Enables/Disables ghost mode on the player. - */ -void ClientSetGhostModeState(int client, bool state) -{ - if (state == g_PlayerInGhostMode[client]) - { - return; - } - - Handle message = StartMessageAll("PlayerTauntSoundLoopEnd", USERMSG_RELIABLE); - BfWriteByte(message, client); - delete message; - EndMessage(); - - if (state && !IsClientInGame(client)) - { - return; - } - - g_PlayerInGhostMode[client] = state; - g_PlayerGhostModeTarget[client] = INVALID_ENT_REFERENCE; - g_PlayerGhostModeBossTarget[client] = INVALID_ENT_REFERENCE; - - if (state) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Entered ghost mode."); - #endif - //Strip the always edict flag - SetEntityFlags(client,GetEntityFlags(client)^FL_EDICT_ALWAYS); - //Remove the fire cond - TF2_RemoveCondition(client,TFCond_OnFire); - //Call the spawn event. - TF2_RespawnPlayer(client); - TF2_RemoveCondition(client,TFCond_Taunting); - - TFClassType desiredClass = TF2_GetPlayerClass(client); - if (desiredClass == TFClass_Unknown) - { - desiredClass = TFClass_Spy; - } - - //Set player's class to spy, this replaces old ghost mode mechanics. - TF2_SetPlayerClass(client, TFClass_Spy); - TF2_RegeneratePlayer(client); - - //Set player's old class as desired class. - SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", desiredClass); - - ClientHandleGhostMode(client, true); - if (g_GhostModeConnectionCheckConVar.BoolValue) - { - g_PlayerGhostModeConnectionCheckTimer[client] = CreateTimer(0.0, Timer_GhostModeConnectionCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; - g_PlayerGhostModeConnectionBootTime[client] = -1.0; - } - - PvP_OnClientGhostModeEnable(client); - } - else - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Exited ghost mode."); - #endif - TF2Attrib_SetByName(client, "mod see enemy health", 0.0); - g_PlayerGhostModeConnectionCheckTimer[client] = null; - g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; - g_PlayerGhostModeConnectionBootTime[client] = -1.0; - - if (IsClientInGame(client)) - { - SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_YES); - TF2_RemoveCondition(client, TFCond_Stealthed); - SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 1); - SetEntityGravity(client, 1.0); - SetEntProp(client, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_PLAYER); - SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); - SetEntityFlags(client, GetEntityFlags(client) &~ FL_NOTARGET); - SetEntProp(client, Prop_Data, "m_usSolidFlags", 16); - SetEntProp(client, Prop_Send, "m_nSolidType", 2); - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - SetEntityRenderMode(client, RENDER_NORMAL); - SetEntityRenderColor(client, _, _, _, 255); - } - } - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - if (!NPCGetRainbowOutlineState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - SlenderAddGlow(npcIndex, view_as({0, 0, 0, 0})); - } - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } - } - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } - } -} - -/** - * Makes sure that the player is a ghost when ghost mode is activated. - */ -void ClientHandleGhostMode(int client, bool forceSpawn=false) -{ - if (!IsClientInGhostMode(client)) - { - return; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientHandleGhostMode(%d, %d)", client, forceSpawn); - } - #endif - - if (!TF2_IsPlayerInCondition(client, TFCond_Stealthed) || forceSpawn) - { - TF2_StripWearables(client); - DestroyAllActiveWeapons(client); - TF2_DestroySpyWeapons(); - SetEntityGravity(client, 0.5); - TF2_AddCondition(client, TFCond_Stealthed, -1.0); - SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 0); - SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_NO); - SetEntProp(client, Prop_Send, "m_usSolidFlags", 4); - SetEntProp(client, Prop_Data, "m_nSolidType", 0); - SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); - SetEntProp(client, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); - SetEntityFlags(client, GetEntityFlags(client) | FL_NOTARGET); - SetEntityRenderMode(client, RENDER_TRANSCOLOR); - SetEntityRenderColor(client, _, _, _, 0); - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - - // Set first observer target. - ClientGhostModeNextTarget(client, true); - ClientActivateUltravision(client); - - // screen overlay timer - g_PlayerOverlayCheck[client] = CreateTimer(0.0, Timer_PlayerOverlayCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerOverlayCheck[client], true); - - CreateTimer(0.2, Timer_ClientGhostStripWearables, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientHandleGhostMode(%d, %d)", client, forceSpawn); - } - #endif -} - -static Action Timer_ClientGhostStripWearables(Handle timer, int userid) -{ - int client = GetClientOfUserId(userid); - if (!IsValidClient(client)) - { - return Plugin_Stop; + return; } - if (!IsClientInGhostMode(client)) + + int glow = TF2_CreateGlow(client); + if (IsValidEntity(glow)) { - return Plugin_Stop; + g_PlayerConstantGlowEnabled[client] = true; + + SDKHook(glow, SDKHook_SetTransmit, Hook_ConstantGlowSetTransmit); + + g_PlayerGlowEntity[client] = EntIndexToEntRef(glow); + //Set our desired glow color + SetVariantColor(color); + AcceptEntityInput(glow, "SetGlowColor"); + SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); + g_DHookShouldTransmit.HookEntity(Hook_Pre, glow, Hook_EntityShouldTransmit); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow, Hook_GlowUpdateTransmitState); } - TF2_StripWearables(client); - DestroyAllActiveWeapons(client); - TF2_DestroySpyWeapons(); - return Plugin_Stop; } -void ClientGhostModeNextTarget(int client, bool ignoreSetting = false) +void ClientResetJumpScare(int client) { #if defined DEBUG if (g_DebugDetailConVar.IntValue > 2) { - DebugMessage("START ClientGhostModeNextTarget(%d)", client); + DebugMessage("START ClientResetJumpScare(%d)", client); } #endif - if (g_PlayerPreferences[client].PlayerPreference_GhostModeTeleportState == 0 || ignoreSetting) - { - int lastTarget = EntRefToEntIndex(g_PlayerGhostModeTarget[client]); - int nextTarget = -1; - int firstTarget = -1; - for (int i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && (!g_PlayerEliminated[i] || g_PlayerProxy[i]) && !IsClientInGhostMode(i) && !DidClientEscape(i) && IsPlayerAlive(i)) - { - if (firstTarget == -1) - { - firstTarget = i; - } - if (i > lastTarget) - { - nextTarget = i; - break; - } - } - } - - int target = -1; - if (IsValidClient(nextTarget)) - { - target = nextTarget; - } - else - { - target = firstTarget; - } - - if (IsValidClient(target)) - { - g_PlayerGhostModeTarget[client] = EntIndexToEntRef(target); - - float pos[3], ang[3], velocity[3]; - GetClientAbsOrigin(target, pos); - GetClientEyeAngles(target, ang); - GetEntPropVector(target, Prop_Data, "m_vecAbsVelocity", velocity); - TeleportEntity(client, pos, ang, velocity); - } + g_PlayerJumpScareBoss[client] = -1; + g_PlayerJumpScareLifeTime[client] = -1.0; - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientGhostModeNextTarget(%d)", client); - } - #endif - } - else + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) { - int lastTarget = NPCGetFromEntIndex(EntRefToEntIndex(g_PlayerGhostModeBossTarget[client])); - int nextTarget = -1; - int firstTarget = -1; - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1 || !IsValidEntity(NPCGetEntIndex(bossIndex))) - { - continue; - } - - if (firstTarget == -1) - { - firstTarget = bossIndex; - } - if (bossIndex > lastTarget) - { - nextTarget = bossIndex; - break; - } - } - - int target = -1; - if (nextTarget != - 1 && NPCGetEntIndex(nextTarget) && NPCGetEntIndex(nextTarget) != INVALID_ENT_REFERENCE) - { - target = nextTarget; - } - else - { - target = firstTarget; - } - - if (target != -1 && IsValidEntity(NPCGetEntIndex(target))) - { - g_PlayerGhostModeBossTarget[client] = EntIndexToEntRef(NPCGetEntIndex(target)); - - float pos[3], ang[3], velocity[3]; - GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsOrigin", pos); - GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_angAbsRotation", ang); - GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsVelocity", velocity); - TeleportEntity(client, pos, ang, velocity); - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientGhostModeNextTarget(%d)", client); - } - #endif + DebugMessage("END ClientResetJumpScare(%d)", client); } + #endif } -bool IsClientInGhostMode(int client) +void ClientDoJumpScare(int client,int bossIndex, float flLifeTime) { - return g_PlayerInGhostMode[client]; -} + g_PlayerJumpScareBoss[client] = NPCGetUniqueID(bossIndex); + g_PlayerJumpScareLifeTime[client] = GetGameTime() + flLifeTime; -Action Hook_GhostNoTouch(int entity, int other) -{ - if (0 < other <= MaxClients && IsClientInGame(other)) + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + char buffer[PLATFORM_MAX_PATH]; + GetBossProfileJumpscareSound(profile, buffer, sizeof(buffer)); + + if (buffer[0] != '\0') { - if (IsClientInGhostMode(other)) - { - return Plugin_Handled; - } + EmitSoundToClient(client, buffer, _, MUSIC_CHAN); } - return Plugin_Continue; } // ========================================================== @@ -3223,267 +1520,6 @@ void ClientResetScare(int client) #endif } -// ========================================================== -// ANTI-CAMPING FUNCTIONS -// ========================================================== - -void ClientResetCampingStats(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetCampingStats(%d)", client); - } - #endif - - g_PlayerCampingStrikes[client] = 0; - g_PlayerIsExitCamping[client] = false; - g_PlayerCampingTimer[client] = null; - g_IsPlayerCampingFirstTime[client] = true; - g_PlayerCampingLastPosition[client][0] = 0.0; - g_PlayerCampingLastPosition[client][1] = 0.0; - g_PlayerCampingLastPosition[client][2] = 0.0; - g_ClientAllowedTimeNearEscape[client] = g_ExitCampingTimeAllowedConVar.FloatValue; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetCampingStats(%d)", client); - } - #endif -} - -void ClientStartCampingTimer(int client) -{ - g_PlayerCampingTimer[client] = CreateTimer(5.0, Timer_ClientCheckCamp, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} - -// ========================================================== -// BLINK FUNCTIONS -// ========================================================== - -bool IsClientBlinking(int client) -{ - return g_PlayerBlink[client]; -} - -float ClientGetBlinkMeter(int client) -{ - return g_PlayerBlinkMeter[client]; -} - -void ClientSetBlinkMeter(int client, float amount) -{ - g_PlayerBlinkMeter[client] = amount; -} - -int ClientGetBlinkCount(int client) -{ - return g_PlayerBlinkCount[client]; -} - -/** - * Resets all data on blinking. - */ -void ClientResetBlink(int client) -{ - g_PlayerBlinkTimer[client] = null; - g_PlayerBlink[client] = false; - g_PlayerHoldingBlink[client] = false; - g_TimeUntilUnblink[client] = 0.0; - g_PlayerBlinkMeter[client] = 1.0; - g_PlayerBlinkCount[client] = 0; -} - -/** - * Sets the player into a blinking state and blinds the player - */ -void ClientBlink(int client) -{ - if (IsRoundInWarmup() || DidClientEscape(client)) - { - return; - } - - if (IsClientBlinking(client)) - { - return; - } - - if (SF_IsRaidMap() || SF_IsBoxingMap()) - { - return; - } - - g_PlayerBlink[client] = true; - g_PlayerBlinkCount[client]++; - g_PlayerBlinkMeter[client] = 0.0; - g_PlayerBlinkTimer[client] = CreateTimer(0.0, Timer_TryUnblink, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - g_TimeUntilUnblink[client] = GetGameTime() + g_PlayerBlinkHoldTimeConVar.FloatValue; - - UTIL_ScreenFade(client, 100, RoundToFloor(g_PlayerBlinkHoldTimeConVar.FloatValue * 1000.0), FFADE_IN, 0, 0, 0, 255); - - Call_StartForward(g_OnClientBlinkFwd); - Call_PushCell(client); - Call_Finish(); -} - -/** - * Unsets the player from the blinking state. - */ -static void ClientUnblink(int client) -{ - if (!IsClientBlinking(client)) - { - return; - } - - g_PlayerBlink[client] = false; - g_PlayerBlinkTimer[client] = null; - g_PlayerBlinkMeter[client] = 1.0; -} - -void ClientStartDrainingBlinkMeter(int client) -{ - g_PlayerBlinkTimer[client] = CreateTimer(ClientGetBlinkRate(client), Timer_BlinkTimer, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} - -static Action Timer_BlinkTimer(Handle timer, any userid) -{ - if (IsRoundInWarmup()) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerBlinkTimer[client]) - { - return Plugin_Stop; - } - - if (IsPlayerAlive(client) && !IsClientInDeathCam(client) && !g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !IsRoundEnding()) - { - int override = g_PlayerInfiniteBlinkOverrideConVar.IntValue; - if ((!g_RoundInfiniteBlink && override != 1) || override == 0) - { - g_PlayerBlinkMeter[client] -= 0.05; - } - - if (g_PlayerBlinkMeter[client] <= 0.0) - { - ClientBlink(client); - return Plugin_Stop; - } - } - - return Plugin_Continue; -} - -static Action Timer_TryUnblink(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0 || timer != g_PlayerBlinkTimer[client] || !g_PlayerBlink[client]) - { - return Plugin_Stop; - } - - if (g_PlayerHoldingBlink[client]) - { - // Some maps use the env_fade entity, so don't use FFADE_PURGE. - // Instead, we resort to spamming fade messages to the client to keep - // them blind. - UTIL_ScreenFade(client, 100, 150, FFADE_IN, 0, 0, 0, 255); - return Plugin_Continue; - } - - if (GetGameTime() < g_TimeUntilUnblink[client]) - { - return Plugin_Continue; - } - - ClientUnblink(client); - ClientStartDrainingBlinkMeter(client); - - return Plugin_Stop; -} - -static float ClientGetBlinkRate(int client) -{ - float value = g_PlayerBlinkRateConVar.FloatValue; - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (GetEntProp(client, Prop_Send, "m_nWaterLevel") >= 3) - { - // Being underwater makes you blink faster, obviously. - value *= 0.75; - } - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (g_PlayerSeesSlender[client][i]) - { - value *= NPCGetBlinkLookRate(i); - } - - else if (g_PlayerStaticMode[client][i] == Static_Increase) - { - value *= NPCGetBlinkStaticRate(i); - } - } - - if (!IsClassConfigsValid()) - { - if (class == TFClass_Sniper) - { - value *= 2.0; - } - } - else - { - value *= g_ClassBlinkRateMultiplier[classToInt]; - } - - if (IsClientUsingFlashlight(client)) - { - float startPos[3], endPos[3], direction[3]; - float length = SF2_FLASHLIGHT_LENGTH; - GetClientEyePosition(client, startPos); - GetClientEyePosition(client, endPos); - GetClientEyeAngles(client, direction); - GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(direction, direction); - ScaleVector(direction, length); - AddVectors(endPos, direction, endPos); - Handle trace = TR_TraceRayFilterEx(startPos, endPos, MASK_VISIBLE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, client); - TR_GetEndPosition(endPos, trace); - bool hit = TR_DidHit(trace); - delete trace; - - if (hit) - { - float percent = ((GetVectorSquareMagnitude(startPos, endPos) / length)); - percent *= 3.5; - if (percent > 1.0) - { - percent = 1.0; - } - value *= percent; - } - } - - return value; -} - // ========================================================== // SCREEN OVERLAY FUNCTIONS // ========================================================== @@ -3516,7 +1552,7 @@ void ClientResetOverlay(int client) g_PlayerOverlayCheck[client] = null; - if (IsClientInGame(client)) + if (IsValidClient(client)) { ClientCommand(client, "r_screenoverlay \"\""); } @@ -3596,14 +1632,14 @@ Action Timer_PlayerOverlayCheck(Handle timer, any userid) void ClientUpdateListeningFlags(int client, bool reset=false) { - if (!IsClientInGame(client)) + if (!IsValidClient(client)) { return; } - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (i == client || !IsClientInGame(i) || IsClientSourceTV(i)) + if (i == client || !IsValidClient(i) || IsClientSourceTV(i)) { continue; } @@ -3965,11 +2001,11 @@ void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) } } -bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV=true, bool checkBlink=false) +bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV = true, bool checkBlink = false) { - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -3982,7 +2018,7 @@ bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV=true, bool checkB return false; } -bool IsPointVisibleToPlayer(int client, const float pos[3], bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true, bool ignoreFog = false) +bool IsPointVisibleToPlayer(int client, const float pos[3], bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true, bool ignoreFog = false) { if (!IsValidClient(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) { @@ -4056,7 +2092,7 @@ Action Timer_RespawnPlayer(Handle timer, any userid) return Plugin_Stop; } - if (!IsValidClient(client) || !IsClientInGame(client) || IsPlayerAlive(client)) + if (!IsValidClient(client) || IsPlayerAlive(client)) { return Plugin_Stop; } @@ -4080,10 +2116,3 @@ Action Timer_RespawnPlayer(Handle timer, any userid) return Plugin_Stop; } - -#include "sf2/functionclients/client_hints.sp" -#include "sf2/functionclients/clients_think.sp" -#include "sf2/functionclients/client_flashlight.sp" -#include "sf2/functionclients/client_music.sp" -#include "sf2/functionclients/client_proxy_functions.sp" -#include "sf2/methodmaps.sp" diff --git a/addons/sourcemod/scripting/sf2/client/blink.sp b/addons/sourcemod/scripting/sf2/client/blink.sp new file mode 100644 index 00000000..017698af --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/blink.sp @@ -0,0 +1,272 @@ +#pragma semicolon 1 + +// Blink data. +static Handle g_PlayerBlinkTimer[MAXTF2PLAYERS] = { null, ... }; +static bool g_PlayerBlink[MAXTF2PLAYERS] = { false, ... }; +bool g_PlayerHoldingBlink[MAXTF2PLAYERS] = { false, ... }; +static float g_PlayerBlinkMeter[MAXTF2PLAYERS] = { 0.0, ... }; +static float g_TimeUntilUnblink[MAXTF2PLAYERS] = { 0.0, ... }; +static int g_PlayerBlinkCount[MAXTF2PLAYERS] = { 0, ... }; + +void SetupBlink() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetBlink(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetBlink(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetBlink(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetBlink(client.index); +} + +bool IsClientBlinking(int client) +{ + return g_PlayerBlink[client]; +} + +bool IsClientHoldingBlink(int client) +{ + return g_PlayerHoldingBlink[client]; +} + +void ClientSetHoldingBlink(int client, bool value) +{ + g_PlayerHoldingBlink[client] = value; +} + +float ClientGetBlinkMeter(int client) +{ + return g_PlayerBlinkMeter[client]; +} + +void ClientSetBlinkMeter(int client, float amount) +{ + g_PlayerBlinkMeter[client] = amount; +} + +int ClientGetBlinkCount(int client) +{ + return g_PlayerBlinkCount[client]; +} + +/** + * Resets all data on blinking. + */ +void ClientResetBlink(int client) +{ + g_PlayerBlinkTimer[client] = null; + g_PlayerBlink[client] = false; + g_PlayerHoldingBlink[client] = false; + g_TimeUntilUnblink[client] = 0.0; + g_PlayerBlinkMeter[client] = 1.0; + g_PlayerBlinkCount[client] = 0; +} + +/** + * Sets the player into a blinking state and blinds the player + */ +void ClientBlink(int client) +{ + if (IsRoundInWarmup() || DidClientEscape(client)) + { + return; + } + + if (IsClientBlinking(client)) + { + return; + } + + if (SF_IsRaidMap() || SF_IsBoxingMap()) + { + return; + } + + g_PlayerBlink[client] = true; + g_PlayerBlinkCount[client]++; + g_PlayerBlinkMeter[client] = 0.0; + g_PlayerBlinkTimer[client] = CreateTimer(0.0, Timer_TryUnblink, GetClientUserId(client), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_TimeUntilUnblink[client] = GetGameTime() + g_PlayerBlinkHoldTimeConVar.FloatValue; + + UTIL_ScreenFade(client, 100, RoundToFloor(g_PlayerBlinkHoldTimeConVar.FloatValue * 1000.0), FFADE_IN, 0, 0, 0, 255); + + Call_StartForward(g_OnClientBlinkFwd); + Call_PushCell(client); + Call_Finish(); +} + +/** + * Unsets the player from the blinking state. + */ +static void ClientUnblink(int client) +{ + if (!IsClientBlinking(client)) + { + return; + } + + g_PlayerBlink[client] = false; + g_PlayerBlinkTimer[client] = null; + g_PlayerBlinkMeter[client] = 1.0; +} + +void ClientStartDrainingBlinkMeter(int client) +{ + g_PlayerBlinkTimer[client] = CreateTimer(ClientGetBlinkRate(client), Timer_BlinkTimer, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); +} + +static Action Timer_BlinkTimer(Handle timer, any userid) +{ + if (IsRoundInWarmup()) + { + return Plugin_Stop; + } + + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerBlinkTimer[client]) + { + return Plugin_Stop; + } + + if (IsPlayerAlive(client) && !IsClientInDeathCam(client) && !g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !IsRoundEnding()) + { + int override = g_PlayerInfiniteBlinkOverrideConVar.IntValue; + if ((!g_RoundInfiniteBlink && override != 1) || override == 0) + { + g_PlayerBlinkMeter[client] -= 0.05; + } + + if (g_PlayerBlinkMeter[client] <= 0.0) + { + ClientBlink(client); + return Plugin_Stop; + } + } + + return Plugin_Continue; +} + +static Action Timer_TryUnblink(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0 || timer != g_PlayerBlinkTimer[client] || !g_PlayerBlink[client]) + { + return Plugin_Stop; + } + + if (g_PlayerHoldingBlink[client]) + { + // Some maps use the env_fade entity, so don't use FFADE_PURGE. + // Instead, we resort to spamming fade messages to the client to keep + // them blind. + UTIL_ScreenFade(client, 100, 150, FFADE_IN, 0, 0, 0, 255); + return Plugin_Continue; + } + + if (GetGameTime() < g_TimeUntilUnblink[client]) + { + return Plugin_Continue; + } + + ClientUnblink(client); + ClientStartDrainingBlinkMeter(client); + + return Plugin_Stop; +} + +static float ClientGetBlinkRate(int client) +{ + float value = g_PlayerBlinkRateConVar.FloatValue; + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + if (GetEntProp(client, Prop_Send, "m_nWaterLevel") >= 3) + { + // Being underwater makes you blink faster, obviously. + value *= 0.75; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if (g_PlayerSeesSlender[client][i]) + { + value *= NPCGetBlinkLookRate(i); + } + + else if (g_PlayerStaticMode[client][i] == Static_Increase) + { + value *= NPCGetBlinkStaticRate(i); + } + } + + if (!IsClassConfigsValid()) + { + if (class == TFClass_Sniper) + { + value *= 2.0; + } + } + else + { + value *= g_ClassBlinkRateMultiplier[classToInt]; + } + + if (IsClientUsingFlashlight(client)) + { + float startPos[3], endPos[3], direction[3]; + float length = SF2_FLASHLIGHT_LENGTH; + GetClientEyePosition(client, startPos); + GetClientEyePosition(client, endPos); + GetClientEyeAngles(client, direction); + GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(direction, direction); + ScaleVector(direction, length); + AddVectors(endPos, direction, endPos); + Handle trace = TR_TraceRayFilterEx(startPos, endPos, MASK_VISIBLE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, client); + TR_GetEndPosition(endPos, trace); + bool hit = TR_DidHit(trace); + delete trace; + + if (hit) + { + float percent = ((GetVectorSquareMagnitude(startPos, endPos) / length)); + percent *= 3.5; + if (percent > 1.0) + { + percent = 1.0; + } + value *= percent; + } + } + + return value; +} diff --git a/addons/sourcemod/scripting/sf2/client/breathing.sp b/addons/sourcemod/scripting/sf2/client/breathing.sp new file mode 100644 index 00000000..78a0adda --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/breathing.sp @@ -0,0 +1,139 @@ +#pragma semicolon 1 + +#define SF2_PLAYER_BREATH_COOLDOWN_MIN 0.8 +#define SF2_PLAYER_BREATH_COOLDOWN_MAX 2.0 + +char g_PlayerBreathSounds[][] = +{ + "slender/fastbreath1.wav" +}; + +static bool g_PlayerBreath[MAXTF2PLAYERS] = { false, ... }; +static Handle g_PlayerBreathTimer[MAXTF2PLAYERS] = { null, ... }; + +void SetupBreathing() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetBreathing(client.index); + + SDKHook(client.index, SDKHook_PreThink, Hook_BreathingThink); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetBreathing(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetBreathing(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetBreathing(client.index); +} + +static void ClientResetBreathing(int client) +{ + g_PlayerBreath[client] = false; + g_PlayerBreathTimer[client] = null; +} + +static float ClientCalculateBreathingCooldown(int client) +{ + float average = 0.0; + int averageNum = 0; + + // Sprinting only, for now. + average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow((float(g_PlayerSprintPoints[client]) / 100.0), 1.65)); + averageNum++; + + average /= float(averageNum); + + if (average < SF2_PLAYER_BREATH_COOLDOWN_MIN) + { + average = SF2_PLAYER_BREATH_COOLDOWN_MIN; + } + + return average; +} + +static void ClientStartBreathing(int client) +{ + g_PlayerBreath[client] = true; + g_PlayerBreathTimer[client] = CreateTimer(ClientCalculateBreathingCooldown(client), Timer_ClientBreath, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); +} + +static void ClientStopBreathing(int client) +{ + g_PlayerBreath[client] = false; + g_PlayerBreathTimer[client] = null; +} + +static bool ClientCanBreath(int client) +{ + return ClientCalculateBreathingCooldown(client) < SF2_PLAYER_BREATH_COOLDOWN_MAX; +} + +static Action Timer_ClientBreath(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerBreathTimer[client]) + { + return Plugin_Stop; + } + + if (!g_PlayerBreath[client]) + { + return Plugin_Stop; + } + + if (ClientCanBreath(client)) + { + EmitSoundToAll(g_PlayerBreathSounds[GetRandomInt(0, sizeof(g_PlayerBreathSounds) - 1)], client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + + ClientStartBreathing(client); + return Plugin_Stop; + } + + ClientStopBreathing(client); + + return Plugin_Stop; +} + +static void Hook_BreathingThink(int client) +{ + if (!g_Enabled) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + RoundState roundState = GameRules_GetRoundState(); + if (player.IsEliminated || roundState != RoundState_RoundRunning || + IsRoundEnding() || IsRoundInWarmup() || player.HasEscaped) + { + return; + } + + if (ClientCanBreath(player.index) && !g_PlayerBreath[player.index]) + { + ClientStartBreathing(player.index); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp new file mode 100644 index 00000000..7e320162 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -0,0 +1,561 @@ +#pragma semicolon 1 + +// Deathcam data. +int g_PlayerDeathCamBoss[MAXTF2PLAYERS] = { -1, ... }; +static bool g_PlayerDeathCam[MAXTF2PLAYERS] = { false, ... }; +bool g_PlayerDeathCamShowOverlay[MAXTF2PLAYERS] = { false, ... }; +int g_PlayerDeathCamEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerDeathCamEnt2[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerDeathCamTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static Handle g_PlayerDeathCamTimer[MAXTF2PLAYERS] = { null, ... }; +bool g_CameraInDeathCamAdvanced[2049] = { false, ... }; +float g_CameraPlayerOffsetBackward[2049] = { 0.0, ... }; +float g_CameraPlayerOffsetDownward[2049] = { 0.0, ... }; +static float g_PlayerOriginalDeathcamPosition[MAXTF2PLAYERS][3]; + +void SetupDeathCams() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetDeathCam(client.index); + + SDKHook(client.index, SDKHook_PreThink, Hook_DeathCamThink); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetDeathCam(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetDeathCam(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetDeathCam(client.index); +} + +bool IsClientInDeathCam(int client) +{ + return g_PlayerDeathCam[client]; +} + +static Action Hook_DeathCamSetTransmit(int slender,int other) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (EntRefToEntIndex(g_PlayerDeathCamEnt2[other]) != slender) + { + return Plugin_Handled; + } + return Plugin_Continue; +} + +static void ClientResetDeathCam(int client) +{ + if (!IsClientInDeathCam(client)) + { + return; // no really need to reset if it wasn't set. + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetDeathCam(%d)", client); + } + #endif + + int deathCamBoss = NPCGetFromUniqueID(g_PlayerDeathCamBoss[client]); + + g_PlayerDeathCamBoss[client] = -1; + g_PlayerDeathCam[client] = false; + g_PlayerDeathCamShowOverlay[client] = false; + g_PlayerDeathCamTimer[client] = null; + + int ent = EntRefToEntIndex(g_PlayerDeathCamEnt[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + g_CameraInDeathCamAdvanced[ent] = false; + AcceptEntityInput(ent, "Disable"); + RemoveEntity(ent); + } + + ent = EntRefToEntIndex(g_PlayerDeathCamEnt2[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + AcceptEntityInput(ent, "Kill"); + } + + ent = EntRefToEntIndex(g_PlayerDeathCamTarget[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + RemoveEntity(ent); + } + + g_PlayerDeathCamEnt[client] = INVALID_ENT_REFERENCE; + g_PlayerDeathCamEnt2[client] = INVALID_ENT_REFERENCE; + g_PlayerDeathCamTarget[client] = INVALID_ENT_REFERENCE; + + if (IsClientInGame(client)) + { + SetClientViewEntity(client, client); + } + + Call_StartForward(g_OnClientEndDeathCamFwd); + Call_PushCell(client); + Call_PushCell(deathCamBoss); + Call_Finish(); + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetDeathCam(%d)", client); + } + #endif +} + +void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool antiCamp = false) +{ + if (IsClientInDeathCam(client)) + { + return; + } + if (!NPCIsValid(bossIndex)) + { + return; + } + + for (int npcIndex; npcIndex < MAX_BOSSES; npcIndex++) + { + if (NPCGetUniqueID(npcIndex) == -1) + { + continue; + } + switch (NPCGetType(npcIndex)) + { + case SF2BossType_Chaser: + { + if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) + { + g_SlenderGiveUp[npcIndex] = true; + } + } + } + } + + GetClientAbsOrigin(client, g_PlayerOriginalDeathcamPosition[client]); + + char buffer[PLATFORM_MAX_PATH]; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + SF2BossProfileSoundInfo soundInfo; + if (g_SlenderDeathCamScareSound[bossIndex]) + { + GetBossProfileScareSounds(profile, soundInfo); + soundInfo.EmitSound(true, client); + } + + GetBossProfileClientDeathCamSounds(profile, soundInfo); + soundInfo.EmitSound(true, client); + + GetBossProfileGlobalDeathCamSounds(profile, soundInfo); + for (int i = 0; i < MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + soundInfo.EmitSound(true, i); + } + + // Call our forward. + Call_StartForward(g_OnClientCaughtByBossFwd); + Call_PushCell(client); + Call_PushCell(bossIndex); + Call_Finish(); + + if (!NPCHasDeathCamEnabled(bossIndex) && !(NPCGetFlags(bossIndex) & SFF_FAKE)) + { + SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. + + float value = NPCGetAttributeValue(bossIndex, SF2Attribute_IgnitePlayerOnDeath); + if (value > 0.0) + { + TF2_IgnitePlayer(client, client); + } + + int slenderEnt = NPCGetEntIndex(bossIndex); + if (slenderEnt > MaxClients) + { + SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + } + SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. + KillClient(client); + return; + } + else if (NPCGetFlags(bossIndex) & SFF_FAKE) + { + SlenderMarkAsFake(bossIndex); + } + + g_PlayerDeathCamBoss[client] = NPCGetUniqueID(bossIndex); + g_PlayerDeathCam[client] = true; + g_PlayerDeathCamShowOverlay[client] = false; + + float eyePos[3], eyeAng[3], angle[3]; + GetClientEyePosition(client, eyePos); + GetClientEyeAngles(client, eyeAng); + SubtractVectors(eyePos, lookPos, angle); + GetVectorAngles(angle, angle); + angle[0] = 0.0; + angle[2] = 0.0; + + // Create fake model. + int slender = SpawnSlenderModel(bossIndex, lookPos, true); + TeleportEntity(slender, lookPos, angle, NULL_VECTOR); + g_PlayerDeathCamEnt2[client] = EntIndexToEntRef(slender); + if (!g_SlenderPublicDeathCam[bossIndex]) + { + SDKHook(slender, SDKHook_SetTransmit, Hook_DeathCamSetTransmit); + } + else + { + GetBossProfileLocalDeathCamSounds(profile, soundInfo); + soundInfo.EmitSound(_, slender); + SetEntityMoveType(client, MOVETYPE_NOCLIP); + if (!antiCamp) + { + int slenderEnt = NPCGetEntIndex(bossIndex); + if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) + { + g_SlenderInDeathcam[bossIndex] = true; + SetEntityRenderMode(slenderEnt, RENDER_TRANSCOLOR); + SetEntityRenderColor(slenderEnt, g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], 0); + NPCChaserUpdateBossAnimation(bossIndex, slenderEnt, STATE_IDLE); + g_SlenderDeathCamTarget[bossIndex] = EntIndexToEntRef(client); + if (g_SlenderEntityThink[bossIndex] != null) + { + KillTimer(g_SlenderEntityThink[bossIndex]); + } + g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderPublicDeathCamThink, EntIndexToEntRef(slenderEnt), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + } + + // Create camera look point. + char name[64]; + FormatEx(name, sizeof(name), "sf2_boss_%d", EntIndexToEntRef(slender)); + + float offsetPos[3]; + int target = CreateEntityByName("info_target"); + if (!g_SlenderPublicDeathCam[bossIndex]) + { + GetBossProfileDeathCamPosition(profile, offsetPos); + AddVectors(lookPos, offsetPos, offsetPos); + TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); + DispatchKeyValue(target, "targetname", name); + SetVariantString("!activator"); + AcceptEntityInput(target, "SetParent", slender); + } + else + { + char boneName[PLATFORM_MAX_PATH]; + AddVectors(lookPos, offsetPos, offsetPos); + TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); + DispatchKeyValue(target, "targetname", name); + SetVariantString("!activator"); + AcceptEntityInput(target, "SetParent", slender); + GetBossProfilePublicDeathCamTargetAttachment(profile, boneName, sizeof(boneName)); + if (boneName[0] != '\0') + { + SetVariantString(boneName); + AcceptEntityInput(target, "SetParentAttachment"); + } + } + g_PlayerDeathCamTarget[client] = EntIndexToEntRef(target); + + // Create the camera itself. + int camera = CreateEntityByName("point_viewcontrol"); + TeleportEntity(camera, eyePos, eyeAng, NULL_VECTOR); + DispatchKeyValue(camera, "spawnflags", "12"); + DispatchKeyValue(camera, "target", name); + DispatchSpawn(camera); + AcceptEntityInput(camera, "Enable", client); + g_PlayerDeathCamEnt[client] = EntIndexToEntRef(camera); + if (g_SlenderPublicDeathCam[bossIndex]) + { + float camSpeed, camAcceleration, camDeceleration; + + camSpeed = g_SlenderPublicDeathCamSpeed[bossIndex]; + camAcceleration = g_SlenderPublicDeathCamAcceleration[bossIndex]; + camDeceleration = g_SlenderPublicDeathCamDeceleration[bossIndex]; + FloatToString(camSpeed, buffer, sizeof(buffer)); + DispatchKeyValue(camera, "acceleration", buffer); + FloatToString(camAcceleration, buffer, sizeof(buffer)); + DispatchKeyValue(camera, "deceleration", buffer); + FloatToString(camDeceleration, buffer, sizeof(buffer)); + DispatchKeyValue(camera, "speed", buffer); + + SetVariantString("!activator"); + AcceptEntityInput(camera, "SetParent", slender); + char attachmentName[PLATFORM_MAX_PATH]; + GetBossProfilePublicDeathCamAttachment(profile, attachmentName, sizeof(attachmentName)); + if (attachmentName[0] != '\0') + { + SetVariantString(attachmentName); + AcceptEntityInput(camera, "SetParentAttachment"); + } + + g_CameraInDeathCamAdvanced[camera] = true; + g_CameraPlayerOffsetBackward[camera] = g_SlenderPublicDeathCamBackwardOffset[bossIndex]; + g_CameraPlayerOffsetDownward[camera] = g_SlenderPublicDeathCamDownwardOffset[bossIndex]; + RequestFrame(Frame_PublicDeathCam, camera); //Resend taunt sound to eliminated players only + } + + if (g_SlenderDeathCamOverlay[bossIndex] && g_SlenderDeathCamOverlayTimeStart[bossIndex] >= 0.0) + { + if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) + { + int slenderEnt = NPCGetEntIndex(bossIndex); + if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) + { + g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_BossDeathCamDelay, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); + } + } + g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_ClientResetDeathCam1, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + else + { + if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) + { + int slenderEnt = NPCGetEntIndex(bossIndex); + if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) + { + g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_BossDeathCamDuration, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); + } + } + g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_ClientResetDeathCamEnd, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); + + Call_StartForward(g_OnClientStartDeathCamFwd); + Call_PushCell(client); + Call_PushCell(bossIndex); + Call_Finish(); +} + +static void Frame_PublicDeathCam(int cameraRef) +{ + int camera = EntRefToEntIndex(cameraRef); + if (!camera || camera == INVALID_ENT_REFERENCE) + { + return; + } + int slender = GetEntPropEnt(camera, Prop_Data, "m_hOwnerEntity"); + int client = GetEntPropEnt(camera, Prop_Data, "m_hPlayer"); + if (IsValidEntity(slender) && IsValidClient(client)) + { + float camPos[3], camAngs[3]; + GetEntPropVector(camera, Prop_Data, "m_angAbsRotation", camAngs); + GetEntPropVector(camera, Prop_Data, "m_vecAbsOrigin", camPos); + + camPos[0] -= g_CameraPlayerOffsetBackward[camera]; + camPos[2] -= g_CameraPlayerOffsetDownward[camera]; + + TeleportEntity(client, camPos, camAngs, NULL_VECTOR); + + RequestFrame(Frame_PublicDeathCam, EntIndexToEntRef(cameraRef)); + } +} + +static Action Timer_ClientResetDeathCam1(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerDeathCamTimer[client]) + { + return Plugin_Stop; + } + + SF2NPC_BaseNPC Npc = view_as(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + if (Npc.IsValid()) + { + g_PlayerDeathCamShowOverlay[client] = true; + Npc.GetProfile(profile, sizeof(profile)); + SF2BossProfileSoundInfo soundInfo; + GetBossProfilePlayerDeathcamOverlaySounds(profile, soundInfo); + soundInfo.EmitSound(true, client); + g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_ClientResetDeathCamEnd, userid, TIMER_FLAG_NO_MAPCHANGE); + } + + return Plugin_Stop; +} + +static Action Timer_BossDeathCamDelay(Handle timer, any entref) +{ + int slender = EntRefToEntIndex(entref); + if (!slender || slender == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } + + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(slender)); + + if (timer != g_SlenderDeathCamTimer[Npc.Index]) + { + return Plugin_Stop; + } + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + Npc.GetProfile(profile, sizeof(profile)); + + g_SlenderDeathCamTimer[Npc.Index] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_BossDeathCamDuration, slender, TIMER_FLAG_NO_MAPCHANGE); + + return Plugin_Stop; +} + +static Action Timer_BossDeathCamDuration(Handle timer, any entref) +{ + int slender = EntRefToEntIndex(entref); + if (!slender || slender == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } + + SF2NPC_Chaser Npc = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); + + if (timer != g_SlenderDeathCamTimer[Npc.Index]) + { + return Plugin_Stop; + } + + if (g_SlenderInDeathcam[Npc.Index]) + { + SetEntityRenderMode(slender, RENDER_NORMAL); + if (!Npc.CloakEnabled) + { + SetEntityRenderColor(slender, Npc.GetRenderColor(0), Npc.GetRenderColor(1), Npc.GetRenderColor(2), Npc.GetRenderColor(3)); + } + g_SlenderEntityThink[Npc.Index] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + if (!(Npc.Flags & SFF_FAKE)) + { + g_SlenderInDeathcam[Npc.Index] = false; + } + Npc.UpdateAnimation(slender, Npc.State); + } + if ((Npc.Flags & SFF_FAKE)) + { + if (g_SlenderInDeathcam[Npc.Index]) + { + g_SlenderInDeathcam[Npc.Index] = false; + } + Npc.MarkAsFake(); + } + g_SlenderDeathCamTimer[Npc.Index] = null; + + return Plugin_Stop; +} + +static Action Timer_ClientResetDeathCamEnd(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerDeathCamTimer[client]) + { + return Plugin_Stop; + } + + SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol entity changes our damage state. + + SF2NPC_BaseNPC deathCamBoss = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); + if (deathCamBoss != SF2_INVALID_NPC) + { + float value = deathCamBoss.GetAttributeValue(SF2Attribute_IgnitePlayerOnDeath); + if (value > 0.0) + { + TF2_IgnitePlayer(client, client); + } + if (!(deathCamBoss.Flags & SFF_FAKE)) + { + int slenderEnt = deathCamBoss.EntIndex; + if (slenderEnt > MaxClients) + { + SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + } + SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. + KillClient(client); + } + else + { + SetEntityMoveType(client, MOVETYPE_WALK); + TeleportEntity(client, g_PlayerOriginalDeathcamPosition[client], NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); + } + } + else //The boss is invalid? But the player got a death cam? + { + //Then kill him anyways. + KillClient(client); + ForcePlayerSuicide(client); + } + ClientResetDeathCam(client); + + return Plugin_Stop; +} + +static void Hook_DeathCamThink(int client) +{ + if (!g_Enabled) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid || !player.IsInDeathCam || player.IsInGhostMode) + { + return; + } + + CBaseEntity ent = CBaseEntity(EntRefToEntIndex(g_PlayerDeathCamEnt[client])); + if (ent.IsValid() && g_CameraInDeathCamAdvanced[ent.index]) + { + float camPos[3], camAngs[3]; + ent.GetAbsAngles(camAngs); + ent.GetAbsOrigin(camPos); + + camPos[0] -= g_CameraPlayerOffsetBackward[ent.index]; + camPos[2] -= g_CameraPlayerOffsetDownward[ent.index]; + + player.SetLocalOrigin(camPos); + player.SetLocalAngles(camAngs); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/flashlight.sp b/addons/sourcemod/scripting/sf2/client/flashlight.sp new file mode 100644 index 00000000..7bcf7b81 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/flashlight.sp @@ -0,0 +1,882 @@ +#if defined _sf2_client_flashlight_included + #endinput +#endif +#define _sf2_client_flashlight_included + +#define SF2_FLASHLIGHT_WIDTH 512.0 // How wide the player's Flashlight should be in world units. +#define SF2_FLASHLIGHT_BRIGHTNESS 0 // Intensity of the players' Flashlight. +#define SF2_FLASHLIGHT_DRAIN_RATE 0.65 // How long (in seconds) each bar on the player's Flashlight meter lasts. +#define SF2_FLASHLIGHT_RECHARGE_RATE 0.68 // How long (in seconds) it takes each bar on the player's Flashlight meter to recharge. +#define SF2_FLASHLIGHT_FLICKERAT 0.25 // The percentage of the Flashlight battery where the Flashlight will start to blink. +#define SF2_FLASHLIGHT_ENABLEAT 0.3 // The percentage of the Flashlight battery where the Flashlight will be able to be used again (if the player shortens out the Flashlight from excessive use). +#define SF2_FLASHLIGHT_COOLDOWN 0.4 // How much time players have to wait before being able to switch their flashlight on again after turning it off. + +#pragma semicolon 1 + +static bool g_PlayerHasFlashlight[MAXTF2PLAYERS] = { false, ... }; +static bool g_PlayerFlashlightBroken[MAXTF2PLAYERS] = { false, ... }; +static float g_PlayerFlashlightBatteryLife[MAXTF2PLAYERS] = { 1.0, ... }; +static Handle g_PlayerFlashlightBatteryTimer[MAXTF2PLAYERS] = { null, ... }; +static float g_PlayerFlashlightNextInputTime[MAXTF2PLAYERS] = { -1.0, ... }; +static int g_PlayerFlashlightEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerFlashlightEntAng[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_ClientFlashlightStartEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_ClientFlashlightEndEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; + +void SetupFlashlight() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetFlashlight(client.index); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + ClientResetFlashlight(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetFlashlight(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetFlashlight(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetFlashlight(client.index); +} + +static Action Timer_DrainFlashlight(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerFlashlightBatteryTimer[client]) + { + return Plugin_Stop; + } + + if (!IsInfiniteFlashlightEnabled()) + { + ClientSetFlashlightBatteryLife(client, ClientGetFlashlightBatteryLife(client) - 0.01); + } + + if (ClientGetFlashlightBatteryLife(client) <= 0.0) + { + // Break the player's flashlight, but also start recharging. + ClientBreakFlashlight(client); + ClientStartRechargingFlashlightBattery(client); + ClientActivateUltravision(client); + return Plugin_Stop; + } + else + { + ClientHandleFlashlightFlickerState(client); + } + + return Plugin_Continue; +} + +static Action Timer_RechargeFlashlight(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerFlashlightBatteryTimer[client]) + { + return Plugin_Stop; + } + + ClientSetFlashlightBatteryLife(client, ClientGetFlashlightBatteryLife(client) + 0.01); + + if (IsClientFlashlightBroken(client) && ClientGetFlashlightBatteryLife(client) >= SF2_FLASHLIGHT_ENABLEAT) + { + // Repair the flashlight. + g_PlayerFlashlightBroken[client] = false; + } + + if (ClientGetFlashlightBatteryLife(client) >= 1.0) + { + // I am fully charged! + ClientSetFlashlightBatteryLife(client, 1.0); + g_PlayerFlashlightBatteryTimer[client] = null; + + return Plugin_Stop; + } + + return Plugin_Continue; +} + +bool IsClientUsingFlashlight(int client) +{ + return g_PlayerHasFlashlight[client]; +} + +float ClientGetFlashlightBatteryLife(int client) +{ + return g_PlayerFlashlightBatteryLife[client]; +} + +void ClientSetFlashlightBatteryLife(int client, float percent) +{ + g_PlayerFlashlightBatteryLife[client] = percent; +} + +/** + * Called in Hook_ClientPreThink, this makes sure the flashlight is oriented correctly on the player. + */ +void ClientProcessFlashlightAngles(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + int fl; + + if (IsClientUsingFlashlight(client)) + { + fl = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); + if (fl && fl != INVALID_ENT_REFERENCE) + { + TeleportEntity(fl, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 }), NULL_VECTOR); + } + } +} + +/** + * Handles whether or not the player's flashlight should be "flickering", a sign of a dying flashlight battery. + */ +void ClientHandleFlashlightFlickerState(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + if (IsClientUsingFlashlight(client)) + { + bool flicker = ClientGetFlashlightBatteryLife(client) <= SF2_FLASHLIGHT_FLICKERAT; + + int fl = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); + if (fl && fl != INVALID_ENT_REFERENCE) + { + if (flicker) + { + SetEntProp(fl, Prop_Data, "m_LightStyle", 10); + } + else + { + SetEntProp(fl, Prop_Data, "m_LightStyle", 0); + } + } + + fl = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); + if (fl && fl != INVALID_ENT_REFERENCE) + { + if (flicker) + { + SetEntityRenderFx(fl, view_as(13)); + } + else + { + SetEntityRenderFx(fl, view_as(0)); + } + } + } +} + +bool IsClientFlashlightBroken(int client) +{ + return g_PlayerFlashlightBroken[client]; +} + +float ClientGetFlashlightNextInputTime(int client) +{ + return g_PlayerFlashlightNextInputTime[client]; +} + +/** + * Breaks the player's flashlight. Nothing else. + */ +void ClientBreakFlashlight(int client) +{ + if (IsClientFlashlightBroken(client)) + { + return; + } + + ClientDeactivateUltravision(client); + + g_PlayerFlashlightBroken[client] = true; + + ClientSetFlashlightBatteryLife(client, 0.0); + ClientTurnOffFlashlight(client); + + ClientAddStress(client, 0.2); + + EmitSoundToAll(FLASHLIGHT_BREAKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + + Call_StartForward(g_OnClientBreakFlashlightFwd); + Call_PushCell(client); + Call_Finish(); +} + +/** + * Resets everything of the player's flashlight. + */ +void ClientResetFlashlight(int client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetFlashlight(%d)", client); + } + #endif + + ClientTurnOffFlashlight(client); + ClientSetFlashlightBatteryLife(client, 1.0); + g_PlayerFlashlightBroken[client] = false; + g_PlayerFlashlightBatteryTimer[client] = null; + g_PlayerFlashlightNextInputTime[client] = -1.0; + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetFlashlight(%d)", client); + } + #endif +} + +static Action Hook_FlashlightSetTransmit(int ent,int other) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (EntRefToEntIndex(g_PlayerFlashlightEnt[other]) != ent) + { + return Plugin_Handled; + } + + // We've already checked for flashlight ownership in the last statement. So we can do just this. + if (g_PlayerPreferences[other].PlayerPreference_ProjectedFlashlight) + { + return Plugin_Handled; + } + + return Plugin_Continue; +} + +/*static Action Hook_Flashlight2SetTransmit(int ent,int other) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + if (!IsValidClient(ownerEntity)) + { + return Plugin_Continue; + } + + if (ownerEntity == other) + { + if (!!(GetEntProp(ownerEntity, Prop_Send, "m_nForceTauntCam")) && !TF2_IsPlayerInCondition(ownerEntity, TFCond_Taunting)) + { + return Plugin_Handled; + } + } + else + { + if (GetEntPropEnt(other, Prop_Send, "m_hObserverTarget") == ownerEntity && GetEntProp(other, Prop_Send, "m_iObserverMode") == 4) + { + return Plugin_Handled; + } + + if (!DidClientEscape(ownerEntity) && !g_PlayerEliminated[ownerEntity]) + { + if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) + { + if (!g_PlayerEliminated[other] && !DidClientEscape(other)) + { + return Plugin_Handled; + } + } + } + } + return Plugin_Continue; +}*/ + +/** + * Turns on the player's flashlight. Nothing else. + */ +void ClientTurnOnFlashlight(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + if (IsClientUsingFlashlight(client)) + { + return; + } + + g_PlayerHasFlashlight[client] = true; + + float eyePos[3], eyeAng[3]; + + float length = SF2_FLASHLIGHT_LENGTH; + float doubleLength = SF2_FLASHLIGHT_LENGTH*3.0; + float radius = SF2_FLASHLIGHT_WIDTH; + float doubleRadius = SF2_FLASHLIGHT_WIDTH*2.0; + GetClientEyePosition(client, eyePos); + GetClientEyeAngles(client, eyeAng); + + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + + if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) + { + // If the player is using the projected flashlight, just set effect flags. + int effects = GetEntProp(client, Prop_Send, "m_fEffects"); + if (!(effects & (1 << 2))) + { + SetEntProp(client, Prop_Send, "m_fEffects", effects | (1 << 2)); + } + } + else + { + // Spawn the light which only the user will see. + int ent = CreateEntityByName("light_dynamic"); + if (ent != -1) + { + TeleportEntity(ent, eyePos, eyeAng, NULL_VECTOR); + DispatchKeyValue(ent, "targetname", "WUBADUBDUBMOTHERBUCKERS"); + switch (g_PlayerPreferences[client].PlayerPreference_FlashlightTemperature) + { + case 1: + { + DispatchKeyValue(ent, "rendercolor", "255 150 50"); + } + case 2: + { + DispatchKeyValue(ent, "rendercolor", "255 210 100"); + } + case 3: + { + DispatchKeyValue(ent, "rendercolor", "255 255 120"); + } + case 4: + { + DispatchKeyValue(ent, "rendercolor", "255 255 185"); + } + case 5: + { + DispatchKeyValue(ent, "rendercolor", "255 255 210"); + } + case 6: + { + DispatchKeyValue(ent, "rendercolor", "255 255 255"); + } + case 7: + { + DispatchKeyValue(ent, "rendercolor", "210 255 255"); + } + case 8: + { + DispatchKeyValue(ent, "rendercolor", "185 255 255"); + } + case 9: + { + DispatchKeyValue(ent, "rendercolor", "150 255 255"); + } + case 10: + { + DispatchKeyValue(ent, "rendercolor", "125 255 255"); + } + } + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer) + { + SetVariantFloat(radius); + } + else + { + SetVariantFloat(doubleRadius); + } + } + else + { + float customRadius = radius * g_ClassFlashlightRadius[classToInt]; + SetVariantFloat(customRadius); + } + AcceptEntityInput(ent, "spotlight_radius"); + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer) + { + SetVariantFloat(length); + } + else + { + SetVariantFloat(doubleLength); + } + } + else + { + float customLength = length * g_ClassFlashlightLength[classToInt]; + SetVariantFloat(customLength); + } + AcceptEntityInput(ent, "distance"); + if (!IsClassConfigsValid()) + { + SetVariantInt(SF2_FLASHLIGHT_BRIGHTNESS); + } + else + { + int customBrightness = SF2_FLASHLIGHT_BRIGHTNESS + g_ClassFlashlightBrightness[classToInt]; + SetVariantInt(customBrightness); + } + AcceptEntityInput(ent, "brightness"); + + // Convert WU to inches. + float cone = 55.0; + cone *= 0.75; + + SetVariantInt(RoundToFloor(cone)); + AcceptEntityInput(ent, "_inner_cone"); + SetVariantInt(RoundToFloor(cone)); + AcceptEntityInput(ent, "_cone"); + DispatchSpawn(ent); + ActivateEntity(ent); + SetVariantString("!activator"); + AcceptEntityInput(ent, "SetParent", client); + AcceptEntityInput(ent, "TurnOn"); + + g_PlayerFlashlightEnt[client] = EntIndexToEntRef(ent); + + SDKHook(ent, SDKHook_SetTransmit, Hook_FlashlightSetTransmit); + } + } + + // Spawn the light that only everyone else will see. + /*int ent = CreateEntityByName("env_beam"); + if (ent != -1) + { + int startEnt = CreateEntityByName("info_target"); + int endEnt = CreateEntityByName("info_target"); + if (startEnt != -1) // Start + { + SetEntPropString(startEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_start"); + SetEntProp(startEnt, Prop_Data, "m_spawnflags", 1); + TeleportEntity(startEnt, eyePos, eyeAng, NULL_VECTOR); + SetVariantString("!activator"); + AcceptEntityInput(startEnt, "SetParent", client); + + DispatchSpawn(startEnt); + SetEntityOwner(startEnt, client); + + g_ClientFlashlightStartEntity[client] = EntIndexToEntRef(startEnt); + + SetEntityTransmitState(startEnt, FL_EDICT_FULLCHECK); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectUpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectShouldTransmit); + } + if (endEnt != -1) // End + { + SetEntPropString(endEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_end"); + SetEntProp(endEnt, Prop_Data, "m_spawnflags", 1); + TeleportEntity(endEnt, eyePos, eyeAng, NULL_VECTOR); + SetVariantString("!activator"); + AcceptEntityInput(endEnt, "SetParent", client); + + DispatchSpawn(endEnt); + SetEntityOwner(endEnt, client); + + g_ClientFlashlightEndEntity[client] = EntIndexToEntRef(endEnt); + + SetEntityTransmitState(endEnt, FL_EDICT_FULLCHECK); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectUpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectShouldTransmit); + } + + switch (g_PlayerPreferences[client].PlayerPreference_FlashlightTemperature) + { + case 1: + { + SetEntityRenderColor(ent, 255, 150, 50, 64); + } + case 2: + { + SetEntityRenderColor(ent, 255, 210, 100, 64); + } + case 3: + { + SetEntityRenderColor(ent, 255, 255, 120, 64); + } + case 4: + { + SetEntityRenderColor(ent, 255, 255, 185, 64); + } + case 5: + { + SetEntityRenderColor(ent, 255, 255, 210, 64); + } + case 6: + { + SetEntityRenderColor(ent, 255, 255, 255, 64); + } + case 7: + { + SetEntityRenderColor(ent, 210, 255, 255, 64); + } + case 8: + { + SetEntityRenderColor(ent, 185, 255, 255, 64); + } + case 9: + { + SetEntityRenderColor(ent, 150, 255, 255, 64); + } + case 10: + { + SetEntityRenderColor(ent, 125, 255, 255, 64); + } + } + + SetEntityModel(ent, SF2_FLASHLIGHT_BEAM_MATERIAL); + SetEntityRenderMode(ent, RENDER_TRANSTEXTURE); + SetEntPropFloat(ent, Prop_Data, "m_life", 0.0); + + TeleportEntity(ent, eyePos, eyeAng, NULL_VECTOR); + + DispatchSpawn(ent); + ActivateEntity(ent); + + SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", startEnt, 0); + SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", endEnt, 1); + SetEntProp(ent, Prop_Send, "m_nNumBeamEnts", 2); + SetEntProp(ent, Prop_Send, "m_nBeamType", 2); + + SetEntPropFloat(ent, Prop_Send, "m_fWidth", 40.0); + SetEntPropFloat(ent, Prop_Data, "m_fEndWidth", 102.0); + + SetEntPropFloat(ent, Prop_Send, "m_fFadeLength", 0.0); + SetEntProp(ent, Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); + SetEntPropFloat(ent, Prop_Send, "m_fHaloScale", 40.0); + SetEntProp(ent, Prop_Send, "m_nBeamFlags", 0x80 | 0x200); + SetEntProp(ent, Prop_Data, "m_spawnflags", 0x8000); + + AcceptEntityInput(ent, "TurnOn"); + + SetVariantString("!activator"); + AcceptEntityInput(ent, "SetParent", client); + + SetEntityOwner(ent, client); + + SDKHook(ent, SDKHook_SetTransmit, Hook_Flashlight2SetTransmit); + SetEntityTransmitState(ent, FL_EDICT_FULLCHECK); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectUpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectShouldTransmit); + + g_PlayerFlashlightEntAng[client] = EntIndexToEntRef(ent); + }*/ + + Call_StartForward(g_OnClientActivateFlashlightFwd); + Call_PushCell(client); + Call_Finish(); +} + +void Hook_OnFlashlightThink(int client) +{ + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + float length; + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer) + { + length = SF2_FLASHLIGHT_LENGTH; + } + else + { + length = SF2_FLASHLIGHT_LENGTH * 3.0; + } + } + else + { + length = SF2_FLASHLIGHT_LENGTH * g_ClassFlashlightLength[classToInt]; + } + if (IsClientUsingFlashlight(client)) + { + int endEnt = EntRefToEntIndex(g_ClientFlashlightEndEntity[client]); + if (IsValidEntity(endEnt)) + { + float entPos[3], entRot[3], tempRot[3], endPos[3]; + GetClientEyePosition(client, entPos); + GetClientEyeAngles(client, entRot); + GetEntPropVector(client, Prop_Data, "m_angAbsRotation", tempRot); + tempRot[0] = 0.0; + tempRot[2] = 0.0; + AddVectors(entRot, tempRot, entRot); + GetAngleVectors(entRot, entRot, NULL_VECTOR, NULL_VECTOR); + endPos = entRot; + ScaleVector(endPos, length); + AddVectors(endPos, entPos, endPos); + + CBaseEntity spotlightEnd = CBaseEntity(endEnt); + if (spotlightEnd.IsValid()) + { + TR_TraceRayFilter(entPos, endPos, MASK_SOLID_BRUSHONLY, RayType_EndPoint, TraceRayDontHitEntity, client); + + float hitPos[3]; + TR_GetEndPosition(hitPos); + + hitPos[2] += 20.0; + + spotlightEnd.SetAbsOrigin(hitPos); + } + } + } +} + +/** + * Turns off the player's flashlight. Nothing else. + */ +void ClientTurnOffFlashlight(int client) +{ + if (!IsClientUsingFlashlight(client)) + { + return; + } + + g_PlayerHasFlashlight[client] = false; + g_PlayerFlashlightBatteryTimer[client] = null; + + // Remove user-only light. + int ent = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + AcceptEntityInput(ent, "TurnOff"); + RemoveEntity(ent); + } + + // Remove everyone-else-only light. + ent = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + AcceptEntityInput(ent, "TurnOff"); + CreateTimer(0.1, Timer_KillEntity, g_PlayerFlashlightEntAng[client], TIMER_FLAG_NO_MAPCHANGE); + } + ent = EntRefToEntIndex(g_ClientFlashlightStartEntity[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + CreateTimer(0.1, Timer_KillEntity, g_ClientFlashlightStartEntity[client], TIMER_FLAG_NO_MAPCHANGE); + } + ent = EntRefToEntIndex(g_ClientFlashlightEndEntity[client]); + if (ent && ent != INVALID_ENT_REFERENCE) + { + CreateTimer(0.1, Timer_KillEntity, g_ClientFlashlightEndEntity[client], TIMER_FLAG_NO_MAPCHANGE); + } + + g_PlayerFlashlightEnt[client] = INVALID_ENT_REFERENCE; + g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; + g_ClientFlashlightStartEntity[client] = INVALID_ENT_REFERENCE; + g_ClientFlashlightEndEntity[client] = INVALID_ENT_REFERENCE; + + if (IsValidClient(client)) + { + if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) + { + int effects = GetEntProp(client, Prop_Send, "m_fEffects"); + if (effects & (1 << 2)) + { + SetEntProp(client, Prop_Send, "m_fEffects", effects &= ~(1 << 2)); + } + } + } + + Call_StartForward(g_OnClientDeactivateFlashlightFwd); + Call_PushCell(client); + Call_Finish(); +} + +void ClientStartRechargingFlashlightBattery(int client) +{ + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + float rechargeRate = SF2_FLASHLIGHT_RECHARGE_RATE; + if (!IsClassConfigsValid()) + { + if (class == TFClass_Engineer) + { + rechargeRate *= 0.8; + } + } + else + { + rechargeRate *= g_ClassFlashlightRechargeRate[classToInt]; + } + + g_PlayerFlashlightBatteryTimer[client] = CreateTimer(rechargeRate, Timer_RechargeFlashlight, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); +} + +void ClientStartDrainingFlashlightBattery(int client) +{ + float drainRate = SF2_FLASHLIGHT_DRAIN_RATE; + + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + + if (!IsClassConfigsValid()) + { + if (class == TFClass_Engineer) + { + // Engineers have a 50% longer battery life and 20% decreased recharge rate, basically. + drainRate *= 1.5; + } + } + else + { + drainRate *= g_ClassFlashlightDrainRate[classToInt]; + } + + g_PlayerFlashlightBatteryTimer[client] = CreateTimer(drainRate, Timer_DrainFlashlight, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); +} + +void ClientHandleFlashlight(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client) || (TF2_IsPlayerInCondition(client, TFCond_Taunting) && !IsClientUsingFlashlight(client))) + { + return; + } + + bool nightVision = (g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)); + + if (IsClientUsingFlashlight(client) || TF2_IsPlayerInCondition(client, TFCond_Taunting)) + { + ClientTurnOffFlashlight(client); + ClientStartRechargingFlashlightBattery(client); + ClientDeactivateUltravision(client); + ClientActivateUltravision(client); + + g_PlayerFlashlightNextInputTime[client] = GetGameTime() + SF2_FLASHLIGHT_COOLDOWN; + + if (!nightVision) + { + if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) + { + EmitSoundToAll(FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + } + else + { + EmitSoundToClient(client, FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + } + } + } + else + { + // Only players in the "game" can use the flashlight. + if (!g_PlayerEliminated[client]) + { + bool canUseFlashlight = true; + if (SF_SpecialRound(SPECIALROUND_LIGHTSOUT) || SF_IsRaidMap() || SF_IsBoxingMap()) + { + // Unequip the flashlight please. + canUseFlashlight = false; + } + + if (!IsClientFlashlightBroken(client) && canUseFlashlight) + { + ClientDeactivateUltravision(client); + if (nightVision) + { + g_PlayerHasFlashlight[client] = true; + ClientActivateUltravision(client, nightVision); + } + else + { + ClientTurnOnFlashlight(client); + } + ClientStartDrainingFlashlightBattery(client); + + g_PlayerFlashlightNextInputTime[client] = GetGameTime(); + if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) + { + EmitSoundToAll((nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + } + else + { + EmitSoundToClient(client, (nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + } + } + else + { + EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); + } + } + } +} + +/* +void ClientSDKFlashlightTurnOn(int client) +{ + if (!IsValidClient(client)) + { + return; + } + + int effects = GetEntProp(client, Prop_Send, "m_fEffects"); + if (effects & EF_DIMLIGHT) + { + return; + } + + effects |= EF_DIMLIGHT; + + SetEntProp(client, Prop_Send, "m_fEffects", effects); +} + +void ClientSDKFlashlightTurnOff(int client) +{ + if (!IsValidClient(client)) + { + return; + } + + int effects = GetEntProp(client, Prop_Send, "m_fEffects"); + if (!(effects & EF_DIMLIGHT)) + { + return; + } + + effects &= ~EF_DIMLIGHT; + + SetEntProp(client, Prop_Send, "m_fEffects", effects); +} +*/ \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/ghostmode.sp b/addons/sourcemod/scripting/sf2/client/ghostmode.sp new file mode 100644 index 00000000..6cd6f89e --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/ghostmode.sp @@ -0,0 +1,483 @@ +#pragma semicolon 1 + +#define SF2_OVERLAY_GHOST "overlays/slender/ghostcamera" + +static bool g_PlayerInGhostMode[MAXTF2PLAYERS] = { false, ... }; +static int g_PlayerGhostModeTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerGhostModeBossTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; +Handle g_PlayerGhostModeConnectionCheckTimer[MAXTF2PLAYERS] = { null, ... }; +float g_PlayerGhostModeConnectionTimeOutTime[MAXTF2PLAYERS] = { -1.0, ... }; +float g_PlayerGhostModeConnectionBootTime[MAXTF2PLAYERS] = { -1.0, ... }; + +void SetupGhost() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerTeamPFwd.AddFunction(null, OnPlayerTeam); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientSetGhostModeState(client.index, false); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!IsClientParticipating(client.index)) + { + ClientSetGhostModeState(client.index, false); + } + ClientHandleGhostMode(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake || IsRoundInWarmup() || client.IsEliminated) + { + return; + } + + if (g_PlayerPreferences[client.index].PlayerPreference_GhostModeToggleState == 2) + { + CreateTimer(0.25, Timer_ToggleGhostModeCommand, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientHandleGhostMode(client.index); +} + +static void OnPlayerTeam(SF2_BasePlayer client, int team) +{ + if (team <= TFTeam_Spectator) + { + ClientSetGhostModeState(client.index, false); + } +} + +bool IsClientInGhostMode(int client) +{ + return g_PlayerInGhostMode[client]; +} + +/** + * Enables/Disables ghost mode on the player. + */ +void ClientSetGhostModeState(int client, bool state) +{ + if (state == g_PlayerInGhostMode[client]) + { + return; + } + + Handle message = StartMessageAll("PlayerTauntSoundLoopEnd", USERMSG_RELIABLE); + BfWriteByte(message, client); + delete message; + EndMessage(); + + if (state && !IsValidClient(client)) + { + return; + } + + g_PlayerInGhostMode[client] = state; + g_PlayerGhostModeTarget[client] = INVALID_ENT_REFERENCE; + g_PlayerGhostModeBossTarget[client] = INVALID_ENT_REFERENCE; + + if (state) + { + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Entered ghost mode."); + #endif + //Strip the always edict flag + SetEntityFlags(client, GetEntityFlags(client)^FL_EDICT_ALWAYS); + //Remove the fire cond + TF2_RemoveCondition(client,TFCond_OnFire); + //Call the spawn event. + TF2_RespawnPlayer(client); + TF2_RemoveCondition(client,TFCond_Taunting); + + TFClassType desiredClass = TF2_GetPlayerClass(client); + if (desiredClass == TFClass_Unknown) + { + desiredClass = TFClass_Spy; + } + + //Set player's class to spy, this replaces old ghost mode mechanics. + TF2_SetPlayerClass(client, TFClass_Spy); + TF2_RegeneratePlayer(client); + + //Set player's old class as desired class. + SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", desiredClass); + + ClientHandleGhostMode(client, true); + if (g_GhostModeConnectionCheckConVar.BoolValue) + { + g_PlayerGhostModeConnectionCheckTimer[client] = CreateTimer(0.0, Timer_GhostModeConnectionCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; + g_PlayerGhostModeConnectionBootTime[client] = -1.0; + } + + SetEntityCollisionGroup(client, 1); + + PvP_OnClientGhostModeEnable(client); + } + else + { + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Exited ghost mode."); + #endif + TF2Attrib_SetByName(client, "mod see enemy health", 0.0); + g_PlayerGhostModeConnectionCheckTimer[client] = null; + g_PlayerGhostModeConnectionTimeOutTime[client] = -1.0; + g_PlayerGhostModeConnectionBootTime[client] = -1.0; + + if (IsValidClient(client)) + { + SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_YES); + TF2_RemoveCondition(client, TFCond_Stealthed); + SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 1); + SetEntityGravity(client, 1.0); + SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); + SetEntityFlags(client, GetEntityFlags(client) &~ FL_NOTARGET); + SetEntProp(client, Prop_Data, "m_usSolidFlags", 16); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + SetEntityRenderMode(client, RENDER_NORMAL); + SetEntityRenderColor(client, _, _, _, 255); + SetEntityCollisionGroup(client, COLLISION_GROUP_PLAYER); + } + } + for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + { + if (NPCGetUniqueID(npcIndex) == -1) + { + continue; + } + SlenderRemoveGlow(npcIndex); + if (NPCGetCustomOutlinesState(npcIndex)) + { + if (!NPCGetRainbowOutlineState(npcIndex)) + { + int color[4]; + color[0] = NPCGetOutlineColorR(npcIndex); + color[1] = NPCGetOutlineColorG(npcIndex); + color[2] = NPCGetOutlineColorB(npcIndex); + color[3] = NPCGetOutlineTransparency(npcIndex); + if (color[0] < 0) + { + color[0] = 0; + } + if (color[1] < 0) + { + color[1] = 0; + } + if (color[2] < 0) + { + color[2] = 0; + } + if (color[3] < 0) + { + color[3] = 0; + } + if (color[0] > 255) + { + color[0] = 255; + } + if (color[1] > 255) + { + color[1] = 255; + } + if (color[2] > 255) + { + color[2] = 255; + } + if (color[3] > 255) + { + color[3] = 255; + } + SlenderAddGlow(npcIndex, color); + } + else + { + SlenderAddGlow(npcIndex, view_as({0, 0, 0, 0})); + } + } + else + { + int purple[4] = {150, 0, 255, 255}; + SlenderAddGlow(npcIndex, purple); + } + } + + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + ClientDisableConstantGlow(i); + if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) + { + int red[4] = {184, 56, 59, 255}; + ClientEnableConstantGlow(i, red); + } + else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) + { + int yellow[4] = {255, 208, 0, 255}; + ClientEnableConstantGlow(i, yellow); + } + } +} + +/** + * Makes sure that the player is a ghost when ghost mode is activated. + */ +void ClientHandleGhostMode(int client, bool forceSpawn = false) +{ + if (!IsClientInGhostMode(client)) + { + return; + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientHandleGhostMode(%d, %d)", client, forceSpawn); + } + #endif + + if (!TF2_IsPlayerInCondition(client, TFCond_Stealthed) || forceSpawn) + { + TF2_StripWearables(client); + DestroyAllActiveWeapons(client); + TF2_DestroySpyWeapons(); + SetEntityGravity(client, 0.5); + TF2_AddCondition(client, TFCond_Stealthed, -1.0); + SetEntProp(client, Prop_Send, "m_bDrawViewmodel", 0); + SetEntProp(client, Prop_Data, "m_takedamage", DAMAGE_NO); + SetEntProp(client, Prop_Send, "m_usSolidFlags", 4); + SetEntProp(client, Prop_Data, "m_nSolidType", 0); + SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); + SetEntProp(client, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityFlags(client, GetEntityFlags(client) | FL_NOTARGET); + SetEntityRenderMode(client, RENDER_TRANSCOLOR); + SetEntityRenderColor(client, _, _, _, 0); + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + + // Set first observer target. + ClientGhostModeNextTarget(client, true); + ClientActivateUltravision(client); + + // screen overlay timer + g_PlayerOverlayCheck[client] = CreateTimer(0.0, Timer_PlayerOverlayCheck, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerOverlayCheck[client], true); + + CreateTimer(0.2, Timer_ClientGhostStripWearables, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientHandleGhostMode(%d, %d)", client, forceSpawn); + } + #endif +} + +static Action Timer_ClientGhostStripWearables(Handle timer, int userid) +{ + int client = GetClientOfUserId(userid); + if (!IsValidClient(client)) + { + return Plugin_Stop; + } + if (!IsClientInGhostMode(client)) + { + return Plugin_Stop; + } + TF2_StripWearables(client); + DestroyAllActiveWeapons(client); + TF2_DestroySpyWeapons(); + return Plugin_Stop; +} + +void ClientGhostModeNextTarget(int client, bool ignoreSetting = false) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientGhostModeNextTarget(%d)", client); + } + #endif + + if (g_PlayerPreferences[client].PlayerPreference_GhostModeTeleportState == 0 || ignoreSetting) + { + int lastTarget = EntRefToEntIndex(g_PlayerGhostModeTarget[client]); + int nextTarget = -1; + int firstTarget = -1; + for (int i = 1; i < MaxClients; i++) + { + if (IsValidClient(i) && (!g_PlayerEliminated[i] || g_PlayerProxy[i]) && !IsClientInGhostMode(i) && !DidClientEscape(i) && IsPlayerAlive(i)) + { + if (firstTarget == -1) + { + firstTarget = i; + } + if (i > lastTarget) + { + nextTarget = i; + break; + } + } + } + + int target = -1; + if (IsValidClient(nextTarget)) + { + target = nextTarget; + } + else + { + target = firstTarget; + } + + if (IsValidClient(target)) + { + g_PlayerGhostModeTarget[client] = EntIndexToEntRef(target); + + float pos[3], ang[3], velocity[3]; + GetClientAbsOrigin(target, pos); + GetClientEyeAngles(target, ang); + GetEntPropVector(target, Prop_Data, "m_vecAbsVelocity", velocity); + TeleportEntity(client, pos, ang, velocity); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientGhostModeNextTarget(%d)", client); + } + #endif + } + else + { + int lastTarget = NPCGetFromEntIndex(EntRefToEntIndex(g_PlayerGhostModeBossTarget[client])); + int nextTarget = -1; + int firstTarget = -1; + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + if (NPCGetUniqueID(bossIndex) == -1 || !IsValidEntity(NPCGetEntIndex(bossIndex))) + { + continue; + } + + if (firstTarget == -1) + { + firstTarget = bossIndex; + } + if (bossIndex > lastTarget) + { + nextTarget = bossIndex; + break; + } + } + + int target = -1; + if (nextTarget != - 1 && NPCGetEntIndex(nextTarget) && NPCGetEntIndex(nextTarget) != INVALID_ENT_REFERENCE) + { + target = nextTarget; + } + else + { + target = firstTarget; + } + + if (target != -1 && IsValidEntity(NPCGetEntIndex(target))) + { + g_PlayerGhostModeBossTarget[client] = EntIndexToEntRef(NPCGetEntIndex(target)); + + float pos[3], ang[3], velocity[3]; + GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_angAbsRotation", ang); + GetEntPropVector(NPCGetEntIndex(target), Prop_Data, "m_vecAbsVelocity", velocity); + TeleportEntity(client, pos, ang, velocity); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientGhostModeNextTarget(%d)", client); + } + #endif + } +} + +Action Hook_GhostNoTouch(int entity, int other) +{ + if (IsValidClient(other)) + { + if (IsClientInGhostMode(other)) + { + return Plugin_Handled; + } + } + return Plugin_Continue; +} + +Action Timer_GhostModeConnectionCheck(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerGhostModeConnectionCheckTimer[client]) + { + return Plugin_Stop; + } + + if (!IsFakeClient(client) && IsClientTimingOut(client)) + { + float bootTime = g_PlayerGhostModeConnectionBootTime[client]; + bool checkBool = g_GhostModeConnectionConVar.BoolValue; + if (bootTime < 0.0 && !checkBool) + { + bootTime = GetGameTime() + g_GhostModeConnectionToleranceConVar.FloatValue; + g_PlayerGhostModeConnectionBootTime[client] = bootTime; + g_PlayerGhostModeConnectionTimeOutTime[client] = GetGameTime(); + } + + if (GetGameTime() >= bootTime || checkBool) + { + ClientSetGhostModeState(client, false); + TF2_RespawnPlayer(client); + + char authString[128]; + GetClientAuthId(client,AuthId_Engine, authString, sizeof(authString)); + + LogSF2Message("Removed %N (%s) from ghost mode due to timing out for %f seconds", client, authString, g_GhostModeConnectionToleranceConVar.FloatValue); + + float timeOutTime = g_PlayerGhostModeConnectionTimeOutTime[client]; + CPrintToChat(client, "\x08FF4040FF%T", "SF2 Ghost Mode Bad Connection", client, RoundFloat(bootTime - timeOutTime)); + + return Plugin_Stop; + } + } + else + { + // Player regained connection; reset. + g_PlayerGhostModeConnectionBootTime[client] = -1.0; + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/client/hints.sp b/addons/sourcemod/scripting/sf2/client/hints.sp new file mode 100644 index 00000000..e4ea9b8d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/hints.sp @@ -0,0 +1,84 @@ +#if defined _sf2_client_hints_included + #endinput +#endif +#define _sf2_client_hints_included + +bool g_PlayerHints[MAXTF2PLAYERS][PlayerHint_MaxNum]; + +void SetupHints() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetHints(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetHints(client.index); +} + +// Hint data. +enum +{ + PlayerHint_Sprint = 0, + PlayerHint_Flashlight, + PlayerHint_MainMenu, + PlayerHint_Blink, + PlayerHint_Trap, + PlayerHint_MaxNum +}; + +void ClientResetHints(int client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetHints(%d)", client); + } + #endif + + for (int i = 0; i < PlayerHint_MaxNum; i++) + { + g_PlayerHints[client][i] = false; + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetHints(%d)", client); + } + #endif +} + +void ClientShowHint(int client,int hint) +{ + g_PlayerHints[client][hint] = true; + + switch (hint) + { + case PlayerHint_Sprint: + { + PrintHintText(client, "%T", "SF2 Hint Sprint", client); + } + case PlayerHint_Flashlight: + { + PrintHintText(client, "%T", "SF2 Hint Flashlight", client); + } + case PlayerHint_Blink: + { + PrintHintText(client, "%T", "SF2 Hint Blink", client); + } + case PlayerHint_MainMenu: + { + PrintHintText(client, "%T", "SF2 Hint Main Menu", client); + } + case PlayerHint_Trap: + { + PrintHintText(client, "%T", "SF2 Hint Trap", client); + } + } +} diff --git a/addons/sourcemod/scripting/sf2/client/music.sp b/addons/sourcemod/scripting/sf2/client/music.sp new file mode 100644 index 00000000..eae740bf --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/music.sp @@ -0,0 +1,1777 @@ +#if defined _sf2_client_music_included + #endinput +#endif +#define _sf2_client_music_included + +#pragma semicolon 1 + +void SetupMusic() +{ + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientUpdateMusicSystem(client.index); + ClientResetChannels(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetChannels(client.index); + + ClientUpdateMusicSystem(client.index); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientUpdateMusicSystem(client.index); + ClientResetChannels(client.index); +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientUpdateMusicSystem(client.index); +} + +static void OnBossRemoved(int bossIndex) +{ + if (!MusicActive()) + { + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + + // Remove chase music. + if (g_PlayerChaseMusicMaster[i] == bossIndex) + { + ClientChaseMusicReset(i); + } + + // Don't forget search theme + if (g_PlayerAlertMusicMaster[i] == bossIndex) + { + ClientAlertMusicReset(i); + } + + if (g_PlayerChaseMusicSeeMaster[i] == bossIndex) + { + ClientChaseMusicSeeReset(i); + } + + if (g_PlayerIdleMusicMaster[i] == bossIndex) + { + ClientIdleMusicReset(i); + } + + ClientUpdateMusicSystem(i); + } + } +} + +void ClientResetChannels(int client) +{ + ClientChaseMusicReset(client); + ClientChaseMusicSeeReset(client); + ClientAlertMusicReset(client); + ClientIdleMusicReset(client); + Client90sMusicReset(client); + ClientMusicReset(client); +} + +void ClientUpdateMusicSystem(int client, bool initialize=false) +{ + int oldPageMusicMaster = EntRefToEntIndex(g_PlayerPageMusicMaster[client]); + int oldPageMusicActiveIndex = g_PageMusicActiveIndex[client]; + int oldMusicFlags = g_PlayerMusicFlags[client]; + int chasingBoss = -1; + int chasingSeeBoss = -1; + int alertBoss = -1; + int idleBoss = -1; + + if (IsRoundEnding() || !IsValidClient(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) + { + g_PlayerMusicFlags[client] = 0; + g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; + g_PageMusicActiveIndex[client] = -1; + + if (MusicActive()) //A boss is overriding the music. + { + char path[PLATFORM_MAX_PATH]; + GetBossMusic(path,sizeof(path)); + if (path[0] != '\0') + { + StopSound(client, MUSIC_CHAN, path); + } + } + } + else + { + bool playMusicOnEscape = !g_RoundStopPageMusicOnEscape; + + // Page music first. + int pageRange = 0; + + if (g_PageMusicRanges.Length > 0) // Map has its own defined page music? + { + for (int i = 0, size = g_PageMusicRanges.Length; i < size; i++) + { + int ent = EntRefToEntIndex(g_PageMusicRanges.Get(i)); + if (!ent || ent == INVALID_ENT_REFERENCE) + { + continue; + } + + int min = g_PageMusicRanges.Get(i, 1); + int max = g_PageMusicRanges.Get(i, 2); + + if (g_PageCount >= min && g_PageCount <= max) + { + g_PlayerPageMusicMaster[client] = g_PageMusicRanges.Get(i); + g_PageMusicActiveIndex[client] = i; + break; + } + } + } + else // Nope. Use old system instead. + { + g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; + g_PageMusicActiveIndex[client] = -1; + + float percent = g_PageMax > 0 ? (float(g_PageCount) / float(g_PageMax)) : 0.0; + if (percent > 0.0 && percent <= 0.25) + { + pageRange = 1; + } + else if (percent > 0.25 && percent <= 0.5) + { + pageRange = 2; + } + else if (percent > 0.5 && percent <= 0.75) + { + pageRange = 3; + } + else if (percent > 0.75) + { + pageRange = 4; + } + + if (pageRange == 1) + { + ClientAddMusicFlag(client, MUSICF_PAGES1PERCENT); + } + else if (pageRange == 2) + { + ClientAddMusicFlag(client, MUSICF_PAGES25PERCENT); + } + else if (pageRange == 3) + { + ClientAddMusicFlag(client, MUSICF_PAGES50PERCENT); + } + else if (pageRange == 4) + { + ClientAddMusicFlag(client, MUSICF_PAGES75PERCENT); + } + } + + if (pageRange != 1) + { + ClientRemoveMusicFlag(client, MUSICF_PAGES1PERCENT); + } + if (pageRange != 2) + { + ClientRemoveMusicFlag(client, MUSICF_PAGES25PERCENT); + } + if (pageRange != 3) + { + ClientRemoveMusicFlag(client, MUSICF_PAGES50PERCENT); + } + if (pageRange != 4) + { + ClientRemoveMusicFlag(client, MUSICF_PAGES75PERCENT); + } + + if (IsRoundInEscapeObjective() && !playMusicOnEscape) + { + ClientRemoveMusicFlag(client, MUSICF_PAGES75PERCENT); + g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; + g_PageMusicActiveIndex[client] = -1; + } + + int oldChasingBoss = g_PlayerChaseMusicMaster[client]; + int oldChasingSeeBoss = g_PlayerChaseMusicSeeMaster[client]; + int oldAlertBoss = g_PlayerAlertMusicMaster[client]; + int oldIdleBoss = g_PlayerIdleMusicMaster[client]; + + float buffer[3], buffer2[3], buffer3[3]; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if (NPCGetEntIndex(i) == INVALID_ENT_REFERENCE) + { + continue; + } + + NPCGetProfile(i, profile, sizeof(profile)); + + int bossType = NPCGetType(i); + + switch (bossType) + { + case SF2BossType_Chaser: + { + SF2BossProfileSoundInfo soundInfo; + ArrayList soundList; + GetClientAbsOrigin(client, buffer); + SlenderGetAbsOrigin(i, buffer3); + + int target = EntRefToEntIndex(g_SlenderTarget[i]); + if (target != -1) + { + GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", buffer2); + + if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && + !(NPCGetFlags(i) & SFF_MARKEDASFAKE)) + { + GetChaserProfileChaseMusics(profile, soundInfo); + + if ((target == client || GetVectorSquareMagnitude(buffer, buffer2) <= SquareFloat(soundInfo.Radius) || + GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius))) + { + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + chasingBoss = i; + } + + if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && + PlayerCanSeeSlender(client, i, false)) + { + GetChaserProfileChaseVisibleMusics(profile, soundInfo); + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + chasingSeeBoss = i; + } + } + } + } + } + + if (g_SlenderState[i] == STATE_ALERT) + { + GetChaserProfileAlertMusics(profile, soundInfo); + soundList = soundInfo.Paths; + if (soundList == null || soundList.Length <= 0) + { + continue; + } + + if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) + { + if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) + { + alertBoss = i; + } + } + } + + if (g_SlenderState[i] == STATE_IDLE || g_SlenderState[i] == STATE_WANDER) + { + GetChaserProfileIdleMusics(profile, soundInfo); + soundList = soundInfo.Paths; + if (soundList == null || soundList.Length <= 0) + { + continue; + } + + if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) + { + if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) + { + idleBoss = i; + } + } + } + soundList = null; + } + } + } + + if (chasingBoss != oldChasingBoss) + { + if (chasingBoss != -1) + { + ClientAddMusicFlag(client, MUSICF_CHASE); + } + else + { + ClientRemoveMusicFlag(client, MUSICF_CHASE); + } + } + + if (chasingSeeBoss != oldChasingSeeBoss) + { + if (chasingSeeBoss != -1) + { + ClientAddMusicFlag(client, MUSICF_CHASEVISIBLE); + } + else + { + ClientRemoveMusicFlag(client, MUSICF_CHASEVISIBLE); + } + } + + if (alertBoss != oldAlertBoss) + { + if (alertBoss != -1) + { + ClientAddMusicFlag(client, MUSICF_ALERT); + } + else + { + ClientRemoveMusicFlag(client, MUSICF_ALERT); + } + } + + if (idleBoss != oldIdleBoss) + { + if (idleBoss != -1) + { + ClientAddMusicFlag(client, MUSICF_IDLE); + } + else + { + ClientRemoveMusicFlag(client, MUSICF_IDLE); + } + } + } + + if (IsValidClient(client)) + { + bool wasChase = ClientHasMusicFlag2(oldMusicFlags, MUSICF_CHASE); + bool inChase = ClientHasMusicFlag(client, MUSICF_CHASE); + bool wasChaseSee = ClientHasMusicFlag2(oldMusicFlags, MUSICF_CHASEVISIBLE); + bool inChaseSee = ClientHasMusicFlag(client, MUSICF_CHASEVISIBLE); + bool inAlert = ClientHasMusicFlag(client, MUSICF_ALERT); + bool wasAlert = ClientHasMusicFlag2(oldMusicFlags, MUSICF_ALERT); + bool inIdle = ClientHasMusicFlag(client, MUSICF_IDLE); + bool wasIdle = ClientHasMusicFlag2(oldMusicFlags, MUSICF_IDLE); + char path[PLATFORM_MAX_PATH]; + + if (IsRoundEnding() || !IsValidClient(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) + { + } + else if (MusicActive()) //A boss is overriding the music. + { + GetBossMusic(path,sizeof(path)); + ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME); + return; + } + + // Custom system. + if (g_PageMusicRanges.Length > 0) + { + int master = EntRefToEntIndex(g_PlayerPageMusicMaster[client]); + int pageMusicActiveIndex = g_PageMusicActiveIndex[client]; + + if (pageMusicActiveIndex != oldPageMusicActiveIndex) + { + // Page music was changed. + + // Stop the old music. + if (oldPageMusicActiveIndex != -1 && oldPageMusicMaster && oldPageMusicMaster != INVALID_ENT_REFERENCE) + { + int oldPageRangeMin = g_PageMusicRanges.Get(oldPageMusicActiveIndex, 1); + + SF2PageMusicEntity oldPageMusic = SF2PageMusicEntity(oldPageMusicMaster); + if (oldPageMusic.IsValid()) + { + oldPageMusic.GetRangeMusic(oldPageRangeMin, path, sizeof(path)); + } + else + { + GetEntPropString(oldPageMusicMaster, Prop_Data, "m_iszSound", path, sizeof(path)); + } + + if (path[0] != '\0') + { + StopSound(client, MUSIC_CHAN, path); + } + } + + // Play new music. + if (pageMusicActiveIndex != -1 && master && master != INVALID_ENT_REFERENCE) + { + int pageRangeMin = g_PageMusicRanges.Get(pageMusicActiveIndex, 1); + int pageRangeMax = g_PageMusicRanges.Get(pageMusicActiveIndex, 2); + + SF2PageMusicEntity pageMusic = SF2PageMusicEntity(master); + if (pageMusic.IsValid()) + { + pageMusic.GetRangeMusic(g_PageCount, path, sizeof(path)); + } + else + { + GetEntPropString(master, Prop_Data, "m_iszSound", path, sizeof(path)); + } + + // Play the new music. + if (path[0] == '\0') + { + LogError("Could not play music of page range %d - %d: no valid sound path specified!", pageRangeMin, pageRangeMax); + } + else + { + ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME, inChase || inAlert); + } + } + } + } + else + { + // Old system. + if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES1PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES1PERCENT)) + { + StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES1_SOUND); + } + else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES1PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES1PERCENT)) + { + ClientMusicStart(client, MUSIC_GOTPAGES1_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); + } + + if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES25PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES25PERCENT)) + { + StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES2_SOUND); + } + else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES25PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES25PERCENT)) + { + ClientMusicStart(client, MUSIC_GOTPAGES2_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); + } + + if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES50PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES50PERCENT)) + { + StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES3_SOUND); + } + else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES50PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES50PERCENT)) + { + ClientMusicStart(client, MUSIC_GOTPAGES3_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); + } + + if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES75PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES75PERCENT)) + { + StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES4_SOUND); + } + else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES75PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES75PERCENT)) + { + ClientMusicStart(client, MUSIC_GOTPAGES4_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); + } + } + + int mainMusicState = 0; + + if (inIdle != wasIdle || idleBoss != g_PlayerIdleMusicMaster[client]) + { + if (inIdle && !inAlert && !inChase) + { + ClientIdleMusicStart(client, idleBoss); + if (!wasIdle) + { + mainMusicState = -1; + } + } + else + { + ClientIdleMusicStop(client, g_PlayerIdleMusicMaster[client]); + if (!inChase && !inAlert && wasIdle) + { + mainMusicState = 1; + } + } + } + + if (inAlert != wasAlert || alertBoss != g_PlayerAlertMusicMaster[client]) + { + if (inAlert && !inChase) + { + ClientAlertMusicStart(client, alertBoss); + if (!wasAlert) + { + mainMusicState = -1; + } + } + else + { + ClientAlertMusicStop(client, g_PlayerAlertMusicMaster[client]); + if (!inChase && wasAlert) + { + mainMusicState = 1; + } + } + } + + if (inChase != wasChase || chasingBoss != g_PlayerChaseMusicMaster[client]) + { + if (inChase) + { + ClientMusicChaseStart(client, chasingBoss); + + if (!wasChase) + { + mainMusicState = -1; + + if (inAlert) + { + ClientAlertMusicStop(client, g_PlayerAlertMusicMaster[client]); + } + else if (inIdle) + { + ClientIdleMusicStop(client, g_PlayerIdleMusicMaster[client]); + } + } + } + else + { + ClientMusicChaseStop(client, g_PlayerChaseMusicMaster[client]); + if (wasChase) + { + if (inAlert) + { + ClientAlertMusicStart(client, alertBoss); + } + else if (inIdle) + { + ClientIdleMusicStart(client, idleBoss); + } + else + { + mainMusicState = 1; + } + } + } + } + + if (inChaseSee != wasChaseSee || chasingSeeBoss != g_PlayerChaseMusicSeeMaster[client]) + { + if (inChaseSee) + { + ClientMusicChaseSeeStart(client, chasingSeeBoss); + } + else + { + ClientMusicChaseSeeStop(client, g_PlayerChaseMusicSeeMaster[client]); + } + } + + if (mainMusicState == 1) + { + ClientMusicStart(client, g_PlayerMusicString[client], _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); + } + else if (mainMusicState == -1) + { + ClientMusicStop(client); + } + + if (inChase || inAlert) //Lets not add an idle variable, this whole if statement just adds stress. + { + int bossToUse = -1; + if (inChase) + { + bossToUse = chasingBoss; + } + else + { + bossToUse = alertBoss; + } + + if (bossToUse != -1) + { + // We got some alert/chase music going on! The player's excitement will no doubt go up! + // Excitement, though, really depends on how close the boss is in relation to the + // player. + + float bossDist = NPCGetDistanceFromEntity(bossToUse, client); + float scalar = (bossDist / SquareFloat(700.0)); + if (scalar > 1.0) + { + scalar = 1.0; + } + float stressAdd = 0.1 * (1.0 - scalar); + + ClientAddStress(client, stressAdd); + } + } + } +} + +void ClientMusicReset(int client) +{ + char oldMusic[PLATFORM_MAX_PATH]; + strcopy(oldMusic, sizeof(oldMusic), g_PlayerMusicString[client]); + g_PlayerMusicString[client][0] = '\0'; + if (IsValidClient(client) && oldMusic[0] != '\0') + { + StopSound(client, MUSIC_CHAN, oldMusic); + } + + g_PlayerMusicFlags[client] = 0; + g_PlayerMusicVolume[client] = 0.0; + g_PlayerMusicTargetVolume[client] = 0.0; + + g_PlayerMusicTimer[client] = null; + g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; + g_PageMusicActiveIndex[client] = -1; +} + +void ClientMusicStart(int client, const char[] newMusic, float volume=-1.0, float targetVolume=-1.0, bool copyOnly=false) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + if (newMusic[0] == '\0') + { + return; + } + + char oldMusic[PLATFORM_MAX_PATH]; + strcopy(oldMusic, sizeof(oldMusic), g_PlayerMusicString[client]); + + if (strcmp(oldMusic, newMusic, false) != 0 && oldMusic[0] != '\0') + { + StopSound(client, MUSIC_CHAN, oldMusic); + } + strcopy(g_PlayerMusicString[client], sizeof(g_PlayerMusicString[]), newMusic); + if (MusicActive())//A boss is overriding the music. + { + GetBossMusic(g_PlayerMusicString[client],sizeof(g_PlayerMusicString[])); + } + if (volume >= 0.0) + { + g_PlayerMusicVolume[client] = volume; + } + if (targetVolume >= 0.0) + { + g_PlayerMusicTargetVolume[client] = targetVolume; + } + + if (!copyOnly) + { + bool playMusicOnEscape = !g_RoundStopPageMusicOnEscape; + + if (g_PageCount < g_PageMax || playMusicOnEscape) + { + g_PlayerMusicTimer[client] = CreateTimer(0.01, Timer_PlayerFadeInMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerMusicTimer[client], true); + } + } + else + { + g_PlayerMusicTimer[client] = null; + } +} + +void ClientMusicStop(int client) +{ + g_PlayerMusicTimer[client] = CreateTimer(0.01, Timer_PlayerFadeOutMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerMusicTimer[client], true); +} + +void ClientAlertMusicReset(int client) +{ + g_PlayerAlertMusicMaster[client] = -1; + g_PlayerAlertMusicOldMaster[client] = -1; + ClientRemoveMusicFlag(client, MUSICF_ALERT); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + for (int i = 0; i < MAX_BOSSES; i++) + { + g_PlayerAlertMusicTimer[client][i] = null; + g_PlayerAlertMusicVolumes[client][i] = 0.0; + g_PlayerAlertMusicString[client][i][0] = '\0'; + + if (NPCGetUniqueID(i) != -1) + { + if (IsValidClient(client)) + { + NPCGetProfile(i, profile, sizeof(profile)); + + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileAlertMusics(profile, soundInfo); + soundInfo.StopAllSounds(client); + } + } + } +} + +void ClientAlertMusicStart(int client,int bossIndex) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + int oldMaster = g_PlayerAlertMusicMaster[client]; + if (oldMaster == bossIndex) + { + return; + } + + g_PlayerAlertMusicOldMaster[client] = oldMaster; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + char buffer[PLATFORM_MAX_PATH]; + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileAlertMusics(profile, soundInfo); + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); + } + + if (buffer[0] == '\0') + { + return; + } + + g_PlayerAlertMusicMaster[client] = bossIndex; + if (g_PlayerAlertMusicVolumes[client][bossIndex] <= 0.0) + { + strcopy(g_PlayerAlertMusicString[client][bossIndex], sizeof(g_PlayerAlertMusicString[][]), buffer); + } + if (oldMaster != -1 && g_PlayerAlertMusicString[client][oldMaster][0] != '\0' && + strcmp(g_PlayerAlertMusicString[client][oldMaster], g_PlayerAlertMusicString[client][bossIndex]) == 0) + { + if (g_PlayerAlertMusicVolumes[client][bossIndex] < 1.0) + { + g_PlayerAlertMusicVolumes[client][bossIndex] = g_PlayerAlertMusicVolumes[client][oldMaster]; + g_PlayerAlertMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInAlertMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerAlertMusicTimer[client][bossIndex], true); + } + return; + } + g_PlayerAlertMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInAlertMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerAlertMusicTimer[client][bossIndex], true); + + if (oldMaster != -1) + { + ClientAlertMusicStop(client, oldMaster); + } +} + +void ClientAlertMusicStop(int client,int bossIndex) +{ + if (!IsValidClient(client)) + { + return; + } + if (bossIndex == -1) + { + return; + } + + if (bossIndex == g_PlayerAlertMusicMaster[client]) + { + g_PlayerAlertMusicMaster[client] = -1; + } + + g_PlayerAlertMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutAlertMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerAlertMusicTimer[client][bossIndex], true); +} + +void ClientIdleMusicReset(int client) +{ + g_PlayerIdleMusicMaster[client] = -1; + g_PlayerIdleMusicOldMaster[client] = -1; + ClientRemoveMusicFlag(client, MUSICF_IDLE); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + for (int i = 0; i < MAX_BOSSES; i++) + { + g_PlayerIdleMusicTimer[client][i] = null; + g_PlayerIdleMusicVolumes[client][i] = 0.0; + g_PlayerIdleMusicString[client][i][0] = '\0'; + + if (NPCGetUniqueID(i) != -1) + { + if (IsValidClient(client)) + { + NPCGetProfile(i, profile, sizeof(profile)); + + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileIdleMusics(profile, soundInfo); + soundInfo.StopAllSounds(client); + } + } + } +} + +void ClientIdleMusicStart(int client,int bossIndex) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + int oldMaster = g_PlayerIdleMusicMaster[client]; + if (oldMaster == bossIndex) + { + return; + } + + g_PlayerIdleMusicOldMaster[client] = oldMaster; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + char buffer[PLATFORM_MAX_PATH]; + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileIdleMusics(profile, soundInfo); + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); + } + + if (buffer[0] == '\0') + { + return; + } + + g_PlayerIdleMusicMaster[client] = bossIndex; + if (g_PlayerIdleMusicVolumes[client][bossIndex] <= 0.0 && strcmp(buffer, g_PlayerIdleMusicString[client][bossIndex], true) != 0) + { + strcopy(g_PlayerIdleMusicString[client][bossIndex], sizeof(g_PlayerIdleMusicString[][]), buffer); + } + if (oldMaster != -1 && g_PlayerIdleMusicString[client][oldMaster][0] != '\0' && + strcmp(g_PlayerIdleMusicString[client][oldMaster], g_PlayerIdleMusicString[client][bossIndex]) == 0) + { + if (g_PlayerIdleMusicVolumes[client][bossIndex] < 1.0) + { + g_PlayerIdleMusicVolumes[client][bossIndex] = g_PlayerIdleMusicVolumes[client][oldMaster]; + g_PlayerIdleMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInIdleMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerIdleMusicTimer[client][bossIndex], true); + } + return; + } + g_PlayerIdleMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInIdleMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerIdleMusicTimer[client][bossIndex], true); + + if (oldMaster != -1) + { + ClientIdleMusicStop(client, oldMaster); + } +} + +void ClientIdleMusicStop(int client,int bossIndex) +{ + if (!IsValidClient(client)) + { + return; + } + if (bossIndex == -1) + { + return; + } + + if (bossIndex == g_PlayerIdleMusicMaster[client]) + { + g_PlayerIdleMusicMaster[client] = -1; + } + + g_PlayerIdleMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutIdleMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerIdleMusicTimer[client][bossIndex], true); +} + +void ClientChaseMusicReset(int client) +{ + g_PlayerChaseMusicMaster[client] = -1; + g_PlayerChaseMusicOldMaster[client] = -1; + ClientRemoveMusicFlag(client, MUSICF_CHASE); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + for (int i = 0; i < MAX_BOSSES; i++) + { + g_PlayerChaseMusicVolumes[client][i] = 0.0; + g_PlayerChaseMusicTimer[client][i] = null; + g_PlayerChaseMusicString[client][i][0] = '\0'; + + if (NPCGetUniqueID(i) != -1) + { + if (IsValidClient(client)) + { + NPCGetProfile(i, profile, sizeof(profile)); + + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileChaseMusics(profile, soundInfo); + soundInfo.StopAllSounds(client); + } + } + } +} + +void ClientMusicChaseStart(int client,int bossIndex) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + int oldMaster = g_PlayerChaseMusicMaster[client]; + if (oldMaster == bossIndex) + { + return; + } + + g_PlayerChaseMusicOldMaster[client] = oldMaster; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + char buffer[PLATFORM_MAX_PATH]; + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileChaseMusics(profile, soundInfo); + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); + } + + if (buffer[0] == '\0') + { + return; + } + + g_PlayerChaseMusicMaster[client] = bossIndex; + if (g_PlayerChaseMusicVolumes[client][bossIndex] <= 0.0) + { + strcopy(g_PlayerChaseMusicString[client][bossIndex], sizeof(g_PlayerChaseMusicString[][]), buffer); + } + if (oldMaster != -1 && g_PlayerChaseMusicString[client][oldMaster][0] != '\0' && + strcmp(g_PlayerChaseMusicString[client][oldMaster], g_PlayerChaseMusicString[client][bossIndex]) == 0) + { + if (g_PlayerChaseMusicVolumes[client][bossIndex] < 1.0) + { + g_PlayerChaseMusicVolumes[client][bossIndex] = g_PlayerChaseMusicVolumes[client][oldMaster]; + g_PlayerChaseMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerChaseMusicTimer[client][bossIndex], true); + } + return; + } + if (MusicActive())//A boss is overriding the music. + { + GetBossMusic(g_PlayerChaseMusicString[client][bossIndex],sizeof(g_PlayerChaseMusicString[][])); + } + g_PlayerChaseMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerChaseMusicTimer[client][bossIndex], true); + + if (oldMaster != -1) + { + ClientMusicChaseStop(client, oldMaster); + } +} + +void ClientMusicChaseStop(int client,int bossIndex) +{ + if (!IsValidClient(client)) + { + return; + } + if (bossIndex == -1) + { + return; + } + + if (bossIndex == g_PlayerChaseMusicMaster[client]) + { + g_PlayerChaseMusicMaster[client] = -1; + } + + g_PlayerChaseMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutChaseMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerChaseMusicTimer[client][bossIndex], true); +} + +void ClientChaseMusicSeeReset(int client) +{ + g_PlayerChaseMusicSeeMaster[client] = -1; + g_PlayerChaseMusicSeeOldMaster[client] = -1; + ClientRemoveMusicFlag(client, MUSICF_CHASEVISIBLE); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + for (int i = 0; i < MAX_BOSSES; i++) + { + g_PlayerChaseMusicSeeTimer[client][i] = null; + g_PlayerChaseMusicSeeVolumes[client][i] = 0.0; + g_PlayerChaseMusicSeeString[client][i][0] = '\0'; + + if (NPCGetUniqueID(i) != -1) + { + if (IsValidClient(client)) + { + NPCGetProfile(i, profile, sizeof(profile)); + + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileChaseVisibleMusics(profile, soundInfo); + soundInfo.StopAllSounds(client); + } + } + } +} + +void ClientMusicChaseSeeStart(int client,int bossIndex) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + int oldMaster = g_PlayerChaseMusicSeeMaster[client]; + if (oldMaster == bossIndex) + { + return; + } + + g_PlayerChaseMusicSeeOldMaster[client] = oldMaster; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + char buffer[PLATFORM_MAX_PATH]; + SF2BossProfileSoundInfo soundInfo; + GetChaserProfileChaseVisibleMusics(profile, soundInfo); + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); + } + + if (buffer[0] == '\0') + { + return; + } + + g_PlayerChaseMusicSeeMaster[client] = bossIndex; + if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] <= 0.0) + { + strcopy(g_PlayerChaseMusicSeeString[client][bossIndex], sizeof(g_PlayerChaseMusicSeeString[][]), buffer); + } + if (oldMaster != -1 && g_PlayerChaseMusicSeeString[client][oldMaster][0] != '\0' && + strcmp(g_PlayerChaseMusicSeeString[client][oldMaster], g_PlayerChaseMusicSeeString[client][bossIndex]) == 0) + { + if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] < 1.0) + { + g_PlayerChaseMusicSeeVolumes[client][bossIndex] = g_PlayerChaseMusicSeeVolumes[client][oldMaster]; + g_PlayerChaseMusicSeeTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusicSee, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerChaseMusicSeeTimer[client][bossIndex], true); + } + return; + } + g_PlayerChaseMusicSeeTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusicSee, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerChaseMusicSeeTimer[client][bossIndex], true); + + if (oldMaster != -1) + { + ClientMusicChaseSeeStop(client, oldMaster); + } +} + +void ClientMusicChaseSeeStop(int client,int bossIndex) +{ + if (!IsValidClient(client)) + { + return; + } + if (bossIndex == -1) + { + return; + } + + if (bossIndex == g_PlayerChaseMusicSeeMaster[client]) + { + g_PlayerChaseMusicSeeMaster[client] = -1; + } + + g_PlayerChaseMusicSeeTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutChaseMusicSee, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerChaseMusicSeeTimer[client][bossIndex], true); +} + +void Client90sMusicReset(int client) +{ + char oldMusic[PLATFORM_MAX_PATH]; + strcopy(oldMusic, sizeof(oldMusic), g_Player90sMusicString[client]); + g_Player90sMusicString[client][0] = '\0'; + if (IsValidClient(client) && oldMusic[0] != '\0') + { + StopSound(client, MUSIC_CHAN, oldMusic); + } + + g_Player90sMusicTimer[client] = null; + g_Player90sMusicVolumes[client] = 0.0; + + if (IsValidClient(client)) + { + oldMusic = NINETYSMUSIC; + if (oldMusic[0] != '\0') + { + StopSound(client, MUSIC_CHAN, oldMusic); + } + } +} + +void Client90sMusicStart(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + char buffer[PLATFORM_MAX_PATH]; + buffer = NINETYSMUSIC; + + if (buffer[0] == '\0') + { + return; + } + + strcopy(g_Player90sMusicString[client], sizeof(g_Player90sMusicString[]), buffer); + g_Player90sMusicTimer[client] = CreateTimer(0.01, Timer_PlayerFadeIn90sMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_Player90sMusicTimer[client], true); +} + +void Client90sMusicStop(int client) +{ + if (!IsValidClient(client)) + { + return; + } + + if (!IsClientSprinting(client)) + { + g_Player90sMusicString[client][0] = '\0'; + } + + g_Player90sMusicTimer[client]= CreateTimer(0.01, Timer_PlayerFadeOut90sMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_Player90sMusicTimer[client], true); +} + +Action Timer_PlayerFadeInMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerMusicTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerMusicVolume[client] += 0.07; + if (g_PlayerMusicVolume[client] > g_PlayerMusicTargetVolume[client]) + { + g_PlayerMusicVolume[client] = g_PlayerMusicTargetVolume[client]; + } + + if (g_PlayerMusicString[client][0] != '\0') + { + EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); + } + + if (g_PlayerMusicVolume[client] >= g_PlayerMusicTargetVolume[client]) + { + g_PlayerMusicTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeOutMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerMusicTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerMusicVolume[client] -= 0.07; + if (g_PlayerMusicVolume[client] < 0.0) + { + g_PlayerMusicVolume[client] = 0.0; + } + + if (g_PlayerMusicString[client][0] != '\0') + { + EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); + } + + if (g_PlayerMusicVolume[client] <= 0.0) + { + g_PlayerMusicTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeInAlertMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerAlertMusicTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + /*int oldBoss = g_PlayerAlertMusicOldMaster[client]; + + if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerAlertMusicString[client][bossIndex], g_PlayerAlertMusicString[client][oldBoss], false) == 0) + { + g_PlayerAlertMusicTimer[client][bossIndex] = null; + g_PlayerAlertMusicVolumes[client][bossIndex] = 1.0; + return Plugin_Stop; + }*/ + + g_PlayerAlertMusicVolumes[client][bossIndex] += 0.07; + if (g_PlayerAlertMusicVolumes[client][bossIndex] > 1.0) + { + g_PlayerAlertMusicVolumes[client][bossIndex] = 1.0; + } + + if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); + } + + if (g_PlayerAlertMusicVolumes[client][bossIndex] >= 1.0) + { + g_PlayerAlertMusicTimer[client][bossIndex] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeOutAlertMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerAlertMusicTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + int oldBoss = g_PlayerAlertMusicOldMaster[client]; + int newBoss = g_PlayerAlertMusicMaster[client]; + + if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerAlertMusicString[client][newBoss], g_PlayerAlertMusicString[client][oldBoss], false) == 0) + { + g_PlayerAlertMusicTimer[client][oldBoss] = null; + g_PlayerAlertMusicString[client][oldBoss][0] = '\0'; + g_PlayerAlertMusicVolumes[client][oldBoss] = 0.0; + g_PlayerAlertMusicOldMaster[client] = -1; + return Plugin_Stop; + } + + g_PlayerAlertMusicVolumes[client][bossIndex] -= 0.07; + if (g_PlayerAlertMusicVolumes[client][bossIndex] < 0.0) + { + g_PlayerAlertMusicVolumes[client][bossIndex] = 0.0; + } + + if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); + } + + if (g_PlayerAlertMusicVolumes[client][bossIndex] <= 0.0) + { + g_PlayerAlertMusicTimer[client][bossIndex] = null; + g_PlayerAlertMusicString[client][bossIndex][0] = '\0'; + g_PlayerAlertMusicOldMaster[client] = -1; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeInIdleMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerIdleMusicTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + /*int oldBoss = g_PlayerIdleMusicOldMaster[client]; + + if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerIdleMusicString[client][bossIndex], g_PlayerIdleMusicString[client][oldBoss], false) == 0) + { + g_PlayerIdleMusicTimer[client][bossIndex] = null; + g_PlayerIdleMusicVolumes[client][bossIndex] = 1.0; + return Plugin_Stop; + }*/ + + g_PlayerIdleMusicVolumes[client][bossIndex] += 0.07; + if (g_PlayerIdleMusicVolumes[client][bossIndex] > 1.0) + { + g_PlayerIdleMusicVolumes[client][bossIndex] = 1.0; + } + + if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); + } + + if (g_PlayerIdleMusicVolumes[client][bossIndex] >= 1.0) + { + g_PlayerIdleMusicTimer[client][bossIndex] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeOutIdleMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerIdleMusicTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + int oldBoss = g_PlayerIdleMusicOldMaster[client]; + int newBoss = g_PlayerIdleMusicMaster[client]; + + if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerIdleMusicString[client][newBoss], g_PlayerIdleMusicString[client][oldBoss], false) == 0) + { + g_PlayerIdleMusicTimer[client][oldBoss] = null; + g_PlayerIdleMusicString[client][oldBoss][0] = '\0'; + g_PlayerIdleMusicVolumes[client][oldBoss] = 0.0; + g_PlayerIdleMusicOldMaster[client] = -1; + return Plugin_Stop; + } + + g_PlayerIdleMusicVolumes[client][bossIndex] -= 0.07; + if (g_PlayerIdleMusicVolumes[client][bossIndex] < 0.0) + { + g_PlayerIdleMusicVolumes[client][bossIndex] = 0.0; + } + + if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); + } + + if (g_PlayerIdleMusicVolumes[client][bossIndex] <= 0.0) + { + g_PlayerIdleMusicTimer[client][bossIndex] = null; + g_PlayerIdleMusicString[client][bossIndex][0] = '\0'; + g_PlayerIdleMusicOldMaster[client] = -1; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeInChaseMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerChaseMusicTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + /*int oldBoss = g_PlayerChaseMusicOldMaster[client]; + + if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicString[client][bossIndex], g_PlayerChaseMusicString[client][oldBoss], false) == 0) + { + g_PlayerChaseMusicTimer[client][bossIndex] = null; + g_PlayerChaseMusicVolumes[client][bossIndex] = 1.0; + return Plugin_Stop; + }*/ + + g_PlayerChaseMusicVolumes[client][bossIndex] += 0.07; + if (g_PlayerChaseMusicVolumes[client][bossIndex] > 1.0) + { + g_PlayerChaseMusicVolumes[client][bossIndex] = 1.0; + } + + if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); + } + + if (g_PlayerChaseMusicVolumes[client][bossIndex] >= 1.0) + { + g_PlayerChaseMusicTimer[client][bossIndex] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeInChaseMusicSee(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerChaseMusicSeeTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + /*int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; + + if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicSeeString[client][bossIndex], g_PlayerChaseMusicSeeString[client][oldBoss], false) == 0) + { + g_PlayerChaseMusicSeeTimer[client][bossIndex] = null; + g_PlayerChaseMusicSeeVolumes[client][bossIndex] = 1.0; + return Plugin_Stop; + }*/ + + g_PlayerChaseMusicSeeVolumes[client][bossIndex] += 0.07; + if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] > 1.0) + { + g_PlayerChaseMusicSeeVolumes[client][bossIndex] = 1.0; + } + + if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); + } + + if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] >= 1.0) + { + g_PlayerChaseMusicSeeTimer[client][bossIndex] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeOutChaseMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerChaseMusicTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + int oldBoss = g_PlayerChaseMusicOldMaster[client]; + int newBoss = g_PlayerChaseMusicMaster[client]; + + if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerChaseMusicString[client][newBoss], g_PlayerChaseMusicString[client][oldBoss], false) == 0) + { + g_PlayerChaseMusicTimer[client][oldBoss] = null; + g_PlayerChaseMusicString[client][oldBoss][0] = '\0'; + g_PlayerChaseMusicVolumes[client][oldBoss] = 0.0; + g_PlayerChaseMusicOldMaster[client] = -1; + return Plugin_Stop; + } + + g_PlayerChaseMusicVolumes[client][bossIndex] -= 0.07; + if (g_PlayerChaseMusicVolumes[client][bossIndex] < 0.0) + { + g_PlayerChaseMusicVolumes[client][bossIndex] = 0.0; + } + + if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); + } + + if (g_PlayerChaseMusicVolumes[client][bossIndex] <= 0.0) + { + g_PlayerChaseMusicTimer[client][bossIndex] = null; + g_PlayerChaseMusicString[client][bossIndex][0] = '\0'; + g_PlayerChaseMusicOldMaster[client] = -1; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeOutChaseMusicSee(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = -1; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_PlayerChaseMusicSeeTimer[client][i] == timer) + { + bossIndex = i; + break; + } + } + + if (bossIndex == -1) + { + return Plugin_Stop; + } + + int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; + int newBoss = g_PlayerChaseMusicSeeMaster[client]; + + if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerChaseMusicSeeString[client][newBoss], g_PlayerChaseMusicSeeString[client][oldBoss], false) == 0) + { + g_PlayerChaseMusicSeeTimer[client][oldBoss] = null; + g_PlayerChaseMusicSeeString[client][oldBoss][0] = '\0'; + g_PlayerChaseMusicSeeVolumes[client][oldBoss] = 0.0; + g_PlayerChaseMusicSeeOldMaster[client] = -1; + return Plugin_Stop; + } + + g_PlayerChaseMusicSeeVolumes[client][bossIndex] -= 0.07; + if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] < 0.0) + { + g_PlayerChaseMusicSeeVolumes[client][bossIndex] = 0.0; + } + + if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') + { + EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); + } + + if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] <= 0.0) + { + g_PlayerChaseMusicSeeTimer[client][bossIndex] = null; + g_PlayerChaseMusicSeeString[client][bossIndex][0] = '\0'; + g_PlayerChaseMusicSeeOldMaster[client] = -1; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeIn90sMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (g_Player90sMusicTimer[client] != timer) + { + return Plugin_Stop; + } + + g_Player90sMusicVolumes[client] += 0.28; + if (g_Player90sMusicVolumes[client] > 0.5) + { + g_Player90sMusicVolumes[client] = 0.5; + } + + if (g_Player90sMusicString[client][0] != '\0') + { + EmitSoundToClient(client, g_Player90sMusicString[client], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); + } + + if (g_Player90sMusicVolumes[client] >= 0.5) + { + g_Player90sMusicTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_PlayerFadeOut90sMusic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (g_Player90sMusicTimer[client] != timer) + { + return Plugin_Stop; + } + + char buffer[PLATFORM_MAX_PATH]; + buffer = NINETYSMUSIC; + + if (strcmp(buffer, g_Player90sMusicString[client], false) == 0) + { + g_Player90sMusicTimer[client] = null; + return Plugin_Stop; + } + + g_Player90sMusicVolumes[client] -= 0.28; + if (g_Player90sMusicVolumes[client] < 0.0) + { + g_Player90sMusicVolumes[client] = 0.0; + } + + if (buffer[0] != '\0') + { + EmitSoundToClient(client, buffer, _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); + } + + if (g_Player90sMusicVolumes[client] <= 0.0) + { + g_Player90sMusicTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +bool ClientHasMusicFlag(int client,int flag) +{ + return !!(g_PlayerMusicFlags[client] & flag); +} + +bool ClientHasMusicFlag2(int value,int flag) +{ + return !!(value & flag); +} + +void ClientAddMusicFlag(int client,int flag) +{ + if (!ClientHasMusicFlag(client, flag)) + { + g_PlayerMusicFlags[client] |= flag; + } +} + +void ClientRemoveMusicFlag(int client,int flag) +{ + if (ClientHasMusicFlag(client, flag)) + { + g_PlayerMusicFlags[client] &= ~flag; + } +} diff --git a/addons/sourcemod/scripting/sf2/client/peek.sp b/addons/sourcemod/scripting/sf2/client/peek.sp new file mode 100644 index 00000000..90ef4e29 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/peek.sp @@ -0,0 +1,27 @@ +#pragma semicolon 1 + +//Peeking Data +static bool g_PlayerPeeking[MAXTF2PLAYERS] = { false, ... }; + +/** + * Handles thirdperson peeking + */ +bool ClientStartPeeking(int client) +{ + if (!g_PlayerPeeking[client] && g_AllowPlayerPeekingConVar.BoolValue && !TF2_IsPlayerInCondition(client, TFCond_Dazed) && GetClientButtons(client) & IN_DUCK) + { + TF2_StunPlayer(client, -1.0, 1.0, TF_STUNFLAGS_LOSERSTATE); + g_PlayerPeeking[client] = true; + return true; + } + return false; +} + +void ClientEndPeeking(int client) +{ + if (g_PlayerPeeking[client]) + { + TF2_RemoveCondition(client, TFCond_Dazed); + g_PlayerPeeking[client] = false; + } +} diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp new file mode 100644 index 00000000..90ea62c7 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -0,0 +1,2052 @@ +#if defined _sf2_client_proxy_functions_included + #endinput +#endif +#define _sf2_client_proxy_functions_included + +#pragma semicolon 1 + +static int g_ActionItemIndexes[] = { 57, 231 }; + +//Proxy model +static char g_ClientProxyModel[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static char g_ClientProxyModelHard[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static char g_ClientProxyModelInsane[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static char g_ClientProxyModelNightmare[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static char g_ClientProxyModelApollyon[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; + +void SetupProxy() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetProxy(client.index); + ClientStartProxyAvailableTimer(client.index); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + ClientStopProxyForce(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetProxy(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client) +{ + ClientResetProxy(client.index, true); + if (!client.IsProxy) + { + return; + } + + // We're a proxy, so play some sounds. + + int proxyMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client.index]); + if (proxyMaster != -1) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(proxyMaster, profile, sizeof(profile)); + + SF2BossProfileSoundInfo soundInfo; + GetBossProfileProxyDeathSounds(profile, soundInfo); + soundInfo.EmitSound(_, client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetProxy(client.index); +} + +void ClientResetProxy(int client, bool resetFull=true) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetProxy(%d)", client); + } + #endif + + int oldMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); + char oldProfileName[SF2_MAX_PROFILE_NAME_LENGTH]; + if (oldMaster >= 0) + { + NPCGetProfile(oldMaster, oldProfileName, sizeof(oldProfileName)); + } + + bool oldProxy = g_PlayerProxy[client]; + if (resetFull) + { + g_PlayerProxy[client] = false; + g_PlayerProxyMaster[client] = -1; + } + + g_PlayerProxyControl[client] = 0; + g_PlayerProxyControlTimer[client] = null; + g_PlayerProxyControlRate[client] = 0.0; + g_PlayerProxyVoiceTimer[client] = null; + + if (IsValidClient(client)) + { + if (oldProxy) + { + ClientStartProxyAvailableTimer(client); + + if (resetFull) + { + ClientDisableConstantGlow(client); + SetVariantString(""); + AcceptEntityInput(client, "SetCustomModel"); + } + + if (oldProfileName[0] != '\0') + { + SF2BossProfileSoundInfo soundInfo; + GetBossProfileProxySpawnSounds(oldProfileName, soundInfo); + soundInfo.StopAllSounds(client); + GetBossProfileProxyHurtSounds(oldProfileName, soundInfo); + soundInfo.StopAllSounds(client); + GetBossProfileProxyIdleSounds(oldProfileName, soundInfo); + soundInfo.StopAllSounds(client); + } + } + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetProxy(%d)", client); + } + #endif +} + +void ClientStartProxyAvailableTimer(int client) +{ + g_PlayerProxyAvailable[client] = false; + float cooldown = g_PlayerProxyWaitTimeConVar.FloatValue; + if (g_InProxySurvivalRageMode) + { + cooldown -= 10.0; + } + if (cooldown <= 0.0) + { + cooldown = 0.0; + } + + g_PlayerProxyAvailableTimer[client] = CreateTimer(cooldown, Timer_ClientProxyAvailable, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); +} + +void ClientStartProxyForce(int client, int slenderID, const float pos[3], int spawnPoint) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientStartProxyForce(%d, %d, pos)", client, slenderID); + } + #endif + + g_PlayerProxyAskMaster[client] = slenderID; + for (int i = 0; i < 3; i++) + { + g_PlayerProxyAskPosition[client][i] = pos[i]; + } + g_PlayerProxyAskSpawnPoint[client] = EnsureEntRef(spawnPoint); + + g_PlayerProxyAvailableCount[client] = 0; + g_PlayerProxyAvailableInForce[client] = true; + g_PlayerProxyAvailableTimer[client] = CreateTimer(1.0, Timer_ClientForceProxy, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + TriggerTimer(g_PlayerProxyAvailableTimer[client], true); + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientStartProxyForce(%d, %d, pos)", client, slenderID); + } + #endif +} + +void ClientStopProxyForce(int client) +{ + g_PlayerProxyAvailableCount[client] = 0; + g_PlayerProxyAvailableInForce[client] = false; + g_PlayerProxyAvailableTimer[client] = null; +} + +Action Timer_ClientForceProxy(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerProxyAvailableTimer[client]) + { + return Plugin_Stop; + } + + if (!IsRoundEnding()) + { + int bossIndex = NPCGetFromUniqueID(g_PlayerProxyAskMaster[client]); + if (bossIndex != -1) + { + int difficulty = GetLocalGlobalDifficulty(bossIndex); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; + int numProxies = 0; + + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i) || !g_PlayerEliminated[i]) + { + continue; + } + if (!g_PlayerProxy[i]) + { + continue; + } + if (NPCGetFromUniqueID(g_PlayerProxyMaster[i]) != bossIndex) + { + continue; + } + + numProxies++; + } + + if (numProxies < maxProxies) + { + if (g_PlayerProxyAvailableCount[client] > 0) + { + g_PlayerProxyAvailableCount[client]--; + + SetHudTextParams(-1.0, 0.25, + 1.0, + 255, 255, 255, 255, + _, + _, + 0.25, 1.25); + + ShowSyncHudText(client, g_HudSync, "%T", "SF2 Proxy Force Message", client, g_PlayerProxyAvailableCount[client]); + + return Plugin_Continue; + } + else + { + ClientEnableProxy(client, bossIndex, g_PlayerProxyAskPosition[client], g_PlayerProxyAskSpawnPoint[client]); + } + } + else + { + //PrintToChat(client, "%T", "SF2 Too Many Proxies", client); + } + } + } + + ClientStopProxyForce(client); + return Plugin_Stop; +} + +void DisplayProxyAskMenu(int client, int askMaster, const float pos[3], int spawnPoint) +{ + if (IsRoundEnding() || IsRoundInIntro() || IsRoundInWarmup()) + { + return; + } + char buffer[512]; + Handle menu = CreateMenu(Menu_ProxyAsk); + SetMenuTitle(menu, "%T\n \n%T\n \n", "SF2 Proxy Ask Menu Title", client, "SF2 Proxy Ask Menu Description", client); + + FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); + AddMenuItem(menu, "1", buffer); + FormatEx(buffer, sizeof(buffer), "%T", "No", client); + AddMenuItem(menu, "0", buffer); + + g_PlayerProxyAskMaster[client] = askMaster; + for (int i = 0; i < 3; i++) + { + g_PlayerProxyAskPosition[client][i] = pos[i]; + } + g_PlayerProxyAskSpawnPoint[client] = EnsureEntRef(spawnPoint); + + DisplayMenu(menu, client, 15); +} + +int Menu_ProxyAsk(Handle menu, MenuAction action,int param1,int param2) +{ + switch (action) + { + case MenuAction_End: + { + delete menu; + } + case MenuAction_Select: + { + if (!IsRoundEnding() && !IsRoundInIntro() && !IsRoundInWarmup()) + { + int bossIndex = NPCGetFromUniqueID(g_PlayerProxyAskMaster[param1]); + if (bossIndex != -1) + { + int difficulty = GetLocalGlobalDifficulty(bossIndex); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; + int numProxies; + + for (int client = 1; client < MaxClients; client++) + { + if (!IsValidClient(client) || !g_PlayerEliminated[client]) + { + continue; + } + if (!g_PlayerProxy[client]) + { + continue; + } + if (NPCGetFromUniqueID(g_PlayerProxyMaster[client]) != bossIndex) + { + continue; + } + + numProxies++; + } + + if (numProxies < maxProxies) + { + if (param2 == 0) + { + bool ignoreVisibility = false; + int spawnPointEnt = g_PlayerProxyAskSpawnPoint[param1]; + float spawnPos[3]; + + if (IsValidEntity(spawnPointEnt)) + { + GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", spawnPos); + + SF2PlayerProxySpawnEntity spawnPoint = SF2PlayerProxySpawnEntity(spawnPointEnt); + if (spawnPoint.IsValid()) + { + ignoreVisibility = spawnPoint.IgnoreVisibility; + } + } + else + { + for (int i = 0; i < 3; i++) + { + spawnPos[i] = g_PlayerProxyAskPosition[param1][i]; + } + } + + if (ignoreVisibility || !IsPointVisibleToAPlayer(spawnPos, _, false)) + { + ClientEnableProxy(param1, bossIndex, spawnPos, spawnPointEnt); + } + else + { + CPrintToChat(param1, "%T", "SF2 Too Much Time", param1); + } + } + else + { + ClientStartProxyAvailableTimer(param1); + } + } + else + { + PrintToChat(param1, "%T", "SF2 Too Many Proxies", param1); + } + } + } + } + } + return 0; +} + +Action Timer_ClientProxyAvailable(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerProxyAvailableTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerProxyAvailable[client] = true; + g_PlayerProxyAvailableTimer[client] = null; + + return Plugin_Stop; +} + +/** + * Respawns a player as a proxy. + * + * @noreturn + */ +void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnPointEnt=-1) +{ + if (NPCGetUniqueID(bossIndex) == -1) + { + return; + } + if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) + { + return; + } + if (GetClientTeam(client) != TFTeam_Blue) + { + return; + } + if (g_PlayerProxy[client]) + { + return; + } + + TF2_RemovePlayerDisguise(client); + + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + ClientSetGhostModeState(client, false); + + ClientStopProxyForce(client); + + if (IsClientInKart(client)) + { + TF2_RemoveCondition(client,TFCond_HalloweenKart); + TF2_RemoveCondition(client,TFCond_HalloweenKartDash); + TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + } + + g_PlayerProxy[client] = true; + ChangeClientTeamNoSuicide(client, TFTeam_Blue); + PvP_SetPlayerPvPState(client, false, true, false); + TF2_RespawnPlayer(client); + + // Speed recalculation. Props to the creators of FF2/VSH for this snippet. + TF2_AddCondition(client, TFCond_SpeedBuffAlly, 0.001); + + g_PlayerProxy[client] = true; + g_PlayerProxyMaster[client] = NPCGetUniqueID(bossIndex); + g_PlayerProxyControl[client] = 100; + g_PlayerProxyControlRate[client] = g_SlenderProxyControlDrainRate[bossIndex][difficulty]; + g_PlayerProxyControlTimer[client] = CreateTimer(g_PlayerProxyControlRate[client], Timer_ClientProxyControl, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + g_PlayerProxyAvailable[client] = false; + g_PlayerProxyAvailableTimer[client] = null; + + char allowedClasses[512]; + GetBossProfileProxyClasses(profile, allowedClasses, sizeof(allowedClasses)); + + char className[64]; + TF2_GetClassName(TF2_GetPlayerClass(client), className, sizeof(className)); + if (allowedClasses[0] != '\0' && className[0] != '\0' && StrContains(allowedClasses, className, false) == -1) + { + // Pick the first class that's allowed. + char allowedClassesList[32][32]; + int classCount = ExplodeString(allowedClasses, " ", allowedClassesList, 32, 32); + if (classCount) + { + TF2_SetPlayerClass(client, TF2_GetClass(allowedClassesList[0]), _, false); + + int maxHealth = GetEntProp(client, Prop_Send, "m_iHealth"); + TF2_RegeneratePlayer(client); + SetEntProp(client, Prop_Data, "m_iHealth", maxHealth); + SetEntProp(client, Prop_Send, "m_iHealth", maxHealth); + } + } + + UTIL_ScreenFade(client, 200, 1, FFADE_IN, 255, 255, 255, 100); + EmitSoundToClient(client, "weapons/teleporter_send.wav", _, SNDCHAN_STATIC); + + ClientDisableConstantGlow(client); + ClientActivateUltravision(client); + + TF2Attrib_SetByDefIndex(client, 28, 1.0); + + CreateTimer(0.33, Timer_ApplyCustomModel, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + + if (NPCHasProxyWeapons(bossIndex)) + { + CreateTimer(1.0, Timer_GiveWeaponAll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + + for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + { + if (NPCGetUniqueID(npcIndex) == -1) + { + continue; + } + if (g_SlenderInDeathcam[npcIndex]) + { + continue; + } + SlenderRemoveGlow(npcIndex); + if (NPCGetCustomOutlinesState(npcIndex)) + { + int color[4]; + color[0] = NPCGetOutlineColorR(npcIndex); + color[1] = NPCGetOutlineColorG(npcIndex); + color[2] = NPCGetOutlineColorB(npcIndex); + color[3] = NPCGetOutlineTransparency(npcIndex); + if (color[0] < 0) + { + color[0] = 0; + } + if (color[1] < 0) + { + color[1] = 0; + } + if (color[2] < 0) + { + color[2] = 0; + } + if (color[3] < 0) + { + color[3] = 0; + } + if (color[0] > 255) + { + color[0] = 255; + } + if (color[1] > 255) + { + color[1] = 255; + } + if (color[2] > 255) + { + color[2] = 255; + } + if (color[3] > 255) + { + color[3] = 255; + } + SlenderAddGlow(npcIndex, color); + } + else + { + int purple[4] = {150, 0, 255, 255}; + SlenderAddGlow(npcIndex, purple); + } + } + + //SDKHook(client, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); + + SF2PlayerProxySpawnEntity spawnPoint = SF2PlayerProxySpawnEntity(spawnPointEnt); + if (spawnPoint.IsValid()) + { + float spawnPos[3]; float ang[3]; + GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", spawnPos); + GetEntPropVector(spawnPointEnt, Prop_Data, "m_angAbsRotation", ang); + TeleportEntity(client, spawnPos, ang, view_as({ 0.0, 0.0, 0.0 })); + spawnPoint.FireOutput("OnSpawn", client); + } + else + { + TeleportEntity(client, pos, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); + } + + if (NPCGetProxySpawnEffectState(bossIndex)) + { + char spawnEffect[PLATFORM_MAX_PATH]; + GetBossProfileProxySpawnEffectName(profile, spawnEffect, sizeof(spawnEffect)); + CreateGeneralParticle(client, spawnEffect, NPCGetProxySpawnEffectZOffset(bossIndex)); + } + + Call_StartForward(g_OnClientSpawnedAsProxyFwd); + Call_PushCell(client); + Call_Finish(); +} + +static Action Timer_GiveWeaponAll(Handle timer, any userid) +{ + if (!g_Enabled) + { + return Plugin_Stop; + } + + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int bossIndex = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); + + if (g_PlayerProxy[client] && bossIndex != -1) + { + if (!NPCHasProxyWeapons(bossIndex)) + { + return Plugin_Stop; + } + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + int weaponIndex, weaponSlot; + char weaponName[PLATFORM_MAX_PATH], weaponStats[PLATFORM_MAX_PATH]; + int classIndex = view_as(TF2_GetPlayerClass(client)); + classIndex--; + ArrayList weaponArray = GetBossProfileProxyWeaponClassNames(profile); + if (weaponArray == null) + { + return Plugin_Stop; + } + weaponArray.GetString(classIndex, weaponName, sizeof(weaponName)); + weaponArray = GetBossProfileProxyWeaponStats(profile); + if (weaponArray == null) + { + return Plugin_Stop; + } + weaponArray.GetString(classIndex, weaponStats, sizeof(weaponStats)); + weaponIndex = GetBossProfileProxyWeaponIndexes(profile, classIndex + 1); + weaponSlot = GetBossProfileProxyWeaponSlots(profile, classIndex + 1); + + switch (weaponSlot) + { + case 0: + { + TF2_RemoveWeaponSlot(client, TFWeaponSlot_Primary); + } + case 1: + { + TF2_RemoveWeaponSlot(client, TFWeaponSlot_Secondary); + } + case 2: + { + TF2_RemoveWeaponSlot(client, TFWeaponSlot_Melee); + } + } + Handle weaponHandle = PrepareItemHandle(weaponName, weaponIndex, 0, 0, weaponStats); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + EquipPlayerWeapon(client, entity); + SetEntProp(entity, Prop_Send, "m_bValidatedAttachedEntity", 1); + } + return Plugin_Stop; +} + +bool Hook_ClientProxyShouldCollide(int ent,int collisiongroup,int contentsmask, bool originalResult) +{ + if (!g_Enabled || !g_PlayerProxy[ent] || IsClientInPvP(ent)) + { + SDKUnhook(ent, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); + return originalResult; + } + if ((contentsmask & MASK_RED)) + { + return true; + } + //To-do add no collision proxy-boss here, the collision boss-proxy is done, see npc_chaser.sp + return originalResult; +} +//RequestFrame// +void ProxyDeathAnimation(any client) +{ + if (client != -1) + { + if (g_ClientFrame[client] >= g_ClientMaxFrameDeathAnim[client]) + { + g_ClientFrame[client]--; + KillClient(client); + } + else + { + g_ClientFrame[client]++; + RequestFrame(ProxyDeathAnimation,client); + } + } +} + +Action Timer_ClientProxyControl(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerProxyControlTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerProxyControl[client]--; + if (TF2_IsPlayerInCondition(client, TFCond_Taunting)) + { + g_PlayerProxyControl[client] -= 5; + } + if (g_PlayerProxyControl[client] <= 0) + { + // ForcePlayerSuicide isn't really dependable, since the player doesn't suicide until several seconds after spawning has passed. + SDKHooks_TakeDamage(client, client, client, 9001.0, DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + return Plugin_Stop; + } + + g_PlayerProxyControlTimer[client] = CreateTimer(g_PlayerProxyControlRate[client], Timer_ClientProxyControl, userid, TIMER_FLAG_NO_MAPCHANGE); + + return Plugin_Stop; +} + +Action Timer_ApplyCustomModel(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + SetEntProp(client, Prop_Send, "m_iAirDash", 99999); + + TF2Attrib_SetByName(client, "crit mod disabled", 0.0); + + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); + + if (g_PlayerProxy[client] && master != -1) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(master, profile, sizeof(profile)); + + int difficulty = GetLocalGlobalDifficulty(master); + + // Set custom model, if any. + char buffer[PLATFORM_MAX_PATH]; + + TF2_RegeneratePlayer(client); + + char className[64]; + TFClassType playerClass = TF2_GetPlayerClass(client); + int classToInt = view_as(playerClass); + TF2_GetClassName(playerClass, className, sizeof(className)); + + ArrayList modelsArray; + + if (GetBossProfileProxyDifficultyModelsState(profile)) + { + switch (difficulty) + { + case Difficulty_Easy, Difficulty_Normal: + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); + } + modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); + strcopy(g_ClientProxyModel[client],sizeof(g_ClientProxyModel[]),buffer); + } + case Difficulty_Hard: + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 1); + } + } + } + modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); + strcopy(g_ClientProxyModelHard[client],sizeof(g_ClientProxyModelHard[]),buffer); + } + case Difficulty_Insane: + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 2); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 2); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 1); + } + } + } + } + } + modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); + strcopy(g_ClientProxyModelInsane[client],sizeof(g_ClientProxyModelInsane[]),buffer); + } + case Difficulty_Nightmare: + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 3); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 3); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 2); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 2); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 1); + } + } + } + } + } + } + } + modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); + strcopy(g_ClientProxyModelNightmare[client],sizeof(g_ClientProxyModelNightmare[]),buffer); + } + case Difficulty_Apollyon: + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 4); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 4); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 3); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 3); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 2); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 2); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 1); + } + } + } + } + } + } + } + } + } + modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); + strcopy(g_ClientProxyModelApollyon[client],sizeof(g_ClientProxyModelApollyon[]),buffer); + } + } + + if (buffer[0] != '\0') + { + SetVariantString(buffer); + AcceptEntityInput(client, "SetCustomModel"); + SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); + + CreateTimer(0.5, ClientCheckProxyModel, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + else + { + modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); + if (modelsArray == null) + { + modelsArray = GetBossProfileProxyModels(profile, 0, 1); + } + if (modelsArray != null) + { + modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); + if (buffer[0] != '\0') + { + SetVariantString(buffer); + AcceptEntityInput(client, "SetCustomModel"); + SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); + strcopy(g_ClientProxyModel[client],sizeof(g_ClientProxyModel[]),buffer); + strcopy(g_ClientProxyModelHard[client],sizeof(g_ClientProxyModelHard[]),buffer); + strcopy(g_ClientProxyModelInsane[client],sizeof(g_ClientProxyModelInsane[]),buffer); + strcopy(g_ClientProxyModelNightmare[client],sizeof(g_ClientProxyModelNightmare[]),buffer); + strcopy(g_ClientProxyModelApollyon[client],sizeof(g_ClientProxyModelApollyon[]),buffer); + //Prevent plugins like Model manager to override proxy model. + CreateTimer(0.5, ClientCheckProxyModel, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + } + + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + ClientDisableConstantGlow(i); + if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) + { + int red[4] = {184, 56, 59, 255}; + ClientEnableConstantGlow(i, red); + } + else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) + { + int yellow[4] = {255, 208, 0, 255}; + ClientEnableConstantGlow(i, yellow); + } + } + + if (IsPlayerAlive(client)) + { + g_PlayerProxyNextVoiceSound[client] = GetGameTime(); + SF2BossProfileSoundInfo soundInfo; + GetBossProfileProxySpawnSounds(profile, soundInfo); + // Play any sounds, if any. + soundInfo.EmitSound(_, client); + + bool zombie = GetBossProfileProxyZombiesState(profile); + if (zombie) + { + int value = g_ForcedHolidayConVar.IntValue; + if (value != 9 && value != 2) + { + g_ForcedHolidayConVar.SetInt(9); //Full-Moon + } + int index; + TFClassType class = TF2_GetPlayerClass(client); + switch (class) + { + case TFClass_Scout: + { + index = 5617; + } + case TFClass_Soldier: + { + index = 5618; + } + case TFClass_Pyro: + { + index = 5624; + } + case TFClass_DemoMan: + { + index = 5620; + } + case TFClass_Engineer: + { + index = 5621; + } + case TFClass_Heavy: + { + index = 5619; + } + case TFClass_Medic: + { + index = 5622; + } + case TFClass_Sniper: + { + index = 5625; + } + case TFClass_Spy: + { + index = 5623; + } + } + Handle zombieSoul = PrepareItemHandle("tf_wearable", index, 100, 7,"448 ; 1.0 ; 450 ; 1"); + int entity = TF2Items_GiveNamedItem(client, zombieSoul); + delete zombieSoul; + zombieSoul = null; + if (IsValidEdict(entity)) + { + SDK_EquipWearable(client, entity); + } + if (TF2_GetPlayerClass(client) == TFClass_Spy) + { + SetEntProp(client, Prop_Send, "m_nForcedSkin", 23); + } + else + { + SetEntProp(client, Prop_Send, "m_nForcedSkin", 5); + } + } + + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + } + } + return Plugin_Stop; +} + +Action ClientCheckProxyModel(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + if (!IsValidClient(client)) + { + return Plugin_Stop; + } + if (!IsPlayerAlive(client)) + { + return Plugin_Stop; + } + if (!g_PlayerProxy[client]) + { + return Plugin_Stop; + } + int difficulty = g_DifficultyConVar.IntValue; + + char model[PLATFORM_MAX_PATH]; + GetEntPropString(client, Prop_Data, "m_ModelName", model, sizeof(model)); + switch (difficulty) + { + case Difficulty_Normal: + { + if (strcmp(model,g_ClientProxyModel[client]) != 0 && g_ClientProxyModel[client][0] != '\0') + { + SetVariantString(g_ClientProxyModel[client]); + AcceptEntityInput(client, "SetCustomModel"); + SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); + } + } + case Difficulty_Hard: + { + if (strcmp(model,g_ClientProxyModelHard[client]) != 0 && g_ClientProxyModelHard[client][0] != '\0') + { + SetVariantString(g_ClientProxyModelHard[client]); + AcceptEntityInput(client, "SetCustomModel"); + SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); + } + } + case Difficulty_Insane: + { + if (strcmp(model,g_ClientProxyModelInsane[client]) != 0 && g_ClientProxyModelInsane[client][0] != '\0') + { + SetVariantString(g_ClientProxyModelInsane[client]); + AcceptEntityInput(client, "SetCustomModel"); + SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); + } + } + case Difficulty_Nightmare: + { + if (strcmp(model,g_ClientProxyModelNightmare[client]) != 0 && g_ClientProxyModelNightmare[client][0] != '\0') + { + SetVariantString(g_ClientProxyModelNightmare[client]); + AcceptEntityInput(client, "SetCustomModel"); + SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); + } + } + case Difficulty_Apollyon: + { + if (strcmp(model,g_ClientProxyModelApollyon[client]) != 0 && g_ClientProxyModelApollyon[client][0] != '\0') + { + SetVariantString(g_ClientProxyModelApollyon[client]); + AcceptEntityInput(client, "SetCustomModel"); + SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); + } + } + } + return Plugin_Continue; +} + +void SF2_RefreshRestrictions() +{ + for(int client = 1; client <= MaxClients; client++) + { + if (IsValidClient(client) && (!g_PlayerEliminated[client] || !IsClientInPvP(client))) + { + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + g_PlayerPostWeaponsTimer[client] = CreateTimer(1.0,Timer_ClientPostWeapons,GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + } +} +Action Timer_ClientPostWeapons(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (IsValidClient(client) && !IsPlayerAlive(client)) + { + return Plugin_Stop; + } + + if (!IsValidClient(client)) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPostWeaponsTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerHasRegenerationItem[client] = false; + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 0) + { + DebugMessage("START Timer_ClientPostWeapons(%d)", client); + } + + int oldWeaponItemIndexes[6] = { -1, ... }; + int newWeaponItemIndexes[6] = { -1, ... }; + + for (int i = 0; i <= 5; i++) + { + if (IsValidClient(client)) + { + int weaponEnt = GetPlayerWeaponSlot(client, i); + if (!IsValidEdict(weaponEnt)) + { + continue; + } + + oldWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + } + } + #endif + + bool removeWeapons = true; + bool keepUtilityItems = false; + bool restrictWeapons = true; + bool useStock = false; + bool removeWearables = false; + bool preventAttack = false; + + if (IsRoundEnding()) + { + if (!g_PlayerEliminated[client]) + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + } + } + + if (g_PlayerEliminated[client] && g_PlayerKeepWeaponsConVar.BoolValue) + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = true; + } + + // pvp + if (IsClientInPvP(client)) + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = false; + } + + if (g_PlayerProxy[client]) + { + restrictWeapons = true; + removeWeapons = true; + useStock = true; + removeWearables = true; + keepUtilityItems = false; + } + + if (IsRoundInWarmup()) + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = false; + } + + if (IsClientInGhostMode(client)) + { + removeWeapons = true; + } + + if (SF_IsRaidMap() && !g_PlayerEliminated[client]) + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = false; + } + + if (SF_IsBoxingMap() && !g_PlayerEliminated[client] && !IsRoundEnding()) + { + restrictWeapons = false; + keepUtilityItems = true; + preventAttack = false; + } + + if (removeWeapons && !keepUtilityItems) + { + for (int i = 0; i <= 5; i++) + { + if (i == TFWeaponSlot_Melee && !IsClientInGhostMode(client)) + { + continue; + } + TF2_RemoveWeaponSlotAndWearables(client, i); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_demoshield")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + } + + if (keepUtilityItems) + { + for (int i = 0; i <= 5; i++) + { + if ((i == TFWeaponSlot_Melee || i == TFWeaponSlot_Secondary) && !IsClientInGhostMode(client)) + { + continue; + } + TF2_RemoveWeaponSlotAndWearables(client, i); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + + int weaponEnt = INVALID_ENT_REFERENCE; + weaponEnt = GetPlayerWeaponSlot(client, TFWeaponSlot_Secondary); + + if (IsValidEdict(weaponEnt)) + { + int itemIndex = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemIndex) + { + case 163, 129, 226, 354, 1001, 131, 406, 1099, 42, 159, 311, 433, 863, 1002, 1190: + { + //Do nothing + } + default: + { + TF2_RemoveWeaponSlotAndWearables(client, TFWeaponSlot_Secondary); + } + } + } + + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + } + + if (removeWearables) + { + TF2_StripWearables(client); + } + + TFClassType playerClass = TF2_GetPlayerClass(client); + int classToInt = view_as(playerClass); + + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (642 == itemIndex) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + } + } + + if (restrictWeapons) + { + int health = GetEntProp(client, Prop_Send, "m_iHealth"); + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + for (int i = 0; i < sizeof(g_ActionItemIndexes); i++) + { + if (g_ActionItemIndexes[i] == itemIndex) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_razorback")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + + if (g_RestrictedWeaponsConfig != null) + { + int weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = 0; slot <= 5; slot++) + { + Handle itemHandle = null; + weaponEnt = GetPlayerWeaponSlot(client, slot); + + if (IsValidEdict(weaponEnt)) + { + if (useStock || IsWeaponRestricted(playerClass, GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"))) + { + TF2_RemoveWeaponSlotAndWearables(client, slot); + switch (slot) + { + case TFWeaponSlot_Primary: + { + switch (playerClass) + { + case TFClass_Scout: + { + itemHandle = PrepareItemHandle("tf_weapon_scattergun", 13, 0, 0, ""); + } + case TFClass_Sniper: + { + itemHandle = PrepareItemHandle("tf_weapon_sniperrifle", 14, 0, 0, ""); + } + case TFClass_Soldier: + { + itemHandle = PrepareItemHandle("tf_weapon_rocketlauncher", 18, 0, 0, ""); + } + case TFClass_DemoMan: + { + itemHandle = PrepareItemHandle("tf_weapon_grenadelauncher", 19, 0, 0, ""); + } + case TFClass_Heavy: + { + itemHandle = PrepareItemHandle("tf_weapon_minigun", 15, 0, 0, ""); + } + case TFClass_Medic: + { + itemHandle = PrepareItemHandle("tf_weapon_syringegun_medic", 17, 0, 0, ""); + } + case TFClass_Pyro: + { + itemHandle = PrepareItemHandle("tf_weapon_flamethrower", 21, 0, 0, "254 ; 4.0"); + } + case TFClass_Spy: + { + itemHandle = PrepareItemHandle("tf_weapon_revolver", 24, 0, 0, ""); + } + case TFClass_Engineer: + { + itemHandle = PrepareItemHandle("tf_weapon_shotgun", 9, 0, 0, ""); + } + } + } + case TFWeaponSlot_Secondary: + { + switch (playerClass) + { + case TFClass_Scout: + { + itemHandle = PrepareItemHandle("tf_weapon_pistol", 23, 0, 0, ""); + } + case TFClass_Sniper: + { + itemHandle = PrepareItemHandle("tf_weapon_smg", 16, 0, 0, ""); + } + case TFClass_Soldier: + { + itemHandle = PrepareItemHandle("tf_weapon_shotgun", 10, 0, 0, ""); + } + case TFClass_DemoMan: + { + itemHandle = PrepareItemHandle("tf_weapon_pipebomblauncher", 20, 0, 0, ""); + } + case TFClass_Heavy: + { + itemHandle = PrepareItemHandle("tf_weapon_shotgun", 11, 0, 0, ""); + } + case TFClass_Medic: + { + itemHandle = PrepareItemHandle("tf_weapon_medigun", 29, 0, 0, ""); + } + case TFClass_Pyro: + { + itemHandle = PrepareItemHandle("tf_weapon_shotgun", 12, 0, 0, ""); + } + case TFClass_Engineer: + { + itemHandle = PrepareItemHandle("tf_weapon_pistol", 22, 0, 0, ""); + } + } + } + case TFWeaponSlot_Melee: + { + switch (playerClass) + { + case TFClass_Scout: + { + itemHandle = PrepareItemHandle("tf_weapon_bat", 0, 0, 0, ""); + } + case TFClass_Sniper: + { + itemHandle = PrepareItemHandle("tf_weapon_club", 3, 0, 0, ""); + } + case TFClass_Soldier: + { + itemHandle = PrepareItemHandle("tf_weapon_shovel", 6, 0, 0, ""); + } + case TFClass_DemoMan: + { + itemHandle = PrepareItemHandle("tf_weapon_bottle", 1, 0, 0, ""); + } + case TFClass_Heavy: + { + itemHandle = PrepareItemHandle("tf_weapon_fists", 5, 0, 0, ""); + } + case TFClass_Medic: + { + itemHandle = PrepareItemHandle("tf_weapon_bonesaw", 8, 0, 0, ""); + } + case TFClass_Pyro: + { + itemHandle = PrepareItemHandle("tf_weapon_fireaxe", 2, 0, 0, ""); + } + case TFClass_Spy: + { + itemHandle = PrepareItemHandle("tf_weapon_knife", 4, 0, 0, ""); + } + case TFClass_Engineer: + { + itemHandle = PrepareItemHandle("tf_weapon_wrench", 7, 0, 0, ""); + } + } + } + case 4: + { + switch (playerClass) + { + case TFClass_Spy: + { + itemHandle = PrepareItemHandle("tf_weapon_invis", 297, 0, 0, ""); + } + } + } + } + + if (itemHandle != null) + { + int newWeapon = TF2Items_GiveNamedItem(client, itemHandle); + if (IsValidEntity(newWeapon)) + { + EquipPlayerWeapon(client, newWeapon); + } + } + } + else + { + if (!g_PlayerHasRegenerationItem[client]) + g_PlayerHasRegenerationItem[client] = IsRegenWeapon(weaponEnt); + } + } + delete itemHandle; + itemHandle = null; + } + } + + // Fixes the Pretty Boy's Pocket Pistol glitch. + int maxHealth = SDKCall(g_SDKGetMaxHealth, client); + if (health > maxHealth) + { + SetEntProp(client, Prop_Data, "m_iHealth", maxHealth); + SetEntProp(client, Prop_Send, "m_iHealth", maxHealth); + } + } + + // Change stats on some weapons. + if (!g_PlayerEliminated[client] || g_PlayerProxy[client]) + { + int weaponEnt = INVALID_ENT_REFERENCE; + Handle weaponHandle = null; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = GetPlayerWeaponSlot(client, slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDef) + { + case 214: // Powerjack + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 214, 0, 0, "180 ; 12.0 ; 412 ; 1.2"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 310: //The Warrior's Spirit + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_fists", 310, 0, 0, "2 ; 1.3 ; 412 ; 1.3"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 326: // The Back Scratcher + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 326, 0, 0, "2 ; 1.25 ; 412 ; 1.25 ; 69 ; 0.25 ; 108 ; 1.25"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 304: // Amputator + { + TF2_RemoveWeaponSlot(client, slot); + + if (!SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "200 ; 0.0 ; 57 ; 2 ; 1 ; 0.8"); + } + else + { + weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "1 ; 0.8"); + } + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 239: //GRU + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_fists", 239, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + if (!IsRoundPlaying()) + { + SetEntityHealth(client, 300); + } + } + case 1100: //Bread Bite + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_fists", 1100, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 426: //Eviction Notice + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_fists", 426, 0, 0, "6 ; 0.6 ; 107 ; 1.15 ; 737 ; 4.0 ; 1 ; 0.4 ; 412 ; 1.2"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + if (!IsRoundPlaying()) + { + SetEntityHealth(client, 300); + } + } + case 775: //The Escape Plan (Its like, real buggy on wearer) + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_shovel", 775, 0, 0, "414 ; 1 ; 734 ; 0.1"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 452: //Three Rune Blade + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_bat", 452, 0, 0, ""); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 325: //Boston Basher + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_bat", 325, 0, 0, ""); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 450: //Atomizer + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_bat", 450, 0, 0, ""); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 225: //Your Eternal Reward + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_knife", 225, 0, 0, ""); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 649: //Spy-cicle + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_knife", 649, 0, 0, ""); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + case 574: //Spy-cicle + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_knife", 574, 0, 0, ""); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + } + } + delete weaponHandle; + } + + if (preventAttack) + { + int weaponEnt = INVALID_ENT_REFERENCE; + while ((weaponEnt = FindEntityByClassname(weaponEnt, "tf_wearable_demoshield")) != INVALID_ENT_REFERENCE) + { + if (GetEntPropEnt(weaponEnt, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(weaponEnt); + } + } + + for (int slot = 0; slot <= 5; slot++) + { + if (slot == TFWeaponSlot_Melee) + { + continue; + } + + weaponEnt = GetPlayerWeaponSlot(client, slot); + if (!IsValidEntity(weaponEnt)) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDef) + { + case 30, 212, 59, 60, 297, 947, 1101: // Invis Watch, Base Jumper + { + TF2_RemoveWeaponSlotAndWearables(client, slot); + } + default: + { + SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextPrimaryAttack", 99999999.9); + SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextSecondaryAttack", 99999999.9); + } + } + } + } + + //Remove the teleport ability + if (IsClientInPvP(client) || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !g_PlayerEliminated[client])) //DidClientEscape(client) + { + int weaponEnt = INVALID_ENT_REFERENCE; + Handle weaponHandle = null; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = GetPlayerWeaponSlot(client, slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDef) + { + case 589: // Eureka Effect + { + TF2_RemoveWeaponSlot(client, slot); + + weaponHandle = PrepareItemHandle("tf_weapon_wrench", 589, 0, 0, "93 ; 0.5 ; 732 ; 0.5"); + int entity = TF2Items_GiveNamedItem(client, weaponHandle); + delete weaponHandle; + weaponHandle = null; + EquipPlayerWeapon(client, entity); + } + } + } + delete weaponHandle; + } + //Force them to take their melee wep, it prevents the civilian bug. + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + + // Remove all hats. + if (IsClientInGhostMode(client)) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_campaign_item")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) + { + RemoveEntity(ent); + } + } + } + + float healthFromPack = 1.0; + if (!IsClassConfigsValid()) + { + if (!g_PlayerEliminated[client] && !SF_IsBoxingMap()) + { + if (g_PlayerHasRegenerationItem[client]) + { + healthFromPack = 0.40; + } + if (TF2_GetPlayerClass(client) == TFClass_Medic) + { + healthFromPack = 0.0; + } + } + } + else + { + if (!g_PlayerEliminated[client] && !SF_IsBoxingMap()) + { + healthFromPack = g_ClassHealthPickupMultiplier[classToInt]; + if (g_PlayerHasRegenerationItem[client]) + { + healthFromPack -= 0.6; + } + if (healthFromPack <= 0.0) + { + healthFromPack = 0.0; + } + } + } + + TF2Attrib_SetByDefIndex(client, 109, healthFromPack); + + #if defined DEBUG + int weaponEnt = INVALID_ENT_REFERENCE; + + for (int i = 0; i <= 5; i++) + { + weaponEnt = GetPlayerWeaponSlot(client, i); + if (!IsValidEdict(weaponEnt)) + { + continue; + } + + newWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + } + + if (g_DebugDetailConVar.IntValue > 0) + { + for (int i = 0; i <= 5; i++) + { + DebugMessage("-> slot %d: %d (old: %d)", i, newWeaponItemIndexes[i], oldWeaponItemIndexes[i]); + } + + DebugMessage("END Timer_ClientPostWeapons(%d) -> remove = %d, restrict = %d", client, removeWeapons, restrictWeapons); + } + #endif + return Plugin_Stop; +} + +public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int itemDefinitionIndex, Handle &itemHandle) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + /*if (itemDefinitionIndex == 649) + { + RequestFrame(Frame_ReplaceSpyCicle, client); + return Plugin_Handled; + }*/ + switch (itemDefinitionIndex) + { + case 642: + { + Handle itemOverride = PrepareItemHandle("tf_wearable", 642, 0, 0, "376 ; 1.0 ; 377 ; 0.2 ; 57 ; 2 ; 412 ; 1.10"); + + if (itemOverride != null) + { + itemHandle = itemOverride; + + return Plugin_Changed; + } + delete itemOverride; + itemOverride = null; + } + } + + return Plugin_Continue; +} + +void Frame_ClientHealArrow(int client) +{ + if (IsValidClient(client) && IsClientInPvP(client)) + { + int entity = -1; + while ((entity = FindEntityByClassname(entity, "tf_projectile_healing_bolt")) != -1) + { + int throwerOffset = FindDataMapInfo(entity, "m_hThrower"); + int ownerEntity = GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity"); + if (ownerEntity != client && throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(entity, throwerOffset); + } + if (ownerEntity == client) + { + SetEntProp(entity, Prop_Data, "m_iInitialTeamNum", GetClientTeam(client)); + SetEntProp(entity, Prop_Send, "m_iTeamNum", GetClientTeam(client)); + } + } + } +} + +bool IsRegenWeapon(int weaponEnt) +{ + Address attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 1003); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 490); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 190); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 130); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 57); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 220); + if (attribRegen != Address_Null) + { + return true; + } + return false; +} + +bool IsWeaponRestricted(TFClassType class,int itemDefInt) +{ + if (g_RestrictedWeaponsConfig == null) + { + return false; + } + + bool returnBool = false; + + char itemDef[32]; + FormatEx(itemDef, sizeof(itemDef), "%d", itemDefInt); + + g_RestrictedWeaponsConfig.Rewind(); + bool proxyBoss = false; + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC Npc = view_as(i); + if (!Npc.IsValid()) + { + continue; + } + if (Npc.Flags & SFF_PROXIES) + { + proxyBoss = true; + break; + } + } + if (g_RestrictedWeaponsConfig.JumpToKey("all")) + { + //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef)); + //view_as bool value turn to 2 into a true value. + if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) + { + returnBool=true; + } + if (proxyBoss && !returnBool) + { + int proxyRestricted = g_RestrictedWeaponsConfig.GetNum(itemDef, 0); + if (proxyRestricted==2) + { + returnBool = true; + } + } + } + + bool bFoundSection = false; + g_RestrictedWeaponsConfig.Rewind(); + + switch (class) + { + case TFClass_Scout: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("scout"); + } + case TFClass_Soldier: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("soldier"); + } + case TFClass_Sniper: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("sniper"); + } + case TFClass_DemoMan: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("demoman"); + } + case TFClass_Heavy: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("heavy"); + + if (!bFoundSection) + { + g_RestrictedWeaponsConfig.Rewind(); + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("heavyweapons"); + } + } + case TFClass_Medic: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("medic"); + } + case TFClass_Spy: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("spy"); + } + case TFClass_Pyro: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("pyro"); + } + case TFClass_Engineer: + { + bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("engineer"); + } + } + + if (bFoundSection) + { + //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef, returnBool)); + if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) + { + returnBool = true; + } + if (proxyBoss && !returnBool) + { + int proxyRestricted = g_RestrictedWeaponsConfig.GetNum(itemDef, 0); + if (proxyRestricted==2) + { + returnBool = true; + } + } + } + + return returnBool; +} diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp new file mode 100644 index 00000000..c1d19ee2 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -0,0 +1,875 @@ +#pragma semicolon 1 + +bool g_PlayerSprint[MAXTF2PLAYERS] = { false, ... }; +int g_PlayerSprintPoints[MAXTF2PLAYERS] = { 100, ... }; +Handle g_PlayerSprintTimer[MAXTF2PLAYERS] = { null, ... }; + +void SetupSprint() +{ + g_OnPlayerJumpPFwd.AddFunction(null, OnJump); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetSprint(client.index); + + SDKHook(client.index, SDKHook_PreThink, Hook_SprintThink); +} + +static void OnJump(SF2_BasePlayer client) +{ + if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped) + { + return; + } + + int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; + if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0 && !client.IsTrapped) + { + if (client.GetSprintPoints() >= 2) + { + TFClassType classType = client.Class; + int classToInt = view_as(classType); + if (!IsClassConfigsValid()) + { + if (classType != TFClass_Soldier || client.GetSprintPoints() <= 10 || client.IsSprinting) + { + client.SetSprintPoints(client.GetSprintPoints() - 7); + } + } + else + { + int sprintPointsLoss = g_ClassSprintPointLossJumping[classToInt]; + if (client.GetSprintPoints() <= 10 || client.IsSprinting) + { + sprintPointsLoss = 7; + } + client.SetSprintPoints(client.GetSprintPoints() - sprintPointsLoss); + } + if (client.GetSprintPoints() <= 0) + { + client.SetSprintPoints(0); + } + } + } + + if (!client.IsSprinting) + { + if (g_PlayerSprintTimer[client.index] == null) + { + // If the player hasn't sprinted recently, force us to regenerate the stamina. + client.SetSprintTimer(true); + } + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetSprint(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetSprint(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetSprint(client.index); +} + +float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknown) +{ + float returnFloat = 340.0; + float returnFloat2 = returnFloat; + Action action = Plugin_Continue; + if (IsValidClient(client)) + { + class = TF2_GetPlayerClass(client); + } + + switch (class) + { + case TFClass_Scout: + { + returnFloat = 305.0; + } + case TFClass_Sniper: + { + returnFloat = 295.0; + } + case TFClass_Soldier: + { + returnFloat = 280.0; + } + case TFClass_DemoMan: + { + returnFloat = 280.0; + } + case TFClass_Heavy: + { + returnFloat = 280.0; + } + case TFClass_Medic: + { + returnFloat = 290.0; + } + case TFClass_Pyro: + { + returnFloat = 290.0; + } + case TFClass_Spy: + { + returnFloat = 300.0; + } + case TFClass_Engineer: + { + returnFloat = 295.0; + } + } + + if (IsValidClient(client)) + { + // Call our forward. + Call_StartForward(g_OnClientGetDefaultSprintSpeedFwd); + Call_PushCell(client); + Call_PushCellRef(returnFloat2); + Call_Finish(action); + } + + if (action == Plugin_Changed) + { + returnFloat = returnFloat2; + } + + return returnFloat; +} + +bool IsClientSprinting(int client) +{ + return g_PlayerSprint[client]; +} + +int ClientGetSprintPoints(int client) +{ + return g_PlayerSprintPoints[client]; +} + +void ClientSetSprintPoints(int client, int value) +{ + g_PlayerSprintPoints[client] = value; +} + +void ClientResetSprint(int client) +{ + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientResetSprint(%d)", client); + } + #endif + + bool wasSprinting = IsClientSprinting(client); + + g_PlayerSprint[client] = false; + g_PlayerSprintPoints[client] = 100; + g_PlayerSprintTimer[client] = null; + + if (IsValidClient(client)) + { + SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); + SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); + + ClientSetFOV(client, g_PlayerDesiredFOV[client]); + } + + if (wasSprinting) + { + Call_StartForward(g_OnClientStopSprintingFwd); + Call_PushCell(client); + Call_Finish(); + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetSprint(%d)", client); + } + #endif +} + +void ClientStartSprint(int client) +{ + if (IsClientSprinting(client)) + { + return; + } + + g_PlayerSprint[client] = true; + g_PlayerSprintTimer[client] = null; + ClientSprintTimer(client); + TriggerTimer(g_PlayerSprintTimer[client], true); + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) + { + Client90sMusicStart(client); + } + + SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); + SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); + + Call_StartForward(g_OnClientStartSprintingFwd); + Call_PushCell(client); + Call_Finish(); +} + +void ClientSprintTimer(int client, bool recharge=false) +{ + float rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 0.38 : 0.28; + if (recharge) + { + rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 1.4 : 0.8; + } + + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + + float velocity[3]; + GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); + + if (recharge) + { + if (!(GetEntityFlags(client) & FL_ONGROUND)) + { + rate *= 0.75; + } + else if (GetVectorLength(velocity, true) == 0.0) + { + if (GetEntProp(client, Prop_Send, "m_bDucked")) + { + rate *= 0.66; + } + else + { + rate *= 0.75; + } + } + } + else + { + if (!IsClassConfigsValid()) + { + if (class == TFClass_DemoMan) + { + rate *= 1.15; + } + else if (class == TFClass_Medic || class == TFClass_Spy) + { + rate *= 1.05; + } + else if (class == TFClass_Scout) + { + rate *= 1.08; + } + } + else + { + rate *= g_ClassSprintDurationMultipler[classToInt]; + } + } + + if (recharge) + { + g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientRechargeSprint, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + else + { + g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientSprinting, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } +} + +void ClientStopSprint(int client) +{ + if (!IsClientSprinting(client)) + { + return; + } + g_PlayerSprint[client] = false; + g_PlayerSprintTimer[client] = null; + ClientSprintTimer(client, true); + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) + { + Client90sMusicStop(client); + } + + SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); + SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); + + Call_StartForward(g_OnClientStopSprintingFwd); + Call_PushCell(client); + Call_Finish(); +} + + /** + * Handles sprinting upon player input. + */ +void ClientHandleSprint(int client, bool sprint) +{ + if (!IsPlayerAlive(client) || + g_PlayerEliminated[client] || + DidClientEscape(client) || + g_PlayerProxy[client] || + IsClientInGhostMode(client)) + { + return; + } + + if (sprint) + { + if (g_PlayerSprintPoints[client] > 0) + { + ClientStartSprint(client); + } + else + { + EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); + } + } + else + { + if (IsClientSprinting(client)) + { + ClientStopSprint(client); + } + } +} +bool IsClientReallySprinting(int client) +{ + if (!IsClientSprinting(client)) + { + return false; + } + if (!(GetEntityFlags(client) & FL_ONGROUND)) + { + return false; + } + + float velocity[3]; + GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); + if (GetVectorLength(velocity, true) < SquareFloat(30.0)) + { + return false; + } + + return true; +} + +static Action Timer_ClientSprinting(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerSprintTimer[client]) + { + return Plugin_Stop; + } + + if (!IsClientSprinting(client)) + { + return Plugin_Stop; + } + + if (g_PlayerSprintPoints[client] <= 0) + { + ClientStopSprint(client); + g_PlayerSprintPoints[client] = 0; + return Plugin_Stop; + } + + if (IsClientReallySprinting(client)) + { + int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; + if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0) + { + g_PlayerSprintPoints[client]--; + } + } + + ClientSprintTimer(client); + + return Plugin_Stop; +} + +static Action Timer_ClientRechargeSprint(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + float velSpeed[3]; + GetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", velSpeed); + float speed = GetVectorLength(velSpeed, true); + + if (timer != g_PlayerSprintTimer[client]) + { + return Plugin_Stop; + } + + if (IsClientSprinting(client)) + { + g_PlayerSprintTimer[client] = null; + return Plugin_Stop; + } + + if (g_PlayerSprintPoints[client] >= 100) + { + g_PlayerSprintPoints[client] = 100; + g_PlayerSprintTimer[client] = null; + return Plugin_Stop; + } + if ((!GetEntProp(client, Prop_Send, "m_bDucking") && !GetEntProp(client, Prop_Send, "m_bDucked")) || (GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked") && IsClientReallySprinting(client) || speed > 0.0)) + { + g_PlayerSprintPoints[client]++; + } + else if ((GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked")) && !IsClientReallySprinting(client) && speed == 0.0) + { + if (!SF_SpecialRound(SPECIALROUND_COFFEE)) + { + g_PlayerSprintPoints[client] += 2; + } + else + { + g_PlayerSprintPoints[client] += 1; + } + } + ClientSprintTimer(client, true); + return Plugin_Stop; +} + +static void Hook_ClientSprintingPreThink(int client) +{ + if (!IsClientReallySprinting(client)) + { + SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); + SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); + return; + } + + int fov = GetEntData(client, g_PlayerDefaultFOVOffset); + + int targetFov = g_PlayerDesiredFOV[client] + 10; + + if (fov < targetFov) + { + int diff = RoundFloat(FloatAbs(float(fov - targetFov))); + if (diff >= 1) + { + ClientSetFOV(client, fov + 1); + } + else + { + ClientSetFOV(client, targetFov); + } + } + else if (fov >= targetFov) + { + ClientSetFOV(client, targetFov); + //SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); + } +} + +static void Hook_ClientRechargeSprintPreThink(int client) +{ + if (IsClientReallySprinting(client)) + { + SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); + SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); + return; + } + + int fov = GetEntData(client, g_PlayerDefaultFOVOffset); + if (fov > g_PlayerDesiredFOV[client]) + { + int diff = RoundFloat(FloatAbs(float(fov - g_PlayerDesiredFOV[client]))); + if (diff >= 1) + { + ClientSetFOV(client, fov - 1); + } + else + { + ClientSetFOV(client, g_PlayerDesiredFOV[client]); + } + } + else if (fov <= g_PlayerDesiredFOV[client]) + { + ClientSetFOV(client, g_PlayerDesiredFOV[client]); + } +} + +static void Hook_SprintThink(int client) +{ + if (!g_Enabled) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + RoundState roundState = GameRules_GetRoundState(); + if (player.IsEliminated || roundState != RoundState_RoundRunning || + IsRoundEnding() || IsRoundInWarmup() || player.HasEscaped) + { + return; + } + + TFClassType class = player.Class; + int classToInt = view_as(class); + + bool inDanger = false; + + if (!inDanger) + { + int state; + int bossTarget; + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if (NPCGetType(i) == SF2BossType_Chaser) + { + bossTarget = EntRefToEntIndex(g_SlenderTarget[i]); + state = g_SlenderState[i]; + + if ((state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) && + ((bossTarget && bossTarget != INVALID_ENT_REFERENCE && (bossTarget == client || ClientGetDistanceFromEntity(client, bossTarget) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) + { + inDanger = true; + ClientSetScareBoostEndTime(player.index, GetGameTime() + 5.0); + + // Induce client stress levels. + float unComfortZoneDist = 512.0; + float stressScalar = ((SquareFloat(unComfortZoneDist) / NPCGetDistanceFromEntity(i, client))); + ClientAddStress(player.index, 0.025 * stressScalar); + + break; + } + } + } + } + + if (g_PlayerStaticAmount[player.index] > 0.4) + { + inDanger = true; + } + if (GetGameTime() < ClientGetScareBoostEndTime(player.index)) + { + inDanger = true; + } + + if (!inDanger) + { + int state; + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if (NPCGetType(i) == SF2BossType_Chaser) + { + if (state == STATE_ALERT) + { + if (player.CanSeeSlender(i)) + { + inDanger = true; + ClientSetScareBoostEndTime(player.index, GetGameTime() + 5.0); + } + } + } + } + } + + if (!inDanger) + { + float curTime = GetGameTime(); + float scareSprintDuration = 3.0; + if (!IsClassConfigsValid()) + { + if (class == TFClass_DemoMan) + { + scareSprintDuration *= 1.667; + } + } + else + { + scareSprintDuration *= g_ClassScareSprintDurationMultipler[classToInt]; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + if ((curTime - g_PlayerScareLastTime[player.index][i]) <= scareSprintDuration) + { + inDanger = true; + break; + } + } + } + + if (player.IsTrapped) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 0.1); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 0.1); + return; + } + + float walkSpeed, sprintSpeed; + if (!IsClassConfigsValid()) + { + walkSpeed = ClientGetDefaultWalkSpeed(player.index); + sprintSpeed = ClientGetDefaultSprintSpeed(player.index); + } + else + { + walkSpeed = g_ClassWalkSpeed[classToInt]; + sprintSpeed = g_ClassRunSpeed[classToInt]; + } + + // Check for weapon speed changes. + int weaponEnt = INVALID_ENT_REFERENCE; + + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDefInt) + { + case 172: // Scotsman's Skullcutter + { + if (player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + sprintSpeed -= (sprintSpeed * 0.05); + walkSpeed -= (walkSpeed * 0.05); + } + } + case 214: // The Powerjack + { + if (player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + sprintSpeed += (sprintSpeed * 0.03); + } + } + case 239: // Gloves of Running Urgently + { + if (player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + sprintSpeed += (sprintSpeed * 0.075); + } + } + case 775: // Escape Plan + { + float health = float(player.GetProp(Prop_Send, "m_iHealth")); + float maxHealth = float(SDKCall(g_SDKGetMaxHealth, player.index)); + float percentage = health / maxHealth; + + if (percentage < 0.805 && percentage >= 0.605) + { + walkSpeed += (walkSpeed * 0.05); + sprintSpeed += (sprintSpeed * 0.05); + } + else if (percentage < 0.605 && percentage >= 0.405) + { + walkSpeed += (walkSpeed * 0.1); + sprintSpeed += (sprintSpeed * 0.1); + } + else if (percentage < 0.405 && percentage >= 0.205) + { + walkSpeed += (walkSpeed * 0.15); + sprintSpeed += (sprintSpeed * 0.15); + } + else if (percentage < 0.205) + { + walkSpeed += (walkSpeed * 0.2); + sprintSpeed += (sprintSpeed * 0.2); + } + } + } + } + + // Speed buff + if (!SF_IsSlaughterRunMap()) + { + if (player.InCondition(TFCond_SpeedBuffAlly)) + { + walkSpeed += (walkSpeed * 0.115); + sprintSpeed += (sprintSpeed * 0.165); + } + } + else + { + if (player.InCondition(TFCond_SpeedBuffAlly)) + { + walkSpeed += (walkSpeed * 0.105); + sprintSpeed += (sprintSpeed * 0.14); + } + } + + if (inDanger) + { + if (!IsClassConfigsValid()) + { + if (class != TFClass_Spy && class != TFClass_Pyro) + { + walkSpeed *= 1.34; + sprintSpeed *= 1.34; + } + else + { + if (class == TFClass_Spy) + { + walkSpeed *= 1.28; + sprintSpeed *= 1.28; + } + else + { + weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + walkSpeed *= 1.32; + sprintSpeed *= 1.32; + } + else + { + walkSpeed *= 1.34; + sprintSpeed *= 1.34; + } + } + } + } + } + else + { + float multiplier = g_ClassDangerSpeedMultipler[classToInt]; + if (class == TFClass_Pyro) + { + weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + multiplier -= 0.02; + } + } + } + walkSpeed *= multiplier; + sprintSpeed *= multiplier; + } + + if (!g_PlayerHints[player.index][PlayerHint_Sprint]) + { + player.ShowHint(PlayerHint_Sprint); + } + } + + float sprintSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.425); + float walkSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.3); + if (player.GetSprintPoints() > 8) + { + sprintSpeedSubtract -= sprintSpeedSubtract * (player.GetSprintPoints() != 0 ? (float(player.GetSprintPoints()) / 100.0) : 0.0); + sprintSpeed -= sprintSpeedSubtract; + } + else + { + sprintSpeedSubtract += 125; + sprintSpeed -= sprintSpeedSubtract; + walkSpeedSubtract += 25; + walkSpeed -= walkSpeedSubtract; + } + + if (player.IsSprinting) + { + if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) + { + if (!player.InCondition(TFCond_Charging)) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed); + } + else + { + if (SF_IsBoxingMap() || SF_IsRaidMap()) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed*2.5); + } + else + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed/2.5); + } + } + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); + } + else + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 520.0); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); + } + } + else + { + if (!player.InCondition(TFCond_Charging)) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed); + } + else + { + if (SF_IsBoxingMap() || SF_IsRaidMap()) + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed*2.5); + } + else + { + player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed/2.5); + } + } + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", walkSpeed-20.0); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/static.sp b/addons/sourcemod/scripting/sf2/client/static.sp new file mode 100644 index 00000000..849e0d4a --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/static.sp @@ -0,0 +1,439 @@ +#pragma semicolon 1 + +// Player static data. +int g_PlayerStaticMode[MAXTF2PLAYERS][MAX_BOSSES]; +float g_PlayerStaticIncreaseRate[MAXTF2PLAYERS]; +float g_PlayerStaticDecreaseRate[MAXTF2PLAYERS]; +Handle g_PlayerStaticTimer[MAXTF2PLAYERS]; +int g_PlayerStaticMaster[MAXTF2PLAYERS] = { -1, ... }; +char g_PlayerStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +char g_PlayerLastStaticSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +float g_PlayerLastStaticTime[MAXTF2PLAYERS]; +float g_PlayerLastStaticVolume[MAXTF2PLAYERS]; +Handle g_PlayerLastStaticTimer[MAXTF2PLAYERS]; + +// Static shake data. +int g_PlayerStaticShakeMaster[MAXTF2PLAYERS]; +bool g_PlayerInStaticShake[MAXTF2PLAYERS]; +char g_PlayerStaticShakeSound[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +float g_PlayerStaticShakeMinVolume[MAXTF2PLAYERS]; +float g_PlayerStaticShakeMaxVolume[MAXTF2PLAYERS]; + +void SetupStatic() +{ + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientResetStatic(client.index); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + ClientResetStatic(client.index); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientResetStatic(client.index); + + SDKHook(client.index, SDKHook_PreThink, Hook_StaticThink); +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetStatic(client.index); +} + +static void OnBossRemoved(int bossIndex) +{ + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + + if (NPCGetUniqueID(bossIndex) == g_PlayerStaticMaster[i]) + { + g_PlayerStaticMaster[i] = -1; + + // No one is the static master. + g_PlayerStaticTimer[i] = CreateTimer(g_PlayerStaticDecreaseRate[i], + Timer_ClientDecreaseStatic, + GetClientUserId(i), + TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + + TriggerTimer(g_PlayerStaticTimer[i], true); + } + } +} + +void ClientResetStatic(int client) +{ + g_PlayerStaticMaster[client] = -1; + g_PlayerStaticTimer[client] = null; + g_PlayerStaticIncreaseRate[client] = 0.0; + g_PlayerStaticDecreaseRate[client] = 0.0; + g_PlayerLastStaticTimer[client] = null; + g_PlayerLastStaticTime[client] = 0.0; + g_PlayerLastStaticVolume[client] = 0.0; + g_PlayerInStaticShake[client] = false; + g_PlayerStaticShakeMaster[client] = -1; + g_PlayerStaticShakeMinVolume[client] = 0.0; + g_PlayerStaticShakeMaxVolume[client] = 0.0; + g_PlayerStaticAmount[client] = 0.0; + + if (IsValidClient(client)) + { + if (g_PlayerStaticSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticSound[client]); + } + if (g_PlayerLastStaticSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + } + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); + } + } + + g_PlayerStaticSound[client][0] = '\0'; + g_PlayerLastStaticSound[client][0] = '\0'; + g_PlayerStaticShakeSound[client][0] = '\0'; +} + +// Static shaking should only affect the x, y portion of the player's view, not roll. +// This is purely for cosmetic effect. + +void ClientProcessStaticShake(int client) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client)) + { + return; + } + + bool oldStaticShake = g_PlayerInStaticShake[client]; + int oldStaticShakeMaster = NPCGetFromUniqueID(g_PlayerStaticShakeMaster[client]); + int newStaticShakeMaster = -1; + + float oldPunchAng[3], oldPunchAngVel[3]; + GetEntDataVector(client, g_PlayerPunchAngleOffset, oldPunchAng); + GetEntDataVector(client, g_PlayerPunchAngleOffsetVel, oldPunchAngVel); + + float newPunchAng[3], newPunchAngVel[3]; + + for (int i = 0; i < 3; i++) + { + newPunchAng[i] = oldPunchAng[i]; + newPunchAngVel[i] = oldPunchAngVel[i]; + } + + if (newStaticShakeMaster != -1) + { + g_PlayerStaticShakeMaster[client] = NPCGetUniqueID(newStaticShakeMaster); + + if (newStaticShakeMaster != oldStaticShakeMaster) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(newStaticShakeMaster, profile, sizeof(profile)); + + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); + } + + g_PlayerStaticShakeMinVolume[client] = GetBossProfileStaticShakeLocalVolumeMin(profile); + g_PlayerStaticShakeMaxVolume[client] = GetBossProfileStaticShakeLocalVolumeMax(profile); + + char staticSound[PLATFORM_MAX_PATH]; + GetBossProfileStaticShakeSound(profile, staticSound, sizeof(staticSound)); + if (staticSound[0] != '\0') + { + strcopy(g_PlayerStaticShakeSound[client], sizeof(g_PlayerStaticShakeSound[]), staticSound); + } + else + { + g_PlayerStaticShakeSound[client][0] = '\0'; + } + } + } + + if (g_PlayerInStaticShake[client]) + { + if (g_PlayerStaticAmount[client] <= 0.0) + { + g_PlayerInStaticShake[client] = false; + } + } + else + { + if (newStaticShakeMaster != -1) + { + g_PlayerInStaticShake[client] = true; + } + } + + if (g_PlayerInStaticShake[client] && !oldStaticShake) + { + for (int i = 0; i < 2; i++) + { + newPunchAng[i] = 0.0; + newPunchAngVel[i] = 0.0; + } + + SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); + SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); + } + else if (!g_PlayerInStaticShake[client] && oldStaticShake) + { + for (int i = 0; i < 2; i++) + { + newPunchAng[i] = 0.0; + newPunchAngVel[i] = 0.0; + } + + g_PlayerStaticShakeMaster[client] = -1; + + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerStaticShakeSound[client]); + } + + g_PlayerStaticShakeSound[client][0] = '\0'; + + g_PlayerStaticShakeMinVolume[client] = 0.0; + g_PlayerStaticShakeMaxVolume[client] = 0.0; + + SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); + SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); + } + + if (g_PlayerInStaticShake[client]) + { + if (g_PlayerStaticShakeSound[client][0] != '\0') + { + float volume = g_PlayerStaticAmount[client]; + if (GetRandomFloat(0.0, 1.0) <= 0.35) + { + volume = 0.0; + } + else + { + if (volume < g_PlayerStaticShakeMinVolume[client]) + { + volume = g_PlayerStaticShakeMinVolume[client]; + } + + if (volume > g_PlayerStaticShakeMaxVolume[client]) + { + volume = g_PlayerStaticShakeMaxVolume[client]; + } + } + + EmitSoundToClient(client, g_PlayerStaticShakeSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL | SND_STOP, volume); + } + + // Spazz our view all over the place. + for (int i = 0; i < 2; i++) + { + newPunchAng[i] = AngleNormalize(GetRandomFloat(0.0, 360.0)); + } + NormalizeVector(newPunchAng, newPunchAng); + + float angVelocityScalar = 5.0 * g_PlayerStaticAmount[client]; + if (angVelocityScalar < 1.0) + { + angVelocityScalar = 1.0; + } + ScaleVector(newPunchAng, angVelocityScalar); + + for (int i = 0; i < 2; i++) + { + newPunchAngVel[i] = 0.0; + } + + SetEntDataVector(client, g_PlayerPunchAngleOffset, newPunchAng, true); + SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, newPunchAngVel, true); + } +} + +static void Hook_StaticThink(int client) +{ + if (!g_Enabled) + { + return; + } + SF2_BasePlayer player = SF2_BasePlayer(client); + + if (g_IsPlayerShakeEnabled) + { + bool doShake = false; + + if (player.IsAlive) + { + int staticMaster = NPCGetFromUniqueID(g_PlayerStaticMaster[player.index]); + if (staticMaster != -1 && NPCGetFlags(staticMaster) & SFF_HASVIEWSHAKE) + { + doShake = true; + } + } + + if (doShake) + { + float percent = g_PlayerStaticAmount[player.index]; + + float amplitudeMax = g_PlayerShakeAmplitudeMaxConVar.FloatValue; + float amplitude = amplitudeMax * percent; + + float frequencyMax = g_PlayerShakeFrequencyMaxConVar.FloatValue; + float frequency = frequencyMax * percent; + + UTIL_ClientScreenShake(player.index, amplitude, 0.5, frequency); + } + } +} + +Action Timer_ClientIncreaseStatic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerStaticTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerStaticAmount[client] += 0.05; + if (g_PlayerStaticAmount[client] > 1.0) + { + g_PlayerStaticAmount[client] = 1.0; + } + + if (g_PlayerStaticSound[client][0] != '\0') + { + EmitSoundToClient(client, g_PlayerStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerStaticAmount[client]); + + if (g_PlayerStaticAmount[client] >= 0.5) + { + ClientAddStress(client, 0.03); + } + else + { + ClientAddStress(client, 0.02); + } + } + + return Plugin_Continue; +} + +Action Timer_ClientDecreaseStatic(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerStaticTimer[client]) + { + return Plugin_Stop; + } + + g_PlayerStaticAmount[client] -= 0.05; + if (g_PlayerStaticAmount[client] < 0.0) + { + g_PlayerStaticAmount[client] = 0.0; + } + + if (g_PlayerLastStaticSound[client][0] != '\0') + { + float volume = g_PlayerStaticAmount[client]; + if (volume > 0.0) + { + EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); + } + } + + if (g_PlayerStaticAmount[client] <= 0.0) + { + // I've done my job; no point to keep on doing it. + if (g_PlayerLastStaticSound[client][0] != '\0') + { + StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + } + g_PlayerStaticTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} + +Action Timer_ClientFadeOutLastStaticSound(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerLastStaticTimer[client]) + { + return Plugin_Stop; + } + + if (strcmp(g_PlayerLastStaticSound[client], g_PlayerStaticSound[client], false) == 0) + { + // Wait, the player's current static sound is the same one we're stopping. Abort! + g_PlayerLastStaticTimer[client] = null; + return Plugin_Stop; + } + + if (g_PlayerLastStaticSound[client][0] != '\0') + { + float diff = (GetGameTime() - g_PlayerLastStaticTime[client]) / 1.0; + if (diff > 1.0) + { + diff = 1.0; + } + + float volume = g_PlayerLastStaticVolume[client] - diff; + if (volume < 0.0) + { + volume = 0.0; + } + + if (volume <= 0.0) + { + // I've done my job; no point to keep on doing it. + StopSound(client, SNDCHAN_STATIC, g_PlayerLastStaticSound[client]); + g_PlayerLastStaticTimer[client] = null; + return Plugin_Stop; + } + else + { + EmitSoundToClient(client, g_PlayerLastStaticSound[client], _, SNDCHAN_STATIC, SNDLEVEL_NONE, SND_CHANGEVOL, volume); + } + } + else + { + // I've done my job; no point to keep on doing it. + g_PlayerLastStaticTimer[client] = null; + return Plugin_Stop; + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp new file mode 100644 index 00000000..aa5ef6d1 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -0,0 +1,958 @@ +#if defined _sf2_clients_think_included + #endinput +#endif +#define _sf2_clients_think_included + +#pragma semicolon 1 + +void Hook_ClientPreThink(int client) +{ + if (!g_Enabled) + { + return; + } + + ClientProcessFlashlightAngles(client); + ClientProcessInteractiveGlow(client); + ClientProcessStaticShake(client); + ClientProcessViewAngles(client); + + if (IsClientInGhostMode(client)) + { + SetEntityFlags(client, GetEntityFlags(client)^FL_EDICT_ALWAYS); + SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 520.0); + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + if (IsClientInKart(client) || !TF2_IsPlayerInCondition(client, TFCond_Stealthed)) + { + TF2_RemoveCondition(client,TFCond_HalloweenKart); + TF2_RemoveCondition(client,TFCond_HalloweenKartDash); + TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_Taunting); + ClientHandleGhostMode(client, true); + } + TF2_RemoveCondition(client, TFCond_Taunting); + } + else if (!g_PlayerEliminated[client] || g_PlayerProxy[client]) + { + if (!IsRoundEnding() && !IsRoundInWarmup() && !DidClientEscape(client)) + { + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + + int roundState = view_as(GameRules_GetRoundState()); + TFClassType class = TF2_GetPlayerClass(client); + + if (!g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Red) + { + if (TF2_IsPlayerInCondition(client, TFCond_Disguised)) + { + TF2_RemoveCondition(client, TFCond_Disguised); + } + + if (TF2_IsPlayerInCondition(client, TFCond_Taunting) && g_PlayerTrapped[client]) + { + TF2_RemoveCondition(client, TFCond_Taunting); + } + + if (TF2_IsPlayerInCondition(client,TFCond_Taunting) && class == TFClass_Soldier) + { + int weaponEnt = GetPlayerWeaponSlot(client, TFWeaponSlot_Melee); + if (weaponEnt && weaponEnt != INVALID_ENT_REFERENCE) + { + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if (itemDefInt == 775 || itemDefInt == 128) + { + TF2_RemoveCondition(client, TFCond_Taunting); //Stop suiciding... + } + } + } + + if (roundState == 4) + { + if (g_PlayerTrapped[client]) + { + TF2Attrib_SetByName(client, "increased jump height", 0.0); + } + else + { + TF2Attrib_SetByName(client, "increased jump height", 1.0); + } + } + } + else if (g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Blue) + { + bool speedup = TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly); + + switch (class) + { + case TFClass_Scout: + { + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 390.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } + } + case TFClass_Medic: + { + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } + } + case TFClass_Spy: + { + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } + } + default: + { + if (g_InProxySurvivalRageMode) + { + float rageSpeed = ClientGetDefaultSprintSpeed(client) + 30.0; + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); + } + } + } + } + } + } + if (g_PlayerEliminated[client] && IsClientInPvP(client)) + { + SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); + if (IsClientInKart(client)) + { + TF2_RemoveCondition(client,TFCond_HalloweenKart); + TF2_RemoveCondition(client,TFCond_HalloweenKartDash); + TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + } + } + if (IsRoundInWarmup() || (IsRoundInIntro() && !g_PlayerEliminated[client]) || IsRoundEnding()) //I told you, stop breaking my plugin + { + if (IsClientInKart(client)) + { + TF2_RemoveCondition(client,TFCond_HalloweenKart); + TF2_RemoveCondition(client,TFCond_HalloweenKartDash); + TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + } + } + + // Calculate player stress levels. + if (GetGameTime() >= g_PlayerStressNextUpdateTime[client]) + { + //float flPagePercent = g_PageMax != 0 ? float(g_PageCount) / float(g_PageMax) : 0.0; + //float flPageCountPercent = g_PageMax != 0? float(g_PlayerPageCount[client]) / float(g_PageMax) : 0.0; + + g_PlayerStressNextUpdateTime[client] = GetGameTime() + 0.33; + ClientAddStress(client, -0.01); + + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_PLAYER_STRESS, 1, "g_PlayerStressAmount[%d]: %0.1f", client, g_PlayerStressAmount[client]); + #endif + } + + if (g_LastVisibilityProcess[client] + 0.30 >= GetGameTime()) + { + return; + } + + /*if (!g_PlayerEliminated[client]) + { + CNavArea targetArea = SDK_GetLastKnownArea(client);//SDK_GetLastKnownArea(client) =/= g_lastNavArea[client], SDK_GetLastKnownArea() retrives the nav area stored by the server + if (targetArea != INVALID_NAV_AREA) + { + ClientNavAreaUpdate(client, g_lastNavArea[client], targetArea); + g_lastNavArea[client] = targetArea; + } + }*/ + + g_LastVisibilityProcess[client] = GetGameTime(); + + ClientProcessVisibility(client); +} + +Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &damage,int &damagetype,int &weapon, float damageForce[3], float damagePosition[3],int damagecustom) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + Action action = Plugin_Continue; + + float damage2 = damage; + Call_StartForward(g_OnClientTakeDamageFwd); + Call_PushCell(victim); + Call_PushCellRef(attacker); + Call_PushCellRef(inflictor); + Call_PushFloatRef(damage2); + Call_Finish(action); + + if (action == Plugin_Changed) + { + damage = damage2; + return Plugin_Changed; + } + + TFClassType class = TF2_GetPlayerClass(victim); + int classToInt = view_as(class); + + if (IsRoundInWarmup() && IsValidClient(attacker)) + { + float modelScale = GetEntPropFloat(attacker, Prop_Send, "m_flModelScale"); + float headScale = GetEntPropFloat(attacker, Prop_Send, "m_flHeadScale"); + float torsoScale = GetEntPropFloat(attacker, Prop_Send, "m_flTorsoScale"); + float handScale = GetEntPropFloat(attacker, Prop_Send, "m_flHandScale"); + if (modelScale < 1.0 || modelScale > 1.0 || headScale < 1.0 || headScale > 1.0 || torsoScale < 1.0 || torsoScale > 1.0 || handScale < 1.0 || handScale > 1.0) + { + damage = 0.0; //So how does it feel? + return Plugin_Changed; + } + } + + if (IsClientInKart(victim) && (attacker == -1 || inflictor == -1)) + { + damage = 0.0; + return Plugin_Changed; + } + + char inflictorClass[32]; + if (inflictor >= 0) + { + GetEdictClassname(inflictor, inflictorClass, sizeof(inflictorClass)); + } + + if (IsValidClient(attacker) && IsValidClient(victim) && g_PlayerProxy[attacker] && GetClientTeam(victim) == TFTeam_Red && TF2_IsPlayerInCondition(victim, TFCond_Gas)) + { + TF2_IgnitePlayer(victim, victim); + TF2_RemoveCondition(victim, TFCond_Gas); + } + + if (IsValidClient(attacker) && IsValidClient(victim) && IsClientInPvP(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) + { + damage = 0.0; + return Plugin_Changed; + } + + if (IsValidClient(attacker) && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && g_PlayerTrapped[victim]) + { + if (!g_PlayerEliminated[attacker] && !g_PlayerEliminated[victim]) + { + if (damagetype & 0x80) // 0x80 == melee damage + { + g_PlayerTrapped[victim] = false; + TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); + TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); + } + } + } + + if (IsValidClient(attacker) && !g_PlayerEliminated[attacker] && !DidClientEscape(attacker) && class == TFClass_Soldier && !(GetEntityFlags(attacker) & FL_ONGROUND)) + { + int weaponEnt = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); + if (IsValidEntity(weaponEnt)) + { + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + float zVelocity[3]; + GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); + if (itemDefInt == 416 && zVelocity[2] < 0.0 && weaponEnt == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) //A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. + { + damagetype |= DMG_ACID; + } + } + } + + if (IsEntityAProjectile(inflictor)) + { + int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); + if (npcIndex != -1) + { + bool attackEliminated = !!(NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS); + if (!attackEliminated && (GetClientTeam(victim) == TFTeam_Blue) && IsValidClient(victim) ) + { + damage = 0.0; + return Plugin_Changed; + } + } + } + + // Prevent telefrags + if ((damagetype & DMG_CRUSH) && damage > 500.0) + { + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(attacker)); + if (Npc != SF2_INVALID_NPC && IsValidClient(victim)) + { + damage = 0.0; + Npc.UnSpawn(); + return Plugin_Changed; + } + } + + if (attacker != victim && IsValidClient(attacker)) + { + if (!IsRoundEnding()) + { + if (IsRoundInWarmup() || (IsClientInPvP(victim) && IsClientInPvP(attacker))) + { + if (attacker == inflictor) + { + if (IsValidEdict(weapon)) + { + char weaponClass[64]; + GetEdictClassname(weapon, weaponClass, sizeof(weaponClass)); + + // Backstab check! + if ((strcmp(weaponClass, "tf_weapon_knife") == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy") == 0)) && + (damagecustom != TF_CUSTOM_TAUNT_FENCING)) + { + float myPos[3], hisPos[3], myDirection[3]; + GetClientAbsOrigin(victim, myPos); + GetClientAbsOrigin(attacker, hisPos); + GetClientEyeAngles(victim, myDirection); + GetAngleVectors(myDirection, myDirection, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(myDirection, myDirection); + ScaleVector(myDirection, 32.0); + AddVectors(myDirection, myPos, myDirection); + + float p[3], s[3]; + MakeVectorFromPoints(myPos, hisPos, p); + MakeVectorFromPoints(myPos, myDirection, s); + if (GetVectorDotProduct(p, s) <= 0.0)//We can backstab him m8 + { + if (GetClientTeam(victim) == GetClientTeam(attacker) && class == TFClass_Sniper) + { + //look if the player has a razorback + int wearableEnt = INVALID_ENT_REFERENCE; + while ((wearableEnt = FindEntityByClassname(wearableEnt, "tf_wearable")) != -1) + { + if (GetEntPropEnt(wearableEnt, Prop_Send, "m_hOwnerEntity") == victim && GetEntProp(wearableEnt, Prop_Send, "m_iItemDefinitionIndex") == 57) + { + RemoveEntity(wearableEnt); + damage = 0.0; + EmitSoundToClient(victim, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + + SetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); + SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); + int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); + if (vm > MaxClients) + { + int meleeIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); + int anim = 41; + switch (meleeIndex) + { + case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: + { + anim = 15; + } + case 638: + { + anim = 31; + } + } + SetEntProp(vm, Prop_Send, "m_nSequence", anim); + } + return Plugin_Changed; + } + } + } + if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. + { + damage = 120.0; + if (damagetype & DMG_ACID) + { + damage = 120.0; + } + } + + if (g_WeaponCriticalsConVar != null && g_WeaponCriticalsConVar.BoolValue) + { + damagetype |= DMG_ACID; + } + + if (!IsClientCritUbercharged(victim)) + { + if (GetClientTeam(victim) == GetClientTeam(attacker)) + { + int pistol = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); + if (pistol > MaxClients && GetEntProp(pistol, Prop_Send, "m_iItemDefinitionIndex") == 525) //Give one crit fort the backstab + { + int crits = GetEntProp(attacker, Prop_Send, "m_iRevengeCrits"); + crits++; + SetEntProp(attacker, Prop_Send, "m_iRevengeCrits", crits); + } + } + if (GetEntProp(victim, Prop_Send, "m_iHealth") <= 120) + { + g_PlayerBackStabbed[victim] = true; + } + else + { + g_PlayerBackStabbed[victim] = false; + } + } + return Plugin_Changed; + } + } + } + } + } + else if (g_PlayerProxy[victim] || g_PlayerProxy[attacker]) + { + if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) + { + damage = 0.0; + return Plugin_Changed; + } + + if (attacker == victim)//Don't allow proxy to self regenerate control. + { + return Plugin_Continue; + } + + if (g_PlayerProxy[attacker]) + { + int maxHealth = SDKCall(g_SDKGetMaxHealth, victim); + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[attacker]); + if (master != -1) + { + int difficulty = GetLocalGlobalDifficulty(master); + if (damagecustom == TF_CUSTOM_TAUNT_GRAND_SLAM || + damagecustom == TF_CUSTOM_TAUNT_FENCING || + damagecustom == TF_CUSTOM_TAUNT_ARROW_STAB || + damagecustom == TF_CUSTOM_TAUNT_GRENADE || + damagecustom == TF_CUSTOM_TAUNT_BARBARIAN_SWING || + damagecustom == TF_CUSTOM_TAUNT_ENGINEER_ARM || + damagecustom == TF_CUSTOM_TAUNT_ARMAGEDDON) + { + if (damage >= float(maxHealth)) + { + damage = float(maxHealth) * 0.5; + } + else + { + damage = 0.0; + } + } + else if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. + { + damage = float(maxHealth) * g_SlenderProxyDamageVsBackstab[master][difficulty]; + if (damagetype & DMG_ACID) + { + damage /= 2.0; + } + } + + g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitEnemy[master][difficulty]; + if (g_PlayerProxyControl[attacker] > 100) + { + g_PlayerProxyControl[attacker] = 100; + } + + float originalPercentage = g_SlenderProxyDamageVsEnemy[master][difficulty]; + float additionPercentage = 0.15; + if (!IsClassConfigsValid()) + { + if (class == TFClass_Medic) + { + damage *= (originalPercentage + additionPercentage); + } + else + { + damage *= originalPercentage; + } + } + else + { + damage *= originalPercentage + g_ClassProxyDamageVulnerability[classToInt]; + } + } + return Plugin_Changed; + } + else if (g_PlayerProxy[victim]) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[victim]); + if (master != -1) + { + NPCGetProfile(master, profile, sizeof(profile)); + int difficulty = GetLocalGlobalDifficulty(master); + g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitByEnemy[master][difficulty]; + if (g_PlayerProxyControl[attacker] > 100) + { + g_PlayerProxyControl[attacker] = 100; + } + + damage *= g_SlenderProxyDamageVsSelf[master][difficulty]; + } + if (TF2_IsPlayerInCondition(victim, view_as(87))) + { + damage = 0.0; + } + if (damage * (damagetype & DMG_CRIT ? 3.0 : 1.0) >= float(GetClientHealth(victim)) && !TF2_IsPlayerInCondition(victim, view_as(87)))//The proxy is about to die + { + char buffer[PLATFORM_MAX_PATH]; + int classIndex = view_as(TF2_GetPlayerClass(victim)); + ArrayList deathAnims = GetBossProfileProxyDeathAnimations(profile); + if (deathAnims != null) + { + deathAnims.GetString(classIndex, buffer, sizeof(buffer)); + if (buffer[0] == '\0') + { + deathAnims.GetString(0, buffer, sizeof(buffer)); + } + if (buffer[0] != '\0') + { + g_ClientMaxFrameDeathAnim[victim]=GetBossProfileProxyDeathAnimFrames(profile, classIndex); + if (g_ClientMaxFrameDeathAnim[victim] == 0) + { + g_ClientMaxFrameDeathAnim[victim] = GetBossProfileProxyDeathAnimFrames(profile, 0); + } + if (g_ClientMaxFrameDeathAnim[victim] > 0) + { + // Cancel out any other taunts. + if (TF2_IsPlayerInCondition(victim, TFCond_Taunting)) + { + TF2_RemoveCondition(victim, TFCond_Taunting); + } + //The model has a death anim play it. + SDK_PlaySpecificSequence(victim,buffer); + g_ClientFrame[victim] = 0; + RequestFrame(ProxyDeathAnimation,victim); + TF2_AddCondition(victim, view_as(87), 5.0); + //Prevent death, and show the damage to the attacker. + TF2_AddCondition(victim, view_as(70), 0.5); + return Plugin_Changed; + } + } + } + + //the player has no death anim leave him die. + } + return Plugin_Changed; + } + } + else + { + damage = 0.0; + return Plugin_Changed; + } + } + else + { + if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) + { + damage = 0.0; + return Plugin_Changed; + } + } + + if (IsClientInGhostMode(victim)) + { + damage = 0.0; + return Plugin_Changed; + } + } + + return Plugin_Continue; +} + +void ClientOnButtonPress(SF2_BasePlayer client, int button) +{ + switch (button) + { + case IN_ATTACK2: + { + if (client.IsAlive) + { + if (!IsRoundInWarmup() && + !IsRoundInIntro() && + !IsRoundEnding() && + !client.HasEscaped) + { + if (GetGameTime() >= client.GetFlashlightNextInputTime()) + { + client.HandleFlashlight(); + } + } + } + } + case IN_ATTACK3: + { + client.HandleSprint(true); + } + case IN_RELOAD: + { + if (client.IsAlive) + { + if (!client.IsEliminated) + { + if (!IsRoundEnding() && + !IsRoundInWarmup() && + !IsRoundInIntro() && + !client.HasEscaped) + { + client.SetHoldingBlink(true); + client.Blink(); + } + } + } + } + case IN_JUMP: + { + if (client.IsAlive && !(client.GetFlags() & FL_FROZEN)) + { + if (!(client.GetProp(Prop_Send, "m_bDucked")) && + (client.GetFlags() & FL_ONGROUND) && + client.GetProp(Prop_Send, "m_nWaterLevel") < 2) + { + Call_StartForward(g_OnPlayerJumpPFwd); + Call_PushCell(client); + Call_Finish(); + } + } + if (client.IsInGhostMode) + { + client.SetGravity(0.0001); + } + else + { + client.SetGravity(1.0); + } + } + case IN_DUCK: + { + if (client.IsInGhostMode) + { + client.SetGravity(4.0); + } + else + { + client.SetGravity(1.0); + } + } + } +} + +void ClientOnButtonRelease(SF2_BasePlayer client, int button) +{ + switch (button) + { + case IN_ATTACK3: + { + client.HandleSprint(false); + } + case IN_DUCK: + { + client.EndPeeking(); + + if (client.IsInGhostMode) + { + client.SetGravity(0.5); + } + else + { + client.SetGravity(1.0); + } + } + case IN_JUMP: + { + if (client.IsInGhostMode) + { + client.SetGravity(0.5); + } + else + { + client.SetGravity(1.0); + } + } + case IN_RELOAD: + { + if (client.IsAlive) + { + if (!client.IsEliminated) + { + if (!IsRoundEnding() && + !IsRoundInWarmup() && + !IsRoundInIntro() && + !client.HasEscaped) + { + client.SetHoldingBlink(false); + } + } + } + } + } +} + +#define SF2_PLAYER_HUD_BLINK_SYMBOL_ON "O" +#define SF2_PLAYER_HUD_BLINK_SYMBOL_OFF "Ɵ" +#define SF2_PLAYER_HUD_BLINK_SYMBOL_OLD "B" +#define SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL "ϟ" +#define SF2_PLAYER_HUD_BAR_SYMBOL "█" +#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL "░" +#define SF2_PLAYER_HUD_BAR_SYMBOL_OLD "|" +#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD "" +#define SF2_PLAYER_HUD_INFINITY_SYMBOL "∞" +#define SF2_PLAYER_HUD_SPRINT_SYMBOL "»" + +Action Timer_ClientAverageUpdate(Handle timer) +{ + if (timer != g_ClientAverageUpdateTimer) + { + return Plugin_Stop; + } + + if (!g_Enabled) + { + return Plugin_Stop; + } + + if (IsRoundInWarmup() || IsRoundEnding()) + { + return Plugin_Continue; + } + + // First, process through HUD stuff. + char buffer[256]; + + static int hudColorHealthy[3]; + static int hudColorCritical[3] = { 255, 10, 10 }; + + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) + { + hudColorHealthy = { 50, 255, 50 }; + } + else + { + hudColorHealthy = { 150, 255, 150 }; + } + + if (IsPlayerAlive(i) && !IsClientInDeathCam(i)) + { + if (!g_PlayerEliminated[i]) + { + if (DidClientEscape(i)) + { + continue; + } + + int maxBars = 12; + int bars; + if (!SF_IsRaidMap() && !SF_IsBoxingMap()) + { + bars = RoundToCeil(float(maxBars) * ClientGetBlinkMeter(i)); + if (bars > maxBars) + { + bars = maxBars; + } + + if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) + { + if (bars != 0) + { + FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_ON); + } + else + { + FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OFF); + } + } + else + { + FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OLD); + } + + if (IsInfiniteBlinkEnabled()) + { + StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); + } + else + { + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + } + } + if (!SF_SpecialRound(SPECIALROUND_LIGHTSOUT) && !SF_IsRaidMap() && !SF_IsBoxingMap()) + { + bars = RoundToCeil(float(maxBars) * ClientGetFlashlightBatteryLife(i)); + if (bars > maxBars) + { + bars = maxBars; + } + + char buffer2[64]; + FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL); + StrCat(buffer, sizeof(buffer), buffer2); + + if (IsInfiniteFlashlightEnabled()) + { + StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); + } + else + { + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + } + } + + bars = RoundToCeil(float(maxBars) * (float(ClientGetSprintPoints(i)) / 100.0)); + if (bars > maxBars) + { + bars = maxBars; + } + + char buffer2[64]; + FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_SPRINT_SYMBOL); + StrCat(buffer, sizeof(buffer), buffer2); + + if (IsInfiniteSprintEnabled()) + { + StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); + } + else + { + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + } + + float healthRatio = float(GetEntProp(i, Prop_Send, "m_iHealth")) / float(SDKCall(g_SDKGetMaxHealth, i)); + + int color[3]; + for (int i2 = 0; i2 < 3; i2++) + { + color[i2] = RoundFloat(float(hudColorHealthy[i2]) + (float(hudColorCritical[i2] - hudColorHealthy[i2]) * (1.0 - healthRatio))); + } + if (!SF_IsRaidMap() && !SF_IsBoxingMap()) + { + SetHudTextParams(0.035, 0.83, + 0.3, + color[0], + color[1], + color[2], + 40, + _, + 1.0, + 0.07, + 0.5); + } + else if (SF_IsRaidMap() || SF_IsBoxingMap()) + { + SetHudTextParams(0.035, 0.43, + 0.3, + color[0], + color[1], + color[2], + 40, + _, + 1.0, + 0.07, + 0.5); + } + ShowSyncHudText(i, g_HudSync2, buffer); + buffer[0] = '\0'; + } + else + { + if (g_PlayerProxy[i]) + { + int maxBars = 12; + int bars = RoundToCeil(float(maxBars) * (float(g_PlayerProxyControl[i]) / 100.0)); + if (bars > maxBars) + { + bars = maxBars; + } + + strcopy(buffer, sizeof(buffer), "CONTROL\n"); + + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + + SetHudTextParams(-1.0, 0.83, + 0.3, + SF2_HUD_TEXT_COLOR_R, + SF2_HUD_TEXT_COLOR_G, + SF2_HUD_TEXT_COLOR_B, + 40, + _, + 1.0, + 0.07, + 0.5); + ShowSyncHudText(i, g_HudSync2, buffer); + } + } + } + ClientUpdateListeningFlags(i); + ClientUpdateMusicSystem(i); + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/client/ultravision.sp b/addons/sourcemod/scripting/sf2/client/ultravision.sp new file mode 100644 index 00000000..694e3e0c --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/ultravision.sp @@ -0,0 +1,284 @@ +#pragma semicolon 1 + +#define SF2_ULTRAVISION_CONE 180.0 +#define SF2_ULTRAVISION_WIDTH 800.0 +#define SF2_ULTRAVISION_LENGTH 800.0 +#define SF2_ULTRAVISION_BRIGHTNESS -4 // Intensity of Ultravision. + +// Ultravision data. +bool g_PlayerHasUltravision[MAXTF2PLAYERS] = { false, ... }; +int g_PlayerUltravisionEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; + +void SetupUltravision() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + ClientDeactivateUltravision(client.index); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + ClientDeactivateUltravision(client.index); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + ClientDeactivateUltravision(client.index); + if (!client.IsEliminated && !client.HasEscaped) + { + ClientActivateUltravision(client.index); + } +} + +static void OnPlayerDeath(SF2_BasePlayer client) +{ + ClientDeactivateUltravision(client.index); +} + +bool IsClientUsingUltravision(int client) +{ + return g_PlayerHasUltravision[client]; +} + +void ClientActivateUltravision(int client, bool nightVision = false) +{ + if (!IsValidClient(client) || IsClientUsingUltravision(client)) + { + return; + } + + if (!g_PlayerEliminated[client] && (SF_SpecialRound(SPECIALROUND_NOULTRAVISION) && !nightVision)) + { + return; + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("START ClientActivateUltravision(%d)", client); + } + #endif + + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + + g_PlayerHasUltravision[client] = true; + + int ent = CreateEntityByName("light_dynamic"); + if (ent != -1) + { + float eyePos[3]; + GetClientEyePosition(client, eyePos); + + TeleportEntity(ent, eyePos, view_as({ 90.0, 0.0, 0.0 }), NULL_VECTOR); + if (nightVision && !g_PlayerEliminated[client]) + { + switch (g_NightvisionType) + { + case 0: + { + DispatchKeyValue(ent, "rendercolor", "50 255 50"); + } + case 1: + { + DispatchKeyValue(ent, "rendercolor", "255 50 50"); + } + } + } + else + { + DispatchKeyValue(ent, "rendercolor", "100 200 255"); + } + + float radius = 0.0; + if (g_PlayerEliminated[client]) + { + radius = g_UltravisionRadiusBlueConVar.FloatValue; + } + else + { + radius = g_UltravisionRadiusRedConVar.FloatValue; + if (nightVision) + { + radius = g_NightvisionRadiusConVar.FloatValue; + if (!IsClassConfigsValid()) + { + radius = g_NightvisionRadiusConVar.FloatValue; + } + else + { + radius = g_NightvisionRadiusConVar.FloatValue * g_ClassNightvisionRadiusMultiplier[classToInt]; + } + } + if (!nightVision && class == TFClass_Sniper && !IsClassConfigsValid()) + { + radius *= 2.0; + } + else if (!nightVision && IsClassConfigsValid()) + { + radius *= g_ClassUltravisionRadiusMultiplier[classToInt]; + } + } + + SetVariantFloat(radius); + AcceptEntityInput(ent, "spotlight_radius"); + SetVariantFloat(radius); + AcceptEntityInput(ent, "distance"); + + SetVariantInt(-15); // Start dark, then fade in via the Timer_UltravisionFadeInEffect timer func. + AcceptEntityInput(ent, "brightness"); + if (nightVision && !g_PlayerEliminated[client]) + { + if (!IsClassConfigsValid()) + { + SetVariantInt(5); + AcceptEntityInput(ent, "brightness"); + } + else + { + int roundedBrightness = RoundToNearest(5.0 * g_ClassNightvisionBrightnessMultiplier[classToInt]); + SetVariantInt(roundedBrightness); + AcceptEntityInput(ent, "brightness"); + } + } + + // Convert WU to inches. + float cone = SF2_ULTRAVISION_CONE; + cone *= 0.75; + + SetVariantInt(RoundToFloor(cone)); + AcceptEntityInput(ent, "_inner_cone"); + SetVariantInt(0); + AcceptEntityInput(ent, "_cone"); + DispatchSpawn(ent); + ActivateEntity(ent); + SetVariantString("!activator"); + AcceptEntityInput(ent, "SetParent", client); + AcceptEntityInput(ent, "TurnOn"); + //SetEntityRenderFx(ent, RENDERFX_SOLID_FAST); + SetEntityRenderColor(ent, 150, 225, 255, 255); + if (nightVision && !g_PlayerEliminated[client]) + { + switch (g_NightvisionType) + { + case 0: + { + DispatchKeyValue(ent, "rendercolor", "50 255 50"); + } + case 1: + { + DispatchKeyValue(ent, "rendercolor", "255 50 50"); + } + } + } + + g_PlayerUltravisionEnt[client] = EntIndexToEntRef(ent); + + SDKHook(ent, SDKHook_SetTransmit, Hook_UltravisionSetTransmit); + + // Fade in effect. + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer || IsClientInGhostMode(client)) + { + CreateTimer(0.0, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + else if (class == TFClass_Engineer && !IsClientInGhostMode(client)) + { + CreateTimer(0.15, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + else + { + CreateTimer(g_ClassUltravisionFadeInTimer[classToInt], Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientActivateUltravision(%d)", client); + } + #endif +} + +static Action Timer_UltravisionFadeInEffect(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); + if (!ent || ent == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } + + TFClassType class = TF2_GetPlayerClass(client); + int classToInt = view_as(class); + + int brightness = GetEntProp(ent, Prop_Send, "m_Exponent"); + int maxBrightness = g_UltravisionBrightnessConVar.IntValue; + if (!IsClassConfigsValid()) + { + if (class == TFClass_Sniper) + { + maxBrightness = RoundToNearest(float(maxBrightness) * 0.75); + } + } + else + { + maxBrightness = RoundToNearest(float(maxBrightness) * g_ClassUltravisionBrightnessMultiplier[classToInt]); + } + if (brightness >= maxBrightness) + { + return Plugin_Stop; + } + + brightness++; + SetVariantInt(brightness); + AcceptEntityInput(ent, "brightness"); + + return Plugin_Continue; +} + +void ClientDeactivateUltravision(int client) +{ + if (!IsClientUsingUltravision(client)) + { + return; + } + + g_PlayerHasUltravision[client] = false; + + int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); + if (ent != INVALID_ENT_REFERENCE) + { + AcceptEntityInput(ent, "TurnOff"); + RemoveEntity(ent); + } + + g_PlayerUltravisionEnt[client] = INVALID_ENT_REFERENCE; +} + +static Action Hook_UltravisionSetTransmit(int ent,int other) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (!g_UltravisionEnabledConVar.BoolValue || EntRefToEntIndex(g_PlayerUltravisionEnt[other]) != ent || !IsPlayerAlive(other)) + { + return Plugin_Handled; + } + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/debug.sp b/addons/sourcemod/scripting/sf2/debug.sp index 021c9138..af0a6f84 100644 --- a/addons/sourcemod/scripting/sf2/debug.sp +++ b/addons/sourcemod/scripting/sf2/debug.sp @@ -73,7 +73,7 @@ void DebugMessage(const char[] message, any ...) void SendDebugMessageToPlayer(int client,int debugFlags,int type, const char[] message, any ...) { - if (!IsClientInGame(client) || IsFakeClient(client)) + if (!IsClientInGameEx(client) || IsFakeClient(client)) { return; } @@ -106,9 +106,9 @@ void SendDebugMessageToPlayers(int debugFlags,int type, const char[] message, an char msg[1024]; VFormat(msg, sizeof(msg), message, 4); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i)) + if (!IsClientInGameEx(i) || IsFakeClient(i)) { continue; } diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index d9008fe5..955591df 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -696,12 +696,12 @@ static Action Timer_DiscoLight(Handle timer, any effect) } int ent = EntRefToEntIndex(effect); - if (!IsValidEntity(ent)) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } - int slender = GetEntPropEnt(ent,Prop_Send,"moveparent"); + int slender = GetEntPropEnt(ent, Prop_Send, "moveparent"); if (!slender || slender == INVALID_ENT_REFERENCE) { return Plugin_Stop; @@ -736,19 +736,7 @@ static Action Timer_FestiveLight(Handle timer, any effect) } int ent = EntRefToEntIndex(effect); - if (!IsValidEntity(ent)) - { - return Plugin_Stop; - } - - int slender = GetEntPropEnt(ent,Prop_Send,"moveparent"); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } @@ -758,15 +746,15 @@ static Action Timer_FestiveLight(Handle timer, any effect) { case 1: { - SetEntityRenderColor(effect, 230, 37, 37, 255); + SetEntityRenderColor(ent, 230, 37, 37, 255); } case 2: { - SetEntityRenderColor(effect, 94, 227, 79, 255); + SetEntityRenderColor(ent, 94, 227, 79, 255); } case 3: { - SetEntityRenderColor(effect, 235, 235, 235, 255); + SetEntityRenderColor(ent, 235, 235, 235, 255); } } diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 64c9626c..2a0e430b 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -138,13 +138,6 @@ public void OnPluginStart() g_MaxPlayersOverrideConVar = CreateConVar("sf2_maxplayers_override", "-1", "Overrides the maximum amount of players that can be in one round.", _, true, -1.0); g_MaxPlayersOverrideConVar.AddChangeHook(OnConVarChanged); - g_CampingEnabledConVar = CreateConVar("sf2_anticamping_enabled", "1", "Enable/Disable anti-camping system for RED.", _, true, 0.0, true, 1.0); - g_CampingMaxStrikesConVar = CreateConVar("sf2_anticamping_maxstrikes", "4", "How many 5-second intervals players are allowed to stay in one spot before he/she is forced to suicide.", _, true, 0.0); - g_CampingStrikesWarnConVar = CreateConVar("sf2_anticamping_strikeswarn", "2", "The amount of strikes left where the player will be warned of camping."); - g_ExitCampingTimeAllowedConVar = CreateConVar("sf2_exitcamping_allowedtime", "25.0", "The amount of time a player can stay near the exit before being flagged as camper."); - g_CampingMinDistanceConVar = CreateConVar("sf2_anticamping_mindistance", "128.0", "Every 5 seconds the player has to be at least this far away from his last position 5 seconds ago or else he'll get a strike."); - g_CampingNoStrikeSanityConVar = CreateConVar("sf2_anticamping_no_strike_sanity", "0.1", "The camping system will NOT give any strikes under any circumstances if the players's Sanity is missing at least this much of his maximum Sanity (max is 1.0)."); - g_CampingNoStrikeBossDistanceConVar = CreateConVar("sf2_anticamping_no_strike_boss_distance", "512.0", "The camping system will NOT give any strikes under any circumstances if the player is this close to a boss (ignoring LOS)."); g_BossMainConVar = CreateConVar("sf2_boss_main", "slenderman", "The name of the main boss (its profile name, not its display name)"); g_BossProfileOverrideConVar = CreateConVar("sf2_boss_profile_override", "", "Overrides which boss will be chosen next. Only applies to the first boss being chosen."); g_DifficultyConVar = CreateConVar("sf2_difficulty", "1", "Difficulty of the game. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon.", _, true, 1.0, true, 5.0); @@ -336,6 +329,17 @@ public void OnPluginStart() RegAdminCmd("sm_sf2_endless_chasing", Command_EndlessChasing, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_red_player_death_switch", Command_RedDeathTeamSwitch, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_set_queue", Command_SetQueuePoints, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_toggle_intro", Command_ToggleIntro, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_global_alltalk", Command_ToggleGlobalAllTalk, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_block_suicide", Command_ToggleBlockSuicide, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_maxplayers", Command_MaxPlayers, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_maxplayers_override", Command_MaxPlayersOverride, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_specialround_mode", Command_SpecialRoundMode, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_player_infinite_sprint_override", Command_InfiniteSprint, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_player_infinite_flashlight_override", Command_InfiniteFlashlight, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_player_infinite_blink_override", Command_InfiniteBlink, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_wall_hax", Command_WallHax, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_keep_weapons", Command_KeepWeapons, ADMFLAG_SLAY); RegAdminCmd("+alltalk", Command_AllTalkOn, ADMFLAG_SLAY); RegAdminCmd("-alltalk", Command_AllTalkOff, ADMFLAG_SLAY); RegAdminCmd("+slalltalk", Command_AllTalkOn, ADMFLAG_SLAY, _, _, FCVAR_HIDDEN); @@ -387,6 +391,22 @@ public void OnPluginStart() HookUserMessage(GetUserMessageId("SayText2"), Hook_BlockUserMessageEx, true); //HookUserMessage(GetUserMessageId("TextMsg"), Hook_BlockUserMessage, true); + g_OnMapStartPFwd = new PrivateForward(ET_Ignore); + g_OnGameFramePFwd = new PrivateForward(ET_Ignore); + g_OnRoundStartPFwd = new PrivateForward(ET_Ignore); + g_OnRoundEndPFwd = new PrivateForward(ET_Ignore); + g_OnEntityCreatedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_String); + g_OnEntityDestroyedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_String); + g_OnPlayerJumpPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerDeathPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + g_OnPlayerPutInServerPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerDisconnectedPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerEscapePFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerTeamPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerClassPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnBossRemovedPFwd = new PrivateForward(ET_Ignore, Param_Cell); + // Hook sounds. AddNormalSoundHook(view_as(Hook_NormalSound)); @@ -400,8 +420,23 @@ public void OnPluginStart() NPCInitialize(); + SetupTraps(); + SetupMenus(); + SetupAntiCamping(); + SetupBlink(); + SetupBreathing(); + SetupDeathCams(); + SetupGhost(); + SetupProxy(); + SetupHints(); + SetupStatic(); + SetupFlashlight(); + SetupMusic(); + SetupSprint(); + SetupUltravision(); + SetupAdminMenu(); SetupPlayerGroups(); @@ -410,16 +445,31 @@ public void OnPluginStart() SetupCustomMapEntities(); + SetupSpecialRounds(); + + SetupRenevantMode(); + g_FuncNavPrefer = new ArrayList(); + CreateTimer(0.1, Timer_OnEverythingLoaded, _, TIMER_FLAG_NO_MAPCHANGE); + // @TODO: When cvars are finalized, set this to true. AutoExecConfig(false); #if defined DEBUG InitializeDebug(); #endif +} +// You have to do this otherwise SF2_OnEverythingLoaded won't call +static Action Timer_OnEverythingLoaded(Handle timer) +{ + if (!g_Enabled) + { + return Plugin_Stop; + } Call_StartForward(g_OnEverythingLoadedFwd); Call_Finish(); + return Plugin_Stop; } static Action Command_BlockCommand(int args) @@ -452,7 +502,7 @@ static void OnDifficultyConVarChangedForward(ConVar cvar, const char[] oldValue, // COMMANDS AND COMMAND HOOK FUNCTIONS // ========================================================== -static Action Command_Help(int client,int args) +static Action Command_Help(int client, int args) { if (!g_Enabled) { @@ -463,7 +513,7 @@ static Action Command_Help(int client,int args) return Plugin_Handled; } -static Action Command_Settings(int client,int args) +static Action Command_Settings(int client, int args) { if (!g_Enabled) { @@ -474,7 +524,7 @@ static Action Command_Settings(int client,int args) return Plugin_Handled; } -static Action Command_MenuSwitchHud(int client,int args) +static Action Command_MenuSwitchHud(int client, int args) { if (!g_Enabled) { @@ -495,7 +545,7 @@ static Action Command_MenuSwitchHud(int client,int args) return Plugin_Handled; } -static Action Command_MenuViewBob(int client,int args) +static Action Command_MenuViewBob(int client, int args) { if (!g_Enabled) { @@ -512,7 +562,7 @@ static Action Command_MenuViewBob(int client,int args) return Plugin_Handled; } -static Action Command_Credits(int client,int args) +static Action Command_Credits(int client, int args) { if (!g_Enabled) { @@ -523,7 +573,7 @@ static Action Command_Credits(int client,int args) return Plugin_Handled; } -static Action Command_BossList(int client,int args) +static Action Command_BossList(int client, int args) { if (!g_Enabled) { @@ -534,7 +584,7 @@ static Action Command_BossList(int client,int args) return Plugin_Handled; } -static Action Command_NoPoints(int client,int args) +static Action Command_NoPoints(int client, int args) { if (!g_Enabled) { @@ -555,14 +605,14 @@ static Action Command_NoPoints(int client,int args) return Plugin_Handled; } -static Action Command_ToggleFlashlight(int client,int args) +static Action Command_ToggleFlashlight(int client, int args) { if (!g_Enabled) { return Plugin_Continue; } - if (!IsClientInGame(client) || !IsPlayerAlive(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client)) { return Plugin_Handled; } @@ -578,7 +628,7 @@ static Action Command_ToggleFlashlight(int client,int args) return Plugin_Handled; } -static Action Command_SprintOn(int client,int args) +static Action Command_SprintOn(int client, int args) { if (!g_Enabled) { @@ -593,7 +643,7 @@ static Action Command_SprintOn(int client,int args) return Plugin_Handled; } -static Action Command_SprintOff(int client,int args) +static Action Command_SprintOff(int client, int args) { if (!g_Enabled) { @@ -645,7 +695,7 @@ static Action Command_BlinkOff(int client, int args) return Plugin_Handled; } -static Action DevCommand_BossPackVote(int client,int args) +static Action DevCommand_BossPackVote(int client, int args) { if (!g_Enabled) { @@ -655,7 +705,7 @@ static Action DevCommand_BossPackVote(int client,int args) return Plugin_Handled; } -static Action Command_NoPointsAdmin(int client,int args) +static Action Command_NoPointsAdmin(int client, int args) { if (!g_Enabled) { @@ -721,7 +771,7 @@ static Action Command_NoPointsAdmin(int client,int args) return Plugin_Handled; } -static Action Command_MainMenu(int client,int args) +static Action Command_MainMenu(int client, int args) { if (!g_Enabled) { @@ -731,7 +781,7 @@ static Action Command_MainMenu(int client,int args) return Plugin_Handled; } -static Action Command_Tutorial(int client,int args) +static Action Command_Tutorial(int client, int args) { if (!g_Enabled) { @@ -751,7 +801,7 @@ static Action Command_Update(int client, int args) return Plugin_Handled; } -static Action Command_Next(int client,int args) +static Action Command_Next(int client, int args) { if (!g_Enabled) { @@ -762,7 +812,7 @@ static Action Command_Next(int client,int args) return Plugin_Handled; } -static Action Command_Group(int client,int args) +static Action Command_Group(int client, int args) { if (!g_Enabled) { @@ -773,7 +823,7 @@ static Action Command_Group(int client,int args) return Plugin_Handled; } -static Action Command_GroupName(int client,int args) +static Action Command_GroupName(int client, int args) { if (!g_Enabled) { @@ -811,7 +861,7 @@ static Action Command_GroupName(int client,int args) GetPlayerGroupName(groupIndex, oldGroupName, sizeof(oldGroupName)); SetPlayerGroupName(groupIndex, groupName); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -826,7 +876,8 @@ static Action Command_GroupName(int client,int args) return Plugin_Handled; } -static Action Command_GhostMode(int client,int args) + +static Action Command_GhostMode(int client, int args) { if (!g_Enabled) { @@ -882,7 +933,7 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] if (g_PlayerEliminated[client]) { - if (IsClientInGame(client) && !IsPlayerAlive(client) && GetClientTeam(client) == TFTeam_Red) + if (IsValidClient(client) && !IsPlayerAlive(client) && GetClientTeam(client) == TFTeam_Red) { return Plugin_Stop; // Plugin_Stop in this case stops message AND post hook so bot won't see message in OnClientSayCommand_Post() } @@ -1046,7 +1097,7 @@ static Action Hook_CommandVoiceMenu(int client, const char[] command,int argc) return Plugin_Continue; } -static Action Command_ClientKillDeathcam(int client,int args) +static Action Command_ClientKillDeathcam(int client, int args) { if (!g_Enabled) { @@ -1084,7 +1135,7 @@ static Action Command_ClientKillDeathcam(int client,int args) for (int i = 0; i < target_count; i++) { int target = target_list[i]; - if (!IsClientInGame(target) || !IsPlayerAlive(target) || g_PlayerEliminated[target] || IsClientInGhostMode(target)) + if (!IsValidClient(target) || !IsPlayerAlive(target) || g_PlayerEliminated[target] || IsClientInGhostMode(target)) { continue; } @@ -1100,7 +1151,7 @@ static Action Command_ClientKillDeathcam(int client,int args) return Plugin_Handled; } -static Action Command_ClientPerformScare(int client,int args) +static Action Command_ClientPerformScare(int client, int args) { if (!g_Enabled) { @@ -1144,7 +1195,7 @@ static Action Command_ClientPerformScare(int client,int args) return Plugin_Handled; } -static Action Command_SpawnSlender(int client,int args) +static Action Command_SpawnSlender(int client, int args) { if (!g_Enabled) { @@ -1187,7 +1238,7 @@ static Action Command_SpawnSlender(int client,int args) static Handle g_SpawnAllSlendersTimer[MAXTF2PLAYERS] = { null, ... }; static int g_SpawnAllBossesCount = 0; -static Action Command_SpawnAllSlenders(int client,int args) +static Action Command_SpawnAllSlenders(int client, int args) { if (!g_Enabled) { @@ -1279,7 +1330,7 @@ static Action Timer_SpawnAllSlenders(Handle timer, any userid) return Plugin_Continue; } -static Action Command_RemoveSlender(int client,int args) +static Action Command_RemoveSlender(int client, int args) { if (!g_Enabled) { @@ -1321,7 +1372,7 @@ static Action Command_RemoveSlender(int client,int args) return Plugin_Handled; } -static Action Command_RemoveAllSlenders(int client,int args) +static Action Command_RemoveAllSlenders(int client, int args) { if (!g_Enabled) { @@ -1353,7 +1404,7 @@ static Action Command_RemoveAllSlenders(int client,int args) return Plugin_Handled; } -static Action Command_GetBossIndexes(int client,int args) +static Action Command_GetBossIndexes(int client, int args) { if (!g_Enabled) { @@ -1422,7 +1473,7 @@ static Action Command_GetBossIndexes(int client,int args) return Plugin_Handled; } -static Action Command_SlenderAttackWaiters(int client,int args) +static Action Command_SlenderAttackWaiters(int client, int args) { if (!g_Enabled) { @@ -1475,7 +1526,7 @@ static Action Command_SlenderAttackWaiters(int client,int args) return Plugin_Handled; } -static Action Command_SlenderNoTeleport(int client,int args) +static Action Command_SlenderNoTeleport(int client, int args) { if (!g_Enabled) { @@ -1528,7 +1579,7 @@ static Action Command_SlenderNoTeleport(int client,int args) return Plugin_Handled; } -static Action Command_ToggleAllBossTeleports(int client,int args) +static Action Command_ToggleAllBossTeleports(int client, int args) { if (!g_Enabled) { @@ -1574,7 +1625,7 @@ static Action Command_ToggleAllBossTeleports(int client,int args) return Plugin_Handled; } -static Action Command_DebugLogicEscape(int client,int args) +static Action Command_DebugLogicEscape(int client, int args) { if (!g_Enabled) { @@ -1608,7 +1659,7 @@ static Action Command_DebugLogicEscape(int client,int args) return Plugin_Handled; } -static Action Command_ForceEndGrace(int client,int args) +static Action Command_ForceEndGrace(int client, int args) { if (!g_Enabled) { @@ -1639,7 +1690,7 @@ static Action Command_ReloadProfiles(int client, int args) return Plugin_Handled; } -static Action Command_ToggleAllAttackWaiters(int client,int args) +static Action Command_ToggleAllAttackWaiters(int client, int args) { if (!g_Enabled) { @@ -1685,7 +1736,7 @@ static Action Command_ToggleAllAttackWaiters(int client,int args) return Plugin_Handled; } -static Action Command_ForceProxy(int client,int args) +static Action Command_ForceProxy(int client, int args) { if (!g_Enabled) { @@ -1778,7 +1829,7 @@ static Action Command_ForceProxy(int client,int args) return Plugin_Handled; } -static Action Command_ForceEscape(int client,int args) +static Action Command_ForceEscape(int client, int args) { if (!g_Enabled) { @@ -1824,7 +1875,7 @@ static Action Command_ForceEscape(int client,int args) return Plugin_Handled; } -static Action Command_ForceDifficulty(int client,int args) +static Action Command_ForceDifficulty(int client, int args) { if (!g_Enabled) { @@ -1894,7 +1945,7 @@ static Action Command_ForceDifficulty(int client,int args) return Plugin_Handled; } -static Action Command_ForceSpecialRound(int client,int args) +static Action Command_ForceSpecialRound(int client, int args) { if (!g_Enabled) { @@ -2084,7 +2135,7 @@ static Action Command_ForceSpecialRound(int client,int args) return Plugin_Handled; } -static Action Command_AddSlender(int client,int args) +static Action Command_AddSlender(int client, int args) { if (!g_Enabled) { @@ -2127,14 +2178,13 @@ static Action Command_AddSlender(int client,int args) return Plugin_Handled; } -static void NPCSpawn(const char[] output,int ent,int activator, float delay) +static void NPCSpawn(const char[] output, int ent, int activator, float delay) { if (!g_Enabled) { return; } char targetName[255]; - float vecPos[3]; GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); if (targetName[0] != '\0') { @@ -2151,7 +2201,7 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) } char profile[SF2_MAX_PROFILE_NAME_LENGTH]; SF2NPC_BaseNPC Npc; - for(int npcIndex;npcIndex<=MAX_BOSSES;npcIndex++) + for(int npcIndex = 0; npcIndex<=MAX_BOSSES; npcIndex++) { Npc = view_as(npcIndex); if (Npc.IsValid()) @@ -2161,7 +2211,7 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) { Npc.UnSpawn(); float pos[3]; - GetEntPropVector(ent, Prop_Data, "m_vecOrigin", pos); + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); SpawnSlender(Npc, pos); break; } @@ -2176,11 +2226,15 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) spawnPoint.Push(ent); } ent = -1; - if (spawnPoint.Length > 0) ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); + if (spawnPoint.Length > 0) + { + ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); + } delete spawnPoint; - if (ent > MaxClients) + if (IsValidEntity(ent)) { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", vecPos); + float pos[3]; + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { SF2NPC_BaseNPC Npc = view_as(npcIndex); @@ -2193,7 +2247,7 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) continue; } Npc.UnSpawn(); - SpawnSlender(Npc, vecPos); + SpawnSlender(Npc, pos); break; } } @@ -2202,7 +2256,7 @@ static void NPCSpawn(const char[] output,int ent,int activator, float delay) return; } -static Action Command_AddSlenderFake(int client,int args) +static Action Command_AddSlenderFake(int client, int args) { if (!g_Enabled) { @@ -2241,7 +2295,7 @@ static Action Command_AddSlenderFake(int client,int args) return Plugin_Handled; } -static Action Command_ForceState(int client,int args) +static Action Command_ForceState(int client, int args) { if (!g_Enabled) { @@ -2294,14 +2348,14 @@ static Action Command_ForceState(int client,int args) if (IsClientSourceTV(target)) { - continue;//Exclude the sourcetv bot + continue; //Exclude the sourcetv bot } FormatEx(name, sizeof(name), "%N", target); if (g_PlayerProxy[target]) { - continue;//Can't force proxies + continue; //Can't force proxies } if (state && g_PlayerEliminated[target]) @@ -2353,7 +2407,7 @@ static Action Command_AllTalkToggle(int client, int args) g_AdminAllTalk[client] = !g_AdminAllTalk[client]; CPrintToChat(client, "{royalblue}%t {default}You will %s hear and speak to all players.", "SF2 Prefix", g_AdminAllTalk[client] ? "now" : "no longer"); - for (int target = 1; target <= MaxClients; target++) + for (int target = 1; target < MaxClients; target++) { ClientUpdateListeningFlags(target); } @@ -2364,7 +2418,7 @@ static Action Command_AllTalkOn(int client, int args) { g_AdminAllTalk[client] = true; - for (int target = 1; target <= MaxClients; target++) + for (int target = 1; target < MaxClients; target++) { ClientUpdateListeningFlags(target); } @@ -2375,7 +2429,7 @@ static Action Command_AllTalkOff(int client, int args) { g_AdminAllTalk[client] = false; - for (int target = 1; target <= MaxClients; target++) + for (int target = 1; target < MaxClients; target++) { ClientUpdateListeningFlags(target); } @@ -2460,4 +2514,248 @@ static Action Command_SetQueuePoints(int client, int args) } return Plugin_Handled; -} \ No newline at end of file +} + +static Action Command_ToggleIntro(int client, int args) +{ + g_IntroEnabledConVar.BoolValue = !g_IntroEnabledConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}Intro is now %s.", "SF2 Prefix", g_IntroEnabledConVar.BoolValue ? "enabled" : "disabled"); + return Plugin_Handled; +} + +static Action Command_ToggleGlobalAllTalk(int client, int args) +{ + g_AllChatConVar.BoolValue = !g_AllChatConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}All talk is now %s.", "SF2 Prefix", g_AllChatConVar.BoolValue ? "enabled" : "disabled"); + return Plugin_Handled; +} + +static Action Command_ToggleBlockSuicide(int client, int args) +{ + g_BlockSuicideDuringRoundConVar.BoolValue = !g_BlockSuicideDuringRoundConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}RED players can %s suicide during rounds.", "SF2 Prefix", g_BlockSuicideDuringRoundConVar.BoolValue ? "now" : "no longer"); + return Plugin_Handled; +} + +static Action Command_MaxPlayers(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_maxplayers "); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int amount = StringToInt(arg1); + + if (amount < 1) + { + amount = 1; + } + else if (amount > 32) + { + amount = 32; + } + + g_MaxPlayersConVar.IntValue = amount; + CPrintToChat(client, "{royalblue}%t {default}Set the max players to %i.", "SF2 Prefix", amount); + return Plugin_Handled; +} + +static Action Command_MaxPlayersOverride(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_maxplayers_override "); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int amount = StringToInt(arg1); + + if (amount < 1) + { + amount = 1; + } + else if (amount > 32) + { + amount = 32; + } + + g_MaxPlayersOverrideConVar.IntValue = amount; + CPrintToChat(client, "{royalblue}%t {default}Set the overrided max players to %i.", "SF2 Prefix", amount); + return Plugin_Handled; +} + +static Action Command_SpecialRoundMode(int client, int args) +{ + g_SpecialRoundBehaviorConVar.BoolValue = !g_SpecialRoundBehaviorConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}Set the special rounds to %s.", "SF2 Prefix", g_BlockSuicideDuringRoundConVar.BoolValue ? "always reset upon the next round" : "keep going until all players have played a special round"); + return Plugin_Handled; +} + +static Action Command_InfiniteSprint(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_player_infinite_sprint_override <-1 - 1>"); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int state = StringToInt(arg1); + + if (state < -1) + { + state = 1; + } + else if (state > 1) + { + state = 1; + } + + g_PlayerInfiniteSprintOverrideConVar.IntValue = state; + switch (state) + { + case -1: + { + CPrintToChat(client, "{royalblue}%t {default}The game will now determine whether infinite sprint is allowed or not.", "SF2 Prefix"); + } + case 0: + { + CPrintToChat(client, "{royalblue}%t {default}Fully disabled infinite sprint across all maps.", "SF2 Prefix"); + } + case 1: + { + CPrintToChat(client, "{royalblue}%t {default}Fully enabled infinite sprint across all maps.", "SF2 Prefix"); + } + } + return Plugin_Handled; +} + +static Action Command_InfiniteFlashlight(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_player_infinite_flashlight_override <-1 - 1>"); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int state = StringToInt(arg1); + + if (state < -1) + { + state = 1; + } + else if (state > 1) + { + state = 1; + } + + g_PlayerInfiniteFlashlightOverrideConVar.IntValue = state; + switch (state) + { + case -1: + { + CPrintToChat(client, "{royalblue}%t {default}The game will now determine whether infinite flashlight is allowed or not.", "SF2 Prefix"); + } + case 0: + { + CPrintToChat(client, "{royalblue}%t {default}Fully disabled infinite flashlight across all maps.", "SF2 Prefix"); + } + case 1: + { + CPrintToChat(client, "{royalblue}%t {default}Fully enabled infinite flashlight across all maps.", "SF2 Prefix"); + } + } + return Plugin_Handled; +} + +static Action Command_InfiniteBlink(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_sf2_player_infinite_blink_override <-1 - 1>"); + return Plugin_Handled; + } + + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + int state = StringToInt(arg1); + + if (state < -1) + { + state = 1; + } + else if (state > 1) + { + state = 1; + } + + g_PlayerInfiniteBlinkOverrideConVar.IntValue = state; + switch (state) + { + case -1: + { + CPrintToChat(client, "{royalblue}%t {default}The game will now determine whether infinite blink is allowed or not.", "SF2 Prefix"); + } + case 0: + { + CPrintToChat(client, "{royalblue}%t {default}Fully disabled infinite blink across all maps.", "SF2 Prefix"); + } + case 1: + { + CPrintToChat(client, "{royalblue}%t {default}Fully enabled infinite blink across all maps.", "SF2 Prefix"); + } + } + return Plugin_Handled; +} + +static Action Command_WallHax(int client, int args) +{ + g_EnableWallHaxConVar.BoolValue = !g_EnableWallHaxConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}%s Wall Hax.", "SF2 Prefix", g_EnableWallHaxConVar.BoolValue ? "enabled" : "disabled"); + return Plugin_Handled; +} + +static Action Command_KeepWeapons(int client, int args) +{ + g_PlayerKeepWeaponsConVar.BoolValue = !g_PlayerKeepWeaponsConVar.BoolValue; + CPrintToChat(client, "{royalblue}%t {default}Lobby players can %s keep their weapons.", "SF2 Prefix", g_PlayerKeepWeaponsConVar.BoolValue ? "now" : "no longer"); + return Plugin_Handled; +} diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 9a6d2617..0f49ce2a 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -48,7 +48,7 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) } // Refresh players. - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { ClientSetGhostModeState(i, false); @@ -89,7 +89,8 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) InitializeNewGame(); } - PvP_OnRoundStart(); + Call_StartForward(g_OnRoundStartPFwd); + Call_Finish(); #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) { @@ -101,23 +102,6 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) delete profiler; #endif - //Nextbot doesn't trigger the triggers with npc flags, for map backward compatibility we are going to change the trigger filter and force a custom one. - /*int iEnt = -1; - while ((iEnt = FindEntityByClassname(iEnt, "trigger_*")) != -1) - { - if (IsValidEntity(iEnt)) - { - int flags = GetEntProp(iEnt, Prop_Data, "m_spawnflags"); - if ((flags & TRIGGER_NPCS) && !(flags & TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS)) - { - //Set the trigger to allow every entity, our custom filter will discard the unwanted entities. - SetEntProp(iEnt, Prop_Data, "m_spawnflags", flags|TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS); - SDKHook(iEnt, SDKHook_StartTouch, Hook_TriggerNPCTouch); - SDKHook(iEnt, SDKHook_Touch, Hook_TriggerNPCTouch); - SDKHook(iEnt, SDKHook_EndTouch, Hook_TriggerNPCTouch); - } - } - }*/ return Plugin_Continue; } @@ -131,7 +115,7 @@ Action Event_WinPanel(Event event, const char[] name, bool dontBroadcast) char cappers[7]; int i = 0; - for (int client; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && DidClientEscape(client) && i < 7) { @@ -146,10 +130,10 @@ Action Event_WinPanel(Event event, const char[] name, bool dontBroadcast) Action Event_Audio(Event event, const char[] name, bool dB) { - char strAudio[PLATFORM_MAX_PATH]; + char audio[PLATFORM_MAX_PATH]; - GetEventString(event, "sound", strAudio, sizeof(strAudio)); - if (strncmp(strAudio, "Game.Your", 9) == 0 || strcmp(strAudio, "Game.Stalemate") == 0) + GetEventString(event, "sound", audio, sizeof(audio)); + if (strncmp(audio, "Game.Your", 9) == 0 || strcmp(audio, "Game.Stalemate") == 0) { for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) { @@ -184,9 +168,7 @@ Action Event_RoundEnd(Handle event, const char[] name, bool dB) SF_FailEnd(); - if (SF_IsRenevantMap() && g_RenevantWaveTimer != null) KillTimer(g_RenevantWaveTimer); - - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -234,8 +216,6 @@ Action Event_RoundEnd(Handle event, const char[] name, bool dB) delete randomBosses; - SpecialRound_RoundEnd(); - SetRoundState(SF2RoundState_Outro); DistributeQueuePointsToPlayers(); @@ -243,6 +223,9 @@ Action Event_RoundEnd(Handle event, const char[] name, bool dB) g_RoundEndCount++; CheckRoundLimitForBossPackVote(g_RoundEndCount); + Call_StartForward(g_OnRoundEndPFwd); + Call_Finish(); + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) { @@ -292,8 +275,6 @@ Action Event_PlayerTeam(Handle event, const char[] name, bool dB) g_PlayerEliminated[client] = true; g_PlayerEscaped[client] = false; - ClientSetGhostModeState(client, false); - if (!GetEntProp(client, Prop_Send, "m_bIsCoaching")) { // This is to prevent player spawn spam when someone is coaching. Who coaches in SF2, anyway? @@ -336,118 +317,11 @@ Action Event_PlayerTeam(Handle event, const char[] name, bool dB) CreateTimer(5.0, Timer_WelcomeMessage, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !g_PlayerEliminated[client] && teamNum == TFTeam_Red && - !DidClientEscape(client)) - { - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - ShowVGUIPanel(client, "class_red"); - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - TFClassType newClass; - int random = GetRandomInt(1, 8); - switch (random) - { - case 1: - { - newClass = TFClass_Scout; - } - case 2: - { - newClass = TFClass_Soldier; - } - case 3: - { - newClass = TFClass_Pyro; - } - case 4: - { - newClass = TFClass_DemoMan; - } - case 5: - { - newClass = TFClass_Heavy; - } - case 6: - { - newClass = TFClass_Engineer; - } - case 7: - { - newClass = TFClass_Sniper; - } - case 8: - { - newClass = TFClass_Spy; - } - } - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - } - } - else - { - if (g_ClassBlockedOnThanatophobia[classToInt]) - { - ShowVGUIPanel(client, "class_red"); - switch (class) - { - case TFClass_Scout: - { - EmitSoundToClient(client, THANATOPHOBIA_SCOUTNO); - } - case TFClass_Soldier: - { - EmitSoundToClient(client, THANATOPHOBIA_SOLDIERNO); - } - case TFClass_Pyro: - { - EmitSoundToClient(client, THANATOPHOBIA_PYRONO); - } - case TFClass_DemoMan: - { - EmitSoundToClient(client, THANATOPHOBIA_DEMOMANNO); - } - case TFClass_Heavy: - { - EmitSoundToClient(client, THANATOPHOBIA_HEAVYNO); - } - case TFClass_Engineer: - { - EmitSoundToClient(client, THANATOPHOBIA_ENGINEERNO); - } - case TFClass_Medic: - { - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - } - case TFClass_Sniper: - { - EmitSoundToClient(client, THANATOPHOBIA_SNIPERNO); - } - case TFClass_Spy: - { - EmitSoundToClient(client, THANATOPHOBIA_SPYNO); - } - } - ArrayList classArrays = new ArrayList(); - for (int i = 1; i < MAX_CLASSES + 1; i++) - { - if (!g_ClassBlockedOnThanatophobia[i]) - { - classArrays.Push(view_as(i)); - } - } - TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - delete classArrays; - } - } - } } + Call_StartForward(g_OnPlayerTeamPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(teamNum); + Call_Finish(); } // Check groups. @@ -498,10 +372,14 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } #endif + for (int i = 0; i < view_as(TFCond_PowerupModeDominant); i++) + { + g_ClientInCondition[client][i] = false; + } + if (GetClientTeam(client) > 1) { g_LastVisibilityProcess[client] = GetGameTime(); - ClientResetStatic(client); if (!g_SeeUpdateMenu[client]) { g_SeeUpdateMenu[client] = true; @@ -513,38 +391,18 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) TF2Attrib_SetByName(client, "increased jump height", 1.0); TF2Attrib_RemoveByDefIndex(client, 10); - ClientSetGhostModeState(client, false); SetEntityGravity(client, 1.0); g_PlayerPageCount[client] = 0; - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); - ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); ClientResetInteractiveGlow(client); ClientDisableConstantGlow(client); - ClientHandleGhostMode(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -615,34 +473,15 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) { g_PlayerPageCount[client] = 0; - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientUpdateListeningFlags(client); - ClientUpdateMusicSystem(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - ClientResetProxy(client); - ClientResetHints(client); ClientResetScare(client); - ClientResetDeathCam(client); - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); ClientResetInteractiveGlow(client); ClientDisableConstantGlow(client); - ClientHandleGhostMode(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -679,16 +518,9 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) CreateTimer(0.1, Timer_StopAirDash, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) - { - TF2_StripContrackerOnly(client); - } - ClientStartDrainingBlinkMeter(client); ClientSetScareBoostEndTime(client, -1.0); - ClientStartCampingTimer(client); - HandlePlayerIntroState(client); if (IsFakeClient(client)) @@ -710,136 +542,12 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) { int red[4] = { 184, 56, 59, 255 }; ClientEnableConstantGlow(client, red); - ClientActivateUltravision(client); } if (SF_IsRenevantMap() && g_RenevantMarkForDeath && !DidClientEscape(client)) { TF2_AddCondition(client, TFCond_MarkedForDeathSilent, -1.0); } - - if (SF_SpecialRound(SPECIALROUND_1UP) && !g_PlayerIn1UpCondition[client] && !g_PlayerDied1Up[client]) - { - g_PlayerDied1Up[client] = false; - g_PlayerIn1UpCondition[client] = true; - g_PlayerFullyDied1Up[client] = false; - } - - if (SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) - { - ClientSetSpecialRoundTimer(client, 0.0, Timer_ClientPageDetector, GetClientUserId(client)); - } - - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !DidClientEscape(client)) - { - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - ShowVGUIPanel(client, "class_red"); - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - TFClassType newClass; - int random = GetRandomInt(1, 8); - switch (random) - { - case 1: - { - newClass = TFClass_Scout; - } - case 2: - { - newClass = TFClass_Soldier; - } - case 3: - { - newClass = TFClass_Pyro; - } - case 4: - { - newClass = TFClass_DemoMan; - } - case 5: - { - newClass = TFClass_Heavy; - } - case 6: - { - newClass = TFClass_Engineer; - } - case 7: - { - newClass = TFClass_Sniper; - } - case 8: - { - newClass = TFClass_Spy; - } - } - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - } - } - else - { - if (g_ClassBlockedOnThanatophobia[classToInt]) - { - ShowVGUIPanel(client, "class_red"); - switch (class) - { - case TFClass_Scout: - { - EmitSoundToClient(client, THANATOPHOBIA_SCOUTNO); - } - case TFClass_Soldier: - { - EmitSoundToClient(client, THANATOPHOBIA_SOLDIERNO); - } - case TFClass_Pyro: - { - EmitSoundToClient(client, THANATOPHOBIA_PYRONO); - } - case TFClass_DemoMan: - { - EmitSoundToClient(client, THANATOPHOBIA_DEMOMANNO); - } - case TFClass_Heavy: - { - EmitSoundToClient(client, THANATOPHOBIA_HEAVYNO); - } - case TFClass_Engineer: - { - EmitSoundToClient(client, THANATOPHOBIA_ENGINEERNO); - } - case TFClass_Medic: - { - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - } - case TFClass_Sniper: - { - EmitSoundToClient(client, THANATOPHOBIA_SNIPERNO); - } - case TFClass_Spy: - { - EmitSoundToClient(client, THANATOPHOBIA_SPYNO); - } - } - ArrayList classArrays = new ArrayList(); - for (int i = 1; i < MAX_CLASSES + 1; i++) - { - if (!g_ClassBlockedOnThanatophobia[i]) - { - classArrays.Push(view_as(i)); - } - } - TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - delete classArrays; - } - } - } } else { @@ -855,7 +563,9 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } } - PvP_OnPlayerSpawn(client); + Call_StartForward(g_OnPlayerSpawnPFwd); + Call_PushCell(client); + Call_Finish(); #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) @@ -881,119 +591,9 @@ void Event_PlayerClass(Event event, const char[] name, bool dontBroadcast) return; } - int teamNum = GetClientTeam(client); - - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !g_PlayerEliminated[client] && - teamNum == TFTeam_Red && TF2_GetPlayerClass(client) == TFClass_Medic && !DidClientEscape(client)) - { - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - ShowVGUIPanel(client, "class_red"); - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - TFClassType newClass; - int random = GetRandomInt(1, 8); - switch (random) - { - case 1: - { - newClass = TFClass_Scout; - } - case 2: - { - newClass = TFClass_Soldier; - } - case 3: - { - newClass = TFClass_Pyro; - } - case 4: - { - newClass = TFClass_DemoMan; - } - case 5: - { - newClass = TFClass_Heavy; - } - case 6: - { - newClass = TFClass_Engineer; - } - case 7: - { - newClass = TFClass_Sniper; - } - case 8: - { - newClass = TFClass_Spy; - } - } - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - } - } - else - { - if (g_ClassBlockedOnThanatophobia[classToInt]) - { - ShowVGUIPanel(client, "class_red"); - switch (class) - { - case TFClass_Scout: - { - EmitSoundToClient(client, THANATOPHOBIA_SCOUTNO); - } - case TFClass_Soldier: - { - EmitSoundToClient(client, THANATOPHOBIA_SOLDIERNO); - } - case TFClass_Pyro: - { - EmitSoundToClient(client, THANATOPHOBIA_PYRONO); - } - case TFClass_DemoMan: - { - EmitSoundToClient(client, THANATOPHOBIA_DEMOMANNO); - } - case TFClass_Heavy: - { - EmitSoundToClient(client, THANATOPHOBIA_HEAVYNO); - } - case TFClass_Engineer: - { - EmitSoundToClient(client, THANATOPHOBIA_ENGINEERNO); - } - case TFClass_Medic: - { - EmitSoundToClient(client, THANATOPHOBIA_MEDICNO); - } - case TFClass_Sniper: - { - EmitSoundToClient(client, THANATOPHOBIA_SNIPERNO); - } - case TFClass_Spy: - { - EmitSoundToClient(client, THANATOPHOBIA_SPYNO); - } - } - ArrayList classArrays = new ArrayList(); - for (int i = 1; i < MAX_CLASSES + 1; i++) - { - if (!g_ClassBlockedOnThanatophobia[i]) - { - classArrays.Push(view_as(i)); - } - } - TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); - TF2_SetPlayerClass(client, newClass); - TF2_RegeneratePlayer(client); - delete classArrays; - } - } - } + Call_StartForward(g_OnPlayerClassPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } Action Event_PostInventoryApplication(Handle event, const char[] name, bool dB) @@ -1010,11 +610,11 @@ Action Event_PostInventoryApplication(Handle event, const char[] name, bool dB) } #endif - int client = GetClientOfUserId(GetEventInt(event, "userid")); - if (client > 0) + SF2_BasePlayer client = SF2_BasePlayer(GetClientOfUserId(GetEventInt(event, "userid"))); + if (client.IsValid) { - ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - g_PlayerPostWeaponsTimer[client] = CreateTimer(0.1, Timer_ClientPostWeapons, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + client.SwitchToWeaponSlot(TFWeaponSlot_Melee); + g_PlayerPostWeaponsTimer[client.index] = CreateTimer(0.1, Timer_ClientPostWeapons, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } #if defined DEBUG @@ -1064,15 +664,25 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) TF2Attrib_RemoveByName(client, "crit mod disabled"); } + for (int i = 0; i < view_as(TFCond_PowerupModeDominant); i++) + { + g_ClientInCondition[client][i] = false; + } + int inflictor = event.GetInt("inflictor_entindex"); + int owner = inflictor; // If this player was killed by a boss, play a sound. - int npcIndex = NPCGetFromEntIndex(inflictor); - if (npcIndex != -1 && !IsEntityAProjectile(inflictor)) + if (IsEntityAProjectile(inflictor)) + { + owner = GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity"); + } + int npcIndex = NPCGetFromEntIndex(owner); + if (npcIndex != -1) { int target = GetClientOfUserId(g_SourceTVUserID); int attackIndex = NPCGetCurrentAttackIndex(npcIndex); - if (MaxClients < target || target < 1 || !IsClientInGame(target) || !IsClientSourceTV(target)) //If the server has a source TV bot uses to print boss' name in kill feed. + if (!IsValidClient(target) || !IsClientSourceTV(target)) //If the server has a source TV bot uses to print boss' name in kill feed. { target = GetClientForDeath(client); } @@ -1096,134 +706,37 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) SetEntPropString(target, Prop_Data, "m_szNetname", bossName); event.SetString("assister_fallback", ""); - if ((NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) || (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS)) - { - if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) - { - char weaponType[PLATFORM_MAX_PATH]; - int weaponNum = NPCChaserGetAttackWeaponTypeInt(npcIndex, attackIndex); - GetChaserProfileAttackWeaponTypeString(profile, attackIndex, weaponType, sizeof(weaponType)); - event.SetString("weapon_logclassname", weaponType); - event.SetString("weapon", weaponType); - event.SetInt("customkill", weaponNum); - } - else if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS) - { - char weaponType[PLATFORM_MAX_PATH]; - int weaponNum = GetBossProfileWeaponInt(profile); - GetBossProfileWeaponString(profile, weaponType, sizeof(weaponType)); - event.SetString("weapon_logclassname", weaponType); - event.SetString("weapon", weaponType); - event.SetInt("customkill", weaponNum); - } - } - else - { - event.SetString("weapon", ""); - event.SetString("weapon_logclassname", ""); - } - - int userid = GetClientUserId(target); - event.SetInt("attacker", userid); - g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - if (IsValidClient(target)) + if (!IsEntityAProjectile(inflictor)) { - event.SetInt("ignore", target); - - //Show a different attacker to the user were taking - target = GetClientForDeath(client, target); - if (target != -1) + if ((NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) || (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS)) { - if (g_TimerChangeClientName[target] != null) + if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) { - KillTimer(g_TimerChangeClientName[target]); + char weaponType[PLATFORM_MAX_PATH]; + int weaponNum = NPCChaserGetAttackWeaponTypeInt(npcIndex, attackIndex); + GetChaserProfileAttackWeaponTypeString(profile, attackIndex, weaponType, sizeof(weaponType)); + event.SetString("weapon_logclassname", weaponType); + event.SetString("weapon", weaponType); + event.SetInt("customkill", weaponNum); } - else + else if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS) { - GetEntPropString(target, Prop_Data, "m_szNetname", g_OldClientName[target], sizeof(g_OldClientName[])); + char weaponType[PLATFORM_MAX_PATH]; + int weaponNum = GetBossProfileWeaponInt(profile); + GetBossProfileWeaponString(profile, weaponType, sizeof(weaponType)); + event.SetString("weapon_logclassname", weaponType); + event.SetString("weapon", weaponType); + event.SetInt("customkill", weaponNum); } - - Format(bossName, sizeof(bossName), " %s", bossName); - - SetClientName(target, bossName); - SetEntPropString(target, Prop_Data, "m_szNetname", bossName); - - g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - char string[64]; - Event event2 = CreateEvent("player_death", true); - event2.SetInt("userid", event.GetInt("userid")); - event2.SetInt("victim_entindex", event.GetInt("victim_entindex")); - event2.SetInt("inflictor_entindex", event.GetInt("inflictor_entindex")); - event2.SetInt("attacker", GetClientUserId(target)); - event2.SetInt("weaponid", event.GetInt("weaponid")); - event2.SetInt("damagebits", event.GetInt("damagebits")); - event2.SetInt("customkill", event.GetInt("customkill")); - event2.SetInt("assister", event.GetInt("assister")); - event2.SetInt("stun_flags", event.GetInt("stun_flags")); - event2.SetInt("death_flags", event.GetInt("death_flags")); - event2.SetBool("silent_kill", event.GetBool("silent_kill")); - event2.SetInt("playerpenetratecount", event.GetInt("playerpenetratecount")); - event2.SetInt("kill_streak_total", event.GetInt("kill_streak_total")); - event2.SetInt("kill_streak_wep", event.GetInt("kill_streak_wep")); - event2.SetInt("kill_streak_assist", event.GetInt("kill_streak_assist")); - event2.SetInt("kill_streak_victim", event.GetInt("kill_streak_victim")); - event2.SetInt("ducks_streaked", event.GetInt("ducks_streaked")); - event2.SetInt("duck_streak_total", event.GetInt("duck_streak_total")); - event2.SetInt("duck_streak_assist", event.GetInt("duck_streak_assist")); - event2.SetInt("duck_streak_victim", event.GetInt("duck_streak_victim")); - event2.SetBool("rocket_jump", event.GetBool("rocket_jump")); - event2.SetInt("weapon_def_index", event.GetInt("weapon_def_index")); - event.GetString("weapon_logclassname", string, sizeof(string)); - event2.SetString("weapon_logclassname", string); - event.GetString("weapon", string, sizeof(string)); - event2.SetString("weapon", string); - event2.SetInt("send", userid); - - CreateTimer(0.2, Timer_SendDeathToSpecific, event2); } - } - } - } - - #if defined DEBUG - char stringName[128]; - event.GetString("weapon", stringName, sizeof(stringName)); - SendDebugMessageToPlayers(DEBUG_KILLICONS, 0, "String kill icon is %s, integer kill icon is %i.", stringName, event.GetInt("customkill")); - #endif - - if (IsEntityAProjectile(inflictor)) - { - int npcIndex2 = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); - if (npcIndex2 != -1) - { - int target = GetClientOfUserId(g_SourceTVUserID); - if (MaxClients < target || target < 1 || !IsClientInGame(target) || !IsClientSourceTV(target)) //If the server has a source TV bot uses to print boss' name in kill feed. - { - target = GetClientForDeath(client); - } - - if (target != -1) - { - if (g_TimerChangeClientName[target] != null) - { - KillTimer(g_TimerChangeClientName[target]); - } - else //No timer running that means the SourceTV bot's current name is the correct one, we can safely update our last known SourceTV bot's name. + else { - GetEntPropString(target, Prop_Data, "m_szNetname", g_OldClientName[target], sizeof(g_OldClientName[])); + event.SetString("weapon", ""); + event.SetString("weapon_logclassname", ""); } - - char bossName[SF2_MAX_NAME_LENGTH], profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(npcIndex2, profile, sizeof(profile)); - NPCGetBossName(npcIndex2, bossName, sizeof(bossName)); - - SetClientName(target, bossName); - SetEntPropString(target, Prop_Data, "m_szNetname", bossName); - - event.SetString("assister_fallback", ""); - + } + else + { switch (ProjectileGetFlags(inflictor)) { case PROJ_ROCKET: @@ -1252,72 +765,20 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) event.SetString("weapon", "obj_sentrygun3"); } } - - int userid = GetClientUserId(target); - event.SetInt("attacker", userid); - g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - if (IsValidClient(target)) - { - event.SetInt("ignore", target); - - //Show a different attacker to the user were taking - target = GetClientForDeath(client, target); - if (target != -1) - { - if (g_TimerChangeClientName[target] != null) - { - KillTimer(g_TimerChangeClientName[target]); - } - else - { - GetEntPropString(target, Prop_Data, "m_szNetname", g_OldClientName[target], sizeof(g_OldClientName[])); - } - - Format(bossName, sizeof(bossName), " %s", bossName); - - SetClientName(target, bossName); - SetEntPropString(target, Prop_Data, "m_szNetname", bossName); - - g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); - - char string[64]; - Event event2 = CreateEvent("player_death", true); - event2.SetInt("userid", event.GetInt("userid")); - event2.SetInt("victim_entindex", event.GetInt("victim_entindex")); - event2.SetInt("inflictor_entindex", event.GetInt("inflictor_entindex")); - event2.SetInt("attacker", GetClientUserId(target)); - event2.SetInt("weaponid", event.GetInt("weaponid")); - event2.SetInt("damagebits", event.GetInt("damagebits")); - event2.SetInt("customkill", event.GetInt("customkill")); - event2.SetInt("assister", event.GetInt("assister")); - event2.SetInt("stun_flags", event.GetInt("stun_flags")); - event2.SetInt("death_flags", event.GetInt("death_flags")); - event2.SetBool("silent_kill", event.GetBool("silent_kill")); - event2.SetInt("playerpenetratecount", event.GetInt("playerpenetratecount")); - event2.SetInt("kill_streak_total", event.GetInt("kill_streak_total")); - event2.SetInt("kill_streak_wep", event.GetInt("kill_streak_wep")); - event2.SetInt("kill_streak_assist", event.GetInt("kill_streak_assist")); - event2.SetInt("kill_streak_victim", event.GetInt("kill_streak_victim")); - event2.SetInt("ducks_streaked", event.GetInt("ducks_streaked")); - event2.SetInt("duck_streak_total", event.GetInt("duck_streak_total")); - event2.SetInt("duck_streak_assist", event.GetInt("duck_streak_assist")); - event2.SetInt("duck_streak_victim", event.GetInt("duck_streak_victim")); - event2.SetBool("rocket_jump", event.GetBool("rocket_jump")); - event2.SetInt("weapon_def_index", event.GetInt("weapon_def_index")); - event.GetString("weapon_logclassname", string, sizeof(string)); - event2.SetString("weapon_logclassname", string); - event.GetString("weapon", string, sizeof(string)); - event2.SetString("weapon", string); - event2.SetInt("send", userid); - - CreateTimer(0.2, Timer_SendDeathToSpecific, event2); - } - } } + + int userid = GetClientUserId(target); + event.SetInt("attacker", userid); + g_TimerChangeClientName[target] = CreateTimer(0.6, Timer_RevertClientName, target, TIMER_FLAG_NO_MAPCHANGE); } } + #if defined DEBUG + char stringName[128]; + event.GetString("weapon", stringName, sizeof(stringName)); + SendDebugMessageToPlayers(DEBUG_KILLICONS, 0, "String kill icon is %s, integer kill icon is %i.", stringName, event.GetInt("customkill")); + #endif + if (!IsRoundInWarmup()) { if (client > 0) @@ -1458,6 +919,8 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) return Plugin_Continue; } + int attacker = event.GetInt("attacker"); + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) { @@ -1477,27 +940,10 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) if (!fake) { - ClientResetStatic(client); ClientResetSlenderStats(client); - ClientResetCampingStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); ClientResetInteractiveGlow(client); - ClientChaseMusicReset(client); - ClientChaseMusicSeeReset(client); - ClientAlertMusicReset(client); - ClientIdleMusicReset(client); - Client90sMusicReset(client); - ClientMusicReset(client); - - ClientResetFlashlight(client); - ClientDeactivateUltravision(client); - ClientResetSprint(client); - ClientResetBreathing(client); - ClientResetBlink(client); - ClientResetDeathCam(client); - - ClientUpdateMusicSystem(client); ClientDisableConstantGlow(client); @@ -1528,8 +974,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } } - PvP_SetPlayerPvPState(client, false, false, false); - if (IsRoundInWarmup()) { CreateTimer(0.3, Timer_RespawnPlayer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); @@ -1542,10 +986,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { TF2_SetPlayerClass(client, TFClass_Sniper); } - if (SF_SpecialRound(SPECIALROUND_MULTIEFFECT) || g_RenevantMultiEffect) - { - CreateTimer(0.1, Timer_ReplacePlayerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } if (IsRoundInIntro() || (!IsRoundPlaying() && !IsRoundEnding()) || DidClientEscape(client) || (SF_SpecialRound(SPECIALROUND_1UP) && g_PlayerIn1UpCondition[client] && !g_PlayerDied1Up[client])) { CreateTimer(0.3, Timer_RespawnPlayer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); @@ -1557,60 +997,19 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) g_PlayerFullyDied1Up[client] = true; g_PlayerSwitchBlueTimer[client] = CreateTimer(0.5, Timer_PlayerSwitchToBlue, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - if (g_PlayerPreferences[client].PlayerPreference_GhostModeToggleState == 2) - { - CreateTimer(0.25, Timer_ToggleGhostModeCommand, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !DidClientEscape(client)) - { - for (int reds = 1; reds <= MaxClients; reds++) - { - if (!IsValidClient(reds) || - g_PlayerEliminated[reds] || - DidClientEscape(reds) || - GetClientTeam(reds) != TFTeam_Red || - !IsPlayerAlive(reds)) - { - continue; - } - int randomNegative = GetRandomInt(1, 5); - switch (randomNegative) - { - case 1: - { - TF2_MakeBleed(reds, reds, 4.0); - EmitSoundToClient(reds, BLEED_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(reds, TFCond_Jarated, 5.0); - EmitSoundToClient(reds, JARATE_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(reds, TFCond_Gas, 5.0); - EmitSoundToClient(reds, GAS_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 4: - { - int maxHealth = SDKCall(g_SDKGetMaxHealth, reds); - float damageToTake = float(maxHealth) / 10.0; - SDKHooks_TakeDamage(reds, reds, reds, damageToTake, 128, _, { 0.0, 0.0, 0.0 } ); - } - case 5: - { - TF2_AddCondition(reds, TFCond_MarkedForDeath, 5.0); - } - } - } - } } else { } + int owner = inflictor; + if (IsEntityAProjectile(inflictor)) + { + owner = GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity"); + } + // If this player was killed by a boss, play a sound, or print a message. - int npcIndex = NPCGetFromEntIndex(inflictor); + int npcIndex = NPCGetFromEntIndex(owner); if (npcIndex != -1) { int slender = NPCGetEntIndex(npcIndex); @@ -1662,43 +1061,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) SlenderPerformVoice(npcIndex, _, SF2BossSound_AttackKilled); } - if (IsEntityAProjectile(inflictor)) - { - int npcIndex2 = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); - if (npcIndex2 != -1) - { - int slender = NPCGetEntIndex(npcIndex2); - if (slender && slender != INVALID_ENT_REFERENCE) - { - g_PlayerBossKillSubject[client] = EntIndexToEntRef(slender); - } - - char npcProfile[SF2_MAX_PROFILE_NAME_LENGTH], bossName[SF2_MAX_NAME_LENGTH]; - NPCGetProfile(npcIndex2, npcProfile, sizeof(npcProfile)); - NPCGetBossName(npcIndex2, bossName, sizeof(bossName)); - - #if defined _store_included - int difficulty = GetLocalGlobalDifficulty(npcIndex2); - if (NPCGetDrainCreditState(npcIndex2)) - { - Store_SetClientCredits(client, Store_GetClientCredits(client) - NPCGetDrainCreditAmount(npcIndex2, difficulty)); - CPrintToChat(client, "{valve}%s{default} has stolen {green}%i credits{default} from you.", bossName, NPCGetDrainCreditAmount(npcIndex2, difficulty)); - } - #endif - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileAttackKilledClientSounds(npcProfile, soundInfo); - soundInfo.EmitSound(true, client); - - GetChaserProfileAttackKilledAllSounds(npcProfile, soundInfo); - soundInfo.EmitSound(); - - SlenderPrintChatMessage(npcIndex2, client); - - SlenderPerformVoice(npcIndex2, _, SF2BossSound_AttackKilled); - } - } - CreateTimer(0.2, Timer_CheckRoundWinConditions, _, TIMER_FLAG_NO_MAPCHANGE); // Notify to other bosses that this player has died. @@ -1724,42 +1086,25 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } } - if (g_PlayerProxy[client]) - { - // We're a proxy, so play some sounds. - - int proxyMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); - if (proxyMaster != -1) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(proxyMaster, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetBossProfileProxyDeathSounds(profile, soundInfo); - soundInfo.EmitSound(_, client); - } - } - - ClientResetProxy(client, true); ClientUpdateListeningFlags(client); // Half-Zatoichi nerf code. int katanaHealthGain = 5; if (katanaHealthGain >= 0) { - int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (attacker > 0) + SF2_BasePlayer attackerClient = SF2_BasePlayer(GetClientOfUserId(attacker)); + if (attackerClient.IsValid) { - if (!IsClientInPvP(attacker) && (!g_PlayerEliminated[attacker] || g_PlayerProxy[attacker])) + if (!attackerClient.IsInPvP && (!attackerClient.IsEliminated || attackerClient.IsProxy)) { char weaponClass[64]; event.GetString("weapon", weaponClass, sizeof(weaponClass)); if (strcmp(weaponClass, "demokatana") == 0) { - int attackerPreHealth = GetEntProp(attacker, Prop_Send, "m_iHealth"); + int attackerPreHealth = attackerClient.GetProp(Prop_Send, "m_iHealth"); Handle pack = CreateDataPack(); - WritePackCell(pack, GetClientUserId(attacker)); + WritePackCell(pack, attackerClient.UserID); WritePackCell(pack, attackerPreHealth + katanaHealthGain); CreateTimer(0.0, Timer_SetPlayerHealth, pack, TIMER_FLAG_NO_MAPCHANGE); @@ -1784,8 +1129,8 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } if (!IsRoundEnding() && !g_RoundWaitingForPlayers) { - int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (IsRoundPlaying() && client != attacker) + SF2_BasePlayer attackerClient = SF2_BasePlayer(GetClientOfUserId(attacker)); + if (IsRoundPlaying() && client != attackerClient.index) { //Copy the data char string[64]; @@ -1819,7 +1164,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) event.GetString("weapon", string, sizeof(string)); event2.SetString("weapon", string); - event2.SetInt("ignore", event.GetInt("ignore")); CreateTimer(0.2, Timer_SendDeath, event2, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); } } @@ -1827,7 +1171,13 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { CreateTimer(0.2, Timer_CheckAlivePlayers, _, TIMER_FLAG_NO_MAPCHANGE); } - PvP_OnPlayerDeath(client, fake); + + Call_StartForward(g_OnPlayerDeathPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(attacker); + Call_PushCell(inflictor); + Call_PushCell(fake); + Call_Finish(); #if defined DEBUG if (g_DebugDetailConVar.IntValue > 0) diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 7f2671c3..505d4f2f 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -9,7 +9,7 @@ // GENERAL PLUGIN HOOK FUNCTIONS // ========================================================== -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error,int err_max) +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { RegPluginLibrary("sf2"); @@ -284,23 +284,23 @@ void SDK_Init() BuildPath(Path_SM, strFilePath, sizeof(strFilePath), "gamedata/tf2items.randomizer.txt"); if (FileExists(strFilePath)) { - Handle hGameConf = LoadGameConfigFile("tf2items.randomizer"); - if (hGameConf != null) + GameData gameConf = LoadGameConfigFile("tf2items.randomizer"); + if (gameConf != null) { StartPrepSDKCall(SDKCall_Player); - PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "CTFPlayer::EquipWearable"); + PrepSDKCall_SetFromConf(gameConf, SDKConf_Virtual, "CTFPlayer::EquipWearable"); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer ); g_SDKEquipWearable = EndPrepSDKCall(); if (g_SDKEquipWearable == null) { // Old gamedata StartPrepSDKCall(SDKCall_Player); - PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "EquipWearable"); + PrepSDKCall_SetFromConf(gameConf, SDKConf_Virtual, "EquipWearable"); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); g_SDKEquipWearable = EndPrepSDKCall(); } } - delete hGameConf; + delete gameConf; } } if (g_SDKEquipWearable == null) @@ -397,17 +397,17 @@ void SDK_Init() // API // ========================================================== -static any Native_GetConfig(Handle plugin,int numParams) +static any Native_GetConfig(Handle plugin, int numParams) { return g_Config; } -static any Native_IsRunning(Handle plugin,int numParams) +static any Native_IsRunning(Handle plugin, int numParams) { return g_Enabled; } -static any Native_GetRoundState(Handle plugin,int numParams) +static any Native_GetRoundState(Handle plugin, int numParams) { return g_RoundState; } @@ -417,12 +417,12 @@ static any Native_IsRoundInGracePeriod(Handle plugin, int numParams) return GetRoundState() == SF2RoundState_Grace; } -static any Native_GetCurrentDifficulty(Handle plugin,int numParams) +static any Native_GetCurrentDifficulty(Handle plugin, int numParams) { return g_DifficultyConVar.IntValue; } -static any Native_GetDifficultyModifier(Handle plugin,int numParams) +static any Native_GetDifficultyModifier(Handle plugin, int numParams) { int difficulty = GetNativeCell(1); if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) @@ -458,107 +458,107 @@ static any Native_GetDifficultyModifier(Handle plugin,int numParams) return DIFFICULTYMODIFIER_NORMAL; } -static any Native_IsInSpecialRound(Handle plugin,int numParams) +static any Native_IsInSpecialRound(Handle plugin, int numParams) { return SF_SpecialRound(GetNativeCell(1)); } -static any Native_GetClientGroup(Handle plugin,int numParams) +static any Native_GetClientGroup(Handle plugin, int numParams) { return ClientGetPlayerGroup(GetNativeCell(1)); } -static any Native_GetClientQueuePoints(Handle plugin,int numParams) +static any Native_GetClientQueuePoints(Handle plugin, int numParams) { return g_PlayerQueuePoints[GetNativeCell(1)]; } -static any Native_SetClientQueuePoints(Handle plugin,int numParams) +static any Native_SetClientQueuePoints(Handle plugin, int numParams) { g_PlayerQueuePoints[GetNativeCell(1)] = GetNativeCell(2); return 0; } -static any Native_IsClientEliminated(Handle plugin,int numParams) +static any Native_IsClientEliminated(Handle plugin, int numParams) { return g_PlayerEliminated[GetNativeCell(1)]; } -static any Native_IsClientInGhostMode(Handle plugin,int numParams) +static any Native_IsClientInGhostMode(Handle plugin, int numParams) { return IsClientInGhostMode(GetNativeCell(1)); } -static any Native_IsClientProxy(Handle plugin,int numParams) +static any Native_IsClientProxy(Handle plugin, int numParams) { return g_PlayerProxy[GetNativeCell(1)]; } -static any Native_GetClientBlinkCount(Handle plugin,int numParams) +static any Native_GetClientBlinkCount(Handle plugin, int numParams) { return ClientGetBlinkCount(GetNativeCell(1)); } -static any Native_IsClientBlinking(Handle plugin,int numParams) +static any Native_IsClientBlinking(Handle plugin, int numParams) { return IsClientBlinking(GetNativeCell(1)); } -static any Native_GetClientBlinkMeter(Handle plugin,int numParams) +static any Native_GetClientBlinkMeter(Handle plugin, int numParams) { return ClientGetBlinkMeter(GetNativeCell(1)); } -static any Native_SetClientBlinkMeter(Handle plugin,int numParams) +static any Native_SetClientBlinkMeter(Handle plugin, int numParams) { ClientSetBlinkMeter(GetNativeCell(1), GetNativeCell(2)); return 0; } -static any Native_GetClientProxyMaster(Handle plugin,int numParams) +static any Native_GetClientProxyMaster(Handle plugin, int numParams) { return NPCGetFromUniqueID(g_PlayerProxyMaster[GetNativeCell(1)]); } -static any Native_GetClientProxyControlAmount(Handle plugin,int numParams) +static any Native_GetClientProxyControlAmount(Handle plugin, int numParams) { return g_PlayerProxyControl[GetNativeCell(1)]; } -static any Native_GetClientProxyControlRate(Handle plugin,int numParams) +static any Native_GetClientProxyControlRate(Handle plugin, int numParams) { return g_PlayerProxyControlRate[GetNativeCell(1)]; } -static any Native_SetClientProxyMaster(Handle plugin,int numParams) +static any Native_SetClientProxyMaster(Handle plugin, int numParams) { g_PlayerProxyMaster[GetNativeCell(1)] = NPCGetUniqueID(GetNativeCell(2)); return 0; } -static any Native_SetClientProxyControlAmount(Handle plugin,int numParams) +static any Native_SetClientProxyControlAmount(Handle plugin, int numParams) { g_PlayerProxyControl[GetNativeCell(1)] = GetNativeCell(2); return 0; } -static any Native_SetClientProxyControlRate(Handle plugin,int numParams) +static any Native_SetClientProxyControlRate(Handle plugin, int numParams) { g_PlayerProxyControlRate[GetNativeCell(1)] = GetNativeCell(2); return 0; } -static any Native_IsClientLookingAtBoss(Handle plugin,int numParams) +static any Native_IsClientLookingAtBoss(Handle plugin, int numParams) { return g_PlayerSeesSlender[GetNativeCell(1)][GetNativeCell(2)]; } -static any Native_DidClientEscape(Handle plugin,int numParams) +static any Native_DidClientEscape(Handle plugin, int numParams) { return DidClientEscape(GetNativeCell(1)); } -static any Native_ForceClientEscape(Handle plugin,int numParams) +static any Native_ForceClientEscape(Handle plugin, int numParams) { int client = GetNativeCell(1); @@ -644,49 +644,49 @@ static any Native_ClientForceProxy(Handle plugin, int numParams) return 0; } -static any Native_CollectAsPage(Handle plugin,int numParams) +static any Native_CollectAsPage(Handle plugin, int numParams) { CollectPage(GetNativeCell(1), GetNativeCell(2)); return 0; } -static any Native_GetMaxBosses(Handle plugin,int numParams) +static any Native_GetMaxBosses(Handle plugin, int numParams) { return MAX_BOSSES; } -static any Native_EntIndexToBossIndex(Handle plugin,int numParams) +static any Native_EntIndexToBossIndex(Handle plugin, int numParams) { return NPCGetFromEntIndex(GetNativeCell(1)); } -static any Native_BossIndexToEntIndex(Handle plugin,int numParams) +static any Native_BossIndexToEntIndex(Handle plugin, int numParams) { return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); } -static any Native_BossIndexToEntIndexEx(Handle plugin,int numParams) +static any Native_BossIndexToEntIndexEx(Handle plugin, int numParams) { return NPCGetEntIndex(GetNativeCell(1)); } -static any Native_BossIDToBossIndex(Handle plugin,int numParams) +static any Native_BossIDToBossIndex(Handle plugin, int numParams) { return NPCGetFromUniqueID(GetNativeCell(1)); } -static any Native_BossIndexToBossID(Handle plugin,int numParams) +static any Native_BossIndexToBossID(Handle plugin, int numParams) { return NPCGetUniqueID(GetNativeCell(1)); } -static any Native_UpdateBossAnimation(Handle plugin,int numParams) +static any Native_UpdateBossAnimation(Handle plugin, int numParams) { NPCChaserUpdateBossAnimation(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); return 0; } -static any Native_ForceBossJump(Handle plugin,int numParams) +static any Native_ForceBossJump(Handle plugin, int numParams) { float startPos[3], endPos[3]; GetNativeArray(2, startPos, 3); @@ -737,7 +737,7 @@ static any Native_PerformBossVoice(Handle plugin, int numParams) return 0; } -static any Native_GetBossName(Handle plugin,int numParams) +static any Native_GetBossName(Handle plugin, int numParams) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(GetNativeCell(1), profile, sizeof(profile)); @@ -782,12 +782,12 @@ static any Native_IsBossSpawning(Handle plugin, int numParams) return g_SlenderSpawning[GetNativeCell(1)]; } -static any Native_GetBossModelEntity(Handle plugin,int numParams) +static any Native_GetBossModelEntity(Handle plugin, int numParams) { return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); } -static any Native_GetBossTarget(Handle plugin,int numParams) +static any Native_GetBossTarget(Handle plugin, int numParams) { return EntRefToEntIndex(g_SlenderTarget[GetNativeCell(1)]); } @@ -803,22 +803,22 @@ static any Native_GetBossPathFollower(Handle plugin, int numParams) return g_BossPathFollower[GetNativeCell(1)]; } -static any Native_GetBossMaster(Handle plugin,int numParams) +static any Native_GetBossMaster(Handle plugin, int numParams) { return g_SlenderCopyMaster[GetNativeCell(1)]; } -static any Native_GetBossIdleLifetime(Handle plugin,int numParams) +static any Native_GetBossIdleLifetime(Handle plugin, int numParams) { return NPCGetIdleLifetime(GetNativeCell(1), GetNativeCell(2)); } -static any Native_GetBossState(Handle plugin,int numParams) +static any Native_GetBossState(Handle plugin, int numParams) { return g_SlenderState[GetNativeCell(1)]; } -static any Native_SetBossState(Handle plugin,int numParams) +static any Native_SetBossState(Handle plugin, int numParams) { g_SlenderState[GetNativeCell(1)] = GetNativeCell(2); return 0; @@ -1007,104 +1007,104 @@ static any Native_CreateBossSoundHint(Handle plugin, int numParams) return 0; } -static any Native_GetBossProjectileType(Handle plugin,int numParams) +static any Native_GetBossProjectileType(Handle plugin, int numParams) { return NPCChaserGetProjectileType(GetNativeCell(1)); } -static any Native_SetBossCurrentAttackIndex(Handle plugin,int numParams) +static any Native_SetBossCurrentAttackIndex(Handle plugin, int numParams) { NPCSetCurrentAttackIndex(GetNativeCell(1), GetNativeCell(2)); return 0; } -static any Native_GetBossCurrentAttackIndex(Handle plugin,int numParams) +static any Native_GetBossCurrentAttackIndex(Handle plugin, int numParams) { return NPCGetCurrentAttackIndex(GetNativeCell(1)); } -static any Native_GetBossMaxAttackIndexes(Handle plugin,int numParams) +static any Native_GetBossMaxAttackIndexes(Handle plugin, int numParams) { return NPCChaserGetAttackCount(GetNativeCell(1)); } -static any Native_GetBossAttackRunDurationTime(Handle plugin,int numParams) +static any Native_GetBossAttackRunDurationTime(Handle plugin, int numParams) { return g_NpcBaseAttackRunDurationTime[GetNativeCell(1)][GetNativeCell(2)]; } -static any Native_SetBossAttackRunDurationTime(Handle plugin,int numParams) +static any Native_SetBossAttackRunDurationTime(Handle plugin, int numParams) { g_NpcBaseAttackRunDurationTime[GetNativeCell(1)][GetNativeCell(2)] = GetNativeCell(3); return 0; } -static any Native_GetBossAttackRunDelayTime(Handle plugin,int numParams) +static any Native_GetBossAttackRunDelayTime(Handle plugin, int numParams) { return g_NpcBaseAttackRunDelayTime[GetNativeCell(1)][GetNativeCell(2)]; } -static any Native_SetBossAttackRunDelayTime(Handle plugin,int numParams) +static any Native_SetBossAttackRunDelayTime(Handle plugin, int numParams) { g_NpcBaseAttackRunDelayTime[GetNativeCell(1)][GetNativeCell(2)] = GetNativeCell(3); return 0; } -static any Native_GetBossAttackIndexDamageType(Handle plugin,int numParams) +static any Native_GetBossAttackIndexDamageType(Handle plugin, int numParams) { return NPCChaserGetAttackDamageType(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); } -static any Native_GetBossAttackIndexDamage(Handle plugin,int numParams) +static any Native_GetBossAttackIndexDamage(Handle plugin, int numParams) { return NPCChaserGetAttackDamage(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3)); } -static any Native_GetBossAttackIndexSpread(Handle plugin,int numParams) +static any Native_GetBossAttackIndexSpread(Handle plugin, int numParams) { return NPCChaserGetAttackSpread(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); } -static any Native_GetBossAttackIndexRange(Handle plugin,int numParams) +static any Native_GetBossAttackIndexRange(Handle plugin, int numParams) { return NPCChaserGetAttackRange(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); } -static any Native_GetBossAttackIndexType(Handle plugin,int numParams) +static any Native_GetBossAttackIndexType(Handle plugin, int numParams) { return NPCChaserGetAttackType(GetNativeCell(1), GetNativeCell(2)); } -static any Native_GetBossTeleportThinkTimer(Handle plugin,int numParams) +static any Native_GetBossTeleportThinkTimer(Handle plugin, int numParams) { return g_SlenderThink[GetNativeCell(1)]; } -static any Native_SetBossTeleportThinkTimer(Handle plugin,int numParams) +static any Native_SetBossTeleportThinkTimer(Handle plugin, int numParams) { int bossIndex = GetNativeCell(1); g_SlenderThink[bossIndex] = GetNativeCell(2); return 0; } -static any Native_GetBossTeleportTarget(Handle plugin,int numParams) +static any Native_GetBossTeleportTarget(Handle plugin, int numParams) { return EntRefToEntIndex(g_SlenderTeleportTarget[GetNativeCell(1)]); } -static any Native_GetBossThinkTimer(Handle plugin,int numParams) +static any Native_GetBossThinkTimer(Handle plugin, int numParams) { return g_SlenderEntityThink[GetNativeCell(1)]; } -static any Native_SetBossThinkTimer(Handle plugin,int numParams) +static any Native_SetBossThinkTimer(Handle plugin, int numParams) { int bossIndex = GetNativeCell(1); g_SlenderEntityThink[bossIndex] = GetNativeCell(2); return 0; } -static any Native_UnhookBossThinkHook(Handle plugin,int numParams) +static any Native_UnhookBossThinkHook(Handle plugin, int numParams) { int bossIndex = GetNativeCell(1); int slender = NPCGetEntIndex(bossIndex); diff --git a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp b/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp index 260133d7..7cc2615c 100644 --- a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp +++ b/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp @@ -5,31 +5,41 @@ #pragma semicolon 1 -bool SF_IsRenevantMap() +void SetupRenevantMode() { - return !!(g_IsRenevantMap || (g_RenevantMapConVar.IntValue == 1)); + g_OnRoundEndPFwd.AddFunction(null, OnRoundEnd); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); } -static bool Renevant_TryAddBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], int profileLen, char[] name, int nameLen, bool playSpawnSound=true, bool invincible = false) +static void OnRoundEnd() { - if (!GetRandomRenevantBossProfile(profile, profileLen)) + if (SF_IsRenevantMap() && g_RenevantWaveTimer != null) { - return false; + KillTimer(g_RenevantWaveTimer); } +} - NPCGetBossName(_, name, nameLen, profile); - if (name[0] == '\0') +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) { - strcopy(name, nameLen, profile); + return; } - AddProfile(profile, _, _, _, playSpawnSound, invincible); - return true; + if (g_RenevantMultiEffect) + { + CreateTimer(0.1, Timer_ReplacePlayerRagdoll, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } } -static bool Renevant_TryAddSingleBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], char[] name, int nameLen, bool playSpawnSound=true, bool invincible = false) +bool SF_IsRenevantMap() { - if (!IsProfileValid(profile)) + return !!(g_IsRenevantMap || (g_RenevantMapConVar.IntValue == 1)); +} + +static bool Renevant_TryAddBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], int profileLen, char[] name, int nameLen, bool playSpawnSound = true, bool invincible = false) +{ + if (!GetRandomRenevantBossProfile(profile, profileLen)) { return false; } @@ -44,6 +54,7 @@ static bool Renevant_TryAddSingleBossProfile(char profile[SF2_MAX_PROFILE_NAME_L return true; } + static void ShowRenevantMessageToClient(int client, const char[] message, int params, any ...) { char messageDisplay[512]; @@ -67,9 +78,9 @@ static void Renevant_BroadcastMessage(const char[] message, int params, ...) char format[512]; VFormat(format, sizeof(format), message, params); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsValidClient(i) || !IsClientInGame(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i)) || !IsPlayerAlive(i)) + if (!IsValidClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i)) || !IsPlayerAlive(i)) { continue; } @@ -120,6 +131,7 @@ static void Renevant_DoWaveAction(RenevantWave action) { case RenevantWave_Normal: { + // Do nothing } case RenevantWave_IncreaseDifficulty: { @@ -159,10 +171,7 @@ static void Renevant_DoWaveAction(RenevantWave action) CPrintToChatAll("The difficulty has been set to: {darkgray}%t{default}!", "SF2 Apollyon Difficulty"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); @@ -304,13 +313,13 @@ static void Renevant_DoWaveAction(RenevantWave action) case RenevantWave_MarkForDeath: { g_RenevantMarkForDeath = true; - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (!IsValidClient(client)) { continue; } - if (!IsClientInGame(client) || + if (!IsValidClient(client) || !IsPlayerAlive(client) || g_PlayerEliminated[client] || IsClientInGhostMode(client) || @@ -331,7 +340,7 @@ static void Renevant_DoWaveAction(RenevantWave action) { char bufferSingle[SF2_MAX_PROFILE_NAME_LENGTH], singleBossName[SF2_MAX_NAME_LENGTH]; g_DefaultRenevantBossConVar.GetString(bufferSingle, sizeof(bufferSingle)); - if (Renevant_TryAddSingleBossProfile(bufferSingle, singleBossName, sizeof(singleBossName), _, true)) + if (Renevant_TryAddBossProfile(bufferSingle, sizeof(bufferSingle), singleBossName, sizeof(singleBossName), _, true)) { addedBossCount++; } @@ -494,12 +503,12 @@ static void Renevant_SpawnApollyon() ent = -1; if (spawnPoint.Length > 0) { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); + ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); } delete spawnPoint; - if (ent > MaxClients) + if (IsValidEntity(ent)) { GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) diff --git a/addons/sourcemod/scripting/sf2/mapentities.sp b/addons/sourcemod/scripting/sf2/mapentities.sp index 203f354e..24080771 100644 --- a/addons/sourcemod/scripting/sf2/mapentities.sp +++ b/addons/sourcemod/scripting/sf2/mapentities.sp @@ -7,10 +7,10 @@ //#define DEBUG_MAPENTITIES -static PrivateForward g_CustomEntityOnMapStart; -static PrivateForward g_CustomEntityOnRoundStateChanged; -static PrivateForward g_CustomEntityOnDifficultyChanged; -static PrivateForward g_CustomEntityOnPageCountChanged; +static PrivateForward g_CustomEntityOnMapStartPFwd; +static PrivateForward g_CustomEntityOnRoundStateChangedPFwd; +static PrivateForward g_CustomEntityOnDifficultyChangedPFwd; +static PrivateForward g_CustomEntityOnPageCountChangedPFwd; static PrivateForward g_CustomEntityOnRenevantWaveTriggered; @@ -60,19 +60,19 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) { case SF2MapEntityHook_OnMapStart: { - g_CustomEntityOnMapStart.AddFunction(null, hookFunc); + g_CustomEntityOnMapStartPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnRoundStateChanged: { - g_CustomEntityOnRoundStateChanged.AddFunction(null, hookFunc); + g_CustomEntityOnRoundStateChangedPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnDifficultyChanged: { - g_CustomEntityOnDifficultyChanged.AddFunction(null, hookFunc); + g_CustomEntityOnDifficultyChangedPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnPageCountChanged: { - g_CustomEntityOnPageCountChanged.AddFunction(null, hookFunc); + g_CustomEntityOnPageCountChangedPFwd.AddFunction(null, hookFunc); } case SF2MapEntityHook_OnRenevantWaveTriggered: { @@ -108,13 +108,15 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) void SetupCustomMapEntities() { - g_CustomEntityOnMapStart = new PrivateForward(ET_Ignore); - g_CustomEntityOnRoundStateChanged = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - g_CustomEntityOnDifficultyChanged = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - g_CustomEntityOnPageCountChanged = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_CustomEntityOnMapStartPFwd = new PrivateForward(ET_Ignore); + g_CustomEntityOnRoundStateChangedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_CustomEntityOnDifficultyChangedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_CustomEntityOnPageCountChangedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_CustomEntityOnRenevantWaveTriggered = new PrivateForward(ET_Ignore, Param_Cell); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_DifficultyConVar.AddChangeHook(SF2MapEntity_OnDifficultyChanged); // Initialize static entity data. @@ -176,9 +178,9 @@ void SetupCustomMapEntities() SF2LogicSlaughterEntity.Initialize(); } -void SF2MapEntity_OnMapStart() +static void MapStart() { - Call_StartForward(g_CustomEntityOnMapStart); + Call_StartForward(g_CustomEntityOnMapStartPFwd); Call_Finish(); } @@ -257,7 +259,7 @@ int SF2MapEntity_FindEntityByTargetname(int startEnt, const char[] name, int sea void SF2MapEntity_OnRoundStateChanged(SF2RoundState roundState, SF2RoundState oldRoundState) { - Call_StartForward(g_CustomEntityOnRoundStateChanged); + Call_StartForward(g_CustomEntityOnRoundStateChangedPFwd); Call_PushCell(roundState); Call_PushCell(oldRoundState); Call_Finish(); @@ -268,7 +270,7 @@ static void SF2MapEntity_OnDifficultyChanged(ConVar cvar, const char[] oldValue, int oldDifficulty = StringToInt(oldValue); int difficulty = StringToInt(newValue); - Call_StartForward(g_CustomEntityOnDifficultyChanged); + Call_StartForward(g_CustomEntityOnDifficultyChangedPFwd); Call_PushCell(difficulty); Call_PushCell(oldDifficulty); Call_Finish(); @@ -276,7 +278,7 @@ static void SF2MapEntity_OnDifficultyChanged(ConVar cvar, const char[] oldValue, void SF2MapEntity_OnPageCountChanged(int pageCount, int oldPageCount) { - Call_StartForward(g_CustomEntityOnPageCountChanged); + Call_StartForward(g_CustomEntityOnPageCountChangedPFwd); Call_PushCell(pageCount); Call_PushCell(oldPageCount); Call_Finish(); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp index 105fd748..fad86cb2 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp @@ -193,7 +193,7 @@ static void InputDisplay(int entity, int activator, int caller, const char[] val // If a targetname is specified, try to show text to the matched entity. int target = -1; - while ((target = SF2MapEntity_FindEntityByTargetname(target, value, caller, activator, caller)) != -1 && target > 0 && target <= MaxClients) + while ((target = SF2MapEntity_FindEntityByTargetname(target, value, caller, activator, caller)) != -1 && IsValidClient(target)) { clients[clientsNum++] = target; } @@ -202,9 +202,9 @@ static void InputDisplay(int entity, int activator, int caller, const char[] val { int spawnFlags = thisEnt.GetProp(Prop_Data, "m_spawnflags"); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 9816f6d0..5fce440d 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -270,8 +270,8 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Version: "); StrCat(buffer, sizeof(buffer), PLUGIN_VERSION); StrCat(buffer, sizeof(buffer), "\n \n"); - Format(buffer, sizeof(buffer), "%s%t\n",buffer,"SF2 Recent Changes"); - Format(buffer, sizeof(buffer), "%s%t\n",buffer,"SF2 Change Log"); + Format(buffer, sizeof(buffer), "%s%t\n", buffer, "SF2 Recent Changes"); + Format(buffer, sizeof(buffer), "%s%t\n", buffer, "SF2 Change Log"); StrCat(buffer, sizeof(buffer), "\n \n"); SetMenuTitle(g_MenuUpdate, buffer); @@ -302,7 +302,7 @@ void RandomizeVoteMenu() bool apollyon = StrContains(buffer, "5") != -1; bool random = StrContains(buffer, "6") != -1; - switch (GetRandomInt(1,6))//There's probably a better way to do this but I was tired. + switch (GetRandomInt(1, 6))//There's probably a better way to do this but I was tired. { case 1: { @@ -413,11 +413,13 @@ void RandomizeVoteMenu() } } } + if (nightmare) { FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); AddMenuItem(g_MenuVoteDifficulty, "4", buffer); } + if(apollyon) { FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); @@ -431,7 +433,7 @@ void RandomizeVoteMenu() } } -static int Menu_Main(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Main(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -478,14 +480,14 @@ static int Menu_Main(Handle menu, MenuAction action,int param1,int param2) return 0; } -static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int param2) +static int Menu_VoteDifficulty(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_VoteEnd && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && !g_RestartSessionConVar.BoolValue) { int clientInGame = 0, clientCallingForNightmare = 0; - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { - if (IsClientInGame(client) && !g_PlayerEliminated[client]) + if (IsValidClient(client) && !g_PlayerEliminated[client]) { clientInGame++; if (g_PlayerCalledForNightmare[client]) @@ -648,10 +650,7 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par FormatEx(display, sizeof(display), "%t!", "SF2 Nightmare Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); strcopy(color, sizeof(color), "{valve}"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); } case Difficulty_Apollyon: @@ -659,10 +658,7 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par FormatEx(display, sizeof(display), "%t!", "SF2 Apollyon Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); strcopy(color, sizeof(color), "{darkgray}"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); if (rng) { @@ -724,7 +720,7 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action,int param1,int par return 0; } -static int Menu_Help(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Help(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -766,7 +762,7 @@ static int Menu_Help(Handle menu, MenuAction action,int param1,int param2) return 0; } -static int Menu_HelpObjective(Handle menu, MenuAction action,int param1,int param2) +static int Menu_HelpObjective(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -785,7 +781,7 @@ static int Menu_HelpObjective(Handle menu, MenuAction action,int param1,int para return 0; } -static int Menu_HelpObjective2(Handle menu, MenuAction action,int param1,int param2) +static int Menu_HelpObjective2(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -800,7 +796,7 @@ static int Menu_HelpObjective2(Handle menu, MenuAction action,int param1,int par return 0; } -static int Menu_BackButtonOnly(Handle menu, MenuAction action,int param1,int param2) +static int Menu_BackButtonOnly(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -815,7 +811,7 @@ static int Menu_BackButtonOnly(Handle menu, MenuAction action,int param1,int par return 0; } -static int Menu_Credits(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -834,7 +830,7 @@ static int Menu_Credits(Handle menu, MenuAction action,int param1,int param2) return 0; } -static int Menu_Credits1(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits1(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -853,7 +849,7 @@ static int Menu_Credits1(Handle menu, MenuAction action,int param1,int param2) return 0; } -static int Menu_ClassInfo(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ClassInfo(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Cancel) { @@ -880,7 +876,7 @@ static int Menu_ClassInfo(Handle menu, MenuAction action,int param1,int param2) return 0; } -static int Menu_ClassInfoBackOnly(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ClassInfoBackOnly(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -893,7 +889,7 @@ static int Menu_ClassInfoBackOnly(Handle menu, MenuAction action,int param1,int return 0; } -static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Settings(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1043,7 +1039,7 @@ static int Menu_Settings(Handle menu, MenuAction action,int param1,int param2) return 0; } -static int Menu_Settings_Flashlighttemp1(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Settings_Flashlighttemp1(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1111,7 +1107,7 @@ static int Menu_Settings_Flashlighttemp1(Handle menu, MenuAction action,int para return 0; } -static int Panel_SettingsFilmGrain(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsFilmGrain(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1136,7 +1132,7 @@ static int Panel_SettingsFilmGrain(Handle menu, MenuAction action,int param1,int return 0; } -static int Panel_SettingsHints(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsHints(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1161,7 +1157,7 @@ static int Panel_SettingsHints(Handle menu, MenuAction action,int param1,int par return 0; } -static int Panel_SettingsProxy(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsProxy(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1186,7 +1182,7 @@ static int Panel_SettingsProxy(Handle menu, MenuAction action,int param1,int par return 0; } -static int Panel_SettingsProxyAskMenu(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsProxyAskMenu(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1211,7 +1207,7 @@ static int Panel_SettingsProxyAskMenu(Handle menu, MenuAction action,int param1, return 0; } -static int Panel_SettingsMuteMode(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsMuteMode(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1245,7 +1241,7 @@ static int Panel_SettingsMuteMode(Handle menu, MenuAction action,int param1,int return 0; } -static int Panel_SettingsGhostModeTeleport(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsGhostModeTeleport(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1272,7 +1268,7 @@ static int Panel_SettingsGhostModeTeleport(Handle menu, MenuAction action,int pa return 0; } -static int Panel_SettingsGhostModeToggleState(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsGhostModeToggleState(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1306,7 +1302,7 @@ static int Panel_SettingsGhostModeToggleState(Handle menu, MenuAction action,int return 0; } -int Panel_SettingsHudVersion(Handle menu, MenuAction action,int param1,int param2) +int Panel_SettingsHudVersion(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1331,7 +1327,7 @@ int Panel_SettingsHudVersion(Handle menu, MenuAction action,int param1,int param return 0; } -int Panel_SettingsViewBobbing(Handle menu, MenuAction action,int param1,int param2) +int Panel_SettingsViewBobbing(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1356,7 +1352,7 @@ int Panel_SettingsViewBobbing(Handle menu, MenuAction action,int param1,int para return 0; } -static int Menu_Credits2(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits2(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1374,7 +1370,7 @@ static int Menu_Credits2(Handle menu, MenuAction action,int param1,int param2) } return 0; } -static int Menu_Credits3(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits3(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1392,7 +1388,7 @@ static int Menu_Credits3(Handle menu, MenuAction action,int param1,int param2) } return 0; } -static int Menu_Credits4(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits4(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1410,7 +1406,7 @@ static int Menu_Credits4(Handle menu, MenuAction action,int param1,int param2) } return 0; } -static int Menu_Credits5(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Credits5(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1424,7 +1420,7 @@ static int Menu_Credits5(Handle menu, MenuAction action,int param1,int param2) } return 0; } -static int Menu_Update(Handle menu, MenuAction action,int param1,int param2) +static int Menu_Update(Handle menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1474,7 +1470,7 @@ void DisplayQueuePointsMenu(int client) } else { - for (int i2 = 1; i2 <= MaxClients; i2++) + for (int i2 = 1; i2 < MaxClients; i2++) { if (!IsValidClient(i2)) { @@ -1514,7 +1510,7 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersArray in DisplayViewGroupMembersQueueMenu.", playersArray); #endif - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -1570,7 +1566,7 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) #endif } -static int Menu_ViewGroupMembersQueue(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ViewGroupMembersQueue(Handle menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1606,7 +1602,7 @@ void DisplayResetQueuePointsMenu(int client) DisplayMenu(menu, client, MENU_TIME_FOREVER); } -static int Menu_QueuePoints(Handle menu, MenuAction action,int param1,int param2) +static int Menu_QueuePoints(Handle menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1645,7 +1641,7 @@ static int Menu_QueuePoints(Handle menu, MenuAction action,int param1,int param2 return 0; } -static int Menu_ResetQueuePoints(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ResetQueuePoints(Handle menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1710,7 +1706,7 @@ void DisplayBossList(int client) DisplayMenu(menu, client, MENU_TIME_FOREVER); } -static int Menu_BossList(Handle menu, MenuAction action,int param1,int param2) +static int Menu_BossList(Handle menu, MenuAction action, int param1, int param2) { switch (action) { diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index a48e3406..e7069df7 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -29,6 +29,14 @@ methodmap SF2NPC_BaseNPC } } + property PathFollower Path + { + public get() + { + return g_BossPathFollower[this.Index]; + } + } + property int ProfileIndex { public get() @@ -53,6 +61,11 @@ methodmap SF2NPC_BaseNPC } } + public static SF2NPC_BaseNPC FromUniqueId(int iUniqueId) + { + return SF2NPC_BaseNPC(NPCGetFromUniqueID(iUniqueId)); + } + property int EntRef { public get() @@ -97,6 +110,11 @@ methodmap SF2NPC_BaseNPC } } + public bool GetModel(int modelState = 0, char[] buffer, int bufferLen) + { + return GetSlenderModel(this.Index, modelState, buffer, bufferLen); + } + property int RaidHitbox { public get() @@ -145,6 +163,11 @@ methodmap SF2NPC_BaseNPC } } + public float GetInstantKillCooldown(int difficulty) + { + return NPCGetInstantKillCooldown(this.Index, difficulty); + } + property int TeleportType { public get() @@ -411,7 +434,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { public get() { - return IsClientInGame(this.index); + return IsClientInGameEx(this.index); } } @@ -542,6 +565,16 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public bool IsCritBoosted() + { + return IsClientCritBoosted(this.index); + } + + public bool IsMiniCritBoosted() + { + return TF2_IsMiniCritBuffed(this.index); + } + public void Ignite(bool self = false, int attacker = 0, float duration = 10.0) { TF2_IgnitePlayer(this.index, !self && attacker > 0 ? attacker : this.index, duration); @@ -552,7 +585,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter TF2_MakeBleed(this.index, !self && attacker > 0 ? attacker : this.index, duration); } - public void Stun(float duration, float slowdown, int stunflags, int attacker = 0) + public void Stun(float duration, float slowdown = 0.0, int stunflags, int attacker = 0) { TF2_StunPlayer(this.index, duration, slowdown, stunflags, attacker); } @@ -597,6 +630,16 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter return GetPlayerWeaponSlot(this.index, slot); } + public void SwitchToWeaponSlot(int slot) + { + ClientSwitchToWeaponSlot(this.index, slot); + } + + public void RemoveWeaponSlot(int slot) + { + TF2_RemoveWeaponSlot(this.index, slot); + } + public void ScreenShake(float amp, float duration, float freq) { UTIL_ClientScreenShake(this.index, amp, duration, freq); @@ -634,6 +677,26 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter ClientUpdateListeningFlags(this.index, false); } + property bool IsParticipating + { + public get() + { + return IsClientParticipating(this.index); + } + } + + property int LastButtons + { + public get() + { + return g_PlayerLastButtons[this.index]; + } + public set(int value) + { + g_PlayerLastButtons[this.index] = value; + } + } + property bool IsEliminated { public get() @@ -719,9 +782,9 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } - public void StartDeathCam(int bossIndex, const float vecLookPos[3], bool antiCamp = false) + public void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false) { - ClientStartDeathCam(this.index, bossIndex, vecLookPos, antiCamp); + ClientStartDeathCam(this.index, bossIndex, lookPos, antiCamp); } property bool HasEscaped @@ -772,6 +835,11 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter ClientResetFlashlight(this.index); } + public float GetFlashlightNextInputTime() + { + return ClientGetFlashlightNextInputTime(this.index); + } + property bool IsSprinting { public get() @@ -788,6 +856,26 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public void HandleSprint(bool sprint) + { + ClientHandleSprint(this.index, sprint); + } + + public void SetSprintTimer(bool recharge = false) + { + ClientSprintTimer(this.index, recharge); + } + + public int GetSprintPoints() + { + return ClientGetSprintPoints(this.index); + } + + public void SetSprintPoints(int value) + { + ClientSetSprintPoints(this.index, value); + } + property bool IsBlinking { public get() @@ -796,6 +884,16 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public bool IsHoldingBlink() + { + return IsClientHoldingBlink(this.index); + } + + public void SetHoldingBlink(bool value) + { + ClientSetHoldingBlink(this.index, value); + } + property float BlinkMeter { public get() @@ -816,9 +914,9 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } - public void ResetBlink() + public void Blink() { - ClientResetBlink(this.index); + ClientBlink(this.index); } public bool StartPeeking() @@ -826,6 +924,11 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter return ClientStartPeeking(this.index); } + public void EndPeeking() + { + ClientEndPeeking(this.index); + } + property int PageCount { public get() @@ -838,11 +941,6 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } - public void ResetHints() - { - ClientResetHints(this.index); - } - public void ShowHint(int hint) { ClientShowHint(this.index, hint); @@ -894,6 +992,21 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return PlayerCanSeeSlender(this.index, bossIndex, checkFOV, checkBlink, checkEliminated); } + + public void SetAFKTime(bool reset = true) + { + AFK_SetTime(this.index); + } + + public void SetAFKState() + { + AFK_SetAFK(this.index); + } + + public void CheckAFKTime() + { + AFK_CheckTime(this.index); + } } methodmap SF2NPC_Chaser < SF2NPC_BaseNPC @@ -1340,4 +1453,24 @@ methodmap SF2NPC_Statue < SF2NPC_BaseNPC { return view_as(SF2NPC_BaseNPC(index)); } + + public float GetIdleLifetime(int difficulty) + { + return NPCStatueGetIdleLifetime(this.Index, difficulty); + } + + public float GetChaseDuration(int difficulty) + { + return NPCStatueChaseDuration(this.Index, difficulty); + } + + public float AddChaseDurationMin(int difficulty) + { + return NPCStatueGetChaseDurationAddMin(this.Index, difficulty); + } + + public float AddChaseDurationMax(int difficulty) + { + return NPCStatueGetChaseDurationAddMax(this.Index, difficulty); + } } diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 1bdc9b61..32b7670a 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -346,6 +346,7 @@ void NPCStopMusic() } } } + void CheckIfMusicValid() { int difficulty = g_DifficultyConVar.IntValue; @@ -524,7 +525,7 @@ void CheckIfMusicValid() } if (currentMusicTrack[0] != '\0') { - timerMusic = CreateTimer(NPCGetSoundMusicLoop(i, difficulty),BossMusic,i,TIMER_FLAG_NO_MAPCHANGE); + timerMusic = CreateTimer(NPCGetSoundMusicLoop(i, difficulty), BossMusic, i, TIMER_FLAG_NO_MAPCHANGE); StopSound(client, MUSIC_CHAN, currentMusicTrack); ClientChaseMusicReset(client); ClientChaseMusicSeeReset(client); @@ -937,7 +938,7 @@ static Action BossMusic(Handle timer,any bossIndex) StopSound(i, MUSIC_CHAN, currentMusicTrack); } } - timerMusic = CreateTimer(time,BossMusic,bossIndex,TIMER_FLAG_NO_MAPCHANGE); + timerMusic = CreateTimer(time, BossMusic, bossIndex, TIMER_FLAG_NO_MAPCHANGE); return Plugin_Continue; } NPCStopMusic(); @@ -1440,7 +1441,7 @@ bool NPCShouldHearEntity(int npcIndex, int entity, SoundType soundType) bool NPCAreAvailablePlayersAlive() { int number = 0; - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (IsValidClient(i) && IsPlayerAlive(i) && GetClientTeam(i) == TFTeam_Red && !g_PlayerEliminated[i] && !DidClientEscape(i)) { @@ -1545,9 +1546,9 @@ bool SlenderCanRemove(int bossIndex) float slenderPos[3], buffer[3]; SlenderGetAbsOrigin(bossIndex, slenderPos); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i) || + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i) || @@ -1918,7 +1919,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl g_SlenderCustomOutroSong[Npc.Index] = GetBossProfileOutroMusicState(profile); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { g_PlayerLastChaseBossEncounterTime[i][Npc.Index] = -1.0; g_SlenderTeleportPlayersRestTime[Npc.Index][i] = -1.0; @@ -1935,7 +1936,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl GetBossProfileEngineSound(profile, g_SlenderEngineSound[Npc.Index], sizeof(g_SlenderEngineSound[])); - g_SlenderThink[Npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, Npc, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + g_SlenderThink[Npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, Npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); switch (bossType) { @@ -1960,7 +1961,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl { SF2BossProfileSoundInfo soundInfo; GetBossProfileIntroSounds(profile, soundInfo); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -2124,7 +2125,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl if ((g_NpcAllowMusicOnDifficulty[Npc.Index] & g_DifficultyConVar.IntValue) && time > 0.0) { timerMusic = CreateTimer(time, BossMusic, Npc.Index, TIMER_FLAG_NO_MAPCHANGE); - for(int client = 1; client <= MaxClients; client++) + for(int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && (!g_PlayerEliminated[client] || IsClientInGhostMode(client))) { @@ -2522,49 +2523,17 @@ void RemoveProfile(int bossIndex) Call_PushCell(bossIndex); Call_Finish(); + Call_StartForward(g_OnBossRemovedPFwd); + Call_PushCell(bossIndex); + Call_Finish(); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); NPCChaserOnRemoveProfile(bossIndex); - // Remove all possible sounds, for emergencies. - if (!MusicActive()) - { - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i)) - { - continue; - } - - // Remove chase music. - if (g_PlayerChaseMusicMaster[i] == bossIndex) - { - ClientChaseMusicReset(i); - } - - // Don't forget search theme - if (g_PlayerAlertMusicMaster[i] == bossIndex) - { - ClientAlertMusicReset(i); - } - - if (g_PlayerChaseMusicSeeMaster[i] == bossIndex) - { - ClientChaseMusicSeeReset(i); - } - - if (g_PlayerIdleMusicMaster[i] == bossIndex) - { - ClientIdleMusicReset(i); - } - - ClientUpdateMusicSystem(i); - } - } - // Clean up on the clients. - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { g_SlenderLastFoundPlayer[bossIndex][i] = -1.0; g_PlayerLastChaseBossEncounterTime[i][bossIndex] = -1.0; @@ -2574,22 +2543,6 @@ void RemoveProfile(int bossIndex) { g_SlenderLastFoundPlayerPos[bossIndex][i][i2] = 0.0; } - - if (IsClientInGame(i)) - { - if (NPCGetUniqueID(bossIndex) == g_PlayerStaticMaster[i]) - { - g_PlayerStaticMaster[i] = -1; - - // No one is the static master. - g_PlayerStaticTimer[i] = CreateTimer(g_PlayerStaticDecreaseRate[i], - Timer_ClientDecreaseStatic, - GetClientUserId(i), - TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - - TriggerTimer(g_PlayerStaticTimer[i], true); - } - } } g_NpcTeleportType[bossIndex] = -1; @@ -2746,123 +2699,51 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) AddVectors(truePos, pos, truePos); int bossIndex = Npc.Index; - int difficulty = GetLocalGlobalDifficulty(bossIndex); char buffer[PLATFORM_MAX_PATH]; GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); - CBaseNPC npcBoss = CBaseNPC(); - CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npcBoss.GetEntity()); - CBaseNPC_Locomotion locomotion = npcBoss.GetLocomotion(); - npcEntity.Hook_HandleAnimEvent(CBaseAnimating_HandleAnimEvent); - - npcEntity.Teleport(truePos, trueAng); - npcEntity.SetModel(buffer); - npcEntity.SetRenderMode(view_as(g_SlenderRenderMode[bossIndex])); - npcEntity.SetRenderFx(view_as(g_SlenderRenderFX[bossIndex])); - npcEntity.SetRenderColor(g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], g_SlenderRenderColor[bossIndex][3]); - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - float scaleModel = NPCGetModelScale(bossIndex) * 0.5; - npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); - } - else - { - npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", NPCGetModelScale(bossIndex)); - } - npcEntity.Spawn(); - npcEntity.Activate(); - - npcBoss.flStepSize = 18.0; - npcBoss.flGravity = g_Gravity; - npcBoss.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; - npcBoss.flDeathDropHeight = 99999.0; - npcBoss.flJumpHeight = 512.0; - npcBoss.flWalkSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; - npcBoss.flRunSpeed = g_SlenderCalculatedSpeed[bossIndex]; - - g_BossPathFollower[bossIndex].SetMinLookAheadDistance(GetBossProfileNodeDistanceLookAhead(profile)); - - if (NPCGetModelSkinMax(bossIndex) > 0) - { - int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(bossIndex)); - npcEntity.SetProp(Prop_Send, "m_nSkin", randomSkin); - } - else - { - if (GetBossProfileSkinDifficultyState(profile)) - { - npcEntity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkinDifficulty(bossIndex, difficulty)); - } - else - { - npcEntity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkin(bossIndex)); - } - } - if (NPCGetModelBodyGroupsMax(bossIndex) > 0) - { - int randomBody = GetRandomInt(0, NPCGetModelBodyGroupsMax(bossIndex)); - npcEntity.SetProp(Prop_Send, "m_nBody", randomBody); - } - else - { - if (GetBossProfileBodyGroupsDifficultyState(profile)) - { - npcEntity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroupsDifficulty(bossIndex, difficulty)); - } - else - { - npcEntity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroups(bossIndex)); - } - } - - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, npcEntity.iEnt, Hook_BossUpdateTransmitState); - SetEntityFlags(npcEntity.iEnt, FL_NPC); - SetEntityTransmitState(npcEntity.iEnt, FL_EDICT_ALWAYS); - - g_SlenderEnt[bossIndex] = EntIndexToEntRef(npcEntity.iEnt); - - if (g_SlenderEngineSound[bossIndex][0] != '\0') - { - EmitSoundToAll(g_SlenderEngineSound[bossIndex], npcEntity.iEnt, SNDCHAN_STATIC, GetBossProfileEngineSoundLevel(profile), - _, GetBossProfileEngineSoundVolume(profile)); - } - - if (GetBossProfileSpawnParticleState(profile)) - { - SlenderCreateParticleSpawnEffect(bossIndex); - } + CBaseCombatCharacter entity; switch (NPCGetType(bossIndex)) { case SF2BossType_Statue: { - npcBoss.flMaxYawRate = 0.0; - - locomotion.SetCallback(LocomotionCallback_IsAbleToJumpAcrossGaps, CanJumpAcrossGaps); - locomotion.SetCallback(LocomotionCallback_IsAbleToClimb, CanJumpAcrossGaps); - locomotion.SetCallback(LocomotionCallback_JumpAcrossGap, JumpAcrossGapsCBase); - locomotion.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); - - npcEntity.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); - npcEntity.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); - - npcEntity.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); - npcEntity.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); - - npcEntity.SetProp(Prop_Data, "m_nSolidType", SOLID_BBOX); - npcEntity.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); - - SDKHook(npcEntity.iEnt, SDKHook_ThinkPost, SlenderStatueSetNextThink); - SDKHook(npcEntity.iEnt, SDKHook_Think, SlenderStatueBossProcessMovement); - - g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderBlinkBossThink, g_SlenderEnt[bossIndex], TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - - Spawn_Statue(bossIndex); + entity = Spawn_Statue(bossIndex); } case SF2BossType_Chaser: { + CBaseNPC npcBoss = CBaseNPC(); + CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npcBoss.GetEntity()); + CBaseNPC_Locomotion locomotion = npcBoss.GetLocomotion(); + npcEntity.Hook_HandleAnimEvent(CBaseAnimating_HandleAnimEvent); + + npcEntity.Teleport(truePos, trueAng); + npcEntity.SetModel(buffer); + npcEntity.SetRenderMode(view_as(g_SlenderRenderMode[bossIndex])); + npcEntity.SetRenderFx(view_as(g_SlenderRenderFX[bossIndex])); + npcEntity.SetRenderColor(g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], g_SlenderRenderColor[bossIndex][3]); + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + float scaleModel = NPCGetModelScale(bossIndex) * 0.5; + npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); + } + else + { + npcEntity.SetPropFloat(Prop_Send, "m_flModelScale", NPCGetModelScale(bossIndex)); + } + npcEntity.Spawn(); + npcEntity.Activate(); + + npcBoss.flStepSize = 18.0; + npcBoss.flGravity = g_Gravity; + npcBoss.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; + npcBoss.flDeathDropHeight = 99999.0; + npcBoss.flJumpHeight = 512.0; + npcBoss.flWalkSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; + npcBoss.flRunSpeed = g_SlenderCalculatedSpeed[bossIndex]; + npcBoss.flMaxYawRate = NPCGetTurnRate(bossIndex); locomotion.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); @@ -3005,7 +2886,7 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) g_SlenderChaseDeathPosition[bossIndex][i] = 0.0; } - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { g_SlenderLastFoundPlayer[bossIndex][i] = -1.0; @@ -3057,9 +2938,65 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) int entHealth = RoundToCeil(maxHealth + 1500000000.0); npcEntity.SetProp(Prop_Data, "m_iHealth", entHealth); npcEntity.SetProp(Prop_Data, "m_iMaxHealth", entHealth); + + entity = npcEntity; } } + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + g_BossPathFollower[bossIndex].SetMinLookAheadDistance(GetBossProfileNodeDistanceLookAhead(profile)); + + if (NPCGetModelSkinMax(bossIndex) > 0) + { + int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(bossIndex)); + entity.SetProp(Prop_Send, "m_nSkin", randomSkin); + } + else + { + if (GetBossProfileSkinDifficultyState(profile)) + { + entity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkinDifficulty(bossIndex, difficulty)); + } + else + { + entity.SetProp(Prop_Send, "m_nSkin", NPCGetModelSkin(bossIndex)); + } + } + if (NPCGetModelBodyGroupsMax(bossIndex) > 0) + { + int randomBody = GetRandomInt(0, NPCGetModelBodyGroupsMax(bossIndex)); + entity.SetProp(Prop_Send, "m_nBody", randomBody); + } + else + { + if (GetBossProfileBodyGroupsDifficultyState(profile)) + { + entity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroupsDifficulty(bossIndex, difficulty)); + } + else + { + entity.SetProp(Prop_Send, "m_nBody", NPCGetModelBodyGroups(bossIndex)); + } + } + + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity.iEnt, Hook_BossUpdateTransmitState); + SetEntityFlags(entity.iEnt, FL_NPC); + SetEntityTransmitState(entity.iEnt, FL_EDICT_ALWAYS); + + g_SlenderEnt[bossIndex] = EntIndexToEntRef(entity.iEnt); + + if (g_SlenderEngineSound[bossIndex][0] != '\0') + { + EmitSoundToAll(g_SlenderEngineSound[bossIndex], entity.iEnt, SNDCHAN_STATIC, GetBossProfileEngineSoundLevel(profile), + _, GetBossProfileEngineSoundVolume(profile)); + } + + if (GetBossProfileSpawnParticleState(profile)) + { + SlenderCreateParticleSpawnEffect(bossIndex); + } + if (NPCGetCustomOutlinesState(bossIndex)) { if (!NPCGetRainbowOutlineState(bossIndex)) @@ -3172,22 +3109,22 @@ static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) return loco.CallBaseFunction(other); } -static bool CanJumpAcrossGaps(CBaseNPC_Locomotion loco) +bool CanJumpAcrossGaps(CBaseNPC_Locomotion loco) { return false; } -static void JumpAcrossGapsCBase(CBaseNPC_Locomotion loco, const float landingGoal[3], const float landingForward[3]) +void JumpAcrossGapsCBase(CBaseNPC_Locomotion loco, const float landingGoal[3], const float landingForward[3]) { return; } -static bool ClimbUpCBase(CBaseNPC_Locomotion loco, const float vecGoal[3], const float vecForward[3], int entity) +bool ClimbUpCBase(CBaseNPC_Locomotion loco, const float goal[3], const float fwd[3], int entity) { return false; } -static MRESReturn CBaseAnimating_HandleAnimEvent(int thisInt, DHookParam params) +MRESReturn CBaseAnimating_HandleAnimEvent(int thisInt, DHookParam params) { int bossIndex = NPCGetFromEntIndex(thisInt); if (bossIndex == -1) @@ -3241,7 +3178,7 @@ void RemoveSlender(int bossIndex) } int glowEnt = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - if (glowEnt != INVALID_ENT_REFERENCE) + if (glowEnt && glowEnt != INVALID_ENT_REFERENCE) { RemoveEntity(glowEnt); g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; @@ -4207,12 +4144,15 @@ void SlenderCreateParticleSpawnEffect(int bossIndex, bool despawn = false) // Teleport progressively closer as time passes in attempt to increase the target's stress level. // Maximum minimum range is capped by the boss's anger level. -static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) +static Action Timer_SlenderTeleportThink(Handle timer, any id) { - if (bossIndex == -1) + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC.FromUniqueId(id); + if (!controller.IsValid()) { return Plugin_Stop; } + + int bossIndex = controller.Index; if (timer != g_SlenderThink[bossIndex]) { return Plugin_Stop; @@ -4228,7 +4168,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) return Plugin_Continue; } - if (NPCGetFlags(bossIndex) & SFF_NOTELEPORT) + if (controller.Flags & SFF_NOTELEPORT) { return Plugin_Continue; } @@ -4245,9 +4185,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) { return Plugin_Continue; } - if (NPCGetTeleportType(bossIndex) == 2) + if (controller.TeleportType == 2) { - int bossEnt = NPCGetEntIndex(bossIndex); + int bossEnt = controller.EntIndex; if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) { if (NPCGetType(bossIndex) == SF2BossType_Chaser) @@ -4379,7 +4319,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) else { canSpawn = true; - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i) || !IsPlayerAlive(i) || IsClientInGhostMode(i) || g_PlayerEliminated[i] || g_PlayerProxy[i] || DidClientEscape(i)) @@ -4474,7 +4414,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) if (canSpawn) { - SpawnSlender(bossIndex, spawnPos); + SpawnSlender(controller, spawnPos); if (g_PlayerIsExitCamping[teleportTarget]) { @@ -4489,9 +4429,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any bossIndex) { bool didJumpScare = false; - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) { continue; } @@ -4779,7 +4719,7 @@ void SlenderRemoveGlow(int bossIndex) } int glow = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - if (glow != INVALID_ENT_REFERENCE) + if (glow && glow != INVALID_ENT_REFERENCE) { RemoveEntity(glow); g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; @@ -4806,7 +4746,7 @@ bool IsNPCVisibleToPlayer(int npcIndex,int client, bool checkFOV=true, bool chec bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) { - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsNPCVisibleToPlayer(npcIndex, client, checkFOV, checkBlink, checkEliminated)) { @@ -4850,8 +4790,13 @@ bool CanNPCSeePlayerNonTransparent(int npcIndex, SF2_BasePlayer client) npcEnt); bool isVisible = !TR_DidHit(trace); + if (!isVisible && TR_GetEntityIndex(trace) == client.index) + { + isVisible = true; + } delete trace; + return isVisible; } return false; @@ -4871,7 +4816,7 @@ float NPCGetDistanceFromPoint(int npcIndex, const float point[3]) return -1.0; } -float NPCGetDistanceFromEntity(int npcIndex,int ent) +float NPCGetDistanceFromEntity(int npcIndex, int ent) { if (!IsValidEntity(ent)) { @@ -4884,118 +4829,74 @@ float NPCGetDistanceFromEntity(int npcIndex,int ent) return NPCGetDistanceFromPoint(npcIndex, pos); } -bool TraceRayBossVisibility(int entity,int mask, any data) +bool TraceRayBossVisibility(int entity, int mask, any data) { - if (entity == data || IsValidClient(entity)) + if (entity == data) { return false; } - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { if (g_PlayerProxy[entity] || IsClientInGhostMode(entity)) { return false; } } - int bossIndex = NPCGetFromEntIndex(entity); - if (entity <= MAX_BOSSES) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (bossIndex != -1) - { - return false; - } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } -bool TraceRayDontHitCharacters(int entity,int mask, any data) +bool TraceRayDontHitCharacters(int entity, int mask, any data) { - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { return false; } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } -bool TraceRayDontHitAnyEntity(int entity,int mask,any data) +bool TraceRayDontHitAnyEntity(int entity, int mask, any data) { if (entity == data) { return false; } - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { if (g_PlayerProxy[entity] || IsClientInGhostMode(entity)) { return false; } } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } bool TraceRayDontHitAnyEntity_Pathing(int entity, int contentsMask, int desiredcollisiongroup) { - if ((entity > 0 && entity <= MaxClients)) + if (IsValidClient(entity)) { if (g_PlayerProxy[entity] || IsClientInGhostMode(entity)) { return false; } } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } - if (IsValidEntity(entity)) - { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc") == 0) - { - return false; - } - } return true; } @@ -5006,16 +4907,12 @@ bool TraceRayDontHitCharactersOrEntity(int entity,int mask, any data) return false; } - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { return false; } - int bossIndex = NPCGetFromEntIndex(entity); - if (bossIndex != -1) - { - return false; - } - if (IsValidEntity(entity) && CBaseEntity(entity).IsCombatCharacter()) + + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } @@ -5247,9 +5144,9 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint bool tooNear = false; // Check minimum range with players. - for (int teleportClient = 1; teleportClient <= MaxClients; teleportClient++) + for (int teleportClient = 1; teleportClient < MaxClients; teleportClient++) { - if (!IsClientInGame(teleportClient) || + if (!IsValidClient(teleportClient) || !IsPlayerAlive(teleportClient) || g_PlayerEliminated[teleportClient] || IsClientInGhostMode(teleportClient) || @@ -5357,4 +5254,5 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint } #include "sf2/npc/npc_chaser.sp" -#include "sf2/npc/npc_chaser_takedamage.sp" \ No newline at end of file +#include "sf2/npc/npc_chaser_takedamage.sp" +#include "sf2/npc/entities/initialize.sp" \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp new file mode 100644 index 00000000..137158d5 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -0,0 +1 @@ +#pragma semicolon 1 \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp b/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp new file mode 100644 index 00000000..d0169490 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp @@ -0,0 +1,9 @@ +#pragma semicolon 1 + +#include "statue/entity.sp" +#include "statue/actions/main.sp" + +void SetupEntityActions() +{ + SF2_StatueEntity.Initialize(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp new file mode 100644 index 00000000..5c5fb2d7 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp @@ -0,0 +1,281 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueBaseAction < NextBotAction +{ + public SF2_StatueBaseAction() + { + return view_as(g_Factory.Create()); + } + + public static NextBotActionFactory GetFactory() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Main"); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + } + return g_Factory; + } +} + +static int Update(SF2_StatueBaseAction action, SF2_StatueEntity actor, float interval) +{ + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + INextBot bot = npc.GetBot(); + + SF2NPC_Statue controller = actor.Controller; + int bossIndex = controller.Index; + + int difficulty = g_DifficultyConVar.IntValue; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + controller.GetProfile(profile, sizeof(profile)); + + bool attackWaiters = !!(controller.Flags & SFF_ATTACKWAITERS); + + float gameTime = GetGameTime(); + float chaseDurationTimeAddMin = controller.AddChaseDurationMin(difficulty); + float chaseDurationTimeAddMax = controller.AddChaseDurationMax(difficulty); + bool move = false; + if (PeopleCanSeeSlender(bossIndex, true, true, !attackWaiters)) + { + if (actor.ChaseTime < gameTime) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + actor.ChaseTime += chaseDurationTimeAddMin; + if (actor.ChaseTime > (gameTime + controller.GetChaseDuration(difficulty))) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + for (int i = 0; i < MAX_NPCTELEPORTER; i++) + { + if (controller.GetTeleporter(i) != INVALID_ENT_REFERENCE) + { + controller.SetTeleporter(i, INVALID_ENT_REFERENCE); + } + } + } + + SF2_BasePlayer origTarget = SF2_BasePlayer(actor.Target); + if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap() && origTarget.IsValid) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + + if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap()) + { + if (!origTarget.IsValid || (origTarget.IsValid && origTarget.IsEliminated)) + { + if (NPCAreAvailablePlayersAlive()) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i) || + !IsPlayerAlive(i) || + g_PlayerEliminated[i] || + IsClientInGhostMode(i) || + DidClientEscape(i)) + { + continue; + } + arrayRaidTargets.Push(i); + } + if (arrayRaidTargets.Length > 0) + { + SF2_BasePlayer raidTarget = SF2_BasePlayer(arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1))); + if (raidTarget.IsValid && !raidTarget.IsEliminated) + { + actor.Target = raidTarget.index; + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + } + delete arrayRaidTargets; + } + } + } + + int bestPlayer = -1; + ArrayList array = new ArrayList(); + + for (int i = 1; i < MaxClients; i++) + { + if (!IsValidClient(i) || !IsPlayerAlive(i) || + IsClientInDeathCam(i) || (!attackWaiters && g_PlayerEliminated[i]) || + DidClientEscape(i) || IsClientInGhostMode(i) || + !PlayerCanSeeSlender(i, bossIndex, false, false, !attackWaiters)) + { + continue; + } + + if (!NPCShouldSeeEntity(bossIndex, i)) + { + continue; + } + + array.Push(i); + } + + if (array.Length > 0) + { + float slenderPos[3]; + actor.GetAbsOrigin(slenderPos); + + float tempPos[3]; + int tempPlayer = -1; + float tempDist = SquareFloat(16384.0); + for (int i = 0; i < array.Length; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(array.Get(i)); + client.GetAbsOrigin(tempPos); + if (GetVectorSquareMagnitude(tempPos, slenderPos) < tempDist) + { + tempPlayer = client.index; + tempDist = GetVectorSquareMagnitude(tempPos, slenderPos); + } + if (SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(tempPos, slenderPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) + { + client.Stun(SPECIALROUND_BOO_DURATION, 0.0, TF_STUNFLAGS_GHOSTSCARE); + } + } + + bestPlayer = tempPlayer; + if (bestPlayer != -1) + { + actor.Target = bestPlayer; + if (actor.ChaseTime < gameTime) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + } + } + + delete array; + + if (!PeopleCanSeeSlender(bossIndex, true, true, !attackWaiters)) + { + SF2_BasePlayer target = SF2_BasePlayer(actor.Target); + if (IsTargetValidForSlender(target, attackWaiters) && gameTime < actor.ChaseTime && (gameTime - actor.LastKillTime) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) + { + move = true; + float slenderPos[3], pos[3]; + actor.GetAbsOrigin(slenderPos); + target.GetAbsOrigin(pos); + if (NPCGetTeleporter(bossIndex, 0) != INVALID_ENT_REFERENCE) + { + CBaseEntity teleporter = CBaseEntity(EntRefToEntIndex(NPCGetTeleporter(bossIndex, 0))); + if (teleporter.IsValid() && teleporter.index > MaxClients) + { + teleporter.GetAbsOrigin(pos); + } + } + controller.Path.ComputeToPos(bot, pos); + + float maxRange = NPCStatueGetModelChangeDistance(bossIndex, difficulty); + float dist = GetVectorSquareMagnitude(slenderPos, pos); + + char buffer[PLATFORM_MAX_PATH]; + + if (dist < SquareFloat(maxRange * 0.33)) + { + controller.GetModel(2, buffer, sizeof(buffer)); + } + else if (dist < SquareFloat(maxRange * 0.66)) + { + controller.GetModel(1, buffer, sizeof(buffer)); + } + else + { + controller.GetModel(_, buffer, sizeof(buffer)); + } + + // Fallback if error. + if (buffer[0] == '\0' || strcmp(buffer, "models/") == 0) + { + controller.GetModel(_, buffer, sizeof(buffer)); + } + + actor.SetModel(buffer); + + if (target.CanSeeSlender(bossIndex, false, !attackWaiters)) + { + float distRatio = (dist / SquareFloat(maxRange)); + + float chaseDurationAdd = chaseDurationTimeAddMax - ((chaseDurationTimeAddMax - chaseDurationTimeAddMin) * distRatio); + + if (chaseDurationAdd > 0.0) + { + actor.ChaseTime += chaseDurationAdd; + if (actor.ChaseTime > (gameTime + controller.GetChaseDuration(difficulty))) + { + actor.ChaseTime = gameTime + controller.GetChaseDuration(difficulty); + } + } + } + + if (dist <= SquareFloat(controller.InstantKillRadius)) + { + if (controller.Flags & SFF_FAKE) + { + controller.MarkAsFake(); + return action.Done("Faker"); + } + else + { + actor.LastKillTime = gameTime; + target.StartDeathCam(bossIndex, pos); + g_LastStuckTime[bossIndex] = gameTime + controller.GetInstantKillCooldown(difficulty) + 0.1; + if (controller.GetInstantKillCooldown(difficulty) > 0.0) + { + actor.IsMoving = false; + loco.ClearStuckStatus(); + } + actor.Target = -1; + } + } + + g_SlenderStatueIdleLifeTime[bossIndex] = gameTime + controller.GetIdleLifetime(difficulty); + } + else + { + float origin[3]; + actor.GetAbsOrigin(origin); + origin[2] -= 10.0; + g_LastPos[bossIndex] = origin; + } + } + + SF2BossProfileSoundInfo soundInfo; + if (move) + { + ArrayList soundList; + char buffer[PLATFORM_MAX_PATH]; + GetStatueProfileSingleMoveSounds(profile, soundInfo); + soundInfo.EmitSound(_, actor.index); + + GetStatueProfileMoveSounds(profile, soundInfo); + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); + if (buffer[0] != '\0') + { + EmitSoundToAll(buffer, actor.index, soundInfo.Channel, soundInfo.Level, SND_CHANGEVOL, soundInfo.Volume, soundInfo.Pitch); + } + } + soundList = null; + } + else + { + GetStatueProfileMoveSounds(profile, soundInfo); + soundInfo.StopAllSounds(actor.index); + } + actor.IsMoving = move; + + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp new file mode 100644 index 00000000..599e9c54 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -0,0 +1,154 @@ +#pragma semicolon 1 + +static CEntityFactory g_Factory; + +methodmap SF2_StatueEntity < CBaseCombatCharacter +{ + public SF2_StatueEntity(int entity) + { + return view_as(entity); + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_statue_npc", OnCreate, OnRemove); + g_Factory.DeriveFromNPC(); + g_Factory.SetInitialActionFactory(SF2_StatueBaseAction.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_Controller") + .DefineBoolField("m_IsMoving") + .DefineEntityField("m_Target") + .DefineFloatField("m_ChaseTime") + .DefineFloatField("m_LastKillTime") + .EndDataMapDesc(); + g_Factory.Install(); + } + + property SF2NPC_Statue Controller + { + public get() + { + return view_as(SF2NPC_Statue.FromUniqueId(this.GetProp(Prop_Data, "m_Controller"))); + } + + public set(SF2NPC_Statue controller) + { + this.SetProp(Prop_Data, "m_Controller", controller.Index == -1 ? -1 : controller.UniqueID); + } + } + + property bool IsMoving + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_IsMoving")); + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsMoving", value); + } + } + + property int Target + { + public get() + { + return this.GetPropEnt(Prop_Data, "m_Target"); + } + public set(int entity) + { + this.SetPropEnt(Prop_Data, "m_Target", entity); + } + } + + property float ChaseTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_ChaseTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_ChaseTime", value); + } + } + + property float LastKillTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LastKillTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LastKillTime", value); + } + } + + public static SF2_StatueEntity Create(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) + { + SF2_StatueEntity statue = SF2_StatueEntity(CreateEntityByName("sf2_statue_npc")); + if (!statue.IsValid()) + { + return SF2_StatueEntity(-1); + } + if (controller == SF2_INVALID_NPC_STATUE) + { + return SF2_StatueEntity(-1); + } + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + controller.GetProfile(profile, sizeof(profile)); + statue.Controller = view_as(controller); + + char buffer[PLATFORM_MAX_PATH]; + + GetSlenderModel(controller.Index, _, buffer, sizeof(buffer)); + + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(statue.index); + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + + npc.flMaxYawRate = 0.0; + loco.SetCallback(LocomotionCallback_IsAbleToJumpAcrossGaps, CanJumpAcrossGaps); + loco.SetCallback(LocomotionCallback_IsAbleToClimb, CanJumpAcrossGaps); + loco.SetCallback(LocomotionCallback_JumpAcrossGap, JumpAcrossGapsCBase); + loco.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); + + statue.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); + statue.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); + + statue.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); + statue.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); + + statue.SetProp(Prop_Data, "m_nSolidType", SOLID_BBOX); + statue.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + + SDKHook(statue.iEnt, SDKHook_ThinkPost, SlenderStatueSetNextThink); + SDKHook(statue.iEnt, SDKHook_Think, SlenderStatueBossProcessMovement); + } +} + +static void OnCreate(int ent) +{ + // Do nothing +} + +static void OnRemove(int ent) +{ + // Do nothing +} + +static void SlenderStatueSetNextThink(int bossEnt) +{ + if (!g_Enabled) + { + return; + } + + CBaseCombatCharacter(bossEnt).SetNextThink(GetGameTime()); + + return; +} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index 81ccd03b..c42d76d4 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -334,9 +334,9 @@ void NPCChaserInitialize() } } -void NPCChaserResetAnimationInfo(int npcIndex, int iSequence = 0) +void NPCChaserResetAnimationInfo(int npcIndex, int sequence = 0) { - g_NpcCurrentAnimationSequence[npcIndex] = iSequence; + g_NpcCurrentAnimationSequence[npcIndex] = sequence; g_NpcHasCurrentAnimationSequenceIsLooped[npcIndex] = false; g_NpcCurrentAnimationSequencePlaybackRate[npcIndex] = 1.0; g_NpcUsesChaseInitialAnimation[npcIndex] = false; @@ -1590,7 +1590,7 @@ void NPCChaserOnSelectProfile(int npcIndex, bool invincible) float stunHealthPerPlayer = GetChaserProfileStunHealthPerPlayer(profile); int count; - for (int client; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { SF2_BasePlayer player = SF2_BasePlayer(client); if (player.IsValid && player.IsEliminated) @@ -2276,7 +2276,7 @@ SF2_BasePlayer NPCChaserGetClosestPlayer(int slender) SF2_BasePlayer closestTarget = SF2_INVALID_PLAYER; float searchRadius = NPCGetSearchRadius(bossIndex, difficulty); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { SF2_BasePlayer client = SF2_BasePlayer(i); if (!client.IsValid || client.IsInGhostMode || client.IsProxy || !client.IsAlive || client.IsEliminated) diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp index 38351b67..e2f03c2e 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp @@ -170,9 +170,9 @@ Action Timer_SlenderStealLife(Handle timer, any entref) continue; } - float targetPos[3], vecClientPos[3]; + float targetPos[3], clientPos[3]; player.GetEyePosition(targetPos); - player.GetAbsOrigin(vecClientPos); + player.GetAbsOrigin(clientPos); traceHandle = TR_TraceRayFilterEx(myEyePos, targetPos, @@ -292,10 +292,10 @@ Action Timer_SlenderChaseBossAttack(Handle timer, any entref) int damageType = NPCChaserGetAttackDamageType(bossIndex, attackIndex, difficulty); // Damage all players within range. - float myPos[3], myEyePos[3], myEyeAng[3], vecMyRot[3]; + float myPos[3], myEyePos[3], myEyeAng[3], myRot[3]; NPCGetEyePosition(bossIndex, myEyePos); GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", vecMyRot); + GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myRot); GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); AddVectors(g_SlenderEyePosOffset[bossIndex], myEyeAng, myEyeAng); @@ -563,10 +563,10 @@ Action Timer_SlenderChaseBossAttack(Handle timer, any entref) continue; } - float targetPos[3], vecClientPos[3], targetPosShockwave[3]; + float targetPos[3], clientPos[3], targetPosShockwave[3]; player.GetEyePosition(targetPos); targetPosShockwave = targetPos; - player.GetAbsOrigin(vecClientPos); + player.GetAbsOrigin(clientPos); if (NPCChaserShockwaveOnAttack(bossIndex)) { @@ -623,7 +623,7 @@ Action Timer_SlenderChaseBossAttack(Handle timer, any entref) { float targetDistShockwave = GetVectorSquareMagnitude(targetPos, myEyePos); - if ((vecClientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) + if ((clientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) { float percentLife; percentLife = player.FlashlightBatteryLife - NPCChaserGetShockwaveDrain(bossIndex, difficulty); @@ -712,7 +712,7 @@ Action Timer_SlenderChaseBossAttack(Handle timer, any entref) { float targetDistShockwave = GetVectorSquareMagnitude(targetPos, myEyePos); - if ((vecClientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) + if ((clientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) { float percentLife; percentLife = player.FlashlightBatteryLife - NPCChaserGetShockwaveDrain(bossIndex, difficulty); @@ -907,8 +907,8 @@ Action Timer_SlenderChaseBossAttack(Handle timer, any entref) float effectAng[3] = {0.0, 0.0, 0.0}; - VectorTransform(myEyePos, myPos, vecMyRot, myEyePos); - AddVectors(effectAng, vecMyRot, effectAng); + VectorTransform(myEyePos, myPos, myRot, myEyePos); + AddVectors(effectAng, myRot, effectAng); float pullDirection[3], pullAngle[3]; SubtractVectors(newClientPos, myEyePos, pullDirection); @@ -1081,7 +1081,7 @@ Action Timer_SlenderChaseBossAttack(Handle timer, any entref) SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); char particleName[PLATFORM_MAX_PATH]; GetChaserProfileAttackBulletTrace(profile, attackIndex, particleName, sizeof(particleName)); - float vecSpread = NPCChaserGetAttackBulletSpread(bossIndex, attackIndex, difficulty); + float spread = NPCChaserGetAttackBulletSpread(bossIndex, attackIndex, difficulty); ArrayList bulletSounds = GetChaserProfileBulletShootSounds(profile); if (bulletSounds != null && bulletSounds.Length > 0) { @@ -1126,13 +1126,13 @@ Action Timer_SlenderChaseBossAttack(Handle timer, any entref) x = GetRandomFloat( -0.5, 0.5 ) + GetRandomFloat( -0.5, 0.5 ); y = GetRandomFloat( -0.5, 0.5 ) + GetRandomFloat( -0.5, 0.5 ); - float dirShooting[3], vecRight[3], vecUp[3]; - GetAngleVectors(shootAng, dirShooting, vecRight, vecUp); + float dirShooting[3], right[3], up[3]; + GetAngleVectors(shootAng, dirShooting, right, up); float dir[3]; - dir[0] = dirShooting[0] + x * vecSpread * vecRight[0] + y * vecSpread * vecUp[0]; - dir[1] = dirShooting[1] + x * vecSpread * vecRight[1] + y * vecSpread * vecUp[1]; - dir[2] = dirShooting[2] + x * vecSpread * vecRight[2] + y * vecSpread * vecUp[2]; + dir[0] = dirShooting[0] + x * spread * right[0] + y * spread * up[0]; + dir[1] = dirShooting[1] + x * spread * right[1] + y * spread * up[1]; + dir[2] = dirShooting[2] + x * spread * right[2] + y * spread * up[2]; NormalizeVector(dir, dir); float end[3]; @@ -1421,7 +1421,7 @@ static Action Timer_SlenderChaseBossExplosiveDance(Handle timer, any entref) explosionPosition[2] = slenderPosition[2] + 50.0; for (int e = 0; e < 5; e++) { - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) @@ -1442,8 +1442,8 @@ static Action Timer_SlenderChaseBossExplosiveDance(Handle timer, any entref) SetEntProp(explosivePower, Prop_Data, "m_iMagnitude", 666, 4); SetEntProp(explosivePower, Prop_Data, "m_iRadiusOverride", 200, 4); SetEntPropEnt(explosivePower, Prop_Data, "m_hOwnerEntity", slender); - explosionPosition[0]=slenderPosition[0]+float(GetRandomInt(-range, range)); - explosionPosition[1]=slenderPosition[1]+float(GetRandomInt(-range, range)); + explosionPosition[0] = slenderPosition[0] + float(GetRandomInt(-range, range)); + explosionPosition[1] = slenderPosition[1] + float(GetRandomInt(-range, range)); TeleportEntity(explosivePower, explosionPosition, NULL_VECTOR, NULL_VECTOR); DispatchSpawn(explosivePower); diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp index 67e93799..b7eca358 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp @@ -7,6 +7,7 @@ #pragma semicolon 1 char cloakParticle[PLATFORM_MAX_PATH]; +static bool g_BossForceAnimationUpdate[MAX_BOSSES] = { false, ... }; Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are so big, you get a file dedicated to only you { @@ -47,6 +48,8 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are INextBot bot = npc.GetBot(); CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npc.GetEntity()); + g_BossForceAnimationUpdate[chaserBoss.Index] = false; + float gameTime = GetGameTime(); #if defined DEBUG @@ -603,6 +606,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) { g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; + g_BossForceAnimationUpdate[chaserBoss.Index] = true; npc.flWalkSpeed = 0.0; npc.flRunSpeed = 0.0; NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, STATE_CHASE); @@ -734,9 +738,9 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are { ArrayList arrayRaidTargets = new ArrayList(); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i) || + if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i) || @@ -771,7 +775,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are for (int i = 0; i < g_NpcChaseOnLookTarget[chaserBoss.Index].Length; i++) { int lookClient = g_NpcChaseOnLookTarget[chaserBoss.Index].Get(i); - if (IsValidClient(lookClient) && !g_PlayerEliminated[lookClient] && IsPlayerAlive(lookClient) && IsClientInGame(lookClient) && + if (IsValidClient(lookClient) && !g_PlayerEliminated[lookClient] && IsPlayerAlive(lookClient) && !IsClientInGhostMode(lookClient) && !DidClientEscape(lookClient)) { bestNewTarget = lookClient; @@ -1231,6 +1235,11 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are delete trace; } + if (!CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target))) + { + + } + if (!building && !playerVisible[target] && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) { if (gameTime >= g_SlenderTimeUntilAlert[chaserBoss.Index] || (!attackEliminated && g_PlayerEliminated[target])) @@ -1615,7 +1624,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (difficulty != 2 && difficulty < 2) { NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) { @@ -1647,7 +1656,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (difficulty != 3 && difficulty < 3) { NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) { @@ -1705,7 +1714,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (difficulty != 4 && difficulty < 4) { NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) { @@ -1877,7 +1886,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (difficulty != 2 && difficulty < 2) { NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) { @@ -1903,7 +1912,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (difficulty != 3 && difficulty < 3) { NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) { @@ -1955,7 +1964,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (difficulty != 4 && difficulty < 4) { NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) { @@ -2266,6 +2275,7 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0)) { g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; + g_BossForceAnimationUpdate[chaserBoss.Index] = true; npc.flWalkSpeed = 0.0; npc.flRunSpeed = 0.0; NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); @@ -2282,8 +2292,8 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0)) { g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; + g_BossForceAnimationUpdate[chaserBoss.Index] = true; npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); g_SlenderChaseInitialTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); } @@ -2321,7 +2331,14 @@ Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are if (oldState != state && !g_SlenderSpawning[chaserBoss.Index]) { - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); + if (g_BossForceAnimationUpdate[chaserBoss.Index]) + { + g_BossForceAnimationUpdate[chaserBoss.Index] = false; + } + else + { + NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); + } } if (g_NpcChangeToCrawl[chaserBoss.Index] && (state == STATE_CHASE || state == STATE_WANDER || state == STATE_ALERT)) diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp index 8f131f67..a5ace8a2 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp @@ -861,9 +861,9 @@ static Action Hook_ProjectileAttackTouch(int entity, int other) int attackIndex = NPCGetCurrentAttackIndex(bossIndex); bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); float radius = NPCChaserGetAttackProjectileRadius(bossIndex, attackIndex, difficulty); - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { - if (!IsValidClient(client) || !IsClientInGame(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) { continue; } @@ -968,7 +968,7 @@ static Action Hook_ProjectileTouch(int entity, int other) float entPos[3], otherPos[3]; GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - if (other > 0 && other <= MaxClients) + if (IsValidClient(other)) { int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); if (slender != INVALID_ENT_REFERENCE) @@ -1000,9 +1000,9 @@ static Action Hook_ProjectileTouch(int entity, int other) bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); float radius = NPCChaserGetProjectileRadius(bossIndex, difficulty); float fallOff = NPCChaserGetProjectileRadius(bossIndex, difficulty)/2.0; - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { - if (!IsValidClient(client) || !IsClientInGame(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) + if (!IsValidClient(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) { continue; } diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp index d6e6f874..05d670c0 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp @@ -606,7 +606,7 @@ void NPCBossTriggerStun(int bossIndex, int victim, char profile[SF2_MAX_PROFILE_ g_NpcSelfHealStage[bossIndex] = 12; if (SF_IsBoxingMap() && NPCChaserIsBoxingBoss(bossIndex)) { - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) { diff --git a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp index 9fd90a62..80932d9f 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp @@ -21,6 +21,26 @@ float NPCStatueChaseDuration(int npcIndex, int difficulty) return g_NpcStatueChaseDuration[npcIndex][difficulty]; } +float NPCStatueGetChaseDurationAddMin(int npcIndex, int difficulty) +{ + return g_NpcStatueChaseDurationAddMin[npcIndex][difficulty]; +} + +float NPCStatueGetChaseDurationAddMax(int npcIndex, int difficulty) +{ + return g_NpcStatueChaseDurationAddMax[npcIndex][difficulty]; +} + +float NPCStatueGetIdleLifetime(int npcIndex, int difficulty) +{ + return g_NpcStatueIdleLifetime[npcIndex][difficulty]; +} + +float NPCStatueGetModelChangeDistance(int npcIndex, int difficulty) +{ + return g_NpcStatueModelChangeDistMax[npcIndex][difficulty]; +} + void NPCStatueOnSelectProfile(const char[] profile, int npcIndex) { for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) @@ -34,11 +54,12 @@ void NPCStatueOnSelectProfile(const char[] profile, int npcIndex) } } -void Spawn_Statue(int npcIndex) +CBaseCombatCharacter Spawn_Statue(int npcIndex) { g_NpcStatueMoving[npcIndex] = false; g_SlenderTarget[npcIndex] = INVALID_ENT_REFERENCE; g_SlenderStatueIdleLifeTime[npcIndex] = GetGameTime() + g_NpcStatueIdleLifetime[npcIndex][GetLocalGlobalDifficulty()]; + return CBaseCombatCharacter(CreateEntityByName("sf2_statue_npc")); } void SlenderStatueBossProcessMovement(int bossEnt) @@ -364,295 +385,4 @@ void SlenderStatueBossProcessMovement(int bossEnt) } return; -} - -void SlenderStatueSetNextThink(int bossEnt) -{ - if (!g_Enabled) - { - return; - } - - CBaseCombatCharacter(bossEnt).SetNextThink(GetGameTime()); - - return; -} - -Action Timer_SlenderBlinkBossThink(Handle timer, any entref) -{ - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderEntityThink[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - if (npc == INVALID_NPC) - { - return Plugin_Stop; - } - - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - INextBot bot = npc.GetBot(); - - int difficulty = g_DifficultyConVar.IntValue; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - bool attackWaiters = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - if (NPCGetType(bossIndex) == SF2BossType_Statue) - { - float gameTime = GetGameTime(); - float chaseDurationTimeAddMin = g_NpcStatueChaseDurationAddMin[bossIndex][difficulty]; - float chaseDurationTimeAddMax = g_NpcStatueChaseDurationAddMax[bossIndex][difficulty]; - bool move = false; - if (PeopleCanSeeSlender(bossIndex, true, SlenderUsesBlink(bossIndex), !attackWaiters)) - { - if (g_NpcTimeUntilAbandon[bossIndex] < gameTime) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - g_NpcTimeUntilAbandon[bossIndex] += chaseDurationTimeAddMin; - if (g_NpcTimeUntilAbandon[bossIndex] > (gameTime + NPCStatueChaseDuration(bossIndex, difficulty))) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - for (int i = 0; i < MAX_NPCTELEPORTER; i++) - { - if (NPCGetTeleporter(bossIndex, i) != INVALID_ENT_REFERENCE) - { - NPCSetTeleporter(bossIndex, i, INVALID_ENT_REFERENCE); - } - } - } - - int origTarget = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap() && IsValidClient(origTarget)) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - - if (SF_BossesChaseEndlessly() || SF_IsSlaughterRunMap()) - { - if (!IsValidClient(origTarget) || (IsValidClient(origTarget) && g_PlayerEliminated[origTarget])) - { - if (NPCAreAvailablePlayersAlive()) - { - ArrayList arrayRaidTargets = new ArrayList(); - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || - !IsPlayerAlive(i) || - g_PlayerEliminated[i] || - IsClientInGhostMode(i) || - DidClientEscape(i)) - { - continue; - } - arrayRaidTargets.Push(i); - } - if(arrayRaidTargets.Length > 0) - { - int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); - if(IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) - { - g_SlenderTarget[bossIndex] = EntIndexToEntRef(raidTarget); - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - } - delete arrayRaidTargets; - } - } - } - - int bestPlayer = -1; - ArrayList array = new ArrayList(); - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || !IsPlayerAlive(i) || IsClientInDeathCam(i) || (!attackWaiters && g_PlayerEliminated[i]) || DidClientEscape(i) || IsClientInGhostMode(i) || !PlayerCanSeeSlender(i, bossIndex, false, false, !attackWaiters)) - { - continue; - } - - if (!NPCShouldSeeEntity(bossIndex, i)) - { - continue; - } - - array.Push(i); - } - - if (array.Length) - { - float slenderPos[3]; - SlenderGetAbsOrigin(bossIndex, slenderPos); - - float tempPos[3]; - int tempPlayer = -1; - float tempDist = SquareFloat(16384.0); - for (int i = 0; i < array.Length; i++) - { - int client = array.Get(i); - GetClientAbsOrigin(client, tempPos); - if (GetVectorSquareMagnitude(tempPos, slenderPos) < tempDist) - { - tempPlayer = client; - tempDist = GetVectorSquareMagnitude(tempPos, slenderPos); - } - if (SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(tempPos, slenderPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) - { - TF2_StunPlayer(client, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); - } - } - - bestPlayer = tempPlayer; - if (bestPlayer != -1) - { - g_SlenderTarget[bossIndex] = EntIndexToEntRef(bestPlayer); - if (g_NpcTimeUntilAbandon[bossIndex] < gameTime) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - } - } - - delete array; - - if (!PeopleCanSeeSlender(bossIndex, true, SlenderUsesBlink(bossIndex), !attackWaiters)) - { - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - if (IsTargetValidForSlender(target, attackWaiters) && gameTime < g_NpcTimeUntilAbandon[bossIndex] && (gameTime - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) - { - move = true; - float slenderPos[3], pos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPos); - target.GetAbsOrigin(pos); - if (NPCGetTeleporter(bossIndex, 0) != INVALID_ENT_REFERENCE) - { - int teleporter = EntRefToEntIndex(NPCGetTeleporter(bossIndex, 0)); - if (IsValidEntity(teleporter) && teleporter > MaxClients) - { - GetEntPropVector(teleporter, Prop_Data, "m_vecAbsOrigin", pos); - } - } - g_BossPathFollower[bossIndex].ComputeToPos(bot, pos); - - float maxRange = g_NpcStatueModelChangeDistMax[bossIndex][difficulty]; - float dist = GetVectorSquareMagnitude(slenderPos, pos); - - char buffer[PLATFORM_MAX_PATH]; - - if (dist < SquareFloat(maxRange * 0.33)) - { - GetSlenderModel(bossIndex, 2, buffer, sizeof(buffer)); - } - else if (dist < SquareFloat(maxRange * 0.66)) - { - GetSlenderModel(bossIndex, 1, buffer, sizeof(buffer)); - } - else - { - GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); - } - - // Fallback if error. - if (buffer[0] == '\0' || strcmp(buffer, "models/") == 0) - { - GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); - } - - SetEntityModel(slender, buffer); - - if (target.CanSeeSlender(bossIndex, false, !attackWaiters)) - { - float distRatio = (dist / SquareFloat(maxRange)); - - float chaseDurationAdd = chaseDurationTimeAddMax - ((chaseDurationTimeAddMax - chaseDurationTimeAddMin) * distRatio); - - if (chaseDurationAdd > 0.0) - { - g_NpcTimeUntilAbandon[bossIndex] += chaseDurationAdd; - if (g_NpcTimeUntilAbandon[bossIndex] > (gameTime + NPCStatueChaseDuration(bossIndex, difficulty))) - { - g_NpcTimeUntilAbandon[bossIndex] = gameTime + NPCStatueChaseDuration(bossIndex, difficulty); - } - } - } - - if (dist <= SquareFloat(NPCGetInstantKillRadius(bossIndex))) - { - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - return Plugin_Stop; - } - else - { - g_SlenderLastKill[bossIndex] = gameTime; - target.StartDeathCam(bossIndex, pos); - g_LastStuckTime[bossIndex] = gameTime + NPCGetInstantKillCooldown(bossIndex, difficulty) + 0.1; - if (NPCGetInstantKillCooldown(bossIndex, difficulty) > 0.0) - { - g_NpcStatueMoving[bossIndex] = false; - loco.ClearStuckStatus(); - } - g_SlenderTarget[bossIndex] = INVALID_ENT_REFERENCE; - } - } - - g_SlenderStatueIdleLifeTime[bossIndex] = gameTime + g_NpcStatueIdleLifetime[bossIndex][difficulty]; - } - else - { - float origin[3]; - SlenderGetAbsOrigin(bossIndex, origin); - origin[2] -= 10.0; - g_LastPos[bossIndex] = origin; - } - } - - SF2BossProfileSoundInfo soundInfo; - if (move) - { - ArrayList soundList; - char buffer[PLATFORM_MAX_PATH]; - GetStatueProfileSingleMoveSounds(profile, soundInfo); - soundInfo.EmitSound(_, slender); - - GetStatueProfileMoveSounds(profile, soundInfo); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0') - { - EmitSoundToAll(buffer, slender, soundInfo.Channel, soundInfo.Level, SND_CHANGEVOL, soundInfo.Volume, soundInfo.Pitch); - } - } - soundList = null; - } - else - { - GetStatueProfileMoveSounds(profile, soundInfo); - soundInfo.StopAllSounds(slender); - } - g_NpcStatueMoving[bossIndex] = move; - } - - return Plugin_Continue; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/playergroups.sp b/addons/sourcemod/scripting/sf2/playergroups.sp index e508793b..4d88ea09 100644 --- a/addons/sourcemod/scripting/sf2/playergroups.sp +++ b/addons/sourcemod/scripting/sf2/playergroups.sp @@ -251,7 +251,7 @@ static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1, { SetPlayerGroupQueuePoints(groupIndex, 0); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -296,7 +296,7 @@ void CheckPlayerGroup(int groupIndex) else { // Remove any person that isn't participating. - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (ClientGetPlayerGroup(i) == groupIndex) { @@ -333,7 +333,7 @@ void CheckPlayerGroup(int groupIndex) CPrintToChat(groupLeader, "%T", "SF2 Group Has Too Many Members", groupLeader); } - for (int i = 1, count; i <= MaxClients && count < excessMemberCount; i++) + for (int i = 1, count; i < MaxClients && count < excessMemberCount; i++) { if (!IsValidClient(i)) { @@ -401,7 +401,7 @@ int CreatePlayerGroup() SetPlayerGroupName(index, ""); SetPlayerGroupPlaying(index, false); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { SetPlayerGroupInvitedPlayer(index, i, false); SetPlayerGroupInvitedPlayerCount(index, i, 0); @@ -434,7 +434,7 @@ void ClearPlayerGroupMembers(int groupIndex) return; } - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (ClientGetPlayerGroup(i) == groupIndex) { @@ -481,7 +481,7 @@ int GetPlayerGroupMemberCount(int groupIndex) { int count; - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -525,7 +525,7 @@ void SetPlayerGroupLeader(int groupIndex,int groupLeader) char name[MAX_NAME_LENGTH]; FormatEx(name, sizeof(name), "%N", groupLeader); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (groupLeader == i || !IsValidClient(i)) { @@ -546,7 +546,7 @@ int PlayerGroupFindNewLeader(int groupIndex) return -1; } - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -631,7 +631,7 @@ void ClientSetPlayerGroup(int client,int groupIndex) GetPlayerGroupName(oldPlayerGroup, groupName, sizeof(groupName)); CPrintToChat(client, "%T", "SF2 Left Group", client, groupName); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (i == client || !IsValidClient(i)) { @@ -675,7 +675,7 @@ void ClientSetPlayerGroup(int client,int groupIndex) GetPlayerGroupName(groupIndex, groupName, sizeof(groupName)); CPrintToChat(client, "%T", "SF2 Joined Group", client, groupName); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (i == client || !IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/playergroups/menus.sp b/addons/sourcemod/scripting/sf2/playergroups/menus.sp index 49284e58..936a3052 100644 --- a/addons/sourcemod/scripting/sf2/playergroups/menus.sp +++ b/addons/sourcemod/scripting/sf2/playergroups/menus.sp @@ -298,7 +298,7 @@ void DisplayViewGroupMembersMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayViewGroupMembersMenuToClient.", playersHandle); #endif - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -389,7 +389,7 @@ void DisplaySetGroupLeaderMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplaySetGroupLeaderMenuToClient.", playersHandle); #endif - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -507,7 +507,7 @@ void DisplayKickFromGroupMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayKickFromGroupMenuToClient.", playersHandle); #endif - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -682,7 +682,7 @@ void DisplayInviteToGroupMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayInviteToGroupMenuToClient.", playersHandle); #endif - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i) || !IsClientParticipating(i)) { diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index de88852a..195bab5f 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -765,7 +765,7 @@ void CheckRoundLimitForBossPackVote(int roundCount) void InitiateBossPackVote(int initiator) { - if (initiator<33) //A admin called the command, it's probably for a good reason + if (initiator < 33) //A admin called the command, it's probably for a good reason { g_BossPackVoteCompleted = false; } @@ -791,8 +791,8 @@ void InitiateBossPackVote(int initiator) Handle voteMenu = NativeVotes_Create(Menu_BossPackVote, NativeVotesType_Custom_Mult); NativeVotes_SetInitiator(voteMenu, initiator); char title[255]; - FormatEx(title,255,"%t%t","SF2 Prefix","SF2 Boss Pack Vote Menu Title"); - NativeVotes_SetDetails(voteMenu,title); + FormatEx(title, 255, "%t%t", "SF2 Prefix", "SF2 Boss Pack Vote Menu Title"); + NativeVotes_SetDetails(voteMenu, title); StringMap menuDisplayNamesTrie = new StringMap(); ArrayList menuOptionsInfo = new ArrayList(128); int voteIndex = 0; diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index 048efa6d..bd307e14 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -181,8 +181,8 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("cloak_on_sound", profileData.CloakOnSound, sizeof(profileData.CloakOnSound), profileData.CloakOnSound); kv.GetString("cloak_off_sound", profileData.CloakOffSound, sizeof(profileData.CloakOffSound), profileData.CloakOffSound); - TryPrecacheBossProfileSoundPath(profileData.CloakOnSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.CloakOffSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.CloakOnSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.CloakOffSound, g_FileCheckConVar.BoolValue); } profileData.ProjectilesEnabled = !!kv.GetNum("projectile_enable", profileData.ProjectilesEnabled); if (profileData.ProjectilesEnabled) @@ -237,8 +237,8 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("fire_shoot_sound", profileData.FireballShootSound, sizeof(profileData.FireballShootSound), profileData.FireballShootSound); kv.GetString("fire_trail", profileData.FireballTrail, sizeof(profileData.FireballTrail), profileData.FireballTrail); - TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Iceball: { @@ -247,9 +247,9 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("fire_iceball_slow_sound", profileData.IceballSlowSound, sizeof(profileData.IceballSlowSound), profileData.IceballSlowSound); kv.GetString("fire_iceball_trail", profileData.IceballTrail, sizeof(profileData.IceballTrail), profileData.IceballTrail); - TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.IceballSlowSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballExplodeSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.FireballShootSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.IceballSlowSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Rocket: { @@ -259,8 +259,8 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("rocket_shoot_sound", profileData.RocketShootSound, sizeof(profileData.RocketShootSound), profileData.RocketShootSound); kv.GetString("rocket_model", profileData.RocketModel, sizeof(profileData.RocketModel), profileData.RocketModel); - TryPrecacheBossProfileSoundPath(profileData.RocketExplodeSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.RocketShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.RocketExplodeSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.RocketShootSound, g_FileCheckConVar.BoolValue); if (strcmp(profileData.RocketModel, ROCKET_MODEL, true) != 0) { @@ -278,32 +278,32 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { kv.GetString("grenade_shoot_sound", profileData.GrenadeShootSound, sizeof(profileData.GrenadeShootSound), profileData.GrenadeShootSound); - TryPrecacheBossProfileSoundPath(profileData.GrenadeShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.GrenadeShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_SentryRocket: { kv.GetString("sentryrocket_shoot_sound", profileData.SentryRocketShootSound, sizeof(profileData.SentryRocketShootSound), profileData.SentryRocketShootSound); - TryPrecacheBossProfileSoundPath(profileData.SentryRocketShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.SentryRocketShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Arrow: { kv.GetString("arrow_shoot_sound", profileData.ArrowShootSound, sizeof(profileData.ArrowShootSound), profileData.ArrowShootSound); - TryPrecacheBossProfileSoundPath(profileData.ArrowShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.ArrowShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Mangler: { kv.GetString("mangler_shoot_sound", profileData.ManglerShootSound, sizeof(profileData.ManglerShootSound), profileData.ManglerShootSound); - TryPrecacheBossProfileSoundPath(profileData.ManglerShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.ManglerShootSound, g_FileCheckConVar.BoolValue); } case SF2BossProjectileType_Baseball: { kv.GetString("baseball_model", profileData.BaseballModel, sizeof(profileData.BaseballModel), profileData.BaseballModel); kv.GetString("baseball_shoot_sound", profileData.BaseballShootSound, sizeof(profileData.BaseballShootSound), profileData.BaseballShootSound); - TryPrecacheBossProfileSoundPath(profileData.BaseballShootSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.BaseballShootSound, g_FileCheckConVar.BoolValue); PrecacheModel2(profileData.BaseballModel, _, _, g_FileCheckConVar.BoolValue); } } @@ -372,7 +372,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("player_jarate_particle", profileData.JarateParticle, sizeof(profileData.JarateParticle), profileData.JarateParticle); kv.GetString("player_jarate_sound", profileData.JarateHitSound, sizeof(profileData.JarateHitSound), profileData.JarateHitSound); - TryPrecacheBossProfileSoundPath(profileData.JarateHitSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.JarateHitSound, g_FileCheckConVar.BoolValue); } profileData.MilkEffects = !!kv.GetNum("player_milk_on_hit", profileData.MilkEffects); @@ -389,7 +389,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("player_milk_particle", profileData.MilkParticle, sizeof(profileData.MilkParticle), profileData.MilkParticle); kv.GetString("player_milk_sound", profileData.MilkHitSound, sizeof(profileData.MilkHitSound), profileData.MilkHitSound); - TryPrecacheBossProfileSoundPath(profileData.MilkHitSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.MilkHitSound, g_FileCheckConVar.BoolValue); } profileData.GasEffects = !!kv.GetNum("player_gas_on_hit", profileData.GasEffects); @@ -406,7 +406,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("player_gas_particle", profileData.GasParticle, sizeof(profileData.GasParticle), profileData.GasParticle); kv.GetString("player_gas_sound", profileData.GasHitSound, sizeof(profileData.GasHitSound), profileData.GasHitSound); - TryPrecacheBossProfileSoundPath(profileData.GasHitSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.GasHitSound, g_FileCheckConVar.BoolValue); } profileData.MarkEffects = !!kv.GetNum("player_mark_on_hit", profileData.MarkEffects); @@ -470,7 +470,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("player_stun_particle", profileData.StunParticle, sizeof(profileData.StunParticle), profileData.StunParticle); kv.GetString("player_stun_sound", profileData.StunHitSound, sizeof(profileData.StunHitSound), profileData.StunHitSound); - TryPrecacheBossProfileSoundPath(profileData.StunHitSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.StunHitSound, g_FileCheckConVar.BoolValue); } profileData.BleedEffects = !!kv.GetNum("player_bleed_on_hit", profileData.BleedEffects); @@ -537,7 +537,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("damage_effect_particle", profileData.DamageParticleName, sizeof(profileData.DamageParticleName), profileData.DamageParticleName); profileData.DamageParticleBeam = !!kv.GetNum("damage_effect_beam_particle", profileData.DamageParticleBeam); kv.GetString("sound_damage_effect", profileData.DamageParticleSound, sizeof(profileData.DamageParticleSound), profileData.DamageParticleSound); - TryPrecacheBossProfileSoundPath(profileData.DamageParticleSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.DamageParticleSound, g_FileCheckConVar.BoolValue); } profileData.Shockwaves = !!kv.GetNum("shockwave", profileData.Shockwaves); @@ -610,9 +610,9 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("trap_animation_idle", profileData.TrapAnimIdle, sizeof(profileData.TrapAnimIdle), profileData.TrapAnimIdle); kv.GetString("trap_animation_closed", profileData.TrapAnimClose, sizeof(profileData.TrapAnimClose), profileData.TrapAnimClose); kv.GetString("trap_animation_open", profileData.TrapAnimOpen, sizeof(profileData.TrapAnimOpen), profileData.TrapAnimOpen); - TryPrecacheBossProfileSoundPath(profileData.TrapDeploySound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.TrapMissSound, _, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.TrapCatchSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.TrapDeploySound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.TrapMissSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.TrapCatchSound, g_FileCheckConVar.BoolValue); if (strcmp(profileData.TrapModel, TRAP_MODEL, true) != 0) { diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 25ed93b3..6e88f0a1 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -175,7 +175,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { kv.GetString("tp_effect_spawn_particle", profileData.SpawnParticle, sizeof(profileData.SpawnParticle), profileData.SpawnParticle); kv.GetString("tp_effect_spawn_sound", profileData.SpawnParticleSound, sizeof(profileData.SpawnParticleSound), profileData.SpawnParticleSound); - TryPrecacheBossProfileSoundPath(profileData.SpawnParticleSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.SpawnParticleSound, g_FileCheckConVar.BoolValue); profileData.SpawnParticleSoundVolume = kv.GetFloat("tp_effect_spawn_sound_volume", profileData.SpawnParticleSoundVolume); profileData.SpawnParticleSoundPitch = kv.GetNum("tp_effect_spawn_sound_pitch", profileData.SpawnParticleSoundPitch); } @@ -185,7 +185,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { kv.GetString("tp_effect_despawn_particle", profileData.DespawnParticle, sizeof(profileData.DespawnParticle), profileData.DespawnParticle); kv.GetString("tp_effect_despawn_sound", profileData.DespawnParticleSound, sizeof(profileData.DespawnParticleSound), profileData.DespawnParticleSound); - TryPrecacheBossProfileSoundPath(profileData.DespawnParticleSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.DespawnParticleSound, g_FileCheckConVar.BoolValue); profileData.DespawnParticleSoundVolume = kv.GetFloat("tp_effect_despawn_sound_volume", profileData.DespawnParticleSoundVolume); profileData.DespawnParticleSoundPitch = kv.GetNum("tp_effect_despawn_sound_pitch", profileData.DespawnParticleSoundPitch); } @@ -719,7 +719,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("constant_sound", profileData.EngineSound, sizeof(profileData.EngineSound), profileData.EngineSound); - TryPrecacheBossProfileSoundPath(profileData.EngineSound, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.EngineSound, g_FileCheckConVar.BoolValue); int index = g_BossProfileList.FindString(profile); if (index == -1) @@ -836,7 +836,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu break; } - TryPrecacheBossProfileSoundPath(s4, _, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(s4, g_FileCheckConVar.BoolValue); // Here comes an if else mess, I'm very sorry if (strcmp(s2, "sound_jumpscare") == 0) diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index c54c6824..1d6265f0 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -117,6 +117,16 @@ void PvP_Initialize() g_PvPBallsOfFire = new ArrayList(sizeof(PvPProjectile_BallOfFire)); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnGameFramePFwd.AddFunction(null, GameFrame); + g_OnRoundStartPFwd.AddFunction(null, RoundStart); + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + AddTempEntHook("TFBlood", TempEntHook_PvPBlood); AddTempEntHook("World Decal", TempEntHook_PvPDecal); AddTempEntHook("Entity Decal", TempEntHook_PvPDecal); @@ -131,7 +141,7 @@ void PvP_SetupMenus() SetMenuExitBackButton(g_MenuSettingsPvP, true); } -void PvP_OnMapStart() +static void MapStart() { int ent = -1; while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) @@ -161,7 +171,7 @@ void PvP_OnMapStart() } } -void PvP_OnRoundStart() +static void RoundStart() { int ent = -1; while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) @@ -205,25 +215,25 @@ void PvP_Precache() PrecacheSound2(SF2_PVP_SPAWN_SOUND, true); } -void PvP_OnClientPutInServer(int client) +static void OnPutInServer(SF2_BasePlayer client) { - g_PlayerEnteredPvPTriggers[client] = new ArrayList(); + g_PlayerEnteredPvPTriggers[client.index] = new ArrayList(); - PvP_ForceResetPlayerPvPData(client); + PvP_ForceResetPlayerPvPData(client.index); } -void PvP_OnClientDisconnect(int client) +static void OnDisconnected(SF2_BasePlayer client) { - PvP_SetPlayerPvPState(client, false, false, false); + PvP_SetPlayerPvPState(client.index, false, false, false); - if (g_PlayerEnteredPvPTriggers[client] != null) + if (g_PlayerEnteredPvPTriggers[client.index] != null) { - delete g_PlayerEnteredPvPTriggers[client]; - g_PlayerEnteredPvPTriggers[client] = null; + delete g_PlayerEnteredPvPTriggers[client.index]; + g_PlayerEnteredPvPTriggers[client.index] = null; } } -void PvP_OnGameFrame() +static void GameFrame() { // Process through PvP projectiles. for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) @@ -261,7 +271,7 @@ void PvP_OnGameFrame() static float mins[3] = { -6.0, ... }; static float maxs[3] = { 6.0, ... }; - float flOrigin[3]; + float origin[3]; Handle trace = null; int ent = -1; @@ -282,9 +292,9 @@ void PvP_OnGameFrame() if (IsValidClient(ownerEntity) && (IsRoundInWarmup() || IsClientInPvP(ownerEntity))) { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", flOrigin); + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", origin); - trace = TR_TraceHullFilterEx(flOrigin, flOrigin, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, ownerEntity); + trace = TR_TraceHullFilterEx(origin, origin, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, ownerEntity); hitEntity = TR_GetEntityIndex(trace); delete trace; @@ -298,17 +308,17 @@ void PvP_OnGameFrame() } } -void PvP_OnEntityCreated(int ent, const char[] classname) +static void EntityCreated(CBaseEntity ent, const char[] classname) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x083EFF3EFF+ %i(%s)",ent,classname); + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x083EFF3EFF+ %i(%s)", ent.index, classname); #endif for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) { if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) { - SDKHook(ent, SDKHook_Spawn, Hook_PvPProjectileSpawn); - SDKHook(ent, SDKHook_SpawnPost, Hook_PvPProjectileSpawnPost); + SDKHook(ent.index, SDKHook_Spawn, Hook_PvPProjectileSpawn); + SDKHook(ent.index, SDKHook_SpawnPost, Hook_PvPProjectileSpawnPost); break; } } @@ -317,21 +327,21 @@ void PvP_OnEntityCreated(int ent, const char[] classname) { if (strcmp(classname, g_PvPProjectileClassesNoTouch[i], false) == 0) { - SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + SDKHook(ent.index, SDKHook_Touch, Hook_PvPProjectile_OnTouch); break; } } } -void PvP_OnEntityDestroyed(int ent, const char[] classname) +static void EntityDestroyed(CBaseEntity ent, const char[] classname) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x08FF4040FF- %i(%s)",ent,classname); + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x08FF4040FF- %i(%s)", ent.index, classname); #endif if (strcmp(classname, "tf_projectile_balloffire", false) == 0) { - int index = g_PvPBallsOfFire.FindValue( ent ); + int index = g_PvPBallsOfFire.FindValue(ent.index); if (index != -1) { PvPProjectile_BallOfFire projectileData; @@ -457,7 +467,7 @@ static void Hook_PvPProjectileSpawnPost(int ent) SDKHook(ent, SDKHook_TouchPost, Hook_PvPProjectileBallOfFireTouchPost); } - if (g_PlayerPvPTimer[ownerEntity]==null) + if (g_PlayerPvPTimer[ownerEntity] == null) { SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); @@ -485,39 +495,39 @@ static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntit } } -void PvP_OnPlayerSpawn(int client) +static void OnPlayerSpawn(SF2_BasePlayer client) { if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) { return; } - PvP_SetPlayerPvPState(client, false, false, false); + PvP_SetPlayerPvPState(client.index, false, false, false); - g_PlayerIsLeavingPvP[client] = false; + g_PlayerIsLeavingPvP[client.index] = false; - if (IsPlayerAlive(client) && IsClientParticipating(client)) + if (client.IsAlive && client.IsParticipating) { - if (!IsClientInGhostMode(client) && !g_PlayerProxy[client]) + if (!client.IsInGhostMode && !client.IsProxy) { - if (g_PlayerEliminated[client] || DidClientEscape(client)) + if (client.IsEliminated || client.HasEscaped) { - bool autoSpawn = g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn; + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; if (autoSpawn) { - g_PlayerPvPRespawnTimer[client] = CreateTimer(0.12, Timer_TeleportPlayerToPvP, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.12, Timer_TeleportPlayerToPvP, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } } else { - g_PlayerPvPRespawnTimer[client] = null; + g_PlayerPvPRespawnTimer[client.index] = null; } } } } -void PvP_ZapProjectile(int projectile,bool bEffects=true) +void PvP_ZapProjectile(int projectile, bool effects=true) { if (!IsValidEntity(projectile)) { @@ -525,7 +535,7 @@ void PvP_ZapProjectile(int projectile,bool bEffects=true) } //Add zap effects - if (bEffects) + if (effects) { float pos[3]; GetEntPropVector(projectile, Prop_Send, "m_vecOrigin", pos); @@ -540,30 +550,32 @@ void PvP_ZapProjectile(int projectile,bool bEffects=true) RemoveEntity(projectile); } -void PvP_OnPlayerDeath(int client, bool fake) +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { if (!fake) { - if (IsClientInPvP(client)) + if (client.IsInPvP) { - g_PlayerIsLeavingPvP[client] = false; + g_PlayerIsLeavingPvP[client.index] = false; } - if (!IsClientInGhostMode(client) && !g_PlayerProxy[client]) + if (!client.IsInGhostMode && !client.IsProxy) { - bool autoSpawn = g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn; + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; if (autoSpawn) { - if (g_PlayerEliminated[client] || DidClientEscape(client)) + if (client.IsEliminated || client.HasEscaped) { if (!IsRoundEnding()) { - g_PlayerPvPRespawnTimer[client] = CreateTimer(0.3, Timer_RespawnPlayer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.3, Timer_RespawnPlayer, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } } } } + + PvP_SetPlayerPvPState(client.index, false, false, false); } } @@ -638,7 +650,7 @@ void PvP_OnTriggerStartTouch(int trigger,int other) } } -void PvP_OnTriggerEndTouch(int trigger,int other) +void PvP_OnTriggerEndTouch(int trigger, int other) { if (IsValidClient(other)) { @@ -677,13 +689,13 @@ void PvP_OnTriggerEndTouch(int trigger,int other) } } - //A projectile went off pvp area. (Experimental) + //A projectile went off pvp area. if (other > MaxClients && IsValidEntity(other)) { //Get entity's classname. char classname[50]; - GetEntityClassname(other,classname,sizeof(classname)); - for (int i = 0; i < (sizeof(g_PvPProjectileClasses)-4); i++) + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) { if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) { @@ -691,40 +703,36 @@ void PvP_OnTriggerEndTouch(int trigger,int other) { //Yup it's a projectile zap it! //But we have to wait to prevent some bugs. - CreateTimer(0.1,EntityStillAlive,other,TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); } } } } } -static void PvP_OnTriggerStartTouchBoxing(int trigger,int other) +static void PvP_OnTriggerStartTouchBoxing(int trigger, int other) { - //A projectile went in the area. (Experimental) if (other > MaxClients && IsValidEntity(other) && !IsRoundInWarmup() && !IsRoundEnding()) { - //Get entity's classname. char classname[50]; - GetEntityClassname(other,classname,sizeof(classname)); - for (int i = 0; i < (sizeof(g_PvPProjectileClasses)-4); i++) + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) { if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) { if (!GetEntProp(other, Prop_Send, "m_iDeflected")) { - //Yup it's a projectile zap it! - //But we have to wait to prevent some bugs. - CreateTimer(0.1,EntityStillAlive,other,TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); } } } } } -static Action EntityStillAlive(Handle timer, int iRef) +static Action EntityStillAlive(Handle timer, int ref) { - int ent = EntRefToEntIndex(iRef); - if (ent > MaxClients) + int ent = EntRefToEntIndex(ref); + if (ent && ent != INVALID_ENT_REFERENCE) { PvP_ZapProjectile(ent); } @@ -735,66 +743,68 @@ static Action EntityStillAlive(Handle timer, int iRef) */ void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles=true, bool regenerate=true) { - if (!IsValidClient(client)) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) { return; } - bool oldInPvP = g_PlayerInPvP[client]; + bool oldInPvP = g_PlayerInPvP[player.index]; if (status == oldInPvP) { return; // no change } - g_PlayerInPvP[client] = status; - g_PlayerPvPTimer[client] = null; - g_PlayerPvPRespawnTimer[client] = null; - g_PlayerPvPTimerCount[client] = 0; + g_PlayerInPvP[player.index] = status; + g_PlayerPvPTimer[player.index] = null; + g_PlayerPvPRespawnTimer[player.index] = null; + g_PlayerPvPTimerCount[player.index] = 0; if (removeProjectiles) { // Remove previous projectiles. - PvP_RemovePlayerProjectiles(client); + PvP_RemovePlayerProjectiles(player.index); } if (regenerate) { // Regenerate player but keep health the same. - int health = GetEntProp(client, Prop_Send, "m_iHealth"); - TF2_RemoveWeaponSlot(client, TFWeaponSlot_Primary); - TF2_RemoveWeaponSlot(client, TFWeaponSlot_Secondary); - TF2_RegeneratePlayer(client); - SetEntProp(client, Prop_Data, "m_iHealth", health); - SetEntProp(client, Prop_Send, "m_iHealth", health); + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); } } -static void PvP_OnFlameEntityStartTouchPost(int flame,int other) //Thanks Fire +static void PvP_OnFlameEntityStartTouchPost(int flame, int other) //Thanks Fire { - static float flasthit[MAXPLAYERS+1]; + static float lastHit[MAXPLAYERS + 1]; - if (IsValidClient(other)) + SF2_BasePlayer client = SF2_BasePlayer(other); + if (client.IsValid) { float time = GetEngineTime(); - if (flasthit[other] < time) + if (lastHit[client.index] < time) { - flasthit[other] = time + FLAME_HIT_DELAY; + lastHit[client.index] = time + FLAME_HIT_DELAY; - if ((IsRoundInWarmup() || IsClientInPvP(other)) && !IsRoundEnding()) + if ((IsRoundInWarmup() || client.IsInPvP) && !IsRoundEnding()) { - int iFlamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); - if (IsValidEdict(iFlamethrower)) + int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); + if (IsValidEdict(flamethrower)) { - int ownerEntity = GetEntPropEnt(iFlamethrower, Prop_Data, "m_hOwnerEntity"); - if (ownerEntity != other && IsValidClient(ownerEntity)) + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.index != client.index && ownerEntity.IsValid) { - if (IsRoundInWarmup() || IsClientInPvP(ownerEntity)) + if (IsRoundInWarmup() || ownerEntity.IsInPvP) { - if (GetClientTeam(other) == GetClientTeam(ownerEntity) && GetClientTeam(ownerEntity) != TFTeam_Red) + if (client.Team == ownerEntity.Team && ownerEntity.Team != TFTeam_Red) { //TF2_MakeBleed(other, ownerEntity, 4.0); - TF2_IgnitePlayer(other, ownerEntity); - SDKHooks_TakeDamage(other, ownerEntity, ownerEntity, 10.0, IsClientCritBoosted(ownerEntity) ? (DMG_BURN | DMG_PREVENT_PHYSICS_FORCE | DMG_ACID) : DMG_BURN | DMG_PREVENT_PHYSICS_FORCE); + client.Ignite(_, ownerEntity.index); + client.TakeDamage(_, ownerEntity.index, ownerEntity.index, 10.0, ownerEntity.IsCritBoosted() ? (DMG_BURN | DMG_PREVENT_PHYSICS_FORCE | DMG_ACID) : DMG_BURN | DMG_PREVENT_PHYSICS_FORCE); } } } @@ -1072,8 +1082,8 @@ MRESReturn PvP_GetWeaponCustomDamageType(int weapon, int client, int &customDama "tf_weapon_sniperrifle_classic" }; - char sWeaponName[256]; - GetEntityClassname(weapon, sWeaponName, sizeof(sWeaponName)); + char weaponName[256]; + GetEntityClassname(weapon, weaponName, sizeof(weaponName)); /* * Fixes the sniper rifle not damaging teammates. @@ -1091,12 +1101,12 @@ MRESReturn PvP_GetWeaponCustomDamageType(int weapon, int client, int &customDama */ for (int i = 0; i < sizeof(fixWeaponPenetrationClasses); i++) { - if (strcmp(sWeaponName, fixWeaponPenetrationClasses[i], false) == 0) + if (strcmp(weaponName, fixWeaponPenetrationClasses[i], false) == 0) { customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS int itemDefIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); - if ((itemDefIndex == 526 || itemDefIndex == 30665) && GetEntPropFloat(weapon, Prop_Send, "m_flChargedDamage") >= 150.0 ) // The Machina, Shooting Star + if ((itemDefIndex == 526 || itemDefIndex == 30665) && GetEntPropFloat(weapon, Prop_Send, "m_flChargedDamage") >= 150.0) // The Machina, Shooting Star { customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS } diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index f898eced..d3277644 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -25,6 +25,237 @@ static bool g_Started = false; static int doublerouletteCount = 0; static int g_SpecialRoundType = 0; +void SetupSpecialRounds() +{ + g_OnRoundEndPFwd.AddFunction(null, OnRoundEnd); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerTeamPFwd.AddFunction(null, OnPlayerTeam); + g_OnPlayerClassPFwd.AddFunction(null, OnPlayerClass); +} + +static void OnRoundEnd() +{ + g_Started = false; + SF_RemoveAllSpecialRound(); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (client.IsEliminated) + { + return; + } + + if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) + { + TF2_StripContrackerOnly(client.index); + } + + if (SF_SpecialRound(SPECIALROUND_1UP) && !g_PlayerIn1UpCondition[client.index] && !g_PlayerDied1Up[client.index]) + { + g_PlayerDied1Up[client.index] = false; + g_PlayerIn1UpCondition[client.index] = true; + g_PlayerFullyDied1Up[client.index] = false; + } + + if (SF_SpecialRound(SPECIALROUND_PAGEDETECTOR)) + { + ClientSetSpecialRoundTimer(client.index, 0.0, Timer_ClientPageDetector, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !client.HasEscaped) + { + ChangeThanatophobiaClass(client); + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) + { + return; + } + + if (SF_SpecialRound(SPECIALROUND_MULTIEFFECT)) + { + CreateTimer(0.1, Timer_ReplacePlayerRagdoll, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !client.HasEscaped) + { + for (int reds = 1; reds < MaxClients; reds++) + { + if (!IsValidClient(reds) || + g_PlayerEliminated[reds] || + DidClientEscape(reds) || + GetClientTeam(reds) != TFTeam_Red || + !IsPlayerAlive(reds)) + { + continue; + } + int randomNegative = GetRandomInt(1, 5); + switch (randomNegative) + { + case 1: + { + TF2_MakeBleed(reds, reds, 4.0); + EmitSoundToClient(reds, BLEED_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + case 2: + { + TF2_AddCondition(reds, TFCond_Jarated, 5.0); + EmitSoundToClient(reds, JARATE_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + case 3: + { + TF2_AddCondition(reds, TFCond_Gas, 5.0); + EmitSoundToClient(reds, GAS_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + case 4: + { + int maxHealth = SDKCall(g_SDKGetMaxHealth, reds); + float damageToTake = float(maxHealth) / 10.0; + SDKHooks_TakeDamage(reds, reds, reds, damageToTake, 128, _, { 0.0, 0.0, 0.0 } ); + } + case 5: + { + TF2_AddCondition(reds, TFCond_MarkedForDeath, 5.0); + } + } + } + } +} + +static void OnPlayerTeam(SF2_BasePlayer client, int team) +{ + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !client.IsEliminated && team == TFTeam_Red && + !client.HasEscaped) + { + ChangeThanatophobiaClass(client); + } +} + +static void OnPlayerClass(SF2_BasePlayer client) +{ + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && !client.IsEliminated && client.Team == TFTeam_Red && + !client.HasEscaped) + { + ChangeThanatophobiaClass(client); + } +} + +static void ChangeThanatophobiaClass(SF2_BasePlayer client) +{ + TFClassType class = client.Class; + int classToInt = view_as(class); + if (!IsClassConfigsValid()) + { + if (class == TFClass_Medic) + { + ShowVGUIPanel(client.index, "class_red"); + EmitSoundToClient(client.index, THANATOPHOBIA_MEDICNO); + TFClassType newClass; + int random = GetRandomInt(1, 8); + switch (random) + { + case 1: + { + newClass = TFClass_Scout; + } + case 2: + { + newClass = TFClass_Soldier; + } + case 3: + { + newClass = TFClass_Pyro; + } + case 4: + { + newClass = TFClass_DemoMan; + } + case 5: + { + newClass = TFClass_Heavy; + } + case 6: + { + newClass = TFClass_Engineer; + } + case 7: + { + newClass = TFClass_Sniper; + } + case 8: + { + newClass = TFClass_Spy; + } + } + client.SetClass(newClass); + client.Regenerate(); + } + } + else + { + if (g_ClassBlockedOnThanatophobia[classToInt]) + { + ShowVGUIPanel(client.index, "class_red"); + switch (class) + { + case TFClass_Scout: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SCOUTNO); + } + case TFClass_Soldier: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SOLDIERNO); + } + case TFClass_Pyro: + { + EmitSoundToClient(client.index, THANATOPHOBIA_PYRONO); + } + case TFClass_DemoMan: + { + EmitSoundToClient(client.index, THANATOPHOBIA_DEMOMANNO); + } + case TFClass_Heavy: + { + EmitSoundToClient(client.index, THANATOPHOBIA_HEAVYNO); + } + case TFClass_Engineer: + { + EmitSoundToClient(client.index, THANATOPHOBIA_ENGINEERNO); + } + case TFClass_Medic: + { + EmitSoundToClient(client.index, THANATOPHOBIA_MEDICNO); + } + case TFClass_Sniper: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SNIPERNO); + } + case TFClass_Spy: + { + EmitSoundToClient(client.index, THANATOPHOBIA_SPYNO); + } + } + ArrayList classArrays = new ArrayList(); + for (int i = 1; i < MAX_CLASSES + 1; i++) + { + if (!g_ClassBlockedOnThanatophobia[i]) + { + classArrays.Push(view_as(i)); + } + } + TFClassType newClass = classArrays.Get(GetRandomInt(0, classArrays.Length - 1)); + client.SetClass(newClass); + client.Regenerate(); + delete classArrays; + } + } +} + void ReloadSpecialRounds() { if (g_SpecialRoundCycleNames == null) @@ -365,7 +596,7 @@ static ArrayList SpecialEnabledList() char snatcher[64] = "hypersnatcher_nerfed"; int players; - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client]) { @@ -631,7 +862,7 @@ void SpecialRoundStart() } case SPECIALROUND_THANATOPHOBIA: { - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { if (!IsValidClient(client)) { @@ -816,7 +1047,7 @@ void SpecialRoundStart() } case SPECIALROUND_CLASSSCRAMBLE: { - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i) || g_PlayerEliminated[i] || GetClientTeam(i) != TFTeam_Red || DidClientEscape(i) || !IsPlayerAlive(i)) { @@ -861,10 +1092,7 @@ void SpecialRoundStart() } case 4: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Nightmare); @@ -872,10 +1100,7 @@ void SpecialRoundStart() } case 5: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Apollyon); @@ -950,10 +1175,7 @@ void SpecialRoundStart() } case 4: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Nightmare); @@ -961,10 +1183,7 @@ void SpecialRoundStart() } case 5: { - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Apollyon); @@ -1058,9 +1277,9 @@ void SpecialRoundStart() SF_RemoveSpecialRound(SPECIALROUND_LIGHTSOUT); SF_AddSpecialRound(SPECIALROUND_NIGHTVISION); } - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1140,7 +1359,7 @@ void SpecialRoundStart() SlenderAddGlow(npcIndex, purple); } } - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { if (!IsValidClient(i)) { @@ -1167,9 +1386,9 @@ void SpecialRoundStart() if (nightVision && g_NightvisionType != 1) { g_NightvisionType = 1; - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1191,9 +1410,9 @@ void SpecialRoundStart() } case SPECIALROUND_1UP: { - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1210,9 +1429,9 @@ void SpecialRoundStart() case SPECIALROUND_NOULTRAVISION: { SF_AddSpecialRound(SPECIALROUND_NOULTRAVISION); - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1266,9 +1485,9 @@ void SpecialRoundStart() } case SPECIALROUND_PAGEDETECTOR: { - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1303,9 +1522,9 @@ void SpecialRoundStart() } case SPECIALROUND_SINGLEPLAYER: { - for (int client = 1; client <= MaxClients; client++) + for (int client = 1; client < MaxClients; client++) { - if (IsValidClient(client) && IsClientInGame(client) && !g_PlayerEliminated[client] && !DidClientEscape(client)) + if (IsValidClient(client) && !g_PlayerEliminated[client] && !DidClientEscape(client)) { TF2_StripContrackerOnly(client); } @@ -1381,7 +1600,7 @@ static void SpecialCreateVote() NativeVotes_SetInitiator(voteMenu, NATIVEVOTES_SERVER_INDEX); char title[255]; - FormatEx(title,255,"%t%t","SF2 Prefix","SF2 Special Round Vote Menu Title"); + FormatEx(title, 255, "%t%t", "SF2 Prefix", "SF2 Special Round Vote Menu Title"); NativeVotes_SetDetails(voteMenu,title); ArrayList enabledRounds = SpecialEnabledList().Clone(); @@ -1571,9 +1790,9 @@ static void SpecialCreateVote() int total = 0; int[] players = new int[MaxClients]; - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { - if (!IsClientInGame(i)) + if (!IsValidClient(i)) { continue; } @@ -1614,7 +1833,7 @@ static int Menu_SpecialVote(Handle menu, MenuAction action,int param1,int param2 NativeVotes_GetItem(menu, param1, specialRound, sizeof(specialRound), specialRoundName, sizeof(specialRoundName)); CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Special Round Vote Successful", specialRoundName); - FormatEx(display,120,"%t","SF2 Special Round Vote Successful", specialRoundName); + FormatEx(display, 120, "%t", "SF2 Special Round Vote Successful", specialRoundName); g_SpecialRoundType = StringToInt(specialRound); g_SpecialRoundOverrideConVar.SetInt(g_SpecialRoundType); @@ -1630,12 +1849,6 @@ static int Menu_SpecialVote(Handle menu, MenuAction action,int param1,int param2 return 0; } -void SpecialRound_RoundEnd() -{ - g_Started = false; - SF_RemoveAllSpecialRound(); -} - void SpecialRoundReset() { g_SpecialRoundType = 0; diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index ad7923bb..c19f66bc 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -117,6 +117,15 @@ float HULL_HUMAN_MAXS[3] = { 13.0, 13.0, 72.0 }; float HULL_TF2PLAYER_MINS[3] = { -24.5, -24.5, 0.0 }; float HULL_TF2PLAYER_MAXS[3] = { 24.5, 24.5, 83.0 }; +// ========================================================== +// Overrides +// ========================================================== +bool TF2_IsPlayerInConditionEx(int client, TFCond condition) +{ + return g_ClientInCondition[client][condition]; +} +#define TF2_IsPlayerInCondition TF2_IsPlayerInConditionEx + // ========================================================== // Map Functions // ========================================================== @@ -262,7 +271,7 @@ float EntityDistanceFromEntity(int ent1, int ent2) return -1.0; } - float myPos[3],hisPos[3]; + float myPos[3], hisPos[3]; GetEntPropVector(ent1, Prop_Data, "m_vecAbsOrigin", myPos); GetEntPropVector(ent2, Prop_Data, "m_vecAbsOrigin", hisPos); return GetVectorSquareMagnitude(myPos, hisPos); @@ -408,15 +417,15 @@ int TF2_CreateGlow(int entIndex) char oldEntName[64]; GetEntPropString(entIndex, Prop_Data, "m_iName", oldEntName, sizeof(oldEntName)); - char strName[126], strClass[64]; - GetEntityClassname(entIndex, strClass, sizeof(strClass)); - FormatEx(strName, sizeof(strName), "%s%i", strClass, entIndex); - DispatchKeyValue(entIndex, "targetname", strName); + char name[126], class[64]; + GetEntityClassname(entIndex, class, sizeof(class)); + FormatEx(name, sizeof(name), "%s%i", class, entIndex); + DispatchKeyValue(entIndex, "targetname", name); int ent = CreateEntityByName("tf_glow"); - DispatchKeyValue(ent, "target", strName); - FormatEx(strName, sizeof(strName), "tf_glow_%i", entIndex); - DispatchKeyValue(ent, "targetname", strName); + DispatchKeyValue(ent, "target", name); + FormatEx(name, sizeof(name), "tf_glow_%i", entIndex); + DispatchKeyValue(ent, "targetname", name); DispatchKeyValue(ent, "Mode", "0"); DispatchSpawn(ent); @@ -432,7 +441,7 @@ int TF2_CreateGlow(int entIndex) // ========================================================== //Credits to Linux_lover for this and signature. -void SDK_PlaySpecificSequence(int client, const char[] strSequence) +void SDK_PlaySpecificSequence(int client, const char[] sequence) { if (g_SDKPlaySpecificSequence != null) { @@ -440,11 +449,11 @@ void SDK_PlaySpecificSequence(int client, const char[] strSequence) static bool once = true; if (once) { - PrintToServer("(SDK_PlaySpecificSequence) Calling on player %N \"%s\"..", client, strSequence); + PrintToServer("(SDK_PlaySpecificSequence) Calling on player %N \"%s\"..", client, sequence); once = false; } #endif - SDKCall(g_SDKPlaySpecificSequence, client, strSequence); + SDKCall(g_SDKPlaySpecificSequence, client, sequence); } } @@ -581,7 +590,7 @@ bool IsClientCritBoosted(int client) void TF2_StripWearables(int client) { - int entity = MaxClients+1; + int entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_wearable")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) @@ -590,7 +599,7 @@ void TF2_StripWearables(int client) } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_wearable_vm")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) @@ -599,7 +608,7 @@ void TF2_StripWearables(int client) } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_powerup_bottle")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) @@ -608,7 +617,7 @@ void TF2_StripWearables(int client) } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_wearable_razorback")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) @@ -620,7 +629,7 @@ void TF2_StripWearables(int client) void TF2_DestroySpyWeapons() { - int entity = MaxClients+1; + int entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_weapon_revolver")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) @@ -629,7 +638,7 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_weapon_builder")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) @@ -638,7 +647,7 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_weapon_knife")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) @@ -647,7 +656,7 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "saxxy")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) @@ -656,7 +665,7 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_weapon_pda_spy")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) @@ -665,7 +674,7 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_weapon_invis")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) @@ -674,7 +683,7 @@ void TF2_DestroySpyWeapons() } } - entity = MaxClients+1; + entity = MaxClients + 1; while((entity = FindEntityByClassname(entity, "tf_weapon_sapper")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) @@ -697,7 +706,7 @@ void ClientSwitchToWeaponSlot(int client,int slot) void ChangeClientTeamNoSuicide(int client,int team, bool respawn=true) { - if (!IsClientInGame(client)) + if (!IsClientInGameEx(client)) { return; } @@ -746,15 +755,20 @@ void UTIL_ScreenFade(int client,int duration,int time,int flags,int r,int g,int EndMessage(); } +bool IsClientInGameEx(int client) +{ + return g_ClientInGame[client]; +} + bool IsValidClient(int client) { - return client > 0 && client <= MaxClients && IsClientInGame(client); + return client > 0 && client <= MaxClients && IsClientInGameEx(client); } void PrintToSourceTV(const char[] message) { int client = GetClientOfUserId(g_SourceTVUserID); - if (MaxClients >= client > 0 && IsClientInGame(client) && IsClientSourceTV(client)) + if (MaxClients >= client > 0 && IsClientInGameEx(client) && IsClientSourceTV(client)) { CPrintToChat(client, message); } @@ -773,7 +787,9 @@ bool TF2_IsMiniCritBuffed(int client) bool IsTauntWep(int weaponEnt) { int index = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (index==37 || index==304 || index==5 || index==195 || index==43 || index==239 || index==310 || index==331 || index==426 || index==587 || index==656 || index==1084 || index==1100 || index == 1143) + if (index == 37 || index == 304 || index == 5 || index == 195 || index == 43 || + index == 239 || index == 310 || index == 331 || index == 426 || index == 587 || + index == 656 || index == 1084 || index == 1100 || index == 1143) { return true; } @@ -808,7 +824,7 @@ void ForceTeamWin(int team) AcceptEntityInput(ent, "SetWinner"); } -Handle PrepareItemHandle(char[] classname,int index,int level,int quality, char[] att) +Handle PrepareItemHandle(char[] classname, int index, int level, int quality, char[] att) { Handle item = TF2Items_CreateItem(OVERRIDE_ALL | FORCE_GENERATION); TF2Items_SetClassname(item, classname); @@ -856,10 +872,13 @@ void SpecialRoundGameText(const char[] message, const char[] icon = "") CreateTimer(2.0, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); } // Removes wearables such as botkillers from weapons. -void TF2_RemoveWeaponSlotAndWearables(int client,int slot) +void TF2_RemoveWeaponSlotAndWearables(int client, int slot) { int weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!IsValidEntity(weaponEnt)) return; + if (!IsValidEntity(weaponEnt)) + { + return; + } int wearable = INVALID_ENT_REFERENCE; while ((wearable = FindEntityByClassname(wearable, "tf_wearable")) != -1) @@ -907,7 +926,7 @@ void TF2_StripContrackerOnly(int client) } } -void TE_Particle(int particleIndex, float origin[3]=NULL_VECTOR, float start[3]=NULL_VECTOR, float angles[3]=NULL_VECTOR, int entindex=-1, int attachtype=-1, int attachpoint=-1, bool resetParticles=true) +void TE_Particle(int particleIndex, float origin[3] = NULL_VECTOR, float start[3] = NULL_VECTOR, float angles[3] = NULL_VECTOR, int entindex = -1, int attachtype = -1, int attachpoint = -1, bool resetParticles = true) { TE_Start("TFParticleEffect"); TE_WriteFloat("m_vecOrigin[0]", origin[0]); @@ -935,7 +954,7 @@ void UTIL_ScreenShake(float center[3], float amplitude, float frequency, float d { for(int i=1; i<=MaxClients; i++) { - if (IsClientInGame(i) && !IsFakeClient(i) && !IsClientInGhostMode(i)) + if (IsClientInGameEx(i) && !IsFakeClient(i) && !IsClientInGhostMode(i)) { if (!airShake && command == 0 && !(GetEntityFlags(i) && FL_ONGROUND)) { @@ -997,7 +1016,7 @@ float ComputeShakeAmplitude(float center[3], float playerPos[3], float amplitude /** * Converts a given timestamp into hours, minutes, and seconds. */ -void FloatToTimeHMS(float time,int &h=0,int &m=0,int &s=0) +void FloatToTimeHMS(float time, int &h=0, int &m=0, int &s=0) { s = RoundFloat(time); h = s / 3600; @@ -1006,7 +1025,7 @@ void FloatToTimeHMS(float time,int &h=0,int &m=0,int &s=0) s = s % 60; } -int FixedUnsigned16(float value,int scale) +int FixedUnsigned16(float value, int scale) { int output; @@ -1111,7 +1130,7 @@ void GetPositionForward(float pos[3], float ang[3], float returnValue[3], float float dir[3]; GetAngleVectors(ang, dir, NULL_VECTOR, NULL_VECTOR); returnValue = pos; - for(int i=0; i<3; i++) + for(int i = 0; i < 3; i++) { returnValue[i] += dir[i] * distance; } @@ -1171,7 +1190,7 @@ float GetAngleBetweenVectors(const float vector1[3], const float vector2[3], con NormalizeVector(direction, direction_n); NormalizeVector(vector1, vector1_n); NormalizeVector(vector2, vector2_n); - float degree = ArcCosine(GetVectorDotProduct( vector1_n, vector2_n )) * 57.29577951; + float degree = ArcCosine(GetVectorDotProduct(vector1_n, vector2_n)) * 57.29577951; GetVectorCrossProduct(vector1_n, vector2_n, cross); if (GetVectorDotProduct(cross, direction_n) < 0.0) @@ -1212,60 +1231,45 @@ void RotateYaw(float angles[3], float degree) // TRACE FUNCTIONS // ========================================================== -bool TraceRayDontHitEntity(int entity,int mask,any data) +bool TraceRayDontHitEntity(int entity, int mask, any data) { if (entity == data) { return false; } - if (IsValidEntity(entity)) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc")) - { - return false; - } + return false; } return true; } -bool TraceRayDontHitPlayers(int entity,int mask, any data) +bool TraceRayDontHitPlayers(int entity, int mask, any data) { - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { return false; } - if (IsValidEntity(entity)) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc")) - { - return false; - } + return false; } return true; } -bool TraceRayDontHitPlayersOrEntity(int entity,int mask,any data) +bool TraceRayDontHitPlayersOrEntity(int entity, int mask, any data) { if (entity == data) { return false; } - if (entity > 0 && entity <= MaxClients) + if (IsValidClient(entity)) { return false; } - if (IsValidEntity(entity)) + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) { - char class[64]; - GetEntityClassname(entity, class, sizeof(class)); - if (strcmp(class, "base_npc")) - { - return false; - } + return false; } return true; @@ -1277,11 +1281,7 @@ bool TraceRayDontHitPlayersOrEntity(int entity,int mask,any data) Action Timer_KillEntity(Handle timer, any entref) { int ent = EntRefToEntIndex(entref); - if (ent == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - if (!IsValidEntity(ent)) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } @@ -1294,7 +1294,7 @@ Action Timer_KillEntity(Handle timer, any entref) Action Timer_KillEdict(Handle timer, any entref) { int ent = EntRefToEntIndex(entref); - if (!IsValidEdict(ent)) + if (!ent || ent == INVALID_ENT_REFERENCE) { return Plugin_Stop; } @@ -1320,9 +1320,9 @@ bool SF_SpecialRound(int specialRound) { return false; } - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { - if (specialRound==g_ArraySpecialRoundType[array]) + if (specialRound == g_ArraySpecialRoundType[array]) { return true; } @@ -1332,7 +1332,7 @@ bool SF_SpecialRound(int specialRound) void SF_AddSpecialRound(int specialRound) { - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { if (g_ArraySpecialRoundType[array] == 0 || g_ArraySpecialRoundType[array] == specialRound) { @@ -1344,28 +1344,11 @@ void SF_AddSpecialRound(int specialRound) void SF_RemoveSpecialRound(int specialRound) { - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { if (g_ArraySpecialRoundType[array] == specialRound) { g_ArraySpecialRoundType[array] = 0; - //Useless - /*if (array != (SPECIALROUND_MAXROUNDS-1)) - { - for (int iArray2 = array;iArray2 < SPECIALROUND_MAXROUNDS; iArray2++) - { - if (g_ArraySpecialRoundType[iArray2+1] != 0) - { - g_ArraySpecialRoundType[iArray2] = g_ArraySpecialRoundType[iArray2+1]; - g_ArraySpecialRoundType[iArray2+1] = 0; - } - else - { - g_ArraySpecialRoundType[iArray2] = 0; - break; - } - } - }*/ break; } } @@ -1373,7 +1356,7 @@ void SF_RemoveSpecialRound(int specialRound) void SF_RemoveAllSpecialRound() { - for (int array = 0;array < SPECIALROUND_MAXROUNDS; array++) + for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { g_ArraySpecialRoundType[array] = 0; } @@ -1514,4 +1497,12 @@ MRESReturn Hook_GlowUpdateTransmitState(int glow, DHookReturn returnHook) { returnHook.Value = SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); return MRES_Supercede; +} + +void PlayNightmareSound() +{ + for (int i = 0; i < sizeof(g_SoundNightmareMode); i++) + { + EmitSoundToAll(g_SoundNightmareMode[i]); + } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index 5fc9d841..a1d9f33e 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -15,6 +15,29 @@ static bool g_TrapAnimChange[2049]; static Handle g_TrapTimer[2049]; //State 0 = Idle, State 1 = Closed +void SetupTraps() +{ + g_OnPlayerJumpPFwd.AddFunction(null, OnJump); +} + +static void OnJump(SF2_BasePlayer client) +{ + if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped) + { + return; + } + + if (client.IsTrapped) + { + client.TrapCount -= 1; + } + if (client.IsTrapped && client.TrapCount <= 1) + { + client.IsTrapped = false; + client.TrapCount = 0; + } +} + void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) { int slender = NPCGetEntIndex(bossIndex); @@ -129,7 +152,7 @@ static Action Timer_TrapThink(Handle timer, any entref) if (!g_TrapClosed[trapEntity]) { - for (int i = 1; i <= MaxClients; i++) + for (int i = 1; i < MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); if (!player.IsValid || @@ -142,7 +165,7 @@ static Action Timer_TrapThink(Handle timer, any entref) } float entPos[3], otherPos[3]; - player.GetPropVector(Prop_Data, "m_vecAbsOrigin", otherPos); + player.GetAbsOrigin(otherPos); GetEntPropVector(trapEntity, Prop_Data, "m_vecAbsOrigin", entPos); float zPos = otherPos[2] - entPos[2]; float distance = GetVectorSquareMagnitude(otherPos, entPos); @@ -171,7 +194,7 @@ static Action Timer_TrapThink(Handle timer, any entref) { player.ShowHint(PlayerHint_Trap); } - SDKHooks_TakeDamage(player.index, player.index, player.index, 10.0, 128); + player.TakeDamage(true, _, _, 10.0, 128); g_TrapState[trapEntity] = 1; g_TrapAnimChange[trapEntity] = true; int bossIndex = g_TrapMaster[trapEntity]; @@ -232,30 +255,7 @@ static void OnTrapOpenComplete(const char[] output, int caller, int activator, f g_TrapDoIdleAnim[caller] = true; } } -/* -public Action Hook_TrapTouch(int trapEntity, int client) -{ - if (MaxClients >= client > 0 && IsClientInGame(client)) - { - if (!g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red && !g_TrapClosed[trapEntity]) - { - g_PlayerTrapped[client] = true; - if (!g_PlayerHints[client][PlayerHint_Trap]) - { - ClientShowHint(client, PlayerHint_Trap); - } - SDKHooks_TakeDamage(client, client, client, 10.0, 128); - g_PlayerTrapCount[client] = GetRandomInt(2, 4); - g_TrapClosed[trapEntity] = true; - g_TrapState[trapEntity] = 1; - EmitSoundToAll(TRAP_CLOSE, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - AcceptEntityInput(trapEntity, "DisableCollision"); - } - if (IsClientInGhostMode(client)) return Plugin_Handled; - } - return Plugin_Continue; -} -*/ + static Action Hook_TrapOnTakeDamage(int trapEntity,int &attacker,int &inflictor,float &damage,int &damagetype,int &weapon, float damageForce[3], float damagePosition[3],int damagecustom) { if (!g_Enabled) From 3131410cee49e1837521ae30265aad8973ed1db7 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sat, 1 Apr 2023 20:49:34 -0700 Subject: [PATCH 02/86] Fixed peeking --- addons/sourcemod/scripting/sf2/client.sp | 2 +- addons/sourcemod/scripting/sf2/client/peek.sp | 7 +++++-- addons/sourcemod/scripting/sf2/client/sprint.sp | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index 5c0d50c2..d7309762 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -39,9 +39,9 @@ int g_ClientFrame[MAXTF2PLAYERS]; #include "sf2/client/deathcam.sp" #include "sf2/client/ultravision.sp" #include "sf2/client/flashlight.sp" +#include "sf2/client/peek.sp" #include "sf2/client/sprint.sp" #include "sf2/client/breathing.sp" -#include "sf2/client/peek.sp" #include "sf2/client/ghostmode.sp" #include "sf2/client/music.sp" #include "sf2/client/proxy.sp" diff --git a/addons/sourcemod/scripting/sf2/client/peek.sp b/addons/sourcemod/scripting/sf2/client/peek.sp index 90ef4e29..b191ee64 100644 --- a/addons/sourcemod/scripting/sf2/client/peek.sp +++ b/addons/sourcemod/scripting/sf2/client/peek.sp @@ -1,7 +1,7 @@ #pragma semicolon 1 //Peeking Data -static bool g_PlayerPeeking[MAXTF2PLAYERS] = { false, ... }; +bool g_PlayerPeeking[MAXTF2PLAYERS] = { false, ... }; /** * Handles thirdperson peeking @@ -10,7 +10,8 @@ bool ClientStartPeeking(int client) { if (!g_PlayerPeeking[client] && g_AllowPlayerPeekingConVar.BoolValue && !TF2_IsPlayerInCondition(client, TFCond_Dazed) && GetClientButtons(client) & IN_DUCK) { - TF2_StunPlayer(client, -1.0, 1.0, TF_STUNFLAGS_LOSERSTATE); + SetVariantInt(1); + AcceptEntityInput(client, "SetForcedTauntCam"); g_PlayerPeeking[client] = true; return true; } @@ -21,6 +22,8 @@ void ClientEndPeeking(int client) { if (g_PlayerPeeking[client]) { + SetVariantInt(0); + AcceptEntityInput(client, "SetForcedTauntCam"); TF2_RemoveCondition(client, TFCond_Dazed); g_PlayerPeeking[client] = false; } diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp index c1d19ee2..3b9f8f0f 100644 --- a/addons/sourcemod/scripting/sf2/client/sprint.sp +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -635,7 +635,7 @@ static void Hook_SprintThink(int client) } } - if (player.IsTrapped) + if (player.IsTrapped || g_PlayerPeeking[player.index]) { player.SetPropFloat(Prop_Send, "m_flMaxspeed", 0.1); player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 0.1); From b432d5cfb6df500e019099c4b462bb72ef9960b2 Mon Sep 17 00:00:00 2001 From: Sandust60 <96904513+M60TM@users.noreply.github.com> Date: Sun, 2 Apr 2023 14:45:16 +0900 Subject: [PATCH 03/86] Add "proxies_max_speed" key (#111) --- .../include/sf2/profiles/profiles.inc | 4 + .../sourcemod/scripting/sf2/client/think.sp | 94 +++++++++++++------ .../sf2/profiles/profiles_boss_functions.sp | 20 +++- 3 files changed, 87 insertions(+), 31 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 81632ffa..85300976 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -1615,6 +1615,8 @@ enum struct SF2BossProfileData ArrayList ProxyModelsInsane[10]; ArrayList ProxyModelsNightmare[10]; ArrayList ProxyModelsApollyon[10]; + bool ProxyOverrideMaxSpeed; + float ProxyMaxSpeed[Difficulty_Max]; bool FakeCopies; @@ -1823,6 +1825,7 @@ enum struct SF2BossProfileData this.ProxyDeathAnimations = null; this.ProxyZombies = false; this.ProxyDifficultyModels = false; + this.ProxyOverrideMaxSpeed = false; this.FakeCopies = false; @@ -1979,6 +1982,7 @@ enum struct SF2BossProfileData this.ProxySpawnCooldownMax[difficulty] = difficulty > 0 ? this.ProxySpawnCooldownMax[difficulty - 1] : 0.0; this.ProxyTeleportRangeMin[difficulty] = difficulty > 0 ? this.ProxyTeleportRangeMin[difficulty - 1] : 500.0; this.ProxyTeleportRangeMax[difficulty] = difficulty > 0 ? this.ProxyTeleportRangeMax[difficulty - 1] : 3200.0; + this.ProxyMaxSpeed[difficulty] = difficulty > 0 ? this.ProxyMaxSpeed[difficulty - 1] : 230.0; this.DrainCreditAmount[difficulty] = difficulty > 0 ? this.DrainCreditAmount[difficulty - 1] : 50; } diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index aa5ef6d1..22a3f0c6 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -88,49 +88,83 @@ void Hook_ClientPreThink(int client) } else if (g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Blue) { + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); + + float maxSpeed; + bool override; + + if (master != -1) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(master, profile, sizeof(profile)); + + override = GetBossProfileProxyOverrideMaxSpeed(profile); + if (override) + { + int difficulty = GetLocalGlobalDifficulty(master); + + maxSpeed = GetBossProfileProxyMaxSpeed(profile, difficulty); + } + } + bool speedup = TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly); - switch (class) + if (override) { - case TFClass_Scout: + if (speedup || g_InProxySurvivalRageMode) { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 390.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } + float rageSpeed = maxSpeed + 30.0; + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); } - case TFClass_Medic: + else { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", maxSpeed); } - case TFClass_Spy: + } + else + { + switch (class) { - if (speedup || g_InProxySurvivalRageMode) + case TFClass_Scout: { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 390.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } } - else + case TFClass_Medic: { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } } - } - default: - { - if (g_InProxySurvivalRageMode) + case TFClass_Spy: { - float rageSpeed = ClientGetDefaultSprintSpeed(client) + 30.0; - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); + if (speedup || g_InProxySurvivalRageMode) + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); + } + else + { + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); + } + } + default: + { + if (g_InProxySurvivalRageMode) + { + float rageSpeed = ClientGetDefaultSprintSpeed(client) + 30.0; + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); + } } } } diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 6e88f0a1..8f66cd07 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -690,6 +690,12 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } } + + profileData.ProxyOverrideMaxSpeed = !!kv.GetNum("proxies_override_max_speed", profileData.ProxyOverrideMaxSpeed); + if (profileData.ProxyOverrideMaxSpeed) + { + GetProfileDifficultyFloatValues(kv, "proxies_max_speed", profileData.ProxyMaxSpeed, profileData.ProxyMaxSpeed); + } } UnloadBossProfile(profile); @@ -1843,6 +1849,18 @@ ArrayList GetBossProfileProxyModels(const char[] profile, int index, int difficu return g_CachedProfileData.ProxyModels[index]; } +bool GetBossProfileProxyOverrideMaxSpeed(const char[] profile) +{ + g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); + return g_CachedProfileData.ProxyOverrideMaxSpeed; +} + +float GetBossProfileProxyMaxSpeed(const char[] profile, int difficulty) +{ + g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); + return g_CachedProfileData.ProxyMaxSpeed[difficulty]; +} + float GetBossProfileFOV(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -2409,4 +2427,4 @@ ArrayList GetBossProfileEffectsArray(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.EffectsArray; -} +} \ No newline at end of file From 542a6da1950e26cbf7e4904765651a343a069dab Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:49:46 -0700 Subject: [PATCH 04/86] Fixes -Fixed g_VoteTimer being triggered after 5 seconds instead of 1 -Fixed SF2_BasePlayer returning not connected clients as -1 instead of the original client index --- addons/sourcemod/scripting/sf2.sp | 2 +- addons/sourcemod/scripting/sf2/client/think.sp | 4 ++-- addons/sourcemod/scripting/sf2/methodmaps.sp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index b5bc3d03..c1429a9c 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -6726,7 +6726,7 @@ static Action Timer_RoundStart(Handle timer) { if (clientsNum) { - g_VoteTimer = CreateTimer(5.0, Timer_VoteDifficulty, arrayClients, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_VoteTimer = CreateTimer(1.0, Timer_VoteDifficulty, arrayClients, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); TriggerTimer(g_VoteTimer, true); int gameText = -1; diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index 22a3f0c6..853f2aca 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -106,7 +106,7 @@ void Hook_ClientPreThink(int client) maxSpeed = GetBossProfileProxyMaxSpeed(profile, difficulty); } } - + bool speedup = TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly); if (override) @@ -989,4 +989,4 @@ Action Timer_ClientAverageUpdate(Handle timer) } return Plugin_Continue; -} +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index e7069df7..f763c48a 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -399,7 +399,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { public SF2_BasePlayer(int client) { - if (!IsValidClient(client)) + if (client <= 0) { return view_as(SF2_INVALID_PLAYER); } From 2cf9e5623abc513c517231593394ae8d238e77ba Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:18:59 -0700 Subject: [PATCH 05/86] Logo replaced --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d70794af..19b7d142 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ Special thanks to: - x3m77rus + Blueberryy for making russian translations, and updating some part of the code to support more translation text. - Blad3z for making Finnish translations. -- Diviously for the new Slender Fortress 2 Modified logo +- Glacetomic for the new Slender Fortress 2 Modified logo # Slender Fortress Modified Versions Since Kit is busy with his real life, Kenzzer is no longer working on SF2, I used the new versions to create the new modified versions to allow much more diversity to SF2. -![SF2M-Logo](https://user-images.githubusercontent.com/42941613/106558495-1359bf00-64e1-11eb-99ac-5e028217622d.png) +![SF2M-Logo](https://user-images.githubusercontent.com/42941613/86555411-1e298280-bf05-11ea-8246-6d893d5aa5c7.png) Slender Fortress ================ From be3f4740f2bdd736103d076a1393a6e2b28e0219 Mon Sep 17 00:00:00 2001 From: Sandust60 <96904513+M60TM@users.noreply.github.com> Date: Wed, 5 Apr 2023 07:13:33 +0900 Subject: [PATCH 06/86] Add Korean Translation (#112) * Logo replaced * Add Korean Translation * Change some occurrences --------- Co-authored-by: Mentrillum <42941613+Mentrillum@users.noreply.github.com> --- addons/sourcemod/translations/sf2.phrases.txt | 271 ++++++++++++++++-- 1 file changed, 249 insertions(+), 22 deletions(-) diff --git a/addons/sourcemod/translations/sf2.phrases.txt b/addons/sourcemod/translations/sf2.phrases.txt index fdbd0b31..a4813aff 100644 --- a/addons/sourcemod/translations/sf2.phrases.txt +++ b/addons/sourcemod/translations/sf2.phrases.txt @@ -7,6 +7,7 @@ "fi" "Valitse vaikeustaso:" "chi" "选择难度:" "pt" "Selecione uma dificuldade:" + "ko" "난이도 선택:" } "SF2 Difficulty Vote Finished" @@ -16,6 +17,7 @@ "fi" "Äänestys onnistui! Vaikeustaso on muutettu tasoon:" "chi" "投票成功!难度已被改为:" "pt" "Votação finalizada! A dificuldade foi alterada para:" + "ko" "투표 성공! 선택된 난이도:" } "SF2 Easy Difficulty" @@ -25,6 +27,7 @@ "fi" "Helppo" "chi" "新手" "pt" "Fácil" + "ko" "쉬움" } "SF2 Normal Difficulty" @@ -34,6 +37,7 @@ "fi" "Tavallinen" "chi" "普通" "pt" "Normal" + "ko" "보통" } "SF2 Hard Difficulty" @@ -43,6 +47,7 @@ "fi" "Vaikea" "chi" "困难" "pt" "Hardcore" + "ko" "하드코어" } "SF2 Insane Difficulty" @@ -52,6 +57,7 @@ "fi" "Hullu" "chi" "疯狂" "pt" "Insana" + "ko" "광기" } "SF2 Nightmare Difficulty" @@ -59,24 +65,28 @@ "en" "Nightmare" "fi" "Painajainen" "pt" "Pesadelo" + "ko" "악몽" } "SF2 Apollyon Difficulty" { "en" "Apollyon" "pt" "Apoliom" + "ko" "아폴리온" } "SF2 Calamity Difficulty" { "en" "Calamity" "pt" "Calamidade" + "ko" "대재앙" } "SF2 Random Difficulty" { "en" "Random" "pt" "Aleatória" + "ko" "랜덤" } "SF2 Default Intro Message Singular" @@ -87,6 +97,7 @@ "fi" "Kerää {1} lappu" "chi" "收集 {1} 纸张" "pt" "Colete {1} página" + "ko" "단서 {1} 개 수집" } "SF2 Default Intro Message Plural" @@ -97,6 +108,7 @@ "fi" "Kerää kaikki {1} lappua" "chi" "收集所有 {1} 纸张" "pt" "Colete todas as {1} páginas" + "ko" "단서 {1} 개 모두 수집" } "SF2 Default Escape Message" @@ -106,6 +118,7 @@ "fi" "Pakene!" "chi" "逃出去!" "pt" "Fuja!" + "ko" "탈출하세요!" } "SF2 Default Survive Message" @@ -115,6 +128,7 @@ "fi" "Selviydy!" "chi" "活下来!" "pt" "Sobreviva!" + "ko" "살아남으세요!" } "SF2 Default Boxing Message" @@ -124,6 +138,7 @@ "fi" "Tapa Mestari!" "chi" "击杀冠军!" "pt" "Mate o campeão!" + "ko" "챔피언을 쓰러뜨리세요!" } "SF2 Grace Period End" @@ -133,13 +148,14 @@ "fi" "Lisäaika loppui. Kuolema johtaa nyt eliminointiin." "chi" "宽限期结束。死亡将会导致出局。" "pt" "O tempo de preparação acabou. Mortes resultarão em eliminação." - + "ko" "유예 기간이 끝났습니다. 이제 죽으면 탈락입니다." } "SF2 Grace Period Page" { "en" "Wait for Grace period to end to collect a page." "pt" "Aguardando o tempo de preparação acabar para coletar uma página." + "ko" "단서를 수집하기 위해선 유예 기간이 끝날 때까지 기다리세요." } "SF2 Player Escaped" @@ -150,6 +166,7 @@ "fi" "{dodgerblue}{1}{default} on paennut!" "chi" "{dodgerblue}{1}{default} 成功逃出了!" "pt" "{dodgerblue}{1}{default} escapou!" + "ko" "{dodgerblue}{1}{default}(이)가 탈출했습니다!" } "SF2 Player Killed Champion" @@ -160,6 +177,7 @@ "fi" "{red}{1}{default} on tappanut {valve}{2}!" "chi" "{red}{1}{default} 击杀了 {valve}{2}!" "pt" "{red}{1}{default} matou o {valve}{2}!" + "ko" "{red}{1}{default} (이)가 {valve}{2}{default}를 쓰러뜨렸습니다!" } "SF2 Camping System Warning" @@ -170,6 +188,7 @@ "fi" "VAROITUS: Jos et liiku paikaltasi {1} sekunttiin, vihollinen nappaa sinut." "chi" "警告:如果你在 {1} 秒内不从当前位置移动的话,你会被Boss抓住。" "pt" "AVISO: caso não se mova do seu lugar em {1} segundos, você será pego pelo chefão." + "ko" "경고: {1} 초 안에 해당 장소를 벗어나지 않으면, 죽게 됩니다." } "SF2 Main Menu Title" @@ -179,6 +198,7 @@ "fi" "Päävalikko" "chi" "主菜单" "pt" "Menu principal" + "ko" "메인 메뉴" } "SF2 Queue Menu Title" @@ -188,6 +208,7 @@ "fi" "Jonolista" "chi" "队列清单" "pt" "Fila de espera" + "ko" "대기열" } "SF2 Reset Queue Points Option" @@ -198,6 +219,7 @@ "fi" "Sinun pisteesi: {1} (valitse tyhjentääksesi)" "chi" "你的点数: {1} (选择此项重置)" "pt" "Seus pontos: {1} (selecione para zerar)" + "ko" "현재 포인트: {1} (누를시 포인트 초기화)" } "SF2 Should Reset Queue Points" @@ -207,6 +229,7 @@ "fi" "Haluatko varmasti tyhjentää pisteesi 0:aan?" "chi" "你真的要重置点数为0吗?" "pt" "Tem certeza de que deseja zerar os seus pontos de espera?" + "ko" "정말 대기열 포인트를 0으로 초기화 하시겠습니까?" } "SF2 Queue Points Reset" @@ -216,6 +239,7 @@ "fi" "Olet tyhjentänyt pisteesi 0:aan" "chi" "你已重置点数为0。" "pt" "Os seus pontos de espera foram zerados." + "ko" "대기열 포인트가 0으로 초기화 되었습니다." } "SF2 Ghost Mode Menu Title" @@ -225,6 +249,7 @@ "fi" "Muuta Haamu Tilaa:" "chi" "开关幽灵模式:" "pt" "Alternar modo fantasma:" + "ko" "유령 모드 토글:" } "SF2 Ghost Mode Enabled" @@ -234,6 +259,7 @@ "fi" "Olet nyt Haamu Tilassa. Käytä äänikomentoja teleporttaaksesi RED pelaajaan. Hiivi napauttaaksesi itsesi maahan ilmasta." "chi" "你现在处于幽灵模式。使用语言命令来传送到一个红队玩家。在空中按蹲来降落。" "pt" "Você está no modo fantasma. Use um comando de voz para teletransportar-se a um jogador da equipe RED. Pressione \"Agachar\" para jogar-se ao chão." + "ko" "유령 모드가 되었습니다. 음성 명령을 사용하면 RED 팀 플레이어에게 텔레포트 됩니다. 공중에 있는 동안 앉기 키를 누르면 빠르게 바닥으로 내려갑니다." } "SF2 Ghost Mode Disabled" @@ -243,6 +269,7 @@ "fi" "Olet ottanut Haamu Tilan pois." "chi" "你已关闭幽灵模式。" "pt" "Modo fantasma desativado." + "ko" "유령 모드가 비활성화되었습니다." } "SF2 Ghost Mode Not Allowed" @@ -252,6 +279,7 @@ "fi" "Et voi mennä Haamu Tilaan juuri nyt." "chi" "你现在不可以切换幽灵模式。" "pt" "Alternação para o modo fantasma indisponível." + "ko" "지금은 유령 모드를 사용할 수 없습니다." } "SF2 Ghost Mode Bad Connection" @@ -262,12 +290,14 @@ "fi" "Sinut potkittiin ulos Haamu Tilasta ajoituksen vuoksi {1} sekunniksi." "chi" "你因为超时了 {1} 秒而被移出幽灵模式。" "pt" "Você foi removido do modo fantasma devido ao tempo-limite de {1} segundo(s) ter acabado." + "ko" "{1}초 간의 연결 지연시간 초과로 인해 유령 모드가 해제되었습니다." } "SF2 Prefix" { "en" "[SF2]" "pt" "[SF2]" + "ko" "[SF2]" } "SF2 Help Menu Title" @@ -277,6 +307,7 @@ "fi" "Ohjevalikko" "chi" "帮助菜单" "pt" "Menu de ajuda" + "ko" "도움말" } "SF2 Help Objective Menu Title" @@ -286,6 +317,7 @@ "fi" "Tehtävä" "chi" "目标" "pt" "Objetivo" + "ko" "목표" } "SF2 Help Objective Description" @@ -295,6 +327,7 @@ "fi" "Slender Fortress 2 (SF2) on pelimuoto joka perustuu\n peliin \"Slender: The Eight Pages\" tehnyt Mark\nJ. Hadley. Tehtäväsi on kerätä kaikki tavarat\nkartalta ja paeta jäämättäsi kiinni\nviholliselle. Keräät lappuja lyömällä\nniitä lähitaistelu aseellasi." "chi" "暗鬼要塞2(SF2)是一个基于游戏\n \"瘦长鬼影:八页纸\" 作者Mark J.Hadley。\n游戏目标是收集地图内所有物品而不被敌人抓住\n使用近战物品攻击物品来收集。" "pt" "O Slender Fortress 2 (SF2) é um modo de jogo baseado no\njogo \"Slender: The Eight Pages\" feito por Mark J. Hadley.\nO objetivo é coletar todos os itens no mapa e escapar sem\nser pego pelos inimigos. Colete páginas ao bater nelas\ncom a sua arma corpo a corpo." + "ko" "슬렌더 포트리스 2 (SF2)는\nMark J. Hadley 가 만든 게임인\n\"Slender: The Eight Pages\"\n을 베이스로 한 게임모드입니다.\n적을 피해 단서를 모두 모으고 시설을 탈출 하세요.\n근접 공격을 통해 단서를 모을 수 있습니다." } "SF2 Help Objective Description 2" @@ -304,6 +337,7 @@ "fi" "Vain osa pelaajista valitaan RED:lle\npelaamaan peliä. Muut pelaajat joutuvat odottamaan\nvuoroaan BLU:lla kunnes on heidän vuoronsa.\nBLU pystyy katsomaan RED:iä reaaliaikaisesti\nkäyttäen komentoa !slghost." "chi" "只有一部分的蓝队玩家会被选入游戏\n剩下的玩家必须在蓝队等待直到轮到自己\n蓝队可以使用!slghost来实时观察红队\n而不用进入观察者队伍。" "pt" "Apenas uma certa quantidade de jogadores serão selecionados\nda equipe BLU para jogar. O resto dos jogadores terão\nde esperar na equipe BLU até que o seu turno chegue.\nJogadores da equipe BLU podem assistir os jogadores\nda equipe RED em tempo real com o comando \"!slghost\"\nsem ter que assisti-los no modo espectador." + "ko" "BLU 팀 플레이어 중에서 일정 인원 수만 선택되어 플레이하게 됩니다.\n나머지 플레이어는 대기실에서 다음 차례가 올 때까지 기다려야 합니다.\nBLU 팀은 관전자 팀에 들어갈 필요 없이\n!slghost 명령어를 통해 유령이 되어\n실시간으로 RED팀 상황을 볼 수 있습니다." } "SF2 Help Commands Menu Title" @@ -313,6 +347,7 @@ "fi" "Komentolista" "chi" "命令列表" "pt" "Lista de comandos" + "ko" "명령어 모음" } "SF2 Help Commands Description" @@ -322,6 +357,7 @@ "fi" "!slender - Näyttää päävalikon.\n!slnext - Näyttää listan seuraavista pelaajista.\n!slghost - Näyttää Haamu Tilan valikon.\n!slhelp - Näyttää ohjevalikon.\n!slcredits - Näyttää mahtavat henkilöt jotka auttoivat tämän pelimuodon kanssa." "chi" "!slender - 显示主菜单。\n!slnext - 显示列表谁是下一个玩家。\n!slghost - 显示幽灵模式菜单。\n!slhelp - 显示帮助菜单。\n!slcredits - 显示帮助制作此mod的人。" "pt" "!slender — exibe o menu principal.\n!slnext — exibe a fila de espera de quem vai jogar.\n!slghost — exibe o menu do modo fantasma.\n!slhelp — exibe o menu de ajuda.\n!slcredits — exibe os nomes de quem ajudou a desenvolver este modo." + "ko" "!slender - 게임모드의 메인메뉴를 보여줍니다.\n!slnext - 현재 대기열을 보여줍니다.\n!slghost - 유령 모드가 됩니다.\n!slhelp - 도움말을 보여줍니다.\n!slcredits - 어떤 개쩌는 녀셕들이 이 게임모드에 도움을 줬는지 알려줍니다." } "SF2 Help Ghost Mode Menu Title" @@ -331,6 +367,7 @@ "fi" "Mikä on Haamu Tila?" "chi" "什么是幽灵模式?" "pt" "O que é o modo fantasma?" + "ko" "유령 모드가 뭔가요?" } "SF2 Help Ghost Mode Description" @@ -340,6 +377,7 @@ "fi" "Haamu Tila on ominaisuus jolla BLU pelaajat\nvoivat katsoa RED pelaajia ilman että\ntarvitsee mennä Katsomoon. RED pelaajat eivät pysty\nnäkemään sinua, eivätkä pysty vaikuttaa\nsinuun. Sinä et myöskään pysty vaikuttamaan RED:iin." "chi" "幽灵模式是让蓝队可以实时观察红队但又不需要\n进入观察者的一项功能。红队无法看见你和交互。\n你也不能和红队进行交互。" "pt" "O modo fantasma é um recurso que jogadores da equipe BLU\npodem usar para assistir jogadores da RED sem ir para o\nmodo espectador. Jogadores da RED não poderão ver nem\ninteragir com você. Você também não pode interagir com\na equipe RED." + "ko" "유령 모드는 BLU 팀 플레이어가 관전자 팀으로 넘어가지 않고\nRED 팀 상황을 볼 수 있게 해줍니다.\nRED 팀 플레이어는 유령이 된 당신을 볼 수도 만질 수도 없습니다.\n또한 통상적으로는 얘기도 나눌 수 없습니다." } "SF2 Help Controls Menu Title" @@ -349,6 +387,7 @@ "fi" "Kontrollit" "chi" "控制" "pt" "Controles" + "ko" "조작법" } "SF2 Help Controls Description" @@ -358,6 +397,7 @@ "fi" "Ensisijainen hyökkäys - Kerää lappu\nToissijainen hyökkäys - Ottaa taskulampun käyttöön\nLataa - Räpäyttää\nErityis hyökkäys - Juokse" "chi" "主要攻击键 - 拾取物品\n次要攻击键 - 开关闪光灯\n装填弹药键 - 眨眼\n按住特殊攻击键 - 冲刺" "pt" "Ataque primário — coletar página\nAtaque secundário — alternar lanterna\nRecarregar — piscar\nSEGURAR \"ataque especial\" — correr" + "ko" "주 공격키 - 단서 수집\n보조 공격키 - 손전등 토글\n재장전키 - 눈 깜빡이기\n특수 공격키 누르기 - 달리기" } "SF2 Help Sprinting And Stamina Menu Title" @@ -367,6 +407,7 @@ "fi" "Juokseminen ja Kestävyys" "chi" "冲刺与耐力" "pt" "Corrida e energia" + "ko" "달리기와 스테미너" } "SF2 Help Sprinting And Stamina Description" @@ -376,6 +417,7 @@ "fi" "Sinulla on kyky juosta, jota pystyt käyttämään\npitämällä pojassa erikois hyökkäys nappiasi\n(oletuksena näppäin MOUSE3, jota voit vaihtaa asetuksista.\nKuitenkin, juokseminen ei kestä loputtomiin,\njoten on parasta säästää kestävyytesi hätätilanteisiin.\nMyös hyppiminen vähentää kestävyyttäsi\n \nLisäksi, juokseminen tekee enemmän ääntä\nkuin kävely tai kyykistely, joten juokseminen\nei ole vaihtoehto hiippailussa." "chi" "你可以通过按住特殊攻击键(默认鼠标中键)来冲刺。\n然而,冲刺需要耐力,因此最好在紧急情况下使用。\n跳跃也会消耗耐力。\n \n另外,冲刺比走路和蹲伏要发出更多的声音,\n因此假如你想隐蔽,那就不要冲刺。" "pt" "Você pode correr ao segurar o botão de ataque especial\n(MOUSE3 por padrão). (Use a bind \"+attack3\" para alterar\na vinculação.) Entretanto, a corrida não é infinita,\nentão é melhor conservar a sua energia para situações\nde emergência. Pular também diminui a sua energia.\n\nAlém disso, correr faz mais barulho do que andar ou\nagachar, então correr não é uma opção furtiva." + "ko" "달릴 수 있는 능력을 가지고 있으며, 특수 공격키를\n꾹 누르면 능력을 쓸 수 있습니다.\n(보통 마우스 휠키로 바인드 되있습니다)\n하지만 무한으로 달릴 수 없으며,\n때문에 아찔한 상황을 대비해 스테미너를 아껴야합니다.\n또한 점프를 해도 스테미너가 닳습니다.\n추가로 뛸 때는 걷거나 앉아서 갈 때보다\n더 큰 소음을 냅니다." } "SF2 Help Class Info Menu Title" @@ -385,6 +427,7 @@ "fi" "Luokkien tiedot" "chi" "兵种信息" "pt" "Informação das classes" + "ko" "클래스 정보" } "SF2 Help Class Info Description" @@ -394,6 +437,7 @@ "fi" "Valitse vaihtoehto nähdäksesi tietoja jokaisesta\npelaaja luokasta, mukaan lukien parannuksista ja heikennyksistä" "chi" "选择一个选项来查看对应兵种的信息,\n其中包括了增强与削弱。" "pt" "Selecione uma opção para ver as informações de\ncada classe, incluindo vantagens e desvantagens." + "ko" "버프와 너프를 포함한 각 클래스에 대한 정보를 보려면 옵션을 선택하세요." } "SF2 Help Scout Class Info Menu Title" @@ -403,6 +447,7 @@ "fi" "[1] Scout" "chi" "[1] 侦察兵" "pt" "[1] Scout" + "ko" "[1] 스카웃" } "SF2 Help Scout Class Info Description" @@ -412,6 +457,7 @@ "fi" "[+] 15%% lisäys juoksun kestoon\n[-] Heikko terveys, ottaa eniten vaikustusta näytön keikkumisesta" "chi" "[+] 增加 15%% 冲刺持续时间\n[-] 低生命值,更容易受到屏幕震动效果" "pt" "[+] Aumento de 15%% na duração da corrida\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "ko" "[+] 8% 더 많은 스테미너 양\n[+] 기본 달리기 속도가 제일 빠름\n[-] 소음이 보스에게 20%% 더 크게 들림\n[-] 체력 낮음\n[-] 스태틱 15%% 취약\n[-] 스태틱 회복 속도 15%% 감소" } "SF2 Help Soldier Class Info Menu Title" @@ -421,15 +467,17 @@ "fi" "[2] Soldier" "chi" "[2] 士兵" "pt" "[2] Soldier" + "ko" "[2] 솔저" } "SF2 Help Soldier Class Info Description" { - "en" "[+] The Disciplinary Action increases both walking\nand sprinting speed\n[+] 5%% higher static resistance and recovery rate\n[+] Market Gardener crits in midair\n[+] Jumping while walking doesn't cost stamina\n[-] The Half-Zatoichi only restores 5 HP on kill\n[-] Slowest sprint speed along with Heavy and Demoman\n[-] The Escape Plan only affects sprinting speed" + "en" "[+] The Disciplinary Action increases both walking\nand sprinting speed\n[+] 5%% higher static resistance and recovery rate\n[+] Market Gardener crits in midair\n[+] Jumping while walking doesn't cost stamina\n[+] The Escape Plan increases speed by your health loss\n[-] The Half-Zatoichi only restores 5 HP on kill\n[-] Slowest sprint speed along with Heavy and Demoman" "ru" "[+] Дисциплинарное взыскание увеличивает скорость бега и ходьбы\n[-] Полудзатоити восстанавливает только 5 ед. за убийство\n[-] План эвакуации влияет только на скорость бега" "fi" "[+] Kuritoimenpide lisää kävelyn ja juoksemisen nopeutta\nPuolisokea Samurai antaa vain 5 terveyttä taposta\n[-] Pakosuunnitelma vaikuttaa vain ja ainostaan juoksu nopeuteen" "chi" "[+] 指挥官的军鞭同时增加走路和冲刺速度\n[-] 座头市之刀击杀后只恢复5HP\n[-] 逃生计划镐只影响冲刺速度" - "pt" "[+] A Ação Disciplinar aumenta a velocidade de andar e de correr\n[-] A Quase-Zatoichi restaura apenas 5 de vida ao matar\n[-] O Plano de Fuga afeta somente a velocidade de correr" + "pt" "[+] A Ação Disciplinar aumenta a velocidade de andar e de correr\n[-] A Quase-Zatoichi restaura apenas 5 de vida ao matar\n[-] O Plano de Fuga afeta somente a velocidade de correr" + "ko" "[+] 징계 조치로 속도 증진 부여 가능\n[+] 스태틱 5%% 저항\n[+] 스태틱 회복 속도 5%% 증가\n[+] 마켓 가든 모종삽으로 공중에 있을 때 2배의 피해를 가할 수 있음\n[+] 걸을 때 점프는 스테미너에 영향을 주지 않음\n[+] 탈출 계획을 들고 있을 때 체력 손실에 따라 속도 증가\n[-] 반맹인 검객을 통해서 얻는 회복량이 5로 고정\n[-] 헤비와 데모맨과 더불어 가장 느린 달리기 속도" } "SF2 Help Sniper Class Info Menu Title" @@ -439,15 +487,17 @@ "fi" "[8] Sniper" "chi" "[8] 狙击手" "pt" "[8] Sniper" + "ko" "[8] 스나이퍼" } "SF2 Help Sniper Class Info Description" { - "en" "[+] Blink rate increased by 45%%\n[-] Low health, most affected by screen wobbling" + "en" "[+] Blink rate increased by 100%%\n[-] Low health, most affected by screen wobbling" "ru" "[+] Моргает на 45%% реже\n[-] Мало здоровья, наиболее подвержен внушению" "fi" "[+] Räpäyttelya lisätty 40%%\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" "chi" "[+] 眨眼频率增益 45%%\n[-] 低生命值,更容易受到屏幕震动效果" "pt" "[+] Aumento da taxa de piscada em 45%%\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "ko" "[+] 눈 깜빡임 지속 시간 100%% 증가\n[+] 울트라비전의 범위가 2배로 증가함\n[+] 스태틱 5%% 저항\n[+] 스태틱 회복 속도 10%% 증가\n[-] 울트라비전의 밝기가 25%% 어두어짐" } "SF2 Help Demoman Class Info Menu Title" @@ -457,6 +507,7 @@ "fi" "[4] Demomies" "chi" "[4] 爆破手" "pt" "[4] Demoman" + "ko" "[4] 데모맨" } "SF2 Help Demoman Class Info Description" @@ -466,6 +517,7 @@ "fi" "[+] 66%% lisäys pelottamisen juoksunopeuteen\n[-] Puolisokea Samurai palauttaa vain 20 terveyttä taposta" "chi" "[+] 惊吓状态下增加 66%% 冲刺持续时间\n[-] 座头市之刀击杀后只恢复20HP" "pt" "[+] Aumento de 66%% na duração da corrida quando assustado\n[-] A Quase-Zatoichi restaura apenas 20 de vida ao matar" + "ko" "[+] 공포 상태일 때, 스테미너 양 66%% 증가\n[-] 솔저와 헤비와 더불어 가장 느린 달리기 속도\n[-] 반맹인 검객을 통해서 얻는 회복량이 5로 고정" } "SF2 Help Heavy Class Info Menu Title" @@ -475,6 +527,7 @@ "fi" "[5] Heavy" "chi" "[5] 机枪手" "pt" "[5] Heavy" + "ko" "[5] 헤비" } "SF2 Help Heavy Class Info Description" @@ -484,6 +537,7 @@ "fi" "[+] Suuri terveys, vähiten vaikutusta näytön keikkumisesta\n Pikajuoksuhanskat vaikuttaa vain juoksunopeuteen" "chi" "[+] 很多生命值,不容易受到屏幕震动效果\n[-] 紧急逃跑手套只影响冲刺速度" "pt" "[+] Mais vida e o balanço da tela afeta menos\n[-] As Geradoras de Rapidez Urgente afetam somente a velocidade de correr" + "ko" "[+] 체력 많음\n[+] 스태틱 15%% 저항\n[+] 스태틱 회복 속도 15%% 증가\n[+] 덫에 걸리지 않음\n[-] G.R.U는 옛날 성능으로 돌아감" } "SF2 Help Medic Class Info Menu Title" @@ -493,6 +547,7 @@ "fi" "[7] Medic" "chi" "[7] 医生" "pt" "[7] Medic" + "ko" "[7] 메딕" } "SF2 Help Medic Class Info Description" @@ -502,6 +557,7 @@ "fi" "[+] Terveys palautuu ajan myötä\n[-] Ensisijainen kohde bosseille" "chi" "[+] 随时间恢复生命值\n[-] Boss们的优先目标" "pt" "[+] Regenera vida ao longo do tempo\n[-] Torna-se o alvo prioritário de chefões" + "ko" "[+] 매 초마다 체력이 회복됨\n[-] 보스 타겟 우선순위 20%% 증가\n[-] 치료제를 먹을 수 없음\n[-] 프록시에게 받는 피해 15%% 취약\n[-] Thanatophobia 스페셜 라운드 때 사용 불가" } "SF2 Help Pyro Class Info Menu Title" @@ -511,6 +567,7 @@ "fi" "[3] Pyro" "chi" "[3] 火焰兵" "pt" "[3] Pyro" + "ko" "[3] 파이로" } "SF2 Help Pyro Class Info Description" @@ -520,6 +577,7 @@ "fi" "[+] Tulenkestävä, duh!\n[-] Tunkki palauttaa vain 20 terveyttä taposta\nlähitaistelu vahingon ottoa lisätty 33%%" "chi" "[+] 防火!哇!\n[-] 强力千斤顶击杀只恢复20HP,受到的近战伤害增加33%%" "pt" "[+] Imune a fogo (ah, vá?)\n[-] A Ligação Direta restaura apenas 20 de vida e\no dano sofrido de armas corpo a corpo é 33%% maior" + "ko" "[+] 화염 면역!\n[-] 전원 잭을 통해 얻을 수 있는 회복량이 20으로 고정,\n착용 시 받는 피해량이 33% 증가함" } "SF2 Help Spy Class Info Menu Title" @@ -529,15 +587,17 @@ "fi" "[9] Spy" "chi" "[9] 间谍" "pt" "[9] Spy" + "ko" "[9] 스파이" } "SF2 Help Spy Class Info Description" { - "en" "[+] All sounds you make are 30%% quieter to bosses\n[+] 5%% more sprint duration\n[-] Low health, most affected by screen wobbling\n[-] Higher boss priority\n[-] Least amount of scare sprint speed boost\n[-] Big Earner and Kunai only work against proxies" + "en" "[+] All sounds you make are 30%% quieter to bosses\n[+] 5%% more sprint duration\n[-] Low health\n[-] Higher boss priority\n[-] Least amount of scare sprint speed boost\n[-] Big Earner and Kunai only work against proxies" "ru" "[+] Все издаваемые звуки на 30%% тише\n[-] Мало здоровья, наиболее подвержен внушению" "fi" "[+] Kaikki äänet jota teet ovat 30%% hiljaisempia bosseille\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" "chi" "[+] 你发出的声音对Boss来说有 30%% 安静加成\n[-] 低生命值,最容易受到屏幕震动效果" - "pt" "[+] Todos os sons que você faz são 30%% mais silenciosos\npara os chefões\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "pt" "[+] Todos os sons que você faz são 30%% mais silenciosos\npara os chefões\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "ko" "[+] 소음이 30%% 더 조용히 들림\n[+] 5%% 더 많은 스테미나 양\n[-] 낮은 체력\n[-] 보스 타겟 우선순위 10%% 증가\n[-] 공포 상태에서 증가하는 속도값 감소\n[-] 재력가와 묵인자의 쿠나이는 프록시한테만 적용됨" } "SF2 Help Engineer Class Info Menu Title" @@ -547,6 +607,7 @@ "fi" "[6] Engineer" "chi" "[6] 工程师" "pt" "[6] Engineer" + "ko" "[6] 엔지니어" } "SF2 Help Engineer Class Info Description" @@ -556,6 +617,7 @@ "fi" "[+] 75%% lisäys taskulampun patterin elämään\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" "chi" "[+] 增加 75%% 手电筒电池容量\n[+] 增益 60%% 手电筒充能速率\n[-] 低生命值,最容易受到屏幕震动效果" "pt" "[+] Aumento de 75%% da duração bateria da lanterna\n[+] Aumento de 60%% da taxa de recarga da lanterna\n[-] Menos vida (afetado principalmente pelo balanço da tela)" + "ko" "[+] 손전등 배터리 용량 50%% 증가\n[+] 손전등 범위 100%% 증가\n[+] 손전등 거리 200%% 증가\n[+] 손전등 배터리 충전 속도 20%% 증가\n[+] 손전등 스턴 피해량 50%% 증가\n[+] 25%% 작은 손전등 소음\n[-] 낮은 체력, 스태틱 10%% 취약\n[-] 울트라비전이 생기기까지 걸리는 시간 증가" } "SF2 Settings Menu Title" @@ -565,6 +627,7 @@ "fi" "Asetukset" "chi" "设置" "pt" "Configurações" + "ko" "설정" } "SF2 Settings PvP Menu Title" @@ -574,6 +637,7 @@ "fi" "PVP Areenan Asetukset" "chi" "PvP 区域设置" "pt" "Configurações da arena JxJ" + "ko" "PvP 구역 설정" } "SF2 Settings PvP Spawn Menu Title" @@ -583,6 +647,7 @@ "fi" "Ota käyttöön/Poista käytöstä neuvot" "chi" "开启/关闭自动重生" "pt" "Ativar/desativar renascimento automático" + "ko" "PvP 구역에서 자동 부활 활성화/비활성화" } "SF2 Settings PvP Spawn Protection Title" @@ -590,6 +655,7 @@ "en" "Enable/Disable spawn protection" "fi" "Ota käyttöön/Poista käytöstä spawn suojaus" "pt" "Ativar/desativar proteção de renascimento" + "ko" "스폰 보호 활성화/비활성화" } "SF2 Settings Hints Menu Title" @@ -599,6 +665,7 @@ "fi" "Ota käyttöön/Poista käytöstä neuvot" "chi" "开关提示" "pt" "Alternar dicas" + "ko" "힌트 표시 토글" } "SF2 Settings Mute Mode Menu Title" @@ -608,6 +675,7 @@ "fi" "Aseta mykistystila" "chi" "设置禁言模式" "pt" "Definir modo de emudecimento" + "ko" "음소거 모드 설정" } "SF2 Settings Film Grain Menu Title" @@ -617,6 +685,7 @@ "fi" "Ota käyttöön/Poista käytöstä näytön rakeisuus tehoste" "chi" "开关膜颗粒效应" "pt" "Alternar granulação de filme" + "ko" "필름 그래인 토글" } "SF2 Settings Proxy Menu Title" @@ -626,6 +695,7 @@ "fi" "Salli valitsemisesi avustajaksi" "chi" "允许被选为小弟" "pt" "Permitir ser selecionado como proxy" + "ko" "프록시로 선택 여부 설정" } "SF2 Settings Proxy Ask Menu Title" @@ -633,6 +703,7 @@ "en" "Enable/Disable the proxy ask message" "fi" "Ota käyttöön/Poista käytöstä avustaja kysymys viesti" "pt" "Ativar/desativar mensagem de \"proxy ou não\"" + "ko" "프록시 참가 요청 메시지 활성화/비활성화" } "SF2 Settings Hud Version Title" @@ -640,6 +711,7 @@ "en" "Switch HUD version between legacy and new" "fi" "Vaihda HUD versio vanhan ja uuden välillä" "pt" "Alternar versão da interface entre antiga e nova" + "ko" "구 버전과 신 버전으로 HUD 전환 설정" } "SF2 Settings Ghost Overlay Menu Title" @@ -649,6 +721,7 @@ "fi" "Vaihda Haamu Tilan overlay" "chi" "开关幽灵模式覆盖" "pt" "Alternar sobreposição do modo fantasma" + "ko" "유령 모드 오버레이 토글" } "SF2 Settings Ghost Mode Teleport Menu Title" @@ -656,6 +729,7 @@ "en" "Set ghost mode teleport state" "fi" "Aseta haamu tila teleportointi tila" "pt" "Definir estado do teletransporte do modo fantasma" + "ko" "유령 모드 텔레포트 상태 설정" } "SF2 Settings Ghost Mode Toggle State Menu Title" @@ -663,6 +737,7 @@ "en" "Set ghost mode toggle state" "fi" "Aseta haamu tilan toggle tila" "pt" "Definir estado da alternação do modo fantasma" + "ko" "유령 모드 돌입 토글 설정" } "SF2 Settings Hud Version Title" @@ -670,6 +745,7 @@ "en" "Set HUD version" "fi" "Aseta HUD versio" "pt" "Definir versão da interface" + "ko" "HUD 버전 설정" } "SF2 Settings Music Volume Title" @@ -679,6 +755,7 @@ "fi" "Vaihda musiikin äänenvoimakkuutta" "chi" "设置音乐音量" "pt" "Definir volume da música" + "ko" "음악 볼륨 설정" } "SF2 Music Volum Changed" @@ -689,6 +766,7 @@ "fi" "{lightblue}Musiikin äänenvoimakkuus vaihdettu {1}%%." "chi" "{lightblue}音乐音量已设为 {1}%% 。" "pt" "{lightblue}Volume da música definido para {1}%%." + "ko" "{lightblue}음악 볼륨이 {1}% 으로 설정 되었습니다." } "SF2 Settings Flashlight Temperature Title" @@ -698,6 +776,7 @@ "fi" "Aseta taskulampun lämpötila" "chi" "设置手电筒温度" "pt" "Definir temperatura da lanterna" + "ko" "손전등 색감 설정" } "SF2 Flashlight Temperature Changed" @@ -708,6 +787,7 @@ "fi" "{lightblue}Taskulampun lämpötila vaihdettu {1}%%." "chi" "{lightblue}手电筒温度设为 {1}%% 。" "pt" "{lightblue}Temperatura da lanterna definida para {1}%%." + "ko" "{lightblue}손전등 색감이 {1}% 으로 설정되었습니다." } "SF2 Ad Message 1" @@ -716,6 +796,7 @@ "ru" "{dodgerblue}Официальный Discord сервер Slender Fortress Modified: {lightblue}https://discord.gg/7Zz7RYTCC4{default}" "chi" "{dodgerblue}Slender Fortress Modified官方Discord服务器:{lightblue}https://discord.gg/7Zz7RYTCC4{default}" "pt" "{dodgerblue}Servidor oficial do Slender Fortress Modified no Discord: {lightblue}https://discord.gg/7Zz7RYTCC4{default}" + "ko" "{dodgerblue}슬렌더 포트리스 모디파이드 공식 디스코드 서버: {lightblue}https://discord.gg/7Zz7RYTCC4{default}" } "SF2 Ad Message 2" @@ -725,6 +806,7 @@ "fi" "{dodgerblue}Uusi tähän pelitilaan? Kirjoita {lightblue}!slender{dodgerblue} chattiin avataksesi päävalikon." "chi" "{dodgerblue}新玩这个模式?在聊天键入{lightblue}!slender{dodgerblue}来打开主菜单。" "pt" "{dodgerblue}Não sabe como este modo funciona? Digite {lightblue}!slender{dodgerblue} na janela de conversa para abrir o menu principal." + "ko" "{dodgerblue}이 게임모드가 처음 이신가요? {lightblue}!slender{dodgerblue}를 채팅에 입력하여 메인 메뉴를 확인하세요." } "SF2 Welcome Message" @@ -734,6 +816,7 @@ "fi" "{dodgerblue}Tervetuloa {lightblue}Slender Fortress:iin{dodgerblue}! Jos olet uusi tähän pelitilaan, pääset päävalikkoon kirjoittamalla {lightblue}!slender{dodgerblue} chattiin." "chi" "{dodgerblue}欢迎来到{lightblue}暗鬼要塞{dodgerblue}!假如你新玩这个模式,在聊天键入{lightblue}!slender{dodgerblue}来进入主菜单。" "pt" "{dodgerblue}Bem-vindo(a) ao {lightblue}Slender Fortress{dodgerblue}! Caso não saiba como este modo funciona, acesse o menu principal com o comando \"{lightblue}!slender{dodgerblue}\"." + "ko" "{lightblue}슬렌더 포트리스{dodgerblue}에 오신 것을 환영합니다! 이 게임모드가 처음 이시면, {lightblue}!slender{dodgerblue}를 채팅에 입력하여 메인 메뉴를 확인하세요." } "SF2 No Queue Points To Spectator" @@ -743,6 +826,7 @@ "fi" "{dodgerblue}Sinulle ei annettu pisteitä, koska olit Katsoja tiimillä." "chi" "{dodgerblue}因为你在观察者队伍,你没有获得任何队列点数。" "pt" "{dodgerblue}Você não recebeu pontos de espera porque você estava no modo espectador." + "ko" "{dodgerblue}관전 팀에 머물러 있어서 대기열 포인트를 받지 못했습니다." } "SF2 PvP Spawn Accept" @@ -752,6 +836,7 @@ "fi" "{lightblue}Otettiin käyttöön automaattinen PvP areenalla syntyminen" "chi" "{lightblue}已开启PvP区域自动重生。" "pt" "{lightblue}Renascimento automático na arena JxJ ativado." + "ko" "{lightblue}PvP 구역 자동 부활이 활성화되었습니다." } "SF2 PvP Spawn Decline" @@ -761,6 +846,7 @@ "fi" "{lightblue}Poistettiin käytöstä automaattinen PvP areenalla syntyminen." "chi" "{lightblue}已关闭PvP区域自动重生。" "pt" "{lightblue}Renascimento automático na arena JxJ desativado." + "ko" "{lightblue}PvP 구역 자동 부활이 비활성화 되었습니다." } "SF2 PvP Protection On" @@ -768,6 +854,7 @@ "en" "{lightblue}Enabled PvP Spawn Protection." "fi" "{lightblue}PvP Spawn Suojaus otettiin käyttöön." "pt" "{lightblue}Proteção na arena JxJ ativada." + "ko" "{lightblue}PvP 구역 부활 보호가 활성화되었습니다." } "SF2 PvP Protection Off" @@ -775,6 +862,7 @@ "en" "{lightblue}Disabled PvP Spawn Protection." "fi" "{lightblue}PvP Spawn Suojaus poistettu käytöstä." "pt" "{lightblue}Proteção na arena JxJ desativada." + "ko" "{lightblue}PvP 구역 부활 보호가 비활성화되었습니다." } "SF2 Exiting PvP Arena" @@ -785,6 +873,7 @@ "fi" "Olet lähtemässä PvP Areenalta!\nMenetät aseesi {1} sekunnin päästä jos et palaa!" "chi" "你正离开PvP区域!\n假如你不回去,你会在 {1} 秒后失去你的武器!" "pt" "Você está saindo da arena JxJ! Você perderá as suas armas em {1} segundo(s) caso não retorne!" + "ko" "PvP 구역을 벗어났습니다!\n무기를 잃기 싫다면 {1} 초 안에 돌아오세요!" } "SF2 AFK On" @@ -792,6 +881,7 @@ "en" "{royalblue}Disabled going to RED naturally." "fi" "{royalblue}Poistettiin pääsy RED:iin normaalisti." "pt" "{royalblue}Ida natural à RED desativada." + "ko" "{royalblue}AFK로 인해 RED 팀 자동 참가가 비활성화되었습니다." } "SF2 AFK Off" @@ -799,6 +889,7 @@ "en" "{royalblue}Enabled going to RED naturally." "fi" "{royalblue}Otettiin käyttöön pääsy RED:iin normaalisti." "pt" "{royalblue}Ida natural à RED ativada." + "ko" "{royalblue}RED팀 자동 참가가 다시 활성화되었습니다." } "SF2 AFK Status" @@ -806,6 +897,7 @@ "en" "You are currently AFK, press a button to wake up." "fi" "Olet nyt AFK, paina jotain nappia herätäksesi." "pt" "Você está ausente. Pressione um botão para acordar." + "ko" "현재 잠수 상태입니다. 아무 키나 눌러 깨어나세요." } "SF2 Enabled Hints" @@ -815,6 +907,7 @@ "fi" "{lightblue}Vihjeet otettiin käyttöön." "chi" "{lightblue}已开启提示。" "pt" "{lightblue}Dicas ativadas." + "ko" "{lightblue}힌트가 활성화되었습니다." } "SF2 Disabled Hints" @@ -824,6 +917,7 @@ "fi" "{lightblue}Vihjeet poistettiin käytöstä." "chi" "{lightblue}已关闭提示。" "pt" "{lightblue}Dicas desativadas." + "ko" "{lightblue}힌트가 비활성화되었습니다." } "SF2 Enabled Film Grain" @@ -833,6 +927,7 @@ "fi" "{lightblue}Otettiin käyttöön näytön rakeisuus tehoste." "chi" "{lightblue}已开启膜颗粒效应" "pt" "{lightblue}Granulação de filme ativada." + "ko" "{lightblue}필름 그래인이 활성화되었습니다." } "SF2 Disabled Film Grain" @@ -842,6 +937,7 @@ "fi" "{lightblue}Poistettiin näytön rakeisuus tehoste." "chi" "{lightblue}已关闭膜颗粒效应" "pt" "{lightblue}Granulação de filme desativada." + "ko" "{lightblue}필름 그래인이 비활성화되었습니다." } "SF2 Enabled Proxy" @@ -851,6 +947,7 @@ "fi" "{lightblue}Olet nyt valittavissa avustajaksi." "chi" "{lightblue}你现在有随机可能被选为小弟。" "pt" "{lightblue}Agora, você pode ser selecionado como um proxy." + "ko" "{lightblue}이제 프록시로 선택될 수 있습니다." } "SF2 Disabled Proxy" @@ -860,6 +957,7 @@ "fi" "{lightblue}Sinua ei enään valita avustajaksi." "chi" "{lightblue}你不再会被选为小弟。" "pt" "{lightblue}Você não poderá mais ser selecionado como um proxy." + "ko" "{lightblue}더 이상 프록시로 선택되지 않습니다." } "SF2 New Hud Use" @@ -867,6 +965,7 @@ "en" "{lightblue}Enabled the new HUD." "fi" "{lightblue}Otettiin käyttöön uusi HUD." "pt" "{lightblue}Interface nova ativada." + "ko" "{lightblue}신 버전 HUD를 활성화했습니다." } "SF2 Legacy Hud Use" @@ -874,6 +973,7 @@ "en" "{lightblue}Enabled the legacy HUD." "fi" "{lightblue}Otettiin käyttöön vanha HUD." "pt" "{lightblue}Interface antiga ativada." + "ko" "{lightblue}구 버전 HUD를 활성화했습니다." } "SF2 Enabled Ask Message Proxy" @@ -881,6 +981,7 @@ "en" "{lightblue}You will now be prompted and asked when you can become a proxy." "fi" "{lightblue}Olet nyt valittavissa avustajaksi." "pt" "{lightblue}Agora, o jogo perguntará se você deseja ser um proxy quando possível." + "ko" "{lightblue}이제 프록시 참가 요청 메시지가 표시됩니다." } "SF2 Disabled Ask Message Proxy" @@ -888,6 +989,7 @@ "en" "{lightblue}You will no longer be prompted and asked when you can become a proxy." "fi" "{lightblue}Sinua ei enään valita avustajaksi." "pt" "{lightblue}Agora, o jogo não perguntará se você deseja ser um proxy quando possível." + "ko" "{lightblue}프록시가 될 수 있어도 더 이상 참가 요청 여부를 묻지 않습니다." } "SF2 Give Queue Points" @@ -898,6 +1000,7 @@ "fi" "{dodgerblue}Sinulle on annettu {1} jonopistettä pelaamisesta." "chi" "{dodgerblue}你因为游玩获得了 {1} 队列点数。" "pt" "{dodgerblue}Você recebeu {1} ponto(s) de espera por jogar." + "ko" "{dodgerblue}플레이를 위해 {1} 만큼의 대기열 포인트를 받았습니다." } "SF2 Give Group Queue Points" @@ -908,6 +1011,7 @@ "fi" "{dodgerblue}Sinun ryhmällesi on annettu {1} jonopistettä pelaamisesta." "chi" "{dodgerblue}你的组因为游玩获得了 {1} 队列点数。" "pt" "{dodgerblue}O seu grupo recebeu {1} ponto(s) de esepra por jogar." + "ko" "{dodgerblue}귀하의 그룹은 {1} 만큼의 대기열 포인트를 받았습니다." } "SF2 Force Play" @@ -917,6 +1021,7 @@ "fi" "{lightblue}Sinut on pakotettu peliin, koska RED:in pelaaja katkaisi yhteyden tai meni katsomoon. Pidä hauskaa!" "chi" "{lightblue}因为一名红队玩家断开连接或进入观察者,你被强制加入游戏。玩的开心!" "pt" "{lightblue}Você foi movido para a equipe RED devido à saída de outro jogador. Divirta-se!" + "ko" "{lightblue}RED 팀에 있는 플레이어가 서버와 연결이 끊겼거나 관전 팀으로 합류해 당신이 대신해서 들어갔습니다. 재밌게 하세요!" } "SF2 Special Round Announce Chat" @@ -927,6 +1032,7 @@ "fi" "Valittu erikois erä: {lightblue}{1}" "chi" "选择了特殊回合:{lightblue}{1}" "pt" "Selecionar rodada especial: {lightblue}{1}" + "ko" "선택된 스페셜 라운드: {lightblue}{1}" } "SF2 Hint Sprint" @@ -936,6 +1042,7 @@ "fi" "Juokse - Pidä Pohjassa Erityis Hyökkäystäsi" "chi" "冲刺 - 按住特殊攻击键" "pt" "Correr — SEGURAR \"ataque especial\"" + "ko" "달리기 - 특수 공격 키 누르기" } "SF2 Hint Flashlight" @@ -945,6 +1052,7 @@ "fi" "Taskulamppu - Toissijainen Hyökkäys" "chi" "闪光灯 - 次要攻击键" "pt" "Lanterna — ataque secundário" + "ko" "손전등 - 보조 공격 키" } "SF2 Hint Blink" @@ -954,6 +1062,7 @@ "fi" "Räpäytys - Lataa" "chi" "眨眼 - 装填弹药键" "pt" "Piscar — recarregar" + "ko" "눈 깜빡임 - 재장전 키" } "SF2 Hint Main Menu" @@ -963,6 +1072,7 @@ "fi" "Päävalikko - Kirjoita !sf2 tai !slender chattiin" "chi" "主菜单 - 在聊天键入!sf2。" "pt" "Menu principal — digite !sf2 ou !slender na janela de conversa" + "ko" "메인 메뉴 - 채팅창에 !sf2 또는 !slender 입력" } "SF2 Hint Trap" @@ -972,6 +1082,7 @@ "fi" "Poistu Ansasta - Paina hyppy nappia pari kertaa." "chi" "跳出陷阱 - 按几次跳跃键。" "pt" "Sair da armadilha — pressione \"Pular\" algumas vezes." + "ko" "함정 벗어나기 - 점프 키를 연타하세요." } "SF2 Admin Menu Boss Main" @@ -981,6 +1092,7 @@ "fi" "Bossien Hallinta" "chi" "Boss管理" "pt" "Administração de chefão" + "ko" "현재 보스 관리" } "SF2 Admin Menu Add Boss" @@ -990,6 +1102,7 @@ "fi" "Lisää bossi" "chi" "增加一个Boss" "pt" "Adicionar um chefão" + "ko" "보스 추가" } "SF2 Admin Menu Add Fake Boss" @@ -999,6 +1112,7 @@ "fi" "Lisää teko bossi" "chi" "增加一个假Boss" "pt" "Adicionar um chefão falso" + "ko" "가짜 보스 추가" } "SF2 Admin Menu Remove Boss" @@ -1008,6 +1122,7 @@ "fi" "Poista bossi" "chi" "移除一个Boss" "pt" "Remover um chefão" + "ko" "보스 제거" } "SF2 Admin Menu Spawn Boss" @@ -1015,8 +1130,9 @@ "en" "Spawn an active boss" "ru" "Создать активного босса" "fi" "Spawnaa aktiivinen bossi" - "chi" "生成一个活跃Boss" + "chi" "生成一个活跃Boss" "pt" "Criar um chefão ativo" + "ko" "활동 중인 보스 소환" } "SF2 Admin Menu Boss Attack Waiters" @@ -1026,6 +1142,7 @@ "fi" "Kytke bossi hyökkäämään odottajiin (BLU)" "chi" "开关Boss攻击等待者(蓝队)" "pt" "Alternar \"Chefão ataca aguardantes\" (BLU)" + "ko" "보스의 대기자(BLU 팀)를 향한 공격 여부 토글" } "SF2 Admin Menu Boss Attack Waiters Confirm" @@ -1036,6 +1153,7 @@ "fi" "Pitäisikö {1} antaa hyökätä odottajiin (BLU)?" "chi" "应该让 {1} 攻击等待者(蓝队)吗?" "pt" "{1} deve atacar os aguardantes (BLU)?" + "ko" "{1} 이(가) 대기자(BLU팀)을 공격하게 하겠습니까?" } "SF2 Admin Menu Boss Teleport" @@ -1045,6 +1163,7 @@ "fi" "Kytke bossin teleporttaaminen RED:iin" "chi" "开关Boss传送到红队" "pt" "Alternar \"Chefão teletransporta-se à RED\"" + "ko" "보스의 RED 팀을 향한 텔레포트 여부 토글" } "SF2 Admin Menu Boss Teleport Confirm" @@ -1055,6 +1174,7 @@ "fi" "Pitäisikö {1} pystyä teleporttaamaan RED:in lähelle?" "chi" "应该让 {1} 能够传送到红队附近吗?" "pt" "{1] deve poder teletransportar-se próximo à RED?" + "ko" "{1} 이(가) RED 팀을 향해 텔레포트하게끔 만들겠습니까?" } "SF2 Admin Menu Override Boss" @@ -1064,6 +1184,7 @@ "fi" "Aseta seuraavan bossin ohitus" "chi" "选择下一个Boss覆盖" "pt" "Defina a substituição do próx. chefão" + "ko" "다음 보스 재정의" } "SF2 Admin Menu Current Boss Override" @@ -1074,6 +1195,7 @@ "fi" "Nykyisen Bossin Ohitus: {1}" "chi" "当前Boss覆盖:{1}" "pt" "Substituição de chefão atual: {1}" + "ko" "현재 보스 재정의 : {1}" } "SF2 Admin Menu Player Set Play State" @@ -1083,6 +1205,7 @@ "fi" "Pakota pelaaja sisään/ulos pelistä" "chi" "强制玩家进入/离开游戏" "pt" "Forçar jogador a entrar/sair da rodada" + "ko" "플레이어 게임 강제 입장/퇴장" } "SF2 Admin Menu Player Set Play State Confirm" @@ -1093,6 +1216,7 @@ "fi" "Pakotetaanko {1} sisään tai ulos pelistä?" "chi" "强制 {1} 进入或离开游戏?" "pt" "Forçar {1} a entrar/sair da rodada?" + "ko" "{1} 을(를) 게임에 강제 입장/퇴장 시키겠습니까?" } "SF2 Admin Menu Player Force Proxy" @@ -1102,6 +1226,7 @@ "fi" "Pakota pelaaja olemaan avustaja" "chi" "强制玩家成为小弟" "pt" "Forçar jogador a ser um proxy" + "ko" "플레이어를 프록시로 강제 설정" } "SF2 Admin Menu Player Force Proxy Boss" @@ -1111,6 +1236,7 @@ "fi" "Minkä bossin avustaja pelaaja olisi?" "chi" "让玩家成为哪个Boss的小弟?" "pt" "Para qual chefão o jogador deverá ser um proxy?" + "ko" "어떤 보스를 위해 플레이어가 프록시가 되어야 합니까?" } "SF2 Boss Does Not Exist" @@ -1120,6 +1246,7 @@ "fi" "Tuota bossia ei ole enään olemassa!" "chi" "Boss已不存在!" "pt" "Este chefão não existe mais!" + "ko" "해당 보스는 더 이상 존재하지 않습니다!" } "SF2 Player Does Not Exist" @@ -1129,6 +1256,7 @@ "fi" "Tuota pelaajaa ei ole olemassa!" "chi" "玩家不存在!" "pt" "Este jogador não existe!" + "ko" "해당 플레이어는 더 이상 존재하지 않습니다!" } "SF2 No Active Bosses" @@ -1138,6 +1266,7 @@ "fi" "Aktiivisia bosseja ei ole saatavilla!" "chi" "没有可用的活跃Boss!" "pt" "Nenhum chefão ativo disponível!" + "ko" "활동 중인 보스 중 사용 가능한 보스가 없습니다!" } "SF2 Spawned Boss" @@ -1147,6 +1276,7 @@ "fi" "Bossi syntyi sijaintiisi!" "chi" "在你的位置生成了Boss!" "pt" "Chefão criado na sua posição!" + "ko" "바라보고 있는 위치에 보스를 소환했습니다!" } "SF2 Removed Boss" @@ -1156,6 +1286,7 @@ "fi" "Poistettiin bossi!" "chi" "移除了Boss!" "pt" "Chefão removido!" + "ko" "보스가 제거되었습니다!" } "SF2 Boss Not Allowed To Have Proxies" @@ -1165,6 +1296,7 @@ "fi" "Tuolle bossille ei ole sallittu avustajia!" "chi" "这个Boss不允许获得小弟!" "pt" "Este chefão não pode ter proxies!" + "ko" "해당 보스는 프록시를 가질 수 없는 보스입니다!" } "SF2 Boss Should Teleport" @@ -1174,6 +1306,7 @@ "fi" "Bossi pystyy nyt teleporttaamaan!" "chi" "Boss现在可以传送了!" "pt" "Agora, o chefão pode teletransportar-se!" + "ko" "이제 보스가 텔레포트할 수 있습니다!" } "SF2 Boss Should Not Teleport" @@ -1183,6 +1316,7 @@ "fi" "Bossi ei pysty enään teleporttaamaan!" "chi" "Boss现在不可以传送了!" "pt" "O chefão não pode mais teletransportar-se!" + "ko" "보스는 더 이상 텔레포트할 수 없습니다!" } "SF2 Boss Attack Waiters" @@ -1192,6 +1326,7 @@ "fi" "Bossi hyökkää nyt odottajiin!" "chi" "Boss现在会攻击等待者!" "pt" "Agora, o chefão atacará os aguardantes!" + "ko" "이제 보스는 대기자를 공격 할 것입니다!" } "SF2 Boss Do Not Attack Waiters" @@ -1201,6 +1336,7 @@ "fi" "Bossi ei enään hyökkää odottajiin!" "chi" "Boss不会攻击等待者!" "pt" "O chefão não atacará mais os aguardantes." + "ko" "더 이상 보스는 대기자를 공격하지 않습니다!" } "SF2 Player Already A Proxy" @@ -1210,6 +1346,7 @@ "fi" "Tuo pelaaja on jo avustaja!" "chi" "这个玩家已经是小弟了!" "pt" "Este jogador já é um proxy!" + "ko" "해당 플레이어는 이미 프록시입니다!" } "SF2 Unable To Perform Action On Player In Round" @@ -1220,6 +1357,7 @@ "fi" "Ei pysty suorittamaan toimintoa {red}{1}{default} koska hän on erässä pelaamassa." "chi" "无法对 {red}{1}{default} 执行操作,因为他/她已在当前回合游玩中。" "pt" "Não foi possível realizar a ação em {red}{1}{default} porque tal jogador está jogando na rodada atual." + "ko" "해당 플레이어는 현재 라운드에서 플레이 중이므로 {red}{1}{default}에 대해 작업을 수행할 수 없습니다." } "SF2 Player Forced In Game" @@ -1230,6 +1368,7 @@ "fi" "Pakotettiin {blue}{1}{default} peliin." "chi" "强制 {blue}{1}{default} 进入游戏。" "pt" "{blue}{1}{default} foi forçado a entrar na rodada atual." + "ko" "{blue}{1}{default}을(를) 게임에 강제 입장시켰습니다." } "SF2 Player Forced Out Of Game" @@ -1240,6 +1379,7 @@ "fi" "Pakotettiin {red}{1}{default} ulos pelistä." "chi" "强制 {red}{1}{default} 离开游戏。" "pt" "{red}{1}{default} foi forçado a sair da rodada atual." + "ko" "{red}{1}{default}을(를) 게임에 강제 퇴장시켰습니다." } "SF2 Set Queue Points" @@ -1247,6 +1387,7 @@ "#format" "{1:s}" "en" "Changed {blue}{1}{default} queue points." "pt" "Os pontos de espera de {blue}{1}{default} foram alterados." + "ko" "{blue}{1}{default}의 대기열 포인트를 변경했습니다." } "SF2 Cannot Use Command" @@ -1256,6 +1397,7 @@ "fi" "Tätä komentoa ei juuri nyt pysty käyttämään." "chi" "这个命令现在不可以使用。" "pt" "Este comando está indisponível no momento." + "ko" "현재 해당 명령어를 사용할 수 없습니다." } "SF2 Player No Place For Proxy" @@ -1266,6 +1408,7 @@ "fi" "Ei pystytty löytämään hyvää paikkaa avustajalle {blue}{1}!" "chi" "无法为 {blue}{1} 找到好的小弟位置。" "pt" "Não foi possível obter uma boa localização de proxy para {blue}{1}{default}!" + "ko" "{blue}{1}에 적합한 프록시 위치를 찾을 수 없습니다." } "SF2 In" @@ -1275,6 +1418,7 @@ "fi" "Sisällä" "chi" "进入" "pt" "Dentro" + "ko" "입장" } "SF2 Out" @@ -1284,6 +1428,7 @@ "fi" "Ulkona" "chi" "离开" "pt" "Fora" + "ko" "퇴장" } "SF2 Group Main Menu Title" @@ -1293,6 +1438,7 @@ "fi" "Ryhmä Valikko" "chi" "组菜单" "pt" "Menu do grupo" + "ko" "그룹 메뉴" } "SF2 Group Main Menu Description" @@ -1302,6 +1448,7 @@ "fi" "Hallinoi, katso tietoja, luo, tai lähde ryhmästä." "chi" "管理,查看信息,创造或退出一个组。" "pt" "Administrar, ver informações, criar ou sair de um grupo." + "ko" "그룹을 관리, 정보 보기, 생성 또는 탈퇴합니다." } "SF2 Admin Group Menu Title" @@ -1311,6 +1458,7 @@ "fi" "Hallinnoi/Katso tietoja nykyisestä ryhmästä" "chi" "管理/查看信息于当前组" "pt" "Administrar/ver informações do grupo atual" + "ko" "현재 그룹의 관리/정보 보기" } "SF2 Admin Group Menu Description" @@ -1321,6 +1469,7 @@ "fi" "Ryhmän Nimi: {1}\nRyhmän Johtaja: {2}\nJäsenet: {3}/{4}\nJonopisteet: {5}" "chi" "组名称:{1}\n组领袖:{2}\n成员:{3}/{4}\n队列点数:{5}" "pt" "Nome do grupo: {1}\nLíder do grupo: {2}\nMembros: {3}/{4}\nPontos de espera: {5}" + "ko" "그룹 이름: {1}\n그룹 리더: {2}\n멤버: {3}/{4}\n대기열 포인트: {5}" } "SF2 View Current Group Info Menu Title" @@ -1330,6 +1479,7 @@ "fi" "Näytä nykyisen ryhmän tiedot" "chi" "查看当前组信息" "pt" "Ver informações do grupo atual" + "ko" "현재 그룹의 정보 보기" } "SF2 View Group Members Menu Title" @@ -1339,6 +1489,7 @@ "fi" "Näytä ryhmän jäsenet" "chi" "查看组成员" "pt" "Ver membros do grupo" + "ko" "그룹 구성원 보기" } "SF2 View Group Members Menu Description" @@ -1348,6 +1499,7 @@ "fi" "Lista kaikista ryhmäsi jäsenistä:" "chi" "你的组的全部成员列表:" "pt" "Lista de todos os membros no seu grupo:" + "ko" "그룹의 모든 구성원 목록:" } "SF2 Set Group Name Menu Title" @@ -1357,6 +1509,7 @@ "fi" "Anna ryhmälle nimi" "chi" "设置组名称" "pt" "Definir nome do grupo" + "ko" "그룹 이름 설정" } "SF2 Set Group Name Menu Description" @@ -1366,6 +1519,7 @@ "fi" "Voit antaa tekemällesi ryhmälle mukautetun nimen\nselvästi tunnistettavissa muista. Voit antaa\nnykyisen ryhmäsi nimen kirjoittamalla seuraavasti\nchattiin:\n \n /slgroupname \n \nMaksimi nimen pituus on 64 merkkiä." "chi" "你可以给你的组设置独特的名称。\n你可以把你要设置的组名在聊天区这样键入:\n \n /slgroupname <名字>\n \n名称不能超过64个字符。" "pt" "Você pode definir um nome personalizado para o\nseu grupo para torná-lo único. Você pode alterar o\nnome do seu grupo atual\nao digitar o seguinte na ja-\nnela de conversa:\n\n/slgroupname \n\nAté 64 caracteres podem ser usados no nome." + "ko" "다른 그룹과의 구별을 위해서 그룹의 이름을 설정할 수 있습니다.\n채팅창에 입력:\n \n /slgroupname <이름>.\n\n이름의 최대 길이는 영문 기준 64자 입니다." } "SF2 Invalid Group Name" @@ -1375,6 +1529,7 @@ "fi" "Epäsopiva ryhmän nimi!" "chi" "无效的组名!" "pt" "Nome do grupo inválido!" + "ko" "잘못된 그룹 이름입니다!" } "SF2 Group Name Set" @@ -1385,6 +1540,7 @@ "fi" "Ryhmäsi nimi on vaihdettu tästä: {1} tähän: {2}." "chi" "你的组名已从 {1} 改为 {2}。" "pt" "O nome do seu grupo foi alterado de {1} para {2}." + "ko" "그룹 이름이 {1}에서 {2}(으)로 변경되었습니다." } "SF2 Set Group Leader Menu Title" @@ -1394,6 +1550,7 @@ "fi" "Aseta ryhmän johtaja" "chi" "设置组领袖" "pt" "Definir líder do grupo" + "ko" "그룹 리더 설정" } "SF2 Set Group Leader Menu Description" @@ -1403,6 +1560,7 @@ "fi" "Kenen pitäisi olla uusi ryhmän johtaja?" "chi" "谁应是新的组领袖?" "pt" "Quem deve ser o novo líder do grupo?" + "ko" "누구를 새로운 리더로 하시겠습니까?" } "SF2 Group Has Too Many Members" @@ -1412,6 +1570,7 @@ "fi" "Ryhmässäsi on liian monta jäsentä! Jotkin ryhmäsi jäsenistäsi potkitaan tilan hankkimiseksi." "chi" "你的组成员太多了!将踢出一些成员来制造空间。" "pt" "O seu grupo tem muitos membros! Alguns serão expulsos para liberar novas vagas." + "ko" "당신의 그룹에는 멤버가 너무 많습니다! 멤버들 중 일부는 자리를 마련하기 위해 그룹에서 쫓아내야 합니다." } "SF2 Invite To Group Menu Title" @@ -1421,6 +1580,7 @@ "fi" "Kutsu pelaajia ryhmääsi" "chi" "邀请玩家进入组" "pt" "Convidar jogadores para o grupo" + "ko" "그룹에 플레이어 초대" } "SF2 Invite To Group Menu Description" @@ -1430,6 +1590,7 @@ "fi" "Kutsu pelaajia liittymään ryhmääsi!\nHUOMAUTUS: Et voi kutsua pelaajia jotka ovat\njo ryhmässä. Tai ovat pelaamassa RED:llä" "chi" "邀请玩家进入你的组!\n \n注意:你不能邀请已有组的玩家,\n或正在以红队身份游玩的玩家。" "pt" "Convide jogadores para o seu grupo!\n\nAVISO: você não pode convidar jogadores que\njá estão em um grupo ou que estão na equipe RED." + "ko" "플레이어를 그룹에 초대하십시오!\n \n참고: 이미 그룹에 있거나 RED 팀으로 플레이 중인 플레이어는 초대할 수 없습니다." } "SF2 Group Invitation Sent" @@ -1440,6 +1601,7 @@ "fi" "Kutsu ryhmääsi lähetettiin pelaajalle: {1}." "chi" "已发送组邀请给 {1} 。" "pt" "Convite de grupo enviado para {1}." + "ko" "{1}에게 그룹 초대를 보냈습니다." } "SF2 Group Invite Menu Description" @@ -1450,6 +1612,7 @@ "fi" "Sait ryhmäkutsun pelaajalta {1} liity\n{2} ryhmään.\n \nVAROITUS: Liittymällä ryhmään, menetät kaikki jonopisteesi!\nSinut laitetaan jonoon vasta, kun ryhmäsi valitaan.\n \nHyväksytkö kutsun?" "chi" "你被 {1} 邀请加入 {2} 组。\n \n警告:加入组后,你的队列点数会被重置为0!\n你只能在你的组被选上时加入队列。\n \n要接受邀请吗?" "pt" "{1} convidou você para juntar-se ao grupo\n{2}.\n\nAVISO: ao entrar em um grupo, todos os seus pontos de espera\nserão zerados! Você apenas poderá jogar quando o seu\ngrupo for selecionado.\n\nDeseja aceitar o convite?" + "ko" "{1}로부터\n{2} 그룹에 가입하도록 초대받았습니다.\n \n경고: 그룹에 가입하면 대기열 포인트가 0으로 초기화됩니다.\n그룹을 선택할 때만 대기열에 들어갑니다.\n \n초대에 응하시겠습니까?" } "SF2 No Group Invite Spam" @@ -1460,6 +1623,7 @@ "fi" "Sinun pitää odottaa {1} sekunttia ennen kuin voit lähettää uutta kutsua pelaajalle {2}." "chi" "你必须等待 {1} 秒才能再次发送邀请给 {2} 。" "pt" "Aguarde {1} antes de enviar um novo convite para {2}." + "ko" "{2}에 다른 초대를 보내기 전에 {1}초를 기다려야 합니다." } "SF2 Kick From Group Menu Title" @@ -1469,6 +1633,7 @@ "fi" "Potki pelaaja ryhmästä" "chi" "将玩家踢出组" "pt" "Expulsar jogadores do grupo" + "ko" "그룹에서 플레이어 쫓아내기" } "SF2 Kick From Group Menu Description" @@ -1478,6 +1643,7 @@ "fi" "Potki ei halutut pelaajat ryhmästä." "chi" "将不想要的玩家踢出组。" "pt" "Expulse jogadores indesejáveis do seu grupo." + "ko" "그룹에서 원치 않는 플레이어를 쫓아냈습니다." } "SF2 Kicked From Group" @@ -1488,6 +1654,7 @@ "fi" "Sinut on potkittu ryhmästä {1}." "chi" "你被 {1} 组踢出。" "pt" "Você foi expulso do grupo {1}." + "ko" "{1} 그룹에서 쫓겨났습니다." } "SF2 Player Kicked From Group" @@ -1498,6 +1665,7 @@ "fi" "{1} Potkittiin ryhmästä." "chi" "已从组踢出 {1} 。" "pt" "{1} foi expulso do grupo." + "ko" "그룹에서 {1}을(를) 쫓아냈습니다." } "SF2 Reset Group Queue Points Menu Title" @@ -1507,6 +1675,7 @@ "fi" "Tyhjää ryhmäsi pisteet" "chi" "重置组的队列点数" "pt" "Redfinir os pontos de espera do grupo" + "ko" "그룹 대기열 포인트 초기화" } "SF2 Reset Group Queue Points Menu Description" @@ -1516,6 +1685,7 @@ "fi" "Oletko varma, että haluat tyhjätä\nryhmäsi pisteet takaisin 0?" "chi" "你确定要重置组的队列点数为0?" "pt" "Tem certeza de que deseja zerar\nos pontos de espera do grupo?" + "ko" "그룹의 대기열 포인트를 0으로 초기화하시겠습니까?" } "SF2 Group Queue Points Reset" @@ -1525,6 +1695,7 @@ "fi" "Ryhmäsi jonopisteet tyhjennettiin takaisin 0!" "chi" "你的组的队列点数被重置为0!" "pt" "Os pontos de espera do seu grupo foram zerados!" + "ko" "그룹의 대기열 포인트가 0으로 초기화되었습니다!" } "SF2 Group Is Full" @@ -1534,6 +1705,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska ryhmäsi on täynnä!" "chi" "因为组已满,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o grupo está cheio!" + "ko" "그룹이 꽉 찼기 때문에 이 명령어는 수행할 수 없습니다!" } "SF2 Not Group Leader" @@ -1543,6 +1715,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska et ole ryhmänjohtaja." "chi" "因为你不是组领袖,所以你不可以使用此命令!" "pt" "Apenas o líder do grupo pode usar este comando." + "ko" "그룹 리더가 아니므로 이 명령어는 수행할 수 없습니다." } "SF2 No Players Available" @@ -1552,6 +1725,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska yhtään pelaajia ei ole käytettävissä!" "chi" "因为已经没有可用玩家,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque não há jogadores disponíveis!" + "ko" "플레이어가 그룹에 없으므로 이 명령어는 수행할 수 없습니다." } "SF2 Player Not In Group" @@ -1561,6 +1735,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska pelaaja ei ole ryhmässäsi." "chi" "因为此玩家不在你的组,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o jogador não está no seu grupo." + "ko" "플레이어가 이미 다른 그룹에 있으므로 이 명령어는 수행할 수 없습니다." } "SF2 Player In Another Group" @@ -1570,6 +1745,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska pelaaja on jo toisessa ryhmässä." "chi" "因为玩家已在其他组,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o jogador já está em outro grupo." + "ko" "플레이어가 이미 그룹에 있으므로 이 명령어는 수행할 수 없습니다." } "SF2 Player In Group" @@ -1585,6 +1761,7 @@ { "en" "You cannot perform this command because the player is currently playing on RED." "pt" "Não é possível usar este comando porque o jogador está jogando na equipe RED." + "ko" "플레이어가 이미 RED 팀에 있으므로 이 명령어는 수행할 수 없습니다." } "SF2 Create Group Menu Title" @@ -1594,6 +1771,7 @@ "fi" "Luo uusi ryhmä" "chi" "创建新的组" "pt" "Criar novo grupo" + "ko" "새 그룹 만들기" } "SF2 Create Group Menu Description" @@ -1604,6 +1782,7 @@ "fi" "Ryhmän luominen antaa sinun pelaata kavereidesi\nkanssa! Mutta, voit ainoastaan kutsua {1} pelaajaa\nryhmääsi. Ja kun jonotatte, koko ryhmän jonotuspisteet\nasetetaan pisteidesi mukaisesti,\njotka ovat {2}. Mutta, henkilökohtaiset jonopisteesi\nTYHJÄTÄÄN TAKAISIN 0!\n \n Luo uusi ryhmä?" "chi" "创建组可以让你和你的朋友一起玩!\n然而你只可以邀请 {1} 玩家进入你的组。\n关于队列,整个组的队列点数都被设为你的,即是{2}。\n但是,你的个人队列点数会被重置为0!\n \n创建新的组吗?" "pt" "Um grupo permite que você jogue junto com os seus\namigos! Entretanto, só é possível convidar {1} joga-\ndor(es) ao seu grupo. Na fila de espera, os pontos\nde todo o grupo será definido para os\nseus, que são {2}. Todavia, os seus pontos de espera\npessoais serão zerados!\n\nCriar novo grupo?" + "ko" "그룹을 만들면\n여러분과 친구들이 함께 플레이할 수 있습니다!\n하지만 최대 {1}명만 그룹에 초대할 수 있습니다.\n대기열의 경우, 전체 그룹의 대기열 포인트가 {2}으로 설정됩니다.\n그러나 자신의 개인 대기열 포인트는 0으로 초기화 됩니다!\n그룹을 만드시겠습니까?" } "SF2 Player New Group Leader" @@ -1614,6 +1793,7 @@ "fi" "{1} on uusi ryhmän johtaja." "chi" "{1} 现在是你的组的新的领袖。" "pt" "Agora, {1} é o novo líder do seu grupo." + "ko" "{1}님이 이제 그룹의 새 리더입니다." } "SF2 New Group Leader" @@ -1624,6 +1804,7 @@ "fi" "Olet nyt ryhmän johtaja {1} ryhmässä." "chi" "你现在是 {1} 组的领袖。" "pt" "Agora, você é o líder do grupo {1}." + "ko" "이제 당신이 {1} 그룹의 리더입니다." } "SF2 In Group" @@ -1633,6 +1814,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska olet jo ryhmässä!" "chi" "因为你已经在组内了,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque você já está no grupo!" + "ko" "그룹에 이미 속해 있으므로 이 명령어는 수행할 수 없습니다!" } "SF2 In Another Group" @@ -1642,6 +1824,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska olet jo toisessa ryhmässä!" "chi" "因为你已经在另一个组内了,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque você já está em outro grupo!" + "ko" "이미 다른 그룹에 속해 있으므로 이 명령어는 수행할 수 없습니다!" } "SF2 Joined Group" @@ -1652,6 +1835,7 @@ "fi" "Olet nyt ryhmässä {1}. Tervetuloa!" "chi" "你现在在 {1} 组。欢迎!" "pt" "Você se juntou ao grupo {1}. Seja bem-vindo(a)!" + "ko" "{1} 그룹에 가입했습니다. 환영합니다!" } "SF2 Left Group" @@ -1662,6 +1846,7 @@ "fi" "Olet lähtenyt ryhmästä {1}." "chi" "你已离开 {1} 组。" "pt" "Você saiu do grupo {1}." + "ko" "{1} 그룹을 탈퇴했습니다." } "SF2 Player Joined Group" @@ -1672,6 +1857,7 @@ "fi" "Pelaaja {1} on liittynyt ryhmääsi." "chi" "玩家 {1} 加入了你的组。" "pt" "{1} entrou no seu grupo." + "ko" "{1} 플레이어가 그룹에 가입했습니다." } "SF2 Player Left Group" @@ -1682,6 +1868,7 @@ "fi" "Pelaaja {1} on lähtenyt ryhmästäsi." "chi" "玩家 {1} 离开了你的组。" "pt" "{1} saiu do seu grupo." + "ko" "{1} 플레이어가 그룹에서 탈퇴했습니다." } "SF2 Created Group" @@ -1692,6 +1879,7 @@ "fi" "Ryhmän luonti onnistui! Uuden ryhmäsi nimi on: {1}" "chi" "创建组成功!你的新的组名称是:{1}" "pt" "Grupo criado com sucesso! O novo nome do seu grupo é: {1}" + "ko" "그룹을 만들었습니다! 새 그룹의 이름은 {1}입니다." } "SF2 Max Groups Reached" @@ -1701,6 +1889,7 @@ "fi" "Uutta ryhmää ei voi luoda, koska ryhmien enimmäismäärä on saavutettu!" "chi" "因为组的最大数量已达到,所以无法创建新的组!" "pt" "Não é possível criar um novo grupo porque o número máximo de grupos foi atingido!" + "ko" "최대 그룹 수에 도달했기 때문에 새 그룹을 만들 수 없습니다!" } "SF2 Group Does Not Exist" @@ -1710,6 +1899,7 @@ "fi" "Et voi suorittaa tätä komentoa, koska ryhmääsi ei enään ole olemassa!" "chi" "因为你的组已不存在,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o seu grupo não existe mais!" + "ko" "그룹이 더 이상 존재하지 않으므로 이 명령어는 수행할 수 없습니다!" } "SF2 Leave Group Menu Title" @@ -1719,6 +1909,7 @@ "fi" "Poistu nykyisestä ryhmästä" "chi" "离开当前组" "pt" "Sair do grupo atual" + "ko" "현재 그룹 나가기" } "SF2 Leave Group Menu Description" @@ -1729,6 +1920,7 @@ "fi" "Oletko varma että haluat lähteä ryhmästä {1}?" "chi" "你确定要离开 {1} 组吗?" "pt" "Tem certeza de que deseja sair do grupo {1}?" + "ko" "정말 {1} 그룹에서 나가겠습니까?" } "SF2 Proxy Ask Menu Title" @@ -1738,6 +1930,7 @@ "fi" "Sinut on valittu olemaan Apulainen!" "chi" "你被选成为了小弟!" "pt" "Você foi selecionado para ser um proxy!" + "ko" "프록시로 선택되었습니다!" } "SF2 Proxy Ask Menu Description" @@ -1747,6 +1940,7 @@ "fi" "Apulaisena, tehtävänäsi on tappaa RED tiimi. Sinut\nteleportataan lähelle RED pelaajien sijaintia\nkun hyväksyt. Lisäksi, Apulaiset näkevät RED tiimin\nääriviivat seinien läpi, jotta voit tehdä yllätyshyökkäyksiä.\nMutta, elämäsi on riippuvainen ohjausmittaristasi,\neli se ei säily loputtomiin.\n \nHyväksytkö?" "chi" "作为小弟,你的目标是击杀红队。\n你在同意后会被传送到一个红队玩家附近的位置\n另外,小弟可以透过墙看见红队的轮廓,\n你可以按需来偷袭。但是你的生命值和控制能力挂钩,\n因此你不能一直成为小弟。\n \n你接受吗?" "pt" "Como um proxy, o seu objetivo é matar a equipe RED.\nVocê será teletransportado próximo de um jogador\nda equipe RED assim que aceitar. Em adição, proxies\npodem ver as silhuetas da equipe RED através das\nparedes, então você pode atacá-los de surpresa. En-\ntretanto, a sua vida depende do seu medidor de con-\ntrole, que não durará para sempre.\n\nDeseja aceitar?" + "ko" "프록시의 목표는 RED 팀을 죽이는 것입니다.\n프록시가 되는 것을 수락하면 RED 팀 근처에서 텔레포트 됩니다.\n또한 프록시는 벽 너머로 RED팀 플레이어의 외곽선을 볼 수 있으며, 그런 점을 이용하여 기습을 시도하세요.\n그러나 화면 하단 수명 미터는 RED 팀을 타격하지 않는 한 계속 감소합니다.\n \n프록시가 되시겠습니까?" } "SF2 Too Much Time" @@ -1756,6 +1950,7 @@ "fi" "{dodgerblue}[SF2] {lightblue}Valitettavasti olet viettänyt liian paljon aikaa, aloituspisteesi on näkyvillä pelaajalle." "chi" "{dodgerblue}[SF2] {lightblue}抱歉你花了太多时间,你的重生点其实对一名玩家可见。" "pt" "{dodgerblue}[SF2] {lightblue}Você demorou demais e o seu ponto de nascimento está visível para um jogador." + "ko" "{dodgerblue}[SF2] {lightblue}시간을 너무 많이 빼앗아 죄송합니다. 당신의 스폰 위치가 플레이어에게 사실상 보이게 됩니다." } "SF2 Too Many Proxies" @@ -1765,6 +1960,7 @@ "fi" "Anteeksi, mutta tällä hetkellä pelissä on liian monta Avustajaa." "chi" "抱歉,现在游戏中太多小弟了。" "pt" "Erro: há muitos proxies na rodada atual." + "ko" "죄송합니다. 현재 게임에 프록시가 너무 많습니다." } "SF2 Proxy Force Message" @@ -1775,42 +1971,47 @@ "fi" "Sinut on valittu olemaan Avustaja!\nSinut teleportataan RED tiimin alueelle {1} sekunnin päästä." "chi" "你被选为了小弟!\n你将在 {1} 秒后传送到红队区域。" "pt" "Você foi selecionado para ser um\nproxy! Você será teletransportado à área da RED em {1} segundo(s)." + "ko" "프록시로 선택되었습니다!\n{1}초 후에 RED 팀 플레이 구역으로 텔레포트됩니다." } "SF2 Boss Pack Menu Title" { - "en" "Show the current pack, if there's one:" - "ru" "Показать текущий набор, если он есть:" - "fi" "Näytä nykyinen paketti, jos sellainen on:" + "en" "Show the current pack, if there's one:" + "ru" "Показать текущий набор, если он есть:" + "fi" "Näytä nykyinen paketti, jos sellainen on:" "chi" "如果有的话,显示当前包:" - "pt" "Exibir o pacote atual (caso haja um):" + "pt" "Exibir o pacote atual (caso haja um):" + "ko" "현재 보스팩이 있는 경우, 해당 보스팩을 표시:" } "SF2 Boss Next Pack Menu Title" { - "en" "Show the next boss pack, if there is one:" - "ru" "Показать следующий набор боссов, если он есть:" - "fi" "Näytä seuraava boss paketti, jos sellainen on:" + "en" "Show the next boss pack, if there is one:" + "ru" "Показать следующий набор боссов, если он есть:" + "fi" "Näytä seuraava boss paketti, jos sellainen on:" "chi" "如果有的话,显示下一个boss包:" - "pt" "Exibir o próximo pacote de chefões (caso haja um):" + "pt" "Exibir o próximo pacote de chefões (caso haja um):" + "ko" "다음 보스팩이 있는 경우, 해당 보스팩을 표시:" } "SF2 Boss View On List Title" { - "en" "Show the bosses on the current pack:" - "ru" "Показать боссов в текущем наборе:" - "fi" "Näytä nykyisen paketin bossit:" + "en" "Show the bosses on the current pack:" + "ru" "Показать боссов в текущем наборе:" + "fi" "Näytä nykyisen paketin bossit:" "chi" "显示当前包的Boss:" - "pt" "Exibir os chefões do pacote atual:" + "pt" "Exibir os chefões do pacote atual:" + "ko" "현재 보스팩에 있는 보스 표시:" } "SF2 Boss List Menu Title" { - "en" "Current Bosses" - "ru" "Текущие боссы" - "fi" "Nykyiset Bossit" + "en" "Current Bosses" + "ru" "Текущие боссы" + "fi" "Nykyiset Bossit" "chi" "当前Boss" "pt" "Chefões atuais" + "ko" "현재 보스 목록" } "SF2 Boss Pack Vote Menu Title" @@ -1820,6 +2021,7 @@ "fi" "Valitse boss paketti seuraavalle kartalle!" "chi" "选择下一个地图的Boss包" "pt" "Selecione o pacote de chefões para o próx. mapa!" + "ko" "다음 맵의 보스팩을 선택하세요!" } "SF2 Boss Pack Vote Successful" @@ -1830,6 +2032,7 @@ "fi" "Äänestys onnistui! Seuraava boss paketti, joka ladataan seuraavalle kartalle on: {1}" "chi" "投票成功!下一个地图的Boss包已设为:{1}" "pt" "Votação finalizada! O próximo pacote de chefões a ser carregado no próx. mapa é: {1}" + "ko" "투표 성공! 다음 맵에 로드할 보스 팩은 {1}입니다." } "SF2 Boss Pack No Vote" @@ -1839,6 +2042,7 @@ "fi" "Kukaan ei äänestänyt. Seuraava kartalle ladattava boss paketti on oletuspaketti." "chi" "无人投票。下一个地图的Boss包将为默认。" "pt" "Ninguém votou. O próximo pacote de chefões a ser carregado no próx. mapa será o padrão." + "ko" "아무도 투표를 안 했어요. 다음 맵의 보스팩은 기본값으로 로드됩니다." } "SF2 Special Round Vote Menu Title" @@ -1848,6 +2052,7 @@ "fi" "Valitse erikoiskierros!" "chi" "选择特殊回合!" "pt" "Selecione a rodada especial!" + "ko" "스페셜 라운드를 고르세요!" } "SF2 Special Round Vote Successful" @@ -1858,6 +2063,7 @@ "fi" "Äänestys onnistui! Aktivoidaan erikoiskierros: {1}" "chi" "投票成功!启动特殊回合:{1}" "pt" "Votação finalizada! Ativando rodada especial: {1}" + "ko" "투표 성공! 스페셜 라운드 활성화: {1}" } "SF2 Projected Flashlight" @@ -1867,6 +2073,7 @@ "fi" "{dodgerblue}Taskulamppusi on asetettu tilaan {lightblue}Projected{dodgerblue}" "chi" "{dodgerblue}你的手电筒模式已设为{lightblue}投影{dodgerblue}。" "pt" "{dodgerblue}O modo da sua lanterna foi alterado para \"{lightblue}Projetado{dodgerblue}\"." + "ko" "{dodgerblue}손전등 조명 모드가 {lightblue}Projected{doggerblue}으로 설정되었습니다." } "SF2 Normal Flashlight" @@ -1876,6 +2083,7 @@ "fi" "{dodgerblue}Taskulamppusi on asetettu tilaan {lightblue}Normaali{dodgerblue}." "chi" "{dodgerblue}你的手电筒模式已设为{lightblue}普通{dodgerblue}。" "pt" "{dodgerblue}O modo da sua lanterna foi alterado para \"{lightblue}Normal{dodgerblue}\"." + "ko" "{dodgerblue}손전등 조명 모드가 {lightblue}기본{dodgerblue}으로 설정되었습니다." } "SF2 Mute Mode Normal" @@ -1885,6 +2093,7 @@ "fi" "{lightblue}Mykistystila asetettu normaaliksi." "chi" "{lightblue}禁言模式设为普通。" "pt" "{lightblue}Modo de emudecimento alterado para \"Normal\"." + "ko" "{lightblue}음소거 모드가 정상으로 설정되었습니다." } "SF2 Mute Mode Opposing" @@ -1894,6 +2103,7 @@ "fi" "{lightblue}Vastatiimi vaimennettiin." "chi" "{lightblue}禁言敌方队伍。" "pt" "{lightblue}Equipe inimiga emudecida." + "ko" "{lightblue}상대 팀을 음소거했습니다." } "SF2 Mute Mode Proxy" @@ -1903,6 +2113,7 @@ "fi" "{lightblue}Vastatiimi hiljennettiin, mutta asetukset asetetaan automaattisesti normaaliksi, jos olet avustaja." "chi" "{lightblue}禁言敌方队伍,但将在成为小弟后自动设为普通。" "pt" "{lightblue}Equipe inimiga emudecida. Entretanto, as configurações voltarão ao normal quando você for um proxy." + "ko" "{lightblue}상대 팀을 음소거하지만 프록시가 되는 경우 설정이 자동으로 기본값으로 설정됩니다." } "SF2 Settings Ghost Mode Teleport Title" @@ -1910,6 +2121,7 @@ "en" "Change ghost mode teleport state" "fi" "Muuta haamu tilan teleportin tilaa" "pt" "Alterar estado de teletransporte do modo fantasma" + "ko" "유령 모드 텔레포트 상태 변경" } "SF2 Teleport Ghost Players" @@ -1917,12 +2129,14 @@ "en" "{lightblue}You can now teleport to only players during ghost mode." "fi" "{lightblue}Voit nyt teleportata pelaajiin vain haamu tilassa." "pt" "{lightblue}Agora, você pode teletransportar-se apenas a jogadores no modo fantasma." + "ko" "{lightblue}이제 플레이어를 향해서만 텔레포트 할 수 있습니다." } "SF2 Teleport Ghost Bosses" { "en" "{lightblue}You can now teleport to only bosses during ghost mode." "pt" "{lightblue}Agora, você pode teletransportar-se apenas ao chefão no modo fantasma." + "ko" "{lightblue}이제 보스를 향해서만 텔레포트 할 수 있습니다." } "SF2 Settings Ghost Mode Toggle State Title" @@ -1930,6 +2144,7 @@ "en" "Set ghost mode toggle state" "fi" "Aseta haamu muodon tila" "pt" "Definir estado da alternação do modo fantasma" + "ko" "유령 모드 돌입 설정 토글" } "SF2 Toggle Ghost Default" @@ -1937,6 +2152,7 @@ "en" "{lightblue}Ghost mode will now be enabled any time you want." "fi" "{lightblue}Haamu tila otetaan nyt käyttöön koska tahansa, kun haluat." "pt" "{lightblue}Agora, o modo fantasma será ativado ao seu comando." + "ko" "{lightblue}유령 모드는 이제 언제든지 사용할 수 있습니다." } "SF2 Toggle Ghost On Grace" @@ -1944,6 +2160,7 @@ "en" "{lightblue}Ghost mode will now be enabled whenever grace period ends." "fi" "{lightblue}Haamu tila otetaan nyt käyttöön, kun armonaika loppuu." "pt" "{lightblue}Agora, o modo fantasma apenas será ativado após o tempo de preparação acabar." + "ko" "{lightblue}이제 유예 기간이 끝날 때마다 유령 모드가 활성화됩니다." } "SF2 Toggle Ghost On Death" @@ -1951,24 +2168,28 @@ "en" "{lightblue}Ghost mode will now be enabled upon dying in RED." "fi" "{lightblue}Haamu tila otetaan nyt käyttöön, kun kuolet RED:llä." "pt" "{lightblue}Agora, o modo fantasma será ativado após morrer estando na RED." + "ko" "{lightblue}이제 RED 팀으로 플레이 도중 사망 시 유령 모드가 활성화됩니다." } "SF2 Settings View Bobbing Toggle Title" { "en" "Set view bobbing toggle" "pt" "Definir alternação da arma balançando ao andar" + "ko" "시야 기울어짐 설정" } "SF2 Toggle View Bobbing On" { "en" "{lightblue}View bobbing is now enabled." "pt" "{lightblue}Agora, a arma balança ao andar." + "ko" "{lightblue}시야 기울어짐이 활성화되었습니다." } "SF2 Toggle View Bobbing Off" { "en" "{lightblue}View bobbing is now disabled." "pt" "{lightblue}Agora, a arma não balança ao andar." + "ko" "{lightblue}시야 기울어짐이 비활성화되었습니다." } "SF2 Recent Changes" @@ -1978,12 +2199,14 @@ "fi" "Viimeaikaiset muutokset:" "chi" "最近改动:" "pt" "Alterações recentes:" + "ko" "최근 변경 사항:" } "SF2 Change Log" { "en" "-Precached literally every boss key value\n-Removed certain backwards compatibility" "pt" "- Pré-carregamento de todos os valores-chave\nde chefões (literalmente)\n- Remoção de certas retrocompatibilidades" + "ko" "-모든 보스의 키밸류 값을 프리캐쉬하고\n사후 지원용으로 남겨뒀던 불필요한 커맨드를 지웠음!" } "SF2 Smite player" @@ -1993,6 +2216,7 @@ "fi" "Iske pelaajaa" "chi" "惩戒玩家" "pt" "Castigar jogador" + "ko" "플레이어에게 천벌내리기" } "SF2 Smote target" @@ -2003,6 +2227,7 @@ "fi" "{valve}{1}{default} kutsui Zeuksen voiman ja iski {red}{2}!" "chi" "{valve}{1}{default} 召唤了宙斯之力并惩罚了 {red}{2} !" "pt" "{valve}{1}{default} evocou o poder de Zeus e castigou {red}{2}{default}!" + "ko" "{valve}{1}{default}이(가) 제우스의 힘을 호출해 {red}{2}을(를) 박살냈습니다." } "SF2 Boxing Initiate" @@ -2012,6 +2237,7 @@ "fi" "Vaikeustaso asetettu tasoon: {yellow}Normaali{default}.\nVaikeustaso pahenee, kun mestaristamme tulee heikompi." "chi" "难度已设为{yellow}普通{default}。\n我们的冠军越虚弱,难度就越大。" "pt" "A dificuldade foi alterada para {yellow}Normal{default}.\nA dificuldade será aumentada de acordo com o enfraquecimento do nosso campeão." + "ko" "난이도가 {yellow}보통{default}(으)로 설정되었습니다.\n챔피언이 약해질수록 난이도는 높아질 것입니다." } "SF2 Boxing Win Message" @@ -2022,5 +2248,6 @@ "fi" "{red}{1}{default} on kukistanut {valve}{2}." "chi" "{red}{1}{default} 击败了 {valve}{2}。" "pt" "{red}{1}{default} derrotou {valve}{2}{default}." + "ko" "{red}{1}{default}이(가) {default}{2}을(를) 쓰러뜨렸습니다!" } } From b10c01d6da431afbc6c00528fa05965c76bc8df3 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:18:01 -0700 Subject: [PATCH 07/86] Logo change for real --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 19b7d142..d01fd78c 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,12 @@ Special thanks to: - x3m77rus + Blueberryy for making russian translations, and updating some part of the code to support more translation text. - Blad3z for making Finnish translations. -- Glacetomic for the new Slender Fortress 2 Modified logo # Slender Fortress Modified Versions Since Kit is busy with his real life, Kenzzer is no longer working on SF2, I used the new versions to create the new modified versions to allow much more diversity to SF2. -![SF2M-Logo](https://user-images.githubusercontent.com/42941613/86555411-1e298280-bf05-11ea-8246-6d893d5aa5c7.png) +![SF2M-Logo](https://user-images.githubusercontent.com/42941613/229934962-2e6ba294-ea21-4256-badc-77c9002a16fc.png) Slender Fortress ================ From aef8a2181e1984c7039ea23887351c49bddbc1e9 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Wed, 26 Jul 2023 21:56:08 -0700 Subject: [PATCH 08/86] Update sf2.txt --- addons/sourcemod/gamedata/sf2.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/gamedata/sf2.txt b/addons/sourcemod/gamedata/sf2.txt index a7bbe654..7298b6a3 100644 --- a/addons/sourcemod/gamedata/sf2.txt +++ b/addons/sourcemod/gamedata/sf2.txt @@ -11,8 +11,8 @@ } "CTFWeaponBase::GetCustomDamageType" { - "linux" "386" - "windows" "380" + "linux" "387" + "windows" "381" } "CBaseEntity::ShouldTransmit" { From 8f0e1b9f00428c4b0f513c009a6cacb18ae6d25d Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sun, 6 Aug 2023 17:54:26 -0700 Subject: [PATCH 09/86] 1.1 --- addons/sourcemod/scripting/include/sf2.inc | 2 + .../sourcemod/scripting/sf2/extras/natives.sp | 2 +- addons/sourcemod/scripting/sf2/npc.sp | 17 +- .../scripting/sf2/npc/npc_chaser_pathing.sp | 439 ++---------------- .../scripting/sf2/npc/npc_creeper.sp | 151 +----- .../sourcemod/scripting/sf2/specialround.sp | 2 +- addons/sourcemod/scripting/sf2/stocks.sp | 4 +- 7 files changed, 62 insertions(+), 555 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 5a34fbde..709e257a 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -3,6 +3,8 @@ #endif #define _sf2_included +#include + #define PLUGIN_VERSION "1.8.0 M Alpha" #define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha" diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 505d4f2f..8e1f1c06 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -691,7 +691,7 @@ static any Native_ForceBossJump(Handle plugin, int numParams) float startPos[3], endPos[3]; GetNativeArray(2, startPos, 3); GetNativeArray(3, endPos, 3); - CBaseNPC_Jump(GetNativeCell(1), startPos, endPos); + //CBaseNPC_Jump(GetNativeCell(1), startPos, endPos); return 0; } diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 32b7670a..3b9f832a 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -2755,10 +2755,11 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) locomotion.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); } + float pathingBoxMin[3], pathingBoxMax[3]; if (NPCGetRaidHitbox(bossIndex) == 1) { - npcBoss.SetBodyMins(g_SlenderDetectMins[bossIndex]); - npcBoss.SetBodyMaxs(g_SlenderDetectMaxs[bossIndex]); + pathingBoxMin = g_SlenderDetectMins[bossIndex]; + pathingBoxMax = g_SlenderDetectMaxs[bossIndex]; npcEntity.SetPropVector(Prop_Send, "m_vecMins", g_SlenderDetectMins[bossIndex]); npcEntity.SetPropVector(Prop_Send, "m_vecMaxs", g_SlenderDetectMaxs[bossIndex]); @@ -2768,8 +2769,8 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) } else if (NPCGetRaidHitbox(bossIndex) == 0) { - npcBoss.SetBodyMins(HULL_HUMAN_MINS); - npcBoss.SetBodyMaxs(HULL_HUMAN_MAXS); + pathingBoxMin = HULL_HUMAN_MINS; + pathingBoxMax = HULL_HUMAN_MAXS; npcEntity.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); npcEntity.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); @@ -2777,6 +2778,14 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) npcEntity.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); npcEntity.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); } + for (int i = 0; i < 2; i++) + { + // Yes we don't want to increase the Z values + pathingBoxMin[i] -= 5.0; + pathingBoxMax[i] += 5.0; + } + npcBoss.SetBodyMins(pathingBoxMin); + npcBoss.SetBodyMaxs(pathingBoxMax); if (SF_IsBoxingMap()) { diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp index 652a23b0..628c08b1 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp @@ -44,6 +44,8 @@ void SlenderChaseBossProcessMovement(int bossEnt) int oldState = g_SlenderOldState[bossIndex]; g_SlenderOldState[bossIndex] = state; + float gameTime = GetGameTime(); + int difficulty = GetLocalGlobalDifficulty(bossIndex); if (!g_SlenderInDeathcam[bossIndex]) @@ -84,11 +86,11 @@ void SlenderChaseBossProcessMovement(int bossEnt) { int currentAttackIndex = NPCGetCurrentAttackIndex(bossIndex); if (NPCChaserGetAttackWhileRunningState(bossIndex, currentAttackIndex, difficulty) && - !NPCIsRaging(bossIndex) && GetGameTime() >= g_NpcBaseAttackRunDelayTime[bossIndex][currentAttackIndex]) + !NPCIsRaging(bossIndex) && gameTime >= g_NpcBaseAttackRunDelayTime[bossIndex][currentAttackIndex]) { if (NPCChaserGetAttackRunDuration(bossIndex, currentAttackIndex, difficulty) > 0.0) { - if (GetGameTime() < g_NpcBaseAttackRunDurationTime[bossIndex][currentAttackIndex]) + if (gameTime < g_NpcBaseAttackRunDurationTime[bossIndex][currentAttackIndex]) { float attackSpeed, originalSpeed; originalSpeed = NPCChaserGetAttackRunSpeed(bossIndex, currentAttackIndex, difficulty); @@ -363,7 +365,7 @@ void SlenderChaseBossProcessMovement(int bossEnt) if (g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, CLIMB_UP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, JUMP_OVER_GAP, 120.0)|| g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, LADDER_UP, 120.0)) { - CBaseNPC_Jump(loco, myPos, pathEndPos); + CBaseNPC_Jump(bossEnt, loco, myPos, pathEndPos); } } } @@ -570,7 +572,7 @@ void SlenderChaseBossProcessMovement(int bossEnt) } if (runUnstuck) { - if (loco.GetGroundSpeed() <= 0.1 || GetVectorSquareMagnitude(myPos, g_LastPos[bossIndex]) < 0.1 || loco.IsStuck()) + if (loco.GetGroundSpeed() <= 0.1 || GetVectorSquareMagnitude(myPos, g_LastPos[bossIndex]) <= 0.13 || loco.IsStuck()) { bool blockingProp = false; @@ -593,348 +595,37 @@ void SlenderChaseBossProcessMovement(int bossEnt) { if (g_LastStuckTime[bossIndex] == 0.0) { - g_LastStuckTime[bossIndex] = GetGameTime(); + g_LastStuckTime[bossIndex] = gameTime; } - if ((g_LastStuckTime[bossIndex] <= GetGameTime()-1.0 || loco.GetStuckDuration() >= 1.0) && - !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex]) + if ((g_LastStuckTime[bossIndex] <= gameTime - 1.0 || loco.GetStuckDuration() >= 1.0) && + !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex] && g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT) { - float movePos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else - { - g_BossPathFollower[bossIndex].GetClosestPosition(myPos, movePos); - } - bool pathResolved = false; - - if (SlenderChaseBoss_OnStuckResolvePath(bossEnt, myPos, myEyeAng, movePos, movePos)) - { - if (NPCGetRaidHitbox(bossIndex) == 1) - { - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - } - if (!pathResolved) - { - if (NPCGetRaidHitbox(bossIndex) == 1) - { - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - CNavArea area = combatChar.GetLastKnownArea(); - if (area == NULL_AREA && segment != NULL_PATH_SEGMENT) - { - area = segment.area; - } - if (area != NULL_AREA) - { - area.GetCenter(movePos); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, g_SlenderDetectMins[bossIndex], g_SlenderDetectMaxs[bossIndex], bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !g_RestartSessionEnabled) - { - RemoveSlender(bossIndex);//We are stuck there's no way out for us, unspawn, players are just going to abuse that we are stuck. - } - else if (SF_IsBoxingMap()) - { - float teleportPos[3]; - ArrayList respawnPoint = new ArrayList(); - char name[32]; - int ent = -1; - while ((ent = FindEntityByClassname(ent, "info_target")) != -1) - { - GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); - if (StrContains(name, "sf2_boss_respawnpoint", false)) - { - respawnPoint.Push(ent); - } - } - ent = -1; - if (respawnPoint.Length > 0) - { - ent = respawnPoint.Get(GetRandomInt(0,respawnPoint.Length-1)); - } - - delete respawnPoint; - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - else if (!SF_IsBoxingMap() && g_RestartSessionEnabled) - { - ArrayList spawnPoint = new ArrayList(); - float teleportPos[3]; - int ent = -1, spawnTeam = 0; - while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) - { - spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); - if (spawnTeam == TFTeam_Red) - { - spawnPoint.Push(ent); - } - - } - ent = -1; - if (spawnPoint.Length > 0) - { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); - } - - delete spawnPoint; - - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - } - } - } - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - CNavArea area = combatChar.GetLastKnownArea(); - if (area == NULL_AREA && segment != NULL_PATH_SEGMENT) - { - area = segment.area; - } - if (area != NULL_AREA) - { - area.GetCenter(movePos); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !g_RestartSessionEnabled) - { - RemoveSlender(bossIndex);//We are stuck there's no way out for us, unspawn, players are just going to abuse that we are stuck. - } - else if (SF_IsBoxingMap()) - { - float teleportPos[3]; - ArrayList respawnPoint = new ArrayList(); - char name[32]; - int ent = -1; - while ((ent = FindEntityByClassname(ent, "info_target")) != -1) - { - GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); - if (StrContains(name, "sf2_boss_respawnpoint", false)) - { - respawnPoint.Push(ent); - } - } - ent = -1; - if (respawnPoint.Length > 0) - { - ent = respawnPoint.Get(GetRandomInt(0,respawnPoint.Length-1)); - } - - delete respawnPoint; - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - else if (!SF_IsBoxingMap() && g_RestartSessionEnabled) - { - ArrayList spawnPoint = new ArrayList(); - float teleportPos[3]; - int ent = -1, spawnTeam = 0; - while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) - { - spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); - if (spawnTeam == TFTeam_Red) - { - spawnPoint.Push(ent); - } - - } - ent = -1; - if (spawnPoint.Length > 0) - { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); - } - - delete spawnPoint; - - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - } - } - } - } - } - if (pathResolved) - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; - } + float movePos[3], segmentPos[3]; + Segment segment = g_BossPathFollower[bossIndex].FirstSegment(); + segment.GetPos(segmentPos); + g_BossPathFollower[bossIndex].GetClosestPosition(segmentPos, movePos, segment, 50.0); + TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); + + loco.ClearStuckStatus(); + g_LastStuckTime[bossIndex] = 0.0; } } } else { loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; + g_LastStuckTime[bossIndex] += 0.03; + if (g_LastStuckTime[bossIndex] > gameTime) + { + g_LastStuckTime[bossIndex] = gameTime; + } g_LastPos[bossIndex] = myPos; } } } - if (GetGameTime() >= g_SlenderNextFootstepSound[bossIndex]) + if (gameTime >= g_SlenderNextFootstepSound[bossIndex]) { SlenderCastFootstep(bossIndex); } @@ -1080,10 +771,10 @@ void SlenderSetNextThink(int bossEnt) return; } -void CBaseNPC_Jump(NextBotGroundLocomotion nextbotLocomotion, float startPos[3], float endPos[3]) +void CBaseNPC_Jump(int entity, NextBotGroundLocomotion nextbotLocomotion, float startPos[3], float endPos[3]) { float jumpVel[3]; - endPos[2] += nextbotLocomotion.GetStepHeight(); + startPos[2] += nextbotLocomotion.GetStepHeight(); float actualHeight = endPos[2] - startPos[2]; float height = actualHeight; if (height < 16.0) @@ -1094,7 +785,7 @@ void CBaseNPC_Jump(NextBotGroundLocomotion nextbotLocomotion, float startPos[3], float additionalHeight = 20.0; if (height < 32.0) { - additionalHeight += 8.0; + additionalHeight += 16.0; } float gravity = nextbotLocomotion.GetGravity(); @@ -1112,80 +803,16 @@ void CBaseNPC_Jump(NextBotGroundLocomotion nextbotLocomotion, float startPos[3], jumpVel[2] = speed; - float flJumpSpeed = GetVectorLength(jumpVel, true); - float flMaxSpeed = SquareFloat(650.0); - if (flJumpSpeed > flMaxSpeed) + float jumpSpeed = GetVectorLength(jumpVel, true); + float maxSpeed = SquareFloat(650.0); + if (jumpSpeed > maxSpeed) { - jumpVel[0] *= (flMaxSpeed / flJumpSpeed); - jumpVel[1] *= (flMaxSpeed / flJumpSpeed); - jumpVel[2] *= (flMaxSpeed / flJumpSpeed); + jumpVel[0] *= (maxSpeed / jumpSpeed); + jumpVel[1] *= (maxSpeed / jumpSpeed); + jumpVel[2] *= (maxSpeed / jumpSpeed); } + CBaseEntity(entity).SetLocalOrigin(startPos); nextbotLocomotion.Jump(); nextbotLocomotion.SetVelocity(jumpVel); } - -bool SlenderChaseBoss_OnStuckResolvePath(int slender, float myPos[3], float myAng[3], float goalPosition[3], float returnFloat[3]) -{ - //We are stuck, try to find a free pos to path on the right or left. - int attemp = 1; - while (attemp <= 2) - { - float yawMin, yawMax, yawInc; - if (attemp == 1) //We will first try on the right - { - yawMin = 10.0; - yawMax = 90.0; - yawInc = 5.0; - } - else if (attemp == 2) //Then on the left - { - yawMin = -90.0; - yawMax = -10.0; - yawInc = 5.0; - } - //Note: Actually there's no right and left, it's just to give you an idea on how this will be done. - for(int y=RoundToNearest(yawMin); y<=RoundToNearest(yawMax); y+=RoundToNearest(yawInc)) - { - myAng[1] += float(y); - for(int r=30; r<=300; r+=10) - { - float freePos[3]; - GetPositionForward(myPos, myAng, freePos, float(r)); - - // Perform a line of sight check to avoid spawning players in unreachable map locations. - TR_TraceRayFilter(myPos, freePos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - - if (!TR_DidHit()) - { - // Perform an other line of sight check to avoid moving in a area that can't reach the original goal! - TR_TraceRayFilter(freePos, goalPosition, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - if (!TR_DidHit()) - { - TR_TraceHullFilter(myPos, freePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, TraceRayDontHitAnyEntity, slender); - - if (!TR_DidHit()) - { - returnFloat = freePos; - myAng[1] -= float(y); - return true; - } - } - else - { - //This free position can't bring us to the goal position. Give up on this angle. - break; - } - } - else - { - // We hit something that breaks the line of sight. Give up on this angle. - break; - } - } - myAng[1] -= float(y); - } - attemp++; - } - return false; -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp index 80932d9f..919ce32f 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp @@ -102,7 +102,6 @@ void SlenderStatueBossProcessMovement(int bossEnt) INextBot bot = npc.GetBot(); CBaseNPC_Locomotion loco = npc.GetLocomotion(); - CBaseCombatCharacter combatChar = CBaseCombatCharacter(bossEnt); char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, slenderProfile, sizeof(slenderProfile)); @@ -185,7 +184,7 @@ void SlenderStatueBossProcessMovement(int bossEnt) if (g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, JUMP_OVER_GAP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, CLIMB_UP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, LADDER_UP, 120.0)) { - CBaseNPC_Jump(loco, myPos, pathEndPos); + CBaseNPC_Jump(bossEnt, loco, myPos, pathEndPos); } } } @@ -232,146 +231,16 @@ void SlenderStatueBossProcessMovement(int bossEnt) if (g_LastStuckTime[bossIndex] == 0.0) g_LastStuckTime[bossIndex] = GetGameTime(); if ((g_LastStuckTime[bossIndex] <= GetGameTime()-1.0 || loco.GetStuckDuration() >= 1.0) && - !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex]) + !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex] && g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT) { - float movePos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && - g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].PriorSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetPos(movePos); - } - else - { - g_BossPathFollower[bossIndex].GetClosestPosition(myPos, movePos); - } - bool pathResolved = false; - - if (SlenderChaseBoss_OnStuckResolvePath(bossEnt, myPos, myEyeAng, movePos, movePos)) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - CNavArea area = combatChar.GetLastKnownArea(); - if (area == NULL_AREA && segment != NULL_PATH_SEGMENT) - { - area = segment.area; - } - if (area != NULL_AREA) - { - area.GetCenter(movePos); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = false; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - movePos[2] += loco.GetStepHeight(); - if (!IsSpaceOccupied(movePos, HULL_HUMAN_MINS, HULL_HUMAN_MAXS, bossEnt)) - { - pathResolved = true; - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - } - } - if (!pathResolved) - { - if (!SF_IsSlaughterRunMap() && !g_RestartSessionEnabled) - { - RemoveSlender(bossIndex);//We are stuck there's no way out for us, unspawn, players are just going to abuse that we are stuck. - } - else - { - ArrayList spawnPoint = new ArrayList(); - float teleportPos[3]; - int ent = -1, spawnTeam = 0; - while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) - { - spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); - if (spawnTeam == TFTeam_Red) - { - spawnPoint.Push(ent); - } - - } - ent = -1; - if (spawnPoint.Length > 0) - { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); - } - - delete spawnPoint; - - if (IsValidEntity(ent)) - { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - TeleportEntity(bossEnt, teleportPos, NULL_VECTOR, NULL_VECTOR); - float origin[3]; - loco.SetVelocity(origin); - } - else - { - RemoveSlender(bossIndex); - } - } - } - } - } - } - if (pathResolved) - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; - } + float movePos[3], segmentPos[3]; + Segment segment = g_BossPathFollower[bossIndex].FirstSegment(); + segment.GetPos(segmentPos); + g_BossPathFollower[bossIndex].GetClosestPosition(segmentPos, movePos, segment, 50.0); + TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); + + loco.ClearStuckStatus(); + g_LastStuckTime[bossIndex] = 0.0; } } } diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index d3277644..5b4a2217 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -82,7 +82,7 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo CreateTimer(0.1, Timer_ReplacePlayerRagdoll, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !client.HasEscaped) + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !client.HasEscaped && !client.IsEliminated) { for (int reds = 1; reds < MaxClients; reds++) { diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index c19f66bc..71eaf262 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -276,7 +276,7 @@ float EntityDistanceFromEntity(int ent1, int ent2) GetEntPropVector(ent2, Prop_Data, "m_vecAbsOrigin", hisPos); return GetVectorSquareMagnitude(myPos, hisPos); } - +/* bool IsSpaceOccupied(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitEntity, entity); @@ -285,7 +285,7 @@ bool IsSpaceOccupied(const float pos[3], const float mins[3], const float maxs[3 delete trace; return hit; } - +*/ bool IsSpaceOccupiedIgnorePlayers(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitPlayersOrEntity, entity); From 63a5146f6f94705bd617faa5f82966c4c93dc0b7 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:50:29 -0700 Subject: [PATCH 10/86] 1.1 --- addons/sourcemod/scripting/sf2/menus.sp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 5fce440d..b1b656a6 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -298,8 +298,8 @@ void RandomizeVoteMenu() bool normal = StrContains(buffer, "1") != -1; bool hard = StrContains(buffer, "2") != -1; bool insane = StrContains(buffer, "3") != -1; - bool nightmare = StrContains(buffer, "4") != -1; - bool apollyon = StrContains(buffer, "5") != -1; + bool nightmare = false; + bool apollyon = false; bool random = StrContains(buffer, "6") != -1; switch (GetRandomInt(1, 6))//There's probably a better way to do this but I was tired. @@ -539,8 +539,8 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action, int param1, int p bool normal = StrContains(info, "1") != -1; bool hard = StrContains(info, "2") != -1; bool insane = StrContains(info, "3") != -1; - bool nightmare = StrContains(info, "4") != -1; - bool apollyon = StrContains(info, "5") != -1; + bool nightmare = false; + bool apollyon = false; int count = ((normal ? 1 : 0) + (hard ? 1 : 0) + (insane ? 1 : 0) + (nightmare ? 1 : 0) + (apollyon ? 1 : 0)); From 3da288292aaccc05efe8dc921cd8b6b99ce19208 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:29:29 -0700 Subject: [PATCH 11/86] Version 1.2 --- .../include/sf2/profiles/profiles.inc | 8 + .../include/sf2/profiles/profiles_chaser.inc | 2 +- addons/sourcemod/scripting/sf2.sp | 34 ++-- addons/sourcemod/scripting/sf2/client.sp | 4 +- .../scripting/sf2/client/deathcam.sp | 2 +- .../scripting/sf2/client/ghostmode.sp | 4 +- .../sourcemod/scripting/sf2/client/music.sp | 2 +- .../sourcemod/scripting/sf2/client/proxy.sp | 6 +- .../sourcemod/scripting/sf2/client/static.sp | 2 +- .../sourcemod/scripting/sf2/client/think.sp | 2 +- addons/sourcemod/scripting/sf2/debug.sp | 2 +- .../scripting/sf2/extras/commands.sp | 12 +- .../scripting/sf2/extras/game_events.sp | 10 +- .../sourcemod/scripting/sf2/extras/natives.sp | 1 + .../scripting/sf2/extras/renevant_mode.sp | 4 +- .../sf2/mapentities/sf2_game_text.sp | 2 +- addons/sourcemod/scripting/sf2/menus.sp | 179 +++++++++++++++++- addons/sourcemod/scripting/sf2/npc.sp | 40 ++-- .../sf2/npc/entities/statue/actions/main.sp | 4 +- .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 4 +- .../scripting/sf2/npc/npc_chaser_pathing.sp | 91 ++++++++- .../sourcemod/scripting/sf2/playergroups.sp | 16 +- .../scripting/sf2/playergroups/menus.sp | 8 +- addons/sourcemod/scripting/sf2/pvp.sp | 12 +- .../sourcemod/scripting/sf2/specialround.sp | 24 +-- addons/sourcemod/scripting/sf2/stocks.sp | 14 ++ addons/sourcemod/scripting/sf2/traps.sp | 2 +- 27 files changed, 385 insertions(+), 106 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 85300976..b37a4f6d 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -1395,6 +1395,10 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima if (this.Animations.GetValue(animType, animDataArray)) { + if (animDataArray.Length == 0) + { + return false; + } int randomAnimation; if (preDefinedIndex <= -1) { @@ -1430,6 +1434,10 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima if (this.Gestures.GetValue(animType, gestureDataArray)) { + if (gestureDataArray.Length == 0) + { + return false; + } SF2BossProfileAttackGestureData gestureData; gestureDataArray.GetArray(definedIndex, gestureData, sizeof(gestureData)); diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 6134d88d..185bf08f 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -736,7 +736,7 @@ enum struct SF2ChaserBossProfileData { this.FootstepSenses.AddCount[difficulty] = 1; this.LoudFootstepSenses.AddCount[difficulty] = 2; - this.QuietFootstepSenses.AddCount[difficulty] = 1; + this.QuietFootstepSenses.AddCount[difficulty] = 0; this.VoiceSenses.AddCount[difficulty] = 10; this.WeaponSenses.AddCount[difficulty] = 5; diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index c1429a9c..935fe785 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -710,6 +710,7 @@ ConVar g_UsePlayersForKillFeedConVar; ConVar g_DefaultLegacyHudConVar; ConVar g_DifficultyVoteOptionsConVar; ConVar g_DifficultyVoteRandomConVar; +ConVar g_DifficultyVoteRevoteConVar; ConVar g_DifficultyNoGracePageConVar; ConVar g_FileCheckConVar; @@ -791,6 +792,7 @@ GlobalForward g_OnDifficultyChangeFwd; GlobalForward g_OnClientEnterGameFwd; GlobalForward g_OnGroupEnterGameFwd; GlobalForward g_OnEverythingLoadedFwd; +GlobalForward g_OnDifficultyVoteFinishedFwd; // Private forwards PrivateForward g_OnMapStartPFwd; @@ -939,8 +941,6 @@ public void OnMapStart() PrecacheModel(SF2_FLASHLIGHT_BEAM_MATERIAL); g_FlashlightHaloModel = PrecacheModel(SF2_FLASHLIGHT_HALO_MATERIAL, true); - SetupEntityActions(); - Call_StartForward(g_OnMapStartPFwd); Call_Finish(); } @@ -2301,7 +2301,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) } else if (cvar == g_RaidMapConVar) { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer client = SF2_BasePlayer(i); if (!client.IsValid) @@ -7027,36 +7027,42 @@ static Action Timer_VoteDifficulty(Handle timer, any data) if (timer != g_VoteTimer || IsRoundEnding()) { - delete arrayClients; + if (arrayClients != null) + { + delete arrayClients; + } return Plugin_Stop; } - if (NativeVotes_IsVoteInProgress()) + if (NativeVotes_IsVoteInProgress() || IsVoteInProgress()) { return Plugin_Continue; // There's another vote in progess. Wait. } int clients[MAXTF2PLAYERS] = { -1, ... }; int clientsNum; - for (int i = 0, size = arrayClients.Length; i < size; i++) + if (arrayClients != null) { - int client = GetClientOfUserId(arrayClients.Get(i)); - if (client <= 0) + for (int i = 0, size = arrayClients.Length; i < size; i++) { - continue; + int client = GetClientOfUserId(arrayClients.Get(i)); + if (client <= 0) + { + continue; + } + + clients[clientsNum] = client; + clientsNum++; } - clients[clientsNum] = client; - clientsNum++; + delete arrayClients; } - - delete arrayClients; #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for arrayClients in Timer_VoteDifficulty.", arrayClients); #endif RandomizeVoteMenu(); - VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, 15); + VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, (g_DifficultyVoteRevoteConVar.FloatValue > 0.0) ? 10 : 15); if (GetMenuItemCount(g_MenuVoteDifficulty) == 1) { for (int i = 0; i < clientsNum; i++) diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index d7309762..431ca3c3 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -1637,7 +1637,7 @@ void ClientUpdateListeningFlags(int client, bool reset=false) return; } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (i == client || !IsValidClient(i) || IsClientSourceTV(i)) { @@ -2003,7 +2003,7 @@ void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV = true, bool checkBlink = false) { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index 7e320162..6a2ee4f6 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -175,7 +175,7 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool soundInfo.EmitSound(true, client); GetBossProfileGlobalDeathCamSounds(profile, soundInfo); - for (int i = 0; i < MaxClients; i++) + for (int i = 0; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/client/ghostmode.sp b/addons/sourcemod/scripting/sf2/client/ghostmode.sp index 6cd6f89e..d9228389 100644 --- a/addons/sourcemod/scripting/sf2/client/ghostmode.sp +++ b/addons/sourcemod/scripting/sf2/client/ghostmode.sp @@ -216,7 +216,7 @@ void ClientSetGhostModeState(int client, bool state) } } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -324,7 +324,7 @@ void ClientGhostModeNextTarget(int client, bool ignoreSetting = false) int lastTarget = EntRefToEntIndex(g_PlayerGhostModeTarget[client]); int nextTarget = -1; int firstTarget = -1; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (IsValidClient(i) && (!g_PlayerEliminated[i] || g_PlayerProxy[i]) && !IsClientInGhostMode(i) && !DidClientEscape(i) && IsPlayerAlive(i)) { diff --git a/addons/sourcemod/scripting/sf2/client/music.sp b/addons/sourcemod/scripting/sf2/client/music.sp index eae740bf..47044e28 100644 --- a/addons/sourcemod/scripting/sf2/client/music.sp +++ b/addons/sourcemod/scripting/sf2/client/music.sp @@ -45,7 +45,7 @@ static void OnBossRemoved(int bossIndex) { if (!MusicActive()) { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 90ea62c7..4d6df933 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -205,7 +205,7 @@ Action Timer_ClientForceProxy(Handle timer, any userid) int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; int numProxies = 0; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !g_PlayerEliminated[i]) { @@ -303,7 +303,7 @@ int Menu_ProxyAsk(Handle menu, MenuAction action,int param1,int param2) int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; int numProxies; - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (!IsValidClient(client) || !g_PlayerEliminated[client]) { @@ -906,7 +906,7 @@ Action Timer_ApplyCustomModel(Handle timer, any userid) } } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/client/static.sp b/addons/sourcemod/scripting/sf2/client/static.sp index 849e0d4a..2e1c9023 100644 --- a/addons/sourcemod/scripting/sf2/client/static.sp +++ b/addons/sourcemod/scripting/sf2/client/static.sp @@ -55,7 +55,7 @@ static void OnPlayerEscape(SF2_BasePlayer client) static void OnBossRemoved(int bossIndex) { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index 853f2aca..b413228a 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -779,7 +779,7 @@ Action Timer_ClientAverageUpdate(Handle timer) static int hudColorHealthy[3]; static int hudColorCritical[3] = { 255, 10, 10 }; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/debug.sp b/addons/sourcemod/scripting/sf2/debug.sp index af0a6f84..e18349c4 100644 --- a/addons/sourcemod/scripting/sf2/debug.sp +++ b/addons/sourcemod/scripting/sf2/debug.sp @@ -106,7 +106,7 @@ void SendDebugMessageToPlayers(int debugFlags,int type, const char[] message, an char msg[1024]; VFormat(msg, sizeof(msg), message, 4); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGameEx(i) || IsFakeClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 2a0e430b..dcb53b77 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -219,8 +219,8 @@ public void OnPluginStart() g_DefaultLegacyHudConVar = CreateConVar("sf2_default_legacy_hud", "0", "Set to 1 if the server should enable the legacy hud by default in their settings.", _, true, 0.0, true, 1.0); g_DifficultyVoteOptionsConVar = CreateConVar("sf2_difficulty_vote_options", "1,2,3", "What vote options will appear on the Difficulty vote. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon, 6 = Random"); + g_DifficultyVoteRevoteConVar = CreateConVar("sf2_difficulty_vote_runoff", "0.0", "If the winning vote has less precentage of player votes, do a run-off vote.", _, true, 0.0, true, 1.0); g_DifficultyVoteRandomConVar = CreateConVar("sf2_difficulty_random_vote", "1", "If random vote will use a random vote option instead of a random difficulty.", _, true, 0.0, true, 1.0); - g_DifficultyNoGracePageConVar = CreateConVar("sf2_difficulty_no_grace_pages", "", "On what difficulties will players be unable to collect pages while grace period is active. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon"); g_FileCheckConVar = CreateConVar("sf2_debug_file_checks", "0", "Determines if the gamemode should look for missing files when loading all the bosses. Note that turning this on leads to longer boss loading times.", _, true, 0.0, true, 1.0); @@ -861,7 +861,7 @@ static Action Command_GroupName(int client, int args) GetPlayerGroupName(groupIndex, oldGroupName, sizeof(oldGroupName)); SetPlayerGroupName(groupIndex, groupName); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -2407,7 +2407,7 @@ static Action Command_AllTalkToggle(int client, int args) g_AdminAllTalk[client] = !g_AdminAllTalk[client]; CPrintToChat(client, "{royalblue}%t {default}You will %s hear and speak to all players.", "SF2 Prefix", g_AdminAllTalk[client] ? "now" : "no longer"); - for (int target = 1; target < MaxClients; target++) + for (int target = 1; target <= MaxClients; target++) { ClientUpdateListeningFlags(target); } @@ -2418,7 +2418,7 @@ static Action Command_AllTalkOn(int client, int args) { g_AdminAllTalk[client] = true; - for (int target = 1; target < MaxClients; target++) + for (int target = 1; target <= MaxClients; target++) { ClientUpdateListeningFlags(target); } @@ -2429,7 +2429,7 @@ static Action Command_AllTalkOff(int client, int args) { g_AdminAllTalk[client] = false; - for (int target = 1; target < MaxClients; target++) + for (int target = 1; target <= MaxClients; target++) { ClientUpdateListeningFlags(target); } @@ -2604,7 +2604,7 @@ static Action Command_MaxPlayersOverride(int client, int args) static Action Command_SpecialRoundMode(int client, int args) { g_SpecialRoundBehaviorConVar.BoolValue = !g_SpecialRoundBehaviorConVar.BoolValue; - CPrintToChat(client, "{royalblue}%t {default}Set the special rounds to %s.", "SF2 Prefix", g_BlockSuicideDuringRoundConVar.BoolValue ? "always reset upon the next round" : "keep going until all players have played a special round"); + CPrintToChat(client, "{royalblue}%t {default}Set the special rounds to %s.", "SF2 Prefix", g_SpecialRoundBehaviorConVar.BoolValue ? "always reset upon the next round" : "keep going until all players have played a special round"); return Plugin_Handled; } diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 0f49ce2a..10e790a5 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -48,8 +48,12 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) } // Refresh players. - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { + if (!IsValidClient(i)) + { + continue; + } ClientSetGhostModeState(i, false); g_PlayerPlaying[i] = false; @@ -115,7 +119,7 @@ Action Event_WinPanel(Event event, const char[] name, bool dontBroadcast) char cappers[7]; int i = 0; - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && DidClientEscape(client) && i < 7) { @@ -168,7 +172,7 @@ Action Event_RoundEnd(Handle event, const char[] name, bool dB) SF_FailEnd(); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 8e1f1c06..139e3507 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -58,6 +58,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnClientEnterGameFwd = new GlobalForward("SF2_OnClientEnterGame", ET_Hook, Param_Cell); g_OnGroupEnterGameFwd = new GlobalForward("SF2_OnGroupEnterGame", ET_Hook, Param_Cell); g_OnEverythingLoadedFwd = new GlobalForward("SF2_OnEverythingLoaded", ET_Ignore); + g_OnDifficultyVoteFinishedFwd = new GlobalForward("SF2_OnDifficultyVoteFinished", ET_Ignore, Param_Cell, Param_Cell); CreateNative("SF2_GetConfig", Native_GetConfig); CreateNative("SF2_IsRunning", Native_IsRunning); diff --git a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp b/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp index 7cc2615c..94c3e8ec 100644 --- a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp +++ b/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp @@ -78,7 +78,7 @@ static void Renevant_BroadcastMessage(const char[] message, int params, ...) char format[512]; VFormat(format, sizeof(format), message, params); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || (g_PlayerEliminated[i] && !IsClientInGhostMode(i)) || !IsPlayerAlive(i)) { @@ -313,7 +313,7 @@ static void Renevant_DoWaveAction(RenevantWave action) case RenevantWave_MarkForDeath: { g_RenevantMarkForDeath = true; - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (!IsValidClient(client)) { diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp index fad86cb2..cd27d124 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp @@ -202,7 +202,7 @@ static void InputDisplay(int entity, int activator, int caller, const char[] val { int spawnFlags = thisEnt.GetProp(Prop_Data, "m_spawnflags"); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index b1b656a6..eacd2774 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -26,6 +26,9 @@ Handle g_MenuCredits4; Handle g_MenuCredits5; Handle g_MenuUpdate; +static ArrayList g_Voters; +static bool g_IsRunOff; + #include "sf2/playergroups/menus.sp" #include "sf2/pvp/menus.sp" @@ -290,7 +293,15 @@ void RandomizeVoteMenu() delete g_MenuVoteDifficulty; } - g_MenuVoteDifficulty = CreateMenu(Menu_VoteDifficulty); + if (g_Voters != null) + { + delete g_Voters; + } + + g_Voters = new ArrayList(2); + g_IsRunOff = false; + + g_MenuVoteDifficulty = CreateMenu((g_DifficultyVoteRevoteConVar.FloatValue > 0.0) ? Menu_VoteNoneDifficulty : Menu_VoteDifficulty); SetMenuTitle(g_MenuVoteDifficulty, "%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); g_DifficultyVoteOptionsConVar.GetString(buffer, sizeof(buffer)); @@ -298,8 +309,8 @@ void RandomizeVoteMenu() bool normal = StrContains(buffer, "1") != -1; bool hard = StrContains(buffer, "2") != -1; bool insane = StrContains(buffer, "3") != -1; - bool nightmare = false; - bool apollyon = false; + bool nightmare = StrContains(buffer, "4") != -1; + bool apollyon = StrContains(buffer, "5") != -1; bool random = StrContains(buffer, "6") != -1; switch (GetRandomInt(1, 6))//There's probably a better way to do this but I was tired. @@ -420,7 +431,7 @@ void RandomizeVoteMenu() AddMenuItem(g_MenuVoteDifficulty, "4", buffer); } - if(apollyon) + if (apollyon) { FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); AddMenuItem(g_MenuVoteDifficulty, "5", buffer); @@ -431,6 +442,11 @@ void RandomizeVoteMenu() FormatEx(buffer, sizeof(buffer), "%t", "SF2 Random Difficulty"); AddMenuItem(g_MenuVoteDifficulty, "", buffer); } + + if (g_DifficultyVoteRevoteConVar.FloatValue > 0.0) + { + SetVoteResultCallback(g_MenuVoteDifficulty, Menu_VoteRunoffDifficulty); + } } static int Menu_Main(Handle menu, MenuAction action, int param1, int param2) @@ -480,8 +496,136 @@ static int Menu_Main(Handle menu, MenuAction action, int param1, int param2) return 0; } -static int Menu_VoteDifficulty(Handle menu, MenuAction action, int param1, int param2) +static void Menu_VoteRunoffDifficulty(Menu oldmenu, int votes, int clients, const int[][] clientInfo, int items, const int[][] itemInfo) { + if (items > 1) + { + float runoff = g_DifficultyVoteRevoteConVar.FloatValue; + if (runoff) + { + if (float(itemInfo[0][VOTEINFO_ITEM_VOTES]) <= (votes * runoff)) + { + g_IsRunOff = true; + Menu newmenu = new Menu(Menu_VoteDifficulty); + newmenu.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); + + ArrayList list = new ArrayList(); + for(int i = 0; i < items; i++) + { + if (itemInfo[i][VOTEINFO_ITEM_VOTES] >= itemInfo[1][VOTEINFO_ITEM_VOTES]) + { + list.Push(itemInfo[i][VOTEINFO_ITEM_INDEX]); + } + } + + char data[64], display[64]; + int length = list.Length; + for (int i = 0; i < length; i++) + { + int index = list.Get(i); + oldmenu.GetItem(index, data, sizeof(data), _, display, sizeof(display)); + newmenu.AddItem(data, display); + } + + delete list; + + list = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i) || g_PlayerEliminated[i]) + { + continue; + } + + list.Push(GetClientUserId(i)); + } + + if (list.Length) + { + //delete g_VoteTimer; + g_VoteTimer = CreateTimer(1.0, Timer_ReVoteDifficulty, list, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE); + } + else + { + delete list; + } + + if (GetMenuItemCount(g_MenuVoteDifficulty) > 1) + { + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(false); + Call_Finish(); + } + g_Voters.Clear(); + + delete g_MenuVoteDifficulty; + g_MenuVoteDifficulty = newmenu; + + return; + } + } + } + + Menu_VoteDifficulty(oldmenu, MenuAction_VoteEnd, itemInfo[0][VOTEINFO_ITEM_INDEX], 0); +} + +static Action Timer_ReVoteDifficulty(Handle timer, ArrayList arrayClients) +{ + if (timer != g_VoteTimer || IsRoundEnding()) + { + g_VoteTimer = null; + return Plugin_Stop; + } + + if (IsVoteInProgress()) + { + return Plugin_Continue; // There's another vote in progess. Wait. + } + + int clients[MAXPLAYERS + 1] = { -1, ... }; + int clientsNum; + for (int i = 0, size = arrayClients.Length; i < size; i++) + { + int client = GetClientOfUserId(arrayClients.Get(i)); + if (client <= 0) + { + continue; + } + + clients[clientsNum] = client; + clientsNum++; + } + + VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, 7); + g_VoteTimer = null; + return Plugin_Stop; +} + +static int Menu_VoteNoneDifficulty(Menu menu, MenuAction action,int param1,int param2) +{ + if (action == MenuAction_Select) + { + char option[64]; + GetMenuItem(menu, param2, option, sizeof(option)); + int index = g_Voters.Push(param1); + int value = StringToInt(option); + g_Voters.Set(index, value, 1); + } + return 0; +} + +static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param2) +{ + if (action == MenuAction_Select) + { + char option[64]; + GetMenuItem(menu, param2, option, sizeof(option)); + int index = g_Voters.Push(param1); + int value = StringToInt(option); + g_Voters.Set(index, value, 1); + } if (action == MenuAction_VoteEnd && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && !g_RestartSessionConVar.BoolValue) { int clientInGame = 0, clientCallingForNightmare = 0; @@ -539,8 +683,8 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action, int param1, int p bool normal = StrContains(info, "1") != -1; bool hard = StrContains(info, "2") != -1; bool insane = StrContains(info, "3") != -1; - bool nightmare = false; - bool apollyon = false; + bool nightmare = StrContains(info, "4") != -1; + bool apollyon = StrContains(info, "5") != -1; int count = ((normal ? 1 : 0) + (hard ? 1 : 0) + (insane ? 1 : 0) + (nightmare ? 1 : 0) + (apollyon ? 1 : 0)); @@ -650,7 +794,10 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action, int param1, int p FormatEx(display, sizeof(display), "%t!", "SF2 Nightmare Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); strcopy(color, sizeof(color), "{valve}"); - PlayNightmareSound(); + for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) + { + EmitSoundToAll(g_SoundNightmareMode[i]); + } SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); } case Difficulty_Apollyon: @@ -658,7 +805,10 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action, int param1, int p FormatEx(display, sizeof(display), "%t!", "SF2 Apollyon Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); strcopy(color, sizeof(color), "{darkgray}"); - PlayNightmareSound(); + for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) + { + EmitSoundToAll(g_SoundNightmareMode[i]); + } SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); if (rng) { @@ -716,6 +866,15 @@ static int Menu_VoteDifficulty(Handle menu, MenuAction action, int param1, int p } CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished", color, display); + char checker[64]; + g_DifficultyVoteRandomConVar.GetString(checker, sizeof(checker)); + if (GetMenuItemCount(g_MenuVoteDifficulty) > 1) + { + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(g_IsRunOff); + Call_Finish(); + } } return 0; } @@ -1723,4 +1882,4 @@ static int Menu_BossList(Handle menu, MenuAction action, int param1, int param2) } } return 0; -} +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 3b9f832a..fbb22077 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -213,6 +213,7 @@ void NPCSetDeathCamEnabled(int npcIndex, bool state) void NPCInitialize() { NPCChaserInitialize(); + SetupEntityActions(); } void NPCOnConfigsExecuted() @@ -1441,7 +1442,7 @@ bool NPCShouldHearEntity(int npcIndex, int entity, SoundType soundType) bool NPCAreAvailablePlayersAlive() { int number = 0; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (IsValidClient(i) && IsPlayerAlive(i) && GetClientTeam(i) == TFTeam_Red && !g_PlayerEliminated[i] && !DidClientEscape(i)) { @@ -1546,7 +1547,7 @@ bool SlenderCanRemove(int bossIndex) float slenderPos[3], buffer[3]; SlenderGetAbsOrigin(bossIndex, slenderPos); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsPlayerAlive(i) || @@ -1919,7 +1920,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl g_SlenderCustomOutroSong[Npc.Index] = GetBossProfileOutroMusicState(profile); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { g_PlayerLastChaseBossEncounterTime[i][Npc.Index] = -1.0; g_SlenderTeleportPlayersRestTime[Npc.Index][i] = -1.0; @@ -1961,7 +1962,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl { SF2BossProfileSoundInfo soundInfo; GetBossProfileIntroSounds(profile, soundInfo); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -2125,7 +2126,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl if ((g_NpcAllowMusicOnDifficulty[Npc.Index] & g_DifficultyConVar.IntValue) && time > 0.0) { timerMusic = CreateTimer(time, BossMusic, Npc.Index, TIMER_FLAG_NO_MAPCHANGE); - for(int client = 1; client < MaxClients; client++) + for(int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && (!g_PlayerEliminated[client] || IsClientInGhostMode(client))) { @@ -2453,7 +2454,11 @@ void ChangeAllSlenderModels() NPCGetProfile(npcIndex, profile, sizeof(profile)); GetSlenderModel(npcIndex, _, buffer, sizeof(buffer)); SetEntityModel(slender, buffer); - SetEntityModel(EntRefToEntIndex(g_NpcGlowEntity[npcIndex]), buffer); + int glow = EntRefToEntIndex(g_NpcGlowEntity[npcIndex]); + if (glow && glow != INVALID_ENT_REFERENCE) + { + SetEntityModel(EntRefToEntIndex(g_NpcGlowEntity[npcIndex]), buffer); + } if (NPCGetModelSkinMax(npcIndex) > 0) { int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(npcIndex)); @@ -2533,7 +2538,7 @@ void RemoveProfile(int bossIndex) NPCChaserOnRemoveProfile(bossIndex); // Clean up on the clients. - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { g_SlenderLastFoundPlayer[bossIndex][i] = -1.0; g_PlayerLastChaseBossEncounterTime[i][bossIndex] = -1.0; @@ -2895,7 +2900,7 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) g_SlenderChaseDeathPosition[bossIndex][i] = 0.0; } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { g_SlenderLastFoundPlayer[bossIndex][i] = -1.0; @@ -4302,17 +4307,16 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) randomArea = areaArray.Get(randomCell); area = collector.Get(randomArea); area.GetCenter(spawnPos); - spawnPos[2] += 15.0; float traceMins[3]; - traceMins[0] = g_SlenderDetectMins[bossIndex][0]; - traceMins[1] = g_SlenderDetectMins[bossIndex][1]; + traceMins[0] = g_SlenderDetectMins[bossIndex][0] - 5.0; + traceMins[1] = g_SlenderDetectMins[bossIndex][1] - 5.0; traceMins[2] = 0.0; float traceMaxs[3]; - traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0]; - traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1]; - traceMaxs[2] = 0.0; + traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0] + 5.0; + traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1] + 5.0; + traceMaxs[2] = g_SlenderDetectMaxs[bossIndex][2]; TR_TraceHullFilter(spawnPos, spawnPos, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitEntity); if (TR_DidHit()) @@ -4328,7 +4332,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) else { canSpawn = true; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsPlayerAlive(i) || IsClientInGhostMode(i) || g_PlayerEliminated[i] || g_PlayerProxy[i] || DidClientEscape(i)) @@ -4438,7 +4442,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) { bool didJumpScare = false; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsPlayerAlive(i) || g_PlayerEliminated[i] || IsClientInGhostMode(i)) { @@ -4755,7 +4759,7 @@ bool IsNPCVisibleToPlayer(int npcIndex,int client, bool checkFOV=true, bool chec bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) { - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (IsNPCVisibleToPlayer(npcIndex, client, checkFOV, checkBlink, checkEliminated)) { @@ -5153,7 +5157,7 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint bool tooNear = false; // Check minimum range with players. - for (int teleportClient = 1; teleportClient < MaxClients; teleportClient++) + for (int teleportClient = 1; teleportClient <= MaxClients; teleportClient++) { if (!IsValidClient(teleportClient) || !IsPlayerAlive(teleportClient) || diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp index 5c5fb2d7..a424187c 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/main.sp @@ -74,7 +74,7 @@ static int Update(SF2_StatueBaseAction action, SF2_StatueEntity actor, float int { ArrayList arrayRaidTargets = new ArrayList(); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsPlayerAlive(i) || @@ -103,7 +103,7 @@ static int Update(SF2_StatueBaseAction action, SF2_StatueEntity actor, float int int bestPlayer = -1; ArrayList array = new ArrayList(); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsPlayerAlive(i) || IsClientInDeathCam(i) || (!attackWaiters && g_PlayerEliminated[i]) || diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index c42d76d4..efb7deef 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -1590,7 +1590,7 @@ void NPCChaserOnSelectProfile(int npcIndex, bool invincible) float stunHealthPerPlayer = GetChaserProfileStunHealthPerPlayer(profile); int count; - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { SF2_BasePlayer player = SF2_BasePlayer(client); if (player.IsValid && player.IsEliminated) @@ -2276,7 +2276,7 @@ SF2_BasePlayer NPCChaserGetClosestPlayer(int slender) SF2_BasePlayer closestTarget = SF2_INVALID_PLAYER; float searchRadius = NPCGetSearchRadius(bossIndex, difficulty); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer client = SF2_BasePlayer(i); if (!client.IsValid || client.IsInGhostMode || client.IsProxy || !client.IsAlive || client.IsEliminated) diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp index 628c08b1..26e8b3e6 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp @@ -599,16 +599,89 @@ void SlenderChaseBossProcessMovement(int bossEnt) } if ((g_LastStuckTime[bossIndex] <= gameTime - 1.0 || loco.GetStuckDuration() >= 1.0) && - !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex] && g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT) + !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex]) { - float movePos[3], segmentPos[3]; - Segment segment = g_BossPathFollower[bossIndex].FirstSegment(); - segment.GetPos(segmentPos); - g_BossPathFollower[bossIndex].GetClosestPosition(segmentPos, movePos, segment, 50.0); - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); + float destination[3]; + CNavArea area = TheNavMesh.GetNearestNavArea(g_LastPos[bossIndex], _, _, _, false); + area.GetCenter(destination); + float tempMaxs[3]; + npc.GetBodyMaxs(tempMaxs); + float traceMins[3]; + traceMins[0] = g_SlenderDetectMins[bossIndex][0] - 5.0; + traceMins[1] = g_SlenderDetectMins[bossIndex][1] - 5.0; + traceMins[2] = 0.0; + + float traceMaxs[3]; + traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0] + 5.0; + traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1] + 5.0; + traceMaxs[2] = tempMaxs[2]; + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (GetVectorSquareMagnitude(destination, g_LastPos[bossIndex]) <= SquareFloat(16.0) || TR_DidHit()) + { + CursorData cursor = g_BossPathFollower[bossIndex].GetCursorData(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).GetCostSoFar() < 16.0) + { + continue; + } + if (cursor.segmentPrior != NULL_PATH_SEGMENT) + { + CNavArea segmentArea = cursor.segmentPrior.area; + if (segmentArea == collector.Get(i)) + { + continue; + } + } + float navPos[3]; + collector.Get(i).GetCenter(navPos); + if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + int randomArea = 0, randomCell = 0; + areaArray.Resize(validAreaCount); + area = NULL_AREA; + while (validAreaCount > 1) + { + randomCell = GetRandomInt(0, validAreaCount - 1); + randomArea = areaArray.Get(randomCell); + area = collector.Get(randomArea); + area.GetCenter(destination); + + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (TR_DidHit()) + { + area = NULL_AREA; + validAreaCount--; + int findValue = areaArray.FindValue(randomCell); + if (findValue != -1) + { + areaArray.Erase(findValue); + } + } + else + { + break; + } + } + + delete collector; + delete areaArray; + } + g_BossPathFollower[bossIndex].GetClosestPosition(destination, destination, g_BossPathFollower[bossIndex].FirstSegment(), 128.0); + TeleportEntity(bossEnt, destination, NULL_VECTOR, NULL_VECTOR); loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; + g_LastStuckTime[bossIndex] = gameTime; } } } @@ -624,6 +697,10 @@ void SlenderChaseBossProcessMovement(int bossEnt) } } } + else + { + g_LastStuckTime[bossIndex] = gameTime; + } if (gameTime >= g_SlenderNextFootstepSound[bossIndex]) { diff --git a/addons/sourcemod/scripting/sf2/playergroups.sp b/addons/sourcemod/scripting/sf2/playergroups.sp index 4d88ea09..2fde6339 100644 --- a/addons/sourcemod/scripting/sf2/playergroups.sp +++ b/addons/sourcemod/scripting/sf2/playergroups.sp @@ -251,7 +251,7 @@ static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1, { SetPlayerGroupQueuePoints(groupIndex, 0); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -296,7 +296,7 @@ void CheckPlayerGroup(int groupIndex) else { // Remove any person that isn't participating. - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (ClientGetPlayerGroup(i) == groupIndex) { @@ -333,7 +333,7 @@ void CheckPlayerGroup(int groupIndex) CPrintToChat(groupLeader, "%T", "SF2 Group Has Too Many Members", groupLeader); } - for (int i = 1, count; i < MaxClients && count < excessMemberCount; i++) + for (int i = 1, count; i <= MaxClients && count < excessMemberCount; i++) { if (!IsValidClient(i)) { @@ -401,7 +401,7 @@ int CreatePlayerGroup() SetPlayerGroupName(index, ""); SetPlayerGroupPlaying(index, false); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { SetPlayerGroupInvitedPlayer(index, i, false); SetPlayerGroupInvitedPlayerCount(index, i, 0); @@ -434,7 +434,7 @@ void ClearPlayerGroupMembers(int groupIndex) return; } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (ClientGetPlayerGroup(i) == groupIndex) { @@ -481,7 +481,7 @@ int GetPlayerGroupMemberCount(int groupIndex) { int count; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -525,7 +525,7 @@ void SetPlayerGroupLeader(int groupIndex,int groupLeader) char name[MAX_NAME_LENGTH]; FormatEx(name, sizeof(name), "%N", groupLeader); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (groupLeader == i || !IsValidClient(i)) { @@ -546,7 +546,7 @@ int PlayerGroupFindNewLeader(int groupIndex) return -1; } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/playergroups/menus.sp b/addons/sourcemod/scripting/sf2/playergroups/menus.sp index 936a3052..49284e58 100644 --- a/addons/sourcemod/scripting/sf2/playergroups/menus.sp +++ b/addons/sourcemod/scripting/sf2/playergroups/menus.sp @@ -298,7 +298,7 @@ void DisplayViewGroupMembersMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayViewGroupMembersMenuToClient.", playersHandle); #endif - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -389,7 +389,7 @@ void DisplaySetGroupLeaderMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplaySetGroupLeaderMenuToClient.", playersHandle); #endif - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -507,7 +507,7 @@ void DisplayKickFromGroupMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayKickFromGroupMenuToClient.", playersHandle); #endif - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -682,7 +682,7 @@ void DisplayInviteToGroupMenuToClient(int client) #if defined DEBUG SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayInviteToGroupMenuToClient.", playersHandle); #endif - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsClientParticipating(i)) { diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index 1d6265f0..286ec7b0 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -1125,13 +1125,19 @@ MRESReturn PvP_GetWeaponCustomDamageType(int weapon, int client, int &customDama static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHookReturn returnHandle, DHookParam params) { - if (!IsValidEdict(projectile) || !IsValidEntity(projectile)) + if (!g_Enabled) { return MRES_Ignored; } - returnHandle.Value = true; - return MRES_Supercede; + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (IsValidClient(owner) && (IsRoundInWarmup() || IsClientInPvP(owner))) + { + returnHandle.Value = true; + return MRES_Supercede; + } + + return MRES_Ignored; } // API diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index 5b4a2217..84d4946c 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -84,7 +84,7 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !client.HasEscaped && !client.IsEliminated) { - for (int reds = 1; reds < MaxClients; reds++) + for (int reds = 1; reds <= MaxClients; reds++) { if (!IsValidClient(reds) || g_PlayerEliminated[reds] || @@ -596,7 +596,7 @@ static ArrayList SpecialEnabledList() char snatcher[64] = "hypersnatcher_nerfed"; int players; - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client]) { @@ -862,7 +862,7 @@ void SpecialRoundStart() } case SPECIALROUND_THANATOPHOBIA: { - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (!IsValidClient(client)) { @@ -1047,7 +1047,7 @@ void SpecialRoundStart() } case SPECIALROUND_CLASSSCRAMBLE: { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || g_PlayerEliminated[i] || GetClientTeam(i) != TFTeam_Red || DidClientEscape(i) || !IsPlayerAlive(i)) { @@ -1277,7 +1277,7 @@ void SpecialRoundStart() SF_RemoveSpecialRound(SPECIALROUND_LIGHTSOUT); SF_AddSpecialRound(SPECIALROUND_NIGHTVISION); } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -1359,7 +1359,7 @@ void SpecialRoundStart() SlenderAddGlow(npcIndex, purple); } } - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -1386,7 +1386,7 @@ void SpecialRoundStart() if (nightVision && g_NightvisionType != 1) { g_NightvisionType = 1; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -1410,7 +1410,7 @@ void SpecialRoundStart() } case SPECIALROUND_1UP: { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -1429,7 +1429,7 @@ void SpecialRoundStart() case SPECIALROUND_NOULTRAVISION: { SF_AddSpecialRound(SPECIALROUND_NOULTRAVISION); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -1485,7 +1485,7 @@ void SpecialRoundStart() } case SPECIALROUND_PAGEDETECTOR: { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { @@ -1522,7 +1522,7 @@ void SpecialRoundStart() } case SPECIALROUND_SINGLEPLAYER: { - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client] && !DidClientEscape(client)) { @@ -1790,7 +1790,7 @@ static void SpecialCreateVote() int total = 0; int[] players = new int[MaxClients]; - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 71eaf262..6fdeeaa5 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -1275,6 +1275,20 @@ bool TraceRayDontHitPlayersOrEntity(int entity, int mask, any data) return true; } +bool TraceRayDontHitPlayersOrEntityEx(int entity, int mask, any data) +{ + if (entity == data) + { + return false; + } + if (IsValidClient(entity)) + { + return false; + } + + return true; +} + // ========================================================== // TIMER/CALLBACK FUNCTIONS // ========================================================== diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index a1d9f33e..0a565f57 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -152,7 +152,7 @@ static Action Timer_TrapThink(Handle timer, any entref) if (!g_TrapClosed[trapEntity]) { - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); if (!player.IsValid || From 1564c15c94854a88a9e82bc71eb975112bd86d2d Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:42:07 -0700 Subject: [PATCH 12/86] Update menus.sp --- addons/sourcemod/scripting/sf2/menus.sp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index eacd2774..7437b2d4 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -794,10 +794,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param FormatEx(display, sizeof(display), "%t!", "SF2 Nightmare Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); strcopy(color, sizeof(color), "{valve}"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); } case Difficulty_Apollyon: @@ -805,10 +802,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param FormatEx(display, sizeof(display), "%t!", "SF2 Apollyon Difficulty"); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); strcopy(color, sizeof(color), "{darkgray}"); - for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) - { - EmitSoundToAll(g_SoundNightmareMode[i]); - } + PlayNightmareSound(); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); if (rng) { From f1dbced9c4ccc3316d1e87d803b61ab793b39797 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sun, 15 Oct 2023 04:21:46 -0700 Subject: [PATCH 13/86] Alpha 1.3 --- .../include/sf2/profiles/profiles.inc | 13 +- .../include/sf2/profiles/profiles_chaser.inc | 5 + .../sourcemod/scripting/sf2/client/blink.sp | 4 + .../scripting/sf2/extras/commands.sp | 2 +- addons/sourcemod/scripting/sf2/menus.sp | 275 +++++++----------- addons/sourcemod/scripting/sf2/npc.sp | 20 +- .../sf2/npc/entities/statue/entity.sp | 36 +++ .../scripting/sf2/npc/npc_creeper.sp | 118 ++++++-- .../sourcemod/scripting/sf2/playergroups.sp | 161 ++++++++-- .../scripting/sf2/playergroups/menus.sp | 179 ++++++------ 10 files changed, 476 insertions(+), 337 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index b37a4f6d..07c74d2d 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -1365,6 +1365,11 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima animDataArray.GetArray(j, animData, sizeof(animData)); animData.Destroy(); } + + if (animDataArray != null) + { + delete animDataArray; + } } for (int i = 0; i < gestureSnapshot.Length; i++) @@ -1377,11 +1382,11 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima animDataArray.GetArray(j, gestureData, sizeof(gestureData)); gestureData.Destroy(); } - } - if (animDataArray != null) - { - delete animDataArray; + if (animDataArray != null) + { + delete animDataArray; + } } delete snapshot; diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 185bf08f..4e9fc9af 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -1172,6 +1172,7 @@ enum struct SF2ChaserBossProfileData } soundInfo.Destroy(); } + delete this.AttackSounds; } if (this.HitSounds != null) { @@ -1187,6 +1188,7 @@ enum struct SF2ChaserBossProfileData } soundInfo.Destroy(); } + delete this.HitSounds; } if (this.MissSounds != null) { @@ -1202,6 +1204,7 @@ enum struct SF2ChaserBossProfileData } soundInfo.Destroy(); } + delete this.MissSounds; } if (this.BulletShootSounds != null) { @@ -1217,6 +1220,7 @@ enum struct SF2ChaserBossProfileData } soundInfo.Destroy(); } + delete this.BulletShootSounds; } if (this.ProjectileShootSounds != null) { @@ -1232,6 +1236,7 @@ enum struct SF2ChaserBossProfileData } soundInfo.Destroy(); } + delete this.ProjectileShootSounds; } if (this.DamageResistances != null) diff --git a/addons/sourcemod/scripting/sf2/client/blink.sp b/addons/sourcemod/scripting/sf2/client/blink.sp index 017698af..a7ca01ac 100644 --- a/addons/sourcemod/scripting/sf2/client/blink.sp +++ b/addons/sourcemod/scripting/sf2/client/blink.sp @@ -24,6 +24,10 @@ static void OnPutInServer(SF2_BasePlayer client) static void OnPlayerSpawn(SF2_BasePlayer client) { ClientResetBlink(client.index); + if (!client.IsEliminated) + { + ClientStartDrainingBlinkMeter(client.index); + } } static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index dcb53b77..16c4b86f 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -218,7 +218,7 @@ public void OnPluginStart() g_DefaultLegacyHudConVar = CreateConVar("sf2_default_legacy_hud", "0", "Set to 1 if the server should enable the legacy hud by default in their settings.", _, true, 0.0, true, 1.0); - g_DifficultyVoteOptionsConVar = CreateConVar("sf2_difficulty_vote_options", "1,2,3", "What vote options will appear on the Difficulty vote. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon, 6 = Random"); + g_DifficultyVoteOptionsConVar = CreateConVar("sf2_difficulty_vote_options", "1,2,3", "What vote options will appear on the Difficulty vote. 1 = Normal, 2 = Hard, 3 = Insane, 6 = Random"); g_DifficultyVoteRevoteConVar = CreateConVar("sf2_difficulty_vote_runoff", "0.0", "If the winning vote has less precentage of player votes, do a run-off vote.", _, true, 0.0, true, 1.0); g_DifficultyVoteRandomConVar = CreateConVar("sf2_difficulty_random_vote", "1", "If random vote will use a random vote option instead of a random difficulty.", _, true, 0.0, true, 1.0); g_DifficultyNoGracePageConVar = CreateConVar("sf2_difficulty_no_grace_pages", "", "On what difficulties will players be unable to collect pages while grace period is active. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon"); diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 7437b2d4..fd5dc0ba 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -7,7 +7,7 @@ #pragma semicolon 1 Handle g_MenuMain; -Handle g_MenuVoteDifficulty; +Menu g_MenuVoteDifficulty; Handle g_MenuHelp; Handle g_MenuHelpObjective; Handle g_MenuHelpObjective2; @@ -301,148 +301,124 @@ void RandomizeVoteMenu() g_Voters = new ArrayList(2); g_IsRunOff = false; - g_MenuVoteDifficulty = CreateMenu((g_DifficultyVoteRevoteConVar.FloatValue > 0.0) ? Menu_VoteNoneDifficulty : Menu_VoteDifficulty); - SetMenuTitle(g_MenuVoteDifficulty, "%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); + g_MenuVoteDifficulty = new Menu((g_DifficultyVoteRevoteConVar.FloatValue > 0.0) ? Menu_VoteNoneDifficulty : Menu_VoteDifficulty); + g_MenuVoteDifficulty.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); g_DifficultyVoteOptionsConVar.GetString(buffer, sizeof(buffer)); bool normal = StrContains(buffer, "1") != -1; bool hard = StrContains(buffer, "2") != -1; bool insane = StrContains(buffer, "3") != -1; - bool nightmare = StrContains(buffer, "4") != -1; - bool apollyon = StrContains(buffer, "5") != -1; bool random = StrContains(buffer, "6") != -1; - switch (GetRandomInt(1, 6))//There's probably a better way to do this but I was tired. + // WHOA there is a better way, and I'm not tired + ArrayList options = new ArrayList(ByteCountToCells(64)); + ArrayList indicies = new ArrayList(); + int index = 0; + if (normal) { - case 1: - { - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - } - case 2: - { - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - } - case 3: + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); + options.PushString(buffer); + indicies.Push(1); + index++; + } + + if (hard) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); + options.PushString(buffer); + indicies.Push(2); + index++; + } + + if (insane) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); + options.PushString(buffer); + indicies.Push(3); + index++; + } + + int participating = 0; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || !IsClientParticipating(i) || IsFakeClient(i)) { - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } + continue; } - case 4: + + participating++; + } + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || g_PlayerEliminated[i]) { - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } + continue; } - case 5: + + int group = ClientGetPlayerGroup(i); + if (!IsPlayerGroupValid(group) || !IsPlayerGroupOptInHarder(group)) { - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - if (normal) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - } - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } + continue; } - case 6: + + if (GetPlayerGroupMemberCount(group) < participating) { - if (insane) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - } - if (hard) - { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - } - if (normal) + if (GetPlayerGroupMemberCount(group) < GetMaxPlayersForRound()) { - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); + continue; } } - } - if (nightmare) - { FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "4", buffer); - } + options.PushString(buffer); + indicies.Push(4); + index++; - if (apollyon) - { FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "5", buffer); + options.PushString(buffer); + indicies.Push(5); + index++; + break; } if (random) { FormatEx(buffer, sizeof(buffer), "%t", "SF2 Random Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "", buffer); + options.PushString(buffer); + indicies.Push(6); + index++; } + // I'm going to say that the random shuffle for the array list sucks, so I'm doing my own + ArrayList actualOptions = new ArrayList(ByteCountToCells(64)); + ArrayList actualIndicies = new ArrayList(); + for (int i = 0; i < options.Length;) + { + int arrayIndex = GetRandomInt(0, options.Length - 1); + char choice[64]; + options.GetString(arrayIndex, choice, sizeof(choice)); + actualOptions.PushString(choice); + actualIndicies.Push(indicies.Get(arrayIndex)); + options.Erase(arrayIndex); + indicies.Erase(arrayIndex); + } + + for (int i = 0; i < actualOptions.Length; i++) + { + int indexToAdd = actualIndicies.Get(i); + char choice[64], value[2]; + actualOptions.GetString(i, choice, sizeof(choice)); + FormatEx(value, sizeof(value), "%d", indexToAdd); + g_MenuVoteDifficulty.AddItem(value, choice); + } + + delete options; + delete indicies; + delete actualOptions; + delete actualIndicies; + if (g_DifficultyVoteRevoteConVar.FloatValue > 0.0) { SetVoteResultCallback(g_MenuVoteDifficulty, Menu_VoteRunoffDifficulty); @@ -616,12 +592,12 @@ static int Menu_VoteNoneDifficulty(Menu menu, MenuAction action,int param1,int p return 0; } -static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param2) +static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { char option[64]; - GetMenuItem(menu, param2, option, sizeof(option)); + menu.GetItem(param2, option, sizeof(option)); int index = g_Voters.Push(param1); int value = StringToInt(option); g_Voters.Set(index, value, 1); @@ -629,7 +605,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param if (action == MenuAction_VoteEnd && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && !g_RestartSessionConVar.BoolValue) { int clientInGame = 0, clientCallingForNightmare = 0; - for (int client = 1; client < MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && !g_PlayerEliminated[client]) { @@ -643,7 +619,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param bool playersCalledForNightmare = (clientInGame == clientCallingForNightmare); char info[64], display[256], color[32], nightmareDisplay[256]; - GetMenuItem(menu, param1, info, sizeof(info), _, display, sizeof(display)); + menu.GetItem(param1, info, sizeof(info), _, display, sizeof(display)); bool rng = false; @@ -651,7 +627,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param { g_DifficultyConVar.SetInt(Difficulty_Insane); } - else if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && !SF_SpecialRound(SPECIALROUND_2DOOM) && + else if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_ESCAPETICKETS) && !SF_SpecialRound(SPECIALROUND_NOGRACE) && !SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) && !SF_SpecialRound(SPECIALROUND_WALLHAX) && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && (GetRandomInt(1, 200) <= 2 || playersCalledForNightmare)) @@ -666,7 +642,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param g_DifficultyConVar.SetInt(Difficulty_Nightmare); } } - else if (IsSpecialRoundRunning() && (SF_SpecialRound(SPECIALROUND_NOGRACE) || SF_SpecialRound(SPECIALROUND_2DOOM))) + else if (IsSpecialRoundRunning() && (SF_SpecialRound(SPECIALROUND_NOGRACE))) { g_DifficultyConVar.SetInt(Difficulty_Hard); } @@ -683,43 +659,15 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param bool normal = StrContains(info, "1") != -1; bool hard = StrContains(info, "2") != -1; bool insane = StrContains(info, "3") != -1; - bool nightmare = StrContains(info, "4") != -1; - bool apollyon = StrContains(info, "5") != -1; - int count = ((normal ? 1 : 0) + (hard ? 1 : 0) + (insane ? 1 : 0) + (nightmare ? 1 : 0) + (apollyon ? 1 : 0)); + int count = ((normal ? 1 : 0) + (hard ? 1 : 0) + (insane ? 1 : 0)); int rand = GetRandomInt(1, count); switch (rand) { - case 5: - { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); - } - case 4: - { - if (nightmare) - { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); - } - else - { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); - } - } case 3: { - if (insane) - { - g_DifficultyConVar.SetInt(Difficulty_Insane); - } - else if (nightmare) - { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); - } - else - { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); - } + g_DifficultyConVar.SetInt(Difficulty_Insane); } case 2: { @@ -727,17 +675,9 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param { g_DifficultyConVar.SetInt(Difficulty_Hard); } - else if (insane) - { - g_DifficultyConVar.SetInt(Difficulty_Insane); - } - else if (nightmare) - { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); - } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + g_DifficultyConVar.SetInt(Difficulty_Insane); } } case 1: @@ -750,35 +690,22 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param { g_DifficultyConVar.SetInt(Difficulty_Hard); } - else if (insane) - { - g_DifficultyConVar.SetInt(Difficulty_Insane); - } - else if (nightmare) - { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); - } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + g_DifficultyConVar.SetInt(Difficulty_Insane); } } } } else { - g_DifficultyConVar.SetInt(GetRandomInt(Difficulty_Normal, Difficulty_Apollyon)); + g_DifficultyConVar.SetInt(GetRandomInt(Difficulty_Normal, Difficulty_Insane)); } } int difficulty = g_DifficultyConVar.IntValue; switch (difficulty) { - case Difficulty_Easy: - { - FormatEx(display, sizeof(display), "%t", "SF2 Easy Difficulty"); - strcopy(color, sizeof(color), "{green}"); - } case Difficulty_Hard: { FormatEx(display, sizeof(display), "%t", "SF2 Hard Difficulty"); @@ -862,7 +789,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action,int param1,int param CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished", color, display); char checker[64]; g_DifficultyVoteRandomConVar.GetString(checker, sizeof(checker)); - if (GetMenuItemCount(g_MenuVoteDifficulty) > 1) + if (g_MenuVoteDifficulty.ItemCount > 1) { Call_StartForward(g_OnDifficultyVoteFinishedFwd); Call_PushCell(g_Voters); diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index fbb22077..85b0cfc9 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -2685,11 +2685,6 @@ void RemoveProfile(int bossIndex) void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) { - if (!IsRoundPlaying()) - { - return; - } - if (SF_IsRenevantMap() && GetRoundState() != SF2RoundState_Escape) { return; // Stop spawning bosses before all pages are picked up in Renevant. @@ -2715,7 +2710,7 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) { case SF2BossType_Statue: { - entity = Spawn_Statue(bossIndex); + entity = Spawn_Statue(Npc, truePos, trueAng); } case SF2BossType_Chaser: { @@ -2783,12 +2778,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) npcEntity.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); npcEntity.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); } - for (int i = 0; i < 2; i++) - { - // Yes we don't want to increase the Z values - pathingBoxMin[i] -= 5.0; - pathingBoxMax[i] += 5.0; - } npcBoss.SetBodyMins(pathingBoxMin); npcBoss.SetBodyMaxs(pathingBoxMax); @@ -4199,9 +4188,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) { return Plugin_Continue; } + int bossEnt = controller.EntIndex; if (controller.TeleportType == 2) { - int bossEnt = controller.EntIndex; if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) { if (NPCGetType(bossIndex) == SF2BossType_Chaser) @@ -4261,6 +4250,11 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) } else { + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) + { + controller.UnSpawn(); + return Plugin_Continue; + } float teleportMinRange = g_SlenderTeleportMinRange[bossIndex][difficulty]; bool shouldBeBehindObstruction = false; if (NPCGetTeleportType(bossIndex) == 2) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index 599e9c54..9bec65a6 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -9,6 +9,16 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter return view_as(entity); } + public bool IsValid() + { + if (!CBaseCombatCharacter(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + public static void Initialize() { g_Factory = new CEntityFactory("sf2_statue_npc", OnCreate, OnRemove); @@ -107,11 +117,30 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter char buffer[PLATFORM_MAX_PATH]; GetSlenderModel(controller.Index, _, buffer, sizeof(buffer)); + statue.SetModel(buffer); + statue.SetRenderMode(view_as(g_SlenderRenderMode[controller.Index])); + statue.SetRenderFx(view_as(g_SlenderRenderFX[controller.Index])); + statue.SetRenderColor(g_SlenderRenderColor[controller.Index][0], g_SlenderRenderColor[controller.Index][1], + g_SlenderRenderColor[controller.Index][2], g_SlenderRenderColor[controller.Index][3]); + + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + float scaleModel = controller.ModelScale * 0.5; + statue.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); + } + else + { + statue.SetPropFloat(Prop_Send, "m_flModelScale", controller.ModelScale); + } CBaseNPC npc = TheNPCs.FindNPCByEntIndex(statue.index); CBaseNPC_Locomotion loco = npc.GetLocomotion(); npc.flMaxYawRate = 0.0; + npc.flStepSize = 18.0; + npc.flGravity = g_Gravity; + npc.flDeathDropHeight = 99999.0; + npc.flJumpHeight = 512.0; loco.SetCallback(LocomotionCallback_IsAbleToJumpAcrossGaps, CanJumpAcrossGaps); loco.SetCallback(LocomotionCallback_IsAbleToClimb, CanJumpAcrossGaps); loco.SetCallback(LocomotionCallback_JumpAcrossGap, JumpAcrossGapsCBase); @@ -128,6 +157,13 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter SDKHook(statue.iEnt, SDKHook_ThinkPost, SlenderStatueSetNextThink); SDKHook(statue.iEnt, SDKHook_Think, SlenderStatueBossProcessMovement); + + statue.Teleport(pos, ang, NULL_VECTOR); + + statue.Spawn(); + statue.Activate(); + + return statue; } } diff --git a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp index 919ce32f..c537d1e2 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp @@ -5,8 +5,6 @@ #pragma semicolon 1 -static bool g_NpcStatueMoving[MAX_BOSSES]; - static float g_NpcStatueChaseDuration[MAX_BOSSES][Difficulty_Max]; static float g_NpcStatueChaseDurationAddMin[MAX_BOSSES][Difficulty_Max]; static float g_NpcStatueChaseDurationAddMax[MAX_BOSSES][Difficulty_Max]; @@ -54,12 +52,11 @@ void NPCStatueOnSelectProfile(const char[] profile, int npcIndex) } } -CBaseCombatCharacter Spawn_Statue(int npcIndex) +CBaseCombatCharacter Spawn_Statue(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) { - g_NpcStatueMoving[npcIndex] = false; - g_SlenderTarget[npcIndex] = INVALID_ENT_REFERENCE; - g_SlenderStatueIdleLifeTime[npcIndex] = GetGameTime() + g_NpcStatueIdleLifetime[npcIndex][GetLocalGlobalDifficulty()]; - return CBaseCombatCharacter(CreateEntityByName("sf2_statue_npc")); + g_SlenderTarget[controller.Index] = INVALID_ENT_REFERENCE; + g_SlenderStatueIdleLifeTime[controller.Index] = GetGameTime() + g_NpcStatueIdleLifetime[controller.Index][GetLocalGlobalDifficulty()]; + return SF2_StatueEntity.Create(controller, pos, ang); } void SlenderStatueBossProcessMovement(int bossEnt) @@ -102,6 +99,7 @@ void SlenderStatueBossProcessMovement(int bossEnt) INextBot bot = npc.GetBot(); CBaseNPC_Locomotion loco = npc.GetLocomotion(); + SF2_StatueEntity statue = SF2_StatueEntity(bossEnt); char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, slenderProfile, sizeof(slenderProfile)); @@ -119,7 +117,7 @@ void SlenderStatueBossProcessMovement(int bossEnt) float speed = originalSpeed; speed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - if (!g_SlenderInDeathcam[bossIndex] && g_NpcStatueMoving[bossIndex]) + if (!g_SlenderInDeathcam[bossIndex] && statue.IsMoving) { npc.flWalkSpeed = speed * 10.0; npc.flRunSpeed = speed * 10.0; @@ -134,9 +132,10 @@ void SlenderStatueBossProcessMovement(int bossEnt) // Process angles. bool changeAngle = false; float posToAt[3]; - if (g_NpcStatueMoving[bossIndex]) + g_SlenderTarget[bossIndex] = EnsureEntRef(statue.Target); + if (statue.IsMoving) { - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); + int target = statue.Target; if (target && target != INVALID_ENT_REFERENCE) { changeAngle = true; @@ -160,7 +159,7 @@ void SlenderStatueBossProcessMovement(int bossEnt) if (!g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) { - if (g_NpcStatueMoving[bossIndex]) + if (statue.IsMoving) { loco.Run(); g_BossPathFollower[bossIndex].Update(bot); @@ -170,7 +169,7 @@ void SlenderStatueBossProcessMovement(int bossEnt) { loco.Stop(); } - if (loco.IsOnGround() && !loco.IsClimbingOrJumping() && g_NpcStatueMoving[bossIndex]) + if (loco.IsOnGround() && !loco.IsClimbingOrJumping() && statue.IsMoving) { float pathNodePos[3], pathEndPos[3]; Segment segment; @@ -217,7 +216,7 @@ void SlenderStatueBossProcessMovement(int bossEnt) g_NpcVelocityCancel[bossIndex] = false; } - if (g_NpcStatueMoving[bossIndex] && !g_SlenderInDeathcam[bossIndex] && GetGameTime() < g_NpcTimeUntilAbandon[bossIndex] && (GetGameTime() - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) + if (statue.IsMoving && !g_SlenderInDeathcam[bossIndex] && GetGameTime() < g_NpcTimeUntilAbandon[bossIndex] && (GetGameTime() - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) { bool runUnstuck = true; if (runUnstuck) @@ -228,19 +227,94 @@ void SlenderStatueBossProcessMovement(int bossEnt) if (!blockingProp) { - if (g_LastStuckTime[bossIndex] == 0.0) g_LastStuckTime[bossIndex] = GetGameTime(); + if (g_LastStuckTime[bossIndex] == 0.0) + { + g_LastStuckTime[bossIndex] = GetGameTime(); + } - if ((g_LastStuckTime[bossIndex] <= GetGameTime()-1.0 || loco.GetStuckDuration() >= 1.0) && - !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex] && g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT) + if ((g_LastStuckTime[bossIndex] <= GetGameTime() - 1.0 || loco.GetStuckDuration() >= 1.0)) { - float movePos[3], segmentPos[3]; - Segment segment = g_BossPathFollower[bossIndex].FirstSegment(); - segment.GetPos(segmentPos); - g_BossPathFollower[bossIndex].GetClosestPosition(segmentPos, movePos, segment, 50.0); - TeleportEntity(bossEnt, movePos, NULL_VECTOR, NULL_VECTOR); + float destination[3]; + CNavArea area = TheNavMesh.GetNearestNavArea(g_LastPos[bossIndex], _, _, _, false); + area.GetCenter(destination); + float tempMaxs[3]; + npc.GetBodyMaxs(tempMaxs); + float traceMins[3]; + traceMins[0] = g_SlenderDetectMins[bossIndex][0]; + traceMins[1] = g_SlenderDetectMins[bossIndex][1]; + traceMins[2] = 0.0; + + float traceMaxs[3]; + traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0]; + traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1]; + traceMaxs[2] = tempMaxs[2]; + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (GetVectorSquareMagnitude(destination, g_LastPos[bossIndex]) <= SquareFloat(16.0) || TR_DidHit()) + { + CursorData cursor = g_BossPathFollower[bossIndex].GetCursorData(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).GetCostSoFar() < 16.0) + { + continue; + } + if (cursor.segmentPrior != NULL_PATH_SEGMENT) + { + CNavArea segmentArea = cursor.segmentPrior.area; + if (segmentArea == collector.Get(i)) + { + continue; + } + } + float navPos[3]; + collector.Get(i).GetCenter(navPos); + if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + int randomArea = 0, randomCell = 0; + areaArray.Resize(validAreaCount); + area = NULL_AREA; + while (validAreaCount > 1) + { + randomCell = GetRandomInt(0, validAreaCount - 1); + randomArea = areaArray.Get(randomCell); + area = collector.Get(randomArea); + area.GetCenter(destination); + + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (TR_DidHit()) + { + area = NULL_AREA; + validAreaCount--; + int findValue = areaArray.FindValue(randomCell); + if (findValue != -1) + { + areaArray.Erase(findValue); + } + } + else + { + break; + } + } + + delete collector; + delete areaArray; + } + g_BossPathFollower[bossIndex].GetClosestPosition(destination, destination, g_BossPathFollower[bossIndex].FirstSegment(), 128.0); + TeleportEntity(bossEnt, destination, NULL_VECTOR, NULL_VECTOR); loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; + g_LastStuckTime[bossIndex] = GetGameTime(); } } } diff --git a/addons/sourcemod/scripting/sf2/playergroups.sp b/addons/sourcemod/scripting/sf2/playergroups.sp index 2fde6339..a95d07af 100644 --- a/addons/sourcemod/scripting/sf2/playergroups.sp +++ b/addons/sourcemod/scripting/sf2/playergroups.sp @@ -15,6 +15,7 @@ static int g_PlayerGroupLeader[SF2_MAX_PLAYER_GROUPS] = { -1, ... }; static int g_PlayerGroupID[SF2_MAX_PLAYER_GROUPS] = { -1, ... }; static int g_PlayerGroupQueuePoints[SF2_MAX_PLAYER_GROUPS]; static int g_PlayerGroupPlaying[SF2_MAX_PLAYER_GROUPS] = { false, ... }; +static bool g_PlayerGroupOptInHarder[SF2_MAX_PLAYER_GROUPS] = { false, ... }; static StringMap g_PlayerGroupNames; static bool g_PlayerGroupInvitedPlayer[SF2_MAX_PLAYER_GROUPS][MAXTF2PLAYERS]; static int g_PlayerGroupInvitedPlayerCount[SF2_MAX_PLAYER_GROUPS][MAXTF2PLAYERS]; @@ -43,7 +44,7 @@ int GetPlayerGroupFromID(int groupID) return -1; } -void SendPlayerGroupInvitation(int client,int groupID,int inviter=-1) +void SendPlayerGroupInvitation(int client, int groupID, int inviter = -1) { if (!IsValidClient(client) || !IsClientParticipating(client)) { @@ -133,18 +134,18 @@ void SendPlayerGroupInvitation(int client,int groupID,int inviter=-1) strcopy(leaderName, sizeof(leaderName), "nobody"); } - Handle menu = CreateMenu(Menu_GroupInvite); - SetMenuTitle(menu, "%t%T\n \n", "SF2 Prefix", "SF2 Group Invite Menu Description", client, leaderName, groupName); + Menu menu = new Menu(Menu_GroupInvite); + menu.SetTitle("%t%T\n \n", "SF2 Prefix", "SF2 Group Invite Menu Description", client, leaderName, groupName); char groupIDString[64]; FormatEx(groupIDString, sizeof(groupIDString), "%d", groupID); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, groupIDString, buffer); + menu.AddItem(groupIDString, buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); - DisplayMenu(menu, client, 10); + menu.AddItem("0", buffer); + menu.Display(client, 10); SetPlayerGroupInvitedPlayer(groupIndex, client, true); SetPlayerGroupInvitedPlayerCount(groupIndex, client, GetPlayerGroupInvitedPlayerCount(groupIndex, client) + 1); @@ -156,7 +157,7 @@ void SendPlayerGroupInvitation(int client,int groupID,int inviter=-1) } } -static int Menu_GroupInvite(Handle menu, MenuAction action,int param1,int param2) +static int Menu_GroupInvite(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -167,7 +168,7 @@ static int Menu_GroupInvite(Handle menu, MenuAction action,int param1,int param2 if (param2 == 0) { char groupID[64]; - GetMenuItem(menu, param2, groupID, sizeof(groupID)); + menu.GetItem(param2, groupID, sizeof(groupID)); int groupIndex = GetPlayerGroupFromID(StringToInt(groupID)); if (IsPlayerGroupActive(groupIndex)) { @@ -219,20 +220,20 @@ void DisplayResetGroupQueuePointsMenuToClient(int client) return; } - Handle menu = CreateMenu(Menu_ResetGroupQueuePoints); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Reset Group Queue Points Menu Title", client, "SF2 Reset Group Queue Points Menu Description", client); + Menu menu = new Menu(Menu_ResetGroupQueuePoints); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Reset Group Queue Points Menu Title", client, "SF2 Reset Group Queue Points Menu Description", client); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ResetGroupQueuePoints(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -240,7 +241,10 @@ static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1, } else if (action == MenuAction_Cancel) { - if (param2 == MenuCancel_ExitBack) DisplayAdminGroupMenuToClient(param1); + if (param2 == MenuCancel_ExitBack) + { + DisplayAdminGroupMenuToClient(param1); + } } else if (action == MenuAction_Select) { @@ -274,6 +278,99 @@ static int Menu_ResetGroupQueuePoints(Handle menu, MenuAction action,int param1, return 0; } +void DisplayOptInHardDifficultiesToClient(int client) +{ + int groupIndex = ClientGetPlayerGroup(client); + if (!IsPlayerGroupActive(groupIndex)) + { + // His group isn't valid anymore. Take him back to the main menu. + DisplayGroupMainMenuToClient(client); + CPrintToChat(client, "%T", "SF2 Group Does Not Exist", client); + return; + } + + if (GetPlayerGroupLeader(groupIndex) != client) + { + DisplayAdminGroupMenuToClient(client); + CPrintToChat(client, "%T", "SF2 Not Group Leader", client); + return; + } + + Menu menu = new Menu(Menu_OptInHarderDifficulties); + menu.SetTitle("%t Opt in to voting higher difficulties\n \nAre you sure you want your group to opt in being able to\nvote for Nightmare + Apollyon? Your group must\nbe 100%% filled to be able to vote for Nightmare + Apollyon.\n \n", "SF2 Prefix"); + + char buffer[256]; + FormatEx(buffer, sizeof(buffer), "Opt in"); + menu.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "Opt out"); + menu.AddItem("0", buffer); + + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); +} + +static int Menu_OptInHarderDifficulties(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayAdminGroupMenuToClient(param1); + } + } + else if (action == MenuAction_Select) + { + int groupIndex = ClientGetPlayerGroup(param1); + if (!IsPlayerGroupActive(groupIndex) || GetPlayerGroupLeader(groupIndex) != param1) + { + CPrintToChat(param1, "%T", "SF2 Not Group Leader", param1); + } + else + { + switch (param2) + { + case 0: + { + SetPlayerGroupOptInHarder(groupIndex, true); + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + if (ClientGetPlayerGroup(i) == groupIndex) + { + CPrintToChat(i, "Your group is now opted in to vote for Nightmare + Apollyon."); + } + } + } + case 1: + { + SetPlayerGroupOptInHarder(groupIndex, false); + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + if (ClientGetPlayerGroup(i) == groupIndex) + { + CPrintToChat(i, "Your group is now opted out to vote for Nightmare + Apollyon."); + } + } + } + } + } + + DisplayAdminGroupMenuToClient(param1); + } + return 0; +} + void CheckPlayerGroup(int groupIndex) { if (!IsPlayerGroupActive(groupIndex)) @@ -400,6 +497,7 @@ int CreatePlayerGroup() SetPlayerGroupLeader(index, -1); SetPlayerGroupName(index, ""); SetPlayerGroupPlaying(index, false); + SetPlayerGroupOptInHarder(index, false); for (int i = 1; i <= MaxClients; i++) { @@ -425,6 +523,7 @@ void RemovePlayerGroup(int groupIndex) SetPlayerGroupLeader(groupIndex, -1); g_PlayerGroupActive[groupIndex] = false; SetPlayerGroupID(groupIndex, -1); + SetPlayerGroupOptInHarder(groupIndex, false); } void ClearPlayerGroupMembers(int groupIndex) @@ -462,9 +561,9 @@ int GetPlayerGroupID(int groupIndex) return g_PlayerGroupID[groupIndex]; } -void SetPlayerGroupID(int groupIndex,int iID) +void SetPlayerGroupID(int groupIndex, int id) { - g_PlayerGroupID[groupIndex] = iID; + g_PlayerGroupID[groupIndex] = id; } bool IsPlayerGroupActive(int groupIndex) @@ -507,6 +606,16 @@ void SetPlayerGroupPlaying(int groupIndex, bool toggle) g_PlayerGroupPlaying[groupIndex] = toggle; } +bool IsPlayerGroupOptInHarder(int groupIndex) +{ + return (IsPlayerGroupActive(groupIndex) && g_PlayerGroupOptInHarder[groupIndex]); +} + +void SetPlayerGroupOptInHarder(int groupIndex, bool value) +{ + g_PlayerGroupOptInHarder[groupIndex] = value; +} + int GetPlayerGroupLeader(int groupIndex) { return g_PlayerGroupLeader[groupIndex]; @@ -578,17 +687,17 @@ bool HasPlayerGroupInvitedPlayer(int groupIndex,int client) return g_PlayerGroupInvitedPlayer[groupIndex][client]; } */ -void SetPlayerGroupInvitedPlayer(int groupIndex,int client, bool toggle) +void SetPlayerGroupInvitedPlayer(int groupIndex, int client, bool toggle) { g_PlayerGroupInvitedPlayer[groupIndex][client] = toggle; } -int GetPlayerGroupInvitedPlayerCount(int groupIndex,int client) +int GetPlayerGroupInvitedPlayerCount(int groupIndex, int client) { return g_PlayerGroupInvitedPlayerCount[groupIndex][client]; } -void SetPlayerGroupInvitedPlayerCount(int groupIndex,int client,int amount) +void SetPlayerGroupInvitedPlayerCount(int groupIndex, int client, int amount) { g_PlayerGroupInvitedPlayerCount[groupIndex][client] = amount; } @@ -598,9 +707,9 @@ float GetPlayerGroupInvitedPlayerTime(int groupIndex,int client) return g_PlayerGroupInvitedPlayerTime[groupIndex][client]; } -void SetPlayerGroupInvitedPlayerTime(int groupIndex,int client, float flTime) +void SetPlayerGroupInvitedPlayerTime(int groupIndex, int client, float time) { - g_PlayerGroupInvitedPlayerTime[groupIndex][client] = flTime; + g_PlayerGroupInvitedPlayerTime[groupIndex][client] = time; } int ClientGetPlayerGroup(int client) @@ -608,7 +717,7 @@ int ClientGetPlayerGroup(int client) return g_PlayerCurrentGroup[client]; } -void ClientSetPlayerGroup(int client,int groupIndex) +void ClientSetPlayerGroup(int client, int groupIndex) { int oldPlayerGroup = ClientGetPlayerGroup(client); if (oldPlayerGroup == groupIndex) @@ -631,7 +740,7 @@ void ClientSetPlayerGroup(int client,int groupIndex) GetPlayerGroupName(oldPlayerGroup, groupName, sizeof(groupName)); CPrintToChat(client, "%T", "SF2 Left Group", client, groupName); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (i == client || !IsValidClient(i)) { @@ -675,7 +784,7 @@ void ClientSetPlayerGroup(int client,int groupIndex) GetPlayerGroupName(groupIndex, groupName, sizeof(groupName)); CPrintToChat(client, "%T", "SF2 Joined Group", client, groupName); - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (i == client || !IsValidClient(i)) { diff --git a/addons/sourcemod/scripting/sf2/playergroups/menus.sp b/addons/sourcemod/scripting/sf2/playergroups/menus.sp index 49284e58..7d148115 100644 --- a/addons/sourcemod/scripting/sf2/playergroups/menus.sp +++ b/addons/sourcemod/scripting/sf2/playergroups/menus.sp @@ -8,8 +8,8 @@ void DisplayGroupMainMenuToClient(int client) { - Handle menu = CreateMenu(Menu_GroupMain); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Group Main Menu Title", client, "SF2 Group Main Menu Description", client); + Menu menu = new Menu(Menu_GroupMain); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Group Main Menu Title", client, "SF2 Group Main Menu Description", client); int groupIndex = ClientGetPlayerGroup(client); bool groupIsActive = IsPlayerGroupActive(groupIndex); @@ -24,18 +24,18 @@ void DisplayGroupMainMenuToClient(int client) Format(buffer, sizeof(buffer), "%T", "SF2 View Current Group Info Menu Title", client); } - AddMenuItem(menu, "0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + menu.AddItem("0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); Format(buffer, sizeof(buffer), "%T", "SF2 Create Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupIsActive ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem("0", buffer, groupIsActive ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); Format(buffer, sizeof(buffer), "%T", "SF2 Leave Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + menu.AddItem("0", buffer, groupIsActive ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_GroupMain(Handle menu, MenuAction action,int param1,int param2) +static int Menu_GroupMain(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -43,7 +43,10 @@ static int Menu_GroupMain(Handle menu, MenuAction action,int param1,int param2) } else if (action == MenuAction_Cancel) { - if (param2 == MenuCancel_ExitBack) DisplayMenu(g_MenuMain, param1, 30); + if (param2 == MenuCancel_ExitBack) + { + DisplayMenu(g_MenuMain, param1, 30); + } } else if (action == MenuAction_Select) { @@ -77,19 +80,19 @@ void DisplayCreateGroupMenuToClient(int client) return; } - Handle menu = CreateMenu(Menu_CreateGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Create Group Menu Title", client, "SF2 Create Group Menu Description", client, GetMaxPlayersForRound(), g_PlayerQueuePoints[client]); + Menu menu = new Menu(Menu_CreateGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Create Group Menu Title", client, "SF2 Create Group Menu Description", client, GetMaxPlayersForRound(), g_PlayerQueuePoints[client]); char buffer[256]; Format(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); Format(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_CreateGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_CreateGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -146,19 +149,19 @@ void DisplayLeaveGroupMenuToClient(int client) char groupName[SF2_MAX_PLAYER_GROUP_NAME_LENGTH]; GetPlayerGroupName(groupIndex, groupName, sizeof(groupName)); - Handle menu = CreateMenu(Menu_LeaveGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Leave Group Menu Title", client, "SF2 Leave Group Menu Description", client, groupName); + Menu menu = new Menu(Menu_LeaveGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Leave Group Menu Title", client, "SF2 Leave Group Menu Description", client, groupName); char buffer[256]; Format(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); Format(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_LeaveGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_LeaveGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -207,28 +210,30 @@ void DisplayAdminGroupMenuToClient(int client) int maxPlayers = GetMaxPlayersForRound(); int queuePoints = GetPlayerGroupQueuePoints(groupIndex); - Handle menu = CreateMenu(Menu_AdminGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Admin Group Menu Title", client, "SF2 Admin Group Menu Description", client, groupName, leaderName, memberCount, maxPlayers, queuePoints); + Menu menu = new Menu(Menu_AdminGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Admin Group Menu Title", client, "SF2 Admin Group Menu Description", client, groupName, leaderName, memberCount, maxPlayers, queuePoints); char buffer[256]; - Format(buffer, sizeof(buffer), "%T", "SF2 View Group Members Menu Title", client); - AddMenuItem(menu, "0", buffer); - Format(buffer, sizeof(buffer), "%T", "SF2 Set Group Name Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Set Group Leader Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Invite To Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client && memberCount < maxPlayers ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Kick From Group Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client && memberCount > 1 ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - Format(buffer, sizeof(buffer), "%T", "SF2 Reset Group Queue Points Menu Title", client); - AddMenuItem(menu, "0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 View Group Members Menu Title", client); + menu.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Set Group Name Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Set Group Leader Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Invite To Group Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client && memberCount < maxPlayers ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Kick From Group Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client && memberCount > 1 ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "%T", "SF2 Reset Group Queue Points Menu Title", client); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + FormatEx(buffer, sizeof(buffer), "Opt in for Nightmare + Apollyon voting"); + menu.AddItem("0", buffer, groupLeader == client ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_AdminGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_AdminGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -272,6 +277,10 @@ static int Menu_AdminGroup(Handle menu, MenuAction action,int param1,int param2) { DisplayResetGroupQueuePointsMenuToClient(param1); } + case 6: + { + DisplayOptInHardDifficultiesToClient(param1); + } } } else @@ -295,9 +304,6 @@ void DisplayViewGroupMembersMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayViewGroupMembersMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -317,8 +323,8 @@ void DisplayViewGroupMembersMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_ViewGroupMembers); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, "SF2 View Group Members Menu Description", client); + Menu menu = new Menu(Menu_ViewGroupMembers); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, "SF2 View Group Members Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -328,11 +334,11 @@ void DisplayViewGroupMembersMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -342,12 +348,9 @@ void DisplayViewGroupMembersMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplayViewGroupMembersMenuToClient.", playersHandle); - #endif } -static int Menu_ViewGroupMembers(Handle menu, MenuAction action,int param1,int param2) +static int Menu_ViewGroupMembers(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -386,9 +389,6 @@ void DisplaySetGroupLeaderMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplaySetGroupLeaderMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -412,8 +412,8 @@ void DisplaySetGroupLeaderMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_SetGroupLeader); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Leader Menu Title", client, "SF2 Set Group Leader Menu Description", client); + Menu menu = new Menu(Menu_SetGroupLeader); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Leader Menu Title", client, "SF2 Set Group Leader Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -423,11 +423,11 @@ void DisplaySetGroupLeaderMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -437,12 +437,9 @@ void DisplaySetGroupLeaderMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplaySetGroupLeaderMenuToClient.", playersHandle); - #endif } -static int Menu_SetGroupLeader(Handle menu, MenuAction action,int param1,int param2) +static int Menu_SetGroupLeader(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -461,7 +458,7 @@ static int Menu_SetGroupLeader(Handle menu, MenuAction action,int param1,int par if (IsPlayerGroupActive(groupIndex) && GetPlayerGroupLeader(groupIndex) == param1) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); int userid = StringToInt(info); int player = GetClientOfUserId(userid); @@ -504,9 +501,6 @@ void DisplayKickFromGroupMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayKickFromGroupMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -530,8 +524,8 @@ void DisplayKickFromGroupMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_KickFromGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Kick From Group Menu Title", client, "SF2 Kick From Group Menu Description", client); + Menu menu = new Menu(Menu_KickFromGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Kick From Group Menu Title", client, "SF2 Kick From Group Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -541,11 +535,11 @@ void DisplayKickFromGroupMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -555,12 +549,9 @@ void DisplayKickFromGroupMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplayKickFromGroupMenuToClient.", playersHandle); - #endif } -static int Menu_KickFromGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_KickFromGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -579,7 +570,7 @@ static int Menu_KickFromGroup(Handle menu, MenuAction action,int param1,int para if (IsPlayerGroupActive(groupIndex) && GetPlayerGroupLeader(groupIndex) == param1) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); int userid = StringToInt(info); int player = GetClientOfUserId(userid); @@ -623,16 +614,16 @@ void DisplaySetGroupNameMenuToClient(int client) return; } - Handle menu = CreateMenu(Menu_SetGroupName); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Name Menu Title", client, "SF2 Set Group Name Menu Description", client); + Menu menu = new Menu(Menu_SetGroupName); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Set Group Name Menu Title", client, "SF2 Set Group Name Menu Description", client); char buffer[256]; Format(buffer, sizeof(buffer), "%T", "Back", client); - AddMenuItem(menu, "0", buffer); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.AddItem("0", buffer); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_SetGroupName(Handle menu, MenuAction action,int param1,int param2) +static int Menu_SetGroupName(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -679,9 +670,6 @@ void DisplayInviteToGroupMenuToClient(int client) } ArrayList playersHandle = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersHandle in DisplayInviteToGroupMenuToClient.", playersHandle); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || !IsClientParticipating(i)) @@ -705,8 +693,8 @@ void DisplayInviteToGroupMenuToClient(int client) int playerCount = playersHandle.Length; if (playerCount) { - Handle menu = CreateMenu(Menu_InviteToGroup); - SetMenuTitle(menu, "%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Invite To Group Menu Title", client, "SF2 Invite To Group Menu Description", client); + Menu menu = new Menu(Menu_InviteToGroup); + menu.SetTitle("%t%T\n \n%T\n \n", "SF2 Prefix", "SF2 Invite To Group Menu Title", client, "SF2 Invite To Group Menu Description", client); char userId[32]; char name[MAX_NAME_LENGTH]; @@ -716,11 +704,11 @@ void DisplayInviteToGroupMenuToClient(int client) int clientArray = playersHandle.Get(i); FormatEx(userId, sizeof(userId), "%d", GetClientUserId(clientArray)); FormatEx(name, sizeof(name), "%N", clientArray); - AddMenuItem(menu, userId, name); + menu.AddItem(userId, name); } - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } else { @@ -730,12 +718,9 @@ void DisplayInviteToGroupMenuToClient(int client) } delete playersHandle; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersHandle in DisplayInviteToGroupMenuToClient.", playersHandle); - #endif } -static int Menu_InviteToGroup(Handle menu, MenuAction action,int param1,int param2) +static int Menu_InviteToGroup(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -754,10 +739,10 @@ static int Menu_InviteToGroup(Handle menu, MenuAction action,int param1,int para if (IsPlayerGroupActive(groupIndex) && GetPlayerGroupLeader(groupIndex) == param1) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); int userid = StringToInt(info); - int iInvitedPlayer = GetClientOfUserId(userid); - SendPlayerGroupInvitation(iInvitedPlayer, GetPlayerGroupID(groupIndex), param1); + int invitedPlayer = GetClientOfUserId(userid); + SendPlayerGroupInvitation(invitedPlayer, GetPlayerGroupID(groupIndex), param1); } DisplayInviteToGroupMenuToClient(param1); From 5a3d1419d8903819a1a6a135e23679a8c6ffba0b Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Tue, 7 Nov 2023 03:24:36 -0700 Subject: [PATCH 14/86] 1.8.0 Alpha 2 --- addons/sourcemod/configs/sf2/changelog.cfg | 36 + addons/sourcemod/configs/sf2/class_stats.cfg | 28 +- .../sourcemod/configs/sf2/specialrounds.cfg | 34 +- addons/sourcemod/gamedata/sf2.txt | 25 +- addons/sourcemod/scripting/include/sf2.inc | 1975 +++++-- .../scripting/include/sf2/defines.inc | 8 +- .../include/sf2/profiles/profiles.inc | 874 ++- .../include/sf2/profiles/profiles_chaser.inc | 2706 ++++++--- .../include/sf2/profiles/profiles_statue.inc | 13 +- .../scripting/include/sf2/stocks.inc | 131 + addons/sourcemod/scripting/sf2.sp | 1088 ++-- addons/sourcemod/scripting/sf2/adminmenu.sp | 735 ++- addons/sourcemod/scripting/sf2/anticamping.sp | 2 +- addons/sourcemod/scripting/sf2/changelog.sp | 132 + .../sourcemod/scripting/sf2/classconfigs.sp | 6 +- addons/sourcemod/scripting/sf2/client.sp | 123 +- .../sourcemod/scripting/sf2/client/blink.sp | 81 +- .../scripting/sf2/client/breathing.sp | 2 +- .../scripting/sf2/client/deathcam.sp | 400 +- .../scripting/sf2/client/flashlight.sp | 203 +- .../scripting/sf2/client/ghostmode.sp | 8 +- .../sourcemod/scripting/sf2/client/hints.sp | 7 +- .../sourcemod/scripting/sf2/client/music.sp | 184 +- .../sourcemod/scripting/sf2/client/proxy.sp | 264 +- .../sourcemod/scripting/sf2/client/sprint.sp | 146 +- .../sourcemod/scripting/sf2/client/static.sp | 4 +- .../sourcemod/scripting/sf2/client/think.sp | 249 +- .../scripting/sf2/client/ultravision.sp | 2 +- addons/sourcemod/scripting/sf2/debug.sp | 151 +- addons/sourcemod/scripting/sf2/effects.sp | 1003 ++-- .../scripting/sf2/entities/initialize.sp | 14 + .../projectiles/sf2_arrow_projectile.sp | 165 + .../projectiles/sf2_baseball_projectile.sp | 160 + .../projectiles/sf2_cow_mangler_projectile.sp | 50 + .../projectiles/sf2_fireball_projectile.sp | 67 + .../projectiles/sf2_grenade_projectile.sp | 356 ++ .../projectiles/sf2_iceball_projectile.sp | 118 + .../projectiles/sf2_rocket_projectile.sp | 55 + .../sf2_sentry_rocket_projectile.sp | 53 + .../sf2/entities/sf2_base_projectile.sp | 498 ++ .../scripting/sf2/extras/commands.sp | 239 +- .../scripting/sf2/extras/game_events.sp | 271 +- .../sourcemod/scripting/sf2/extras/natives.sp | 627 +-- .../sf2/functionclients/client_flashlight.sp | 1083 ---- .../sf2/functionclients/client_hints.sp | 69 - .../sf2/functionclients/client_music.sp | 1693 ------ .../functionclients/client_proxy_functions.sp | 2018 ------- .../sf2/functionclients/clients_think.sp | 1718 ------ .../renevant.sp} | 39 +- addons/sourcemod/scripting/sf2/mapentities.sp | 21 +- .../sf2/mapentities/sf2_boss_maker.sp | 24 +- .../sf2/mapentities/sf2_game_text.sp | 16 +- .../sf2/mapentities/sf2_gamerules.sp | 31 +- .../sf2/mapentities/sf2_info_boss_spawn.sp | 3 +- .../sf2/mapentities/sf2_info_page_music.sp | 4 +- .../sf2/mapentities/sf2_info_page_spawn.sp | 8 +- .../sf2_info_player_escapespawn.sp | 1 + .../mapentities/sf2_info_player_proxyspawn.sp | 4 +- .../mapentities/sf2_info_player_pvpspawn.sp | 1 + .../sf2/mapentities/sf2_logic_arena.sp | 1 + .../sf2/mapentities/sf2_point_spotlight.sp | 398 ++ .../sf2/mapentities/sf2_trigger_pve.sp | 78 + addons/sourcemod/scripting/sf2/menus.sp | 886 +-- addons/sourcemod/scripting/sf2/methodmaps.sp | 1852 +++++-- addons/sourcemod/scripting/sf2/npc.sp | 2502 ++++----- .../sf2/npc/entities/base/actions/deathcam.sp | 71 + .../base/actions/playsequenceandwait.sp | 151 + .../scripting/sf2/npc/entities/base/entity.sp | 880 +++ .../sf2/npc/entities/chaser/actions/alert.sp | 486 ++ .../sf2/npc/entities/chaser/actions/attack.sp | 415 ++ .../entities/chaser/actions/attacklayer.sp | 43 + .../entities/chaser/actions/attacks/bullet.sp | 350 ++ .../entities/chaser/actions/attacks/custom.sp | 41 + .../chaser/actions/attacks/explosive_dance.sp | 215 + .../chaser/actions/attacks/laser_beam.sp | 269 + .../entities/chaser/actions/attacks/melee.sp | 420 ++ .../chaser/actions/attacks/projectile.sp | 295 + .../entities/chaser/actions/attacks/tongue.sp | 640 +++ .../entities/chaser/actions/beatbox_freeze.sp | 88 + .../sf2/npc/entities/chaser/actions/chase.sp | 208 + .../entities/chaser/actions/chaseinitial.sp | 86 + .../npc/entities/chaser/actions/chaselayer.sp | 96 + .../sf2/npc/entities/chaser/actions/death.sp | 160 + .../entities/chaser/actions/flee_to_heal.sp | 285 + .../sf2/npc/entities/chaser/actions/idle.sp | 365 ++ .../npc/entities/chaser/actions/mainlayer.sp | 569 ++ .../sf2/npc/entities/chaser/actions/rage.sp | 108 + .../sf2/npc/entities/chaser/actions/smell.sp | 177 + .../sf2/npc/entities/chaser/actions/spawn.sp | 55 + .../sf2/npc/entities/chaser/actions/stun.sp | 175 + .../npc/entities/chaser/actions/tauntkill.sp | 74 + .../sf2/npc/entities/chaser/entity.sp | 4821 ++++++++++++++++- .../npc/entities/chaser/postures/on_alert.sp | 47 + .../npc/entities/chaser/postures/on_cloak.sp | 47 + .../npc/entities/chaser/postures/on_look.sp | 46 + .../entities/chaser/postures/rage_phase.sp | 54 + .../entities/chaser/postures/running_away.sp | 44 + .../entities/chaser/postures/within_bounds.sp | 51 + .../entities/chaser/postures/within_range.sp | 76 + .../scripting/sf2/npc/entities/initialize.sp | 5 +- .../sf2/npc/entities/statue/actions/chase.sp | 158 + .../sf2/npc/entities/statue/actions/idle.sp | 39 + .../npc/entities/statue/actions/mainlayer.sp | 252 + .../sf2/npc/entities/statue/entity.sp | 394 +- .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 4567 ++-------------- .../scripting/sf2/npc/npc_chaser_attacks.sp | 1815 ------- .../scripting/sf2/npc/npc_chaser_mind.sp | 2687 --------- .../scripting/sf2/npc/npc_chaser_pathing.sp | 895 --- .../sf2/npc/npc_chaser_projectiles.sp | 1107 ---- .../sf2/npc/npc_chaser_takedamage.sp | 694 --- .../scripting/sf2/npc/npc_creeper.sp | 341 +- .../scripting/sf2/npc/npc_death_msg.sp | 1 - .../scripting/sf2/playergroups/menus.sp | 2 +- addons/sourcemod/scripting/sf2/profiles.sp | 110 +- .../scripting/sf2/profiles/profile_chaser.sp | 2383 +------- .../sf2/profiles/profile_chaser_precache.sp | 1193 ++-- .../scripting/sf2/profiles/profile_statue.sp | 70 +- .../sf2/profiles/profiles_boss_functions.sp | 415 +- addons/sourcemod/scripting/sf2/pve.sp | 1251 +++++ addons/sourcemod/scripting/sf2/pvp.sp | 42 +- addons/sourcemod/scripting/sf2/pvp/menus.sp | 34 +- .../sourcemod/scripting/sf2/specialround.sp | 370 +- addons/sourcemod/scripting/sf2/stocks.sp | 547 +- addons/sourcemod/scripting/sf2/traps.sp | 60 +- addons/sourcemod/translations/sf2.phrases.txt | 359 +- sf2.fgd | 9 +- 126 files changed, 28588 insertions(+), 28863 deletions(-) create mode 100644 addons/sourcemod/configs/sf2/changelog.cfg create mode 100644 addons/sourcemod/scripting/include/sf2/stocks.inc create mode 100644 addons/sourcemod/scripting/sf2/changelog.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/initialize.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp delete mode 100644 addons/sourcemod/scripting/sf2/functionclients/client_flashlight.sp delete mode 100644 addons/sourcemod/scripting/sf2/functionclients/client_hints.sp delete mode 100644 addons/sourcemod/scripting/sf2/functionclients/client_music.sp delete mode 100644 addons/sourcemod/scripting/sf2/functionclients/client_proxy_functions.sp delete mode 100644 addons/sourcemod/scripting/sf2/functionclients/clients_think.sp rename addons/sourcemod/scripting/sf2/{extras/renevant_mode.sp => gamemodes/renevant.sp} (93%) create mode 100644 addons/sourcemod/scripting/sf2/mapentities/sf2_point_spotlight.sp create mode 100644 addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/base/actions/deathcam.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/custom.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/explosive_dance.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/beatbox_freeze.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/spawn.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/tauntkill.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_alert.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_cloak.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_look.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/rage_phase.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/running_away.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_bounds.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_range.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/statue/actions/idle.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp delete mode 100644 addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp delete mode 100644 addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp delete mode 100644 addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp delete mode 100644 addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp delete mode 100644 addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp delete mode 100644 addons/sourcemod/scripting/sf2/npc/npc_death_msg.sp create mode 100644 addons/sourcemod/scripting/sf2/pve.sp diff --git a/addons/sourcemod/configs/sf2/changelog.cfg b/addons/sourcemod/configs/sf2/changelog.cfg new file mode 100644 index 00000000..8bdcc09f --- /dev/null +++ b/addons/sourcemod/configs/sf2/changelog.cfg @@ -0,0 +1,36 @@ +"Changelog" +{ + "plugin" + { + "name" "New/Changed Features" + + "1" "Change the boss AI to use the behavior system." + "2" "Added a customizable changelog." + "3" "Added the PvE arena and some custom bosses." + "4" "Fixed the explosive dance attack one shotting anyone." + "5" "Sentries no longer target bosses (unless they are a PvE boss)." + "6" "Changed the class sprint speeds to be a consistent 350-400 HU/s." + "7" "Removed the mechanic where class sprint speed decreases overtime." + "8" "Crouch footsteps no longer alert bosses by default." + "9" "Tweaked the FOV change when sprinting." + "10" "Tweaked peeking to no longer be in the scared state." + "11" "The blink meter now only appears when you see a statue boss." + } + + "bosses" + { + "name" "Boss Changes" + + "1" "Decreased/Fixed the increase chase duration rate for bosses." + "2" "Reworked the boss projectiles to be customizable." + "3" "Bosses can now have 2.147 billion attacks." + "4" "Reworked the auto chase behavior to be player tied now." + "5" "Added boss death states." + "6" "Added boss smell states." + "7" "Improved chase upon look for bosses." + "8" "Added attack begin and attack end animations + sounds." + "9" "Added spatial/3D sounds for bosses." + "10" "Alerted bosses can now wander around." + "11" "Bosses can now 'look around' while in the idle state." + } +} \ No newline at end of file diff --git a/addons/sourcemod/configs/sf2/class_stats.cfg b/addons/sourcemod/configs/sf2/class_stats.cfg index b036a8ec..35a2429f 100644 --- a/addons/sourcemod/configs/sf2/class_stats.cfg +++ b/addons/sourcemod/configs/sf2/class_stats.cfg @@ -77,10 +77,10 @@ { "scout" { - "runspeed" "305.0" + "runspeed" "400.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" - "sprint_multiplier" "1.08" + "sprint_multiplier" "1.05" "scare_sprint_multiplier" "1.0" "sprint_loss_while_jumping" "7" @@ -118,7 +118,7 @@ "soldier" { - "runspeed" "280.0" + "runspeed" "350.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" @@ -159,7 +159,7 @@ "pyro" { - "runspeed" "290.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.33" "sprint_multiplier" "1.0" @@ -200,10 +200,10 @@ "demoman" { - "runspeed" "280.0" + "runspeed" "350.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" - "sprint_multiplier" "1.15" + "sprint_multiplier" "1.1" "scare_sprint_multiplier" "1.667" "sprint_loss_while_jumping" "7" @@ -241,7 +241,7 @@ "heavy" { - "runspeed" "280.0" + "runspeed" "350.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" @@ -282,7 +282,7 @@ "engineer" { - "runspeed" "295.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" @@ -323,10 +323,10 @@ "medic" { - "runspeed" "290.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" - "sprint_multiplier" "1.05" + "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" "sprint_loss_while_jumping" "7" @@ -364,7 +364,7 @@ "sniper" { - "runspeed" "295.0" + "runspeed" "370.0" "walkspeed" "190.0" "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" @@ -405,10 +405,10 @@ "spy" { - "runspeed" "300.0" + "runspeed" "370.0" "walkspeed" "190.0" - "danger_speed_multiplier" "1.28" - "sprint_multiplier" "1.05" + "danger_speed_multiplier" "1.34" + "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" "sprint_loss_while_jumping" "7" diff --git a/addons/sourcemod/configs/sf2/specialrounds.cfg b/addons/sourcemod/configs/sf2/specialrounds.cfg index 1e35c152..3f58a1f1 100644 --- a/addons/sourcemod/configs/sf2/specialrounds.cfg +++ b/addons/sourcemod/configs/sf2/specialrounds.cfg @@ -33,8 +33,8 @@ "6" { - "display_text_hud" "Stealth Box of Doom - Spawns 2 extra bosses in slient mode." - "display_text_chat" "Stealth Box of Doom - They really wanna go MGS on your ass." + "display_text_hud" "SILENT SLENDER - More survivors, yet more bosses on silent!" + "display_text_chat" "SILENT SLENDER - 7 years and you still play me?!" "display_icon_hud" "d_purgatory" } @@ -193,74 +193,74 @@ } "29" - { - "display_text_hud" "SILENT SLENDER - More survivors, yet more bosses on silent!" - "display_text_chat" "SILENT SLENDER - Now its Lag Simulator 2020!" - "display_icon_hud" "d_purgatory" - } - - "30" { "display_text_hud" "PAGE REWARDS - Get a reward or get punished!" "display_text_chat" "PAGE REWARDS - Slender has left some goodies for us." "display_icon_hud" "leaderboard_dominated" } - "31" + "30" { "display_text_hud" "TINY BOSSES - Bosses size is reduced by 2!" "display_text_chat" "TINY BOSSES - Tiny boss is tiny, is funny to me!" "display_icon_hud" "d_purgatory" } - "32" + "31" { "display_text_hud" "IN THE 90s - Run for your life!" "display_text_chat" "IN THE 90s - Running in the 90s!" "display_icon_hud" "ico_kart" } - "33" + "32" { "display_text_hud" "TRIPLE BOSSES - Triple the bosses, triple the death!" "display_text_chat" "TRIPLE BOSSES - Escape the bosses... 3 times?!" "display_icon_hud" "leaderboard_streak" } - "34" + "33" { "display_text_hud" "MODified Bosses - You like pain and suffering? We got you!" "display_text_chat" "MODified Bosses - Pain and suffering on a new level, good luck..." "display_icon_hud" "d_purgatory" } - "35" + "34" { "display_text_hud" "BOSS ROULETTE - An RNG rigged version of Russian Roulette." "display_text_chat" "BOSS ROULETTE - Pick a page, pick a boss!" "display_icon_hud" "hud_taunt_menu_icon" } - "36" + "35" { "display_text_hud" "THANATOPHOBIA - The fear of death." "display_text_chat" "THANATOPHOBIA - Do not die, or your teammates will suffer..." "display_icon_hud" "d_skull" } - "37" + "36" { "display_text_hud" "WALL HAX - You can see the bosses through the walls!" "display_text_chat" "WALL HAX - GG EZ GET GOOD!" "display_icon_hud" "ico_build" } - "38" + "37" { "display_text_hud" "SINGLE PLAYER - Playing by yourself...?" "display_text_chat" "SINGLE PLAYER - Playing by yourself...?" "display_icon_hud" "number_1" } + + "38" + { + "display_text_hud" "BEATBOX - 3... 2... 1... Stop!" + "display_text_chat" "BEATBOX - 3... 2... 1... Stop!" + "display_icon_hud" "ico_notify_sixty_seconds" + } "jokes" { diff --git a/addons/sourcemod/gamedata/sf2.txt b/addons/sourcemod/gamedata/sf2.txt index 7298b6a3..b5d3e1f3 100644 --- a/addons/sourcemod/gamedata/sf2.txt +++ b/addons/sourcemod/gamedata/sf2.txt @@ -9,42 +9,50 @@ "linux" "335" "windows" "334" } + "CTFWeaponBase::GetCustomDamageType" { "linux" "387" "windows" "381" } + "CBaseEntity::ShouldTransmit" { "linux" "21" "windows" "20" } + "CBaseEntity::GetSmoothedVelocity" { "linux" "146" "windows" "145" } + "CBaseEntity::UpdateTransmitState" { "linux" "22" "windows" "21" } + "CBaseProjectile::CanCollideWithTeammates" { "linux" "230" "windows" "229" } + "CTFPlayer::EquipWearable" { "linux" "439" "windows" "438" } + "CBaseTrigger::PassesTriggerFilters" { "linux" "208" "windows" "207" } } + "Signatures" { "CTFPlayer::PlaySpecificSequence" @@ -53,6 +61,7 @@ "linux" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" "windows" "\x55\x8B\xEC\x53\x56\x8B\x75\x08\x57\x56\x8B\xF9\xE8\x2A\x2A\x2A\x2A\x8B\xD8" } + "CBaseTrigger::PointIsWithin" { "library" "server" @@ -67,6 +76,20 @@ "linux" "@_Z18CreateEntityByNamePKci" "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x0C\x83\xFE\xFF\x74\x24\x8B\x0D" } + + "CBaseAnimating::LookupBone" // int CBaseAnimating::LookupBone( const char *szName ) + { + "library" "server" + "windows" "\x55\x8B\xEC\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\x83\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x8B\xCE\xE8\x2A\x2A\x2A\x2A\x8B\x86\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x83\x38\x00\x74\x2A\xFF\x75\x2A\x50\xE8\x2A\x2A\x2A\x2A\x83\xC4\x08\x5E" + "linux" "@_ZN14CBaseAnimating10LookupBoneEPKc" + } + + "CBaseAnimating::GetBonePosition" // void CBaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x30\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A" + "linux" "@_ZN14CBaseAnimating15GetBonePositionEiR6VectorR6QAngle" + } } } -} +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 709e257a..507a7f64 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -4,9 +4,11 @@ #define _sf2_included #include +#include +#include -#define PLUGIN_VERSION "1.8.0 M Alpha" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha" +#define PLUGIN_VERSION "1.8.0 M Alpha 2" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 @@ -14,7 +16,6 @@ #define MAX_BOSSES 64 #define MAX_NPCTELEPORTER 5 -#define MAX_NODES -1 #define MAX_TRAPS 64 #define MAX_CLASSES 9 @@ -46,7 +47,7 @@ enum SPECIALROUND_DOUBLEMAXPLAYERS, SPECIALROUND_LIGHTSOUT, SPECIALROUND_BEACON, - SPECIALROUND_DOOMBOX, + SPECIALROUND_SILENTSLENDER, SPECIALROUND_NOGRACE, SPECIALROUND_2DOUBLE, SPECIALROUND_DOUBLEROULETTE, @@ -69,7 +70,6 @@ enum SPECIALROUND_COFFEE, SPECIALROUND_PAGEDETECTOR, SPECIALROUND_CLASSSCRAMBLE, - SPECIALROUND_2DOOM, SPECIALROUND_PAGEREWARDS, SPECIALROUND_TINYBOSSES, SPECIALROUND_RUNNINGINTHE90S, @@ -79,6 +79,7 @@ enum SPECIALROUND_THANATOPHOBIA, SPECIALROUND_WALLHAX, SPECIALROUND_SINGLEPLAYER, + SPECIALROUND_BEATBOX, SPECIALROUND_MAXROUNDS }; @@ -86,12 +87,12 @@ enum enum { STATE_IDLE = 0, - STATE_WANDER, STATE_ALERT, STATE_CHASE, STATE_ATTACK, STATE_STUN, STATE_DEATHCAM, + STATE_DEATH, MAX_STATE }; @@ -109,7 +110,7 @@ enum SoundType enum { - Difficulty_Easy = 0, + Difficulty_Easy = 0, // Also called the none difficulty Difficulty_Normal, Difficulty_Hard, Difficulty_Insane, @@ -131,7 +132,6 @@ enum SF2BossType_Static = 0, SF2BossType_Statue, SF2BossType_Chaser, - SF2BossType_AdvancedChaser, SF2BossType_MaxTypes }; @@ -145,10 +145,11 @@ enum SF2BossSound_Stun = 5, SF2BossSound_Attack, SF2BossSound_AttackKilled, - SF2BossSound_RageAll, - SF2BossSound_RageTwo, - SF2BossSound_RageThree = 10, - SF2BossSound_SelfHeal, + SF2BossSound_Death, + SF2BossSound_TauntKill, + SF2BossSound_Smell, + SF2BossSound_AttackBegin, + SF2BossSound_AttackEnd, SF2BossSound_MaxSounds }; @@ -156,20 +157,21 @@ enum { SF2BossAnimation_Idle = 0, SF2BossAnimation_Walk, - SF2BossAnimation_WalkAlert, SF2BossAnimation_Attack, - SF2BossAnimation_Shoot, - SF2BossAnimation_Run = 5, + SF2BossAnimation_Run, SF2BossAnimation_Stun, - SF2BossAnimation_ChaseInitial, + SF2BossAnimation_ChaseInitial = 5, SF2BossAnimation_Rage, SF2BossAnimation_Spawn, - SF2BossAnimation_FleeInitial = 10, + SF2BossAnimation_FleeInitial, SF2BossAnimation_Heal, - SF2BossAnimation_DeathCam, - SF2BossAnimation_CrawlWalk, - SF2BossAnimation_CrawlRun, - SF2BossAnimation_MaxAnimations = 15 + SF2BossAnimation_DeathCam = 10, + SF2BossAnimation_Death, + SF2BossAnimation_TauntKill, + SF2BossAnimation_Smell, + SF2BossAnimation_AttackBegin, + SF2BossAnimation_AttackEnd = 15, + SF2BossAnimation_MaxAnimations } enum SF2RoundState @@ -187,7 +189,7 @@ enum { SF2Attribute_ReducedSpeedOnLook = 0, SF2Attribute_ReducedWalkSpeedOnLook, - SF2Attribute_AddStunHealhOnStun, + SF2Attribute_AddStunHealthOnStun, SF2Attribute_AlwaysLookAtTarget, SF2Attribute_AlwaysLookAtTargetWhileChasing, SF2Attribute_AlwaysLookAtTargetWhileAttacking = 5, @@ -203,15 +205,14 @@ enum SF2Attribute_SilentMarkPlayerForDeathOnScare = 15, SF2Attribute_IgnitePlayerOnDeath, SF2Attribute_AddSpeedOnStun, - SF2Attribute_AddMaxSpeedOnStun, SF2Attribute_DeathCamOnLowHealth, - SF2Attribute_AlertCopies = 20, - SF2Attribute_AlertCompanions, + SF2Attribute_AlertCopies, + SF2Attribute_AlertCompanions = 20, SF2Attribute_BlockWalkSpeedUnderDifficulty, SF2Attribute_ChaseTargetOnScare, SF2Attribute_IgnoreNonMarkedForChase, - SF2Attribute_ReducedAccelerationOnLook = 25, - SF2Attribute_AddAccelerationOnStun, + SF2Attribute_ReducedAccelerationOnLook, + SF2Attribute_AddAccelerationOnStun = 25, SF2Attribute_Max }; @@ -225,10 +226,11 @@ char g_SlenderVoiceList[SF2BossSound_MaxSounds][] = "sound_stun", "sound_attackenemy", "sound_attack_killed", - "sound_rage", - "sound_rage_2", - "sound_rage_3", - "sound_heal_self" + "sound_death", + "sound_taunt_kill", + "sound_smell", + "sound_attack_begin", + "sound_attack_end" }; char g_AttributesList[SF2Attribute_Max][] = @@ -251,7 +253,6 @@ char g_AttributesList[SF2Attribute_Max][] = "silent mark player for death on scare", "ignite player on death", "add speed on stun", - "add max speed on stun", "death cam on low health", "alert copies", "alert companions", @@ -274,6 +275,15 @@ enum ParticleAttachment MAX_PATTACH_TYPES, }; +enum SF2NPCMoveTypes +{ + SF2NPCMoveType_Walk = 0, + SF2NPCMoveType_Run, + SF2NPCMoveType_Attack, + SF2NPCMoveType_Max +} + +#include #include #include @@ -291,7 +301,7 @@ enum ParticleAttachment #define SFF_HASSIGHTSOUNDS (1 << 10) #define SFF_HASSTATICLOOPLOCALSOUND (1 << 11) #define SFF_HASVIEWSHAKE (1 << 12) -#define SFF_COPIES (1 << 13) +#define SFF_NOCOPIES (1 << 13) #define SFF_ATTACKPROPS (1 << 14) #define SFF_WEAPONKILLS (1 << 15) #define SFF_WEAPONKILLSONRADIUS (1 << 16) @@ -306,30 +316,25 @@ enum ParticleAttachment #define COND_CHASETARGETINVALIDATED (1 << 6) #define COND_SAWENEMY (1 << 7) #define COND_HEARDFLASHLIGHT (1 << 8) - -// Projectile flags. -#define PROJ_FIREBALL (1 << 0) -#define PROJ_ICEBALL (1 << 1) -#define PROJ_ROCKET (1 << 2) -#define PROJ_SENTRYROCKET (1 << 3) -#define PROJ_GRENADE (1 << 4) -#define PROJ_ARROW (1 << 5) -#define PROJ_MANGLER (1 << 6) -#define PROJ_BASEBALL (1 << 7) -#define PROJ_FIREBALL_ATTACK (1 << 8) -#define PROJ_ICEBALL_ATTACK (1 << 9) +#define COND_ALERT_TRIGGER (1 << 9) +#define COND_NEWENEMY (1 << 10) +#define COND_ENEMYNEAR (1 << 11) +#define COND_ENEMYVISIBLE (1 << 12) +#define COND_ENEMYRECHASE (1 << 13) +#define COND_DEBUG (1 << 14) /** * Called after a boss profile is successfully loaded. * - * @param profile Profile name + * @param profile Profile name + * @param kv Key Values */ forward void SF2_OnBossProfileLoaded(const char[] profile, KeyValues kv); /** * Called before a boss profile is unloaded. * - * @param profile Profile name + * @param profile Profile name */ forward void SF2_OnBossProfileUnloaded(const char[] profile); @@ -363,14 +368,6 @@ forward void SF2_OnBossDespawn(int bossIndex); */ forward void SF2_OnBossChangeState(int bossIndex, int oldState, int newState); -/** - * Called when the boss updates its animation. - * - * @param bossIndex Boss index. - * @return If Plugin_Handled, animation change will be blocked. - */ -forward Action SF2_OnBossAnimationUpdate(int bossIndex); - /** * Called to determine the boss's run speed. * @@ -418,11 +415,19 @@ forward void SF2_OnBossRemoved(int bossIndex); /** * Called when the boss gets stun by a player. * - * @param bossIndex Boss index. + * @param bossIndex Boss index. * @param client The client who stunned the boss, will return -1 if the stun was a flashlight stun. */ forward void SF2_OnBossStunned(int bossIndex, int client); +/** + * Called when the boss gets killed.. + * + * @param bossIndex Boss index. + * @param attacker The attacker who killed the boss. + */ +forward void SF2_OnBossKilled(int bossIndex, int attacker); + /** * Called when the boss cloaks. * @@ -453,12 +458,34 @@ forward void SF2_OnBossFinishSpawning(int bossIndex); forward void SF2_OnBossPreAttack(int bossIndex, ArrayList attackArray); /** - * Called when the boss finishes an attack yet not exiting it's attack state. + * Called just before a chaser boss takes damage. * - * @param bossIndex Boss index. - * @param attackIndex Current attack index. + * @param chaser Chaser entity + * @param attacker Attacker entity + * @param inflictor Inflictor entity + * @param damage Damage about to be taken + * @return Returning Plugin_Stop will invalidate any damage taken by the boss. + */ +forward Action SF2_OnBossPreTakeDamage(SF2_ChaserBossEntity chaser, CBaseEntity attacker, CBaseEntity inflictor, float damage); + +/** + * Called whenever a chaser boss registers a player uses a flashlight for flashight stunning. + * + * @param chaser Chaser entity + * @param player Player using the flashlight + * @return Returning Plugin_Stop will invalidate any flashlight damage taken by the boss from the specified player. + */ +forward Action SF2_OnBossPreFlashlightDamage(SF2_ChaserBossEntity chaser, SF2_Player player); + + +/** + * Called whenever a chaser boss gets a command string "suspend for action" using INextBotEventResponder.OnCommandString() + * + * @param chaser Chaser entity + * @param action Action to use + * @return Returning anything not Plugin_Continue will make the boss use the specified action and suspend for that */ -forward void SF2_OnBossAttacked(int bossIndex, int attackIndex); +forward Action SF2_OnChaserBossGetSuspendAction(SF2_ChaserBossEntity chaser, NextBotAction &action); /** * Called when pages have spawned into the map. @@ -580,6 +607,14 @@ forward void SF2_OnClientLooksAwayFromBoss(int client, int bossIndex); */ forward void SF2_OnClientStartDeathCam(int client, int bossIndex); +/** + * Called right before the player dies in a death cam + * + * @param client Client index. + * @param bossIndex Boss index. + */ +forward void SF2_OnClientPreKillDeathCam(int client, int bossIndex); + /** * Called when the player gets exits a boss's deathcam. * @@ -683,6 +718,63 @@ forward Action SF2_OnGroupEnterGame(int groupIndex); */ forward void SF2_OnEverythingLoaded(); +/** + * Called whenever the difficulty vote finishes + * + * @param list ArrayList containing all valid attacks + */ +forward void SF2_OnDifficultyVoteFinished(ArrayList list, bool isRunOff); + +/** + * Called when a Chaser boss is determining if it's allowed to use the custom attack. + * + * @param chaser Chaser entity + * @param attackName Attack name + * @param data Attack data + * @param target The target entity that the boss is trying to use this attack on + * @return Returning a value other than Plugin_Continue will block the boss from using + * the attack. + */ +forward Action SF2_OnIsBossCustomAttackPossible(SF2_ChaserBossEntity chaser, const char[] attackName, SF2ChaserBossProfileAttackData data, CBaseEntity target); + +/** + * Called when the Chaser boss has selected the custom attack and is requesting a + * NextBotAction to handle the attack logic. + * + * @param chaser Chaser entity + * @param attackName Attack name + * @param data Attack data + * @param target The target entity to use the attack on + * @param action Parameter to fill with the NextBotAction + * @return Return a value other than Plugin_Continue to use `action` + */ +forward Action SF2_OnBossGetCustomAttackAction(SF2_ChaserBossEntity chaser, const char[] attackName, SF2ChaserBossProfileAttackData data, CBaseEntity target, NextBotAction &action); + +/** + * Fires each time a boss starts an attack. + * + * @param chaser Chaser entity + * @param attackName Attack name + */ +forward void SF2_OnChaserBossStartAttack(SF2_ChaserBossEntity chaser, const char[] attackName); + +/** + * Fires after a boss has ended an attack. + * + * @param chaser Chaser entity + * @param attackName Attack name + */ +forward void SF2_OnChaserBossEndAttack(SF2_ChaserBossEntity chaser, const char[] attackName); + +/** + * Called whenever the gamemode picks a custom boss. Use this for creating entities. + * + * @param boss The chosen boss + * @param pos The selected position the boss will spawn on + * @param ang The selected angle the boss will spawn on + */ +forward void SF2_OnSelectedPvEBoss(const char[] boss, const float pos[3], const float ang[3]); + /** * Returns the global variable g_Config. * @@ -718,6 +810,15 @@ native bool SF2_IsRoundInGracePeriod(); */ native int SF2_GetCurrentDifficulty(); +/** + * Returns the current difficulty on the boss. This is only useful for boxing/PvE bosses. + * If the boss is not a PvE/boxing boss, then this will return the current gamemode difficulty. + * + * @param bossIndex Boss index. + * @return Integer of the difficulty. + */ +native int SF2_GetBossDifficulty(int bossIndex); + /** * Returns the current difficulty of the round. * @@ -771,6 +872,55 @@ native int SF2_GetClientQueuePoints(int client); */ native void SF2_SetClientQueuePoints(int client, int amount); +/** + * Returns whether or not the client is valid for SF2. + * + * @param client Client index. + * @return True if the player is valid, false if not. + */ +native bool SF2_IsValidClient(int client); + +/** + * Returns whether or not the client is crit boosted for SF2. + * + * @param client Client index. + * @return True if the player is crit boosted, false if not. + */ +native bool SF2_IsClientCritBoosted(int client); + +/** + * Returns whether or not the client is mini crit boosted for SF2. + * + * @param client Client index. + * @return True if the player is mini crit boosted, false if not. + */ +native bool SF2_IsClientMiniCritBoosted(int client); + +/** + * Returns whether or not the client is ubercharged for SF2. + * + * @param client Client index. + * @return True if the player is ubercharged, false if not. + */ +native bool SF2_IsClientUbercharged(int client); + +/** + * Returns whether or not the client is in a kart for SF2. + * + * @param client Client index. + * @return True if the player is in a kart, false if not. + */ +native bool SF2_IsClientInKart(int client); + +/** + * Returns whether or not the client is in a specific condition for SF2. + * + * @param client Client index. + * @param condition Condition to check. + * @return True if the player is in a condition, false if not. + */ +native bool SF2_IsClientInCondition(int client, TFCond condition); + /** * Returns a bool about the client's elimination state. * @@ -795,6 +945,14 @@ native bool SF2_IsClientInGhostMode(int client); */ native bool SF2_IsClientInPvP(int client); +/** + * Returns a bool if the client is in a Player vs. Environment zone or not. + * + * @param client Client index. + * @return True if the player is in a PvE zone, false if not. + */ +native bool SF2_IsClientInPvE(int client); + /** * Tells whether if the client is a Proxy or not. * @@ -1023,6 +1181,11 @@ native bool SF2_ClientForceProxy(int client, int bossIndex, const float teleport */ native void SF2_CollectAsPage(int pageEnt, int client); +/** + * Returns the empty page spawn points not occupied by a page in ArrayList form. + */ +native ArrayList SF2_GetEmptyPageSpawnPoints(); + /** * Returns the maximum boss count. * @@ -1070,16 +1233,6 @@ native int SF2_BossIDToBossIndex(int bossID); */ native int SF2_BossIndexToBossID(int bossIndex); -/** - * Updates the boss animation based on the defined state - * - * @param bossIndex Boss index. - * @param ent Entity index. - * @param state State. - * @param spawn Determines if the boss should use a spawn animation. -*/ -native void SF2_UpdateBossAnimation(int bossIndex, int ent, int state, bool spawn = false); - /** * Forces a boss to jump at a position. * @@ -1248,92 +1401,10 @@ native void SF2_GetBossEyePosition(int bossIndex, float eyePos[3]); */ native void SF2_GetBossEyePositionOffset(int bossIndex, float eyePosOffset[3]); -/** - * Returns the boss's field of view. - * - * @param bossIndex Boss index. - * @return Field of view. - */ -native float SF2_GetBossFOV(int bossIndex); - -/** - * If the boss is in a chase state, returns the time which the boss's persistency on the target ends. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time where persistency ends. - */ -native float SF2_GetBossTimeUntilNoPersistence(int bossIndex); - -/** - * If the boss is in a chase state, sets the time which the boss's persistency on the target ends. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time where persistency ends. - */ -native void SF2_SetBossTimeUntilNoPersistence(int bossIndex, float time); - -/** - * If the boss is in an alert state, returns the time which the boss should revert to idle state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time where chase state should revert to idle state. - */ -native float SF2_GetBossTimeUntilIdle(int bossIndex); - -/** - * If the boss is in an alert state, sets the time which the boss should revert to idle state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time where chase state should revert to idle state. - */ -native void SF2_SetBossTimeUntilIdle(int bossIndex, float time); - -/** - * If the boss is in a chase state, returns the time which the boss should revert to alert state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time where chase state should revert to alert state. - */ -native float SF2_GetBossTimeUntilAlert(int bossIndex); - -/** - * If the boss is in a chase state, sets the time which the boss should revert to alert state. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time where chase state should revert to alert state. - */ -native void SF2_SetBossTimeUntilAlert(int bossIndex, float time); - -/** - * Returns whether or not the boss is stunnable. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return True if stunnable, false otherwise. - */ +#pragma deprecated Use SF2ChaserBossProfileAttackData.StunEnabled[Difficulty_Max] instead. native bool SF2_IsBossStunnable(int bossIndex); -/** - * Returns whether or not the boss is stunnable by flashlight. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return True if stunnable by flashlight, false otherwise. - */ +#pragma deprecated Use SF2ChaserBossProfileAttackData.StunFlashlight[Difficulty_Max] instead. native bool SF2_IsBossStunnableByFlashlight(int bossIndex); /** @@ -1366,37 +1437,6 @@ native float SF2_GetBossStunHealth(int bossIndex); */ native void SF2_SetBossStunHealth(int bossIndex, float health); -/** - * Returns the time where the boss is not under stun cooldown. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Time when boss is not under stun cooldown. - */ -native float SF2_GetBossNextStunTime(int bossIndex); - -/** - * Sets the time where the boss is not under stun cooldown. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param time Time when boss is not under stun cooldown. - */ -native float SF2_SetBossNextStunTime(int bossIndex, float time); - -/** - * Forces the boss to stop chasing. - * - * Only used with Chaser bosses. - * - * Note: If the boss is set to chase endlessly then this will not do anything. - * - * @param bossIndex Boss index. - */ -native void SF2_ForceBossGiveUp(int bossIndex); - /** * Gets the current goal position of the boss. * @@ -1407,30 +1447,6 @@ native void SF2_ForceBossGiveUp(int bossIndex); */ native void SF2_GetBossGoalPosition(int bossIndex, float position[3]); -/** - * Returns whether or not the boss is able to hear the player given sound type. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param client Client index. - * @param soundType The type of sound to check. - * @return True if boss can hear, false otherwise. - */ -native bool SF2_CanBossHearClient(int bossIndex, int client, SoundType soundType); - -/** - * Creates a sound hint for the boss to follow. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param soundType The type of sound hint to create. - * @param position The position of the sound. - * @param difficulty The difficulty. - */ -native void SF2_CreateBossSoundHint(int bossIndex, SoundType soundType, const float position[3], int difficulty); - /** * Returns if the given profile is loaded or not. * @@ -1439,152 +1455,31 @@ native void SF2_CreateBossSoundHint(int bossIndex, SoundType soundType, const fl */ native bool SF2_IsBossProfileValid(const char[] profile); -/** - * Retrieves an integer keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2BossProfileData instead. native int SF2_GetBossProfileNum(const char[] profile, const char[] key, int defaultValue=0); -/** - * Retrieves a float keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2BossProfileData instead. native float SF2_GetBossProfileFloat(const char[] profile, const char[] key, float defaultValue=0.0); -/** - * Retrieves a string keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param bufferLen Max length of buffer. - * @param defaultValue Fallback value. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetBossProfileString(const char[] profile, const char[] key, char[] buffer, int bufferLen, const char[] defaultValue=""); -/** - * Retrieves a vector keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param defaultValue Fallback value. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetBossProfileVector(const char[] profile, const char[] key, float buffer[3], const float defaultValue[3]=NULL_VECTOR); -/** - * Retrieves integer difficulty keyvalues from the profile. - * - * @param kv KeyValues - * @param baseKeyName Name of the base key. - * @param buffer Values to store the key values in - * @param defaultValues Fallback values - * @return The value, or defaultValue if the key doesn't exist. - */ -native void SF2_GetBossProfileDifficultyNumValues(KeyValues kv, const char[] baseKeyName, int buffer[Difficulty_Max], const int defaultValues[Difficulty_Max] = { 0, ... }) - -/** - * Retrieves bool difficulty keyvalues from the profile. - * - * @param kv KeyValues - * @param baseKeyName Name of the base key. - * @param buffer Values to store the key values in - * @param defaultValues Fallback values - * @return The value, or defaultValue if the key doesn't exist. - */ -native void SF2_GetBossProfileDifficultyBoolValues(KeyValues kv, const char[] baseKeyName, bool buffer[Difficulty_Max], const bool defaultValues[Difficulty_Max] = { false, ... }) - -/** - * Retrieves float difficulty keyvalues from the profile. - * - * @param kv KeyValues - * @param baseKeyName Name of the base key. - * @param buffer Values to store the key values in - * @param defaultValues Fallback values - * @return The value, or defaultValue if the key doesn't exist. - */ -native void SF2_GetBossProfileDifficultyFloatValues(KeyValues kv, const char[] baseKeyName, float buffer[Difficulty_Max], const float defaultValues[Difficulty_Max] = { 0.0, ... }) - -/** - * Retrieves an integer keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native int SF2_GetBossAttackProfileNum(const char[] profile, const char[] key, int defaultValue=0, const int attackIndex); -/** - * Retrieves a float keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return The value, or defaultValue if the key doesn't exist. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native float SF2_GetBossAttackProfileFloat(const char[] profile, const char[] key, float defaultValue=0.0, const int attackIndex); -/** - * Retrieves a string keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param bufferLen Max length of buffer. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native bool SF2_GetBossAttackProfileString(const char[] profile, const char[] key, char[] buffer, int bufferLen, const char[] defaultValue="", const int attackIndex); -/** - * Retrieves a vector keyvalue from the profile. - * - * @param profile Profile name. - * @param key Name of the key. - * @param buffer Buffer to store the value. - * @param defaultValue Fallback value. - * @param attackIndex Attack Index to use. - * @return True if key exists; false otherwise. If false, buffer is set to defaultValue. - */ +#pragma deprecated Use SF2ChaserBossProfileData.Attacks instead. native bool SF2_GetBossAttackProfileVector(const char[] profile, const char[] key, float buffer[3], const float defaultValue[3]=NULL_VECTOR, const int attackIndex); -/** - * Retrieves a random string keyvalue from a section within the profile. - * - * Example: In a profile, a valid section would look like this: - * - * "profile" - * { - * "section" - * { - * "1" "item1" - * "2" "item2" - * } - * } - * - * This function will randomly choose a string value within "section" if key = "section". - * - * @param profile Profile name. - * @param key Name of the key. Must be a section. - * @param buffer Buffer to store the value. - * @param bufferLen Max length of buffer. - * @param index Mainly used for attack indexes, selects a random sound from an array that is index. - * @return True if profile exists and key is set; false otherwise. - */ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetRandomStringFromBossProfile(const char[] profile, const char[] key, char[] buffer, int bufferLen, int index=-1); /** @@ -1603,228 +1498,1287 @@ native bool SF2_GetBossAttributeName(int bossIndex, int attribute); native float SF2_GetBossAttributeValue(int bossIndex, int attribute); /** - * Returns the projectile type a boss uses. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. -*/ -native int SF2_GetBossProjectileType(int bossIndex); - -/** - * Returns the current attack index of the boss. - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @return Attack index. -*/ -native int SF2_GetBossCurrentAttackIndex(int bossIndex); - -/** - * Sets the boss' current attack index. + * Returns the teleport think timer in Handle form * * Only used with Chaser bosses. * * @param bossIndex Boss index. - * @param attackIndex New attack index. */ -native int SF2_SetBossCurrentAttackIndex(int bossIndex, int attackIndex); +native Handle SF2_GetBossTeleportThinkTimer(int bossIndex); /** - * Returns all available attack indexes of the boss - * - * Only used with Chaser bosses. + * Sets the teleport think timer and destroys the old think timer * * @param bossIndex Boss index. + * @param timer New timer (Can also be null). */ -native int SF2_GetBossMaxAttackIndexes(int bossIndex); +native void SF2_SetBossTeleportThinkTimer(int bossIndex, Handle timer); /** - * Returns the boss' current run duration time - * - * Only used with Chaser bosses. + * Returns the boss' teleport target * * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @return Attack run duration time. + * @param difficulty The difficulty index. + * @return Teleport entity index. */ -native float SF2_GetBossAttackRunDurationTime(int bossIndex, int attackIndex); +native int SF2_GetBossTeleportTarget(int bossIndex); /** - * Sets the boss' current run duration time - * - * Only used with Chaser bosses. + * Returns the StringMap variable containing every precached base boss profile * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param newTime New attack run duration time. + * @return StringMap containing all base boss profile datas */ -native void SF2_SetBossAttackRunDurationTime(int bossIndex, int attackIndex, float newTime); +native StringMap SF2_GetBossProfileData(); /** - * Returns the boss' current run duration time - * - * Only used with Chaser bosses. + * Returns the StringMap variable containing every precached chaser boss profile * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @return Attack run delay time. + * @return StringMap containing all chaser boss profile datas */ -native float SF2_GetBossAttackRunDelayTime(int bossIndex, int attackIndex); +native StringMap SF2_GetChaserBossProfileData(); /** - * Sets the boss' current run delay time - * - * Only used with Chaser bosses. + * Returns the StringMap variable containing every precached statue boss profile * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param newTime New attack run delay time. + * @return StringMap containing all statue boss profile datas */ -native void SF2_SetBossAttackRunDelayTime(int bossIndex, int attackIndex, float newTime); +native StringMap SF2_GetStatueBossProfileData(); /** - * Returns the damage type of a set attack index. - * - * Only used with Chaser bosses. + * Spawns boss effects based on the list of effects used. + * Note this will not spawn the extra effects like the festive lights and the disco ball + * Only use this best for spawning in temporary effects. + * Note the array of effects must be an ArrayList that contains SF2BossProfileBaseEffectInfo. * - * @param bossIndex Boss index. - * @param attackIndex Attack index. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.DamageType[Difficulty_Max] instead. -native int SF2_GetBossAttackIndexDamageType(int bossIndex, int attackIndex); + * @param effects The array of effects + * @param bossIndex Boss to use + * @param overridePos Override the spawn position of the effects + * @param overrideAng Override the spawn angles of the effects + * @param output The ArrayList that will contain all of the outputted entities as entity references + * @param entityOverride If not INVALID_ENT_REFERENCE will attach all particles to the desired entity + */ +native void SF2_SpawnBossEffects(ArrayList effects, int bossIndex, float overridePos[3] = NULL_VECTOR, float overrideAng[3] = NULL_VECTOR, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE); /** - * Returns the damage of a set attack index. + * Translates an activity name string into an Activity. * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param difficulty The difficulty. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.Damage[Difficulty_Max] instead. -native float SF2_GetBossAttackIndexDamage(int bossIndex, int attackIndex, int difficulty); + * @param sActivity + * @return Activity, or ACT_INVALID if not found. + */ +native Activity SF2_TranslateProfileActivityFromName(const char[] activity); /** - * Returns the spread of a set attack index - * - * Only used with Chaser bosses. + * Given an activity or sequence, translates into a sequence index. * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param difficulty The difficulty. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.Spread[Difficulty_Max] instead. -native float SF2_GetBossAttackIndexSpread(int bossIndex, int attackIndex); + * @param iEntity Entity index + * @param sAnimation Activity/sequence string + * @return Sequence index, or -1 if not found. + */ +native int SF2_LookupProfileAnimation(int entity, const char[] animation); /** - * Returns the range of a set attack index + * Returns the profile data found on a boss index, note this is the basic profile data, I.E. what all bosses use * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Attack index. - * @param difficulty The difficulty. -*/ -#pragma deprecated Use SF2ChaserBossProfileAttackData.Range[Difficulty_Max] instead. -native float SF2_GetBossAttackIndexRange(int bossIndex, int attackIndex); + * @param bossIndex Boss index. + * @param data Data to be stored in. Note this takes in SF2BossProfileData + */ +native void SF2_GetProfileFromBossIndex(int bossIndex, any data[sizeof(SF2BossProfileData)]); /** - * Returns the attack type of a set attack index. + * Returns the chaser profile data found on a boss index. * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Attack index. -*/ -native int SF2_GetBossAttackIndexType(int bossIndex, int attackIndex); + * @param bossIndex Boss index. + * @param data Data to be stored in. Note this takes in SF2ChaserBossProfileData + */ +native void SF2_GetChaserProfileFromBossIndex(int bossIndex, any data[sizeof(SF2ChaserBossProfileData)]); /** - * Returns the teleport think timer in Handle form - * - * Only used with Chaser bosses. + * Returns the statue profile data found on a boss index. * - * @param bossIndex Boss index. -*/ -native Handle SF2_GetBossTeleportThinkTimer(int bossIndex); + * @param bossIndex Boss index. + * @param data Data to be stored in. Note this takes in SF2StatueBossProfileData + */ +native void SF2_GetStatueProfileFromBossIndex(int bossIndex, any data[sizeof(SF2StatueBossProfileData)]); /** - * Sets the teleport think timer and destroys the old think timer + * Returns the profile data found from a profile name, note this is the basic profile data, I.E. what all bosses use * - * @param bossIndex Boss index. - * @param timer New timer (Can also be null). -*/ -native void SF2_SetBossTeleportThinkTimer(int bossIndex, Handle timer); + * @param profile Profile name. + * @param data Data to be stored in. Note this takes in SF2BossProfileData + */ +native bool SF2_GetProfileFromName(const char[] profile, any data[sizeof(SF2BossProfileData)]); /** - * Returns the boss' teleport target + * Returns the chaser profile data found from a profile name. * - * @param bossIndex Boss index. - * @param difficulty The difficulty index. - * @return Teleport entity index. -*/ -native int SF2_GetBossTeleportTarget(int bossIndex); + * @param profile Profile name. + * @param data Data to be stored in. Note this takes in SF2ChaserBossProfileData + */ +native bool SF2_GetChaserProfileFromName(const char[] profile, any data[sizeof(SF2ChaserBossProfileData)]); /** - * Returns the boss think timer in Handle form - * - * Only used with Chaser bosses. + * Returns the statue profile data found from a profile name. * - * @param bossIndex Boss index. - * @return Handle timer. -*/ -native Handle SF2_GetBossThinkTimer(int bossIndex); + * @param profile Profile name. + * @param data Data to be stored in. Note this takes in SF2StatueBossProfileData + */ +native bool SF2_GetStatueProfileFromName(const char[] profile, any data[sizeof(SF2ChaserBossProfileData)]); /** - * Sets the boss think timer and destroys the old think timer - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param timer New timer (Can also be null). + * Methodmap responsible for all boss entities. */ -native void SF2_SetBossThinkTimer(int bossIndex, Handle timer); +methodmap SF2_BaseBossEntity < CBaseCombatCharacter +{ + /** + * Whether or not the entity is actually a base boss + */ + property bool IsValid + { + public native get(); + } + + /** + * Gets the controller index that owns this entity. + */ + property int Controller + { + public native get(); + } + + /** + * Gets the target entity index. + */ + property CBaseEntity Target + { + public native get(); + } + + /** + * The current state of the boss. + */ + property int State + { + public native get(); + } + + /** + * Time left in its current chase. Note that this does not always tick down + * at a steady rate. + */ + property float CurrentChaseDuration + { + public native get(); + } + + /** + * Gets the boss's eye position. + * + * @param buffer Buffer to store position + */ + public native void EyePosition(float buffer[3]); + + /** + * Gets the boss's profile name + * + * @param buffer Buffer to name + * @param bufferLen Size of buffer + */ + public native void GetProfileName(char[] buffer, int bufferLen); + + /** + * Gets the boss's name + * + * @param buffer Buffer to name + * @param bufferLen Size of buffer + */ + public native void GetName(char[] buffer, int bufferLen); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_BaseBossEntity(int entity) + { + return view_as(entity); + } + + /** + * Gets the boss's profile data. + * + * @param data Data to be stored in. Note this takes in SF2BossProfileData + */ + public native void ProfileData(any data[sizeof(SF2BossProfileData)]); + + /** + * Plays an animation on the boss from a specific animation section, even custom ones. + * + * @param animType Animation section to search in. + * @param preDefinedIndex Optional animation index to use. + * @param preDefinedName Optional animation name to use. + * @param duration Optional animation duration return. + */ + public native void ResetProfileAnimation(const char[] animType, int preDefinedIndex = -1, const char[] preDefinedName = "", float &duration = 0.0) +} /** - * Unhooks the boss' current pre-think hook function. Use this sparingly. (Or don't) - * - * @param bossIndex Boss index. + * The methodmap designed for statue bosses. */ -native void SF2_UnhookBossThinkHook(int bossIndex); - -/** - * Forcefully makes the boss perform a voice based on pre-defined voices - * - * Only used with Chaser bosses. - * - * @param bossIndex Boss index. - * @param attackIndex Optional index that looks mainly for attack sounds in sub sections - * @param soundType Type of sound the boss should perform -*/ -native void SF2_PerformBossVoice(int bossIndex, const int attackIndex = -1, int soundType = -1); +methodmap SF2_StatueBossEntity < SF2_BaseBossEntity +{ + /** + * Whether or not the entity is actually a statue boss + */ + property bool IsValid + { + public native get(); + } + + /** + * Returns whether or not the statue is moving. + */ + property bool IsMoving + { + public native get(); + } + + /** + * Gets the time since the statue killed someone. + */ + property float LastKillTime + { + public native get(); + } + + /** + * Gets the boss's profile data. + * + * @param data Data to be stored in. Note this takes in SF2StatueBossProfileData + */ + public native void ProfileData(any data[sizeof(SF2StatueBossProfileData)]); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_StatueBossEntity(int entity) + { + return view_as(entity); + } +} -/** - * Returns the StringMap variable containing every precached base boss profile - * - * @return StringMap containing all base boss profile datas -*/ -native StringMap SF2_GetBossProfileData(); +methodmap SF2_ChaserBossEntity < SF2_BaseBossEntity +{ + /** + * Whether or not the entity is actually a chaser boss + */ + property bool IsValid + { + public native get(); + } + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_ChaserBossEntity(int entity) + { + return view_as(entity); + } + + /** + * Whether or not the boss is attempting to move somewhere else. + */ + property bool IsAttemptingToMove + { + public native get(); + } + + /** + * Whether or not the boss is attacking. + */ + property bool IsAttacking + { + public native get(); + } + + /** + * Whether or not the boss is stunned or not. + */ + property bool IsStunned + { + public native get(); + } + + /** + * The current stun health. + */ + property int StunHealth + { + public native get(); + } + + /** + * The maximum stun health the boss spawns with. + */ + property int MaxStunHealth + { + public native get(); + } + + /** + * Whether or not stun health can be subtracted from the boss. This is + * affected by several factors including: if stunning is enabled or + * stun cooldown has elapsed. + */ + property bool CanBeStunned + { + public native get(); + } + + /** + * Whether or not the boss can take damage. This is mainly useful if the + * boss is performing an attack that makes them invulnerable or are currently + * in a rage state. + */ + property bool CanTakeDamage + { + public native get(); + } + + /** + * Whether or not the boss is currently raging. + */ + property bool IsRaging + { + public native get(); + } + + /** + * Whether or not the boss is currently running away to go heal. + */ + property bool IsRunningAway + { + public native get(); + } + + /** + * Whether or not the boss is currently trying to self heal. + */ + property bool IsSelfHealing + { + public native get(); + } + + /** + * Gets the boss's profile data. + * + * @param data Data to be stored in. Note this takes in SF2ChaserBossProfileData + */ + public native void ProfileData(any data[sizeof(SF2ChaserBossProfileData)]); + + /** + * Forces the boss to perform a voice. Note custom sound sections will not work. + * + * @param soundType Sound type. + * @param attackName Optional attack name to search for. + * @return Whether or not the boss performed a voice. + */ + public native bool PerformVoice(int soundType = -1, const char[] attackName = "") + + /** + * Gets the default posture of the boss. The default posture of a boss + * is named "default". + * + * @param buffer Buffer to store string + * @param bufferSize Size of buffer + * @return Number of characters written to buffer + */ + public native int GetDefaultPosture(char[] buffer, int bufferSize); + + /** + * Sets the default posture of the boss. + * + * @param buffer Posture to set to + * @return True if successful, false otherwise + */ + public native void SetDefaultPosture(const char[] buffer); + + /** + * Gets the current attack name of the boss. + * + * @param buffer Buffer to store string + * @param bufferSize Size of buffer + * @return Number of characters written to buffer + */ + public native int GetAttackName(char[] buffer, int bufferSize); + + /** + * Gets the time which the attack can be used again + * + * @param attackName Attack name + * @return Timestamp + */ + public native float GetNextAttackTime(const char[] attackName); + + /** + * Sets the time which the attack can be used again + * + * @param attackName Attack name + * @param time Timestamp + */ + public native void SetNextAttackTime(const char[] attackName, float time); + + /** + * Drops a medkit/ammo pack depending on how the boss is configured + * + * @param death Whether or not to use the death state's item drop state + */ + public native void DropItem(bool death = false) + + /** + * Checks if an attack is possible the default way + * This only includes FOV and range checks + * Difficulty checks and health checks are not included by default + * Note that cooldown checks are already handled in SF2_ChaserEntity.GetAttackAction + * And visibility checks are already handled in SF2_ChaserAttackLayerAction + * You would use this primarily in SF2_OnIsBossCustomAttackPossible + * + * @param attackName Name of the attack + * @param includeDifficulties Whether or not to include the difficulty checks + * @param includeHealth Whether or not to include target health checks + * @return Whether or not the attack is possible with the distance and FOV checks + */ + public bool IsAttackPossibleDefault(const char[] attackName, bool includeDifficulties = false, bool includeHealth = false) + { + if (!this.Target.IsValid()) + { + return false; + } + + int controller = this.Controller; + int difficulty = SF2_GetBossDifficulty(controller); + + INextBot bot = this.MyNextBotPointer(); + SF2ChaserBossProfileData data; + this.ProfileData(data); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + float eyePos[3], targetPos[3], direction[3], eyeAng[3]; + this.GetAbsAngles(eyeAng); + SF2_GetBossEyePosition(controller, eyePos); + this.Target.GetAbsOrigin(targetPos); + SubtractVectors(targetPos, eyePos, direction); + GetVectorAngles(direction, direction); + direction[2] = 180.0; + + float distance = bot.GetRangeSquaredTo(this.Target.index); + float fov = FloatAbs(AngleDiff(direction[1], eyeAng[1])); + + if (distance > Pow(attackData.BeginRange[difficulty], 2.0)) + { + return false; + } + + if (fov > attackData.BeginFOV[difficulty]) + { + return false; + } + + if (includeDifficulties) + { + if (difficulty < attackData.UseOnDifficulty) + { + return false; + } + + if (difficulty >= attackData.BlockOnDifficulty) + { + return false; + } + } + + if (includeHealth) + { + float health = float(this.Target.GetProp(Prop_Send, "m_iHealth")); + if (attackData.UseOnHealth != -1.0 && health < attackData.UseOnHealth) + { + return false; + } + if (attackData.BlockOnHealth != -1.0 && health >= attackData.BlockOnHealth) + { + return false; + } + } + return true; + } +} /** - * Returns the StringMap variable containing every precached chaser boss profile - * - * @return StringMap containing all chaser boss profile datas -*/ -native StringMap SF2_GetChaserBossProfileData(); + * An action that plays a given sequence on the actor. This action exits when the + * duration has elapsed. + */ +methodmap SF2_PlaySequenceAndWaitAction < NextBotAction +{ + /** + * @param sequence Sequence index + * @param duration How long the action should last. If 0.0, then this will be the sequence's + * duration. + * @param rate Playback rate + * @param cycle Start cycle value, from 0.0 to 1.0 + */ + public native SF2_PlaySequenceAndWaitAction(int sequence, float duration = 0.0, float rate = 1.0, float cycle = 0.0); +} /** - * Returns the StringMap variable containing every precached statue boss profile - * - * @return StringMap containing all statue boss profile datas -*/ -native StringMap SF2_GetStatueBossProfileData(); + * Interface to SF2_BasePlayer + */ +methodmap SF2_Player < CBaseCombatCharacter +{ + /** + * Attempts to cast a client to be this entity. + * + * @param client Client to use + */ + public SF2_Player(int client) + { + if (client <= 0) + { + return view_as(-1); + } + return view_as(client); + } + + property int UserID + { + public native get(); + } + + /** + * Returns true if the client index is > 0 and <= MaxClients, and if the client is in game. + */ + property bool IsValid + { + public native get(); + } + + /** + * Alias of IsPlayerAlive() + */ + property bool IsAlive + { + public native get(); + } + + /** + * Alias of IsClientInGame() + * Use this over IsClientInGame() though as it has extra optimizations + */ + property bool IsInGame + { + public native get(); + } + + /** + * Alias of IsFakeClient() + */ + property bool IsBot + { + public native get(); + } + + /** + * Alias of IsClientSourceTV() + */ + property bool IsSourceTV + { + public native get(); + } + + /** + * Alias of IsClientReplay() + */ + property bool IsReplay + { + public native get(); + } + + /** + * Whether or not the client is in third person mode + */ + property bool InThirdPerson + { + public native get(); + } + + /** + * Alias of GetClientButtons() + */ + property int Buttons + { + public native get(); + } + + /** + * Whether or not the client is moving + */ + public native bool IsMoving(); + + /** + * Gets/Sets the client's health + */ + property int Health + { + public native get(); + + public native set(int value); + } + + /** + * Gets the client's max health + */ + property int MaxHealth + { + public native get(); + } + + /** + * Whether or not the client is trying to duck + */ + property bool Ducking + { + public native get(); + } + + /** + * Whether or not the client is currently ducked + */ + property bool Ducked + { + public native get(); + } + + /** + * Alias of GetEntDataEnt2() + */ + public native int GetDataEnt(int offset); + + /** + * Alias of TF2_GetPlayerClass() + */ + property TFClassType Class + { + public native get(); + } + + /** + * Alias of GetClientTeam() + */ + property int Team + { + public native get(); + } + + /** + * Whether or not the client has a regenerating item like the Cozy Camper + */ + property bool HasRegenItem + { + public native get(); + + public native set(bool state); + } + + /** + * Alias of TF2_IsPlayerInCondition() + * Use this over TF2_IsPlayerInCondition() though as it has extra optimizations + * + * @param condition Condition type + */ + public native bool InCondition(TFCond condition); + + /** + * Alias of both TF2_RemoveCondition() and TF2_AddCondition() + * + * @param condition Condition type + * @param remove Whether or not the condition should be removed + * @param duration How long the condition will last + * @param inflictor Inflictor of the condition + */ + public native bool ChangeCondition(TFCond condition, bool remove = false, float duration = -1.0, int inflictor = 0); + + /** + * Whether or not the client is crit boosted + */ + public native bool IsCritBoosted(); + + /** + * Whether or not the client is mini crit boosted + */ + public native bool IsMiniCritBoosted(); + + /** + * Alias of TF2_IgnitePlayer() + * + * @param self Whether or not the attacker should be the player itself + * @param attacker Attacker index, this doesn't need to be set if self is true + * @param duration How long the afterburn should last + */ + public native void Ignite(bool self = false, int attacker = 0, float duration = 10.0); + + /** + * Alias of TF2_MakeBleed() + * + * @param self Whether or not the attacker should be the player itself + * @param attacker Attacker index, this doesn't need to be set if self is true + * @param duration How long the bleed should last + */ + public native void Bleed(bool self = false, int attacker = 0, float duration = 5.0); + + /** + * Alias of TF2_StunPlayer() + * + * @param duration How long the stun should last + * @param slowdown The multiplier to the player's speed in which they'll be slowed for + * @param stunFlags The flags of the stun + * @param attacker Attacker index + */ + public native void Stun(float duration, float slowdown = 0.0, int stunflags, int attacker = 0); + + /** + * Alias of TF2_RegeneratePlayer() + */ + public native void Regenerate(); + + /** + * Alias of TF2_SetPlayerClass() + * + * @param classType New class + * @param weapons Ignored/Unused + * @param persistent Whether or not the class change should remain after death + */ + public native void SetClass(TFClassType classType, bool weapons = true, bool persistent = true); + + /** + * Alias of GetClientEyePosition() + * + * @param vector Destination to store the eye position + */ + public native void GetEyePosition(float vector[3]); + + /** + * Alias of GetClientEyeAngles() + * + * @param vector Destination to store the eye angles + */ + public native void GetEyeAngles(float vector[3]); + + /** + * Alias of GetEntDataVector() + * + * @param offset Offset to use + * @param buffer Vector to store the data in + */ + public native void GetDataVector(int offset, float buffer[3]); + + /** + * Alias of SetEntDataVector() + * + * @param offset Offset to use + * @param buffer Vector to set + * @param state Whether or not the state should be sent over the network + */ + public native void SetDataVector(int offset, const float buffer[3], bool state = false); + + /** + * Returns the player's distance from an entity + * Note this does not use the square root method + * + * @param ent Entity to check + */ + public native float GetDistanceFromEntity(int ent); + + /** + * Alias of GetPlayerWeaponSlot() + * + * @param slot Weapon slot + */ + public native int GetWeaponSlot(int slot); + + /** + * Switches the player's weapon slot to a specified index + * + * @param slot Weapon slot + */ + public native void SwitchToWeaponSlot(int slot); + + /** + * Alias of TF2_RemoveWeaponSlot() + * + * @param slot Weapon slot + */ + public native void RemoveWeaponSlot(int slot); + + /** + * Shakes the player's screen + * + * @param amp Amplitude + * @param duration Duration of the screen shake + * @param freq Frequency + */ + public native void ScreenShake(float amp, float duration, float freq); + + /** + * Flings the client's camera in a specific direction + * + * @param punchVel Direction/Strength + */ + public native void ViewPunch(const float punchVel[3]); + + /** + * Alias of SDKHooks_TakeDamage() + * + * @param self Self damage + * @param inflictor Inflictor index, don't need to set if self is true + * @param attacker Attacker index, don't need to set if self is true + * @param damage Damage to take + * @param damageType Type of damage + * @param weapon Weapon index + * @param damageForce Velocity of damage force + * @param damagePosition Origin of damage + * @param bypassHooks Whether or not to bypass SDK hooks on OnTakeDamage + */ + public native void TakeDamage(bool self = false, int inflictor = 0, int attacker = 0, float damage, int damageType = 0, int weapon = -1, + const float damageForce[3] = NULL_VECTOR, const float damagePosition[3] = NULL_VECTOR, bool bypassHooks = true); + + /** + * Alias of TF2_RespawnPlayer() + */ + public native void Respawn(); + + /** + * Updates the player's listening flags which determine if a player can hear the voice chat from a player + * + * @param reset Resets all flags + */ + public native void UpdateListeningFlags(bool reset = false); + + /** + * Determines if the player is actually is a valid class, is in RED or BLU, and if the player is not coaching + */ + property bool IsParticipating + { + public native get(); + } + + /** + * Alias of GetClientName() + * + * @param name Buffer to store the client's name. + * @param length Maximum length of string buffer + */ + public native bool GetName(char[] name, int length); + + /** + * Gets/Sets the last buttons the player pressed + */ + property int LastButtons + { + public native get(); + + public native set(int value); + } + + /** + * Makes the player's speed fade to a specific color + */ + public native void ScreenFade(int duration, int time, int flags, int r, int g, int b, int a); + + /** + * Whether or not the player is eliminated from the game + */ + property bool IsEliminated + { + public native get(); + + public native set(bool state); + } + + /** + * Whether or not the player is in ghost mode + */ + property bool IsInGhostMode + { + public native get(); + } + + /** + * Sets the player's ghost mode state + * + * @param state New ghost mode state + */ + public native void SetGhostState(bool state); + + /** + * Whether or not the player is a proxy + */ + property bool IsProxy + { + public native get(); + + public native set(bool state); + } + + /** + * Gets/Sets the player's proxy control points + */ + property int ProxyControl + { + public native get(); + + public native set(int value); + } + + /** + * Gets/Sets the player's proxy master as a boss index + */ + property int ProxyMaster + { + public native get(); + + public native set(int value); + } + + /** + * Whether or not the player is in a PvP arena + */ + property bool IsInPvP + { + public native get(); + } + + /** + * Whether or not the player is in a PvE arena + */ + property bool IsInPvE + { + public native get(); + } + + /** + * Whether or not the player is in a death cam + */ + property bool IsInDeathCam + { + public native get(); + } + + /** + * Starts a death cam for a boss + * + * @param bossIndex Boss index to use + * @param lookPos What position the player should look at + * @param antiCamp Whether or not this was an anticamping death, only useful for public death cams + */ + public native void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false); + + /** + * Whether or not the player has escaped + */ + property bool HasEscaped + { + public native get(); + } + + /** + * Makes the player escape. Note this will not teleport the player to a escape point + */ + public native void Escape(); + + /** + * Teleports the player to an escape point + */ + public native void TeleportToEscapePoint(); + + /** + * Makes the player escape and teleports them to an escape point + */ + public native void ForceEscape(); + + /** + * Whether or not the player is using a flashlight + */ + property bool UsingFlashlight + { + public native get(); + } + + /** + * Toggles the player's flashlight state + */ + public native void HandleFlashlight(); + + /** + * Gets/Sets the player's flashlight battery life + */ + property float FlashlightBatteryLife + { + public native get(); + + public native set(float value); + } + + /** + * Resets the player's flashlight + */ + public native void ResetFlashlight(); + + /** + * Returns the player's next flashlight input time + */ + public native float GetFlashlightNextInputTime(); + + /** + * Whether or not the player is sprinting. Note this only checks if the player is pressing the sprint button + */ + property bool IsSprinting + { + public native get(); + } + + /** + * Whether or not the player is really sprinting. + * This does consider if the player is on the ground and is actually moving + */ + property bool IsReallySprinting + { + public native get(); + } + + /** + * Toggles the player's sprint state + */ + public native void HandleSprint(bool sprint); + + /** + * Sets the player's sprint timer + * + * @param recharge Whether or not the timer should be recharging the player's sprint + */ + public native void SetSprintTimer(bool recharge = false); + + /** + * Returns the player's sprint points from 0-100 + */ + public native int GetSprintPoints(); + + /** + * Sets the player's sprint points + */ + public native void SetSprintPoints(int value); + + /** + * Whether or not the player's blink HUD element is showing + */ + property bool HasStartedBlinking + { + public native get(); + } + + /** + * Whether or not the player is currently blinking + */ + property bool IsBlinking + { + public native get(); + } + + /** + * Whether or not the player is continuing to stay blinked + */ + public native bool IsHoldingBlink(); + + /** + * Sets the player's blink state + * + * @param value Blink state + */ + public native void SetHoldingBlink(bool value); + + /** + * Gets/Sets the player's blink meter from 0.0 to 1.0 + */ + property float BlinkMeter + { + public native get(); + + public native set(float amount); + } + + /** + * Returns how many times the player blinked + */ + property int BlinkCount + { + public native get(); + } + + /** + * Makes the player blink + */ + public native void Blink(); + + /** + * Makes the player start the peeking. This puts the player in third person + */ + public native bool StartPeeking(); + + /** + * Makes the player end the peeking state. This simply puts the player back in first person + */ + public native void EndPeeking(); + + /** + * Gets/Sets the amount of pages the player has collected + */ + property int PageCount + { + public native get(); + + public native set(int amount); + } + + /** + * Shows a hint to the player if the player has it enabled + * + * @param hint Hint index + */ + public native void ShowHint(int hint); + + /** + * Gets/Sets the player's trap state + */ + property bool IsTrapped + { + public native get(); + + public native set(bool state); + } + + /** + * Gets/Sets the player's times they need to jump to not be trapped anymore + */ + property int TrapCount + { + public native get(); + + public native set(int amount); + } + + /** + * Gets/Sets the player's latched state if they're latched onto a boss's rope/tongue ability + */ + property bool IsLatched + { + public native get(); + + public native set(bool state); + } + + /** + * Gets/Sets the player's times they need to jump to not be latched by a boss anymore + */ + property int LatchCount + { + public native get(); + + public native set(int amount); + } + + /** + * Gets/Sets the player's latcher based on which boss is latching them with a boss's rope/tongue ability + */ + property int Latcher + { + public native get(); + + public native set(int value); + } + + /** + * Updates the player's music state + * + * @param initialize Unknown????? Nobody knows what this actually does. + */ + public native void UpdateMusicSystem(bool initialize = false); + + /** + * Whether or not the player has a glow active + */ + property bool HasConstantGlow + { + public native get(); + } + + /** + * Sets the player's play state based on if they're in RED + * + * @param state State to be in + * @param enablePlay Whether or not the player is actively playing. Only set this if you know what you're doing + */ + public native void SetPlayState(bool state, bool enablePlay = true); + + /** + * Whether or not the player can see a boss + * + * @param bossIndex Boss to check + * @param checkFOV Considers if the boss is in the player's FOV + * @param checkBlink Considers if the player is blinking + * @param checkEliminated Considers if the player is eliminated + */ + public native bool CanSeeSlender(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true); + + /** + * Sets the player's AFK time based on a set number of conditions + * + * @param reset Whether or not the AFK time should be reset to the default AFK time + */ + public native void SetAFKTime(bool reset = true); + + /** + * Sets the player's AFK state + */ + public native void SetAFKState(); + + /** + * Checks to make sure if the player is AFK or not, if they are the game puts the player in a AFK state + */ + public native void CheckAFKTime(); + + /** + * Returns if the player should be immediately chased by a boss + * + * @param bossIndex Boss to check + */ + public native bool ShouldBeForceChased(int bossIndex); + + /** + * Sets the player's state to be immediately chased by a boss + * + * @param bossIndex Boss to use + * @param value State + */ + public native void SetForceChaseState(int bossIndex, bool value); + + /** + * Checks if a player is looking at a boss + * + * @param bossIndex Boss to check + */ + public native bool IsLookingAtBoss(int bossIndex); +} /** * Returns the distance of 2 vectors without utilizing the square root function and converting the values properly. @@ -1842,20 +2796,33 @@ native float SF2_GetVectorSquareMagnitude(const float vec1[3], const float vec2[ native void SF2_InitiateBossPackVote(int initiator); /** - * Returns the flags of the projectile. + * Adds a boss to the pool of PvE bosses. Note this takes entity names * - * @param projectile Entity index. - * @return Flags. + * @param bossName Boss entity name */ -native int SF2_GetProjectileFlags(int projectile); +native void SF2_RegisterPvEBoss(char[] bossName); /** - * Sets the flags of the projectile. + * Removes a boss from the pool of PvE bosses. Note this takes entity names + * You MUST call this whenever your boss sub plugin ends or the gamemode will spawn an invalid entity * - * @param projectile Entity index. - * @param flags Flags. + * @param bossName Boss entity name + */ +native void SF2_UnregisterPvEBoss(char[] bossName); + +/** + * Adds a boss onto the PvE boss list. This is required when you spawn your bosses in. + * + * @param bossEnt Entity to add + */ +native void SF2_AddPvEBoss(int bossEnt); + +/** + * Removes a boss from the PvE boss list. This is required when your boss dies. + * + * @param bossEnt Entity to remove */ -native void SF2_SetProjectileFlags(int projectile, int flags); +native void SF2_KillPvEBoss(int bossEnt); /** * Returns if current map is a survival map or not. diff --git a/addons/sourcemod/scripting/include/sf2/defines.inc b/addons/sourcemod/scripting/include/sf2/defines.inc index f1ca4841..4c08161c 100644 --- a/addons/sourcemod/scripting/include/sf2/defines.inc +++ b/addons/sourcemod/scripting/include/sf2/defines.inc @@ -9,8 +9,6 @@ #define FILE_CLASSCONFIGS "configs/sf2/class_stats.cfg" #define FILE_CLASSCONFIGS_DATA "data/sf2/class_stats.cfg" -#define BOSS_THINKRATE 0.1 // doesn't really matter much since timers go at a minimum of 0.1 seconds anyways - #define CRIT_SOUND "player/crit_hit.wav" #define CRIT_PARTICLENAME "crit_text" #define MINICRIT_SOUND "player/crit_hit_mini.wav" @@ -22,10 +20,6 @@ #define TELEPORTEDINBLU_PARTICLENAME "teleported_red" #define SOUND_THUNDER ")ambient/explosions/explode_9.wav" -#define EXPLOSIVEDANCE_EXPLOSION1 ")weapons/explode1.wav" -#define EXPLOSIVEDANCE_EXPLOSION2 ")weapons/explode2.wav" -#define EXPLOSIVEDANCE_EXPLOSION3 ")weapons/explode3.wav" - #define SPECIAL1UPSOUND "mvm/mvm_revive.wav" #define SPECIALROUND_BOO_DISTANCE 120.0 @@ -139,3 +133,5 @@ #define SF2_HUD_TEXT_COLOR_A 255 #define SF2_CHASER_BOSS_MAX_ATTACKS 32 + +#define SF2_PROFILE_CHASER_DEFAULT_POSTURE "default" \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 07c74d2d..b7bf6d76 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -1,5 +1,19 @@ #include +static const char g_SoundModifiers[][] = { + "*", + "#", + "@", + ">", + "<", + "^", + ")", + "}", + "$", + "!", + "?" +}; + // ====================================== // FUNCTIONS // ====================================== @@ -11,8 +25,10 @@ stock void PrecacheSound2(const char[] path, bool checkFile, bool replace = true PrecacheSound(tempPath, true); if (replace) { - ReplaceStringEx(tempPath, sizeof(tempPath), ")", ""); - ReplaceStringEx(tempPath, sizeof(tempPath), "#", ""); + for (int i = 0; i < sizeof(g_SoundModifiers); i++) + { + ReplaceStringEx(tempPath, sizeof(tempPath), g_SoundModifiers[i], ""); + } } char buffer[PLATFORM_MAX_PATH]; FormatEx(buffer, sizeof(buffer), "sound/%s", tempPath); @@ -52,8 +68,10 @@ stock void TryPrecacheBossProfileSoundPath(const char[] soundPath, bool checkFil char fullPath[PLATFORM_MAX_PATH], tempPath[PLATFORM_MAX_PATH]; strcopy(tempPath, sizeof(tempPath), soundPath); PrecacheSound(tempPath); - ReplaceStringEx(tempPath, sizeof(tempPath), ")", ""); - ReplaceStringEx(tempPath, sizeof(tempPath), "#", ""); + for (int i = 0; i < sizeof(g_SoundModifiers); i++) + { + ReplaceStringEx(tempPath, sizeof(tempPath), g_SoundModifiers[i], ""); + } FormatEx(fullPath, sizeof(fullPath), "sound/%s", tempPath); if (checkFile) @@ -82,11 +100,11 @@ stock void TryPrecacheBossProfileSoundPath(const char[] soundPath, bool checkFil * @param xbox Determines if the .xbox file extension should be checked. * @param checkFile Determines if missing files should be checked. */ -stock void PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, bool checkFile) +stock int PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, bool checkFile) { if (path[0] == '\0') { - return; + return -1; } char fixedPath[PLATFORM_MAX_PATH]; @@ -138,9 +156,10 @@ stock void PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, } FormatEx(buffer, sizeof(buffer), "models/%s.mdl", fixedPath); + int returnValue = -1; if (!IsModelPrecached(buffer)) { - PrecacheModel(buffer); + returnValue = PrecacheModel(buffer); } if (checkFile) @@ -228,6 +247,8 @@ stock void PrecacheModel2(const char[] path, bool phy = true, bool xbox = false, AddFileToDownloadsTable(buffer); } } + + return returnValue; } /** @@ -396,6 +417,13 @@ stock void GetProfileDifficultyNumValues(KeyValues kv, const char[] baseKeyName, buffer[j] = value; } } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } } } @@ -434,6 +462,13 @@ stock void GetProfileDifficultyBoolValues(KeyValues kv, const char[] baseKeyName buffer[j] = value; } } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } } } @@ -472,6 +507,13 @@ stock void GetProfileDifficultyFloatValues(KeyValues kv, const char[] baseKeyNam buffer[j] = value; } } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } } } @@ -526,6 +568,13 @@ stock void GetProfileDifficultyVectorValues(KeyValues kv, const char[] baseKeyNa buffer[j] = value; } } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } } } @@ -580,6 +629,13 @@ void GetProfileDifficultyStringValues(KeyValues kv, const char[] baseKeyName, ch strcopy(bufferArray[j], maxStringLen, value); } } + else + { + if (i > 0) + { + strcopy(bufferArray[i], maxStringLen, bufferArray[i - 1]); + } + } } } @@ -587,12 +643,12 @@ stock void SetProfileDifficultyStringArrayValues(KeyValues kv, const char[] base { char key[64]; char temporaryKeys[PLATFORM_MAX_PATH]; + char[] value = new char[PLATFORM_MAX_PATH]; for (int i = 0; i < Difficulty_Max; i++) { GetProfileKeyWithDifficultySuffix(baseKeyName, i == 0 ? 1 : i, key, sizeof(key)); - char[] value = new char[PLATFORM_MAX_PATH]; - kv.GetString(key, value, PLATFORM_MAX_PATH, temporaryKeys); + kv.GetString(key, value, PLATFORM_MAX_PATH, value); if (models && strncmp(value, "models/", 7, false) != 0 && strncmp(value, "models\\", 7, false) != 0 && value[0] != '\0') { Format(value, PLATFORM_MAX_PATH, "models/%s", value); @@ -611,10 +667,10 @@ bool GetProfileColorNoBacks(KeyValues kv, int &g, int &b, int &a, - int dr=255, - int dg=255, - int db=255, - int da=255) + int dr = 255, + int dg = 255, + int db = 255, + int da = 255) { r = dr; g = dg; @@ -682,6 +738,13 @@ void GetProfileDifficultyColorValues(KeyValues kv, const char[] baseKeyName, int buffer[j] = value; } } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } + } } } @@ -691,6 +754,7 @@ void GetProfileDifficultyColorValues(KeyValues kv, const char[] baseKeyName, int enum struct SF2BossProfileSoundInfo { + char SectionName[64]; int Channel; float Volume; int Flags; @@ -707,6 +771,7 @@ enum struct SF2BossProfileSoundInfo void Init() { + this.SectionName[0] = '\0'; this.Channel = SNDCHAN_AUTO; this.Volume = SNDVOL_NORMAL; this.Flags = SND_NOFLAGS; @@ -719,13 +784,14 @@ enum struct SF2BossProfileSoundInfo this.Radius = 850.0; this.Chance = 1.0; - this.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + this.Paths = null; } void Load(KeyValues kv, bool checkFile) { if (kv.JumpToKey("paths")) { + this.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); char soundPath[PLATFORM_MAX_PATH], section[64]; for (int i = 1;; i++) { @@ -801,14 +867,34 @@ enum struct SF2BossProfileSoundInfo } } - void EmitSound(bool toClient = false, int entity = -1, int emitter = SOUND_FROM_PLAYER, float origin[3] = NULL_VECTOR, int addPitch = 0) + void EmitSound(bool toClient = false, int entity = -1, int emitter = SOUND_FROM_PLAYER, float origin[3] = NULL_VECTOR, int addPitch = 0, char returnSound[PLATFORM_MAX_PATH] = "", int definedIndex = -1) { if (this.Paths == null || this.Paths.Length <= 0) { return; } char buffer[PLATFORM_MAX_PATH]; - this.Paths.GetString(GetRandomInt(0, this.Paths.Length - 1), buffer, sizeof(buffer)); + if (definedIndex != -1) + { + this.Paths.GetString(definedIndex, buffer, sizeof(buffer)); + } + else + { + this.Paths.GetString(GetRandomInt(0, this.Paths.Length - 1), buffer, sizeof(buffer)); + } + returnSound = buffer; + if (StrContains(buffer, ".mp3", true) == -1 && StrContains(buffer, ".wav", true) == -1) + { + if (!toClient) + { + EmitGameSoundToAll(buffer, entity, this.Flags); + } + else + { + EmitGameSoundToClient(entity, buffer, emitter, this.Flags); + } + return; + } int pitch = GetRandomInt(this.PitchRandomMin, this.PitchRandomMax); int volumeCount = RoundToCeil(this.Volume); for (int i = 0; i < volumeCount; i++) @@ -870,6 +956,98 @@ enum struct SF2BossProfileSoundInfo } } +enum struct SF2ParticleData +{ + char ParticleName[128]; + bool AttachParticles; + bool BeamParticles; + ParticleAttachment Type; + char Attachment[64]; + + void Init() + { + this.ParticleName[0] = '\0'; + this.AttachParticles = true; + this.BeamParticles = false; + this.Type = PATTACH_CUSTOMORIGIN; + this.Attachment[0] = '\0'; + } + + void Load(KeyValues kv) + { + kv.GetString("particle", this.ParticleName, sizeof(this.ParticleName), this.ParticleName); + this.AttachParticles = kv.GetNum("attach", this.AttachParticles) != 0; + this.BeamParticles = kv.GetNum("beam", this.BeamParticles) != 0; + this.Type = view_as(kv.GetNum("attach_type", view_as(this.Type))); + kv.GetString("attachment", this.Attachment, sizeof(this.Attachment), this.Attachment); + } + + void Apply(CBaseAnimating player, CBaseAnimating caster) + { + CBaseAnimating castTo; + float myPos[3], myAng[3], targetPos[3]; + if (!this.AttachParticles || this.BeamParticles) + { + caster.WorldSpaceCenter(myPos); + caster.GetAbsAngles(myAng); + player.WorldSpaceCenter(targetPos); + if (this.AttachParticles) + { + castTo = caster; + } + } + else + { + player.WorldSpaceCenter(myPos); + player.GetAbsAngles(myAng); + castTo = player; + } + + int attachment = 0; + if (this.Attachment[0] != '\0') + { + attachment = castTo.LookupAttachment(this.Attachment); + } + if (attachment != 0) + { + castTo.GetAttachment(attachment, myPos, myAng); + } + + if (this.Type == PATTACH_ROOTBONE_FOLLOW) + { + myPos = NULL_VECTOR; + myAng = NULL_VECTOR; + } + + if (this.BeamParticles) + { + DispatchParticleEffectBeam(castTo.index, this.ParticleName, myPos, myAng, targetPos, attachment, this.Type, true); + } + else + { + if (!DispatchParticleEffect(castTo.index, this.ParticleName, myPos, myAng, myPos, attachment, this.Type, true)) + { + if (this.Type == PATTACH_ROOTBONE_FOLLOW) + { + castTo.WorldSpaceCenter(myPos); + castTo.GetAbsAngles(myAng); + } + CBaseEntity particleEnt = CBaseEntity(CreateEntityByName("info_particle_system")); + if (particleEnt.IsValid()) + { + particleEnt.Teleport(myPos, myAng, NULL_VECTOR); + + particleEnt.KeyValue("effect_name", this.ParticleName); + particleEnt.Spawn(); + particleEnt.Activate(); + particleEnt.AcceptInput("start"); + RemoveEntity(particleEnt.index); + } + } + } + } +} + enum EffectEvent { EffectEvent_Invalid = -1, @@ -887,11 +1065,15 @@ enum EffectType EffectType_Trail, EffectType_PropDynamic, EffectType_PointSpotlight, - EffectType_Sprite + EffectType_Sprite, + EffectType_TempEntBeamRing, + EffectType_TempEntParticle, + EffectType_Sound }; enum struct SF2BossProfileBaseEffectInfo { + char SectionName[64]; EffectEvent Event; EffectType Type; float Origin[3]; @@ -906,6 +1088,8 @@ enum struct SF2BossProfileBaseEffectInfo char AttachmentName[PLATFORM_MAX_PATH]; ArrayList Colors; int DifficultyIndexes; + float Delay; + bool AttachPlayer; int SteamSpreadSpeed; int SteamSpeed; @@ -935,18 +1119,45 @@ enum struct SF2BossProfileBaseEffectInfo float SpotlightWidth; float SpotlightLength; + float SpotlightHaloScale; char SpriteName[PLATFORM_MAX_PATH]; float SpriteScale; + float BeamRingStartRadius; + float BeamRingEndRadius; + int BeamRingStartFrame; + int BeamRingFrameRate; + float BeamRingWidth; + float BeamRingAmplitude; + int BeamRingSpeed; + int BeamRingFlags; + char BeamRingColor[32]; + char BeamRingBeamSprite[PLATFORM_MAX_PATH]; + int BeamRingBeamModel; + char BeamRingHaloSprite[PLATFORM_MAX_PATH]; + int BeamRingHaloModel; + + float TEParticleStartPos[3]; + int TEParticleAttachType; + bool TEParticleReset; + bool TEParticleHasControlPoint; + int TEParticleControlPointAttachType; + float TEParticleControlPointOffset[3]; + + SF2BossProfileSoundInfo SoundSounds; + void Init() { + this.SectionName[0] = '\0'; this.Event = EffectEvent_Invalid; this.Type = EffectType_Invalid; for (int i = 0; i < 3; i++) { this.Origin[i] = 0.0; this.Angles[i] = 0.0; + this.TEParticleStartPos[i] = 0.0; + this.TEParticleControlPointOffset[i] = 0.0; } this.RenderModes = RENDER_NORMAL; this.RenderEffects = RENDERFX_NONE; @@ -956,6 +1167,7 @@ enum struct SF2BossProfileBaseEffectInfo this.LifeTime = -1.0; this.Colors = null; this.DifficultyIndexes = 123456; + this.Delay = 0.0; this.SteamSpreadSpeed = 0; this.SteamSpeed = 0; @@ -985,14 +1197,37 @@ enum struct SF2BossProfileBaseEffectInfo this.SpotlightWidth = 40.0; this.SpotlightLength = 512.0; + this.SpotlightHaloScale = 40.0; this.SpriteName[0] = '\0'; this.SpriteScale = 1.0; + + this.BeamRingStartRadius = 5.0; + this.BeamRingEndRadius = 10.0; + this.BeamRingStartFrame = 0; + this.BeamRingFrameRate = 12; + this.BeamRingWidth = 100.0; + this.BeamRingAmplitude = 1.0; + this.BeamRingSpeed = 5; + this.BeamRingFlags = 0; + this.BeamRingColor = "255 255 255 255"; + this.BeamRingBeamSprite[0] = '\0'; + this.BeamRingBeamModel = -1; + this.BeamRingHaloSprite[0] = '\0'; + this.BeamRingHaloModel = -1; + + this.TEParticleAttachType = 0; + this.TEParticleReset = true; + this.TEParticleHasControlPoint = false; + this.TEParticleControlPointAttachType = 0; + + this.SoundSounds.Init(); } - void Load(KeyValues kv) + void Load(KeyValues kv, bool checkFile) { - char effectTypeString[64]; + kv.GetSectionName(this.SectionName, sizeof(this.SectionName)); + char effectTypeString[64], buffer[PLATFORM_MAX_PATH]; kv.GetString("type", effectTypeString, sizeof(effectTypeString)); if (strcmp(effectTypeString, "steam", false) == 0) { @@ -1006,7 +1241,7 @@ enum struct SF2BossProfileBaseEffectInfo this.SteamRollSpeed = kv.GetFloat("rollspeed", this.SteamRollSpeed); this.SteamType = kv.GetNum("particletype", this.SteamType); } - if (strcmp(effectTypeString, "dynamiclight", false) == 0) + else if (strcmp(effectTypeString, "dynamiclight", false) == 0) { this.Type = EffectType_DynamicLight; this.LightBrightness = kv.GetNum("brightness", this.LightBrightness); @@ -1016,12 +1251,12 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } - if (strcmp(effectTypeString, "particle", false) == 0) + else if (strcmp(effectTypeString, "particle", false) == 0) { this.Type = EffectType_Particle; kv.GetString("particlename", this.ParticleName, sizeof(this.ParticleName), this.ParticleName); } - if (strcmp(effectTypeString, "trail", false) == 0) + else if (strcmp(effectTypeString, "trail", false) == 0) { this.Type = EffectType_Trail; this.TrailTime = kv.GetFloat("trailtime", this.TrailTime); @@ -1031,7 +1266,7 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } - if (strcmp(effectTypeString, "propdynamic", false) == 0) + else if (strcmp(effectTypeString, "propdynamic", false) == 0) { this.Type = EffectType_PropDynamic; kv.GetString("modelname", this.ModelName, sizeof(this.ModelName), this.ModelName); @@ -1041,16 +1276,20 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } - if (strcmp(effectTypeString, "pointspotlight", false) == 0) + else if (strcmp(effectTypeString, "pointspotlight", false) == 0) { this.Type = EffectType_PointSpotlight; this.RenderModes = RENDER_TRANSTEXTURE; this.SpotlightWidth = kv.GetFloat("spotlightwidth", this.SpotlightWidth); this.SpotlightLength = kv.GetFloat("spotlightlength", this.SpotlightLength); + this.SpotlightHaloScale = kv.GetFloat("halo_scale", this.SpotlightHaloScale); + this.LightBrightness = kv.GetNum("brightness", this.LightBrightness); + this.LightMaxDistance = kv.GetFloat("distance", this.LightMaxDistance); + this.LightCone = kv.GetNum("cone", this.LightCone); this.SetupColors(kv); } - if (strcmp(effectTypeString, "sprite", false) == 0) + else if (strcmp(effectTypeString, "sprite", false) == 0) { this.Type = EffectType_Sprite; kv.GetString("spritename", this.SpriteName, sizeof(this.SpriteName), this.SpriteName); @@ -1058,8 +1297,78 @@ enum struct SF2BossProfileBaseEffectInfo this.SetupColors(kv); } + else if (strcmp(effectTypeString, "te_beamring", false) == 0) + { + this.Type = EffectType_TempEntBeamRing; + this.BeamRingStartRadius = kv.GetFloat("start_radius", this.BeamRingStartRadius); + this.BeamRingEndRadius = kv.GetFloat("end_radius", this.BeamRingEndRadius); + this.BeamRingStartFrame = kv.GetNum("start_frame", this.BeamRingStartFrame); + this.BeamRingFrameRate = kv.GetNum("framerate", this.BeamRingFrameRate); + this.BeamRingWidth = kv.GetFloat("width", this.BeamRingWidth); + this.BeamRingAmplitude = kv.GetFloat("amplitude", this.BeamRingAmplitude); + this.BeamRingSpeed = kv.GetNum("speed", this.BeamRingSpeed); + this.BeamRingFlags = kv.GetNum("flags", this.BeamRingFlags); + kv.GetString("color", this.BeamRingColor, sizeof(this.BeamRingColor), this.BeamRingColor); + kv.GetString("beam_sprite", this.BeamRingBeamSprite, sizeof(this.BeamRingBeamSprite), this.BeamRingBeamSprite); + if (this.BeamRingBeamSprite[0] != '\0') + { + this.BeamRingBeamModel = PrecacheModel(this.BeamRingBeamSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", this.BeamRingBeamSprite); + AddFileToDownloadsTable(buffer); + } + kv.GetString("halo_sprite", this.BeamRingHaloSprite, sizeof(this.BeamRingHaloSprite), this.BeamRingHaloSprite); + if (this.BeamRingHaloSprite[0] != '\0') + { + this.BeamRingHaloModel = PrecacheModel(this.BeamRingHaloSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", this.BeamRingHaloSprite); + AddFileToDownloadsTable(buffer); + } + } + else if (strcmp(effectTypeString, "te_particle", false) == 0) + { + this.Type = EffectType_TempEntParticle; + kv.GetString("particlename", this.ParticleName, sizeof(this.ParticleName), this.ParticleName); + kv.GetVector("start", this.TEParticleStartPos, this.TEParticleStartPos); + + char attachType[64]; + kv.GetString("attach_type", attachType, sizeof(attachType)); + if (strcmp(attachType, "follow_origin", false) == 0) + { + this.TEParticleAttachType = 1; + } + else if (strcmp(attachType, "start_at_customorigin", false) == 0) + { + this.TEParticleAttachType = 2; + } + else if (strcmp(attachType, "start_at_attachment", false) == 0) + { + this.TEParticleAttachType = 3; + } + else if (strcmp(attachType, "follow_attachment", false) == 0) + { + this.TEParticleAttachType = 4; + } + else if (strcmp(attachType, "start_at_worldorigin", false) == 0) + { + this.TEParticleAttachType = 5; + } + else if (strcmp(attachType, "follow_rootbone", false) == 0) + { + this.TEParticleAttachType = 6; + } + + this.TEParticleReset = kv.GetNum("reset_particles", this.TEParticleReset) != 0; + this.TEParticleHasControlPoint = kv.GetNum("control_point", this.TEParticleHasControlPoint) != 0; + this.TEParticleControlPointAttachType = kv.GetNum("control_point_attach_type", this.TEParticleControlPointAttachType); + kv.GetVector("control_point_offset", this.TEParticleControlPointOffset, this.TEParticleControlPointOffset); + } + else if (strcmp(effectTypeString, "sound", false) == 0) + { + this.Type = EffectType_Sound; + this.SoundSounds.Load(kv, checkFile); + } - kv.GetString("event", effectTypeString, sizeof(effectTypeString)); + kv.GetString("event", effectTypeString, sizeof(effectTypeString), "constant"); if (strcmp(effectTypeString, "constant", false) == 0) { this.Event = EffectEvent_Constant; @@ -1080,8 +1389,11 @@ enum struct SF2BossProfileBaseEffectInfo kv.GetVector("origin", this.Origin, this.Origin); kv.GetVector("angles", this.Angles, this.Angles); this.FadeAlpha = kv.GetNum("renderamt", this.FadeAlpha); - this.Attachment = view_as(kv.GetNum("attach_point", this.Attachment)); + this.Attachment = kv.GetNum("attach_point", this.Attachment) != 0; kv.GetString("attachment_point", this.AttachmentName, sizeof(this.AttachmentName), this.AttachmentName); + this.LifeTime = kv.GetFloat("lifetime", this.LifeTime); + this.Delay = kv.GetFloat("delay", this.Delay); + this.AttachPlayer = kv.GetNum("attach_player", this.AttachPlayer) != 0; } void SetupColors(KeyValues kv) @@ -1096,12 +1408,18 @@ enum struct SF2BossProfileBaseEffectInfo } } + void PostLoad() + { + this.SoundSounds.PostLoad(); + } + void Destroy() { if (this.Colors != null) { delete this.Colors; } + this.SoundSounds.Destroy(); } } @@ -1114,16 +1432,20 @@ enum struct SF2BossProfileCompanionsInfo { for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - this.Weight[difficulty] = difficulty > 0 ? this.Weight[difficulty - 1] : 1.0; + this.Weight[difficulty] = 1.0; } - this.Bosses = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); + this.Bosses = null; } void Load(KeyValues kv) { char compProfile[SF2_MAX_PROFILE_NAME_LENGTH], num[32]; - kv.JumpToKey("bosses"); - for (int i = 1;;i++) + if (!kv.JumpToKey("bosses")) + { + return; + } + this.Bosses = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); + for (int i = 1;; i++) { FormatEx(num, sizeof(num), "%d", i); kv.GetString(num, compProfile, sizeof(compProfile)); @@ -1187,12 +1509,14 @@ enum struct SF2BossProfileAnimationSectionNameData // This does NOT cover the en float Playbackrate[Difficulty_Max]; float Cycle[Difficulty_Max]; float FootstepInterval[Difficulty_Max]; + bool OverrideLoop[Difficulty_Max]; + bool Loop[Difficulty_Max]; char SectionName[64]; + char SubSectionName[64]; - void Init(char optionalSectionName[64] = "") + void Init(char optionalSectionName[64] = "", char optionalSubSection[64] = "") { - this.Sequences = new ArrayList(ByteCountToCells(64)); - this.Sequences.Resize(Difficulty_Max); + this.Sequences = null; if (optionalSectionName[0] != '\0') { this.SectionName[0] = '\0'; @@ -1201,32 +1525,32 @@ enum struct SF2BossProfileAnimationSectionNameData // This does NOT cover the en { this.SectionName = optionalSectionName; } + this.SubSectionName = optionalSubSection; for (int i = 0; i < Difficulty_Max; i++) { - if (strcmp(optionalSectionName, "stun") == 0) - { - this.Duration[i] = this.Duration[i] = i > 0 ? this.Duration[i - 1] : 3.5; - } - else - { - this.Duration[i] = this.Duration[i] = i > 0 ? this.Duration[i - 1] : 0.0; - } - this.Playbackrate[i] = this.Playbackrate[i] = i > 0 ? this.Playbackrate[i - 1] : 1.0; - this.Cycle[i] = this.Cycle[i] = i > 0 ? this.Cycle[i - 1] : 0.0; - this.FootstepInterval[i] = this.FootstepInterval[i] = i > 0 ? this.FootstepInterval[i - 1] : 0.0; + this.Duration[i] = 0.0; + this.Playbackrate[i] = 1.0; + this.Cycle[i] = 0.0; + this.FootstepInterval[i] = -1.0; + this.OverrideLoop[i] = false; + this.Loop[i] = false; } } void Load(KeyValues kv) { char animNames[Difficulty_Max][64]; + this.Sequences = new ArrayList(ByteCountToCells(64)); + this.Sequences.Resize(Difficulty_Max); GetProfileDifficultyStringValues(kv, "name", animNames, sizeof(animNames[]), animNames); GetProfileDifficultyFloatValues(kv, "playbackrate", this.Playbackrate, this.Playbackrate); GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); GetProfileDifficultyFloatValues(kv, "cycle", this.Cycle, this.Cycle); GetProfileDifficultyFloatValues(kv, "footstepinterval", this.FootstepInterval, this.FootstepInterval); + GetProfileDifficultyBoolValues(kv, "override_loop", this.OverrideLoop, this.OverrideLoop); + GetProfileDifficultyBoolValues(kv, "loop", this.Loop, this.Loop); for (int i = 0; i < Difficulty_Max; i++) { @@ -1260,14 +1584,16 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima void Init() { - this.Animations = new StringMap(); - this.Gestures = new StringMap(); + this.Animations = null; + this.Gestures = null; } void Load(KeyValues kv) { if (kv.JumpToKey("animations")) { + this.Animations = new StringMap(); + this.Gestures = new StringMap(); char animType[64]; SF2BossProfileAnimationSectionNameData animData; SF2BossProfileAttackGestureData gestureData; @@ -1285,7 +1611,9 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima { do { - gestureData.Init(); + char animType2[64]; + kv.GetSectionName(animType2, sizeof(animType2)); + gestureData.Init(animType2); gestureData.Load(kv); gestureDataArray.PushArray(gestureData); } @@ -1297,43 +1625,20 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima } ArrayList animDataArray = new ArrayList(sizeof(animData)); - bool doResize = false; - if (strcmp(animType, "shoot") == 0) - { - doResize = true; - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - SF2BossProfileAnimationSectionNameData tempData; - animDataArray.PushArray(tempData, sizeof(tempData)); - } - } - if (kv.GotoFirstSubKey()) // Find the indexes { do { char animType2[64]; kv.GetSectionName(animType2, sizeof(animType2)); - animData.Init(animType); + animData.Init(animType, animType2); animData.Load(kv); - if (!doResize) - { - animDataArray.PushArray(animData); - } - else - { - int index = StringToInt(animType2); - if (index > -1) - { - animDataArray.SetArray(index - 1, animData, sizeof(animData)); - } - } + animDataArray.PushArray(animData); } while (kv.GotoNextKey()); kv.GoBack(); } - this.Animations.SetValue(animType, animDataArray); } while (kv.GotoNextKey()); @@ -1347,6 +1652,10 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima void Destroy() { + if (this.Animations == null || this.Gestures == null) + { + return; + } StringMapSnapshot snapshot = this.Animations.Snapshot(); StringMapSnapshot gestureSnapshot = this.Gestures.Snapshot(); @@ -1365,7 +1674,6 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima animDataArray.GetArray(j, animData, sizeof(animData)); animData.Destroy(); } - if (animDataArray != null) { delete animDataArray; @@ -1382,7 +1690,6 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima animDataArray.GetArray(j, gestureData, sizeof(gestureData)); gestureData.Destroy(); } - if (animDataArray != null) { delete animDataArray; @@ -1390,19 +1697,35 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima } delete snapshot; + delete gestureSnapshot; delete this.Animations; delete this.Gestures; } - bool GetAnimation(const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &duration = 0.0, float &cycle = 0.0, float &footstepinterval = 0.0, int &index = 0, int preDefinedIndex = -1) + bool HasAnimationSection(const char[] animType) + { + if (this.Animations == null) + { + return false; + } + ArrayList tempArray; + + return this.Animations.GetValue(animType, tempArray); + } + + bool GetAnimation(const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &duration = 0.0, float &cycle = 0.0, float &footstepinterval = 0.0, int &index = 0, int preDefinedIndex = -1, const char[] preDefinedName = "", bool &overrideLoop = false, bool &loop = false, int findIndex = -1) { + if (this.Animations == null) + { + return false; + } ArrayList animDataArray; if (this.Animations.GetValue(animType, animDataArray)) { - if (animDataArray.Length == 0) + if (findIndex >= 0) { - return false; + preDefinedIndex = animDataArray.FindValue(findIndex); } int randomAnimation; if (preDefinedIndex <= -1) @@ -1418,13 +1741,35 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima randomAnimation = preDefinedIndex; } SF2BossProfileAnimationSectionNameData animData; - animDataArray.GetArray(randomAnimation, animData, sizeof(animData)); + if (preDefinedName[0] != '\0') + { + bool found = false; + for (int i = 0; i < animDataArray.Length; i++) + { + animDataArray.GetArray(i, animData, sizeof(animData)); + if (strcmp(animData.SubSectionName, preDefinedName) == 0) + { + found = true; + break; + } + } + if (!found) + { + return false; + } + } + else + { + animDataArray.GetArray(randomAnimation, animData, sizeof(animData)); + } animData.GetSequence(difficulty, buffer, bufferLen); rate = animData.Playbackrate[difficulty]; duration = animData.Duration[difficulty]; cycle = animData.Cycle[difficulty]; footstepinterval = animData.FootstepInterval[difficulty]; + overrideLoop = animData.OverrideLoop[difficulty]; + loop = animData.Loop[difficulty]; index = randomAnimation; return true; @@ -1433,18 +1778,38 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima return false; } - bool GetGesture(int definedIndex, const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &cycle = 0.0) + bool GetGesture(int definedIndex, const char[] definedName = "", const char[] animType, int difficulty, char[] buffer, int bufferLen, float &rate = 1.0, float &cycle = 0.0) { + if (this.Gestures == null) + { + return false; + } ArrayList gestureDataArray; if (this.Gestures.GetValue(animType, gestureDataArray)) { - if (gestureDataArray.Length == 0) + SF2BossProfileAttackGestureData gestureData; + if (definedName[0] != '\0') + { + bool found = false; + for (int i = 0; i < gestureDataArray.Length; i++) + { + gestureDataArray.GetArray(i, gestureData, sizeof(gestureData)); + if (strcmp(gestureData.SectionName, definedName) == 0) + { + found = true; + break; + } + } + if (!found) + { + return false; + } + } + else { - return false; + gestureDataArray.GetArray(definedIndex, gestureData, sizeof(gestureData)); } - SF2BossProfileAttackGestureData gestureData; - gestureDataArray.GetArray(definedIndex, gestureData, sizeof(gestureData)); gestureData.GetGesture(difficulty, buffer, bufferLen); rate = gestureData.Playbackrate[difficulty]; @@ -1457,6 +1822,137 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima } } +enum struct SF2BossProfileCopiesInfo +{ + bool Enabled[Difficulty_Max]; + int MinCopies[Difficulty_Max]; + int MaxCopies[Difficulty_Max]; + float TeleportDistanceSpacing[Difficulty_Max]; + bool Fakes[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.MinCopies[difficulty] = 0; + this.MaxCopies[difficulty] = 1; + this.TeleportDistanceSpacing[difficulty] = 800.0; + this.Fakes[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + if (!kv.JumpToKey("copies")) + { + GetProfileDifficultyBoolValues(kv, "copy", this.Enabled, this.Enabled); + GetProfileDifficultyNumValues(kv, "copy_max", this.MaxCopies, this.MaxCopies); + GetProfileDifficultyFloatValues(kv, "copy_teleport_dist_from_others", this.TeleportDistanceSpacing, this.TeleportDistanceSpacing); + GetProfileDifficultyBoolValues(kv, "fake_copies", this.Fakes, this.Fakes); + } + else + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyNumValues(kv, "min", this.MinCopies, this.MinCopies); + GetProfileDifficultyNumValues(kv, "max", this.MaxCopies, this.MaxCopies); + GetProfileDifficultyFloatValues(kv, "teleport_spacing_between", this.TeleportDistanceSpacing, this.TeleportDistanceSpacing); + GetProfileDifficultyBoolValues(kv, "fakes", this.Fakes, this.Fakes); + kv.GoBack(); + } + } +} + +enum struct SF2BossProfilePublicDeathCamData +{ + bool Enabled; + bool Blackout; + SF2BossProfileSoundInfo ExecutionSounds; + char Attachment[64]; + char TargetAttachment[64]; + float DownwardOffset; + float BackwardOffset; + + void Init() + { + this.Enabled = false; + this.Blackout = false; + this.ExecutionSounds.Init(); + this.Attachment[0] = '\0'; + this.TargetAttachment[0] = '\0'; + this.DownwardOffset = 0.0; + this.BackwardOffset = 0.0; + } + + void Load(KeyValues kv, bool checkFile) + { + this.Enabled = true; + this.Blackout = kv.GetNum("blackout", this.Blackout) != 0; + if (kv.JumpToKey("sounds")) + { + if (kv.JumpToKey("execution")) + { + this.ExecutionSounds.Load(kv, checkFile); + kv.GoBack(); + } + kv.GoBack(); + } + kv.GetString("attachment", this.Attachment, sizeof(this.Attachment), this.Attachment); + kv.GetString("target_attachment", this.TargetAttachment, sizeof(this.TargetAttachment), this.TargetAttachment); + if (kv.JumpToKey("offset")) + { + this.DownwardOffset = kv.GetFloat("downward", this.DownwardOffset); + this.BackwardOffset = kv.GetFloat("backward", this.BackwardOffset); + kv.GoBack(); + } + } + + void PostLoad() + { + this.ExecutionSounds.PostLoad(); + } + + void Destroy() + { + this.ExecutionSounds.Destroy(); + } +} + +enum struct SF2BossProfileEyeData +{ + int Type; + float OffsetPos[3]; + float OffsetAng[3]; + char Bone[128]; + + void Init() + { + this.Type = 0; + for (int i = 0; i < 3; i++) + { + this.OffsetPos[i] = 0.0; + this.OffsetAng[i] = 0.0; + } + + this.OffsetPos[2] = 45.0; + this.Bone[0] = '\0'; + } + + void Load(KeyValues kv) + { + char type[64]; + kv.GetString("mode", type, sizeof(type), "default"); + if (strcmp(type, "bone") == 0) + { + this.Type = 1; + kv.GetString("bone", this.Bone, sizeof(this.Bone), this.Bone); + this.OffsetPos[2] = 0.0; + } + kv.GetVector("offset_pos", this.OffsetPos, this.OffsetPos); + kv.GetVector("offset_ang", this.OffsetAng, this.OffsetAng); + } +} + enum struct SF2BossProfileData { ArrayList Models; @@ -1529,10 +2025,9 @@ enum struct SF2BossProfileData float DeathCamTime; float DeathCamPos[3]; - int Copies[Difficulty_Max]; + SF2BossProfilePublicDeathCamData DeathCamData; float RunSpeed[Difficulty_Max]; - float MaxRunSpeed[Difficulty_Max]; float Acceleration[Difficulty_Max]; float IdleLifeTime[Difficulty_Max]; @@ -1585,7 +2080,6 @@ enum struct SF2BossProfileData float TeleportPersistencyPeriod[Difficulty_Max]; bool TeleportIgnoreChases; bool TeleportIgnoreVis; - float TeleportCopyDistance; float JumpscareDistance[Difficulty_Max]; float JumpscareDuration[Difficulty_Max]; @@ -1622,6 +2116,7 @@ enum struct SF2BossProfileData ArrayList ProxyDeathAnimations; int ProxyDeathAnimFrames[10]; bool ProxyZombies; + bool ProxyRobots; bool ProxyDifficultyModels; ArrayList ProxyModels[10]; ArrayList ProxyModelsHard[10]; @@ -1631,8 +2126,6 @@ enum struct SF2BossProfileData bool ProxyOverrideMaxSpeed; float ProxyMaxSpeed[Difficulty_Max]; - bool FakeCopies; - bool DrainCredits; int DrainCreditAmount[Difficulty_Max]; @@ -1672,6 +2165,10 @@ enum struct SF2BossProfileData int EngineSoundLevel; float EngineSoundVolume; + bool IsPvEBoss; + ArrayList PvESpawnMessagesArray; + char PvESpawnMessagePrefix[PLATFORM_MAX_PATH]; + // Sounds SF2BossProfileSoundInfo LocalDeathCamSounds; SF2BossProfileSoundInfo ClientDeathCamSounds; @@ -1710,17 +2207,21 @@ enum struct SF2BossProfileData ArrayList CompanionsArray; char CompanionSpawnType[64]; + SF2BossProfileCopiesInfo CopiesInfo; + SF2BossProfileAttributesInfo AttributesInfo; SF2BossProfileMasterAnimationsData AnimationData; ArrayList EffectsArray; + SF2BossProfileEyeData EyeData; + void Init() { this.Models = null; this.Names = null; - this.Type = SF2BossType_Unknown; + this.Type = SF2BossType_Chaser; this.ModelScale = 1.0; this.SkinMax = 0; this.SkinDifficultiesOn = false; @@ -1780,6 +2281,8 @@ enum struct SF2BossProfileData this.DeathCamOverlayStartTime = 0.0; this.DeathCamTime = 0.0; + this.DeathCamData.Init(); + this.FOV = 90.0; this.TurnRate = 250.0; @@ -1808,10 +2311,9 @@ enum struct SF2BossProfileData this.StaticScareAmount = 0.0; - this.TeleportType = 0; + this.TeleportType = 2; this.TeleportIgnoreChases = false; this.TeleportIgnoreVis = false; - this.TeleportCopyDistance = 800.0; this.JumpscareOnScare = false; this.JumpscareNoSight = false; @@ -1837,11 +2339,10 @@ enum struct SF2BossProfileData this.ProxySpawnEffectZOffset = 0.0; this.ProxyDeathAnimations = null; this.ProxyZombies = false; + this.ProxyRobots = false; this.ProxyDifficultyModels = false; this.ProxyOverrideMaxSpeed = false; - this.FakeCopies = false; - this.DrainCredits = false; this.Healthbar = false; @@ -1875,20 +2376,26 @@ enum struct SF2BossProfileData this.OutroMusic = false; + this.IsPvEBoss = false; + this.PvESpawnMessagesArray = null; + this.PvESpawnMessagePrefix[0] = '\0'; + this.EngineSound[0] = '\0'; this.EngineSoundLevel = 83; this.EngineSoundVolume = 0.8; this.LocalDeathCamSounds.Init(); this.ClientDeathCamSounds.Init(); + this.ClientDeathCamSounds.Channel = SNDCHAN_STATIC; + this.ClientDeathCamSounds.Level = SNDLEVEL_NONE; this.GlobalDeathCamSounds.Init(); this.ScareSounds.Init(); - this.ScareSounds.Channel = MUSIC_CHAN; + this.ScareSounds.Channel = SNDCHAN_STATIC; this.ScareSounds.Level = SNDLEVEL_NONE; this.SightSounds.Init(); this.SightSounds.CooldownMin = 8.0; this.SightSounds.CooldownMax = 14.0; - this.SightSounds.Channel = MUSIC_CHAN; + this.SightSounds.Channel = SNDCHAN_STATIC; this.SightSounds.Level = SNDLEVEL_NONE; this.IntroSounds.Init(); this.IntroSounds.Channel = SNDCHAN_STATIC; @@ -1933,71 +2440,71 @@ enum struct SF2BossProfileData this.CompanionsArray = null; this.CompanionSpawnType = "on_spawn on_difficulty_change"; + this.CopiesInfo.Init(); this.AttributesInfo.Init(); this.AnimationData.Init(); // Save this here whenever I do statue boss animations this.EffectsArray = null; + this.EyeData.Init(); + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - this.Skin[difficulty] = difficulty > 0 ? this.Skin[difficulty - 1] : 0; - this.Body[difficulty] = difficulty > 0 ? this.Body[difficulty - 1] : 0; - - this.SoundMusicLoop[difficulty] = difficulty > 0 ? this.SoundMusicLoop[difficulty - 1] : 0.0; - - this.Copies[difficulty] = difficulty > 0 ? this.Copies[difficulty - 1] : 1; - - this.RunSpeed[difficulty] = difficulty > 0 ? this.RunSpeed[difficulty - 1] : 150.0; - this.MaxRunSpeed[difficulty] = difficulty > 0 ? this.MaxRunSpeed[difficulty - 1] : 150.0; - this.Acceleration[difficulty] = difficulty > 0 ? this.Acceleration[difficulty - 1] : 4000.0; - - this.IdleLifeTime[difficulty] = difficulty > 0 ? this.IdleLifeTime[difficulty - 1] : 10.0; - - this.SearchRange[difficulty] = difficulty > 0 ? this.SearchRange[difficulty - 1] : 1024.0; - this.SearchSoundRange[difficulty] = difficulty > 0 ? this.SearchSoundRange[difficulty - 1] : 1024.0; - this.TauntAlertRange[difficulty] = difficulty > 0 ? this.TauntAlertRange[difficulty - 1] : 512.0; - - this.InstantKillCooldown[difficulty] = difficulty > 0 ? this.InstantKillCooldown[difficulty - 1] : 0.0; - - this.StaticRadius[difficulty] = difficulty > 0 ? this.StaticRadius[difficulty - 1] : 0.0; - this.StaticRate[difficulty] = difficulty > 0 ? this.StaticRate[difficulty - 1] : 0.0; - this.StaticRateDecay[difficulty] = difficulty > 0 ? this.StaticRateDecay[difficulty - 1] : 0.0; - this.StaticGraceTime[difficulty] = difficulty > 0 ? this.StaticGraceTime[difficulty - 1] : 1.0; - - this.TeleportAllowed[difficulty] = difficulty > 0 ? this.TeleportAllowed[difficulty - 1] : true; - this.TeleportRangeMin[difficulty] = difficulty > 0 ? this.TeleportRangeMin[difficulty - 1] : 325.0; - this.TeleportRangeMax[difficulty] = difficulty > 0 ? this.TeleportRangeMax[difficulty - 1] : 1024.0; - this.TeleportTimeMin[difficulty] = difficulty > 0 ? this.TeleportTimeMin[difficulty - 1] : 5.0; - this.TeleportTimeMax[difficulty] = difficulty > 0 ? this.TeleportTimeMax[difficulty - 1] : 9.0; - this.TeleportRestPeriod[difficulty] = difficulty > 0 ? this.TeleportRestPeriod[difficulty - 1] : 15.0; - this.TeleportStressMin[difficulty] = difficulty > 0 ? this.TeleportStressMin[difficulty - 1] : 0.2; - this.TeleportStressMax[difficulty] = difficulty > 0 ? this.TeleportStressMax[difficulty - 1] : 0.9; - this.TeleportPersistencyPeriod[difficulty] = difficulty > 0 ? this.TeleportPersistencyPeriod[difficulty - 1] : 13.0; - - this.JumpscareDistance[difficulty] = difficulty > 0 ? this.JumpscareDistance[difficulty - 1] : 0.0; - this.JumpscareDuration[difficulty] = difficulty > 0 ? this.JumpscareDuration[difficulty - 1] : 0.0; - this.JumpscareCooldown[difficulty] = difficulty > 0 ? this.JumpscareCooldown[difficulty - 1] : 0.0; - - this.ProxyDamageVsEnemy[difficulty] = difficulty > 0 ? this.ProxyDamageVsEnemy[difficulty - 1] : 1.0; - this.ProxyDamageVsBackstab[difficulty] = difficulty > 0 ? this.ProxyDamageVsBackstab[difficulty - 1] : 0.25; - this.ProxyDamageVsSelf[difficulty] = difficulty > 0 ? this.ProxyDamageVsSelf[difficulty - 1] : 1.0; - this.ProxyControlGainHitEnemy[difficulty] = difficulty > 0 ? this.ProxyControlGainHitEnemy[difficulty - 1] : 0; - this.ProxyControlGainHitByEnemy[difficulty] = difficulty > 0 ? this.ProxyControlGainHitByEnemy[difficulty - 1] : 0; - this.ProxyControlDrainRate[difficulty] = difficulty > 0 ? this.ProxyControlDrainRate[difficulty - 1] : 0.0; - this.MaxProxies[difficulty] = difficulty > 0 ? this.MaxProxies[difficulty - 1] : 0; - this.ProxySpawnChanceMin[difficulty] = difficulty > 0 ? this.ProxySpawnChanceMin[difficulty - 1] : 0.0; - this.ProxySpawnChaceMax[difficulty] = difficulty > 0 ? this.ProxySpawnChaceMax[difficulty - 1] : 0.0; - this.ProxySpawnChanceThreshold[difficulty] = difficulty > 0 ? this.ProxySpawnChanceThreshold[difficulty - 1] : 0.0; - this.ProxySpawnNumMin[difficulty] = difficulty > 0 ? this.ProxySpawnNumMin[difficulty - 1] : 0; - this.ProxySpawnNumMax[difficulty] = difficulty > 0 ? this.ProxySpawnNumMax[difficulty - 1] : 0; - this.ProxySpawnCooldownMin[difficulty] = difficulty > 0 ? this.ProxySpawnCooldownMin[difficulty - 1] : 0.0; - this.ProxySpawnCooldownMax[difficulty] = difficulty > 0 ? this.ProxySpawnCooldownMax[difficulty - 1] : 0.0; - this.ProxyTeleportRangeMin[difficulty] = difficulty > 0 ? this.ProxyTeleportRangeMin[difficulty - 1] : 500.0; - this.ProxyTeleportRangeMax[difficulty] = difficulty > 0 ? this.ProxyTeleportRangeMax[difficulty - 1] : 3200.0; - this.ProxyMaxSpeed[difficulty] = difficulty > 0 ? this.ProxyMaxSpeed[difficulty - 1] : 230.0; - - this.DrainCreditAmount[difficulty] = difficulty > 0 ? this.DrainCreditAmount[difficulty - 1] : 50; + this.Skin[difficulty] = 0; + this.Body[difficulty] = 0; + + this.SoundMusicLoop[difficulty] = 0.0; + + this.RunSpeed[difficulty] = 150.0; + this.Acceleration[difficulty] = 6000.0; + + this.IdleLifeTime[difficulty] = 10.0; + + this.SearchRange[difficulty] = 1024.0; + this.SearchSoundRange[difficulty] = 1024.0; + this.TauntAlertRange[difficulty] = 512.0; + + this.InstantKillCooldown[difficulty] = 0.0; + + this.StaticRadius[difficulty] = 15.0; + this.StaticRate[difficulty] = 0.6; + this.StaticRateDecay[difficulty] = 0.2; + this.StaticGraceTime[difficulty] = 1.0; + + this.TeleportAllowed[difficulty] = true; + this.TeleportRangeMin[difficulty] = 450.0; + this.TeleportRangeMax[difficulty] = 1500.0; + this.TeleportTimeMin[difficulty] = 5.0; + this.TeleportTimeMax[difficulty] = 9.0; + this.TeleportRestPeriod[difficulty] = 15.0; + this.TeleportStressMin[difficulty] = 0.2; + this.TeleportStressMax[difficulty] = 1.0; + this.TeleportPersistencyPeriod[difficulty] = 13.0; + + this.JumpscareDistance[difficulty] = 0.0; + this.JumpscareDuration[difficulty] = 0.0; + this.JumpscareCooldown[difficulty] = 0.0; + + this.ProxyDamageVsEnemy[difficulty] = 1.0; + this.ProxyDamageVsBackstab[difficulty] = 0.25; + this.ProxyDamageVsSelf[difficulty] = 1.0; + this.ProxyControlGainHitEnemy[difficulty] = 0; + this.ProxyControlGainHitByEnemy[difficulty] = 0; + this.ProxyControlDrainRate[difficulty] = 0.0; + this.MaxProxies[difficulty] = 0; + this.ProxySpawnChanceMin[difficulty] = 0.0; + this.ProxySpawnChaceMax[difficulty] = 0.0; + this.ProxySpawnChanceThreshold[difficulty] = 0.0; + this.ProxySpawnNumMin[difficulty] = 0; + this.ProxySpawnNumMax[difficulty] = 0; + this.ProxySpawnCooldownMin[difficulty] = 0.0; + this.ProxySpawnCooldownMax[difficulty] = 0.0; + this.ProxyTeleportRangeMin[difficulty] = 500.0; + this.ProxyTeleportRangeMax[difficulty] = 3200.0; + this.ProxyMaxSpeed[difficulty] = 230.0; + + this.DrainCreditAmount[difficulty] = 50; } for (int i = 0; i < 3; i++) @@ -2019,10 +2526,12 @@ enum struct SF2BossProfileData this.EyePosOffset[i] = 0.0; this.EyeAngOffset[i] = 0.0; } + this.EyePosOffset[2] = 45.0; } void PostLoad() { + this.DeathCamData.PostLoad(); this.ClientDeathCamSounds.PostLoad(); this.GlobalDeathCamSounds.PostLoad(); this.LocalDeathCamSounds.PostLoad(); @@ -2055,6 +2564,16 @@ enum struct SF2BossProfileData this.CompanionsArray.SetArray(i, companions, sizeof(companions)); } } + if (this.EffectsArray != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.EffectsArray.Length; i++) + { + this.EffectsArray.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } if (this.EffectsArray != null && this.EffectsArray.Length <= 0) { delete this.EffectsArray; @@ -2098,10 +2617,31 @@ enum struct SF2BossProfileData { delete this.EventIndexes; } + if (this.ProxyWeaponClassNames != null && this.ProxyWeaponClassNames.Length <= 0) + { + delete this.ProxyWeaponClassNames; + } + if (this.ProxyWeaponStats != null && this.ProxyWeaponStats.Length <= 0) + { + delete this.ProxyWeaponStats; + } + if (this.ProxyDeathAnimations != null && this.ProxyDeathAnimations.Length <= 0) + { + delete this.ProxyDeathAnimations; + } + if (this.DeathMessagesArray != null && this.DeathMessagesArray.Length <= 0) + { + delete this.DeathMessagesArray; + } + if (this.PvESpawnMessagesArray != null && this.PvESpawnMessagesArray.Length <= 0) + { + delete this.PvESpawnMessagesArray; + } } void Destroy() { + this.DeathCamData.Destroy(); // Sounds this.LocalDeathCamSounds.Destroy(); this.ClientDeathCamSounds.Destroy(); @@ -2156,6 +2696,10 @@ enum struct SF2BossProfileData { delete this.DeathMessagesArray; } + if (this.PvESpawnMessagesArray != null) + { + delete this.PvESpawnMessagesArray; + } if (this.ProxyWeaponClassNames != null) { delete this.ProxyWeaponClassNames; @@ -2198,6 +2742,30 @@ enum struct SF2BossProfileData { delete this.EventIndexes; } + + for (int i = 0; i < 10; i++) + { + if (this.ProxyModels[i] != null) + { + delete this.ProxyModels[i]; + } + if (this.ProxyModelsHard[i] != null) + { + delete this.ProxyModelsHard[i]; + } + if (this.ProxyModelsInsane[i] != null) + { + delete this.ProxyModelsInsane[i]; + } + if (this.ProxyModelsNightmare[i] != null) + { + delete this.ProxyModelsNightmare[i]; + } + if (this.ProxyModelsApollyon[i] != null) + { + delete this.ProxyModelsApollyon[i]; + } + } } void SortSoundSections(KeyValues kv, const char[] sectionName, bool checkFile) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 4e9fc9af..3bc3f22b 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -1,4 +1,5 @@ ConVar g_DefaultBossVisibilityStateConVar; +#include enum { @@ -9,7 +10,7 @@ enum SF2BossAttackType_ExplosiveDance = 3, SF2BossAttackType_LaserBeam = 4, SF2BossAttackType_Custom = 5, - SF2BossAttackType_Unused + SF2BossAttackType_Tongue = 6 }; enum @@ -23,7 +24,6 @@ enum SF2BossProjectileType_Arrow = 5, SF2BossProjectileType_Mangler = 6, SF2BossProjectileType_Baseball = 7, - SF2BossProjectileType_Unused, SF2BossProjectileType_Custom }; @@ -31,31 +31,47 @@ enum { SF2BossTrapType_Invalid = -1, SF2BossTrapType_BearTrap = 0, - SF2BossTrapType_Unused, SF2BossTrapType_Custom }; +enum +{ + SF2DamageType_Invalid = -1, + SF2DamageType_Jarate = 0, + SF2DamageType_Milk, + SF2DamageType_Gas, + SF2DamageType_Mark, + SF2DamageType_Ignite, + SF2DamageType_Stun, + SF2DamageType_Bleed, + SF2DamageType_Smite, + SF2DamageType_Random +} + enum struct SF2BossProfileAttackGestureData // This one covers the gestures { ArrayList Names; float Playbackrate[Difficulty_Max]; float Cycle[Difficulty_Max]; + char SectionName[64]; - void Init() + void Init(char optionalSectionName[64] = "") { - this.Names = new ArrayList(ByteCountToCells(64)); - this.Names.Resize(Difficulty_Max); + this.Names = null; + this.SectionName = optionalSectionName; for (int i = 0; i < Difficulty_Max; i++) { - this.Playbackrate[i] = this.Playbackrate[i] = i > 0 ? this.Playbackrate[i - 1] : 1.0; - this.Cycle[i] = this.Cycle[i] = i > 0 ? this.Cycle[i - 1] : 0.0; + this.Playbackrate[i] = 1.0; + this.Cycle[i] = 0.0; } } void Load(KeyValues kv) { char animNames[Difficulty_Max][64]; + this.Names = new ArrayList(ByteCountToCells(64)); + this.Names.Resize(Difficulty_Max); GetProfileDifficultyStringValues(kv, "gesture_name", animNames, sizeof(animNames[]), animNames); GetProfileDifficultyFloatValues(kv, "gesture_playbackrate", this.Playbackrate, this.Playbackrate); @@ -86,214 +102,1800 @@ enum struct SF2BossProfileAttackGestureData // This one covers the gestures } } -enum struct SF2ChaserBossProfileAttackData +enum struct SF2ChaserBossProfileDamageEffectData // Can be any data { int Type; + bool Enabled[Difficulty_Max]; + float Duration[Difficulty_Max]; + SF2BossProfileSoundInfo Sounds; + ArrayList Particles; - bool CanUseAgainstProps; + bool MarkSilent[Difficulty_Max]; - float Damage[Difficulty_Max]; - float DamageVsProps; - float DamageForce[Difficulty_Max]; - int DamageType[Difficulty_Max]; - float DamageDelay[Difficulty_Max]; + float StunSlowdown[Difficulty_Max]; + ArrayList StunFlags; - float Range[Difficulty_Max]; - float Duration[Difficulty_Max]; - float Spread[Difficulty_Max]; - float BeginRange[Difficulty_Max]; - float BeginFOV[Difficulty_Max]; - float PunchVelocity[3]; + int SmiteSprite; + int SmiteSmokeSprite; + float SmiteDamage[Difficulty_Max]; + int SmiteDamageType[Difficulty_Max]; + int SmiteColor[4]; + bool SmiteMessage; + char SmiteHitSound[PLATFORM_MAX_PATH]; - float Cooldown[Difficulty_Max]; + ArrayList Types; - bool Disappear[Difficulty_Max]; - int Repeat; - int MaxRepeats; - ArrayList RepeatTimers; + void Init() + { + this.Type = SF2DamageType_Invalid; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.Duration[i] = 8.0; - bool IgnoreAlwaysLooking[Difficulty_Max]; + this.MarkSilent[i] = false; - int WeaponInt; - char WeaponString[PLATFORM_MAX_PATH]; + this.StunSlowdown[i] = 0.5; - bool LifeSteal[Difficulty_Max]; - float LifeStealDuration[Difficulty_Max]; + this.SmiteDamage[i] = 9001.0; + this.SmiteDamageType[i] = (1 << 20); + } + this.Sounds.Init(); + this.Particles = null; - float ProjectileDamage[Difficulty_Max]; - float ProjectileSpeed[Difficulty_Max]; - float ProjectileRadius[Difficulty_Max]; - float ProjectileDeviation[Difficulty_Max]; - int ProjectileCount[Difficulty_Max]; - bool CritProjectiles[Difficulty_Max]; - int ProjectileType; - float ProjectileOffset[3]; - char FireballTrail[PLATFORM_MAX_PATH]; - char RocketModel[PLATFORM_MAX_PATH]; + this.StunFlags = null; - float IceballSlowdownPercent[Difficulty_Max]; - float IceballSlowdownDuration[Difficulty_Max]; - char IceballTrail[PLATFORM_MAX_PATH]; + this.SmiteSprite = PrecacheModel("sprites/lgtning.vmt"); + this.SmiteSmokeSprite = PrecacheModel("sprites/steam1.vmt"); + for (int i = 0; i < 4; i++) + { + this.SmiteColor[i] = 255; + } + this.SmiteMessage = false; + this.SmiteHitSound = SOUND_THUNDER; - int BulletCount[Difficulty_Max]; - float BulletDamage[Difficulty_Max]; - float BulletSpread[Difficulty_Max]; - char BulletTrace[PLATFORM_MAX_PATH]; - float BulletOffset[3]; + this.Types = null; + } - float LaserDamage[Difficulty_Max]; - float LaserSize; - int LaserColor[3]; - bool LaserAttachment; - float LaserDuration[Difficulty_Max]; - float LaserNoise; - float LaserOffset[3]; - char LaserAttachmentName[PLATFORM_MAX_PATH]; + bool SetType(const char[] section) + { + if (strcmp(section, "ignite") == 0) + { + this.Type = SF2DamageType_Ignite; + } + else if (strcmp(section, "gas") == 0) + { + this.Type = SF2DamageType_Gas; + } + else if (strcmp(section, "bleed") == 0) + { + this.Type = SF2DamageType_Bleed; + } + else if (strcmp(section, "mark") == 0) + { + this.Type = SF2DamageType_Mark; + } + else if (strcmp(section, "jarate") == 0) + { + this.Type = SF2DamageType_Jarate; + } + else if (strcmp(section, "milk") == 0) + { + this.Type = SF2DamageType_Milk; + } + else if (strcmp(section, "stun") == 0) + { + this.Type = SF2DamageType_Stun; + } + else if (strcmp(section, "smite") == 0) + { + this.Type = SF2DamageType_Smite; + } + else if (strcmp(section, "random") == 0) + { + this.Types = new ArrayList(); + this.Type = SF2DamageType_Random; + } - bool PullIn; + if (this.Type == SF2DamageType_Invalid) + { + return false; + } - bool AttackWhileRunning[Difficulty_Max]; - float RunSpeed[Difficulty_Max]; - float RunDuration[Difficulty_Max]; - float RunDelay[Difficulty_Max]; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } - int UseOnDifficulty; - int BlockOnDifficulty; - float UseOnHealth; - float BlockOnHealth; + return true; + } - int ExplosiveDanceRadius[Difficulty_Max]; + void Load(KeyValues kv, bool checkFile) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); - bool Gestures; + GetProfileDifficultyBoolValues(kv, "silent", this.MarkSilent, this.MarkSilent); - bool DeathCamLowHealth; + char flags[Difficulty_Max][256]; + GetProfileDifficultyFloatValues(kv, "slow_multiplier", this.StunSlowdown, this.StunSlowdown); + this.StunFlags = new ArrayList(ByteCountToCells(256)); + for (int i = 0; i < Difficulty_Max; i++) + { + flags[i] = "slow"; + } + GetProfileDifficultyStringValues(kv, "flags", flags, sizeof(flags[]), flags); + for (int i = 0; i < Difficulty_Max; i++) + { + this.StunFlags.PushString(flags[i]); + } - bool DontInterruptChaseInitial[Difficulty_Max]; + GetProfileDifficultyFloatValues(kv, "damage", this.SmiteDamage, this.SmiteDamage); + GetProfileDifficultyNumValues(kv, "damagetype", this.SmiteDamageType, this.SmiteDamageType); - bool CancelLos[Difficulty_Max]; - float CancelDistance[Difficulty_Max]; + GetProfileColorNoBacks(kv, "color", this.SmiteColor[0], this.SmiteColor[1], this.SmiteColor[2], this.SmiteColor[3], + this.SmiteColor[0], this.SmiteColor[1], this.SmiteColor[2], this.SmiteColor[3]); + this.SmiteMessage = kv.GetNum("message", this.SmiteMessage) != 0; + kv.GetString("hit_sound", this.SmiteHitSound, sizeof(this.SmiteHitSound), this.SmiteHitSound); - void Init() - { - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + if (this.Types != null) { - this.Damage[difficulty] = 0.0; - this.DamageForce[difficulty] = 0.0; - this.DamageType[difficulty] = 0; - this.DamageDelay[difficulty] = 0.0; + char type[256]; + kv.GetString("random_types", type, sizeof(type)); + char types[32][32]; + int nums = ExplodeString(type, " ", types, sizeof(types), sizeof(types[])); + for (int i = 0; i < nums; i++) + { + if (strcmp(types[i], "ignite") == 0) + { + this.Types.Push(SF2DamageType_Ignite); + } + else if (strcmp(types[i], "gas") == 0) + { + this.Types.Push(SF2DamageType_Gas); + } + else if (strcmp(types[i], "bleed") == 0) + { + this.Types.Push(SF2DamageType_Bleed); + } + else if (strcmp(types[i], "mark") == 0) + { + this.Types.Push(SF2DamageType_Mark); + } + else if (strcmp(types[i], "jarate") == 0) + { + this.Types.Push(SF2DamageType_Jarate); + } + else if (strcmp(types[i], "milk") == 0) + { + this.Types.Push(SF2DamageType_Milk); + } + else if (strcmp(types[i], "stun") == 0) + { + this.Types.Push(SF2DamageType_Stun); + } + else if (strcmp(types[i], "smite") == 0) + { + this.Types.Push(SF2DamageType_Smite); + } + } + } - this.Range[difficulty] = 0.0; - this.Duration[difficulty] = 0.0; - this.Spread[difficulty] = 45.0; - this.BeginRange[difficulty] = this.Range[difficulty]; - this.BeginFOV[difficulty] = this.Spread[difficulty]; + if (kv.JumpToKey("sounds")) + { + this.Sounds.Load(kv, checkFile); + kv.GoBack(); + } - this.Cooldown[difficulty] = 0.0; + if (kv.JumpToKey("particles")) + { + this.Particles = new ArrayList(sizeof(SF2ParticleData)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2ParticleData particle; + particle.Init(); + particle.Load(kv); + this.Particles.PushArray(particle, sizeof(particle)); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + } - this.Disappear[difficulty] = false; + void PostLoad() + { + this.Sounds.PostLoad(); + } - this.IgnoreAlwaysLooking[difficulty] = false; + int GetStunType(int difficulty) + { + char flag[256]; + this.StunFlags.GetString(difficulty, flag, sizeof(flag)); + char flags[32][64]; + int nums = ExplodeString(flag, " ", flags, sizeof(flags), sizeof(flags[])); + int value = 0; - this.LifeSteal[difficulty] = false; - this.LifeStealDuration[difficulty] = 0.0; + for (int i = 0; i < nums; i++) + { + if (strcmp(flags[i], "slow") == 0) + { + value |= (1 << 0); + } + else if (strcmp(flags[i], "stuck") == 0) + { + value |= (1 << 1); + } + else if (strcmp(flags[i], "cheer_fx") == 0) + { + value |= (1 << 3); + } + else if (strcmp(flags[i], "no_fx") == 0) + { + value |= (1 << 5); + } + else if (strcmp(flags[i], "thirdperson") == 0) + { + value |= (1 << 6); + } + else if (strcmp(flags[i], "ghost_fx") == 0) + { + value |= (1 << 7); + } + else if (strcmp(flags[i], "loser") == 0) + { + value |= (1 << 0) | (1 << 5) | (1 << 6); + } + else if (strcmp(flags[i], "boo") == 0) + { + value |= (1 << 7) | (1 << 6); + } + } - this.ProjectileDamage[difficulty] = 20.0; - this.ProjectileSpeed[difficulty] = 1100.0; - this.ProjectileRadius[difficulty] = 128.0; - this.ProjectileDeviation[difficulty] = 0.0; - this.ProjectileCount[difficulty] = 1; - this.CritProjectiles[difficulty] = false; + return value; + } - this.IceballSlowdownPercent[difficulty] = 0.55; - this.IceballSlowdownDuration[difficulty] = 2.0; + void Destroy() + { + if (this.Particles != null) + { + delete this.Particles; + } + if (this.StunFlags != null) + { + delete this.StunFlags; + } + if (this.Types != null) + { + delete this.Types; + } + this.Sounds.Destroy(); + } - this.BulletCount[difficulty] = 4; - this.BulletDamage[difficulty] = 8.0; - this.BulletSpread[difficulty] = 0.1; + void Apply(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = SF2_ChaserBossEntity(-1)) + { + if (!this.Enabled[difficulty]) + { + return; + } + this.Sounds.EmitSound(_, chaser.index); + if (this.Particles != null) + { + SF2ParticleData particle; + this.Particles.GetArray(GetRandomInt(0, this.Particles.Length - 1), particle, sizeof(particle)); + particle.Apply(player, chaser); + } + int type = this.Type; + if (type == SF2DamageType_Random) + { + type = this.Types.Get(GetRandomInt(0, this.Types.Length - 1)); + } + switch (type) + { + case SF2DamageType_Ignite: + { + TF2_IgnitePlayer(player.index, player.index, this.Duration[difficulty]); + } + case SF2DamageType_Gas: + { + TF2_AddCondition(player.index, TFCond_Gas, this.Duration[difficulty], player.index); + } + case SF2DamageType_Mark: + { + TF2_AddCondition(player.index, this.MarkSilent[difficulty] ? TFCond_MarkedForDeathSilent : TFCond_MarkedForDeath, this.Duration[difficulty], player.index); + } + case SF2DamageType_Bleed: + { + TF2_MakeBleed(player.index, player.index, this.Duration[difficulty]); + } + case SF2DamageType_Milk: + { + TF2_AddCondition(player.index, TFCond_Milked, this.Duration[difficulty], player.index); + } + case SF2DamageType_Jarate: + { + TF2_AddCondition(player.index, TFCond_Jarated, this.Duration[difficulty], player.index); + } + case SF2DamageType_Stun: + { + TF2_StunPlayer(player.index, this.Duration[difficulty], this.StunSlowdown[difficulty], this.GetStunType(difficulty)); + } + case SF2DamageType_Smite: + { + float targetPos[3]; + player.GetAbsOrigin(targetPos); + targetPos[2] -= 26.0; - this.LaserDamage[difficulty] = 25.0; - this.LaserDuration[difficulty] = this.Duration[difficulty]; + int randomX = GetRandomInt(-500, 500); + int randomY = GetRandomInt(-500, 500); - this.AttackWhileRunning[difficulty] = false; - this.RunDuration[difficulty] = 0.0; - this.RunDelay[difficulty] = 0.0; - this.RunSpeed[difficulty] = 0.0; + float startPos[3]; + startPos[0] = targetPos[0] + randomX; + startPos[1] = targetPos[1] + randomY; + startPos[2] = targetPos[2] + 800.0; - this.ExplosiveDanceRadius[difficulty] = 350; + float origin[3]; + TE_SetupBeamPoints(startPos, targetPos, this.SmiteSprite, 0, 0, 0, 0.2, 20.0, 10.0, 0, 1.0, this.SmiteColor, 3); + TE_SendToAll(); - this.DontInterruptChaseInitial[difficulty] = false; + TE_SetupSparks(targetPos, origin, 5000, 1000); + TE_SendToAll(); - this.CancelLos[difficulty] = false; - this.CancelDistance[difficulty] = -1.0; - } - for (int i = 0; i < 3; i++) - { - this.PunchVelocity[i] = 0.0; + TE_SetupEnergySplash(targetPos, origin, false); + TE_SendToAll(); - this.ProjectileOffset[i] = 0.0; + TE_SetupSmoke(targetPos, this.SmiteSmokeSprite, 5.0, 10); + TE_SendToAll(); - this.BulletOffset[i] = 0.0; + EmitAmbientSound(this.SmiteHitSound, startPos, player.index, SNDLEVEL_SCREAMING); + SDKHooks_TakeDamage(player.index, chaser.index, chaser.index, this.SmiteDamage[difficulty], this.SmiteDamageType[difficulty]); - this.LaserColor[i] = 0; - this.LaserOffset[i] = 0.0; + int controller = chaser.Controller; + if (this.SmiteMessage && controller > -1 && GetClientTeam(player.index) == 2) + { + char playerName[32], bossName[SF2_MAX_NAME_LENGTH]; + GetClientName(player.index, playerName, sizeof(playerName)); + chaser.GetName(bossName, sizeof(bossName)); + CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Smote target", bossName, playerName); + } + } } - this.Type = SF2BossAttackType_Melee; + } +} - this.CanUseAgainstProps = false; +enum struct SF2ChaserBossProfileShockwaveData +{ + bool Enabled; + float Height[Difficulty_Max]; + float Radius[Difficulty_Max]; + float Force[Difficulty_Max]; + float BatteryDrainPercent[Difficulty_Max]; + int StaminaDrainPercent[Difficulty_Max]; - this.DamageVsProps = this.Damage[1]; + ArrayList Effects; + ArrayList DamageEffects; + + void Init() + { + this.Enabled = false; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Height[i] = 80.0; + this.Radius[i] = 200.0; + this.Force[i] = 600.0; + this.BatteryDrainPercent[i] = 0.0; + this.StaminaDrainPercent[i] = 0; + } + + this.Effects = null; + this.DamageEffects = null; + } + + void Load(KeyValues kv, bool checkFile) + { + this.Enabled = true; + GetProfileDifficultyFloatValues(kv, "height", this.Height, this.Height); + GetProfileDifficultyFloatValues(kv, "radius", this.Radius, this.Radius); + GetProfileDifficultyFloatValues(kv, "force", this.Force, this.Force); + GetProfileDifficultyFloatValues(kv, "battery_drain", this.BatteryDrainPercent, this.BatteryDrainPercent); + GetProfileDifficultyNumValues(kv, "stamina_drain", this.StaminaDrainPercent, this.StaminaDrainPercent); + + if (kv.JumpToKey("effects")) + { + this.Effects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.Effects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("apply_conditions")) + { + this.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + SF2ChaserBossProfileDamageEffectData damageData; + damageData.Init(); + kv.GetSectionName(section, sizeof(section)); + if (!damageData.SetType(section)) + { + continue; + } + damageData.Load(kv, checkFile); + this.DamageEffects.PushArray(damageData, sizeof(damageData)); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + } + + void PostLoad() + { + if (this.Effects != null && this.Effects.Length <= 0) + { + delete this.Effects; + } + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.PostLoad(); + } + } + + if (this.Effects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.Effects.Length; i++) + { + this.Effects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + } + + void Destroy() + { + if (this.Effects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.Effects.Length; i++) + { + this.Effects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.Effects; + } + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.Destroy(); + } + delete this.DamageEffects; + } + } + + void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = SF2_ChaserBossEntity(-1)) + { + if (this.DamageEffects == null) + { + return; + } + + if (!player.IsValid()) + { + return; + } + + for (int i = 0; i < this.DamageEffects.Length; i++) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i2 = 0; i2 < this.DamageEffects.Length; i2++) + { + this.DamageEffects.GetArray(i2, damageData, sizeof(damageData)); + damageData.Apply(player, difficulty, chaser); + } + } + } +} + +enum struct SF2ChaserBossProfileAttackData +{ + int Index; + char Name[64]; + + int Type; + + bool CanUseAgainstProps; + + float Damage[Difficulty_Max]; + float DamageVsProps; + float DamageForce[Difficulty_Max]; + int DamageType[Difficulty_Max]; + float DamageDelay[Difficulty_Max]; + + float Range[Difficulty_Max]; + float Duration[Difficulty_Max]; + float Spread[Difficulty_Max]; + float BeginRange[Difficulty_Max]; + float BeginFOV[Difficulty_Max]; + float PunchVelocity[3]; + + float Cooldown[Difficulty_Max]; + + bool Disappear[Difficulty_Max]; + int Repeat; + int MaxRepeats; + ArrayList RepeatTimers; + + bool IgnoreAlwaysLooking[Difficulty_Max]; + + int WeaponInt; + char WeaponString[PLATFORM_MAX_PATH]; + + bool LifeSteal[Difficulty_Max]; + float LifeStealDuration[Difficulty_Max]; + + float ProjectileDamage[Difficulty_Max]; + float ProjectileSpeed[Difficulty_Max]; + float ProjectileRadius[Difficulty_Max]; + float ProjectileDeviation[Difficulty_Max]; + int ProjectileCount[Difficulty_Max]; + bool CritProjectiles[Difficulty_Max]; + int ProjectileType; + float ProjectileOffset[3]; + char FireballTrail[PLATFORM_MAX_PATH]; + char RocketModel[PLATFORM_MAX_PATH]; + + float IceballSlowdownPercent[Difficulty_Max]; + float IceballSlowdownDuration[Difficulty_Max]; + char IceballTrail[PLATFORM_MAX_PATH]; + + int BulletCount[Difficulty_Max]; + float BulletDamage[Difficulty_Max]; + float BulletSpread[Difficulty_Max]; + char BulletTrace[PLATFORM_MAX_PATH]; + float BulletOffset[3]; + + float LaserDamage[Difficulty_Max]; + float LaserSize; + int LaserColor[3]; + bool LaserAttachment; + float LaserDuration[Difficulty_Max]; + float LaserNoise; + float LaserOffset[3]; + char LaserAttachmentName[PLATFORM_MAX_PATH]; + + bool PullIn; + + bool AttackWhileRunning[Difficulty_Max]; + float RunSpeed[Difficulty_Max]; + float RunDuration[Difficulty_Max]; + float RunDelay[Difficulty_Max]; + float RunAcceleration[Difficulty_Max]; + bool RunGroundSpeed[Difficulty_Max]; + + int UseOnDifficulty; + int BlockOnDifficulty; + float UseOnHealth; + float BlockOnHealth; + + float ExplosiveDanceRadius[Difficulty_Max]; + + bool Gestures; + + bool DeathCamLowHealth; + + bool DontInterruptChaseInitial[Difficulty_Max]; + + bool CancelLos[Difficulty_Max]; + float CancelDistance[Difficulty_Max]; + float MinCancelDistance[Difficulty_Max]; + + int EventNumber; + + char SubType[64]; + + SF2ChaserBossProfileShockwaveData Shockwave; + + ArrayList StartEffects; + ArrayList HitEffects; + ArrayList MissEffects; + ArrayList DamageEffects; + + bool ImmuneToDamage[Difficulty_Max]; + + ArrayList PostureWhitelist; + + SF2BossProfileSoundInfo TongueLaunchSound; + SF2BossProfileSoundInfo TongueHitSound; + SF2BossProfileSoundInfo TongueTiedSound; + float TongueSpeed[Difficulty_Max]; + float TonguePullScale[Difficulty_Max]; + bool TongueCanEscape[Difficulty_Max]; + char TongueMaterial[PLATFORM_MAX_PATH]; + char TongueAttachment[PLATFORM_MAX_PATH]; + float TongueOffset[3]; + + SF2BossProfileMasterAnimationsData Animations; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Damage[difficulty] = 50.0; + this.DamageForce[difficulty] = 0.0; + this.DamageType[difficulty] = 128; + this.DamageDelay[difficulty] = 0.0; + + this.Range[difficulty] = 180.0; + this.Duration[difficulty] = 0.0; + this.Spread[difficulty] = 45.0; + this.BeginRange[difficulty] = 80.0; + this.BeginFOV[difficulty] = this.Spread[difficulty]; + + this.Cooldown[difficulty] = 0.0; + + this.Disappear[difficulty] = false; + + this.IgnoreAlwaysLooking[difficulty] = false; + + this.LifeSteal[difficulty] = false; + this.LifeStealDuration[difficulty] = 0.0; + + this.ProjectileDamage[difficulty] = 20.0; + this.ProjectileSpeed[difficulty] = 1100.0; + this.ProjectileRadius[difficulty] = 128.0; + this.ProjectileDeviation[difficulty] = 0.0; + this.ProjectileCount[difficulty] = 1; + this.CritProjectiles[difficulty] = false; + + this.IceballSlowdownPercent[difficulty] = 0.55; + this.IceballSlowdownDuration[difficulty] = 2.0; + + this.BulletCount[difficulty] = 4; + this.BulletDamage[difficulty] = 8.0; + this.BulletSpread[difficulty] = 0.1; + + this.LaserDamage[difficulty] = 25.0; + this.LaserDuration[difficulty] = this.Duration[difficulty]; + + this.AttackWhileRunning[difficulty] = false; + this.RunDuration[difficulty] = 0.0; + this.RunDelay[difficulty] = 0.0; + this.RunSpeed[difficulty] = 0.0; + this.RunAcceleration[difficulty] = 4000.0; + this.RunGroundSpeed[difficulty] = false; + + this.ExplosiveDanceRadius[difficulty] = 350.0; + + this.DontInterruptChaseInitial[difficulty] = false; + + this.CancelLos[difficulty] = false; + this.CancelDistance[difficulty] = -1.0; + this.MinCancelDistance[difficulty] = -1.0; + + this.ImmuneToDamage[difficulty] = false; + + this.TongueSpeed[difficulty] = 900.0; + this.TonguePullScale[difficulty] = 6.0; + this.TongueCanEscape[difficulty] = true; + } + for (int i = 0; i < 3; i++) + { + this.PunchVelocity[i] = 0.0; + + this.ProjectileOffset[i] = 0.0; + + this.BulletOffset[i] = 0.0; + + this.LaserColor[i] = 0; + this.LaserOffset[i] = 0.0; + + this.TongueOffset[i] = 0.0; + } + this.Index = -1; + this.Name[0] = '\0'; + + this.Type = SF2BossAttackType_Melee; + + this.CanUseAgainstProps = false; + + this.DamageVsProps = this.Damage[1]; + + this.Repeat = 0; + this.MaxRepeats = 0; + this.RepeatTimers = null; + + this.WeaponInt = 0; + this.WeaponString[0] = '\0'; + + this.ProjectileType = 0; + this.FireballTrail = FIREBALL_TRAIL; + this.IceballTrail = ICEBALL_TRAIL; + this.RocketModel = ROCKET_MODEL; + + this.BulletTrace = "bullet_tracer02_blue"; + + this.LaserSize = 12.0; + this.LaserAttachment = false; + this.LaserNoise = 1.0; + this.LaserAttachmentName[0] = '\0'; + + this.PullIn = false; + + this.UseOnDifficulty = 0; + this.BlockOnDifficulty = 6; + this.UseOnHealth = -1.0; + this.BlockOnHealth = -1.0; + + this.Gestures = false; + + this.DeathCamLowHealth = false; + + this.EventNumber = -1; + + this.SubType[0] = '\0'; + + this.Shockwave.Init(); + + this.StartEffects = null; + this.HitEffects = null; + this.MissEffects = null; + this.DamageEffects = null; + + this.PostureWhitelist = null; + + this.TongueLaunchSound.Init(); + this.TongueHitSound.Init(); + this.TongueTiedSound.Init(); + this.TongueMaterial[0] = '\0'; + this.TongueAttachment[0] = '\0'; + } + + void PostLoad() + { + this.Shockwave.PostLoad(); + if (this.StartEffects != null && this.StartEffects.Length <= 0) + { + delete this.StartEffects; + } + + if (this.HitEffects != null && this.HitEffects.Length <= 0) + { + delete this.HitEffects; + } + + if (this.MissEffects != null && this.MissEffects.Length <= 0) + { + delete this.MissEffects; + } + + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.PostLoad(); + } + } + + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.HitEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.HitEffects.Length; i++) + { + this.HitEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.MissEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.MissEffects.Length; i++) + { + this.MissEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.PostureWhitelist != null && this.PostureWhitelist.Length == 0) + { + delete this.PostureWhitelist; + } + + this.TongueLaunchSound.PostLoad(); + this.TongueHitSound.PostLoad(); + this.TongueTiedSound.PostLoad(); + } + + void Destroy() + { + if (this.RepeatTimers != null) + { + delete this.RepeatTimers; + } + + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.StartEffects; + } + + if (this.HitEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.HitEffects.Length; i++) + { + this.HitEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.HitEffects; + } + + if (this.MissEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.MissEffects.Length; i++) + { + this.MissEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.MissEffects; + } + + if (this.DamageEffects != null) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i = 0; i < this.DamageEffects.Length; i++) + { + this.DamageEffects.GetArray(i, damageData, sizeof(damageData)); + damageData.Destroy(); + } + delete this.DamageEffects; + } + this.Shockwave.Destroy(); + + if (this.PostureWhitelist != null) + { + delete this.PostureWhitelist; + } + + this.TongueLaunchSound.Destroy(); + this.TongueHitSound.Destroy(); + this.TongueTiedSound.Destroy(); + + this.Animations.Destroy(); + } + + bool IsSubType(const char[] subType) + { + return strcmp(subType, this.SubType) == 0; + } + + void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = SF2_ChaserBossEntity(-1)) + { + if (this.DamageEffects == null) + { + return; + } + + if (!player.IsValid()) + { + return; + } + + for (int i = 0; i < this.DamageEffects.Length; i++) + { + SF2ChaserBossProfileDamageEffectData damageData; + for (int i2 = 0; i2 < this.DamageEffects.Length; i2++) + { + this.DamageEffects.GetArray(i2, damageData, sizeof(damageData)); + damageData.Apply(player, difficulty, chaser); + } + } + } + + bool CanBeUsedWithPosture(const char[] posture) + { + if (this.PostureWhitelist == null) + { + return true; + } + + return this.PostureWhitelist.FindString(posture) != -1; + } +} + +enum struct SF2ChaserBossProfileSoundSenseData +{ + int AddCount[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.AddCount[difficulty] = 2; + this.Cooldown[difficulty] = 0.0; + } + } +} + +enum struct SF2ChaserBossProfileSmellData +{ + bool Enabled[Difficulty_Max]; + float CooldownMin[Difficulty_Max]; + float CooldownMax[Difficulty_Max]; + int PlayerCount[Difficulty_Max]; + float PlayerRange[Difficulty_Max]; + float SmellRange[Difficulty_Max]; + bool ShouldChase[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.CooldownMin[difficulty] = 6.0; + this.CooldownMax[difficulty] = 12.0; + this.PlayerCount[difficulty] = 1; + this.PlayerRange[difficulty] = 1000.0; + this.SmellRange[difficulty] = 1500.0; + this.ShouldChase[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "cooldown_min", this.CooldownMin, this.CooldownMin); + GetProfileDifficultyFloatValues(kv, "cooldown_max", this.CooldownMax, this.CooldownMax); + GetProfileDifficultyNumValues(kv, "required_players", this.PlayerCount, this.PlayerCount); + GetProfileDifficultyFloatValues(kv, "required_player_range", this.PlayerRange, this.PlayerRange); + GetProfileDifficultyFloatValues(kv, "smelling_range", this.SmellRange, this.SmellRange); + GetProfileDifficultyBoolValues(kv, "should_chase_upon_smelled", this.ShouldChase, this.ShouldChase); + } +} + +enum struct SF2ChaserBossProfileDeathData +{ + bool Enabled[Difficulty_Max]; + float Health[Difficulty_Max]; + bool RemoveOnDeath; + bool DisappearOnDeath; + bool RagdollOnDeath; + + bool KeyDrop; + char KeyModel[PLATFORM_MAX_PATH]; + char KeyTrigger[PLATFORM_MAX_PATH]; + + bool ItemDrop[Difficulty_Max]; + int ItemDropType[Difficulty_Max]; + + float AddHealthPerPlayer[Difficulty_Max]; + float AddHealthPerScout[Difficulty_Max]; + float AddHealthPerSoldier[Difficulty_Max]; + float AddHealthPerPyro[Difficulty_Max]; + float AddHealthPerDemoman[Difficulty_Max]; + float AddHealthPerHeavy[Difficulty_Max]; + float AddHealthPerEngineer[Difficulty_Max]; + float AddHealthPerMedic[Difficulty_Max]; + float AddHealthPerSniper[Difficulty_Max]; + float AddHealthPerSpy[Difficulty_Max]; + float AddHealthPerDeath[Difficulty_Max]; + + void Init() + { + this.RemoveOnDeath = false; + this.DisappearOnDeath = false; + this.RagdollOnDeath = false; + + this.KeyDrop = false; + this.KeyModel = SF_KEYMODEL; + this.KeyTrigger[0] = '\0'; + + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.Health[difficulty] = 400.0; + + this.ItemDrop[difficulty] = false; + this.ItemDropType[difficulty] = 1; + + this.AddHealthPerPlayer[difficulty] = 0.0; + this.AddHealthPerScout[difficulty] = 0.0; + this.AddHealthPerSoldier[difficulty] = 0.0; + this.AddHealthPerPyro[difficulty] = 0.0; + this.AddHealthPerDemoman[difficulty] = 0.0; + this.AddHealthPerHeavy[difficulty] = 0.0; + this.AddHealthPerEngineer[difficulty] = 0.0; + this.AddHealthPerMedic[difficulty] = 0.0; + this.AddHealthPerSniper[difficulty] = 0.0; + this.AddHealthPerSpy[difficulty] = 0.0; + this.AddHealthPerDeath[difficulty] = 0.0; + } + } + + void Load(KeyValues kv, bool checkFile) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "health", this.Health, this.Health); + this.RemoveOnDeath = kv.GetNum("remove", this.RemoveOnDeath) != 0; + this.DisappearOnDeath = kv.GetNum("disappear", this.DisappearOnDeath) != 0; + this.RagdollOnDeath = kv.GetNum("become_ragdoll", this.RagdollOnDeath) != 0; + + this.KeyDrop = kv.GetNum("key_drop", this.KeyDrop) != 0; + kv.GetString("key_model", this.KeyModel, sizeof(this.KeyModel), this.KeyModel); + PrecacheModel2(this.KeyModel, _, _, checkFile); + kv.GetString("key_trigger", this.KeyTrigger, sizeof(this.KeyTrigger), this.KeyTrigger); + + GetProfileDifficultyBoolValues(kv, "item_drop", this.ItemDrop, this.ItemDrop); + GetProfileDifficultyNumValues(kv, "item_drop_type", this.ItemDropType, this.ItemDropType); + + if (kv.JumpToKey("add_health")) + { + GetProfileDifficultyFloatValues(kv, "player", this.AddHealthPerPlayer, this.AddHealthPerPlayer); + GetProfileDifficultyFloatValues(kv, "scout", this.AddHealthPerScout, this.AddHealthPerScout); + GetProfileDifficultyFloatValues(kv, "soldier", this.AddHealthPerSoldier, this.AddHealthPerSoldier); + GetProfileDifficultyFloatValues(kv, "pyro", this.AddHealthPerPyro, this.AddHealthPerPyro); + GetProfileDifficultyFloatValues(kv, "demoman", this.AddHealthPerDemoman, this.AddHealthPerDemoman); + GetProfileDifficultyFloatValues(kv, "heavy", this.AddHealthPerHeavy, this.AddHealthPerHeavy); + GetProfileDifficultyFloatValues(kv, "engineer", this.AddHealthPerEngineer, this.AddHealthPerEngineer); + GetProfileDifficultyFloatValues(kv, "medic", this.AddHealthPerMedic, this.AddHealthPerMedic); + GetProfileDifficultyFloatValues(kv, "sniper", this.AddHealthPerSniper, this.AddHealthPerSniper); + GetProfileDifficultyFloatValues(kv, "spy", this.AddHealthPerSpy, this.AddHealthPerSpy); + GetProfileDifficultyFloatValues(kv, "on_death", this.AddHealthPerDeath, this.AddHealthPerDeath); + kv.GoBack(); + } + } +} + +enum struct SF2ChaserBossProfileChaseOnLookData +{ + bool Enabled[Difficulty_Max]; + bool AddTargets[Difficulty_Max]; + float RequiredLookPosition[3]; + float MinimumXAngle[Difficulty_Max]; + float MaximumXAngle[Difficulty_Max]; + float MinimumYAngle[Difficulty_Max]; + float MaximumYAngle[Difficulty_Max]; + float RequiredYAngle[Difficulty_Max]; + float RequiredFOV[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.AddTargets[difficulty] = true; + this.MinimumXAngle[difficulty] = 90.0; + this.MaximumXAngle[difficulty] = 180.0; + this.MinimumYAngle[difficulty] = 0.0; + this.MaximumYAngle[difficulty] = 90.0; + this.RequiredFOV[difficulty] = -1.0; + } + this.RequiredLookPosition[2] = 35.0; + for (int i = 0; i < 2; i++) + { + this.RequiredLookPosition[i] = 0.0; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyBoolValues(kv, "add_targets", this.AddTargets, this.AddTargets); + kv.GetVector("look_position", this.RequiredLookPosition, this.RequiredLookPosition); + GetProfileDifficultyFloatValues(kv, "minimum_x_angle", this.MinimumXAngle, this.MinimumXAngle); + GetProfileDifficultyFloatValues(kv, "maximum_x_angle", this.MaximumXAngle, this.MaximumXAngle); + GetProfileDifficultyFloatValues(kv, "minimum_y_angle", this.MinimumYAngle, this.MinimumYAngle); + GetProfileDifficultyFloatValues(kv, "maximum_y_angle", this.MaximumYAngle, this.MaximumYAngle); + GetProfileDifficultyFloatValues(kv, "required_fov", this.RequiredFOV, this.RequiredFOV); + } +} + +enum struct SF2PostureConditionWithinBoundsInfo +{ + bool Enabled; + float Mins[3]; + float Maxs[3]; + + void Init() + { + this.Enabled = false; + for (int i = 0; i < 3; i++) + { + this.Mins[0] = 0.0; + this.Maxs[0] = 0.0; + } + } + + void Load(KeyValues kv) + { + this.Enabled = true; + kv.GetVector("mins", this.Mins, this.Mins); + kv.GetVector("maxs", this.Maxs, this.Maxs); + } +} + +enum struct SF2PostureConditionRunAwayInfo +{ + bool Enabled; + + void Init() + { + this.Enabled = false; + } + + void Load() + { + this.Enabled = true; + } +} + +enum struct SF2PostureConditionRagePhase +{ + bool Enabled; + ArrayList Names; + + void Init() + { + this.Enabled = false; + this.Names = null; + } + + void Load(KeyValues kv) + { + this.Enabled = true; + this.Names = new ArrayList(ByteCountToCells(64)); + char value[1024]; + kv.GetString("phases", value, sizeof(value), value); + char phase[64][64]; + int maxLength = ExplodeString(value, " ", phase, sizeof(phase), sizeof(phase)); + for (int i = 0; i < maxLength; i++) + { + this.Names.PushString(phase[i]); + } + } + + void Destroy() + { + if (this.Names != null) + { + delete this.Names; + } + } +} + +enum struct SF2PostureConditionWithinRangeInfo +{ + bool Enabled[Difficulty_Max]; + float MinRange[Difficulty_Max]; + float MaxRange[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + float CurrentCooldown; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.MinRange[i] = 0.0; + this.MaxRange[i] = 512.0; + this.Cooldown[i] = 1.0; + } + this.CurrentCooldown = 0.0; + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "min_range", this.MinRange, this.MinRange); + GetProfileDifficultyFloatValues(kv, "max_range", this.MaxRange, this.MaxRange); + GetProfileDifficultyFloatValues(kv, "cooldown", this.Cooldown, this.Cooldown); + } +} + +enum struct SF2PostureConditionLookAtInfo +{ + bool Enabled[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + } + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + } +} + +enum struct SF2PostureConditionAlertInfo +{ + bool Enabled[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + } + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + } +} + +enum struct SF2PostureConditionCloakInfo +{ + bool Enabled[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + } + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + } +} + +enum struct SF2ChaserBossProfilePostureInfo +{ + char Name[64]; + float Speed[Difficulty_Max]; + float WalkSpeed[Difficulty_Max]; + float Acceleration[Difficulty_Max]; + SF2BossProfileMasterAnimationsData Animations; + SF2PostureConditionWithinBoundsInfo BoundsInfo; + SF2PostureConditionRunAwayInfo RunAwayInfo; + SF2PostureConditionRagePhase RagePhaseInfo; + SF2PostureConditionWithinRangeInfo RangeInfo; + SF2PostureConditionLookAtInfo LookAtInfo; + SF2PostureConditionAlertInfo AlertInfo; + SF2PostureConditionCloakInfo CloakInfo; + + void Init() + { + this.Name[0] = '\0'; + for (int i = 0; i < Difficulty_Max; i++) + { + this.Speed[i] = 300.0; + this.WalkSpeed[i] = 100.0; + this.Acceleration[i] = 4000.0; + } + this.Animations.Init(); + this.BoundsInfo.Init(); + this.RunAwayInfo.Init(); + this.RagePhaseInfo.Init(); + this.RangeInfo.Init(); + this.LookAtInfo.Init(); + this.AlertInfo.Init(); + this.CloakInfo.Init(); + } + + void Load(KeyValues kv) + { + kv.GetSectionName(this.Name, sizeof(this.Name)); + GetProfileDifficultyFloatValues(kv, "speed", this.Speed, this.Speed); + GetProfileDifficultyFloatValues(kv, "walkspeed", this.WalkSpeed, this.WalkSpeed); + GetProfileDifficultyFloatValues(kv, "acceleration", this.Acceleration, this.Acceleration); + this.Animations.Load(kv); + if (kv.JumpToKey("conditions")) + { + if (kv.JumpToKey("within_bounds")) + { + this.BoundsInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("running_away")) + { + this.RunAwayInfo.Load(); + kv.GoBack(); + } + + if (kv.JumpToKey("in_phases")) + { + this.RagePhaseInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("within_range")) + { + this.RangeInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("on_look")) + { + this.LookAtInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("on_alert")) + { + this.AlertInfo.Load(kv); + kv.GoBack(); + } + + if (kv.JumpToKey("on_cloak")) + { + this.CloakInfo.Load(kv); + kv.GoBack(); + } + kv.GoBack(); + } + } + + void Destroy() + { + this.Animations.Destroy(); + this.RagePhaseInfo.Destroy(); + } +} + +enum struct SF2ChaserBossProfileAlertOnAlertInfo +{ + bool Enabled[Difficulty_Max]; + bool OnChangeState[Difficulty_Max]; + float Radius[Difficulty_Max]; + bool ShouldBeVisible[Difficulty_Max]; + bool Follow[Difficulty_Max]; + float FollowCooldown[Difficulty_Max]; + bool Patrol[Difficulty_Max]; + float PatrolDurationMin[Difficulty_Max]; + float PatrolDurationMax[Difficulty_Max]; + float PatrolRadiusMin[Difficulty_Max]; + float PatrolRadiusMax[Difficulty_Max]; + bool Copies[Difficulty_Max]; + bool Companions[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.OnChangeState[difficulty] = true; + this.Radius[difficulty] = 1024.0; + this.ShouldBeVisible[difficulty] = false; + this.Follow[difficulty] = false; + this.FollowCooldown[difficulty] = 10.0; + this.Patrol[difficulty] = false; + this.PatrolDurationMin[difficulty] = 10.0; + this.PatrolDurationMax[difficulty] = 15.0; + this.PatrolRadiusMin[difficulty] = 750.0; + this.PatrolRadiusMax[difficulty] = 1500.0; + this.Copies[difficulty] = false; + this.Companions[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + if (kv.JumpToKey("sync")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyBoolValues(kv, "on_change_state", this.OnChangeState, this.OnChangeState); + GetProfileDifficultyFloatValues(kv, "radius", this.Radius, this.Radius); + GetProfileDifficultyBoolValues(kv, "should_be_visible", this.ShouldBeVisible, this.ShouldBeVisible); + GetProfileDifficultyBoolValues(kv, "follow_leader", this.Follow, this.Follow); + GetProfileDifficultyFloatValues(kv, "follow_cooldown", this.FollowCooldown, this.FollowCooldown); + GetProfileDifficultyBoolValues(kv, "copies", this.Copies, this.Copies); + GetProfileDifficultyBoolValues(kv, "companions", this.Companions, this.Companions); + + if (kv.JumpToKey("patrol")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Patrol, this.Patrol); + GetProfileDifficultyFloatValues(kv, "duration_min", this.PatrolDurationMin, this.PatrolDurationMin); + GetProfileDifficultyFloatValues(kv, "duration_max", this.PatrolDurationMax, this.PatrolDurationMax); + GetProfileDifficultyFloatValues(kv, "radius_min", this.PatrolRadiusMin, this.PatrolRadiusMin); + GetProfileDifficultyFloatValues(kv, "radius_max", this.PatrolRadiusMax, this.PatrolRadiusMax); + + kv.GoBack(); + } + + kv.GoBack(); + } + } +} + +enum struct SF2ChaserBossProfileAlertOnChaseInfo +{ + bool Enabled[Difficulty_Max]; + bool OnChangeState[Difficulty_Max]; + float Radius[Difficulty_Max]; + bool ShouldBeVisible[Difficulty_Max]; + bool Follow[Difficulty_Max]; + float FollowCooldown[Difficulty_Max]; + bool Copies[Difficulty_Max]; + bool Companions[Difficulty_Max]; + + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.OnChangeState[difficulty] = true; + this.Radius[difficulty] = 1024.0; + this.ShouldBeVisible[difficulty] = false; + this.Follow[difficulty] = false; + this.FollowCooldown[difficulty] = 10.0; + this.Copies[difficulty] = false; + this.Companions[difficulty] = false; + } + } + + void Load(KeyValues kv) + { + if (kv.JumpToKey("chase_together")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyBoolValues(kv, "on_change_state", this.OnChangeState, this.OnChangeState); + GetProfileDifficultyFloatValues(kv, "radius", this.Radius, this.Radius); + GetProfileDifficultyBoolValues(kv, "should_be_visible", this.ShouldBeVisible, this.ShouldBeVisible); + GetProfileDifficultyBoolValues(kv, "follow_leader", this.Follow, this.Follow); + GetProfileDifficultyFloatValues(kv, "follow_cooldown", this.FollowCooldown, this.FollowCooldown); + GetProfileDifficultyBoolValues(kv, "copies", this.Copies, this.Copies); + GetProfileDifficultyBoolValues(kv, "companions", this.Companions, this.Companions); + + kv.GoBack(); + } + } +} + +enum struct SF2ChaserRageInfo +{ + char Name[64]; + float PercentageThreshold; + + bool IncreaseDifficulty; + + bool Invincible; + + SF2BossProfileSoundInfo StartSounds; + SF2BossProfileSoundInfo HealSounds; + + bool IsHealing; + bool HealCloak; + float FleeRange[2]; + float HealAmount; + float HealDelay; + float HealDuration; + + SF2BossProfileMasterAnimationsData Animations; + + void Init() + { + this.Name[0] = '\0'; + this.PercentageThreshold = 0.75; + this.IncreaseDifficulty = true; + this.Invincible = false; + + this.StartSounds.Init(); + this.HealSounds.Init(); + + this.IsHealing = false; + this.HealCloak = false; + this.FleeRange[0] = 512.0; + this.FleeRange[1] = 1024.0; + this.HealAmount = 0.5; + this.HealDelay = 0.0; + this.HealDuration = 1.0; + + this.Animations.Init(); + } + + void Load(KeyValues kv, bool checkFile) + { + kv.GetSectionName(this.Name, sizeof(this.Name)); + this.PercentageThreshold = kv.GetFloat("health_percent", this.PercentageThreshold); + this.IncreaseDifficulty = kv.GetNum("increase_difficulty", this.IncreaseDifficulty) != 0; + this.Invincible = kv.GetNum("invincible", this.Invincible) != 0; + + if (kv.JumpToKey("heal")) + { + this.IsHealing = true; + this.HealCloak = kv.GetNum("cloak", this.HealCloak) != 0; + this.FleeRange[0] = kv.GetFloat("flee_range_min", this.FleeRange[0]); + this.FleeRange[1] = kv.GetFloat("flee_range_max", this.FleeRange[1]); + this.HealAmount = kv.GetFloat("amount", this.HealAmount); + this.HealDelay = kv.GetFloat("delay", this.HealDelay); + this.HealDuration = kv.GetFloat("duration", this.HealDuration); + kv.GoBack(); + } + + if (kv.JumpToKey("sounds")) + { + if (kv.JumpToKey("start")) + { + this.StartSounds.Load(kv, checkFile); + kv.GoBack(); + } + + if (kv.JumpToKey("healing")) + { + this.HealSounds.Load(kv, checkFile); + kv.GoBack(); + } + + kv.GoBack(); + } + + this.Animations.Load(kv); + } + + void PostLoad() + { + this.StartSounds.PostLoad(); + this.HealSounds.PostLoad(); + } + + void Destroy() + { + this.StartSounds.Destroy(); + this.HealSounds.Destroy(); + this.Animations.Destroy(); + } +} + +enum struct SF2ChaserBossProfileCloakData +{ + bool Enabled[Difficulty_Max]; + float Duration[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + float CloakRange[Difficulty_Max]; + float DecloakRange[Difficulty_Max]; + int CloakRenderColor[4]; + int CloakRenderMode; + ArrayList CloakEffects; + ArrayList DecloakEffects; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.Cooldown[i] = 8.0; + this.Duration[i] = 10.0; + this.CloakRange[i] = 350.0; + this.DecloakRange[i] = 150.0; + } + + this.CloakRenderColor[0] = 0; + this.CloakRenderColor[1] = 0; + this.CloakRenderColor[2] = 0; + this.CloakRenderColor[3] = 0; + this.CloakRenderMode = 1; + this.CloakEffects = null; + this.DecloakEffects = null; + } + + void Load(KeyValues kv, bool checkFile) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); + GetProfileDifficultyFloatValues(kv, "cooldown", this.Cooldown, this.Cooldown); + GetProfileDifficultyFloatValues(kv, "cloak_range", this.CloakRange, this.CloakRange); + GetProfileDifficultyFloatValues(kv, "decloak_range", this.DecloakRange, this.DecloakRange); + + GetProfileColorNoBacks(kv, "color", this.CloakRenderColor[0], this.CloakRenderColor[1], this.CloakRenderColor[2], this.CloakRenderColor[3], + this.CloakRenderColor[0], this.CloakRenderColor[1], this.CloakRenderColor[2], this.CloakRenderColor[3]); + this.CloakRenderMode = kv.GetNum("rendermode", this.CloakRenderMode); + + if (kv.JumpToKey("effects")) + { + if (kv.JumpToKey("cloak")) + { + this.CloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.CloakEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + + kv.GoBack(); + } + + if (kv.JumpToKey("decloak")) + { + this.DecloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); - this.Repeat = 0; - this.MaxRepeats = 0; - this.RepeatTimers = null; + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.DecloakEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } - this.WeaponInt = 0; - this.WeaponString[0] = '\0'; + kv.GoBack(); + } - this.ProjectileType = 0; - this.FireballTrail = FIREBALL_TRAIL; - this.IceballTrail = ICEBALL_TRAIL; - this.RocketModel = ROCKET_MODEL; + kv.GoBack(); + } + } - this.BulletTrace = "bullet_tracer02_blue"; + void PostLoad() + { + if (this.CloakEffects != null && this.CloakEffects.Length <= 0) + { + delete this.CloakEffects; + } - this.LaserSize = 12.0; - this.LaserAttachment = false; - this.LaserNoise = 1.0; - this.LaserAttachmentName[0] = '\0'; + if (this.DecloakEffects != null && this.DecloakEffects.Length <= 0) + { + delete this.DecloakEffects; + } - this.PullIn = false; + if (this.CloakEffects != null) + { + SF2BossProfileBaseEffectInfo effects; - this.UseOnDifficulty = 0; - this.BlockOnDifficulty = 6; - this.UseOnHealth = -1.0; - this.BlockOnHealth = -1.0; + for (int i = 0; i < this.CloakEffects.Length; i++) + { + this.CloakEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } - this.Gestures = false; + if (this.DecloakEffects != null) + { + SF2BossProfileBaseEffectInfo effects; - this.DeathCamLowHealth = false; + for (int i = 0; i < this.DecloakEffects.Length; i++) + { + this.DecloakEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } } void Destroy() { - if (this.RepeatTimers != null) + if (this.CloakEffects != null) { - delete this.RepeatTimers; + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.CloakEffects.Length; i++) + { + this.CloakEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.CloakEffects; + } + + if (this.DecloakEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.DecloakEffects.Length; i++) + { + this.DecloakEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.DecloakEffects; } } } -enum struct SF2ChaserBossProfileSoundSenseData +enum struct SF2ChaserBossProfileIdleData { - int AddCount[Difficulty_Max]; - float Cooldown[Difficulty_Max]; + bool TurnEnabled[Difficulty_Max]; + float TurnAngle[Difficulty_Max]; + float TurnMinTime[Difficulty_Max]; + float TurnMaxTime[Difficulty_Max]; void Init() { - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + for (int i = 0; i < Difficulty_Max; i++) { - this.AddCount[difficulty] = this.AddCount[difficulty] = difficulty > 0 ? this.AddCount[difficulty - 1] : 2; - this.Cooldown[difficulty] = this.Cooldown[difficulty] = difficulty > 0 ? this.Cooldown[difficulty - 1] : 0.0; + this.TurnEnabled[i] = true; + this.TurnAngle[i] = 360.0; + this.TurnMinTime[i] = 1.5; + this.TurnMaxTime[i] = 3.0; + } + } + + void Load(KeyValues kv) + { + if (kv.JumpToKey("turn")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.TurnEnabled, this.TurnEnabled); + GetProfileDifficultyFloatValues(kv, "angle", this.TurnAngle, this.TurnAngle); + GetProfileDifficultyFloatValues(kv, "cooldown_min", this.TurnMinTime, this.TurnMinTime); + GetProfileDifficultyFloatValues(kv, "cooldown_max", this.TurnMaxTime, this.TurnMaxTime); + kv.GoBack(); } } } @@ -301,7 +1903,6 @@ enum struct SF2ChaserBossProfileSoundSenseData enum struct SF2ChaserBossProfileData { float WalkSpeed[Difficulty_Max]; - float MaxWalkSpeed[Difficulty_Max]; float WakeRadius; @@ -311,19 +1912,13 @@ enum struct SF2ChaserBossProfileData float AlertDuration[Difficulty_Max]; float AlertGracetime[Difficulty_Max]; + bool AlertRunOnHearSound[Difficulty_Max]; + bool AlertRunOnWander[Difficulty_Max]; + float ChaseDuration[Difficulty_Max]; - float ChaseDurationAddVisibleMin; - float ChaseDurationAddVisibleMax; - - float ChasePersistencyTimeInit; - float ChaseAttackPersistencyTimeInit; - float ChaseAttackPersistencyTimeAdd; - float ChaseNewTargetPersistencyTimeInit; - float ChaseNewTargetPersistencyTimeAdd; - float ChasePersistencyAddVisibleMin; - float ChasePersistencyAddVisibleMax; - float ChaseStunPersistencyTimeInit; - float ChaseStunPersistencyTimeAdd; + float ChaseDurationAddMaxRange[Difficulty_Max]; + float ChaseDurationAddVisibleMin[Difficulty_Max]; + float ChaseDurationAddVisibleMax[Difficulty_Max]; int SoundCountToAlert[Difficulty_Max]; float SoundPosDiscardTime[Difficulty_Max]; @@ -335,6 +1930,9 @@ enum struct SF2ChaserBossProfileData SF2ChaserBossProfileSoundSenseData WeaponSenses; SF2ChaserBossProfileSoundSenseData FlashlightSenses; + SF2ChaserBossProfileSmellData SmellData; + + bool CanWander[Difficulty_Max]; float WanderRangeMin[Difficulty_Max]; float WanderRangeMax[Difficulty_Max]; float WanderTimeMin[Difficulty_Max]; @@ -343,33 +1941,34 @@ enum struct SF2ChaserBossProfileData float WanderEnterTimeMax[Difficulty_Max]; bool StunEnabled; - float StunCooldown; - float StunHealth; + float StunCooldown[Difficulty_Max]; + float StunHealth[Difficulty_Max]; float StunHealthPerPlayer; float StunHealthPerClass[10]; - bool FlashlightStun; - float FlashlightDamage; + bool FlashlightStun[Difficulty_Max]; + float FlashlightDamage[Difficulty_Max]; bool DisappearOnStun; - bool ItemDropOnStun; - int StunItemDropType; + bool ItemDropOnStun[Difficulty_Max]; + int StunItemDropType[Difficulty_Max]; bool ChaseInitialOnStun; ArrayList DamageResistances; + SF2ChaserBossProfileDeathData DeathData; + bool KeyDrop; char KeyModel[PLATFORM_MAX_PATH]; char KeyTrigger[PLATFORM_MAX_PATH]; - float AwarenessIncreaseRate[Difficulty_Max]; - float AwarenessDecreaseRate[Difficulty_Max]; - - bool AutoChaseEnabled; + bool AutoChaseEnabled[Difficulty_Max]; int AutoChaseCount[Difficulty_Max]; int AutoChaseAdd[Difficulty_Max]; int AutoChaseAddFootstep[Difficulty_Max]; int AutoChaseAddLoudFootstep[Difficulty_Max]; + int AutoChaseAddQuietFootstep[Difficulty_Max]; int AutoChaseAddVoice[Difficulty_Max]; int AutoChaseAddWeapon[Difficulty_Max]; - bool AutoChaseSprinters; + bool AutoChaseSprinters[Difficulty_Max]; + float AutoChaseAfterChaseCooldown[Difficulty_Max]; bool ChasesEndlessly; @@ -380,19 +1979,6 @@ enum struct SF2ChaserBossProfileData float EarthquakeFootstepAmplitude; float EarthquakeFootstepFrequency; - char CloakOnSound[PLATFORM_MAX_PATH]; - char CloakOffSound[PLATFORM_MAX_PATH]; - bool CanCloak; - bool CloakOnFlee; - float CloakCooldown[Difficulty_Max]; - float CloakRange[Difficulty_Max]; - float DecloakRange[Difficulty_Max]; - float CloakDuration[Difficulty_Max]; - float CloakSpeedMultiplier[Difficulty_Max]; - char CloakParticle[PLATFORM_MAX_PATH]; - int CloakRenderColor[4]; - int CloakRenderMode; - bool ProjectilesEnabled; float ProjectileCooldownMin[Difficulty_Max]; float ProjectileCooldownMax[Difficulty_Max]; @@ -434,119 +2020,14 @@ enum struct SF2ChaserBossProfileData float IceballSlowDuration[Difficulty_Max]; float IceballSlowPercent[Difficulty_Max]; - bool AdvancedDamageEffects; - bool RandomAdvancedDamageEffects; - bool AdvancedDamageEffectParticles; - - int RandomEffectIndexes; - char RandomEffectIndexesString[88]; - float RandomEffectDuration[Difficulty_Max]; - float RandomEffectSlowdown[Difficulty_Max]; - int RandomEffectStunType; - - bool JarateEffects; - int JarateIndexes; - char JarateIndexesString[88]; - float JarateDuration[Difficulty_Max]; - bool JarateBeamParticle; - char JarateParticle[PLATFORM_MAX_PATH]; - char JarateHitSound[PLATFORM_MAX_PATH]; - - bool MilkEffects; - int MilkIndexes; - char MilkIndexesString[88]; - float MilkDuration[Difficulty_Max]; - bool MilkBeamParaticle; - char MilkParticle[PLATFORM_MAX_PATH]; - char MilkHitSound[PLATFORM_MAX_PATH]; - - bool GasEffects; - int GasIndexes; - char GasIndexesString[88]; - float GasDuration[Difficulty_Max]; - bool GasBeamParticle; - char GasParticle[PLATFORM_MAX_PATH]; - char GasHitSound[PLATFORM_MAX_PATH]; - - bool MarkEffects; - int MarkIndexes; - char MarkIndexesString[88]; - float MarkDuration[Difficulty_Max]; - - bool SilentMarkEffects; - int SilentMarkIndexes; - char SilentMarkIndexesString[88]; - float SilentMarkDuration[Difficulty_Max]; - - bool IgniteEffects; - int IgniteIndexes; - char IgniteIndexesString[88]; - float IgniteDuration[Difficulty_Max]; - float IgniteDelay[Difficulty_Max]; - - bool StunEffects; - int StunEffectIndexes; - char StunEffectIndexesString[88]; - float StunEffectDuration[Difficulty_Max]; - float StunEffectSlowdown[Difficulty_Max]; - int StunEffectType; - bool StunEffectBeamParticle; - char StunParticle[PLATFORM_MAX_PATH]; - char StunHitSound[PLATFORM_MAX_PATH]; - - bool BleedEffects; - int BleedIndexes; - char BleedIndexesString[88]; - float BleedDuration[Difficulty_Max]; - - bool ElectricEffects; - int ElectricIndexes; - char ElectricIndexesString[88]; - float ElectricDuration[Difficulty_Max]; - float ElectricSlowdown[Difficulty_Max]; - int ElectricType; - bool ElectricBeamParticle; - char ElectricParticleRed[PLATFORM_MAX_PATH]; - char ElectricParticleBlue[PLATFORM_MAX_PATH]; - - bool SmiteEffects; - int SmiteIndexes; - char SmiteIndexesString[88]; - float SmiteDamage; - int SmiteDamageType; - int SmiteColor[4]; - bool SmiteMessage; - char SmiteHitSound[PLATFORM_MAX_PATH]; - bool XenobladeCombo; float XenobladeDuration; float XenobladeToppleDuration; float XenobladeToppleSlowdown; float XenobladeDazeDuration; - bool Shockwaves; - float ShockwaveHeight[Difficulty_Max]; - float ShockwaveRange[Difficulty_Max]; - float ShockwaveDrain[Difficulty_Max]; - float ShockwaveForce[Difficulty_Max]; - bool ShockwaveStun; - float ShockwaveStunDuration[Difficulty_Max]; - float ShockwaveStunSlowdown[Difficulty_Max]; - int ShockwaveIndexes; - char ShockwaveIndexesString[88]; - float ShockwaveWidths[2]; - float ShockwaveAmplitude; - int ShockwaveColor1[3]; - int ShockwaveColor2[3]; - int ShockwaveAlpha1; - int ShockwaveAlpha2; - char ShockwaveBeamSprite[PLATFORM_MAX_PATH]; - char ShockwaveHaloSprite[PLATFORM_MAX_PATH]; - int ShockwaveBeamModel; - int ShockwaveHaloModel; - - bool Traps; - int TrapType; + bool Traps[Difficulty_Max]; + int TrapType[Difficulty_Max]; float TrapCooldown[Difficulty_Max]; char TrapModel[PLATFORM_MAX_PATH]; char TrapDeploySound[PLATFORM_MAX_PATH]; @@ -556,30 +2037,14 @@ enum struct SF2ChaserBossProfileData char TrapAnimClose[PLATFORM_MAX_PATH]; char TrapAnimOpen[PLATFORM_MAX_PATH]; - bool DamageParticles; - char DamageParticleName[PLATFORM_MAX_PATH]; - bool DamageParticleBeam; - char DamageParticleSound[PLATFORM_MAX_PATH]; - - bool SelfHeal; - float SelfHealPercentageStart; - float SelfHealRecover[3]; - bool CloakToHeal; + ArrayList Rages; bool BoxingBoss; bool NormalSoundHook; - bool ChaseInitialAnimations; bool OldAnimationAI; - bool AlertWalkingAnimation; - bool SpawnAnimationsEnabled; - bool Crawling; - float CrawlSpeedMultiplier[Difficulty_Max]; - float CrawlDetectMins[3]; - float CrawlDetectMaxs[3]; - - bool ChaseOnLook; + SF2ChaserBossProfileChaseOnLookData ChaseOnLookData; bool UnnerfedVisibility; @@ -603,107 +2068,97 @@ enum struct SF2ChaserBossProfileData SF2BossProfileSoundInfo AttackKilledSounds; SF2BossProfileSoundInfo AttackKilledAllSounds; SF2BossProfileSoundInfo AttackKilledClientSounds; - SF2BossProfileSoundInfo RageAllSounds; - SF2BossProfileSoundInfo RageTwoSounds; - SF2BossProfileSoundInfo RageThreeSounds; - SF2BossProfileSoundInfo SelfHealSounds; SF2BossProfileSoundInfo StunnedSounds; + SF2BossProfileSoundInfo DeathSounds; + SF2BossProfileSoundInfo TauntKillSounds; + SF2BossProfileSoundInfo SmellSounds; SF2BossProfileSoundInfo FootstepSounds; ArrayList AttackSounds; ArrayList HitSounds; ArrayList MissSounds; ArrayList BulletShootSounds; ArrayList ProjectileShootSounds; + ArrayList AttackBeginSounds; + ArrayList AttackEndSounds; + ArrayList AttackLoopSounds; SF2BossProfileSoundInfo ChaseMusics; SF2BossProfileSoundInfo ChaseVisibleMusics; SF2BossProfileSoundInfo AlertMusics; SF2BossProfileSoundInfo IdleMusics; - void Init() - { - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - this.WalkSpeed[difficulty] = this.WalkSpeed[difficulty] = difficulty > 0 ? this.WalkSpeed[difficulty - 1] : 30.0; - this.MaxWalkSpeed[difficulty] = this.MaxWalkSpeed[difficulty] = difficulty > 0 ? this.MaxWalkSpeed[difficulty - 1] : 30.0; - - this.AlertDuration[difficulty] = this.AlertDuration[difficulty] = difficulty > 0 ? this.AlertDuration[difficulty - 1] : 5.0; - this.AlertGracetime[difficulty] = this.AlertGracetime[difficulty] = difficulty > 0 ? this.AlertGracetime[difficulty - 1] : 0.5; - this.ChaseDuration[difficulty] = this.ChaseDuration[difficulty] = difficulty > 0 ? this.ChaseDuration[difficulty - 1] : 10.0; + StringMap Postures; - this.SoundCountToAlert[difficulty] = this.SoundCountToAlert[difficulty] = difficulty > 0 ? this.SoundCountToAlert[difficulty - 1] : 8; - this.SoundPosDiscardTime[difficulty] = this.SoundPosDiscardTime[difficulty] = difficulty > 0 ? this.SoundPosDiscardTime[difficulty - 1] : 2.0; - this.SoundPosDistanceTolerance[difficulty] = this.SoundPosDistanceTolerance[difficulty] = difficulty > 0 ? this.SoundPosDistanceTolerance[difficulty - 1] : 512.0; + SF2ChaserBossProfileAlertOnAlertInfo AlertOnAlertInfo; + SF2ChaserBossProfileAlertOnChaseInfo AlertOnChaseInfo; - this.WanderRangeMin[difficulty] = this.WanderRangeMin[difficulty] = difficulty > 0 ? this.WanderRangeMin[difficulty - 1] : 400.0; - this.WanderRangeMax[difficulty] = this.WanderRangeMax[difficulty] = difficulty > 0 ? this.WanderRangeMax[difficulty - 1] : 1024.0; - this.WanderTimeMin[difficulty] = this.WanderTimeMin[difficulty] = difficulty > 0 ? this.WanderTimeMin[difficulty - 1] : 8.0; - this.WanderTimeMax[difficulty] = this.WanderTimeMax[difficulty] = difficulty > 0 ? this.WanderTimeMax[difficulty - 1] : 12.0; - this.WanderEnterTimeMin[difficulty] = this.WanderEnterTimeMin[difficulty] = difficulty > 0 ? this.WanderEnterTimeMin[difficulty - 1] : 2.0; - this.WanderEnterTimeMax[difficulty] = this.WanderEnterTimeMax[difficulty] = difficulty > 0 ? this.WanderEnterTimeMax[difficulty - 1] : 4.5; + SF2ChaserBossProfileCloakData CloakData; - this.AwarenessIncreaseRate[difficulty] = this.AwarenessIncreaseRate[difficulty] = difficulty > 0 ? this.AwarenessIncreaseRate[difficulty - 1] : 75.0; - this.AwarenessDecreaseRate[difficulty] = this.AwarenessDecreaseRate[difficulty] = difficulty > 0 ? this.AwarenessDecreaseRate[difficulty - 1] : 150.0; + SF2ChaserBossProfileIdleData IdleData; - this.AutoChaseCount[difficulty] = this.AutoChaseCount[difficulty] = difficulty > 0 ? this.AutoChaseCount[difficulty - 1] : 30; - this.AutoChaseAdd[difficulty] = this.AutoChaseAdd[difficulty] = difficulty > 0 ? this.AutoChaseAdd[difficulty - 1] : 0; - this.AutoChaseAddFootstep[difficulty] = this.AutoChaseAddFootstep[difficulty] = difficulty > 0 ? this.AutoChaseAddFootstep[difficulty - 1] : 2; + void Init() + { + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.WalkSpeed[difficulty] = 90.0; + + this.AlertDuration[difficulty] = 5.0; + this.AlertGracetime[difficulty] = 0.5; + this.AlertRunOnHearSound[difficulty] = false; + this.AlertRunOnWander[difficulty] = false; + + this.ChaseDuration[difficulty] = 10.0; + this.ChaseDurationAddMaxRange[difficulty] = 1024.0; + this.ChaseDurationAddVisibleMin[difficulty] = 0.01; + this.ChaseDurationAddVisibleMax[difficulty] = 0.05; + + this.SoundCountToAlert[difficulty] = 8; + this.SoundPosDiscardTime[difficulty] = 2.0; + this.SoundPosDistanceTolerance[difficulty] = 512.0; + + this.CanWander[difficulty] = true; + this.WanderRangeMin[difficulty] = 400.0; + this.WanderRangeMax[difficulty] = 1024.0; + this.WanderTimeMin[difficulty] = 8.0; + this.WanderTimeMax[difficulty] = 12.0; + this.WanderEnterTimeMin[difficulty] = 2.0; + this.WanderEnterTimeMax[difficulty] = 4.5; + + this.StunCooldown[difficulty] = 3.5; + this.StunHealth[difficulty] = 0.0; + this.FlashlightStun[difficulty] = false; + this.FlashlightDamage[difficulty] = 0.0; + this.ItemDropOnStun[difficulty] = false; + this.StunItemDropType[difficulty] = 1; + + this.AutoChaseEnabled[difficulty] = false; + this.AutoChaseCount[difficulty] = 100; + this.AutoChaseAdd[difficulty] = 0; + this.AutoChaseAddFootstep[difficulty] = 2; this.AutoChaseAddLoudFootstep[difficulty] = this.AutoChaseAddFootstep[difficulty]; - this.AutoChaseAddVoice[difficulty] = this.AutoChaseAddVoice[difficulty] = difficulty > 0 ? this.AutoChaseAddVoice[difficulty - 1] : 8; - this.AutoChaseAddWeapon[difficulty] = this.AutoChaseAddWeapon[difficulty] = difficulty > 0 ? this.AutoChaseAddWeapon[difficulty - 1] : 4; - - this.CloakCooldown[difficulty] = this.CloakCooldown[difficulty] = difficulty > 0 ? this.CloakCooldown[difficulty - 1] : 8.0; - this.CloakRange[difficulty] = this.CloakRange[difficulty] = difficulty > 0 ? this.CloakRange[difficulty - 1] : 350.0; - this.DecloakRange[difficulty] = this.DecloakRange[difficulty] = difficulty > 0 ? this.DecloakRange[difficulty - 1] : 150.0; - this.CloakDuration[difficulty] = this.CloakDuration[difficulty] = difficulty > 0 ? this.CloakDuration[difficulty - 1] : 10.0; - this.CloakSpeedMultiplier[difficulty] = this.CloakSpeedMultiplier[difficulty] = difficulty > 0 ? this.CloakSpeedMultiplier[difficulty - 1] : 1.0; - - this.ProjectileCooldownMin[difficulty] = this.ProjectileCooldownMin[difficulty] = difficulty > 0 ? this.ProjectileCooldownMin[difficulty - 1] : 1.0; - this.ProjectileCooldownMax[difficulty] = this.ProjectileCooldownMax[difficulty] = difficulty > 0 ? this.ProjectileCooldownMax[difficulty - 1] : 2.0; - this.ProjectileSpeed[difficulty] = this.ProjectileSpeed[difficulty] = difficulty > 0 ? this.ProjectileSpeed[difficulty - 1] : 400.0; - this.ProjectileDamage[difficulty] = this.ProjectileDamage[difficulty] = difficulty > 0 ? this.ProjectileDamage[difficulty - 1] : 50.0; - this.ProjectileRadius[difficulty] = this.ProjectileRadius[difficulty] = difficulty > 0 ? this.ProjectileRadius[difficulty - 1] : 128.0; - this.ProjectileDeviation[difficulty] = this.ProjectileDeviation[difficulty] = difficulty > 0 ? this.ProjectileDeviation[difficulty - 1] : 0.0; - this.ProjectileClipSize[difficulty] = this.ProjectileClipSize[difficulty] = difficulty > 0 ? this.ProjectileClipSize[difficulty - 1] : 3; - this.ProjectileReloadTime[difficulty] = this.ProjectileReloadTime[difficulty] = difficulty > 0 ? this.ProjectileReloadTime[difficulty - 1] : 2.0; - this.ProjectileChargeUp[difficulty] = this.ProjectileChargeUp[difficulty] = difficulty > 0 ? this.ProjectileChargeUp[difficulty - 1] : 5.0; - this.ProjectileCount[difficulty] = this.ProjectileCount[difficulty] = difficulty > 0 ? this.ProjectileCount[difficulty - 1] : 1; - - this.IceballSlowDuration[difficulty] = this.IceballSlowDuration[difficulty] = difficulty > 0 ? this.IceballSlowDuration[difficulty - 1] : 2.0; - this.IceballSlowPercent[difficulty] = this.IceballSlowPercent[difficulty] = difficulty > 0 ? this.IceballSlowPercent[difficulty - 1] : 0.55; - - this.RandomEffectDuration[difficulty] = this.RandomEffectDuration[difficulty] = difficulty > 0 ? this.RandomEffectDuration[difficulty - 1] : 0.0; - this.RandomEffectSlowdown[difficulty] = this.RandomEffectSlowdown[difficulty] = difficulty > 0 ? this.RandomEffectSlowdown[difficulty - 1] : 0.0; - this.JarateDuration[difficulty] = this.JarateDuration[difficulty] = difficulty > 0 ? this.JarateDuration[difficulty - 1] : 0.0; - this.MilkDuration[difficulty] = this.MilkDuration[difficulty] = difficulty > 0 ? this.MilkDuration[difficulty - 1] : 0.0; - this.GasDuration[difficulty] = this.GasDuration[difficulty] = difficulty > 0 ? this.GasDuration[difficulty - 1] : 0.0; - this.MarkDuration[difficulty] = this.MarkDuration[difficulty] = difficulty > 0 ? this.MarkDuration[difficulty - 1] : 0.0; - this.SilentMarkDuration[difficulty] = this.SilentMarkDuration[difficulty] = difficulty > 0 ? this.SilentMarkDuration[difficulty - 1] : 0.0; - this.IgniteDuration[difficulty] = this.IgniteDuration[difficulty] = difficulty > 0 ? this.IgniteDuration[difficulty - 1] : 10.0; - this.IgniteDelay[difficulty] = this.IgniteDelay[difficulty] = difficulty > 0 ? this.IgniteDelay[difficulty - 1] : 0.0; - this.StunEffectDuration[difficulty] = this.StunEffectDuration[difficulty] = difficulty > 0 ? this.StunEffectDuration[difficulty - 1] : 0.0; - this.StunEffectSlowdown[difficulty] = this.StunEffectSlowdown[difficulty] = difficulty > 0 ? this.StunEffectSlowdown[difficulty - 1] : 0.0; - this.BleedDuration[difficulty] = this.BleedDuration[difficulty] = difficulty > 0 ? this.BleedDuration[difficulty - 1] : 0.0; - this.ElectricDuration[difficulty] = this.ElectricDuration[difficulty] = difficulty > 0 ? this.ElectricDuration[difficulty - 1] : 0.0; - this.ElectricSlowdown[difficulty] = this.ElectricSlowdown[difficulty] = difficulty > 0 ? this.ElectricSlowdown[difficulty - 1] : 0.0; - - this.ShockwaveHeight[difficulty] = this.ShockwaveHeight[difficulty] = difficulty > 0 ? this.ShockwaveHeight[difficulty - 1] : 64.0; - this.ShockwaveRange[difficulty] = this.ShockwaveRange[difficulty] = difficulty > 0 ? this.ShockwaveRange[difficulty - 1] : 200.0; - this.ShockwaveDrain[difficulty] = this.ShockwaveDrain[difficulty] = difficulty > 0 ? this.ShockwaveDrain[difficulty - 1] : 0.2; - this.ShockwaveForce[difficulty] = this.ShockwaveForce[difficulty] = difficulty > 0 ? this.ShockwaveForce[difficulty - 1] : 6.0; - - this.ShockwaveStunDuration[difficulty] = this.ShockwaveStunDuration[difficulty] = difficulty > 0 ? this.ShockwaveStunDuration[difficulty - 1] : 2.0; - this.ShockwaveStunSlowdown[difficulty] = this.ShockwaveStunSlowdown[difficulty] = difficulty > 0 ? this.ShockwaveStunSlowdown[difficulty - 1] : 0.7; - - this.TrapCooldown[difficulty] = this.TrapCooldown[difficulty] = difficulty > 0 ? this.TrapCooldown[difficulty - 1] : 8.0; - - this.CrawlSpeedMultiplier[difficulty] = this.CrawlSpeedMultiplier[difficulty] = difficulty > 0 ? this.CrawlSpeedMultiplier[difficulty - 1] : 0.5; - } + this.AutoChaseAddQuietFootstep[difficulty] = 0; + this.AutoChaseAddVoice[difficulty] = 8; + this.AutoChaseAddWeapon[difficulty] = 4; + this.AutoChaseSprinters[difficulty] = false; + this.AutoChaseAfterChaseCooldown[difficulty] = 3.0; + + this.ProjectileCooldownMin[difficulty] = 1.0; + this.ProjectileCooldownMax[difficulty] = 2.0; + this.ProjectileSpeed[difficulty] = 400.0; + this.ProjectileDamage[difficulty] = 50.0; + this.ProjectileRadius[difficulty] = 128.0; + this.ProjectileDeviation[difficulty] = 0.0; + this.ProjectileClipSize[difficulty] = 3; + this.ProjectileReloadTime[difficulty] = 2.0; + this.ProjectileChargeUp[difficulty] = 5.0; + this.ProjectileCount[difficulty] = 1; - for (int i = 0; i < 3; i++) - { - this.ShockwaveColor1[i] = 128; - this.ShockwaveColor2[i] = 255; + this.IceballSlowDuration[difficulty] = 2.0; + this.IceballSlowPercent[difficulty] = 0.55; + + this.Traps[difficulty] = false; + this.TrapType[difficulty] = 0; + this.TrapCooldown[difficulty] = 8.0; } this.WakeRadius = 0.0; @@ -712,19 +2167,6 @@ enum struct SF2ChaserBossProfileData this.AttackPropModels = null; - this.ChaseDurationAddVisibleMin = 0.025; - this.ChaseDurationAddVisibleMax = 0.2; - - this.ChasePersistencyTimeInit = 5.0; - this.ChaseAttackPersistencyTimeInit = -1.0; - this.ChaseAttackPersistencyTimeAdd = 2.0; - this.ChaseNewTargetPersistencyTimeInit = -1.0; - this.ChaseNewTargetPersistencyTimeAdd = 2.0; - this.ChasePersistencyAddVisibleMin = 0.05; - this.ChasePersistencyAddVisibleMax = 0.15; - this.ChaseStunPersistencyTimeInit = -1.0; - this.ChaseStunPersistencyTimeAdd = 2.0; - this.FootstepSenses.Init(); this.LoudFootstepSenses.Init(); this.QuietFootstepSenses.Init(); @@ -732,6 +2174,8 @@ enum struct SF2ChaserBossProfileData this.WeaponSenses.Init(); this.FlashlightSenses.Init(); + this.SmellData.Init(); + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { this.FootstepSenses.AddCount[difficulty] = 1; @@ -744,28 +2188,21 @@ enum struct SF2ChaserBossProfileData } this.StunEnabled = false; - this.StunCooldown = 3.5; - this.StunHealth = 0.0; this.StunHealthPerPlayer = 0.0; for (int i = 0; i < 10; i++) { this.StunHealthPerClass[i] = 0.0; } - this.FlashlightStun = false; - this.FlashlightDamage = 0.0; this.DisappearOnStun = false; - this.ItemDropOnStun = false; - this.StunItemDropType = 1; this.ChaseInitialOnStun = false; this.DamageResistances = null; + this.DeathData.Init(); + this.KeyDrop = false; this.KeyModel = SF_KEYMODEL; this.KeyTrigger[0] = '\0'; - this.AutoChaseEnabled = false; - this.AutoChaseSprinters = false; - this.ChasesEndlessly = false; this.EarthquakeFootsteps = false; @@ -775,17 +2212,6 @@ enum struct SF2ChaserBossProfileData this.EarthquakeFootstepAmplitude = 5.0; this.EarthquakeFootstepFrequency = 25.0; - this.CanCloak = false; - this.CloakOnFlee = false; - this.CloakParticle = "drg_cow_explosioncore_charged_blue"; - this.CloakRenderColor[0] = 0; - this.CloakRenderColor[1] = 1; - this.CloakRenderColor[2] = 2; - this.CloakRenderColor[3] = 3; - this.CloakRenderMode = 1; - this.CloakOnSound = DEFAULT_CLOAKONSOUND; - this.CloakOffSound = DEFAULT_CLOAKOFFSOUND; - this.ProjectilesEnabled = false; this.ProjectileType = SF2BossProjectileType_Fireball; this.CriticalProjectiles = false; @@ -814,101 +2240,12 @@ enum struct SF2ChaserBossProfileData this.BaseballShootSound = BASEBALL_SHOOT; this.BaseballModel = BASEBALL_MODEL; - this.AdvancedDamageEffects = false; - this.AdvancedDamageEffectParticles = true; - - this.RandomAdvancedDamageEffects = false; - this.RandomEffectIndexes = 1; - this.RandomEffectIndexesString = "1"; - this.RandomEffectStunType = 0; - - this.JarateEffects = false; - this.JarateIndexes = 1; - this.JarateIndexesString = "1"; - this.JarateBeamParticle = false; - this.JarateParticle = JARATE_PARTICLE; - this.JarateHitSound = JARATE_HITPLAYER; - - this.MilkEffects = false; - this.MilkIndexes = 1; - this.MilkIndexesString = "1"; - this.MilkBeamParaticle = false; - this.MilkParticle = MILK_PARTICLE; - this.MilkHitSound = JARATE_HITPLAYER; - - this.GasEffects = false; - this.GasIndexes = 1; - this.GasIndexesString = "1"; - this.GasBeamParticle = false; - this.GasParticle = GAS_PARTICLE; - this.GasHitSound = JARATE_HITPLAYER; - - this.MarkEffects = false; - this.MarkIndexes = 1; - this.MarkIndexesString = "1"; - - this.SilentMarkEffects = false; - this.SilentMarkIndexes = 1; - this.SilentMarkIndexesString = "1"; - - this.IgniteEffects = false; - this.IgniteIndexes = 1; - this.IgniteIndexesString = "1"; - - this.StunEffects = false; - this.StunEffectIndexes = 1; - this.StunEffectIndexesString = "1"; - this.StunEffectType = 0; - this.StunEffectBeamParticle = false; - this.StunParticle = STUN_PARTICLE; - this.StunHitSound = STUN_HITPLAYER; - - this.BleedEffects = false; - this.BleedIndexes = 1; - this.BleedIndexesString = "1"; - - this.ElectricEffects = false; - this.ElectricIndexes = 1; - this.ElectricIndexesString = "1"; - this.ElectricType = 0; - this.ElectricBeamParticle = false; - this.ElectricParticleRed = ELECTRIC_RED_PARTICLE; - this.ElectricParticleBlue = ELECTRIC_BLUE_PARTICLE; - - this.SmiteEffects = false; - this.SmiteIndexes = 1; - this.SmiteIndexesString = "1"; - this.SmiteDamage = 9001.0; - this.SmiteDamageType = 1048576; //Critical cause we're mean - this.SmiteColor[0] = 255; - this.SmiteColor[1] = 255; - this.SmiteColor[2] = 255; - this.SmiteColor[3] = 255; - this.SmiteMessage = false; - this.SmiteHitSound = SOUND_THUNDER; - this.XenobladeCombo = false; this.XenobladeDuration = 0.0; this.XenobladeToppleDuration = 0.0; this.XenobladeToppleSlowdown = 0.5; this.XenobladeDazeDuration = 0.0; - this.Shockwaves = false; - this.ShockwaveStun = false; - this.ShockwaveIndexes = 1; - this.ShockwaveIndexesString = "1"; - this.ShockwaveWidths[0] = 40.0; - this.ShockwaveWidths[1] = 20.0; - this.ShockwaveAmplitude = 5.0; - this.ShockwaveAlpha1 = 255; - this.ShockwaveAlpha2 = 255; - this.ShockwaveBeamSprite = "sprites/laser.vmt"; - this.ShockwaveHaloSprite = "sprites/halo01.vmt"; - this.ShockwaveBeamModel = -1; - this.ShockwaveHaloModel = -1; - - this.Traps = false; - this.TrapType = 0; this.TrapModel = TRAP_MODEL; this.TrapDeploySound = TRAP_DEPLOY; this.TrapMissSound = TRAP_CLOSE; @@ -917,31 +2254,14 @@ enum struct SF2ChaserBossProfileData this.TrapAnimClose = "trapclosed"; this.TrapAnimOpen[0] = '\0'; - this.DamageParticles = false; - this.DamageParticleName[0] = '\0'; - this.DamageParticleBeam = false; - this.DamageParticleSound[0] = '\0'; - - this.SelfHeal = false; - this.SelfHealPercentageStart = 0.35; - this.SelfHealRecover[0] = 0.75; - this.SelfHealRecover[1] = 0.5; - this.SelfHealRecover[2] = 0.25; - this.CloakToHeal = false; + this.Rages = null; this.BoxingBoss = false; this.NormalSoundHook = false; - this.ChaseInitialAnimations = false; this.OldAnimationAI = false; - this.AlertWalkingAnimation = false; - this.SpawnAnimationsEnabled = false; - - this.Crawling = false; - this.CrawlDetectMins = view_as({0.0, 0.0, 0.0}); - this.CrawlDetectMaxs = view_as({0.0, 0.0, 0.0}); - this.ChaseOnLook = false; + this.ChaseOnLookData.Init(); this.UnnerfedVisibility = g_DefaultBossVisibilityStateConVar.BoolValue; @@ -970,17 +2290,19 @@ enum struct SF2ChaserBossProfileData this.AttackKilledClientSounds.Init(); this.AttackKilledClientSounds.Channel = SNDCHAN_STATIC; this.AttackKilledClientSounds.Level = SNDLEVEL_HELICOPTER; - this.RageAllSounds.Init(); - this.RageTwoSounds.Init(); - this.RageThreeSounds.Init(); - this.SelfHealSounds.Init(); this.StunnedSounds.Init(); + this.DeathSounds.Init(); + this.TauntKillSounds.Init(); + this.SmellSounds.Init(); this.FootstepSounds.Init(); this.AttackSounds = null; this.HitSounds = null; this.MissSounds = null; this.BulletShootSounds = null; this.ProjectileShootSounds = null; + this.AttackBeginSounds = null; + this.AttackEndSounds = null; + this.AttackLoopSounds = null; this.ChaseMusics.Init(); this.ChaseMusics.Channel = MUSIC_CHAN; @@ -990,6 +2312,15 @@ enum struct SF2ChaserBossProfileData this.AlertMusics.Channel = MUSIC_CHAN; this.IdleMusics.Init(); this.IdleMusics.Channel = MUSIC_CHAN; + + this.Postures = null; + + this.AlertOnAlertInfo.Init(); + this.AlertOnChaseInfo.Init(); + + this.CloakData.Init(); + + this.IdleData.Init(); } void PostLoad() @@ -1001,11 +2332,10 @@ enum struct SF2ChaserBossProfileData this.AttackKilledSounds.PostLoad(); this.AttackKilledAllSounds.PostLoad(); this.AttackKilledClientSounds.PostLoad(); - this.RageAllSounds.PostLoad(); - this.RageTwoSounds.PostLoad(); - this.RageThreeSounds.PostLoad(); - this.SelfHealSounds.PostLoad(); this.StunnedSounds.PostLoad(); + this.DeathSounds.PostLoad(); + this.TauntKillSounds.PostLoad(); + this.SmellSounds.PostLoad(); this.FootstepSounds.PostLoad(); this.ChaseMusics.PostLoad(); @@ -1013,6 +2343,8 @@ enum struct SF2ChaserBossProfileData this.AlertMusics.PostLoad(); this.IdleMusics.PostLoad(); + this.CloakData.PostLoad(); + if (this.ProjectilePosOffsets != null && this.ProjectilePosOffsets.Length <= 0) { delete this.ProjectilePosOffsets; @@ -1022,22 +2354,70 @@ enum struct SF2ChaserBossProfileData { delete this.AttackSounds; } + if (this.HitSounds != null && this.HitSounds.Length <= 0) { delete this.HitSounds; } + if (this.MissSounds != null && this.MissSounds.Length <= 0) { delete this.MissSounds; } + if (this.BulletShootSounds != null && this.BulletShootSounds.Length <= 0) { delete this.BulletShootSounds; } + if (this.ProjectileShootSounds != null && this.ProjectileShootSounds.Length <= 0) { delete this.ProjectileShootSounds; } + + if (this.AttackBeginSounds != null && this.AttackBeginSounds.Length <= 0) + { + delete this.AttackBeginSounds; + } + + if (this.AttackEndSounds != null && this.AttackEndSounds.Length <= 0) + { + delete this.AttackEndSounds; + } + + if (this.AttackLoopSounds != null && this.AttackLoopSounds.Length <= 0) + { + delete this.AttackLoopSounds; + } + + if (this.Rages != null) + { + SF2ChaserRageInfo rageData; + + for (int i = 0; i < this.Rages.Length; i++) + { + this.Rages.GetArray(i, rageData, sizeof(rageData)); + + rageData.PostLoad(); + } + } + + if (this.Attacks != null) + { + SF2ChaserBossProfileAttackData attackData; + + for (int i = 0; i < this.Attacks.Length; i++) + { + this.Attacks.GetArray(i, attackData, sizeof(attackData)); + + if (attackData.Type == SF2BossAttackType_Invalid) + { + continue; + } + + attackData.PostLoad(); + } + } } void SortSoundSections(KeyValues kv, const char[] sectionName, bool checkFile) @@ -1070,25 +2450,21 @@ enum struct SF2ChaserBossProfileData { this.AttackKilledClientSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageAll]) == 0) - { - this.RageAllSounds.Load(kv, checkFile); - } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageTwo]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Stun]) == 0) { - this.RageTwoSounds.Load(kv, checkFile); + this.StunnedSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageThree]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Death]) == 0) { - this.RageThreeSounds.Load(kv, checkFile); + this.DeathSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_SelfHeal]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_TauntKill]) == 0) { - this.SelfHealSounds.Load(kv, checkFile); + this.TauntKillSounds.Load(kv, checkFile); } - else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Stun]) == 0) + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Smell]) == 0) { - this.StunnedSounds.Load(kv, checkFile); + this.SmellSounds.Load(kv, checkFile); } else if (strcmp(sectionName, "sound_footsteps") == 0) { @@ -1112,6 +2488,75 @@ enum struct SF2ChaserBossProfileData } } + void LoadNestedSoundSections(KeyValues kv, bool checkFile, ArrayList &list, bool soundHook = false) + { + list = new ArrayList(sizeof(SF2BossProfileSoundInfo)); + if (kv.GotoFirstSubKey()) + { + char sectionName[64]; + kv.GetSectionName(sectionName, sizeof(sectionName)); + if (strcmp(sectionName, "paths") == 0) + { + kv.GoBack(); + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } + } + else + { + do + { + kv.GetSectionName(sectionName, sizeof(sectionName)); + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.SectionName = sectionName; + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + } + kv.GoBack(); + } + + void DestroyNestedSection(ArrayList list) + { + if (list != null) + { + SF2BossProfileSoundInfo soundInfo; + + for (int i = 0; i < list.Length; i++) + { + list.GetArray(i, soundInfo, sizeof(soundInfo)); + + if (soundInfo.Paths == null) + { + continue; + } + soundInfo.Destroy(); + } + delete list; + } + } + void Destroy() { this.IdleSounds.Destroy(); @@ -1121,17 +2566,19 @@ enum struct SF2ChaserBossProfileData this.AttackKilledSounds.Destroy(); this.AttackKilledAllSounds.Destroy(); this.AttackKilledClientSounds.Destroy(); - this.RageAllSounds.Destroy(); - this.RageTwoSounds.Destroy(); - this.RageThreeSounds.Destroy(); - this.SelfHealSounds.Destroy(); this.StunnedSounds.Destroy(); + this.DeathSounds.Destroy(); + this.TauntKillSounds.Destroy(); + this.SmellSounds.Destroy(); this.FootstepSounds.Destroy(); this.ChaseMusics.Destroy(); this.ChaseVisibleMusics.Destroy(); this.AlertMusics.Destroy(); this.IdleMusics.Destroy(); + + this.CloakData.Destroy(); + if (this.Attacks != null) { SF2ChaserBossProfileAttackData attackData; @@ -1144,104 +2591,125 @@ enum struct SF2ChaserBossProfileData { continue; } - attackData.Destroy(); } delete this.Attacks; } + if (this.ProjectilePosOffsets != null) { delete this.ProjectilePosOffsets; } + if (this.AttackPropModels != null) { delete this.AttackPropModels; } - if (this.AttackSounds != null) + + if (this.DamageResistances != null) { - SF2BossProfileSoundInfo soundInfo; + delete this.DamageResistances; + } - for (int i = 0; i < this.AttackSounds.Length; i++) - { - this.AttackSounds.GetArray(i, soundInfo, sizeof(soundInfo)); + this.DestroyNestedSection(this.AttackSounds); + this.DestroyNestedSection(this.HitSounds); + this.DestroyNestedSection(this.MissSounds); + this.DestroyNestedSection(this.BulletShootSounds); + this.DestroyNestedSection(this.ProjectileShootSounds); + this.DestroyNestedSection(this.AttackBeginSounds); + this.DestroyNestedSection(this.AttackEndSounds); + this.DestroyNestedSection(this.AttackLoopSounds); - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); - } - delete this.AttackSounds; - } - if (this.HitSounds != null) + if (this.Rages != null) { - SF2BossProfileSoundInfo soundInfo; + SF2ChaserRageInfo rageData; - for (int i = 0; i < this.HitSounds.Length; i++) + for (int i = 0; i < this.Rages.Length; i++) { - this.HitSounds.GetArray(i, soundInfo, sizeof(soundInfo)); + this.Rages.GetArray(i, rageData, sizeof(rageData)); - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); + rageData.Destroy(); } - delete this.HitSounds; + + delete this.Rages; } - if (this.MissSounds != null) + + if (this.Postures != null) { - SF2BossProfileSoundInfo soundInfo; + StringMapSnapshot snapshot = this.Postures.Snapshot(); + SF2ChaserBossProfilePostureInfo posture; - for (int i = 0; i < this.MissSounds.Length; i++) + char postureName[64]; + for (int i = 0; i < snapshot.Length; i++) { - this.MissSounds.GetArray(i, soundInfo, sizeof(soundInfo)); - - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); + snapshot.GetKey(i, postureName, sizeof(postureName)); + this.Postures.GetArray(postureName, posture, sizeof(posture)); + posture.Destroy(); } - delete this.MissSounds; + + delete snapshot; + delete this.Postures; } - if (this.BulletShootSounds != null) + + } + + bool GetAttackFromIndex(int attackIndex, SF2ChaserBossProfileAttackData data) + { + if (this.Attacks == null || this.Attacks.Length <= attackIndex) { - SF2BossProfileSoundInfo soundInfo; + return false; + } + this.Attacks.GetArray(attackIndex, data, sizeof(data)); + return true; + } - for (int i = 0; i < this.BulletShootSounds.Length; i++) + bool GetAttack(const char[] attackName, SF2ChaserBossProfileAttackData data) + { + if (this.Attacks == null || this.Attacks.Length == 0) + { + return false; + } + for (int i = 0; i < this.Attacks.Length; i++) + { + SF2ChaserBossProfileAttackData tempData; + this.Attacks.GetArray(i, tempData, sizeof(tempData)); + if (strcmp(attackName, tempData.Name) == 0) { - this.BulletShootSounds.GetArray(i, soundInfo, sizeof(soundInfo)); - - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); + data = tempData; + return true; } - delete this.BulletShootSounds; } - if (this.ProjectileShootSounds != null) + return false; + } + + bool GetPosture(const char[] posture, SF2ChaserBossProfilePostureInfo postureInfo) + { + if (this.Postures == null) { - SF2BossProfileSoundInfo soundInfo; + return false; + } - for (int i = 0; i < this.ProjectileShootSounds.Length; i++) - { - this.ProjectileShootSounds.GetArray(i, soundInfo, sizeof(soundInfo)); + return this.Postures.GetArray(posture, postureInfo, sizeof(postureInfo)); + } - if (soundInfo.Paths == null) - { - continue; - } - soundInfo.Destroy(); - } - delete this.ProjectileShootSounds; + bool GetPostureFromIndex(int index, SF2ChaserBossProfilePostureInfo postureInfo) + { + if (this.Postures == null) + { + return false; } - if (this.DamageResistances != null) + StringMapSnapshot snapshot = this.Postures.Snapshot(); + if (index >= snapshot.Length) { - delete this.DamageResistances; + delete snapshot; + return false; } + char postureName[64]; + snapshot.GetKey(index, postureName, sizeof(postureName)); + bool value = this.Postures.GetArray(postureName, postureInfo, sizeof(postureInfo)); + delete snapshot; + return value; } } diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc index 976733f5..08cca9af 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_statue.inc @@ -5,6 +5,7 @@ enum struct SF2StatueBossProfileData ArrayList ModelsCloseDist; float ChaseDuration[Difficulty_Max]; + float ChaseDurationAddMaxRange[Difficulty_Max]; float ChaseDurationAddVisibilityMin[Difficulty_Max]; float ChaseDurationAddVisibilityMax[Difficulty_Max]; @@ -20,15 +21,17 @@ enum struct SF2StatueBossProfileData this.ModelsCloseDist = null; this.MoveSounds.Init(); + this.MoveSounds.Flags = SND_CHANGEVOL; this.SingleMoveSounds.Init(); for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - this.ChaseDuration[difficulty] = difficulty > 0 ? this.ChaseDuration[difficulty - 1] : 5.0; - this.ChaseDurationAddVisibilityMin[difficulty] = difficulty > 0 ? this.ChaseDurationAddVisibilityMin[difficulty - 1] : 0.025; - this.ChaseDurationAddVisibilityMax[difficulty] = difficulty > 0 ? this.ChaseDurationAddVisibilityMax[difficulty - 1] : 0.15; - this.ModelChangeDistanceMax[difficulty] = difficulty > 0 ? this.ModelChangeDistanceMax[difficulty - 1] : 1024.0; + this.ChaseDuration[difficulty] = 5.0; + this.ChaseDurationAddMaxRange[difficulty] = 1024.0; + this.ChaseDurationAddVisibilityMin[difficulty] = 0.025; + this.ChaseDurationAddVisibilityMax[difficulty] = 0.15; + this.ModelChangeDistanceMax[difficulty] = 1024.0; - this.IdleLifeTime[difficulty] = difficulty > 0 ? this.IdleLifeTime[difficulty - 1] : 3.0; + this.IdleLifeTime[difficulty] = 3.0; } } diff --git a/addons/sourcemod/scripting/include/sf2/stocks.inc b/addons/sourcemod/scripting/include/sf2/stocks.inc new file mode 100644 index 00000000..ffe87262 --- /dev/null +++ b/addons/sourcemod/scripting/include/sf2/stocks.inc @@ -0,0 +1,131 @@ +#pragma semicolon 1 + +bool DispatchParticleEffect(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], + int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) +{ + char particleReal[PLATFORM_MAX_PATH]; + FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); + if (particle[0] != '\0') + { + int tblidx = FindStringTable("ParticleEffectNames"); + if (tblidx == INVALID_STRING_TABLE) + { + LogError("Could not find string table: ParticleEffectNames"); + return false; + } + char tmp[256]; + int count = GetStringTableNumStrings(tblidx); + int stridx = INVALID_STRING_INDEX; + for (int i = 0; i < count; i++) + { + ReadStringTable(tblidx, i, tmp, sizeof(tmp)); + if (strcmp(tmp, particleReal, false) == 0) + { + stridx = i; + break; + } + } + if (stridx == INVALID_STRING_INDEX) + { + LogError("Could not find particle: %s", particleReal); + return false; + } + + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", startPos[0]); + TE_WriteFloat("m_vecOrigin[1]", startPos[1]); + TE_WriteFloat("m_vecOrigin[2]", startPos[2]); + TE_WriteVector("m_vecAngles", angles); + TE_WriteNum("m_iParticleSystemIndex", stridx); + TE_WriteNum("entindex", entity); + TE_WriteNum("m_iAttachType", view_as(attachType)); + TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); + TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); + TE_WriteNum("m_bControlPoint1", 0); + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 0); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); + TE_SendToAll(); + } + else + { + return false; + } + return true; +} + +bool DispatchParticleEffectBeam(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], + int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) +{ + char particleReal[PLATFORM_MAX_PATH]; + FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); + if (particle[0] != '\0') + { + int tblidx = FindStringTable("ParticleEffectNames"); + if (tblidx == INVALID_STRING_TABLE) + { + LogError("Could not find string table: ParticleEffectNames"); + return false; + } + char tmp[256]; + int count = GetStringTableNumStrings(tblidx); + int stridx = INVALID_STRING_INDEX; + for (int i = 0; i < count; i++) + { + ReadStringTable(tblidx, i, tmp, sizeof(tmp)); + if (strcmp(tmp, particleReal, false) == 0) + { + stridx = i; + break; + } + } + if (stridx == INVALID_STRING_INDEX) + { + LogError("Could not find particle: %s", particleReal); + return false; + } + + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", startPos[0]); + TE_WriteFloat("m_vecOrigin[1]", startPos[1]); + TE_WriteFloat("m_vecOrigin[2]", startPos[2]); + TE_WriteVector("m_vecAngles", angles); + TE_WriteNum("m_iParticleSystemIndex", stridx); + TE_WriteNum("entindex", entity); + TE_WriteNum("m_iAttachType", view_as(attachType)); + TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); + TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); + TE_WriteNum("m_bControlPoint1", 1); + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); + TE_SendToAll(); + } + else + { + //LogError("There is no valid particle to use for effects."); + return false; + } + return true; +} + +float AngleNormalize(float angle) +{ + while (angle > 180.0) + { + angle -= 360.0; + } + while (angle < -180.0) + { + angle += 360.0; + } + return angle; +} + +float AngleDiff(float firstAngle, float secondAngle) +{ + float diff = secondAngle - firstAngle; + return AngleNormalize(diff); +} diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 935fe785..dc7deb17 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #pragma semicolon 1 @@ -57,9 +59,7 @@ char g_SlenderAnimationsList[SF2BossAnimation_MaxAnimations][] = { "idle", "walk", - "walkalert", "attack", - "shoot", "run", "stun", "chaseinitial", @@ -68,8 +68,11 @@ char g_SlenderAnimationsList[SF2BossAnimation_MaxAnimations][] = "fleestart", "heal", "deathcam", - "crawlwalk", - "crawlrun" + "death", + "tauntkill", + "smell", + "attack_begin", + "attack_end" }; enum struct MuteMode @@ -117,8 +120,6 @@ static const char g_PageCollectDuckSounds[][] = bool g_ClientInGame[MAXTF2PLAYERS] = { false, ... }; bool g_ClientInCondition[MAXTF2PLAYERS][TFCond_PowerupModeDominant]; -//Update -bool g_SeeUpdateMenu[MAXTF2PLAYERS] = { false, ... }; //Command bool g_PlayerNoPoints[MAXTF2PLAYERS] = { false, ... }; bool g_AdminNoPoints[MAXTF2PLAYERS] = { false, ... }; @@ -164,21 +165,10 @@ Handle g_SlenderEntityThink[MAX_BOSSES]; Handle g_SlenderFakeTimer[MAX_BOSSES]; Handle g_SlenderDeathCamTimer[MAX_BOSSES]; int g_SlenderDeathCamTarget[MAX_BOSSES]; -float g_SlenderLastKill[MAX_BOSSES]; -int g_SlenderState[MAX_BOSSES]; -int g_SlenderTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -bool g_SlenderTargetIsVisible[MAX_BOSSES] = { false, ... }; -bool g_SlenderSpawning[MAX_BOSSES] = { false, ... }; -float g_SlenderAcceleration[MAX_BOSSES][Difficulty_Max]; -float g_SlenderGoalPos[MAX_BOSSES][3]; float g_SlenderStaticRadius[MAX_BOSSES][Difficulty_Max]; float g_SlenderStaticRate[MAX_BOSSES][Difficulty_Max]; float g_SlenderStaticRateDecay[MAX_BOSSES][Difficulty_Max]; float g_SlenderStaticGraceTime[MAX_BOSSES][Difficulty_Max]; -float g_SlenderChaseDeathPosition[MAX_BOSSES][3]; -bool g_SlenderChaseDeathPositionBool[MAX_BOSSES]; -bool g_SlenderHasAutoChaseEnabled[MAX_BOSSES]; -bool g_SlenderChasesEndlessly[MAX_BOSSES] = { false, ... }; bool g_SlenderAddCompanionsOnDifficulty[MAX_BOSSES] = { false, ... }; float g_SlenderStatueIdleLifeTime[MAX_BOSSES]; @@ -194,29 +184,7 @@ float g_SlenderDeathCamOverlayTimeStart[MAX_BOSSES]; float g_SlenderDeathCamTime[MAX_BOSSES]; //The Gaben's stuff -bool g_SlenderHasBurnKillEffect[MAX_BOSSES]; -bool g_SlenderHasCloakKillEffect[MAX_BOSSES]; -bool g_SlenderHasDecapKillEffect[MAX_BOSSES]; -bool g_SlenderHasGibKillEffect[MAX_BOSSES]; -bool g_SlenderHasGoldKillEffect[MAX_BOSSES]; -bool g_SlenderHasIceKillEffect[MAX_BOSSES]; -bool g_SlenderHasElectrocuteKillEffect[MAX_BOSSES]; -bool g_SlenderHasAshKillEffect[MAX_BOSSES]; -bool g_SlenderHasDeleteKillEffect[MAX_BOSSES]; -bool g_SlenderHasPushRagdollOnKill[MAX_BOSSES]; -bool g_SlenderHasDissolveRagdollOnKill[MAX_BOSSES]; -int g_SlenderDissolveRagdollType[MAX_BOSSES]; -bool g_SlenderHasPlasmaRagdollOnKill[MAX_BOSSES]; -bool g_SlenderHasResizeRagdollOnKill[MAX_BOSSES]; -float g_SlenderResizeRagdollHands[MAX_BOSSES]; -float g_SlenderResizeRagdollHead[MAX_BOSSES]; -float g_SlenderResizeRagdollTorso[MAX_BOSSES]; bool g_SlenderCustomOutroSong[MAX_BOSSES]; -bool g_SlenderHasDecapOrGibKillEffect[MAX_BOSSES]; -bool g_SlenderHasSilentKill[MAX_BOSSES]; -bool g_SlenderHasMultiKillEffect[MAX_BOSSES]; -bool g_SlenderPlayerCustomDeathFlag[MAX_BOSSES]; -int g_SlenderPlayerSetDeathFlag[MAX_BOSSES]; bool g_SlenderUseCustomOutlines[MAX_BOSSES]; int g_SlenderOutlineColorR[MAX_BOSSES]; @@ -226,43 +194,9 @@ int g_SlenderOutlineTransparency[MAX_BOSSES]; bool g_SlenderUseRainbowOutline[MAX_BOSSES]; float g_SlenderRainbowCycleRate[MAX_BOSSES]; -int g_ProjectileFlags[2049] = { 0, ... }; int g_TrapEntityCount; float g_RoundTimeMessage = 0.0; -char g_SlenderCloakOnSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderCloakOffSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderJarateHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderMilkHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderGasHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderStunHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderFireballExplodeSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderFireballShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderFireballTrail[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderIceballImpactSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderIceballTrail[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketExplodeSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketModel[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketTrailParticle[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderRocketExplodeParticle[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderGrenadeShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderSentryRocketShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderArrowShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderManglerShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderBaseballShootSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderEngineSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderShockwaveBeamSprite[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderShockwaveHaloSprite[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderSmiteSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapModel[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapDeploySound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapMissSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapHitSound[MAX_BOSSES][PLATFORM_MAX_PATH]; -char g_SlenderTrapAnimIdle[MAX_BOSSES][65]; -char g_SlenderTrapAnimOpen[MAX_BOSSES][65]; -char g_SlenderTrapAnimClose[MAX_BOSSES][65]; - int g_SlenderTeleportTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; int g_SlenderProxyTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; bool g_SlenderTeleportTargetIsCamping[MAX_BOSSES] = { false, ... }; @@ -291,75 +225,14 @@ Handle g_OnGameFrameTimer = null; // For boss type 2 // General variables PathFollower g_BossPathFollower[MAX_BOSSES]; -bool g_IsSlenderAttacking[MAX_BOSSES]; -bool g_SlenderGiveUp[MAX_BOSSES]; -Handle g_SlenderAttackTimer[MAX_BOSSES]; -Handle g_SlenderLaserTimer[MAX_BOSSES]; -Handle g_SlenderBackupAtkTimer[MAX_BOSSES]; -Handle g_SlenderChaseInitialTimer[MAX_BOSSES]; -Handle g_SlenderRage1Timer[MAX_BOSSES]; -Handle g_SlenderRage2Timer[MAX_BOSSES]; -Handle g_SlenderRage3Timer[MAX_BOSSES]; -Handle g_SlenderSpawnTimer[MAX_BOSSES]; -Handle g_SlenderHealTimer[MAX_BOSSES]; -Handle g_SlenderHealDelayTimer[MAX_BOSSES]; -Handle g_SlenderHealEventTimer[MAX_BOSSES]; -Handle g_SlenderStartFleeTimer[MAX_BOSSES]; - -int g_SlenderInterruptConditions[MAX_BOSSES]; -float g_SlenderLastFoundPlayer[MAX_BOSSES][MAXTF2PLAYERS]; -float g_SlenderLastFoundPlayerPos[MAX_BOSSES][MAXTF2PLAYERS][3]; -float g_SlenderNextPathTime[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderLastCalculPathTime[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderCalculatedWalkSpeed[MAX_BOSSES]; -float g_SlenderCalculatedSpeed[MAX_BOSSES]; -float g_SlenderCalculatedAcceleration[MAX_BOSSES]; -float g_SlenderCalculatedMaxWalkSpeed[MAX_BOSSES]; -float g_SlenderCalculatedMaxSpeed[MAX_BOSSES]; -float g_SlenderSpeedMultiplier[MAX_BOSSES]; -float g_SlenderTimeUntilNoPersistence[MAX_BOSSES]; -int g_SlenderTauntAlertCount[MAX_BOSSES]; float g_SlenderProxyTeleportMinRange[MAX_BOSSES][Difficulty_Max]; float g_SlenderProxyTeleportMaxRange[MAX_BOSSES][Difficulty_Max]; -// Sound variables -float g_SlenderTargetSoundLastTime[MAX_BOSSES] = { -1.0, ... }; -SoundType g_SlenderTargetSoundType[MAX_BOSSES] = { SoundType_None, ... }; -float g_SlenderTargetSoundMasterPos[MAX_BOSSES][3]; // to determine hearing focus -float g_SlenderTargetSoundTempPos[MAX_BOSSES][3]; -float g_SlenderTargetSoundDiscardMasterPosTime[MAX_BOSSES]; -bool g_SlenderInvestigatingSound[MAX_BOSSES]; -int g_SlenderTargetSoundCount[MAX_BOSSES]; -int g_SlenderAutoChaseCount[MAX_BOSSES]; -float g_SlenderAutoChaseCooldown[MAX_BOSSES]; -float g_SlenderSoundPositionSetCooldown[MAX_BOSSES]; -int g_SlenderSoundTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -int g_SlenderSeeTarget[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -bool g_SlenderIsAutoChasingLoudPlayer[MAX_BOSSES]; -float g_SlenderLastHeardVoice[MAX_BOSSES]; -float g_SlenderLastHeardFootstep[MAX_BOSSES]; -float g_SlenderLastHeardWeapon[MAX_BOSSES]; - -float g_SlenderNextStunTime[MAX_BOSSES] = { -1.0, ... }; float g_SlenderNextJumpScare[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextVoiceSound[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextMoanSound[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextWanderPos[MAX_BOSSES][Difficulty_Max]; -float g_SlenderNextCloakTime[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextTrapPlacement[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderNextFootstepSound[MAX_BOSSES] = { -1.0, ... }; - -float g_SlenderFootstepTime[MAX_BOSSES]; -float g_SlenderAnimationDuration[MAX_BOSSES]; - -float g_SlenderTimeUntilRecover[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilAlert[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilIdle[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilChase[MAX_BOSSES] = { -1.0, ... }; + float g_SlenderTimeUntilKill[MAX_BOSSES] = { -1.0, ... }; float g_SlenderTimeUntilNextProxy[MAX_BOSSES] = { -1.0, ... }; -float g_SlenderTimeUntilAttackEnd[MAX_BOSSES] = { -1.0, ... }; float g_SlenderProxyDamageVsEnemy[MAX_BOSSES][Difficulty_Max]; float g_SlenderProxyDamageVsBackstab[MAX_BOSSES][Difficulty_Max]; @@ -369,8 +242,6 @@ int g_SlenderProxyControlGainHitByEnemy[MAX_BOSSES][Difficulty_Max]; float g_SlenderProxyControlDrainRate[MAX_BOSSES][Difficulty_Max]; int g_SlenderMaxProxies[MAX_BOSSES][Difficulty_Max]; -bool g_SlenderInBacon[MAX_BOSSES]; - int g_NightvisionType = 0; //Healthbar @@ -385,6 +256,7 @@ enum struct SF2PageEntityData } ArrayList g_Pages; +ArrayList g_EmptySpawnPagePoints; int g_PageCount; int g_PageMax; bool g_PageRef; @@ -393,8 +265,6 @@ float g_PageRefModelScale; static Handle g_PlayerIntroMusicTimer[MAXTF2PLAYERS] = { null, ... }; -// Seeing Mr. Slendy data. - float g_LastVisibilityProcess[MAXTF2PLAYERS]; bool g_PlayerSeesSlender[MAXTF2PLAYERS][MAX_BOSSES]; float g_PlayerSeesSlenderLastTime[MAXTF2PLAYERS][MAX_BOSSES]; @@ -405,21 +275,7 @@ float g_PlayerScareLastTime[MAXTF2PLAYERS][MAX_BOSSES]; float g_PlayerScareNextTime[MAXTF2PLAYERS][MAX_BOSSES]; float g_PlayerStaticAmount[MAXTF2PLAYERS]; -int g_NpcPlayerScareVictin[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -bool g_NpcChasingScareVictin[MAX_BOSSES]; -bool g_NpcLostChasingScareVictim[MAX_BOSSES]; -bool g_PlayerScaredByBoss[MAXTF2PLAYERS][MAX_BOSSES]; - -bool g_NpcVelocityCancel[MAX_BOSSES]; - //Boxing data -Handle g_SlenderBurnTimer[MAX_BOSSES]; -Handle g_SlenderBleedTimer[MAX_BOSSES]; -Handle g_SlenderMarkedTimer[MAX_BOSSES]; -float g_SlenderStopBurningTimer[MAX_BOSSES]; -float g_SlenderStopBleedingTimer[MAX_BOSSES]; -bool g_SlenderIsBurning[MAX_BOSSES]; //This is for the Sun-on-a-Stick -bool g_SlenderIsMarked[MAX_BOSSES]; //For mini-crits and Bushwacka int g_PlayerHitsToCrits[MAXTF2PLAYERS]; int g_PlayerHitsToHeads[MAXTF2PLAYERS]; @@ -430,13 +286,15 @@ bool g_PlayerIn1UpCondition[MAXTF2PLAYERS]; bool g_PlayerDied1Up[MAXTF2PLAYERS]; bool g_PlayerFullyDied1Up[MAXTF2PLAYERS]; -float g_PlayerLastChaseBossEncounterTime[MAXTF2PLAYERS][MAX_BOSSES]; - float g_PlayerProxyNextVoiceSound[MAXTF2PLAYERS]; bool g_PlayerTrapped[MAXTF2PLAYERS]; int g_PlayerTrapCount[MAXTF2PLAYERS]; +bool g_PlayerLatchedByTongue[MAXTF2PLAYERS]; +int g_PlayerLatchCount[MAXTF2PLAYERS]; +int g_PlayerLatcher[MAXTF2PLAYERS]; + int g_PlayerBossKillSubject[MAXTF2PLAYERS]; // Difficulty @@ -463,6 +321,7 @@ enum struct PlayerPreferences int PlayerPreference_GhostModeToggleState; //0 = Nothing, 1 = Ghost on grace end, 2 = Ghost on death int PlayerPreference_GhostModeTeleportState; //0 = Players, 1 = Bosses + float PlayerPreference_MusicVolume; } PlayerPreferences g_PlayerPreferences[MAXTF2PLAYERS]; @@ -518,8 +377,6 @@ int g_PlayerProxyAskSpawnPoint[MAXTF2PLAYERS] = { -1, ... }; int g_PlayerDesiredFOV[MAXTF2PLAYERS]; Handle g_PlayerPostWeaponsTimer[MAXTF2PLAYERS] = { null, ... }; -float g_PlayerIgniteDurationEffect[MAXTF2PLAYERS] = { 10.0, ... }; -Handle g_PlayerIgniteTimer[MAXTF2PLAYERS] = { null, ... }; Handle g_PlayerPageRewardCycleTimer[MAXTF2PLAYERS] = { null, ... }; static float g_PlayerPageRewardCycleCooldown[MAXTF2PLAYERS] = { 0.0, ... }; static int g_PlayerPageRewardCycleCount[MAXTF2PLAYERS] = { 0, ... }; @@ -712,6 +569,7 @@ ConVar g_DifficultyVoteOptionsConVar; ConVar g_DifficultyVoteRandomConVar; ConVar g_DifficultyVoteRevoteConVar; ConVar g_DifficultyNoGracePageConVar; +ConVar g_HighDifficultyPercentConVar; ConVar g_FileCheckConVar; ConVar g_RestartSessionConVar; @@ -737,32 +595,34 @@ bool g_PlayerViewbobSprintEnabled; Handle g_HudSync; Handle g_HudSync2; Handle g_HudSync3; +Handle g_HudSync4; Handle g_RoundTimerSync; Handle g_Cookie; -int g_SmokeSprite; -int g_LightningSprite; int g_ShockwaveBeam; int g_ShockwaveHalo; +char g_DebugBeamSound[PLATFORM_MAX_PATH]; // Global forwards. GlobalForward g_OnBossAddedFwd; GlobalForward g_OnBossSpawnFwd; GlobalForward g_OnBossDespawnFwd; GlobalForward g_OnBossChangeStateFwd; -GlobalForward g_OnBossAnimationUpdateFwd; GlobalForward g_OnBossGetSpeedFwd; GlobalForward g_OnBossGetWalkSpeedFwd; GlobalForward g_OnBossSeeEntityFwd; GlobalForward g_OnBossHearEntityFwd; GlobalForward g_OnBossRemovedFwd; GlobalForward g_OnBossStunnedFwd; +GlobalForward g_OnBossKilledFwd; GlobalForward g_OnBossCloakedFwd; GlobalForward g_OnBossDecloakedFwd; GlobalForward g_OnBossFinishSpawningFwd; GlobalForward g_OnBossPreAttackFwd; -GlobalForward g_OnBossAttackedFwd; +GlobalForward g_OnBossPreTakeDamageFwd; +GlobalForward g_OnBossPreFlashlightDamageFwd; +GlobalForward g_OnChaserBossGetSuspendActionFwd; GlobalForward g_OnPagesSpawnedFwd; GlobalForward g_OnRoundStateChangeFwd; GlobalForward g_OnClientCollectPageFwd; @@ -779,6 +639,7 @@ GlobalForward g_OnClientEscapeFwd; GlobalForward g_OnClientLooksAtBossFwd; GlobalForward g_OnClientLooksAwayFromBossFwd; GlobalForward g_OnClientStartDeathCamFwd; +GlobalForward g_OnClientPreKillDeathCamFwd; GlobalForward g_OnClientEndDeathCamFwd; GlobalForward g_OnClientGetDefaultWalkSpeedFwd; GlobalForward g_OnClientGetDefaultSprintSpeedFwd; @@ -786,21 +647,25 @@ GlobalForward g_OnClientTakeDamageFwd; GlobalForward g_OnClientSpawnedAsProxyFwd; GlobalForward g_OnClientDamagedByBossFwd; GlobalForward g_OnGroupGiveQueuePointsFwd; -GlobalForward g_OnRenevantTriggerWaveFwd; GlobalForward g_OnBossPackVoteStartFwd; GlobalForward g_OnDifficultyChangeFwd; GlobalForward g_OnClientEnterGameFwd; GlobalForward g_OnGroupEnterGameFwd; GlobalForward g_OnEverythingLoadedFwd; GlobalForward g_OnDifficultyVoteFinishedFwd; +GlobalForward g_OnIsBossCustomAttackPossibleFwd; +GlobalForward g_OnBossGetCustomAttackActionFwd; // Private forwards +PrivateForward g_OnGamemodeStartPFwd; +PrivateForward g_OnGamemodeEnd; PrivateForward g_OnMapStartPFwd; PrivateForward g_OnGameFramePFwd; PrivateForward g_OnRoundStartPFwd; PrivateForward g_OnRoundEndPFwd; PrivateForward g_OnEntityCreatedPFwd; PrivateForward g_OnEntityDestroyedPFwd; +PrivateForward g_OnAdminMenuCreateOptionsPFwd; PrivateForward g_OnPlayerJumpPFwd; PrivateForward g_OnPlayerSpawnPFwd; PrivateForward g_OnPlayerDeathPFwd; @@ -809,7 +674,11 @@ PrivateForward g_OnPlayerDisconnectedPFwd; PrivateForward g_OnPlayerEscapePFwd; PrivateForward g_OnPlayerTeamPFwd; PrivateForward g_OnPlayerClassPFwd; +PrivateForward g_OnPlayerLookAtBossPFwd; PrivateForward g_OnBossRemovedPFwd; +PrivateForward g_OnChaserGetAttackActionPFwd; +PrivateForward g_OnChaserGetCustomAttackPossibleStatePFwd; +PrivateForward g_OnChaserUpdatePosturePFwd; Handle g_SDKGetMaxHealth; Handle g_SDKEquipWearable; @@ -817,6 +686,8 @@ Handle g_SDKPlaySpecificSequence; Handle g_SDKPointIsWithin; Handle g_SDKPassesTriggerFilters; //Handle g_SDKGetSmoothedVelocity; +Handle g_SDKLookupBone; +Handle g_SDKGetBonePosition; Handle g_SDKStartTouch; Handle g_SDKEndTouch; Handle g_SDKWeaponSwitch; @@ -867,15 +738,17 @@ int g_FlashlightHaloModel = -1; #include "sf2/effects.sp" #include "sf2/playergroups.sp" #include "sf2/mapentities.sp" +#include "sf2/entities/initialize.sp" #include "sf2/menus.sp" #include "sf2/npc.sp" #include "sf2/pvp.sp" +#include "sf2/pve.sp" #include "sf2/client.sp" #include "sf2/specialround.sp" #include "sf2/anticamping.sp" #include "sf2/adminmenu.sp" #include "sf2/traps.sp" -#include "sf2/extras/renevant_mode.sp" +#include "sf2/gamemodes/renevant.sp" #include "sf2/extras/natives.sp" #include "sf2/extras/commands.sp" #include "sf2/extras/game_events.sp" @@ -925,8 +798,6 @@ public void OnMapStart() FindHealthBar(); PrecacheSound(SOUND_THUNDER, true); PrecacheSound("weapons/teleporter_send.wav"); - g_SmokeSprite = PrecacheModel("sprites/steam1.vmt"); - g_LightningSprite = PrecacheModel("sprites/lgtning.vmt"); g_ShockwaveBeam = PrecacheModel("sprites/laser.vmt"); g_ShockwaveHalo = PrecacheModel("sprites/halo01.vmt"); PrecacheModel(LASER_MODEL, true); @@ -937,6 +808,8 @@ public void OnMapStart() PrecacheMaterial2(overlay, true); g_GhostOverlayConVar.GetString(overlay, sizeof(overlay)); PrecacheMaterial2(overlay, true); + g_DebugBeamSound = "buttons/blip1.wav"; + PrecacheSound(g_DebugBeamSound); PrecacheModel(SF2_FLASHLIGHT_BEAM_MATERIAL); g_FlashlightHaloModel = PrecacheModel(SF2_FLASHLIGHT_HALO_MATERIAL, true); @@ -1126,6 +999,9 @@ static void StartPlugin() } OnClientPutInServer(i); } + + Call_StartForward(g_OnGamemodeStartPFwd); + Call_Finish(); } static void PrecacheStuff() @@ -1149,9 +1025,6 @@ static void PrecacheStuff() PrecacheSound(CRIT_SOUND); PrecacheSound(ZAP_SOUND); PrecacheSound(PAGE_DETECTOR_BEEP); - PrecacheSound(EXPLOSIVEDANCE_EXPLOSION1); - PrecacheSound(EXPLOSIVEDANCE_EXPLOSION2); - PrecacheSound(EXPLOSIVEDANCE_EXPLOSION3); PrecacheSound(SPECIAL1UPSOUND); PrecacheSound("player/spy_shield_break.wav"); @@ -1312,9 +1185,6 @@ static void StopPlugin() g_Enabled = false; - g_RestartSessionEnabled = false; - g_RestartSessionConVar.SetBool(false); - // Reset CVars. ConVar cvar = FindConVar("mp_friendlyfire"); if (cvar != null) @@ -1339,9 +1209,6 @@ static void StopPlugin() NPCStopMusic(); } - // Cleanup bosses. - NPCRemoveAll(); - // Cleanup clients. for (int i = 1; i <= MaxClients; i++) { @@ -1361,13 +1228,8 @@ static void StopPlugin() } } - g_RenevantMultiEffect = false; - g_RenevantBeaconEffect = false; - g_Renevant90sEffect = false; - g_RenevantMarkForDeath = false; - g_RenevantWallHax = false; - - BossProfilesOnMapEnd(); + Call_StartForward(g_OnGamemodeEnd); + Call_Finish(); if (g_FuncNavPrefer != null) { @@ -1379,7 +1241,24 @@ static void StopPlugin() public void OnMapEnd() { + if (!g_Enabled) + { + return; + } + StopPlugin(); + + g_RestartSessionEnabled = false; + g_RestartSessionConVar.SetBool(false); + + g_RenevantMultiEffect = false; + g_RenevantBeaconEffect = false; + g_Renevant90sEffect = false; + g_RenevantMarkForDeath = false; + g_RenevantWallHax = false; + + BossProfilesOnMapEnd(); + NPCRemoveAll(); } public void OnMapTimeLeftChanged() @@ -1417,7 +1296,7 @@ public void TF2_OnConditionAdded(int client, TFCond cond) player.HandleFlashlight(); } } - if (cond == view_as(82)) + if (cond == TFCond_HalloweenKart) { if (player.IsProxy) { @@ -1466,14 +1345,38 @@ static Action Timer_GlobalGameFrame(Handle timer) { continue; } + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(i)); + if (!chaser.IsValid()) + { + continue; + } + + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.GetProfileData().BoxingBoss) + { + continue; + } + + SF2NPC_BaseNPC baseNPC = view_as(controller); + if (baseNPC.GetProfileData().IsPvEBoss) + { + continue; + } NPCGetBossName(i, boxingBossName, sizeof(boxingBossName)); - int stunHealth = RoundToNearest(NPCChaserGetStunHealth(i)); - if (stunHealth < 0 || NPCGetEntRef(i) == INVALID_ENT_REFERENCE) + float health = float(chaser.GetProp(Prop_Data, "m_iHealth")); + float maxHealth = chaser.MaxHealth; + if (chaser.GetProp(Prop_Data, "m_takedamage") == DAMAGE_EVENTS_ONLY) + { + health = chaser.StunHealth; + maxHealth = chaser.MaxStunHealth; + } + int displayHealth = RoundToFloor(health); + if (displayHealth < 0) { - stunHealth = 0; + displayHealth = 0; } - int stunInitHealth = RoundToNearest(NPCChaserGetStunInitialHealth(i)); - Format(message, sizeof(message), "%s\n%s's current health is %i of %i", message, boxingBossName, stunHealth, stunInitHealth); + int displayMaxHealth = RoundToFloor(maxHealth); + Format(message, sizeof(message), "%s\n%s's current health is %i of %i", message, boxingBossName, displayHealth, displayMaxHealth); } for (int client = 1; client <= MaxClients; client++) { @@ -1664,7 +1567,10 @@ static Action Timer_GlobalGameFrame(Handle timer) } else { - if (!IsRoundEnding() && !IsRoundInWarmup() && !IsRoundInIntro()) DisplayProxyAskMenu(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); + if (!IsRoundEnding() && !IsRoundInWarmup() && !IsRoundInIntro()) + { + DisplayProxyAskMenu(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); + } } } // Set the cooldown time! @@ -1759,13 +1665,54 @@ static Action Timer_BossCountUpdate(Handle timer) { SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (Npc.UniqueID == -1 || - g_SlenderCopyMaster[Npc.Index] != -1 || - (Npc.Flags & SFF_FAKE)) + Npc.IsCopy || + (Npc.Flags & SFF_FAKE) || Npc.GetProfileData().IsPvEBoss) { continue; } bossPreferredCount++; + + int difficulty = GetLocalGlobalDifficulty(Npc.Index); + SF2BossProfileData data; + data = Npc.GetProfileData(); + if (!data.CopiesInfo.Enabled[GetLocalGlobalDifficulty(i)] || (Npc.Flags & SFF_NOCOPIES) != 0) + { + continue; + } + + int minCount = data.CopiesInfo.MinCopies[difficulty]; + if (minCount > 0) + { + for (int i2 = 0; i2 < MAX_BOSSES; i2++) + { + if (i2 == Npc.Index) + { + continue; + } + if (minCount <= 0) + { + break; + } + SF2NPC_BaseNPC tempNPC = SF2NPC_BaseNPC(i2); + if (tempNPC.UniqueID != -1) + { + if ((tempNPC.Flags & SFF_FAKE) != 0) + { + continue; + } + + if (tempNPC.IsCopy && tempNPC.CopyMaster == Npc) + { + minCount--; + continue; + } + } + + minCount--; + bossPreferredCount++; + } + } } for (int i = 1; i <= MaxClients; i++) @@ -1782,6 +1729,7 @@ static Action Timer_BossCountUpdate(Handle timer) // Check if we're near any bosses. int closest = -1; + bool bossIsClose = false; float bestDist = SquareFloat(SF2_BOSS_PAGE_CALCULATION); for (int bossEnt = 0; bossEnt < MAX_BOSSES; bossEnt++) @@ -1799,8 +1747,21 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } + SF2_BaseBoss boss = SF2_BaseBoss(Npc.EntIndex); + if (boss.Target == CBaseEntity(i)) + { + continue; + } + SF2_ChaserEntity chaser = SF2_ChaserEntity(Npc.EntIndex); + if (chaser.IsValid() && chaser.AlertTriggerTarget == SF2_BasePlayer(i)) + { + continue; + } - float dist = Npc.GetDistanceFrom(i); + float myPos[3], bossPos[3]; + boss.GetAbsOrigin(bossPos); + GetClientAbsOrigin(i, myPos); + float dist = GetVectorDistance(bossPos, myPos, true); if (dist < bestDist) { closest = bossEnt; @@ -1811,12 +1772,9 @@ static Action Timer_BossCountUpdate(Handle timer) if (closest != -1) { - continue; + bossIsClose = true; } - closest = -1; - bestDist = SquareFloat(SF2_BOSS_PAGE_CALCULATION); - for (int client = 1; client <= MaxClients; client++) { if (!IsValidClient(client) || @@ -1824,7 +1782,8 @@ static Action Timer_BossCountUpdate(Handle timer) g_PlayerEliminated[client] || IsClientInGhostMode(client) || IsClientInDeathCam(client) || - DidClientEscape(client)) + DidClientEscape(client) || + client == i) { continue; } @@ -1841,8 +1800,13 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } + if (Npc.GetProfileData().IsPvEBoss) + { + continue; + } - if (EntRefToEntIndex(g_SlenderTarget[Npc.Index]) == client) + SF2_BaseBoss boss = SF2_BaseBoss(Npc.EntIndex); + if (boss.IsValid() && boss.Target == CBaseEntity(client)) { busy = true; break; @@ -1862,7 +1826,7 @@ static Action Timer_BossCountUpdate(Handle timer) } } - if (!IsValidClient(closest)) + if (!IsValidClient(closest) && !bossIsClose) { // No one's close to this dude? DUDE! WE NEED ANOTHER BOSS! bossPreferredCount++; @@ -1870,7 +1834,7 @@ static Action Timer_BossCountUpdate(Handle timer) } int diff = bossCount - bossPreferredCount; - if (diff) + if (diff != 0) { if (diff > 0) { @@ -1884,23 +1848,69 @@ static Action Timer_BossCountUpdate(Handle timer) continue; } - if (g_SlenderCopyMaster[Npc.Index] == -1) + if (!Npc.IsCopy) { continue; } + if (Npc.CanBeSeen(_, false)) { continue; } + if (Npc.Flags & SFF_FAKE) { continue; } + if (Npc.GetProfileData().IsPvEBoss) + { + continue; + } + if (Npc.CanRemove) { - Npc.Remove(); - count--; + SF2NPC_BaseNPC master = Npc.CopyMaster; + SF2BossProfileData data; + data = master.GetProfileData(); + int difficulty = GetLocalGlobalDifficulty(master.Index); + if (data.CopiesInfo.MinCopies[difficulty] > 0) + { + int copyCount = 0; + for (int i2 = 0; i2 < MAX_BOSSES; i2++) + { + if (i2 == master.Index) + { + continue; + } + SF2NPC_BaseNPC tempNPC = SF2NPC_BaseNPC(i2); + if (tempNPC.UniqueID == -1) + { + continue; + } + + if (Npc.Flags & SFF_FAKE) + { + continue; + } + + if (tempNPC.CopyMaster != master) + { + continue; + } + copyCount++; + } + if (copyCount > data.CopiesInfo.MinCopies[difficulty]) + { + Npc.Remove(); + count--; + } + } + else + { + Npc.Remove(); + count--; + } } if (count <= 0) @@ -1922,14 +1932,24 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } - if (g_SlenderCopyMaster[Npc.Index] != -1) + + if (Npc.IsCopy) + { + continue; + } + + SF2BossProfileData data; + data = Npc.GetProfileData(); + if (!data.CopiesInfo.Enabled[GetLocalGlobalDifficulty(i)] || (Npc.Flags & SFF_NOCOPIES) != 0) { continue; } - if (!(Npc.Flags & SFF_COPIES)) + + if (data.IsPvEBoss) { continue; } + if (Npc.Flags & SFF_FAKE) { continue; @@ -1944,7 +1964,7 @@ static Action Timer_BossCountUpdate(Handle timer) { continue; } - if (g_SlenderCopyMaster[tempNpc.Index] != i) + if (tempNpc.CopyMaster != Npc) { continue; } @@ -1954,12 +1974,11 @@ static Action Timer_BossCountUpdate(Handle timer) int difficulty = GetLocalGlobalDifficulty(Npc.Index); - int copyDifficulty = g_SlenderMaxCopies[Npc.Index][difficulty]; + int copyDifficulty = Npc.GetMaxCopies(difficulty); if (copyCount >= copyDifficulty) { continue; } - Npc.GetProfile(profile, sizeof(profile)); AddProfile(profile, _, Npc); @@ -2066,10 +2085,6 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { switch (StringToInt(intValue)) { - case Difficulty_Easy: - { - g_RoundDifficultyModifier = DIFFICULTYMODIFIER_NORMAL; - } case Difficulty_Hard: { g_RoundDifficultyModifier = DIFFICULTYMODIFIER_HARD; @@ -2126,7 +2141,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { continue; } - SF2NPC_BaseNPC masterNpc = SF2NPC_BaseNPC(g_SlenderCompanionMaster[Npc.Index]); + SF2NPC_BaseNPC masterNpc = Npc.CompanionMaster; if (masterNpc.IsValid() && g_SlenderAddCompanionsOnDifficulty[masterNpc.Index]) { Npc.RemoveFromGame(); @@ -2154,15 +2169,15 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) } else if (cvar == g_PlayerShakeEnabledConVar) { - g_IsPlayerShakeEnabled = !!StringToInt(intValue); + g_IsPlayerShakeEnabled = StringToInt(intValue) != 0; } else if (cvar == g_PlayerViewbobHurtEnabledConVar) { - g_PlayerViewbobHurtEnabled = !!StringToInt(intValue); + g_PlayerViewbobHurtEnabled = StringToInt(intValue) != 0; } else if (cvar == g_PlayerViewbobSprintEnabledConVar) { - g_PlayerViewbobSprintEnabled = !!StringToInt(intValue); + g_PlayerViewbobSprintEnabled = StringToInt(intValue) != 0; } else if (cvar == g_GravityConVar) { @@ -2274,7 +2289,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) if (IsValidEntity(ent)) { GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); - SF2NPC_BaseNPC Npc = view_as(npcIndex); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); if (!Npc.IsValid()) { continue; @@ -2368,6 +2383,10 @@ public void OnEntityCreated(int ent, const char[] classname) { RemoveEntity(ent); } + else if (strcmp(classname, "tf_ragdoll") == 0) + { + SetEntProp(ent, Prop_Send, "m_bIceRagdoll", true); + } Call_StartForward(g_OnEntityCreatedPFwd); Call_PushCell(CBaseEntity(ent)); @@ -2420,13 +2439,6 @@ public void OnEntityDestroyed(int ent) return; } - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(ent)); - if (Npc != SF2_INVALID_NPC) - { - Npc.UnSpawn(); - return; - } - char classname[64]; GetEntityClassname(ent, classname, sizeof(classname)); @@ -2561,8 +2573,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M return Plugin_Continue; } - int difficulty = g_DifficultyConVar.IntValue; - SF2_BasePlayer client = SF2_BasePlayer(entity); if (client.IsValid) { @@ -2576,26 +2586,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M } } } - else if (client.IsProxy) - { - int master = NPCGetFromUniqueID(client.ProxyMaster); - if (master != -1) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(master, profile, sizeof(profile)); - - switch (channel) - { - case SNDCHAN_VOICE: - { - if (!g_SlenderProxiesAllowNormalVoices[master]) - { - return Plugin_Handled; - } - } - } - } - } else if (!client.IsEliminated && !client.HasEscaped) { switch (channel) @@ -2606,32 +2596,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M { return Plugin_Handled; } - if (!StrContains(sample, "vo/halloween_scream")) - { - return Plugin_Handled; - } - - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Voice) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Voice)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDVOICE; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddVoice(bossIndex, difficulty); - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = SoundType_Voice; - } - } } case SNDCHAN_BODY: { @@ -2652,146 +2616,6 @@ Action Hook_NormalSound(int clients[64], int &numClients, char sample[PLATFORM_M client.ViewPunch(punchVelStep); } - - bool isLoudStep = false; - - bool isCrouchStep = false; - - if (client.IsSprinting && !(client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked"))) - { - isLoudStep = true; - } - else if (client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked")) - { - isCrouchStep = true; - } - - SoundType soundType = SoundType_Footstep; - if (isLoudStep) - { - soundType = SoundType_LoudFootstep; - } - else if (isCrouchStep) - { - soundType = SoundType_QuietFootstep; - } - - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, soundType) && NPCShouldHearEntity(bossIndex, client.index, soundType)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - if (isLoudStep) - { - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFOOTSTEPLOUD; - } - else if (isCrouchStep) - { - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFOOTSTEPQUIET; - } - else - { - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFOOTSTEP; - } - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - if (isLoudStep) - { - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddLoudFootstep(bossIndex, difficulty); - } - else - { - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddFootstep(bossIndex, difficulty); - } - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = soundType; - } - } - } - } - case SNDCHAN_ITEM, SNDCHAN_WEAPON: - { - if (StrContains(sample, "swing", false) || StrContains(sample, "impact", false) != -1 || StrContains(sample, "hit", false) != -1 || StrContains(sample, "slice", false) != -1 || StrContains(sample, "reload", false) != -1 || StrContains(sample, "woosh", false) != -1 || StrContains(sample, "eviction", false) != -1 || StrContains(sample, "holy", false) != -1) - { - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Weapon) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Weapon)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDWEAPON; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddWeapon(bossIndex, difficulty); - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = SoundType_Weapon; - } - } - } - } - case SNDCHAN_STATIC: - { - if (StrContains(sample, FLASHLIGHT_CLICKSOUND, false) != -1) - { - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Flashlight) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Flashlight)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDFLASHLIGHT; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddWeapon(bossIndex, difficulty); - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - g_SlenderTargetSoundType[bossIndex] = SoundType_Flashlight; - } - } - } - if (StrContains(sample, "happy_birthday_tf", false) != -1 || StrContains(sample, "jingle_bells_nm", false) != -1) - { - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) - { - if (NPCGetUniqueID(bossIndex) == -1) - { - continue; - } - - if (SlenderCanHearPlayer(bossIndex, client.index, SoundType_Voice) && NPCShouldHearEntity(bossIndex, client.index, SoundType_Voice)) - { - client.GetAbsOrigin(g_SlenderTargetSoundTempPos[bossIndex]); - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDSUSPICIOUSSOUND; - g_SlenderInterruptConditions[bossIndex] |= COND_HEARDVOICE; - if (NPCChaserIsAutoChaseEnabled(bossIndex) && g_SlenderAutoChaseCooldown[bossIndex] < GetGameTime()) - { - g_SlenderSoundTarget[bossIndex] = EntIndexToEntRef(client.index); - g_SlenderAutoChaseCount[bossIndex] += NPCChaserAutoChaseAddVoice(bossIndex, difficulty) * 2; - g_SlenderAutoChaseCooldown[bossIndex] = GetGameTime() + 0.3; - } - } - } } } } @@ -2966,6 +2790,7 @@ void Hook_TriggerOnStartTouch(const char[] output, int caller, int activator, fl } PvP_OnTriggerStartTouch(caller, activator); + PvE_OnTriggerStartTouch(caller, activator); } void Hook_TriggerOnEndTouch(const char[] output, int caller, int activator, float delay) @@ -3018,7 +2843,7 @@ void Hook_TriggerTeleportOnStartTouch(const char[] output, int caller, int activ { continue; } - if (EntRefToEntIndex(g_SlenderTarget[i]) == activator) + /*if (EntRefToEntIndex(g_SlenderTarget[i]) == activator) { for (int ii = 0; ii < MAX_NPCTELEPORTER; ii++) { @@ -3028,12 +2853,12 @@ void Hook_TriggerTeleportOnStartTouch(const char[] output, int caller, int activ break; } } - } + }*/ } } return; } - SF2NPC_BaseNPC Npc = view_as(NPCGetFromEntIndex(activator)); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(activator)); if (Npc.IsValid()) { //A boss took a teleporter @@ -3070,10 +2895,6 @@ void Hook_TriggerTeleportOnStartTouch(const char[] output, int caller, int activ { continue; } - if (EntRefToEntIndex(g_SlenderTarget[i]) == activator) - { - g_SlenderGiveUp[i] = true; - } } } } @@ -3189,8 +3010,8 @@ void CollectPage(int page, int activator) } int maxHealth = SDKCall(g_SDKGetMaxHealth, reds); float healthToRecover = float(maxHealth) / 10.0; - int iHealthToRecover = RoundToNearest(healthToRecover) + GetEntProp(reds, Prop_Send, "m_iHealth"); - SetEntityHealth(reds, iHealthToRecover); + int healthToRecoverInt = RoundToNearest(healthToRecover) + GetEntProp(reds, Prop_Send, "m_iHealth"); + SetEntityHealth(reds, healthToRecoverInt); } } @@ -3395,7 +3216,8 @@ static void GiveRandomPageReward(int player) { EmitSoundToClient(player, LOSE_SPRINT_ROLL, player, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); ClientStopSprint(player); - g_PlayerSprintPoints[player] = 0; + ClientSetSprintPoints(player, 0); + ClientStopSprint(player); } case 17: { @@ -3601,11 +3423,12 @@ public void OnClientCookiesCached(int client) g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = true; g_PlayerPreferences[client].PlayerPreference_ViewBobbing = g_PlayerViewbobEnabledConVar.BoolValue; g_PlayerPreferences[client].PlayerPreference_LegacyHud = g_DefaultLegacyHudConVar.BoolValue; + g_PlayerPreferences[client].PlayerPreference_MusicVolume = 1.0; if (cookie[0] != '\0') { - char s2[15][32]; - int count = ExplodeString(cookie, " ; ", s2, 15, 32); + char s2[16][64]; + int count = ExplodeString(cookie, " ; ", s2, 16, 64); if (count > 0) { @@ -3613,11 +3436,11 @@ public void OnClientCookiesCached(int client) } if (count > 1) { - g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn = !!StringToInt(s2[1]); + g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn = StringToInt(s2[1]) != 0; } if (count > 2) { - g_PlayerPreferences[client].PlayerPreference_ShowHints = !!StringToInt(s2[2]); + g_PlayerPreferences[client].PlayerPreference_ShowHints = StringToInt(s2[2]) != 0; } if (count > 3) { @@ -3625,11 +3448,11 @@ public void OnClientCookiesCached(int client) } if (count > 4) { - g_PlayerPreferences[client].PlayerPreference_FilmGrain = !!StringToInt(s2[4]); + g_PlayerPreferences[client].PlayerPreference_FilmGrain = StringToInt(s2[4]) != 0; } if (count > 5) { - g_PlayerPreferences[client].PlayerPreference_EnableProxySelection = !!StringToInt(s2[5]); + g_PlayerPreferences[client].PlayerPreference_EnableProxySelection = StringToInt(s2[5]) != 0; } if (count > 6) { @@ -3637,15 +3460,15 @@ public void OnClientCookiesCached(int client) } if (count > 7) { - g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = !!StringToInt(s2[7]); + g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = StringToInt(s2[7]) != 0; } if (count > 8) { - g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage = !!StringToInt(s2[8]); + g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage = StringToInt(s2[8]) != 0; } if (count > 9) { - g_PlayerPreferences[client].PlayerPreference_ViewBobbing = !!StringToInt(s2[9]); + g_PlayerPreferences[client].PlayerPreference_ViewBobbing = StringToInt(s2[9]) != 0; } if (count > 10) { @@ -3653,7 +3476,7 @@ public void OnClientCookiesCached(int client) } if (count > 11) { - g_PlayerPreferences[client].PlayerPreference_GroupOutline = !!StringToInt(s2[11]); + g_PlayerPreferences[client].PlayerPreference_GroupOutline = StringToInt(s2[11]) != 0; } if (count > 12) { @@ -3661,7 +3484,11 @@ public void OnClientCookiesCached(int client) } if (count > 13) { - g_PlayerPreferences[client].PlayerPreference_LegacyHud = !!StringToInt(s2[13]); + g_PlayerPreferences[client].PlayerPreference_LegacyHud = StringToInt(s2[13]) != 0; + } + if (count > 14) + { + g_PlayerPreferences[client].PlayerPreference_MusicVolume = StringToFloat(s2[14]) / 100.0; } } } @@ -3685,6 +3512,11 @@ public void OnClientPutInServer(int client) } #endif + if (AreClientCookiesCached(client)) + { + OnClientCookiesCached(client); + } + ClientSetPlayerGroup(client, -1); g_LastCommandTime[client] = GetEngineTime(); @@ -3738,8 +3570,6 @@ public void OnClientPutInServer(int client) ClientSetScareBoostEndTime(client, -1.0); - AFK_SetAFK(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -3832,7 +3662,6 @@ public void OnClientDisconnect(int client) delete message; EndMessage(); - g_SeeUpdateMenu[client] = false; g_PlayerEscaped[client] = false; g_PlayerNoPoints[client] = false; g_AdminNoPoints[client] = false; @@ -3859,6 +3688,7 @@ public void OnClientDisconnect(int client) g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection = true; g_PlayerPreferences[client].PlayerPreference_ViewBobbing = g_PlayerViewbobEnabledConVar.BoolValue; g_PlayerPreferences[client].PlayerPreference_LegacyHud = g_DefaultLegacyHudConVar.BoolValue; + g_PlayerPreferences[client].PlayerPreference_MusicVolume = 1.0; // Reset any client functions that may be still active. ClientResetOverlay(client); @@ -3929,9 +3759,9 @@ SF2RoundState GetRoundState() return g_RoundState; } -void SetRoundTimerPaused(bool bPaused) +void SetRoundTimerPaused(bool paused) { - g_RoundTimerPaused = bPaused; + g_RoundTimerPaused = paused; } void SetRoundTime(int currentTime) @@ -4036,9 +3866,6 @@ void SetRoundState(SF2RoundState roundState) if (g_RestartSessionEnabled) { ArrayList spawnPoint = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for spawnPoint in SetRoundState(SF2RoundState_Grace).", spawnPoint); - #endif float teleportPos[3]; int ent = -1, spawnTeam = 0; while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) @@ -4070,9 +3897,6 @@ void SetRoundState(SF2RoundState roundState) } } delete spawnPoint; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for spawnPoint in SetRoundState(SF2RoundState_Grace).", spawnPoint); - #endif } CPrintToChatAll("{dodgerblue}%t", "SF2 Grace Period End"); @@ -4154,7 +3978,10 @@ void SetRoundState(SF2RoundState roundState) } case SF2RoundState_Active: { - if (SF_IsRenevantMap()) NPCRemoveAll(); + if (SF_IsRenevantMap()) + { + NPCRemoveAll(); + } // Initialize the main round timer. if (g_RoundTimeLimit > 0) { @@ -4210,14 +4037,14 @@ void SetRoundState(SF2RoundState roundState) for (int bossEnt = 0; bossEnt < MAX_BOSSES; bossEnt++) { - SF2NPC_BaseNPC Npc = view_as(bossEnt); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(bossEnt); if (!Npc.IsValid()) { continue; } - if (NPCChaserIsBoxingBoss(Npc.Index)) + if (NPCChaserIsBoxingBoss(Npc.Index) && !Npc.GetProfileData().IsPvEBoss) { - g_SlenderBoxingBossCount += 1; + g_SlenderBoxingBossCount++; } } } @@ -4324,7 +4151,7 @@ bool IsClientParticipating(int client) return false; } - if (!!GetEntProp(client, Prop_Send, "m_bIsCoaching")) + if (GetEntProp(client, Prop_Send, "m_bIsCoaching") != 0) { // Who would coach in this game? return false; @@ -4340,7 +4167,7 @@ bool IsClientParticipating(int client) } } - if (view_as(TF2_GetPlayerClass(client)) == 0) + if (TF2_GetPlayerClass(client) == TFClass_Unknown) { // Player hasn't chosen a class? What. return false; @@ -4352,9 +4179,6 @@ bool IsClientParticipating(int client) ArrayList GetQueueList() { ArrayList array = new ArrayList(3); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for array in GetQueueList.", array); - #endif for (int i = 1; i <= MaxClients; i++) { if (!IsClientParticipating(i)) @@ -4426,7 +4250,7 @@ void SetClientPlayState(int client, bool state, bool enablePlay = true) SetClientPlayNewBossRoundState(client, true); } - if (TF2_GetPlayerClass(client) == view_as(0)) + if (TF2_GetPlayerClass(client) == TFClass_Unknown) { // Player hasn't chosen a class for some reason. Choose one for him. TF2_SetPlayerClass(client, view_as(GetRandomInt(1, 9)), true, true); @@ -4509,7 +4333,7 @@ void TeleportClientToEscapePoint(int client) GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); - TeleportEntity(client, pos, ang, view_as( { 0.0, 0.0, 0.0 } )); + TeleportEntity(client, pos, ang, NULL_VECTOR); if (spawnPoint.IsValid()) { @@ -4529,10 +4353,6 @@ void ForceInNextPlayersInQueue(int amount, bool showMessage = false) ArrayList players = new ArrayList(); ArrayList array = GetQueueList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for players in ForceInNextPlayersInQueue.", players); - #endif - for (int i = 0, size = array.Length; i < size && amountLeft > 0; i++) { if (!array.Get(i, 2)) @@ -4595,9 +4415,6 @@ void ForceInNextPlayersInQueue(int amount, bool showMessage = false) } delete array; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for array in ForceInNextPlayersInQueue.", array); - #endif for (int i = 0, size = players.Length; i < size; i++) { @@ -4623,9 +4440,6 @@ void ForceInNextPlayersInQueue(int amount, bool showMessage = false) } delete players; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for players in ForceInNextPlayersInQueue.", players); - #endif } static int SortQueueList(int index1, int index2, Handle arrayHandle, Handle hndl) @@ -4716,6 +4530,7 @@ static Action Hook_SlenderObjectSetTransmitEx(int ent, int other) void SlenderOnClientStressUpdate(int client) { int difficulty = g_DifficultyConVar.IntValue; + float gameTime = GetGameTime(); float stress = g_PlayerStressAmount[client]; @@ -4730,11 +4545,11 @@ void SlenderOnClientStressUpdate(int client) } int bossFlags = Npc.Flags; - if (bossFlags & SFF_MARKEDASFAKE) + if ((bossFlags & SFF_MARKEDASFAKE) != 0) { continue; } - if ((bossFlags & SFF_NOTELEPORT) && (bossFlags & SFF_PROXIES) && g_SlenderCopyMaster[Npc.Index] == -1) + if ((bossFlags & SFF_NOTELEPORT) != 0 && (bossFlags & SFF_PROXIES) != 0 && !Npc.IsCopy) { //Go get a proxy target anyways ArrayList proxyArray = new ArrayList(); @@ -4762,17 +4577,16 @@ void SlenderOnClientStressUpdate(int client) int teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[Npc.Index]); if (teleportTarget && teleportTarget != INVALID_ENT_REFERENCE && !g_PlayerIsExitCamping[teleportTarget]) { - if (g_PlayerEliminated[teleportTarget] || - DidClientEscape(teleportTarget) || + if (g_PlayerEliminated[teleportTarget] || DidClientEscape(teleportTarget) || (!SF_BossesChaseEndlessly() && !SF_IsRenevantMap() && !SF_IsSurvivalMap() && !g_SlenderTeleportIgnoreChases[Npc.Index] && stress >= g_SlenderTeleportMaxTargetStress[bossIndex]) || - GetGameTime() >= g_SlenderTeleportMaxTargetTime[Npc.Index]) + gameTime >= g_SlenderTeleportMaxTargetTime[Npc.Index]) { // Queue for a new target and mark the old target in the rest period. float restPeriod = Npc.GetTeleportRestPeriod(difficulty); restPeriod = (restPeriod * GetRandomFloat(0.92, 1.08)) / (g_RoundDifficultyModifier); g_SlenderTeleportTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderTeleportPlayersRestTime[Npc.Index][teleportTarget] = GetGameTime() + restPeriod; + g_SlenderTeleportPlayersRestTime[Npc.Index][teleportTarget] = gameTime + restPeriod; g_SlenderTeleportMaxTargetStress[Npc.Index] = 9999.0; g_SlenderTeleportMaxTargetTime[Npc.Index] = -1.0; g_SlenderTeleportTargetTime[Npc.Index] = -1.0; @@ -4808,9 +4622,9 @@ void SlenderOnClientStressUpdate(int client) break; } } - if (g_PlayerStressAmount[i] < preferredTeleportTargetStress || g_RestartSessionEnabled) + if (g_PlayerStressAmount[i] < preferredTeleportTargetStress || g_RestartSessionEnabled || g_SlenderTeleportIgnoreChases[Npc.Index]) { - if (g_SlenderTeleportPlayersRestTime[Npc.Index][i] <= GetGameTime()) + if (g_SlenderTeleportPlayersRestTime[Npc.Index][i] <= gameTime) { preferredTeleportTargetStress = g_PlayerStressAmount[i]; raidArrays.Push(i); @@ -4818,6 +4632,7 @@ void SlenderOnClientStressUpdate(int client) } } } + if (raidArrays != null && raidArrays.Length > 0) { int raidClient = raidArrays.Get(GetRandomInt(0, raidArrays.Length - 1)); @@ -4842,8 +4657,8 @@ void SlenderOnClientStressUpdate(int client) g_SlenderTeleportTarget[Npc.Index] = EntIndexToEntRef(preferredTeleportTarget); g_SlenderTeleportPlayersRestTime[Npc.Index][preferredTeleportTarget] = -1.0; - g_SlenderTeleportMaxTargetTime[Npc.Index] = GetGameTime() + targetDuration; - g_SlenderTeleportTargetTime[Npc.Index] = GetGameTime(); + g_SlenderTeleportMaxTargetTime[Npc.Index] = gameTime + targetDuration; + g_SlenderTeleportTargetTime[Npc.Index] = gameTime; g_SlenderTeleportMaxTargetStress[Npc.Index] = targetStress; teleportTarget = preferredTeleportTarget; @@ -4987,23 +4802,56 @@ void SetPageCount(int num) ArrayList clientSwap = new ArrayList(); for (int client = 1; client <= MaxClients; client++) { - if (!IsValidClient(client)) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) { continue; } - if (!IsPlayerAlive(client)) + if (!player.IsAlive) { continue; } - if (g_PlayerEliminated[client]) + if (player.IsEliminated) { continue; } - if (DidClientEscape(client)) + if (player.HasEscaped) { continue; } - if (IsClientInDeathCam(client)) + if (player.IsInDeathCam) + { + continue; + } + bool isBossNear = false; + float clientPos[3]; + player.GetAbsOrigin(clientPos); + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(bossIndex); + if (!npc.IsValid()) + { + continue; + } + if (!npc.EntIndex || npc.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + float bossPos[3]; + SF2_BaseBoss baseBoss = SF2_BaseBoss(npc.EntIndex); + baseBoss.GetAbsOrigin(bossPos); + if (GetVectorSquareMagnitude(bossPos, clientPos) <= Pow(750.0, 2.0)) + { + isBossNear = true; + break; + } + if (baseBoss.Target == player) + { + isBossNear = true; + break; + } + } + if (isBossNear) { continue; } @@ -5132,9 +4980,11 @@ void SetPageCount(int num) { continue; } + SF2BossProfileData data; + data = NPCGetProfileData(npcIndex); float originalSpeed, speed, timerCheck; - originalSpeed = NPCGetSpeed(npcIndex, difficulty) + NPCGetAddSpeed(npcIndex); + originalSpeed = data.RunSpeed[difficulty] + NPCGetAddSpeed(npcIndex); if (originalSpeed < 600.0) { originalSpeed = 600.0; @@ -5217,11 +5067,16 @@ void SetPageCount(int num) for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i)) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + if (player.IsInDeathCam) { continue; } - if (!g_PlayerEliminated[i] || IsClientInGhostMode(i)) + if (!player.IsEliminated || player.IsInGhostMode) { if (g_PageCount) { @@ -5358,11 +5213,14 @@ static Action Timer_SlaughterRunSpawnBosses(Handle timer) return Plugin_Stop; } -bool Player_FindFreePosition2(int client, float position[3], float mins[3], float maxs[3]) +static bool Player_FindFreePosition2(int client, float position[3], float mins[3], float maxs[3]) { int team = GetClientTeam(client); int mask = MASK_RED; - if (team != TFTeam_Red) mask = MASK_BLUE; + if (team != TFTeam_Red) + { + mask = MASK_BLUE; + } // -90 to 90 float pitchMin = 75.0; // down @@ -5414,19 +5272,22 @@ bool Player_FindFreePosition2(int client, float position[3], float mins[3], floa } return false; } + static bool TraceFilter_NotTeam(int entity, int contentsMask, int team) { if (entity >= 1 && entity <= MaxClients && GetClientTeam(entity) == team) { return false; } + if (IsValidEdict(entity) && NPCGetFromEntIndex(entity) != -1) { return false; } return true; } -static int GetTextEntity(const char[] tempTargetName, bool bCaseSensitive = true) + +static int GetTextEntity(const char[] tempTargetName, bool caseSensitive = true) { // Try to see if we can use a custom message instead of the default. char targetName[64]; @@ -5436,7 +5297,7 @@ static int GetTextEntity(const char[] tempTargetName, bool bCaseSensitive = true GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); if (targetName[0] != '\0') { - if (strcmp(targetName, tempTargetName, bCaseSensitive) == 0) + if (strcmp(targetName, tempTargetName, caseSensitive) == 0) { return ent; } @@ -5598,7 +5459,7 @@ void DistributeQueuePointsToPlayers() /** * Sets the player to the correct team if needed. Returns true if a change was necessary, false if no change occurred. */ -bool HandlePlayerTeam(int client, bool respawn=true) +bool HandlePlayerTeam(int client, bool respawn = true) { if (!IsValidClient(client) || !IsClientParticipating(client)) { @@ -5900,6 +5761,7 @@ Action Timer_ToggleGhostModeCommand(Handle timer, any userid) CPrintToChat(client, "{red}%T", "SF2 Ghost Mode Not Allowed", client); return Plugin_Stop; } + if (!IsClientInGhostMode(client)) { TF2_RespawnPlayer(client); @@ -5981,35 +5843,24 @@ Action Timer_CheckAlivePlayers(Handle timer) { SetRoundTime(120); CPrintToChatAll("Only 1 {red}RED{default} player is alive, 2 minutes left on the timer..."); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); - if (Npc.UniqueID == -1) - { - continue; - } - Npc.SetAddSpeed(50.0); - Npc.SetAddMaxSpeed(75.0); - Npc.SetAddAcceleration(250.0); - } - g_PlayersAreCritted = true; + } else { CPrintToChatAll("Only 1 {red}RED{default} player is alive..."); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + } + + for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(npcIndex); + if (npc.UniqueID == -1) { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); - if (Npc.UniqueID == -1) - { - continue; - } - Npc.SetAddSpeed(50.0); - Npc.SetAddMaxSpeed(75.0); - Npc.SetAddAcceleration(250.0); + continue; } - g_PlayersAreCritted = true; + npc.SetAddSpeed(50.0); + npc.SetAddAcceleration(250.0); } + g_PlayersAreCritted = true; } } case 2, 3: @@ -6148,7 +5999,7 @@ Action Timer_ReplacePlayerRagdoll(Handle timer, any userid) force[0] = 40.0; force[1] = 40.0; force[2] = 40.0; - MakeVectorFromPoints(pos, view_as( { 0.0, 0.0, 0.0 } ), velocity); + MakeVectorFromPoints(pos, { 0.0, 0.0, 0.0 }, velocity); ScaleVector(velocity, 20000.0); ScaleVector(force, 20000.0); SetEntPropVector(ent, Prop_Send, "m_vecForce", force); @@ -6423,24 +6274,19 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) { return Plugin_Stop; } - int slender = EntRefToEntIndex(g_PlayerBossKillSubject[client]); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - int bossIndex = NPCGetFromEntIndex(slender); + int bossIndex = g_PlayerBossKillSubject[client]; if (bossIndex == -1) { return Plugin_Stop; } - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); int ragdoll = GetEntPropEnt(client, Prop_Send, "m_hRagdoll"); if (!IsValidEntity(ragdoll)) { return Plugin_Stop; } - if (!g_SlenderHasDeleteKillEffect[bossIndex]) + if (!data.DeleteRagdoll) { int ent = CreateEntityByName("tf_ragdoll", -1); if (ent != -1) @@ -6452,9 +6298,9 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) GetEntPropVector(ragdoll, Prop_Data, "m_angAbsRotation", ang); TeleportEntity(ent, pos, ang, NULL_VECTOR); SetEntPropVector(ent, Prop_Send, "m_vecRagdollOrigin", pos); - if (g_SlenderHasPushRagdollOnKill[bossIndex]) + if (data.PushRagdoll) { - GetBossProfilePushRagdollForce(profile, force); + force = data.PushRagdollForce; SetEntPropVector(ent, Prop_Send, "m_vecRagdollVelocity", force); SetEntPropVector(ent, Prop_Send, "m_vecForce", force); } @@ -6463,11 +6309,11 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntPropVector(ent, Prop_Send, "m_vecRagdollVelocity", velocity); SetEntPropVector(ent, Prop_Send, "m_vecForce", force); } - if (g_SlenderHasResizeRagdollOnKill[bossIndex]) + if (data.ResizeRagdoll) { - SetEntPropFloat(ent, Prop_Send, "m_flHeadScale", g_SlenderResizeRagdollHead[bossIndex]); - SetEntPropFloat(ent, Prop_Send, "m_flTorsoScale", g_SlenderResizeRagdollTorso[bossIndex]); - SetEntPropFloat(ent, Prop_Send, "m_flHandScale", g_SlenderResizeRagdollHands[bossIndex]); + SetEntPropFloat(ent, Prop_Send, "m_flHeadScale", data.ResizeRagdollHead); + SetEntPropFloat(ent, Prop_Send, "m_flTorsoScale", data.ResizeRagdollTorso); + SetEntPropFloat(ent, Prop_Send, "m_flHandScale", data.ResizeRagdollHands); } else { @@ -6478,7 +6324,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_nForceBone", GetEntProp(ragdoll, Prop_Send, "m_nForceBone")); SetEntProp(ent, Prop_Send, "m_bOnGround", GetEntProp(ragdoll, Prop_Send, "m_bOnGround")); - if (g_SlenderHasCloakKillEffect[bossIndex]) + if (data.CloakRagdoll) { SetEntProp(ent, Prop_Send, "m_bCloaked", true); } @@ -6493,7 +6339,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bWasDisguised", GetEntProp(ragdoll, Prop_Send, "m_bWasDisguised")); SetEntProp(ent, Prop_Send, "m_bFeignDeath", GetEntProp(ragdoll, Prop_Send, "m_bFeignDeath")); - if (g_SlenderHasGibKillEffect[bossIndex]) + if (data.GibRagdoll) { SetEntProp(ent, Prop_Send, "m_bGib", true); } @@ -6502,11 +6348,11 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bGib", GetEntProp(ragdoll, Prop_Send, "m_bGib")); } - if (g_SlenderHasDecapKillEffect[bossIndex]) + if (data.DecapRagdoll) { SetEntProp(ent, Prop_Send, "m_iDamageCustom", TF_CUSTOM_DECAPITATION); } - else if (g_SlenderHasPlasmaRagdollOnKill[bossIndex]) + else if (data.PlasmaRagdoll) { SetEntProp(ent, Prop_Send, "m_iDamageCustom", TF_CUSTOM_PLASMA); } @@ -6515,7 +6361,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_iDamageCustom", GetEntProp(ragdoll, Prop_Send, "m_iDamageCustom")); } - if (g_SlenderHasBurnKillEffect[bossIndex]) + if (data.BurnRagdoll) { SetEntProp(ent, Prop_Send, "m_bBurning", true); } @@ -6524,7 +6370,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bBurning", GetEntProp(ragdoll, Prop_Send, "m_bBurning")); } - if (g_SlenderHasAshKillEffect[bossIndex]) + if (data.AshRagdoll) { SetEntProp(ent, Prop_Send, "m_bBecomeAsh", true); } @@ -6533,7 +6379,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bBecomeAsh", GetEntProp(ragdoll, Prop_Send, "m_bBecomeAsh")); } - if (g_SlenderHasGoldKillEffect[bossIndex]) + if (data.GoldRagdoll) { SetEntProp(ent, Prop_Send, "m_bGoldRagdoll", true); } @@ -6542,7 +6388,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bGoldRagdoll", GetEntProp(ragdoll, Prop_Send, "m_bGoldRagdoll")); } - if (g_SlenderHasIceKillEffect[bossIndex]) + if (data.IceRagdoll) { SetEntProp(ent, Prop_Send, "m_bIceRagdoll", true); } @@ -6551,7 +6397,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) SetEntProp(ent, Prop_Send, "m_bIceRagdoll", GetEntProp(ragdoll, Prop_Send, "m_bIceRagdoll")); } - if (g_SlenderHasElectrocuteKillEffect[bossIndex]) + if (data.ElectrocuteRagdoll) { SetEntProp(ent, Prop_Send, "m_bElectrocuted", true); } @@ -6564,7 +6410,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) ActivateEntity(ent); SetEntPropEnt(client, Prop_Send, "m_hRagdoll", ent, 0); } - if (g_SlenderHasDissolveRagdollOnKill[bossIndex]) + if (data.DissolveRagdoll) { int dissolver = CreateEntityByName("env_entity_dissolver"); if (!IsValidEntity(dissolver)) @@ -6572,7 +6418,7 @@ Action Timer_ModifyRagdoll(Handle timer, any userid) return Plugin_Stop; } char type[2]; - int typeInt = g_SlenderDissolveRagdollType[bossIndex]; + int typeInt = data.DissolveKillType; FormatEx(type, sizeof(type), "%d", typeInt); DispatchKeyValue(dissolver, "dissolvetype", type); DispatchKeyValue(dissolver, "magnitude", "1"); @@ -6624,7 +6470,7 @@ Action Timer_PlayerSwitchToBlue(Handle timer, any userid) ChangeClientTeam(client, TFTeam_Blue); - if (TF2_GetPlayerClass(client) == view_as(0)) + if (TF2_GetPlayerClass(client) == TFClass_Unknown) { // Player hasn't chosen a class for some reason. Choose one for him. TF2_SetPlayerClass(client, view_as(GetRandomInt(1, 9)), true, true); @@ -6633,38 +6479,6 @@ Action Timer_PlayerSwitchToBlue(Handle timer, any userid) return Plugin_Stop; } -stock int ProjectileGetFlags(int projectile) -{ - return g_ProjectileFlags[projectile]; -} - -stock void ProjectileSetFlags(int projectile, int iFlags) -{ - g_ProjectileFlags[projectile] = iFlags; -} - -stock int AttachParticle(int entity, char[] particleType, float posOffset[3] = { 0.0, 0.0, 0.0 } ) -{ - int particle = CreateEntityByName("info_particle_system"); - - if (IsValidEntity(particle)) - { - SetEntPropEnt(particle, Prop_Data, "m_hOwnerEntity", entity); - DispatchKeyValue(particle, "effect_name", particleType); - SetVariantString("!activator"); - AcceptEntityInput(particle, "SetParent", entity, particle, 0); - float start[3]; - TeleportEntity(particle, start, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(particle); - - AcceptEntityInput(particle, "start"); - ActivateEntity(particle); - - return EntIndexToEntRef(particle); - } - return -1; -} - void CreateGeneralParticle(int entity, const char[] sectionName, float particleZPos = 0.0) { if (entity == -1) @@ -6703,20 +6517,18 @@ static Action Timer_RoundStart(Handle timer) if (g_PageMax > 0) { ArrayList arrayClients = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for arrayClients in Timer_RoundStart.", arrayClients); - #endif int clients[MAXTF2PLAYERS]; int clientsNum = 0; for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i) || IsFakeClient(i) || g_PlayerEliminated[i]) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsBot || player.IsEliminated) { continue; } - arrayClients.Push(GetClientUserId(i)); + arrayClients.Push(player.UserID); clients[clientsNum] = i; clientsNum++; } @@ -6754,17 +6566,11 @@ static Action Timer_RoundStart(Handle timer) else { delete arrayClients; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for arrayClients in Timer_RoundStart due to 0 clients.", arrayClients); - #endif } } else { delete arrayClients; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for arrayClients in Timer_RoundStart.", arrayClients); - #endif } } @@ -6835,7 +6641,7 @@ static Action Timer_RoundTime(Handle timer) g_SpecialRoundTime++; } - if (!g_RoundTimerPaused) + if (!g_RoundTimerPaused && !IsBeatBoxBeating(2)) { SetRoundTime(g_RoundTime - 1); } @@ -7021,49 +6827,42 @@ static Action Timer_RoundTimeEscape(Handle timer) static Action Timer_VoteDifficulty(Handle timer, any data) { ArrayList arrayClients = view_as(data); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for arrayClients in Timer_VoteDifficulty.", arrayClients); - #endif if (timer != g_VoteTimer || IsRoundEnding()) { - if (arrayClients != null) - { - delete arrayClients; - } + delete arrayClients; return Plugin_Stop; } - if (NativeVotes_IsVoteInProgress() || IsVoteInProgress()) + if (NativeVotes_IsVoteInProgress()) { return Plugin_Continue; // There's another vote in progess. Wait. } + if (arrayClients == null) + { + return Plugin_Stop; + } + int clients[MAXTF2PLAYERS] = { -1, ... }; int clientsNum; - if (arrayClients != null) + for (int i = 0, size = arrayClients.Length; i < size; i++) { - for (int i = 0, size = arrayClients.Length; i < size; i++) + int client = GetClientOfUserId(arrayClients.Get(i)); + if (client <= 0) { - int client = GetClientOfUserId(arrayClients.Get(i)); - if (client <= 0) - { - continue; - } - - clients[clientsNum] = client; - clientsNum++; + continue; } - delete arrayClients; + clients[clientsNum] = client; + clientsNum++; } - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for arrayClients in Timer_VoteDifficulty.", arrayClients); - #endif + + delete arrayClients; RandomizeVoteMenu(); - VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, (g_DifficultyVoteRevoteConVar.FloatValue > 0.0) ? 10 : 15); - if (GetMenuItemCount(g_MenuVoteDifficulty) == 1) + g_MenuVoteDifficulty.DisplayVote(clients, clientsNum, 15); + if (g_MenuVoteDifficulty.ItemCount == 1) { for (int i = 0; i < clientsNum; i++) { @@ -7444,6 +7243,7 @@ static void InitializeMapEntities() static void SpawnPages() { g_Pages.Clear(); + g_EmptySpawnPagePoints.Clear(); ArrayList array = new ArrayList(2); StringMap pageGroupsByName = new StringMap(); @@ -7454,7 +7254,6 @@ static void SpawnPages() char targetName[64]; // Collect all possible page spawn points. - ent = -1; while ((ent = FindEntityByClassname(ent, "info_target")) != -1) { GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); @@ -7557,12 +7356,28 @@ static void SpawnPages() int pageRenderColor[4]; char pageAnimation[64]; + for (int i = 0, size = array.Length; i < size; i++) + { + if (array.Get(i, 1) != 0) + { + ArrayList grouped = array.Get(i); + for (int i2 = 0; i2 < grouped.Length; i2++) + { + g_EmptySpawnPagePoints.Push(EntRefToEntIndex(grouped.Get(i2))); + } + } + else + { + g_EmptySpawnPagePoints.Push(EntRefToEntIndex(array.Get(i))); + } + } + for (int i = 0; i < pageCount && (i + 1) <= g_PageMax; i++) { int spawnPointEnt = -1; - if (!!array.Get(i, 1)) + if (array.Get(i, 1) != 0) { - ArrayList buttStallion = view_as(array.Get(i)); + ArrayList buttStallion = array.Get(i); spawnPointEnt = buttStallion.Get(GetRandomInt(0, buttStallion.Length - 1)); } else @@ -7698,12 +7513,18 @@ static void SpawnPages() g_Pages.PushArray(pageData, sizeof(pageData)); } + + int index = g_EmptySpawnPagePoints.FindValue(EntRefToEntIndex(spawnPointEnt)); + if (index != -1) + { + g_EmptySpawnPagePoints.Erase(index); + } } // Safely remove all handles. for (int i = 0, size = array.Length; i < size; i++) { - if (!!array.Get(i, 1)) + if (array.Get(i, 1) != 0) { delete view_as(array.Get(i)); } @@ -7727,6 +7548,7 @@ static Action Page_RemoveAlwaysTransmit(Handle timer, int ref) } return Plugin_Stop; } + static bool HandleSpecialRoundState() { #if defined DEBUG @@ -7829,10 +7651,6 @@ static ArrayList GetNewBossRoundProfileList() { ArrayList bossList = GetSelectableBossProfileQueueList().Clone(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been created for bossList in GetNewBossRoundProfileList.", bossList); - #endif - if (bossList.Length > 0) { char mainBoss[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -7925,10 +7743,6 @@ static void HandleNewBossRoundState() } delete bossList; - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been deleted for bossList in HandleNewBossRoundState which comes from GetNewBossRoundProfileList.", bossList); - #endif } if (g_NewBossRound) @@ -7998,10 +7812,6 @@ static void SelectStartingBossesForRound() ArrayList selectableBossList = GetSelectableBossProfileQueueList().Clone(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been created for selectableBossList in SelectStartingBossesForRound.", selectableBossList); - #endif - // Select which boss profile to use. char profileOverride[SF2_MAX_PROFILE_NAME_LENGTH]; g_BossProfileOverrideConVar.GetString(profileOverride, sizeof(profileOverride)); @@ -8023,10 +7833,6 @@ static void SelectStartingBossesForRound() bossList.GetString(GetRandomInt(0, bossList.Length - 1), g_NewBossRoundProfileRoundProfile, sizeof(g_NewBossRoundProfileRoundProfile)); delete bossList; - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been deleted for bossList in SelectStartingBossesForRound which comes from GetNewBossRoundProfileList.", bossList); - #endif } strcopy(g_RoundBossProfile, sizeof(g_RoundBossProfile), g_NewBossRoundProfileRoundProfile); @@ -8079,10 +7885,6 @@ static void SelectStartingBossesForRound() bossList.GetString(GetRandomInt(0, bossList.Length - 1), g_NewBossRoundProfileRoundProfile, sizeof(g_NewBossRoundProfileRoundProfile)); delete bossList; - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Clone array list %b has been deleted for bossList in SelectStartingBossesForRound which comes from GetNewBossRoundProfileList.", bossList); - #endif } strcopy(g_RoundBoxingBossProfile, sizeof(g_RoundBoxingBossProfile), g_NewBossRoundProfileRoundProfile); @@ -8249,7 +8051,7 @@ void InitializeNewGame() HandleSpecialRoundState(); // Was a new special round initialized? - if (g_IsSpecialRound && !SF_IsRenevantMap()) + if (g_IsSpecialRound && !SF_IsRenevantMap() && !SF_IsBoxingMap() && !SF_IsRaidMap()) { if (g_IsSpecialRoundNew) { @@ -8380,7 +8182,7 @@ void InitializeNewGame() { if (!SF_IsBoxingMap() && !SF_IsRenevantMap()) { - if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_DOOMBOX) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_2DOOM)) + if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_SILENTSLENDER) || SF_SpecialRound(SPECIALROUND_2DOUBLE)) { AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); @@ -8392,9 +8194,9 @@ void InitializeNewGame() AddProfile(g_RoundBossProfile, _, _, _, false); RemoveBossProfileFromQueueList(g_RoundBossProfile); } - else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_DOOMBOX) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_2DOOM) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) + else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_SILENTSLENDER) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) { - SelectProfile(view_as(0), g_RoundBossProfile); + AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); } } @@ -8413,7 +8215,10 @@ void InitializeNewGame() } #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 0) DebugMessage("END InitializeNewGame()"); + if (g_DebugDetailConVar.IntValue > 0) + { + DebugMessage("END InitializeNewGame()"); + } #endif } @@ -8530,7 +8335,8 @@ static Action Timer_IntroTextSequence(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i) || g_PlayerEliminated[i]) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsEliminated) { continue; } @@ -8541,10 +8347,10 @@ static Action Timer_IntroTextSequence(Handle timer) if (!g_RoundIntroTextDefault) { - char sTargetname[64]; - FormatEx(sTargetname, sizeof(sTargetname), "sf2_intro_text_%d", g_RoundIntroText); + char targetname[64]; + FormatEx(targetname, sizeof(targetname), "sf2_intro_text_%d", g_RoundIntroText); - int gameText = FindEntityByTargetname(sTargetname, "game_text"); + int gameText = FindEntityByTargetname(targetname, "game_text"); if (gameText && gameText != INVALID_ENT_REFERENCE) { foundGameText = true; @@ -8617,7 +8423,7 @@ static Action Timer_ActivateRoundFromIntro(Handle timer) { if (!SF_IsBoxingMap() && !SF_IsRenevantMap()) { - if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_DOOMBOX) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_2DOOM)) + if (SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) || SF_SpecialRound(SPECIALROUND_SILENTSLENDER) || SF_SpecialRound(SPECIALROUND_2DOUBLE)) { AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); @@ -8629,9 +8435,9 @@ static Action Timer_ActivateRoundFromIntro(Handle timer) AddProfile(g_RoundBossProfile, _, _, _, false); RemoveBossProfileFromQueueList(g_RoundBossProfile); } - else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_DOOMBOX) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_2DOOM) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) + else if (!SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_SILENTSLENDER) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_TRIPLEBOSSES)) { - SelectProfile(view_as(0), g_RoundBossProfile); + AddProfile(g_RoundBossProfile); RemoveBossProfileFromQueueList(g_RoundBossProfile); } } diff --git a/addons/sourcemod/scripting/sf2/adminmenu.sp b/addons/sourcemod/scripting/sf2/adminmenu.sp index 5f2f34c9..5f5cc430 100644 --- a/addons/sourcemod/scripting/sf2/adminmenu.sp +++ b/addons/sourcemod/scripting/sf2/adminmenu.sp @@ -7,9 +7,11 @@ static Handle g_TopMenu = null; static int g_PlayerAdminMenuTargetUserId[MAXTF2PLAYERS] = { -1, ... }; +static SF2NPC_Chaser g_SelectedBoss[MAXTF2PLAYERS]; void SetupAdminMenu() { + g_OnAdminMenuCreateOptionsPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); /* Account for late loading */ Handle topMenu = null; if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) @@ -18,8 +20,10 @@ void SetupAdminMenu() } } -public void OnAdminMenuReady(Handle topMenu) +public void OnAdminMenuReady(Handle topMenuHndl) { + TopMenu topMenu = TopMenu.FromHandle(topMenuHndl); + if (topMenu == g_TopMenu) { return; @@ -27,35 +31,46 @@ public void OnAdminMenuReady(Handle topMenu) g_TopMenu = topMenu; - TopMenuObject hServerCommands = FindTopMenuCategory(topMenu, ADMINMENU_SERVERCOMMANDS); - if (hServerCommands != INVALID_TOPMENUOBJECT) + TopMenuObject commands = topMenu.FindCategory("SF2Commands"); + if (commands == INVALID_TOPMENUOBJECT) { - AddToTopMenu(topMenu, "sf2_boss_admin_main", TopMenuObject_Item, AdminTopMenu_BossMain, hServerCommands, "sm_sf2_add_boss", ADMFLAG_SLAY); + commands = topMenu.AddCategory("SF2Commands", AdminTopMenu_Main); } - TopMenuObject hPlayerCommands = FindTopMenuCategory(topMenu, ADMINMENU_PLAYERCOMMANDS); - if (hPlayerCommands != INVALID_TOPMENUOBJECT) + if (commands != INVALID_TOPMENUOBJECT) + { + topMenu.AddItem("sf2_boss_admin_main", AdminTopMenu_BossMain, commands, "sm_sf2_add_boss", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_setplaystate", AdminTopMenu_PlayerSetPlayState, commands, "sm_sf2_setplaystate", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_force_proxy", AdminTopMenu_PlayerForceProxy, commands, "sm_sf2_force_proxy", ADMFLAG_SLAY); + } +} + +static void AdminTopMenu_Main(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if (action == TopMenuAction_DisplayTitle) + { + FormatEx(buffer, maxlength, "%T:", "SF2 Admin Menu Title", param); + } + else if (action == TopMenuAction_DisplayOption) { - AddToTopMenu(topMenu, "sf2_player_setplaystate", TopMenuObject_Item, AdminTopMenu_PlayerSetPlayState, hPlayerCommands, "sm_sf2_setplaystate", ADMFLAG_SLAY); - AddToTopMenu(topMenu, "sf2_player_force_proxy", TopMenuObject_Item, AdminTopMenu_PlayerForceProxy, hPlayerCommands, "sm_sf2_force_proxy", ADMFLAG_SLAY); + FormatEx(buffer, maxlength, "%T", "SF2 Admin Menu Title", param); } - delete topMenu; } static void DisplayPlayerForceProxyAdminMenu(int client) { - Handle menuHandle = CreateMenu(AdminMenu_PlayerForceProxy); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", client); + Menu menuHandle = new Menu(AdminMenu_PlayerForceProxy); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", client); AddTargetsToMenu(menuHandle, client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } -static int AdminTopMenu_PlayerForceProxy(Handle topmenu, TopMenuAction action, TopMenuObject object_id,int param, char[] buffer,int maxlength) +static int AdminTopMenu_PlayerForceProxy(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { - FormatEx(buffer, maxlength, "%t%T", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", param); + FormatEx(buffer, maxlength, "%t %T", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy", param); } else if (action == TopMenuAction_SelectOption) { @@ -64,7 +79,7 @@ static int AdminTopMenu_PlayerForceProxy(Handle topmenu, TopMenuAction action, T return 0; } -static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerForceProxy(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -80,7 +95,7 @@ static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1, else if (action == MenuAction_Select) { char userId[64]; - GetMenuItem(menu, param2, userId, sizeof(userId)); + menu.GetItem(param2, userId, sizeof(userId)); int client = GetClientOfUserId(StringToInt(userId)); if (client <= 0) @@ -95,7 +110,7 @@ static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1, char name[MAX_NAME_LENGTH]; FormatEx(name, sizeof(name), "%N", client); - Handle menuHandle = CreateMenu(AdminMenu_PlayerForceProxyBoss); + Menu menuHandle = new Menu(AdminMenu_PlayerForceProxyBoss); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -104,16 +119,16 @@ static int AdminMenu_PlayerForceProxy(Handle menu, MenuAction action,int param1, } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy Boss", param1, name); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Force Proxy Boss", param1, name); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int AdminMenu_PlayerForceProxyBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerForceProxyBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -136,7 +151,7 @@ static int AdminMenu_PlayerForceProxyBoss(Handle menu, MenuAction action,int par else { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -171,18 +186,18 @@ static int AdminMenu_PlayerForceProxyBoss(Handle menu, MenuAction action,int par static void DisplayPlayerSetPlayStateAdminMenu(int client) { - Handle menuHandle = CreateMenu(AdminMenu_PlayerSetPlayState); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", client); + Menu menuHandle = new Menu(AdminMenu_PlayerSetPlayState); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", client); AddTargetsToMenu(menuHandle, client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } -static int AdminTopMenu_PlayerSetPlayState(Handle topmenu, TopMenuAction action, TopMenuObject object_id,int param, char[] buffer,int maxlength) +static int AdminTopMenu_PlayerSetPlayState(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { - FormatEx(buffer, maxlength, "%t%T", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", param); + FormatEx(buffer, maxlength, "%t %T", "SF2 Prefix", "SF2 Admin Menu Player Set Play State", param); } else if (action == TopMenuAction_SelectOption) { @@ -191,7 +206,7 @@ static int AdminTopMenu_PlayerSetPlayState(Handle topmenu, TopMenuAction action, return 0; } -static int AdminMenu_PlayerSetPlayState(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerSetPlayState(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -207,7 +222,7 @@ static int AdminMenu_PlayerSetPlayState(Handle menu, MenuAction action,int param else if (action == MenuAction_Select) { char userId[64]; - GetMenuItem(menu, param2, userId, sizeof(userId)); + menu.GetItem(param2, userId, sizeof(userId)); int client = GetClientOfUserId(StringToInt(userId)); if (client <= 0) { @@ -219,21 +234,21 @@ static int AdminMenu_PlayerSetPlayState(Handle menu, MenuAction action,int param char name[MAX_NAME_LENGTH]; FormatEx(name, sizeof(name), "%N", client); - Handle menuHandle = CreateMenu(AdminMenu_PlayerSetPlayStateConfirm); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State Confirm", param1, name); + Menu menuHandle = new Menu(AdminMenu_PlayerSetPlayStateConfirm); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Player Set Play State Confirm", param1, name); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "SF2 In", param1); - AddMenuItem(menuHandle, userId, buffer); + menuHandle.AddItem(userId, buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Out", param1); - AddMenuItem(menuHandle, userId, buffer); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.AddItem(userId, buffer); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } return 0; } -static int AdminMenu_PlayerSetPlayStateConfirm(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_PlayerSetPlayStateConfirm(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -249,7 +264,7 @@ static int AdminMenu_PlayerSetPlayStateConfirm(Handle menu, MenuAction action,in else if (action == MenuAction_Select) { char userId[64]; - GetMenuItem(menu, param2, userId, sizeof(userId)); + menu.GetItem(param2, userId, sizeof(userId)); int client = GetClientOfUserId(StringToInt(userId)); if (client <= 0) { @@ -278,30 +293,36 @@ static int AdminMenu_PlayerSetPlayStateConfirm(Handle menu, MenuAction action,in static void DisplayBossMainAdminMenu(int client) { - Handle menuHandle = CreateMenu(AdminMenu_BossMain); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Main", client); + Menu menuHandle = new Menu(AdminMenu_BossMain); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Main", client); char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Add Boss", client); - AddMenuItem(menuHandle, "add_boss", buffer); + menuHandle.AddItem("add_boss", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Add Fake Boss", client); - AddMenuItem(menuHandle, "add_boss_fake", buffer); + menuHandle.AddItem("add_boss_fake", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Remove Boss", client); - AddMenuItem(menuHandle, "remove_boss", buffer); + menuHandle.AddItem("remove_boss", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Spawn Boss", client); - AddMenuItem(menuHandle, "spawn_boss", buffer); + menuHandle.AddItem("spawn_boss", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Boss Attack Waiters", client); - AddMenuItem(menuHandle, "boss_attack_waiters", buffer); + menuHandle.AddItem("boss_attack_waiters", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Boss Teleport", client); - AddMenuItem(menuHandle, "boss_no_teleport", buffer); + menuHandle.AddItem("boss_no_teleport", buffer); FormatEx(buffer, sizeof(buffer), "%T", "SF2 Admin Menu Override Boss", client); - AddMenuItem(menuHandle, "override_boss", buffer); - - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.AddItem("override_boss", buffer); + FormatEx(buffer, sizeof(buffer), "Make a boss wander to aimed position"); + menuHandle.AddItem("force_wander", buffer); + FormatEx(buffer, sizeof(buffer), "Make a boss alerted to aimed position"); + menuHandle.AddItem("force_alert", buffer); + FormatEx(buffer, sizeof(buffer), "Make a boss use a attack"); + menuHandle.AddItem("force_attack", buffer); + + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } -static int AdminMenu_BossMain(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossMain(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -317,7 +338,7 @@ static int AdminMenu_BossMain(Handle menu, MenuAction action,int param1,int para else if (action == MenuAction_Select) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); if (strcmp(info, "add_boss") == 0) { DisplayAddBossAdminMenu(param1); @@ -346,15 +367,27 @@ static int AdminMenu_BossMain(Handle menu, MenuAction action,int param1,int para { DisplayOverrideBossAdminMenu(param1); } + else if (strcmp(info, "force_wander") == 0) + { + DisplayBossWanderAdminMenu(param1); + } + else if (strcmp(info, "force_alert") == 0) + { + DisplayBossAlertAdminMenu(param1); + } + else if (strcmp(info, "force_attack") == 0) + { + DisplayBossAttackAdminMenu(param1); + } } return 0; } -static int AdminTopMenu_BossMain(Handle topmenu, TopMenuAction action, TopMenuObject object_id,int param, char[] buffer,int maxlength) +static int AdminTopMenu_BossMain(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if (action == TopMenuAction_DisplayOption) { - FormatEx(buffer, maxlength, "%t%T", "SF2 Prefix", "SF2 Admin Menu Boss Main", param); + FormatEx(buffer, maxlength, "%t %T", "SF2 Prefix", "SF2 Admin Menu Boss Main", param); } else if (action == TopMenuAction_SelectOption) { @@ -363,30 +396,42 @@ static int AdminTopMenu_BossMain(Handle topmenu, TopMenuAction action, TopMenuOb return 0; } +static void AddAllBossesToMenu(Menu menu) +{ + ArrayList bossList = GetBossProfileList(); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + char displayName[SF2_MAX_NAME_LENGTH]; + for (int i = 0; i < bossList.Length; i++) + { + bossList.GetString(i, profile, sizeof(profile)); + NPCGetBossName(_, displayName, sizeof(displayName), profile); + if (displayName[0] == '\0') + { + strcopy(displayName, sizeof(displayName), profile); + } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + continue; + } + menu.AddItem(profile, displayName); + } +} + static bool DisplayAddBossAdminMenu(int client) //Use for view boss list { ArrayList bossList = GetBossProfileList(); if (bossList != null) { - Handle menuHandle = CreateMenu(AdminMenu_AddBoss); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Boss", client); + Menu menuHandle = new Menu(AdminMenu_AddBoss); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Boss", client); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - char displayName[SF2_MAX_NAME_LENGTH]; + AddAllBossesToMenu(menuHandle); - for (int i = 0; i < bossList.Length; i++) - { - bossList.GetString(i, profile, sizeof(profile)); - NPCGetBossName(_, displayName, sizeof(displayName), profile); - if (displayName[0] == '\0') - { - strcopy(displayName, sizeof(displayName), profile); - } - AddMenuItem(menuHandle, profile, displayName); - } - SetMenuExitBackButton(menuHandle, true); + menuHandle.ExitBackButton = true; - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } @@ -395,7 +440,7 @@ static bool DisplayAddBossAdminMenu(int client) //Use for view boss list return false; } -static int AdminMenu_AddBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_AddBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -411,7 +456,7 @@ static int AdminMenu_AddBoss(Handle menu, MenuAction action,int param1,int param else if (action == MenuAction_Select) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - GetMenuItem(menu, param2, profile, sizeof(profile)); + menu.GetItem(param2, profile, sizeof(profile)); FakeClientCommand(param1, "sm_sf2_add_boss %s", profile); @@ -425,26 +470,14 @@ static bool DisplayAddFakeBossAdminMenu(int client) ArrayList bossList = GetBossProfileList(); if (bossList != null) { - Handle menuHandle = CreateMenu(AdminMenu_AddFakeBoss); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Fake Boss", client); + Menu menuHandle = new Menu(AdminMenu_AddFakeBoss); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Add Fake Boss", client); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - char displayName[SF2_MAX_NAME_LENGTH]; - - for (int i = 0; i < bossList.Length; i++) - { - bossList.GetString(i, profile, sizeof(profile)); - NPCGetBossName(_, displayName, sizeof(displayName), profile); - if (displayName[0] == '\0') - { - strcopy(displayName, sizeof(displayName), profile); - } - AddMenuItem(menuHandle, profile, displayName); - } + AddAllBossesToMenu(menuHandle); - SetMenuExitBackButton(menuHandle, true); + menuHandle.ExitBackButton = true; - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } @@ -453,7 +486,7 @@ static bool DisplayAddFakeBossAdminMenu(int client) return false; } -static int AdminMenu_AddFakeBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_AddFakeBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -469,7 +502,7 @@ static int AdminMenu_AddFakeBoss(Handle menu, MenuAction action,int param1,int p else if (action == MenuAction_Select) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - GetMenuItem(menu, param2, profile, sizeof(profile)); + menu.GetItem(param2, profile, sizeof(profile)); FakeClientCommand(param1, "sm_sf2_add_boss_fake %s", profile); @@ -478,7 +511,7 @@ static int AdminMenu_AddFakeBoss(Handle menu, MenuAction action,int param1,int p return 0; } -static int AddBossTargetsToMenu(Handle menuHandle) +static int AddBossTargetsToMenu(Menu menuHandle) { char buffer[512]; char display[512], info[64]; @@ -514,9 +547,17 @@ static int AddBossTargetsToMenu(Handle menuHandle) StrCat(display, sizeof(display), " (fake)"); } + SF2BossProfileData data; + data = NPCGetProfileData(i); + + if (data.IsPvEBoss) + { + continue; + } + FormatEx(info, sizeof(info), "%d", uniqueID); - AddMenuItem(menuHandle, info, display); + menuHandle.AddItem(info, display); count++; } @@ -527,7 +568,7 @@ static bool DisplayRemoveBossAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_RemoveBoss); + Menu menuHandle = new Menu(AdminMenu_RemoveBoss); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -535,9 +576,9 @@ static bool DisplayRemoveBossAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Remove Boss", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Remove Boss", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -546,7 +587,7 @@ static bool DisplayRemoveBossAdminMenu(int client) return false; } -static int AdminMenu_RemoveBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_RemoveBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -562,7 +603,7 @@ static int AdminMenu_RemoveBoss(Handle menu, MenuAction action,int param1,int pa else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -582,7 +623,7 @@ static bool DisplaySpawnBossAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_SpawnBoss); + Menu menuHandle = new Menu(AdminMenu_SpawnBoss); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -590,9 +631,9 @@ static bool DisplaySpawnBossAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Spawn Boss", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Spawn Boss", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -601,7 +642,7 @@ static bool DisplaySpawnBossAdminMenu(int client) return false; } -static int AdminMenu_SpawnBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_SpawnBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -617,7 +658,7 @@ static int AdminMenu_SpawnBoss(Handle menu, MenuAction action,int param1,int par else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -637,7 +678,7 @@ static bool DisplayBossAttackWaitersAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_BossAttackWaiters); + Menu menuHandle = new Menu(AdminMenu_BossAttackWaiters); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -645,9 +686,9 @@ static bool DisplayBossAttackWaitersAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -656,7 +697,7 @@ static bool DisplayBossAttackWaitersAdminMenu(int client) return false; } -static int AdminMenu_BossAttackWaiters(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossAttackWaiters(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -672,7 +713,7 @@ static int AdminMenu_BossAttackWaiters(Handle menu, MenuAction action,int param1 else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -691,21 +732,21 @@ static int AdminMenu_BossAttackWaiters(Handle menu, MenuAction action,int param1 strcopy(name, sizeof(name), profile); } - Handle menuHandle = CreateMenu(AdminMenu_BossAttackWaitersConfirm); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters Confirm", param1, name); + Menu menuHandle = new Menu(AdminMenu_BossAttackWaitersConfirm); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Attack Waiters Confirm", param1, name); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - AddMenuItem(menuHandle, id, buffer); + menuHandle.AddItem(id, buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - AddMenuItem(menuHandle, id, buffer); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.AddItem(id, buffer); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } return 0; } -static int AdminMenu_BossAttackWaitersConfirm(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossAttackWaitersConfirm(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -721,7 +762,7 @@ static int AdminMenu_BossAttackWaitersConfirm(Handle menu, MenuAction action,int else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -751,7 +792,7 @@ static bool DisplayBossTeleportAdminMenu(int client) { if (GetBossProfileList() != null) { - Handle menuHandle = CreateMenu(AdminMenu_BossTeleport); + Menu menuHandle = new Menu(AdminMenu_BossTeleport); if (!AddBossTargetsToMenu(menuHandle)) { delete menuHandle; @@ -759,9 +800,9 @@ static bool DisplayBossTeleportAdminMenu(int client) } else { - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport", client); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport", client); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } } @@ -770,7 +811,7 @@ static bool DisplayBossTeleportAdminMenu(int client) return false; } -static int AdminMenu_BossTeleport(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossTeleport(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -786,7 +827,7 @@ static int AdminMenu_BossTeleport(Handle menu, MenuAction action,int param1,int else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -805,21 +846,21 @@ static int AdminMenu_BossTeleport(Handle menu, MenuAction action,int param1,int strcopy(name, sizeof(name), profile); } - Handle menuHandle = CreateMenu(AdminMenu_BossTeleportConfirm); - SetMenuTitle(menuHandle, "%t%T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport Confirm", param1, name); + Menu menuHandle = new Menu(AdminMenu_BossTeleportConfirm); + menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Teleport Confirm", param1, name); char buffer[256]; FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - AddMenuItem(menuHandle, id, buffer); + menuHandle.AddItem(id, buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - AddMenuItem(menuHandle, id, buffer); - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, param1, MENU_TIME_FOREVER); + menuHandle.AddItem(id, buffer); + menuHandle.ExitBackButton = true; + menuHandle.Display(param1, MENU_TIME_FOREVER); } } return 0; } -static int AdminMenu_BossTeleportConfirm(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_BossTeleportConfirm(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -835,7 +876,7 @@ static int AdminMenu_BossTeleportConfirm(Handle menu, MenuAction action,int para else if (action == MenuAction_Select) { char id[64]; - GetMenuItem(menu, param2, id, sizeof(id)); + menu.GetItem(param2, id, sizeof(id)); int index = NPCGetFromUniqueID(StringToInt(id)); if (index == -1) { @@ -866,7 +907,7 @@ static bool DisplayOverrideBossAdminMenu(int client) ArrayList bossList = GetBossProfileList(); if (bossList != null) { - Handle menuHandle = CreateMenu(AdminMenu_OverrideBoss); + Menu menuHandle = new Menu(AdminMenu_OverrideBoss); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; char displayName[SF2_MAX_NAME_LENGTH]; @@ -879,10 +920,10 @@ static bool DisplayOverrideBossAdminMenu(int client) { strcopy(displayName, sizeof(displayName), profile); } - AddMenuItem(menuHandle, profile, displayName); + menuHandle.AddItem(profile, displayName); } - SetMenuExitBackButton(menuHandle, true); + menuHandle.ExitBackButton = true; char profileOverride[SF2_MAX_PROFILE_NAME_LENGTH], profileDisplayName[SF2_MAX_PROFILE_NAME_LENGTH]; g_BossProfileOverrideConVar.GetString(profileOverride, sizeof(profileOverride)); @@ -901,9 +942,9 @@ static bool DisplayOverrideBossAdminMenu(int client) strcopy(profileDisplayName, sizeof(profileDisplayName), "---"); } - SetMenuTitle(menuHandle, "%t%T\n%T\n \n", "SF2 Prefix", "SF2 Admin Menu Override Boss", client, "SF2 Admin Menu Current Boss Override", client, profileDisplayName); + menuHandle.SetTitle("%t %T\n%T\n \n", "SF2 Prefix", "SF2 Admin Menu Override Boss", client, "SF2 Admin Menu Current Boss Override", client, profileDisplayName); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.Display(client, MENU_TIME_FOREVER); return true; } @@ -912,7 +953,7 @@ static bool DisplayOverrideBossAdminMenu(int client) return false; } -static int AdminMenu_OverrideBoss(Handle menu, MenuAction action,int param1,int param2) +static int AdminMenu_OverrideBoss(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -928,7 +969,7 @@ static int AdminMenu_OverrideBoss(Handle menu, MenuAction action,int param1,int else if (action == MenuAction_Select) { char profile[SF2_MAX_PROFILE_NAME_LENGTH], name[SF2_MAX_NAME_LENGTH]; - GetMenuItem(menu, param2, profile, sizeof(profile)); + menu.GetItem(param2, profile, sizeof(profile)); g_BossProfileOverrideConVar.SetString(profile); @@ -941,4 +982,414 @@ static int AdminMenu_OverrideBoss(Handle menu, MenuAction action,int param1,int DisplayOverrideBossAdminMenu(param1); } return 0; -} \ No newline at end of file +} + +static bool DisplayBossWanderAdminMenu(int client) +{ + if (GetBossProfileList() != null) + { + Menu menuHandle = new Menu(AdminMenu_BossWanderToPos); + if (!AddBossTargetsToMenu(menuHandle)) + { + delete menuHandle; + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 No Active Bosses", client); + } + else + { + menuHandle.SetTitle("%t Make a boss wander to aimed position\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; + } + } + + DisplayBossMainAdminMenu(client); + return false; +} + +static int AdminMenu_BossWanderToPos(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + int index = NPCGetFromUniqueID(StringToInt(id)); + if (index == -1) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = NPCGetEntIndex(index); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(index); + switch (npc.Type) + { + case SF2BossType_Chaser: + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not idling!", "SF2 Prefix"); + } + else + { + SF2_BasePlayer admin = SF2_BasePlayer(param1); + float eyePos[3], eyeAng[3], endPos[3]; + admin.GetEyePosition(eyePos); + admin.GetEyeAngles(eyeAng); + + Handle trace = TR_TraceRayFilterEx(eyePos, eyeAng, MASK_NPCSOLID, RayType_Infinite, TraceRayDontHitEntity, admin.index); + TR_GetEndPosition(endPos, trace); + delete trace; + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 15, 0.5, 5.0, 0.0, ( { 128, 128, 128, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 10, 0.6, 10.0, 0.5, ( { 0, 255, 0, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + EmitSoundToClient(admin.index, g_DebugBeamSound); + + chaser.SetForceWanderPosition(endPos); + chaser.DebugShouldGoToPos = true; + } + } + case SF2BossType_Statue: + { + + } + } + } + } + + DisplayBossWanderAdminMenu(param1); + } + return 0; +} + +static bool DisplayBossAlertAdminMenu(int client) +{ + if (GetBossProfileList() != null) + { + Menu menuHandle = new Menu(AdminMenu_BossAlertToPos); + if (!AddBossTargetsToMenu(menuHandle)) + { + delete menuHandle; + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 No Active Bosses", client); + } + else + { + menuHandle.SetTitle("%t Make a boss alert to aimed position\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; + } + } + + DisplayBossMainAdminMenu(client); + return false; +} + +static int AdminMenu_BossAlertToPos(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + int index = NPCGetFromUniqueID(StringToInt(id)); + if (index == -1) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = NPCGetEntIndex(index); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(index); + switch (npc.Type) + { + case SF2BossType_Chaser: + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE && chaser.State != STATE_ALERT) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is busy right now!", "SF2 Prefix"); + } + else + { + SF2_BasePlayer admin = SF2_BasePlayer(param1); + float eyePos[3], eyeAng[3], endPos[3]; + admin.GetEyePosition(eyePos); + admin.GetEyeAngles(eyeAng); + + Handle trace = TR_TraceRayFilterEx(eyePos, eyeAng, MASK_NPCSOLID, RayType_Infinite, TraceRayDontHitEntity, admin.index); + TR_GetEndPosition(endPos, trace); + delete trace; + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 15, 0.5, 5.0, 0.0, ( { 128, 128, 128, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + TE_SetupBeamRingPoint(endPos, 10.0, 375.0, g_ShockwaveBeam, g_ShockwaveHalo, 0, 10, 0.6, 10.0, 0.5, ( { 255, 0, 0, 255 } ), 10, 0); + TE_SendToClient(admin.index); + + EmitSoundToClient(admin.index, g_DebugBeamSound); + + chaser.SetForceWanderPosition(endPos); + chaser.InterruptConditions |= COND_DEBUG; + } + } + case SF2BossType_Statue: + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not a chaser!", "SF2 Prefix"); + } + } + } + } + + DisplayBossAlertAdminMenu(param1); + } + return 0; +} + +static bool DisplayBossAttackAdminMenu(int client) +{ + if (GetBossProfileList() != null) + { + Menu menuHandle = new Menu(AdminMenu_BossAttack); + char buffer[512]; + char display[512], info[64]; + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + int count; + + for (int i = 0; i < MAX_BOSSES; i++) + { + int uniqueID = NPCGetUniqueID(i); + if (uniqueID == -1) + { + continue; + } + + if (NPCGetType(i) != SF2BossType_Chaser) + { + continue; + } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); + + SF2ChaserBossProfileData chaserData; + chaserData = controller.GetProfileData(); + if (chaserData.Attacks == null || chaserData.Attacks.Length == 0) + { + continue; + } + + controller.GetProfile(profile, sizeof(profile)); + controller.GetName(buffer, sizeof(buffer)); + if (buffer[0] == '\0') + { + strcopy(buffer, sizeof(buffer), profile); + } + + FormatEx(display, sizeof(display), "%d - %s", i, buffer); + if (g_SlenderCopyMaster[i] != -1) + { + FormatEx(buffer, sizeof(buffer), " (copy of boss %d)", g_SlenderCopyMaster[i]); + StrCat(display, sizeof(display), buffer); + } + + if (NPCGetFlags(i) & SFF_FAKE) + { + StrCat(display, sizeof(display), " (fake)"); + } + + SF2BossProfileData data; + data = NPCGetProfileData(i); + + if (data.IsPvEBoss) + { + StrCat(display, sizeof(display), " (PvE)"); + } + + FormatEx(info, sizeof(info), "%d", i); + + menuHandle.AddItem(info, display); + count++; + } + + if (count <= 0) + { + delete menuHandle; + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 No Active Bosses", client); + } + else + { + menuHandle.SetTitle("%t Make a boss use a attack\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; + } + } + + DisplayBossMainAdminMenu(client); + return false; +} + +static int AdminMenu_BossAttack(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + int index = StringToInt(id); + if (index == -1) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = NPCGetEntIndex(index); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE && chaser.State != STATE_ALERT && chaser.State != STATE_CHASE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is busy right now!", "SF2 Prefix"); + } + else + { + g_SelectedBoss[param1] = SF2NPC_Chaser(index); + DisplayBossAttackListAdminMenu(param1); + return 0; + } + } + } + + DisplayBossAttackAdminMenu(param1); + } + return 0; +} + +static bool DisplayBossAttackListAdminMenu(int client) +{ + if (!g_SelectedBoss[client].IsValid()) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", client); + DisplayBossMainAdminMenu(client); + return false; + } + + SF2ChaserBossProfileData data; + data = g_SelectedBoss[client].GetProfileData(); + Menu menuHandle = new Menu(AdminMenu_BossAttackList); + for (int i = 0; i < data.Attacks.Length; i++) + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(i, attackData); + menuHandle.AddItem(attackData.Name, attackData.Name); + } + + menuHandle.SetTitle("%t Make a boss use a attack\n \n", "SF2 Prefix"); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); + return true; +} + +static int AdminMenu_BossAttackList(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + delete menu; + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossAttackAdminMenu(param1); + } + } + else if (action == MenuAction_Select) + { + char id[64]; + menu.GetItem(param2, id, sizeof(id)); + SF2NPC_Chaser npc = g_SelectedBoss[param1]; + if (!npc.IsValid()) + { + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Does Not Exist", param1); + } + else + { + int entity = npc.EntIndex; + if (!entity || entity == INVALID_ENT_REFERENCE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is not on the map!", "SF2 Prefix"); + } + else + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (chaser.State != STATE_IDLE && chaser.State != STATE_ALERT && chaser.State != STATE_CHASE) + { + CPrintToChat(param1, "{royalblue}%t {default}That boss is busy right now!", "SF2 Prefix"); + } + else + { + char formatter[128]; + FormatEx(formatter, sizeof(formatter), "debug attack %s", id); + chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString(formatter); + } + DisplayBossAttackListAdminMenu(param1); + return 0; + } + } + + DisplayBossAttackAdminMenu(param1); + } + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/anticamping.sp b/addons/sourcemod/scripting/sf2/anticamping.sp index 25dda4d2..be5cc650 100644 --- a/addons/sourcemod/scripting/sf2/anticamping.sp +++ b/addons/sourcemod/scripting/sf2/anticamping.sp @@ -169,7 +169,7 @@ static Action Timer_ClientCheckCamp(Handle timer, any userid) } float slenderPos[3]; - SlenderGetAbsOrigin(i, slenderPos); + CBaseEntity(slender).GetAbsOrigin(slenderPos); float dist = GetVectorSquareMagnitude(slenderPos, pos); if (dist < distFromClosestBoss) diff --git a/addons/sourcemod/scripting/sf2/changelog.sp b/addons/sourcemod/scripting/sf2/changelog.sp new file mode 100644 index 00000000..6617cbc0 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/changelog.sp @@ -0,0 +1,132 @@ +#pragma semicolon 1 + +static Menu g_ChangelogUpdate; +static KeyValues g_ChangelogConfig; + +#define FILE_CHANGELOG "configs/sf2/changelog.cfg" +#define FILE_CHANGELOG_DATA "data/sf2/changelog.cfg" + +static bool g_SeeUpdateMenu[MAXTF2PLAYERS] = { false, ... }; + +void InitializeChangelog() +{ + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + + RegConsoleCmd("sm_slupdate", Command_Update); + RegAdminCmd("sm_sf2_reloadchangelog", Command_ReloadChangelog, ADMFLAG_SLAY); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (client.Team > 1 && !g_SeeUpdateMenu[client.index]) + { + g_SeeUpdateMenu[client.index] = true; + g_ChangelogUpdate.Display(client.index, 30); + } +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + g_SeeUpdateMenu[client.index] = false; +} + +static void OnGamemodeStart() +{ + CreateChangelog(); +} + +static Action Command_Update(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + g_ChangelogUpdate.Display(client, 30); + return Plugin_Handled; +} + +static Action Command_ReloadChangelog(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + CreateChangelog(); + PrintToChat(client, "Reloaded the changelog."); + return Plugin_Handled; +} + +static void CreateChangelog() +{ + if (g_ChangelogConfig != null) + { + delete g_ChangelogConfig; + } + + char buffer[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, buffer, sizeof(buffer), g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_CHANGELOG_DATA : FILE_CHANGELOG); + g_ChangelogConfig = new KeyValues("root"); + if (!g_ChangelogConfig.ImportFromFile(buffer)) + { + LogSF2Message("Failed to load changelog! File not found!"); + delete g_ChangelogConfig; + return; + } + + char list[1024]; + g_ChangelogUpdate = new Menu(Menu_Update); + FormatEx(list, sizeof(list), "%tSlender Fortress\n \n", "SF2 Prefix"); + StrCat(list, sizeof(list), "Coders: KitRifty, Kenzzer, Mentrillum, The Gaben\n"); + StrCat(list, sizeof(list), "Version: "); + StrCat(list, sizeof(list), PLUGIN_VERSION); + StrCat(list, sizeof(list), "\n \n"); + Format(list, sizeof(list), "%s%t\n", list, "SF2 Recent Changes"); + StrCat(list, sizeof(list), "\n \n"); + + g_ChangelogUpdate.SetTitle(list); + g_ChangelogConfig.Rewind(); + char section[5], name[64], message[64]; + if (g_ChangelogConfig.GotoFirstSubKey()) + { + do + { + g_ChangelogConfig.GetString("name", name, sizeof(name)); + FormatEx(message, sizeof(message), " ----- %s ----- ", name); + g_ChangelogUpdate.AddItem("", message, ITEMDRAW_DISABLED); + + int count = 1; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + g_ChangelogConfig.GetString(section, message, sizeof(message)); + if (message[0] == '\0') + { + break; + } + + g_ChangelogUpdate.AddItem("", message, ITEMDRAW_DISABLED); + count++; + } + while (count % 7 != 0) + { + g_ChangelogUpdate.AddItem("", " ", ITEMDRAW_SPACER); + count++; + } + } + while (g_ChangelogConfig.GotoNextKey()); + + g_ChangelogUpdate.AddItem("", " ", ITEMDRAW_SPACER); + g_ChangelogUpdate.AddItem("0", "Display Main Menu"); + } +} + +static int Menu_Update(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select && param2 == 0) + { + g_MenuMain.Display(param1, 30); + } + return 0; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/classconfigs.sp b/addons/sourcemod/scripting/sf2/classconfigs.sp index 3ab74d8b..4702f847 100644 --- a/addons/sourcemod/scripting/sf2/classconfigs.sp +++ b/addons/sourcemod/scripting/sf2/classconfigs.sp @@ -245,7 +245,7 @@ static void PrecacheClassProfiles() g_ClassSprintPointLossJumping[i] = GetClassStatNum(className, "sprint_loss_while_jumping", 7); g_ClassProxyDamageVulnerability[i] = GetClassStatFloat(className, "proxy_damage_vulnerability"); - g_ClassCanPickUpHealth[i] = !!GetClassStatNum(className, "can_pickup_health", 1); + g_ClassCanPickUpHealth[i] = GetClassStatNum(className, "can_pickup_health", 1) != 0; g_ClassHealthPickupMultiplier[i] = GetClassStatFloat(className, "health_pickup_multiplier", 1.0); g_ClassBossPriorityMultiplier[i] = GetClassStatFloat(className, "boss_priority"); @@ -271,8 +271,8 @@ static void PrecacheClassProfiles() g_ClassFlashlightRechargeRate[i] = GetClassStatFloat(className, "flashlight_recharge_rate", 1.0); g_ClassFlashlightSoundRadius[i] = GetClassStatFloat(className, "flashlight_sound_radius", 0.5); - g_ClassBlockedOnThanatophobia[i] = !!GetClassStatNum(className, "blocked_on_thanatophobia"); + g_ClassBlockedOnThanatophobia[i] = GetClassStatNum(className, "blocked_on_thanatophobia") != 0; - g_ClassInvulnerableToTraps[i] = !!GetClassStatNum(className, "immune_to_traps"); + g_ClassInvulnerableToTraps[i] = GetClassStatNum(className, "immune_to_traps") != 0; } } diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index 431ca3c3..ffe4b971 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -45,7 +45,6 @@ int g_ClientFrame[MAXTF2PLAYERS]; #include "sf2/client/ghostmode.sp" #include "sf2/client/music.sp" #include "sf2/client/proxy.sp" -#include "sf2/methodmaps.sp" // ========================================================== // GENERAL CLIENT HOOK FUNCTIONS @@ -282,12 +281,6 @@ void ClientEscape(int client) { continue; } - if (EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderInterruptConditions[npcIndex] |= COND_CHASETARGETINVALIDATED; - GetClientAbsOrigin(client, g_SlenderChaseDeathPosition[npcIndex]); - g_BossPathFollower[npcIndex].Invalidate(); - } if (g_NpcChaseOnLookTarget[npcIndex] == null) { continue; @@ -297,16 +290,6 @@ void ClientEscape(int client) { g_NpcChaseOnLookTarget[npcIndex].Erase(foundClient); } - switch (NPCGetType(npcIndex)) - { - case SF2BossType_Chaser: - { - if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderGiveUp[npcIndex] = true; - } - } - } } // Speed recalculation. Props to the creators of FF2/VSH for this snippet. @@ -322,11 +305,6 @@ void ClientEscape(int client) CPrintToChatAll("%t", "SF2 Player Escaped", name); } - if (SF_IsRenevantMap() && g_RenevantMarkForDeath) - { - TF2_RemoveCondition(client, TFCond_MarkedForDeathSilent); - } - CheckRoundWinConditions(); Call_StartForward(g_OnClientEscapeFwd); @@ -482,7 +460,7 @@ void ClientProcessVisibility(int client) if (boss && boss != INVALID_ENT_REFERENCE) { - SlenderGetAbsOrigin(i, slenderPos); + CBaseEntity(boss).GetAbsOrigin(slenderPos); NPCGetEyePosition(i, slenderEyePos); float slenderMins[3], slenderMaxs[3]; @@ -574,7 +552,6 @@ void ClientProcessVisibility(int client) if (GetGameTime() >= g_PlayerScareNextTime[client][master]) { - g_PlayerScaredByBoss[client][master] = false; if (GetVectorSquareMagnitude(myPos, slenderPos) <= SquareFloat(NPCGetScareRadius(i))) { ClientPerformScare(client, master); @@ -608,7 +585,7 @@ void ClientProcessVisibility(int client) if (NPCGetScareReplenishSprintState(master)) { int clientSprintPoints = ClientGetSprintPoints(client); - g_PlayerSprintPoints[client] = clientSprintPoints + NPCGetScareReplenishSprintAmount(master); + ClientSetSprintPoints(client, clientSprintPoints + NPCGetScareReplenishSprintAmount(master)); } float value = NPCGetAttributeValue(master, SF2Attribute_IgnitePlayerOnScare); @@ -628,37 +605,10 @@ void ClientProcessVisibility(int client) } if (NPCHasAttribute(master, SF2Attribute_ChaseTargetOnScare)) { - if (g_SlenderState[i] != STATE_CHASE && g_SlenderState[i] != STATE_ATTACK && g_SlenderState[i] != STATE_STUN) + SF2_ChaserEntity chaser = SF2_ChaserEntity(boss); + if (chaser.IsValid() && chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) { - int slender = NPCGetEntIndex(i); - g_NpcPlayerScareVictin[i] = EntIndexToEntRef(client); - g_SlenderState[i] = STATE_CHASE; - GetClientAbsOrigin(client, g_SlenderGoalPos[i]); - g_SlenderTarget[i] = EntIndexToEntRef(client); - g_SlenderTimeUntilNoPersistence[i] = GetGameTime() + NPCChaserGetChaseDuration(i, difficulty); - g_SlenderTimeUntilAlert[i] = GetGameTime() + NPCChaserGetChaseDuration(i, difficulty); - SlenderPerformVoice(i, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(i) && !g_NpcUsesChaseInitialAnimation[i] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[i] == null) - { - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - g_NpcUsesChaseInitialAnimation[i] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - NPCChaserUpdateBossAnimation(i, slender, g_SlenderState[i]); - g_SlenderChaseInitialTimer[i] = CreateTimer(g_SlenderAnimationDuration[i], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - } - } - else - { - if (i != -1 && slender && slender != INVALID_ENT_REFERENCE) - { - NPCChaserUpdateBossAnimation(i, slender, g_SlenderState[i]); - } - } - g_PlayerScaredByBoss[client][i] = true; - SlenderAlertAllValidBosses(i, client, client); + SF2_BasePlayer(client).SetForceChaseState(SF2NPC_BaseNPC(i), true); } } if (NPCGetJumpscareOnScare(master)) @@ -682,6 +632,11 @@ void ClientProcessVisibility(int client) } } + Call_StartForward(g_OnPlayerLookAtBossPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(SF2NPC_BaseNPC(i)); + Call_Finish(); + Call_StartForward(g_OnClientLooksAtBossFwd); Call_PushCell(client); Call_PushCell(i); @@ -1014,33 +969,48 @@ Action Timer_ClientPageDetector(Handle timer, int userid) return Plugin_Stop; } + CNavArea area = CBaseCombatCharacter(client).GetLastKnownArea(); + if (area == NULL_AREA) + { + g_ClientSpecialRoundTimer[client] = CreateTimer(0.1, Timer_ClientPageDetector, userid, TIMER_FLAG_NO_MAPCHANGE); + return Plugin_Stop; + } + int closestPageEntIndex = -1; - float distance = SquareFloat(99999.0); + float bestCost = 9999999.9; float clientPos[3], pagePos[3]; GetClientAbsOrigin(client, clientPos); ArrayList pageEntities = new ArrayList(); GetPageEntities(pageEntities); + CNavArea goalArea; for (int i = 0; i < pageEntities.Length; i++) { CBaseEntity pageEnt = CBaseEntity(pageEntities.Get(i)); pageEnt.GetAbsOrigin(pagePos); + goalArea = TheNavMesh.GetNearestNavArea(pagePos); + TheNavMesh.BuildPath(area, goalArea, pagePos, _, _, 5000.0); - float squareDistance = GetVectorSquareMagnitude(clientPos, pagePos); + float cost = goalArea.GetCostSoFar(); - if (closestPageEntIndex == -1 || squareDistance < distance) + if (closestPageEntIndex == -1 || cost < bestCost) { closestPageEntIndex = pageEnt.index; - distance = squareDistance; + bestCost = cost; } } delete pageEntities; - float nextBeepTime = distance/SquareFloat(800.0); + float nextBeepTime = bestCost / 1200.0; + if (nextBeepTime == 0.0) + { + g_ClientSpecialRoundTimer[client] = CreateTimer(0.1, Timer_ClientPageDetector, userid, TIMER_FLAG_NO_MAPCHANGE); + return Plugin_Stop; + } if (nextBeepTime > 5.0) { nextBeepTime = 5.0; @@ -1050,7 +1020,7 @@ Action Timer_ClientPageDetector(Handle timer, int userid) nextBeepTime = 0.1; } - EmitSoundToClient(client, PAGE_DETECTOR_BEEP, _, _, _, _, _, 100 - RoundToNearest(nextBeepTime * 10.0)); + EmitSoundToClient(client, PAGE_DETECTOR_BEEP, _, _, _, _, 0.7, 100 - RoundToNearest(nextBeepTime * 10.0)); g_ClientSpecialRoundTimer[client] = CreateTimer(nextBeepTime, Timer_ClientPageDetector, userid, TIMER_FLAG_NO_MAPCHANGE); return Plugin_Stop; } @@ -1217,7 +1187,7 @@ static bool ClientCreateInteractiveGlow(int client,int entity, const char[] atta return false; } -static Action Hook_InterativeGlowSetTransmit(int ent,int other) +static Action Hook_InterativeGlowSetTransmit(int ent, int other) { if (!g_Enabled) { @@ -1421,10 +1391,7 @@ void ClientPerformScare(int client,int bossIndex) { soundInfo.EmitSound(true, client); - if (NPCGetFlags(bossIndex) & SFF_HASSIGHTSOUNDS) - { - g_PlayerSightSoundNextTime[client][bossIndex] = GetGameTime() + GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); - } + g_PlayerSightSoundNextTime[client][bossIndex] = GetGameTime() + GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); if (g_PlayerStressAmount[client] > 0.4) { @@ -1461,11 +1428,6 @@ static void ClientPerformSightSound(int client,int bossIndex) return; } - if (!(NPCGetFlags(bossIndex) & SFF_HASSIGHTSOUNDS)) - { - return; - } - int master = NPCGetFromUniqueID(g_SlenderCopyMaster[bossIndex]); if (master == -1) { @@ -1630,7 +1592,7 @@ Action Timer_PlayerOverlayCheck(Handle timer, any userid) // MISC FUNCTIONS // ========================================================== -void ClientUpdateListeningFlags(int client, bool reset=false) +void ClientUpdateListeningFlags(int client, bool reset = false) { if (!IsValidClient(client)) { @@ -1809,7 +1771,6 @@ void ClientResetSlenderStats(int client) g_PlayerSeesSlender[client][i] = false; g_PlayerSeesSlenderLastTime[client][i] = -1.0; g_PlayerSightSoundNextTime[client][i] = -1.0; - g_PlayerScaredByBoss[client][i] = false; } #if defined DEBUG @@ -1840,7 +1801,8 @@ void ClientSaveCookies(int client) // Save and reset our queue points. char s[512]; - FormatEx(s, sizeof(s), "%d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d", g_PlayerQueuePoints[client], + FormatEx(s, sizeof(s), "%d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d ; %d", + g_PlayerQueuePoints[client], g_PlayerPreferences[client].PlayerPreference_PvPAutoSpawn, g_PlayerPreferences[client].PlayerPreference_ShowHints, g_PlayerPreferences[client].PlayerPreference_MuteMode, @@ -1853,7 +1815,8 @@ void ClientSaveCookies(int client) g_PlayerPreferences[client].PlayerPreference_GhostModeToggleState, g_PlayerPreferences[client].PlayerPreference_GroupOutline, g_PlayerPreferences[client].PlayerPreference_GhostModeTeleportState, - g_PlayerPreferences[client].PlayerPreference_LegacyHud); + g_PlayerPreferences[client].PlayerPreference_LegacyHud, + RoundToNearest(g_PlayerPreferences[client].PlayerPreference_MusicVolume * 100)); SetClientCookie(client, g_Cookie, s); } @@ -1954,7 +1917,7 @@ void ClientSetFOV(int client,int fov) SetEntData(client, g_PlayerDefaultFOVOffset, fov); } -void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) +void TF2_GetClassName(TFClassType class, char[] buffer, int bufferLen, bool alt = false) { switch (class) { @@ -1976,7 +1939,7 @@ void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) } case TFClass_Heavy: { - strcopy(buffer, bufferLen, "heavyweapons"); + strcopy(buffer, bufferLen, !alt ? "heavyweapons" : "heavy"); } case TFClass_Medic: { @@ -2001,7 +1964,7 @@ void TF2_GetClassName(TFClassType class, char[] buffer,int bufferLen) } } -bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV = true, bool checkBlink = false) +bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true, bool ignoreFog = false) { for (int i = 1; i <= MaxClients; i++) { @@ -2009,7 +1972,7 @@ bool IsPointVisibleToAPlayer(const float pos[3], bool checkFOV = true, bool chec { continue; } - if (IsPointVisibleToPlayer(i, pos, checkFOV, checkBlink)) + if (IsPointVisibleToPlayer(i, pos, checkFOV, checkBlink, checkEliminated, ignoreFog)) { return true; } @@ -2030,7 +1993,7 @@ bool IsPointVisibleToPlayer(int client, const float pos[3], bool checkFOV = true return false; } - if (checkBlink && IsClientBlinking(client)) + if (checkBlink && IsClientBlinking(client) && !g_PlayerEliminated[client]) { return false; } diff --git a/addons/sourcemod/scripting/sf2/client/blink.sp b/addons/sourcemod/scripting/sf2/client/blink.sp index a7ca01ac..6a7f72cd 100644 --- a/addons/sourcemod/scripting/sf2/client/blink.sp +++ b/addons/sourcemod/scripting/sf2/client/blink.sp @@ -2,6 +2,8 @@ // Blink data. static Handle g_PlayerBlinkTimer[MAXTF2PLAYERS] = { null, ... }; +static bool g_StartBlinking[MAXTF2PLAYERS] = { false, ... }; +static float g_TimeUntilStopBlinking[MAXTF2PLAYERS] = { -1.0, ... }; static bool g_PlayerBlink[MAXTF2PLAYERS] = { false, ... }; bool g_PlayerHoldingBlink[MAXTF2PLAYERS] = { false, ... }; static float g_PlayerBlinkMeter[MAXTF2PLAYERS] = { 0.0, ... }; @@ -14,20 +16,22 @@ void SetupBlink() g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerLookAtBossPFwd.AddFunction(null, OnPlayerLookAtBoss); } static void OnPutInServer(SF2_BasePlayer client) { ClientResetBlink(client.index); + + if (!client.IsSourceTV) + { + SDKHook(client.index, SDKHook_PreThinkPost, BlinkThink); + } } static void OnPlayerSpawn(SF2_BasePlayer client) { ClientResetBlink(client.index); - if (!client.IsEliminated) - { - ClientStartDrainingBlinkMeter(client.index); - } } static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) @@ -43,6 +47,15 @@ static void OnPlayerEscape(SF2_BasePlayer client) ClientResetBlink(client.index); } +static void OnPlayerLookAtBoss(SF2_BasePlayer client, SF2NPC_BaseNPC boss) +{ + if (boss.IsAffectedBySight()) + { + g_StartBlinking[client.index] = true; + g_TimeUntilStopBlinking[client.index] = GetGameTime() + 25.0; + } +} + bool IsClientBlinking(int client) { return g_PlayerBlink[client]; @@ -73,12 +86,19 @@ int ClientGetBlinkCount(int client) return g_PlayerBlinkCount[client]; } +bool ClientHasStartedBlinking(int client) +{ + return g_StartBlinking[client]; +} + /** * Resets all data on blinking. */ void ClientResetBlink(int client) { g_PlayerBlinkTimer[client] = null; + g_StartBlinking[client] = false; + g_TimeUntilStopBlinking[client] = -1.0; g_PlayerBlink[client] = false; g_PlayerHoldingBlink[client] = false; g_TimeUntilUnblink[client] = 0.0; @@ -134,45 +154,59 @@ static void ClientUnblink(int client) g_PlayerBlinkMeter[client] = 1.0; } -void ClientStartDrainingBlinkMeter(int client) +static void BlinkThink(int client) { - g_PlayerBlinkTimer[client] = CreateTimer(ClientGetBlinkRate(client), Timer_BlinkTimer, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} + if (IsRoundInWarmup() || IsRoundInIntro()) + { + return; + } -static Action Timer_BlinkTimer(Handle timer, any userid) -{ - if (IsRoundInWarmup()) + if (IsInfiniteBlinkEnabled()) { - return Plugin_Stop; + return; } - int client = GetClientOfUserId(userid); - if (client <= 0) + if (!g_StartBlinking[client]) { - return Plugin_Stop; + return; } - if (timer != g_PlayerBlinkTimer[client]) + SF2_BasePlayer player = SF2_BasePlayer(client); + + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) { - return Plugin_Stop; + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(bossIndex); + if (!controller.IsValid()) + { + continue; + } + + if (controller.IsAffectedBySight() && player.IsLookingAtBoss(controller)) + { + g_TimeUntilStopBlinking[player.index] = GetGameTime() + 25.0; + } } - if (IsPlayerAlive(client) && !IsClientInDeathCam(client) && !g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !IsRoundEnding()) + if (g_TimeUntilStopBlinking[player.index] < GetGameTime()) + { + g_StartBlinking[player.index] = false; + g_TimeUntilStopBlinking[player.index] = -1.0; + g_PlayerBlinkMeter[player.index] = 1.0; + } + + if (player.IsAlive && !player.IsInDeathCam && !player.IsEliminated && !player.IsInGhostMode && !IsRoundEnding()) { int override = g_PlayerInfiniteBlinkOverrideConVar.IntValue; if ((!g_RoundInfiniteBlink && override != 1) || override == 0) { - g_PlayerBlinkMeter[client] -= 0.05; + g_PlayerBlinkMeter[player.index] -= (0.15 * ClientGetBlinkRate(player.index) * GetGameFrameTime()); } - if (g_PlayerBlinkMeter[client] <= 0.0) + if (g_PlayerBlinkMeter[player.index] <= 0.0) { - ClientBlink(client); - return Plugin_Stop; + player.Blink(); } } - - return Plugin_Continue; } static Action Timer_TryUnblink(Handle timer, any userid) @@ -198,7 +232,6 @@ static Action Timer_TryUnblink(Handle timer, any userid) } ClientUnblink(client); - ClientStartDrainingBlinkMeter(client); return Plugin_Stop; } diff --git a/addons/sourcemod/scripting/sf2/client/breathing.sp b/addons/sourcemod/scripting/sf2/client/breathing.sp index 78a0adda..63fdbe3f 100644 --- a/addons/sourcemod/scripting/sf2/client/breathing.sp +++ b/addons/sourcemod/scripting/sf2/client/breathing.sp @@ -56,7 +56,7 @@ static float ClientCalculateBreathingCooldown(int client) int averageNum = 0; // Sprinting only, for now. - average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow((float(g_PlayerSprintPoints[client]) / 100.0), 1.65)); + average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow((float(ClientGetSprintPoints(client)) / 100.0), 1.65)); averageNum++; average /= float(averageNum); diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index 6a2ee4f6..c50dd2e9 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -3,15 +3,17 @@ // Deathcam data. int g_PlayerDeathCamBoss[MAXTF2PLAYERS] = { -1, ... }; static bool g_PlayerDeathCam[MAXTF2PLAYERS] = { false, ... }; +static float g_PlayerDeathCamTimer[MAXTF2PLAYERS]; +static bool g_PlayerDeathCamMustDoOverlay[MAXTF2PLAYERS]; bool g_PlayerDeathCamShowOverlay[MAXTF2PLAYERS] = { false, ... }; int g_PlayerDeathCamEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; static int g_PlayerDeathCamEnt2[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; static int g_PlayerDeathCamTarget[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static Handle g_PlayerDeathCamTimer[MAXTF2PLAYERS] = { null, ... }; bool g_CameraInDeathCamAdvanced[2049] = { false, ... }; float g_CameraPlayerOffsetBackward[2049] = { 0.0, ... }; float g_CameraPlayerOffsetDownward[2049] = { 0.0, ... }; static float g_PlayerOriginalDeathcamPosition[MAXTF2PLAYERS][3]; +static bool g_Hooked = false; void SetupDeathCams() { @@ -84,7 +86,7 @@ static void ClientResetDeathCam(int client) g_PlayerDeathCamBoss[client] = -1; g_PlayerDeathCam[client] = false; g_PlayerDeathCamShowOverlay[client] = false; - g_PlayerDeathCamTimer[client] = null; + g_PlayerDeathCamTimer[client] = 0.0; int ent = EntRefToEntIndex(g_PlayerDeathCamEnt[client]); if (ent && ent != INVALID_ENT_REFERENCE) @@ -97,7 +99,10 @@ static void ClientResetDeathCam(int client) ent = EntRefToEntIndex(g_PlayerDeathCamEnt2[client]); if (ent && ent != INVALID_ENT_REFERENCE) { - AcceptEntityInput(ent, "Kill"); + if (!SF2_BaseBoss(ent).IsValid() && !SF2_ChaserEntity(ent).IsValid() && !SF2_StatueEntity(ent).IsValid()) + { + RemoveEntity(ent); + } } ent = EntRefToEntIndex(g_PlayerDeathCamTarget[client]); @@ -115,6 +120,27 @@ static void ClientResetDeathCam(int client) SetClientViewEntity(client, client); } + SF2BossProfileData data; + data = NPCGetProfileData(deathCamBoss); + if (data.DeathCamData.Enabled && data.DeathCamData.Blackout) + { + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + CreateTimer(0.1, Timer_DeleteRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + TeleportEntity(client, {16000.0, 16000.0, 16000.0}); + + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.DeathCamData.ExecutionSounds; + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + for (int i = 0; i < soundInfo.Paths.Length; i++) + { + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER, _, _, _, i); + } + } + + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + } + Call_StartForward(g_OnClientEndDeathCamFwd); Call_PushCell(client); Call_PushCell(deathCamBoss); @@ -128,35 +154,56 @@ static void ClientResetDeathCam(int client) #endif } -void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool antiCamp = false) +static Action NoPlayerVoiceHook(int clients[64], int& numClients, char sample[PLATFORM_MAX_PATH], int& client, int& channel, float& volume, int& level, int& pitch, int& flags) { - if (IsClientInDeathCam(client)) + if (!IsValidClient(client)) { - return; + return Plugin_Continue; } - if (!NPCIsValid(bossIndex)) + + if (channel == SNDCHAN_VOICE) { - return; + RemoveNormalSoundHook(NoPlayerVoiceHook); + g_Hooked = false; + return Plugin_Stop; } - for (int npcIndex; npcIndex < MAX_BOSSES; npcIndex++) + return Plugin_Continue; +} + +static Action Timer_DeleteRagdoll(Handle timer, int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(GetClientOfUserId(client)); + if (!player.IsValid) + { + return Plugin_Continue; + } + + int ragdoll = -1; + while ((ragdoll = FindEntityByClassname(ragdoll, "tf_ragdoll")) != -1) { - if (NPCGetUniqueID(npcIndex) == -1) + if (GetEntPropEnt(ragdoll, Prop_Send, "m_hPlayer") == player.index) { - continue; - } - switch (NPCGetType(npcIndex)) - { - case SF2BossType_Chaser: - { - if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderGiveUp[npcIndex] = true; - } - } + RemoveEntity(ragdoll); + break; } } + return Plugin_Continue; +} + +void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false) +{ + if (IsClientInDeathCam(client)) + { + return; + } + if (!NPCIsValid(bossIndex)) + { + return; + } + int difficulty = GetLocalGlobalDifficulty(bossIndex); + GetClientAbsOrigin(client, g_PlayerOriginalDeathcamPosition[client]); char buffer[PLATFORM_MAX_PATH]; @@ -164,17 +211,20 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); + SF2BossProfileSoundInfo soundInfo; if (g_SlenderDeathCamScareSound[bossIndex]) { - GetBossProfileScareSounds(profile, soundInfo); - soundInfo.EmitSound(true, client); + soundInfo = data.ScareSounds; + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER); } - GetBossProfileClientDeathCamSounds(profile, soundInfo); - soundInfo.EmitSound(true, client); + soundInfo = data.ClientDeathCamSounds; + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER); - GetBossProfileGlobalDeathCamSounds(profile, soundInfo); + soundInfo = data.GlobalDeathCamSounds; for (int i = 0; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -190,23 +240,17 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool Call_PushCell(bossIndex); Call_Finish(); - if (!NPCHasDeathCamEnabled(bossIndex) && !(NPCGetFlags(bossIndex) & SFF_FAKE)) + if ((!NPCHasDeathCamEnabled(bossIndex) || antiCamp) && !(NPCGetFlags(bossIndex) & SFF_FAKE)) { SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. - float value = NPCGetAttributeValue(bossIndex, SF2Attribute_IgnitePlayerOnDeath); - if (value > 0.0) + if (NPCHasAttribute(bossIndex, SF2Attribute_IgnitePlayerOnDeath)) { TF2_IgnitePlayer(client, client); } - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt > MaxClients) - { - SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - } SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. + ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. KillClient(client); return; } @@ -216,7 +260,6 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool } g_PlayerDeathCamBoss[client] = NPCGetUniqueID(bossIndex); - g_PlayerDeathCam[client] = true; g_PlayerDeathCamShowOverlay[client] = false; float eyePos[3], eyeAng[3], angle[3]; @@ -228,46 +271,34 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool angle[2] = 0.0; // Create fake model. - int slender = SpawnSlenderModel(bossIndex, lookPos, true); - TeleportEntity(slender, lookPos, angle, NULL_VECTOR); - g_PlayerDeathCamEnt2[client] = EntIndexToEntRef(slender); - if (!g_SlenderPublicDeathCam[bossIndex]) + int slender = -1; + bool publicDeathcam = data.PublicDeathCam || data.DeathCamData.Enabled; + if (!publicDeathcam) { + slender = SpawnSlenderModel(bossIndex, lookPos, true); + TeleportEntity(slender, lookPos, angle, NULL_VECTOR); SDKHook(slender, SDKHook_SetTransmit, Hook_DeathCamSetTransmit); } else { - GetBossProfileLocalDeathCamSounds(profile, soundInfo); - soundInfo.EmitSound(_, slender); SetEntityMoveType(client, MOVETYPE_NOCLIP); - if (!antiCamp) + slender = NPCGetEntIndex(bossIndex); + if (SF2_ChaserEntity(slender).IsValid() || SF2_StatueEntity(slender).IsValid()) { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderInDeathcam[bossIndex] = true; - SetEntityRenderMode(slenderEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(slenderEnt, g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], 0); - NPCChaserUpdateBossAnimation(bossIndex, slenderEnt, STATE_IDLE); - g_SlenderDeathCamTarget[bossIndex] = EntIndexToEntRef(client); - if (g_SlenderEntityThink[bossIndex] != null) - { - KillTimer(g_SlenderEntityThink[bossIndex]); - } - g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderPublicDeathCamThink, EntIndexToEntRef(slenderEnt), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } + SF2_BaseBoss(slender).IsKillingSomeone = true; + SF2_BaseBoss(slender).KillTarget = CBaseEntity(client); } } + g_PlayerDeathCamEnt2[client] = EntIndexToEntRef(slender); - // Create camera look point. char name[64]; FormatEx(name, sizeof(name), "sf2_boss_%d", EntIndexToEntRef(slender)); float offsetPos[3]; int target = CreateEntityByName("info_target"); - if (!g_SlenderPublicDeathCam[bossIndex]) + if (!publicDeathcam) { - GetBossProfileDeathCamPosition(profile, offsetPos); + offsetPos = data.DeathCamPos; AddVectors(lookPos, offsetPos, offsetPos); TeleportEntity(target, offsetPos, NULL_VECTOR, NULL_VECTOR); DispatchKeyValue(target, "targetname", name); @@ -282,7 +313,11 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool DispatchKeyValue(target, "targetname", name); SetVariantString("!activator"); AcceptEntityInput(target, "SetParent", slender); - GetBossProfilePublicDeathCamTargetAttachment(profile, boneName, sizeof(boneName)); + strcopy(boneName, sizeof(boneName), data.PublicDeathCamAttachment); + if (data.DeathCamData.Attachment[0] != '\0') + { + strcopy(boneName, sizeof(boneName), data.DeathCamData.Attachment); + } if (boneName[0] != '\0') { SetVariantString(boneName); @@ -299,7 +334,16 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool DispatchSpawn(camera); AcceptEntityInput(camera, "Enable", client); g_PlayerDeathCamEnt[client] = EntIndexToEntRef(camera); - if (g_SlenderPublicDeathCam[bossIndex]) + int light = ClientGetFlashlightEntity(client); + if (light && light != INVALID_ENT_REFERENCE) + { + SetVariantString("!activator"); + AcceptEntityInput(light, "ClearParent"); + TeleportEntity(light, eyePos, eyeAng, NULL_VECTOR); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", camera); + } + if (publicDeathcam) { float camSpeed, camAcceleration, camDeceleration; @@ -316,45 +360,56 @@ void ClientStartDeathCam(int client,int bossIndex, const float lookPos[3], bool SetVariantString("!activator"); AcceptEntityInput(camera, "SetParent", slender); char attachmentName[PLATFORM_MAX_PATH]; - GetBossProfilePublicDeathCamAttachment(profile, attachmentName, sizeof(attachmentName)); + strcopy(attachmentName, sizeof(attachmentName), data.PublicDeathCamAttachment); + if (data.DeathCamData.Attachment[0] != '\0') + { + strcopy(attachmentName, sizeof(attachmentName), data.DeathCamData.Attachment); + } if (attachmentName[0] != '\0') { SetVariantString(attachmentName); AcceptEntityInput(camera, "SetParentAttachment"); } + strcopy(attachmentName, sizeof(attachmentName), data.PublicDeathCamAttachmentTarget); + if (data.DeathCamData.Attachment[0] != '\0') + { + strcopy(attachmentName, sizeof(attachmentName), data.DeathCamData.TargetAttachment); + } + DispatchKeyValue(camera, "targetname", attachmentName); + g_CameraInDeathCamAdvanced[camera] = true; g_CameraPlayerOffsetBackward[camera] = g_SlenderPublicDeathCamBackwardOffset[bossIndex]; g_CameraPlayerOffsetDownward[camera] = g_SlenderPublicDeathCamDownwardOffset[bossIndex]; RequestFrame(Frame_PublicDeathCam, camera); //Resend taunt sound to eliminated players only } + float duration = g_SlenderDeathCamTime[bossIndex]; if (g_SlenderDeathCamOverlay[bossIndex] && g_SlenderDeathCamOverlayTimeStart[bossIndex] >= 0.0) { - if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) - { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_BossDeathCamDelay, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); - } - } - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamOverlayTimeStart[bossIndex], Timer_ClientResetDeathCam1, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + duration = g_SlenderDeathCamOverlayTimeStart[bossIndex]; } - else + if (duration <= 0.0 && publicDeathcam) { - if (g_SlenderPublicDeathCam[bossIndex] && !antiCamp) + char animation[64]; + SF2BossProfileMasterAnimationsData animData; + animData = data.AnimationData; + float rate = 1.0, cycle = 0.0; + animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, animation, sizeof(animation), rate, duration, cycle); + CBaseAnimating animator = CBaseAnimating(slender); + int sequence = animator.LookupSequence(animation); + if (duration <= 0.0 && sequence != -1) { - int slenderEnt = NPCGetEntIndex(bossIndex); - if (slenderEnt && slenderEnt != INVALID_ENT_REFERENCE) - { - g_SlenderDeathCamTimer[bossIndex] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_BossDeathCamDuration, EntIndexToEntRef(slenderEnt), TIMER_FLAG_NO_MAPCHANGE); - } + duration = animator.SequenceDuration(sequence) / rate; + duration *= (1.0 - cycle); } - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[bossIndex], Timer_ClientResetDeathCamEnd, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); + g_PlayerDeathCamTimer[client] = duration; + g_PlayerDeathCamMustDoOverlay[client] = g_SlenderDeathCamOverlay[bossIndex]; + g_PlayerDeathCam[client] = true; + + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, { 0.0, 0.0, 0.0 }); Call_StartForward(g_OnClientStartDeathCamFwd); Call_PushCell(client); @@ -386,112 +441,11 @@ static void Frame_PublicDeathCam(int cameraRef) } } -static Action Timer_ClientResetDeathCam1(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerDeathCamTimer[client]) - { - return Plugin_Stop; - } - - SF2NPC_BaseNPC Npc = view_as(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - if (Npc.IsValid()) - { - g_PlayerDeathCamShowOverlay[client] = true; - Npc.GetProfile(profile, sizeof(profile)); - SF2BossProfileSoundInfo soundInfo; - GetBossProfilePlayerDeathcamOverlaySounds(profile, soundInfo); - soundInfo.EmitSound(true, client); - g_PlayerDeathCamTimer[client] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_ClientResetDeathCamEnd, userid, TIMER_FLAG_NO_MAPCHANGE); - } - - return Plugin_Stop; -} - -static Action Timer_BossDeathCamDelay(Handle timer, any entref) -{ - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(slender)); - - if (timer != g_SlenderDeathCamTimer[Npc.Index]) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.GetProfile(profile, sizeof(profile)); - - g_SlenderDeathCamTimer[Npc.Index] = CreateTimer(g_SlenderDeathCamTime[Npc.Index], Timer_BossDeathCamDuration, slender, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Stop; -} - -static Action Timer_BossDeathCamDuration(Handle timer, any entref) -{ - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_Chaser Npc = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); - - if (timer != g_SlenderDeathCamTimer[Npc.Index]) - { - return Plugin_Stop; - } - - if (g_SlenderInDeathcam[Npc.Index]) - { - SetEntityRenderMode(slender, RENDER_NORMAL); - if (!Npc.CloakEnabled) - { - SetEntityRenderColor(slender, Npc.GetRenderColor(0), Npc.GetRenderColor(1), Npc.GetRenderColor(2), Npc.GetRenderColor(3)); - } - g_SlenderEntityThink[Npc.Index] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - if (!(Npc.Flags & SFF_FAKE)) - { - g_SlenderInDeathcam[Npc.Index] = false; - } - Npc.UpdateAnimation(slender, Npc.State); - } - if ((Npc.Flags & SFF_FAKE)) - { - if (g_SlenderInDeathcam[Npc.Index]) - { - g_SlenderInDeathcam[Npc.Index] = false; - } - Npc.MarkAsFake(); - } - g_SlenderDeathCamTimer[Npc.Index] = null; - - return Plugin_Stop; -} - -static Action Timer_ClientResetDeathCamEnd(Handle timer, any userid) +static void StopDeathCam(int client) { - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerDeathCamTimer[client]) + if (!IsValidClient(client)) { - return Plugin_Stop; + return; } SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol entity changes our damage state. @@ -499,37 +453,55 @@ static Action Timer_ClientResetDeathCamEnd(Handle timer, any userid) SF2NPC_BaseNPC deathCamBoss = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); if (deathCamBoss != SF2_INVALID_NPC) { - float value = deathCamBoss.GetAttributeValue(SF2Attribute_IgnitePlayerOnDeath); - if (value > 0.0) - { - TF2_IgnitePlayer(client, client); - } if (!(deathCamBoss.Flags & SFF_FAKE)) { - int slenderEnt = deathCamBoss.EntIndex; - if (slenderEnt > MaxClients) + if (deathCamBoss.HasAttribute(SF2Attribute_IgnitePlayerOnDeath)) { - SDKHooks_TakeDamage(client, slenderEnt, slenderEnt, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + TF2_IgnitePlayer(client, client); } - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - ForcePlayerSuicide(client);//Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. + + SF2BossProfileData data; + data = deathCamBoss.GetProfileData(); + if (data.DeathCamData.Enabled && data.DeathCamData.Blackout && !g_Hooked) + { + AddNormalSoundHook(NoPlayerVoiceHook); + g_Hooked = true; + } + + Call_StartForward(g_OnClientPreKillDeathCamFwd); + Call_PushCell(client); + Call_PushCell(deathCamBoss.Index); + Call_Finish(); + + SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); + ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. KillClient(client); } else { SetEntityMoveType(client, MOVETYPE_WALK); - TeleportEntity(client, g_PlayerOriginalDeathcamPosition[client], NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); + TeleportEntity(client, g_PlayerOriginalDeathcamPosition[client], NULL_VECTOR, { 0.0, 0.0, 0.0 }); + int light = ClientGetFlashlightEntity(client); + if (light && light != INVALID_ENT_REFERENCE) + { + float eyePos[3], eyeAng[3]; + GetClientEyePosition(client, eyePos); + GetClientEyeAngles(client, eyeAng); + SetVariantString("!activator"); + AcceptEntityInput(light, "ClearParent"); + TeleportEntity(light, eyePos, eyeAng, NULL_VECTOR); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", client); + } } } - else //The boss is invalid? But the player got a death cam? + else // The boss is invalid? But the player got a death cam? { - //Then kill him anyways. + // Then kill him anyways. KillClient(client); ForcePlayerSuicide(client); } ClientResetDeathCam(client); - - return Plugin_Stop; } static void Hook_DeathCamThink(int client) @@ -558,4 +530,32 @@ static void Hook_DeathCamThink(int client) player.SetLocalOrigin(camPos); player.SetLocalAngles(camAngs); } + + g_PlayerDeathCamTimer[player.index] -= GetGameFrameTime(); + if (g_PlayerDeathCamTimer[player.index] <= 0.0) + { + if (g_PlayerDeathCamMustDoOverlay[player.index]) + { + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[player.index])); + float duration = 0.0; + if (Npc.IsValid()) + { + g_PlayerDeathCamShowOverlay[player.index] = true; + SF2BossProfileData data; + data = Npc.GetProfileData(); + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.PlayerDeathCamOverlaySounds; + soundInfo.EmitSound(true, player.index); + duration = g_SlenderDeathCamTime[Npc.Index]; + duration -= g_SlenderDeathCamOverlayTimeStart[Npc.Index]; + } + g_PlayerDeathCamTimer[player.index] = duration; + g_PlayerDeathCamMustDoOverlay[player.index] = false; + return; + } + else + { + StopDeathCam(player.index); + } + } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/flashlight.sp b/addons/sourcemod/scripting/sf2/client/flashlight.sp index 7bcf7b81..987d3353 100644 --- a/addons/sourcemod/scripting/sf2/client/flashlight.sp +++ b/addons/sourcemod/scripting/sf2/client/flashlight.sp @@ -159,7 +159,14 @@ void ClientProcessFlashlightAngles(int client) fl = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); if (fl && fl != INVALID_ENT_REFERENCE) { - TeleportEntity(fl, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 }), NULL_VECTOR); + TeleportEntity(fl, NULL_VECTOR, { 0.0, 0.0, 0.0 }, NULL_VECTOR); + } + fl = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); + if (fl && fl != INVALID_ENT_REFERENCE) + { + float ang[3]; + GetClientEyeAngles(client, ang); + TeleportEntity(fl, NULL_VECTOR, ang, NULL_VECTOR); } } } @@ -216,6 +223,11 @@ float ClientGetFlashlightNextInputTime(int client) return g_PlayerFlashlightNextInputTime[client]; } +int ClientGetFlashlightEntity(int client) +{ + return EntRefToEntIndex(g_PlayerFlashlightEnt[client]); +} + /** * Breaks the player's flashlight. Nothing else. */ @@ -260,6 +272,14 @@ void ClientResetFlashlight(int client) g_PlayerFlashlightBatteryTimer[client] = null; g_PlayerFlashlightNextInputTime[client] = -1.0; + SF2PointSpotlightEntity spotlight = SF2PointSpotlightEntity(EntRefToEntIndex(g_PlayerFlashlightEntAng[client])); + if (spotlight.IsValid()) + { + spotlight.TurnOff(); + RemoveEntity(spotlight.index); + } + g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; + #if defined DEBUG if (g_DebugDetailConVar.IntValue > 2) { @@ -304,7 +324,7 @@ static Action Hook_FlashlightSetTransmit(int ent,int other) if (ownerEntity == other) { - if (!!(GetEntProp(ownerEntity, Prop_Send, "m_nForceTauntCam")) && !TF2_IsPlayerInCondition(ownerEntity, TFCond_Taunting)) + if ((GetEntProp(ownerEntity, Prop_Send, "m_nForceTauntCam") != 0) && !TF2_IsPlayerInCondition(ownerEntity, TFCond_Taunting)) { return Plugin_Handled; } @@ -350,15 +370,19 @@ void ClientTurnOnFlashlight(int client) float eyePos[3], eyeAng[3]; float length = SF2_FLASHLIGHT_LENGTH; - float doubleLength = SF2_FLASHLIGHT_LENGTH*3.0; + float doubleLength = SF2_FLASHLIGHT_LENGTH * 3.0; float radius = SF2_FLASHLIGHT_WIDTH; - float doubleRadius = SF2_FLASHLIGHT_WIDTH*2.0; + float doubleRadius = SF2_FLASHLIGHT_WIDTH * 2.0; GetClientEyePosition(client, eyePos); GetClientEyeAngles(client, eyeAng); TFClassType class = TF2_GetPlayerClass(client); int classToInt = view_as(class); + // Convert WU to inches. + float cone = 55.0; + cone *= 0.75; + if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) { // If the player is using the projected flashlight, just set effect flags. @@ -464,10 +488,6 @@ void ClientTurnOnFlashlight(int client) } AcceptEntityInput(ent, "brightness"); - // Convert WU to inches. - float cone = 55.0; - cone *= 0.75; - SetVariantInt(RoundToFloor(cone)); AcceptEntityInput(ent, "_inner_cone"); SetVariantInt(RoundToFloor(cone)); @@ -485,126 +505,105 @@ void ClientTurnOnFlashlight(int client) } // Spawn the light that only everyone else will see. - /*int ent = CreateEntityByName("env_beam"); - if (ent != -1) + /*SF2PointSpotlightEntity spotlight = SF2PointSpotlightEntity(CreateEntityByName("sf2_point_spotlight")); + if (spotlight.IsValid()) { - int startEnt = CreateEntityByName("info_target"); - int endEnt = CreateEntityByName("info_target"); - if (startEnt != -1) // Start - { - SetEntPropString(startEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_start"); - SetEntProp(startEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(startEnt, eyePos, eyeAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(startEnt, "SetParent", client); - - DispatchSpawn(startEnt); - SetEntityOwner(startEnt, client); - - g_ClientFlashlightStartEntity[client] = EntIndexToEntRef(startEnt); - - SetEntityTransmitState(startEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectShouldTransmit); - } - if (endEnt != -1) // End - { - SetEntPropString(endEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_end"); - SetEntProp(endEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(endEnt, eyePos, eyeAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(endEnt, "SetParent", client); - - DispatchSpawn(endEnt); - SetEntityOwner(endEnt, client); - - g_ClientFlashlightEndEntity[client] = EntIndexToEntRef(endEnt); - - SetEntityTransmitState(endEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectShouldTransmit); - } - + TeleportEntity(spotlight.index, eyePos, eyeAng); switch (g_PlayerPreferences[client].PlayerPreference_FlashlightTemperature) { case 1: { - SetEntityRenderColor(ent, 255, 150, 50, 64); + spotlight.SetRenderColor(255, 150, 50, 255); } case 2: { - SetEntityRenderColor(ent, 255, 210, 100, 64); + spotlight.SetRenderColor(255, 210, 100, 255); } case 3: { - SetEntityRenderColor(ent, 255, 255, 120, 64); + spotlight.SetRenderColor(255, 255, 120, 255); } case 4: { - SetEntityRenderColor(ent, 255, 255, 185, 64); + spotlight.SetRenderColor(255, 255, 185, 255); } case 5: { - SetEntityRenderColor(ent, 255, 255, 210, 64); + spotlight.SetRenderColor(255, 255, 210, 255); } case 6: { - SetEntityRenderColor(ent, 255, 255, 255, 64); + spotlight.SetRenderColor(255, 255, 255, 255); } case 7: { - SetEntityRenderColor(ent, 210, 255, 255, 64); + spotlight.SetRenderColor(210, 255, 255, 255); } case 8: { - SetEntityRenderColor(ent, 185, 255, 255, 64); + spotlight.SetRenderColor(185, 255, 255, 255); } case 9: { - SetEntityRenderColor(ent, 150, 255, 255, 64); + spotlight.SetRenderColor(150, 255, 255, 255); } case 10: { - SetEntityRenderColor(ent, 125, 255, 255, 64); + spotlight.SetRenderColor(125, 255, 255, 255); } } - - SetEntityModel(ent, SF2_FLASHLIGHT_BEAM_MATERIAL); - SetEntityRenderMode(ent, RENDER_TRANSTEXTURE); - SetEntPropFloat(ent, Prop_Data, "m_life", 0.0); - - TeleportEntity(ent, eyePos, eyeAng, NULL_VECTOR); - - DispatchSpawn(ent); - ActivateEntity(ent); - - SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", startEnt, 0); - SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", endEnt, 1); - SetEntProp(ent, Prop_Send, "m_nNumBeamEnts", 2); - SetEntProp(ent, Prop_Send, "m_nBeamType", 2); - - SetEntPropFloat(ent, Prop_Send, "m_fWidth", 40.0); - SetEntPropFloat(ent, Prop_Data, "m_fEndWidth", 102.0); - - SetEntPropFloat(ent, Prop_Send, "m_fFadeLength", 0.0); - SetEntProp(ent, Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); - SetEntPropFloat(ent, Prop_Send, "m_fHaloScale", 40.0); - SetEntProp(ent, Prop_Send, "m_nBeamFlags", 0x80 | 0x200); - SetEntProp(ent, Prop_Data, "m_spawnflags", 0x8000); - - AcceptEntityInput(ent, "TurnOn"); - + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer) + { + spotlight.Length = length; + } + else + { + spotlight.Length = doubleLength; + } + } + else + { + float customLength = length * g_ClassFlashlightLength[classToInt]; + spotlight.Length = customLength; + } + if (!IsClassConfigsValid()) + { + if (class != TFClass_Engineer) + { + spotlight.Width = radius; + } + else + { + spotlight.Width = doubleRadius; + } + } + else + { + float customRadius = radius * g_ClassFlashlightRadius[classToInt]; + spotlight.Width = customRadius; + } + if (!IsClassConfigsValid()) + { + spotlight.Brightness = SF2_FLASHLIGHT_BRIGHTNESS; + } + else + { + int customBrightness = SF2_FLASHLIGHT_BRIGHTNESS + g_ClassFlashlightBrightness[classToInt]; + spotlight.Brightness = customBrightness; + } + spotlight.Cone = RoundToFloor(cone); + spotlight.EndWidth = spotlight.Width * 2.0; + spotlight.Distance = spotlight.EndWidth; + spotlight.SpotlightRadius = spotlight.Length; + spotlight.HaloScale = 20.0; + spotlight.Spawn(); + spotlight.Activate(); SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", client); - - SetEntityOwner(ent, client); - - SDKHook(ent, SDKHook_SetTransmit, Hook_Flashlight2SetTransmit); - SetEntityTransmitState(ent, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectShouldTransmit); - - g_PlayerFlashlightEntAng[client] = EntIndexToEntRef(ent); + spotlight.AcceptInput("SetParent", client); + spotlight.TurnOn(); + g_PlayerFlashlightEntAng[client] = EntIndexToEntRef(spotlight.index); }*/ Call_StartForward(g_OnClientActivateFlashlightFwd); @@ -690,8 +689,7 @@ void ClientTurnOffFlashlight(int client) ent = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); if (ent && ent != INVALID_ENT_REFERENCE) { - AcceptEntityInput(ent, "TurnOff"); - CreateTimer(0.1, Timer_KillEntity, g_PlayerFlashlightEntAng[client], TIMER_FLAG_NO_MAPCHANGE); + RemoveEntity(ent); } ent = EntRefToEntIndex(g_ClientFlashlightStartEntity[client]); if (ent && ent != INVALID_ENT_REFERENCE) @@ -705,9 +703,9 @@ void ClientTurnOffFlashlight(int client) } g_PlayerFlashlightEnt[client] = INVALID_ENT_REFERENCE; - g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; g_ClientFlashlightStartEntity[client] = INVALID_ENT_REFERENCE; g_ClientFlashlightEndEntity[client] = INVALID_ENT_REFERENCE; + g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; if (IsValidClient(client)) { @@ -776,6 +774,11 @@ void ClientHandleFlashlight(int client) return; } + if (IsClientInDeathCam(client)) + { + return; + } + bool nightVision = (g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)); if (IsClientUsingFlashlight(client) || TF2_IsPlayerInCondition(client, TFCond_Taunting)) @@ -791,11 +794,11 @@ void ClientHandleFlashlight(int client) { if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) { - EmitSoundToAll(FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToAll(FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } else { - EmitSoundToClient(client, FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToClient(client, FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } } } @@ -828,11 +831,11 @@ void ClientHandleFlashlight(int client) g_PlayerFlashlightNextInputTime[client] = GetGameTime(); if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) { - EmitSoundToAll((nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToAll((nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } else { - EmitSoundToClient(client, (nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); + EmitSoundToClient(client, (nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } } else diff --git a/addons/sourcemod/scripting/sf2/client/ghostmode.sp b/addons/sourcemod/scripting/sf2/client/ghostmode.sp index d9228389..22958d5b 100644 --- a/addons/sourcemod/scripting/sf2/client/ghostmode.sp +++ b/addons/sourcemod/scripting/sf2/client/ghostmode.sp @@ -93,7 +93,7 @@ void ClientSetGhostModeState(int client, bool state) SendDebugMessageToPlayer(client, DEBUG_GHOSTMODE, 0, "{green}Entered ghost mode."); #endif //Strip the always edict flag - SetEntityFlags(client, GetEntityFlags(client)^FL_EDICT_ALWAYS); + SetEntityFlags(client, GetEntityFlags(client) ^ FL_EDICT_ALWAYS); //Remove the fire cond TF2_RemoveCondition(client,TFCond_OnFire); //Call the spawn event. @@ -160,6 +160,12 @@ void ClientSetGhostModeState(int client, bool state) { continue; } + SF2BossProfileData data; + data = NPCGetProfileData(npcIndex); + if (data.IsPvEBoss) + { + continue; + } SlenderRemoveGlow(npcIndex); if (NPCGetCustomOutlinesState(npcIndex)) { diff --git a/addons/sourcemod/scripting/sf2/client/hints.sp b/addons/sourcemod/scripting/sf2/client/hints.sp index e4ea9b8d..f430f5e9 100644 --- a/addons/sourcemod/scripting/sf2/client/hints.sp +++ b/addons/sourcemod/scripting/sf2/client/hints.sp @@ -54,7 +54,7 @@ void ClientResetHints(int client) #endif } -void ClientShowHint(int client,int hint) +void ClientShowHint(int client, int hint) { g_PlayerHints[client][hint] = true; @@ -82,3 +82,8 @@ void ClientShowHint(int client,int hint) } } } + +bool ClientHasHint(int client, int hint) +{ + return g_PlayerHints[client][hint]; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/music.sp b/addons/sourcemod/scripting/sf2/client/music.sp index 47044e28..c640480f 100644 --- a/addons/sourcemod/scripting/sf2/client/music.sp +++ b/addons/sourcemod/scripting/sf2/client/music.sp @@ -7,6 +7,7 @@ void SetupMusic() { + g_OnGamemodeEnd.AddFunction(null, OnGamemodeEnd); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); @@ -14,6 +15,19 @@ void SetupMusic() g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); } +static void OnGamemodeEnd() +{ + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + ClientUpdateMusicSystem(i); + ClientResetChannels(i); + } +} + static void OnPlayerSpawn(SF2_BasePlayer client) { ClientUpdateMusicSystem(client.index); @@ -25,23 +39,23 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo if (!fake) { ClientResetChannels(client.index); - ClientUpdateMusicSystem(client.index); } } static void OnPutInServer(SF2_BasePlayer client) { - ClientUpdateMusicSystem(client.index); ClientResetChannels(client.index); + ClientUpdateMusicSystem(client.index); } static void OnPlayerEscape(SF2_BasePlayer client) { + ClientResetChannels(client.index); ClientUpdateMusicSystem(client.index); } -static void OnBossRemoved(int bossIndex) +static void OnBossRemoved(SF2NPC_BaseNPC npc) { if (!MusicActive()) { @@ -53,23 +67,23 @@ static void OnBossRemoved(int bossIndex) } // Remove chase music. - if (g_PlayerChaseMusicMaster[i] == bossIndex) + if (g_PlayerChaseMusicMaster[i] == npc.Index) { ClientChaseMusicReset(i); } // Don't forget search theme - if (g_PlayerAlertMusicMaster[i] == bossIndex) + if (g_PlayerAlertMusicMaster[i] == npc.Index) { ClientAlertMusicReset(i); } - if (g_PlayerChaseMusicSeeMaster[i] == bossIndex) + if (g_PlayerChaseMusicSeeMaster[i] == npc.Index) { ClientChaseMusicSeeReset(i); } - if (g_PlayerIdleMusicMaster[i] == bossIndex) + if (g_PlayerIdleMusicMaster[i] == npc.Index) { ClientIdleMusicReset(i); } @@ -89,7 +103,7 @@ void ClientResetChannels(int client) ClientMusicReset(client); } -void ClientUpdateMusicSystem(int client, bool initialize=false) +void ClientUpdateMusicSystem(int client, bool initialize = false) { int oldPageMusicMaster = EntRefToEntIndex(g_PlayerPageMusicMaster[client]); int oldPageMusicActiveIndex = g_PageMusicActiveIndex[client]; @@ -99,7 +113,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) int alertBoss = -1; int idleBoss = -1; - if (IsRoundEnding() || !IsValidClient(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) + if (IsRoundEnding() || !IsValidClient(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client]) || SF_SpecialRound(SPECIALROUND_BEATBOX)) { g_PlayerMusicFlags[client] = 0; g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; @@ -108,7 +122,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) if (MusicActive()) //A boss is overriding the music. { char path[PLATFORM_MAX_PATH]; - GetBossMusic(path,sizeof(path)); + GetBossMusic(path, sizeof(path)); if (path[0] != '\0') { StopSound(client, MUSIC_CHAN, path); @@ -229,6 +243,13 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) continue; } + SF2BossProfileData data; + data = NPCGetProfileData(i); + if (data.IsPvEBoss) + { + continue; + } + NPCGetProfile(i, profile, sizeof(profile)); int bossType = NPCGetType(i); @@ -237,23 +258,39 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) { case SF2BossType_Chaser: { + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(i)); + + if (chaser.State == STATE_DEATH) + { + continue; + } + SF2BossProfileSoundInfo soundInfo; ArrayList soundList; GetClientAbsOrigin(client, buffer); - SlenderGetAbsOrigin(i, buffer3); + chaser.GetAbsOrigin(buffer3); + float pos[3]; + if (!chaser.Controller.Path.IsValid()) + { + pos = buffer3; + } + else + { + chaser.Controller.Path.GetEndPosition(pos); + } - int target = EntRefToEntIndex(g_SlenderTarget[i]); - if (target != -1) + CBaseEntity target = chaser.Target; + if (target.IsValid()) { - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", buffer2); + target.GetAbsOrigin(buffer2); - if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && + if ((chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN) && !(NPCGetFlags(i) & SFF_MARKEDASFAKE)) { GetChaserProfileChaseMusics(profile, soundInfo); - if ((target == client || GetVectorSquareMagnitude(buffer, buffer2) <= SquareFloat(soundInfo.Radius) || - GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius))) + if ((target.index == client || GetVectorSquareMagnitude(buffer, buffer2) <= SquareFloat(soundInfo.Radius) || + GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, pos) <= SquareFloat(soundInfo.Radius))) { soundList = soundInfo.Paths; if (soundList != null && soundList.Length > 0) @@ -261,7 +298,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) chasingBoss = i; } - if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && + if ((chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN) && PlayerCanSeeSlender(client, i, false)) { GetChaserProfileChaseVisibleMusics(profile, soundInfo); @@ -275,7 +312,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) } } - if (g_SlenderState[i] == STATE_ALERT) + if (chaser.State == STATE_ALERT) { GetChaserProfileAlertMusics(profile, soundInfo); soundList = soundInfo.Paths; @@ -286,14 +323,14 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) { - if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) + if (GetVectorSquareMagnitude(buffer, pos) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius)) { alertBoss = i; } } } - if (g_SlenderState[i] == STATE_IDLE || g_SlenderState[i] == STATE_WANDER) + if (chaser.State == STATE_IDLE) { GetChaserProfileIdleMusics(profile, soundInfo); soundList = soundInfo.Paths; @@ -304,7 +341,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) { - if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) + if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, pos) <= SquareFloat(soundInfo.Radius)) { idleBoss = i; } @@ -364,7 +401,7 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) } } - if (IsValidClient(client)) + if (IsValidClient(client) && IsPlayerAlive(client)) { bool wasChase = ClientHasMusicFlag2(oldMusicFlags, MUSICF_CHASE); bool inChase = ClientHasMusicFlag(client, MUSICF_CHASE); @@ -376,14 +413,23 @@ void ClientUpdateMusicSystem(int client, bool initialize=false) bool wasIdle = ClientHasMusicFlag2(oldMusicFlags, MUSICF_IDLE); char path[PLATFORM_MAX_PATH]; + if (SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + return; + } + if (IsRoundEnding() || !IsValidClient(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) { + // Do nothing } - else if (MusicActive()) //A boss is overriding the music. + else { - GetBossMusic(path,sizeof(path)); - ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME); - return; + if (MusicActive()) //A boss is overriding the music. + { + GetBossMusic(path, sizeof(path)); + ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME); + return; + } } // Custom system. @@ -640,7 +686,7 @@ void ClientMusicReset(int client) g_PageMusicActiveIndex[client] = -1; } -void ClientMusicStart(int client, const char[] newMusic, float volume=-1.0, float targetVolume=-1.0, bool copyOnly=false) +void ClientMusicStart(int client, const char[] newMusic, float volume = -1.0, float targetVolume = -1.0, bool copyOnly = false) { if (!IsValidClient(client) || !IsPlayerAlive(client)) { @@ -659,9 +705,9 @@ void ClientMusicStart(int client, const char[] newMusic, float volume=-1.0, floa StopSound(client, MUSIC_CHAN, oldMusic); } strcopy(g_PlayerMusicString[client], sizeof(g_PlayerMusicString[]), newMusic); - if (MusicActive())//A boss is overriding the music. + if (MusicActive()) // A boss is overriding the music. { - GetBossMusic(g_PlayerMusicString[client],sizeof(g_PlayerMusicString[])); + GetBossMusic(g_PlayerMusicString[client], sizeof(g_PlayerMusicString[])); } if (volume >= 0.0) { @@ -977,7 +1023,7 @@ void ClientMusicChaseStart(int client,int bossIndex) } return; } - if (MusicActive())//A boss is overriding the music. + if (MusicActive()) // A boss is overriding the music. { GetBossMusic(g_PlayerChaseMusicString[client][bossIndex],sizeof(g_PlayerChaseMusicString[][])); } @@ -1194,7 +1240,7 @@ Action Timer_PlayerFadeInMusic(Handle timer, any userid) if (g_PlayerMusicString[client][0] != '\0') { - EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); + EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_PlayerMusicVolume[client] >= g_PlayerMusicTargetVolume[client]) @@ -1227,7 +1273,7 @@ Action Timer_PlayerFadeOutMusic(Handle timer, any userid) if (g_PlayerMusicString[client][0] != '\0') { - EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); + EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_PlayerMusicVolume[client] <= 0.0) @@ -1262,6 +1308,11 @@ Action Timer_PlayerFadeInAlertMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.AlertMusics; + /*int oldBoss = g_PlayerAlertMusicOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerAlertMusicString[client][bossIndex], g_PlayerAlertMusicString[client][oldBoss], false) == 0) @@ -1279,7 +1330,7 @@ Action Timer_PlayerFadeInAlertMusic(Handle timer, any userid) if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerAlertMusicVolumes[client][bossIndex] >= 1.0) @@ -1314,6 +1365,11 @@ Action Timer_PlayerFadeOutAlertMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.AlertMusics; + int oldBoss = g_PlayerAlertMusicOldMaster[client]; int newBoss = g_PlayerAlertMusicMaster[client]; @@ -1334,7 +1390,7 @@ Action Timer_PlayerFadeOutAlertMusic(Handle timer, any userid) if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerAlertMusicVolumes[client][bossIndex] <= 0.0) @@ -1371,6 +1427,11 @@ Action Timer_PlayerFadeInIdleMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.IdleMusics; + /*int oldBoss = g_PlayerIdleMusicOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerIdleMusicString[client][bossIndex], g_PlayerIdleMusicString[client][oldBoss], false) == 0) @@ -1388,7 +1449,7 @@ Action Timer_PlayerFadeInIdleMusic(Handle timer, any userid) if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerIdleMusicVolumes[client][bossIndex] >= 1.0) @@ -1423,6 +1484,11 @@ Action Timer_PlayerFadeOutIdleMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.IdleMusics; + int oldBoss = g_PlayerIdleMusicOldMaster[client]; int newBoss = g_PlayerIdleMusicMaster[client]; @@ -1443,7 +1509,7 @@ Action Timer_PlayerFadeOutIdleMusic(Handle timer, any userid) if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerIdleMusicVolumes[client][bossIndex] <= 0.0) @@ -1480,6 +1546,11 @@ Action Timer_PlayerFadeInChaseMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseMusics; + /*int oldBoss = g_PlayerChaseMusicOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicString[client][bossIndex], g_PlayerChaseMusicString[client][oldBoss], false) == 0) @@ -1497,7 +1568,7 @@ Action Timer_PlayerFadeInChaseMusic(Handle timer, any userid) if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicVolumes[client][bossIndex] >= 1.0) @@ -1532,6 +1603,11 @@ Action Timer_PlayerFadeInChaseMusicSee(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseVisibleMusics; + /*int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicSeeString[client][bossIndex], g_PlayerChaseMusicSeeString[client][oldBoss], false) == 0) @@ -1549,7 +1625,7 @@ Action Timer_PlayerFadeInChaseMusicSee(Handle timer, any userid) if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] >= 1.0) @@ -1584,6 +1660,11 @@ Action Timer_PlayerFadeOutChaseMusic(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseMusics; + int oldBoss = g_PlayerChaseMusicOldMaster[client]; int newBoss = g_PlayerChaseMusicMaster[client]; @@ -1604,7 +1685,7 @@ Action Timer_PlayerFadeOutChaseMusic(Handle timer, any userid) if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicVolumes[client][bossIndex] <= 0.0) @@ -1641,6 +1722,11 @@ Action Timer_PlayerFadeOutChaseMusicSee(Handle timer, any userid) return Plugin_Stop; } + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(bossIndex); + SF2BossProfileSoundInfo info; + info = data.ChaseVisibleMusics; + int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; int newBoss = g_PlayerChaseMusicSeeMaster[client]; @@ -1661,7 +1747,7 @@ Action Timer_PlayerFadeOutChaseMusicSee(Handle timer, any userid) if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') { - EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); + EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex] * info.Volume * g_PlayerPreferences[client].PlayerPreference_MusicVolume, info.Pitch); } if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] <= 0.0) @@ -1696,7 +1782,7 @@ Action Timer_PlayerFadeIn90sMusic(Handle timer, any userid) if (g_Player90sMusicString[client][0] != '\0') { - EmitSoundToClient(client, g_Player90sMusicString[client], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); + EmitSoundToClient(client, g_Player90sMusicString[client], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_Player90sMusicVolumes[client] >= 0.5) @@ -1738,7 +1824,7 @@ Action Timer_PlayerFadeOut90sMusic(Handle timer, any userid) if (buffer[0] != '\0') { - EmitSoundToClient(client, buffer, _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); + EmitSoundToClient(client, buffer, _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client] * g_PlayerPreferences[client].PlayerPreference_MusicVolume, 100); } if (g_Player90sMusicVolumes[client] <= 0.0) @@ -1750,17 +1836,17 @@ Action Timer_PlayerFadeOut90sMusic(Handle timer, any userid) return Plugin_Continue; } -bool ClientHasMusicFlag(int client,int flag) +bool ClientHasMusicFlag(int client, int flag) { - return !!(g_PlayerMusicFlags[client] & flag); + return (g_PlayerMusicFlags[client] & flag) != 0; } -bool ClientHasMusicFlag2(int value,int flag) +bool ClientHasMusicFlag2(int value, int flag) { - return !!(value & flag); + return (value & flag) != 0; } -void ClientAddMusicFlag(int client,int flag) +void ClientAddMusicFlag(int client, int flag) { if (!ClientHasMusicFlag(client, flag)) { @@ -1768,7 +1854,7 @@ void ClientAddMusicFlag(int client,int flag) } } -void ClientRemoveMusicFlag(int client,int flag) +void ClientRemoveMusicFlag(int client, int flag) { if (ClientHasMusicFlag(client, flag)) { diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 4d6df933..2f9d2801 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -16,11 +16,24 @@ static char g_ClientProxyModelApollyon[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; void SetupProxy() { + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + + AddNormalSoundHook(Hook_ProxySoundHook); +} + +static void OnGamemodeStart() +{ + for (int i = 1; i <= 18; i++) + { + char sound[PLATFORM_MAX_PATH]; + FormatEx(sound, sizeof(sound), "mvm/player/footsteps/robostep_%s%i.wav", (i < 10) ? "0" : "", i); + PrecacheSound(sound, true); + } } static void OnPutInServer(SF2_BasePlayer client) @@ -41,7 +54,6 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client) { - ClientResetProxy(client.index, true); if (!client.IsProxy) { return; @@ -59,6 +71,20 @@ static void OnPlayerDeath(SF2_BasePlayer client) GetBossProfileProxyDeathSounds(profile, soundInfo); soundInfo.EmitSound(_, client.index); } + + CreateTimer(0.1, Timer_ResetProxy, client.UserID, TIMER_FLAG_NO_MAPCHANGE); +} + +static Action Timer_ResetProxy(Handle timer, any userid) +{ + SF2_BasePlayer client = SF2_BasePlayer(GetClientOfUserId(userid)); + if (!client.IsValid) + { + return Plugin_Stop; + } + + ClientResetProxy(client.index, true); + return Plugin_Stop; } static void OnPlayerEscape(SF2_BasePlayer client) @@ -66,7 +92,82 @@ static void OnPlayerEscape(SF2_BasePlayer client) ClientResetProxy(client.index); } -void ClientResetProxy(int client, bool resetFull=true) +static Action Hook_ProxySoundHook(int clients[64], int &numClients, char sample[PLATFORM_MAX_PATH], int &entity, int &channel, float &volume, int &level, int &pitch, int &flags) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + SF2_BasePlayer client = SF2_BasePlayer(entity); + if (!client.IsValid || !client.IsProxy) + { + return Plugin_Continue; + } + + int master = NPCGetFromUniqueID(client.ProxyMaster); + if (master == -1) + { + return Plugin_Continue; + } + + SF2BossProfileData data; + data = NPCGetProfileData(master); + + switch (channel) + { + case SNDCHAN_VOICE: + { + if (!data.ProxyAllowVoices) + { + return Plugin_Handled; + } + } + } + + if (data.ProxyRobots) + { + switch (channel) + { + case SNDCHAN_VOICE: + { + if (StrContains(sample, "vo/", false) == -1) + { + return Plugin_Continue; + } + + ReplaceString(sample, sizeof(sample), "vo/", "vo/mvm/norm/", false); + ReplaceString(sample, sizeof(sample), ".wav", ".mp3", false); + char className[10], classMvM[15]; + TF2_GetClassName(client.Class, className, sizeof(className), true); + FormatEx(classMvM, sizeof(classMvM), "%s_mvm", className); + ReplaceString(sample, sizeof(sample), className, classMvM, false); + for (int i = 0; i < strlen(sample); i++) + { + sample[i] = CharToLower(sample[i]); + } + PrecacheSound(sample); + return Plugin_Changed; + } + case SNDCHAN_BODY: + { + if (StrContains(sample, "player/footsteps/", false) == -1 || client.Class == TFClass_Medic) + { + return Plugin_Handled; + } + int random = GetRandomInt(1, 18); + pitch = GetRandomInt(95, 100); + FormatEx(sample, sizeof(sample), "mvm/player/footsteps/robostep_%s%i.wav", (random < 10) ? "0" : "", random); + EmitSoundToAll(sample, client.index, _, _, _, 0.25, pitch); + return Plugin_Changed; + } + } + } + + return Plugin_Continue; +} + +void ClientResetProxy(int client, bool resetFull = true) { #if defined DEBUG if (g_DebugDetailConVar.IntValue > 2) @@ -75,13 +176,6 @@ void ClientResetProxy(int client, bool resetFull=true) } #endif - int oldMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); - char oldProfileName[SF2_MAX_PROFILE_NAME_LENGTH]; - if (oldMaster >= 0) - { - NPCGetProfile(oldMaster, oldProfileName, sizeof(oldProfileName)); - } - bool oldProxy = g_PlayerProxy[client]; if (resetFull) { @@ -106,17 +200,6 @@ void ClientResetProxy(int client, bool resetFull=true) SetVariantString(""); AcceptEntityInput(client, "SetCustomModel"); } - - if (oldProfileName[0] != '\0') - { - SF2BossProfileSoundInfo soundInfo; - GetBossProfileProxySpawnSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - GetBossProfileProxyHurtSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - GetBossProfileProxyIdleSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - } } } @@ -263,13 +346,13 @@ void DisplayProxyAskMenu(int client, int askMaster, const float pos[3], int spaw return; } char buffer[512]; - Handle menu = CreateMenu(Menu_ProxyAsk); - SetMenuTitle(menu, "%T\n \n%T\n \n", "SF2 Proxy Ask Menu Title", client, "SF2 Proxy Ask Menu Description", client); + Menu menu = new Menu(Menu_ProxyAsk); + menu.SetTitle("%T\n \n%T\n \n", "SF2 Proxy Ask Menu Title", client, "SF2 Proxy Ask Menu Description", client); FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "1", buffer); + menu.AddItem("1", buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); g_PlayerProxyAskMaster[client] = askMaster; for (int i = 0; i < 3; i++) @@ -278,10 +361,10 @@ void DisplayProxyAskMenu(int client, int askMaster, const float pos[3], int spaw } g_PlayerProxyAskSpawnPoint[client] = EnsureEntRef(spawnPoint); - DisplayMenu(menu, client, 15); + menu.Display(client, 15); } -int Menu_ProxyAsk(Handle menu, MenuAction action,int param1,int param2) +int Menu_ProxyAsk(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -496,47 +579,60 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP { continue; } + SF2BossProfileData data; + data = NPCGetProfileData(npcIndex); + if (data.IsPvEBoss) + { + continue; + } SlenderRemoveGlow(npcIndex); if (NPCGetCustomOutlinesState(npcIndex)) { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) + if (!NPCGetRainbowOutlineState(npcIndex)) { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; + int color[4]; + color[0] = NPCGetOutlineColorR(npcIndex); + color[1] = NPCGetOutlineColorG(npcIndex); + color[2] = NPCGetOutlineColorB(npcIndex); + color[3] = NPCGetOutlineTransparency(npcIndex); + if (color[0] < 0) + { + color[0] = 0; + } + if (color[1] < 0) + { + color[1] = 0; + } + if (color[2] < 0) + { + color[2] = 0; + } + if (color[3] < 0) + { + color[3] = 0; + } + if (color[0] > 255) + { + color[0] = 255; + } + if (color[1] > 255) + { + color[1] = 255; + } + if (color[2] > 255) + { + color[2] = 255; + } + if (color[3] > 255) + { + color[3] = 255; + } + SlenderAddGlow(npcIndex, color); } - if (color[3] > 255) + else { - color[3] = 255; + SlenderAddGlow(bossIndex, {0, 0, 0, 0}); } - SlenderAddGlow(npcIndex, color); } else { @@ -640,9 +736,9 @@ static Action Timer_GiveWeaponAll(Handle timer, any userid) return Plugin_Stop; } -bool Hook_ClientProxyShouldCollide(int ent,int collisiongroup,int contentsmask, bool originalResult) +bool Hook_ClientProxyShouldCollide(int ent, int collisiongroup, int contentsmask, bool originalResult) { - if (!g_Enabled || !g_PlayerProxy[ent] || IsClientInPvP(ent)) + if (!g_Enabled || !g_PlayerProxy[ent] || IsClientInPvP(ent) || IsClientInPvE(ent)) { SDKUnhook(ent, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); return originalResult; @@ -739,7 +835,7 @@ Action Timer_ApplyCustomModel(Handle timer, any userid) { switch (difficulty) { - case Difficulty_Easy, Difficulty_Normal: + case Difficulty_Normal: { modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); if (modelsArray == null) @@ -1033,7 +1129,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) { case Difficulty_Normal: { - if (strcmp(model,g_ClientProxyModel[client]) != 0 && g_ClientProxyModel[client][0] != '\0') + if (strcmp(model, g_ClientProxyModel[client]) != 0 && g_ClientProxyModel[client][0] != '\0') { SetVariantString(g_ClientProxyModel[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1042,7 +1138,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Hard: { - if (strcmp(model,g_ClientProxyModelHard[client]) != 0 && g_ClientProxyModelHard[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelHard[client]) != 0 && g_ClientProxyModelHard[client][0] != '\0') { SetVariantString(g_ClientProxyModelHard[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1051,7 +1147,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Insane: { - if (strcmp(model,g_ClientProxyModelInsane[client]) != 0 && g_ClientProxyModelInsane[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelInsane[client]) != 0 && g_ClientProxyModelInsane[client][0] != '\0') { SetVariantString(g_ClientProxyModelInsane[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1060,7 +1156,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Nightmare: { - if (strcmp(model,g_ClientProxyModelNightmare[client]) != 0 && g_ClientProxyModelNightmare[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelNightmare[client]) != 0 && g_ClientProxyModelNightmare[client][0] != '\0') { SetVariantString(g_ClientProxyModelNightmare[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1069,7 +1165,7 @@ Action ClientCheckProxyModel(Handle timer, any userid) } case Difficulty_Apollyon: { - if (strcmp(model,g_ClientProxyModelApollyon[client]) != 0 && g_ClientProxyModelApollyon[client][0] != '\0') + if (strcmp(model, g_ClientProxyModelApollyon[client]) != 0 && g_ClientProxyModelApollyon[client][0] != '\0') { SetVariantString(g_ClientProxyModelApollyon[client]); AcceptEntityInput(client, "SetCustomModel"); @@ -1084,13 +1180,14 @@ void SF2_RefreshRestrictions() { for(int client = 1; client <= MaxClients; client++) { - if (IsValidClient(client) && (!g_PlayerEliminated[client] || !IsClientInPvP(client))) + if (IsValidClient(client) && (!g_PlayerEliminated[client] || (g_PlayerEliminated[client] && !IsClientInPvP(client) && !IsClientInPvE(client)))) { ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - g_PlayerPostWeaponsTimer[client] = CreateTimer(1.0,Timer_ClientPostWeapons,GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + g_PlayerPostWeaponsTimer[client] = CreateTimer(1.0, Timer_ClientPostWeapons, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } } } + Action Timer_ClientPostWeapons(Handle timer, any userid) { int client = GetClientOfUserId(userid); @@ -1153,7 +1250,6 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) { removeWeapons = false; restrictWeapons = false; - keepUtilityItems = false; } } @@ -1161,7 +1257,6 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) { removeWeapons = false; restrictWeapons = false; - keepUtilityItems = false; preventAttack = true; } @@ -1174,6 +1269,23 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) preventAttack = false; } + if (IsClientInPvE(client)) + { + if (IsPvEBoxing()) + { + restrictWeapons = false; + keepUtilityItems = true; + preventAttack = false; + } + else + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = false; + } + } + if (g_PlayerProxy[client]) { restrictWeapons = true; @@ -1735,7 +1847,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) } //Remove the teleport ability - if (IsClientInPvP(client) || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !g_PlayerEliminated[client])) //DidClientEscape(client) + if (IsClientInPvP(client) || IsClientInPvE(client) || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !g_PlayerEliminated[client])) //DidClientEscape(client) { int weaponEnt = INVALID_ENT_REFERENCE; Handle weaponHandle = null; @@ -1953,7 +2065,7 @@ bool IsWeaponRestricted(TFClassType class,int itemDefInt) bool proxyBoss = false; for (int i = 0; i < MAX_BOSSES; i++) { - SF2NPC_BaseNPC Npc = view_as(i); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (!Npc.IsValid()) { continue; @@ -1966,7 +2078,7 @@ bool IsWeaponRestricted(TFClassType class,int itemDefInt) } if (g_RestrictedWeaponsConfig.JumpToKey("all")) { - //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef)); + //returnBool = (g_RestrictedWeaponsConfig.GetNum(itemDef)) != 0; //view_as bool value turn to 2 into a true value. if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) { @@ -2033,7 +2145,7 @@ bool IsWeaponRestricted(TFClassType class,int itemDefInt) if (bFoundSection) { - //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef, returnBool)); + //returnBool = (g_RestrictedWeaponsConfig.GetNum(itemDef, returnBool)) != 0; if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) { returnBool = true; diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp index 3b9f8f0f..79766a73 100644 --- a/addons/sourcemod/scripting/sf2/client/sprint.sp +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -1,8 +1,10 @@ #pragma semicolon 1 -bool g_PlayerSprint[MAXTF2PLAYERS] = { false, ... }; -int g_PlayerSprintPoints[MAXTF2PLAYERS] = { 100, ... }; -Handle g_PlayerSprintTimer[MAXTF2PLAYERS] = { null, ... }; +static bool g_PlayerSprint[MAXTF2PLAYERS] = { false, ... }; +static int g_PlayerSprintPoints[MAXTF2PLAYERS] = { 100, ... }; +static Handle g_PlayerSprintTimer[MAXTF2PLAYERS] = { null, ... }; + +static ConVar g_PlayerScareSprintBoost; void SetupSprint() { @@ -11,6 +13,8 @@ void SetupSprint() g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + + g_PlayerScareSprintBoost = CreateConVar("sf2_player_scare_boost", "0", "If player is in danger, provide a small boost in speed.", _, true, 0.0, true, 1.0); } static void OnPutInServer(SF2_BasePlayer client) @@ -22,13 +26,13 @@ static void OnPutInServer(SF2_BasePlayer client) static void OnJump(SF2_BasePlayer client) { - if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped) + if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped || client.IsLatched || client.IsTrapped) { return; } int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; - if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0 && !client.IsTrapped) + if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0) { if (client.GetSprintPoints() >= 2) { @@ -87,7 +91,7 @@ static void OnPlayerEscape(SF2_BasePlayer client) float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknown) { - float returnFloat = 340.0; + float returnFloat = 400.0; float returnFloat2 = returnFloat; Action action = Plugin_Continue; if (IsValidClient(client)) @@ -99,39 +103,39 @@ float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknow { case TFClass_Scout: { - returnFloat = 305.0; + returnFloat = 400.0; } case TFClass_Sniper: { - returnFloat = 295.0; + returnFloat = 370.0; } case TFClass_Soldier: { - returnFloat = 280.0; + returnFloat = 350.0; } case TFClass_DemoMan: { - returnFloat = 280.0; + returnFloat = 350.0; } case TFClass_Heavy: { - returnFloat = 280.0; + returnFloat = 350.0; } case TFClass_Medic: { - returnFloat = 290.0; + returnFloat = 370.0; } case TFClass_Pyro: { - returnFloat = 290.0; + returnFloat = 370.0; } case TFClass_Spy: { - returnFloat = 300.0; + returnFloat = 370.0; } case TFClass_Engineer: { - returnFloat = 295.0; + returnFloat = 370.0; } } @@ -165,6 +169,14 @@ int ClientGetSprintPoints(int client) void ClientSetSprintPoints(int client, int value) { g_PlayerSprintPoints[client] = value; + if (g_PlayerSprintPoints[client] > 100) + { + g_PlayerSprintPoints[client] = 100; + } + if (g_PlayerSprintPoints[client] < 0) + { + g_PlayerSprintPoints[client] = 0; + } } void ClientResetSprint(int client) @@ -229,7 +241,7 @@ void ClientStartSprint(int client) Call_Finish(); } -void ClientSprintTimer(int client, bool recharge=false) +void ClientSprintTimer(int client, bool recharge = false) { float rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 0.38 : 0.28; if (recharge) @@ -267,15 +279,15 @@ void ClientSprintTimer(int client, bool recharge=false) { if (class == TFClass_DemoMan) { - rate *= 1.15; + rate *= 1.1; } else if (class == TFClass_Medic || class == TFClass_Spy) { - rate *= 1.05; + rate *= 1.0; } else if (class == TFClass_Scout) { - rate *= 1.08; + rate *= 1.05; } } else @@ -349,6 +361,7 @@ void ClientHandleSprint(int client, bool sprint) } } } + bool IsClientReallySprinting(int client) { if (!IsClientSprinting(client)) @@ -459,7 +472,7 @@ static Action Timer_ClientRechargeSprint(Handle timer, any userid) static void Hook_ClientSprintingPreThink(int client) { - if (!IsClientReallySprinting(client)) + if (!IsClientSprinting(client)) { SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); @@ -485,7 +498,6 @@ static void Hook_ClientSprintingPreThink(int client) else if (fov >= targetFov) { ClientSetFOV(client, targetFov); - //SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); } } @@ -549,10 +561,17 @@ static void Hook_SprintThink(int client) continue; } + int ent = NPCGetEntIndex(i); + if (!ent || ent == INVALID_ENT_REFERENCE) + { + continue; + } + if (NPCGetType(i) == SF2BossType_Chaser) { - bossTarget = EntRefToEntIndex(g_SlenderTarget[i]); - state = g_SlenderState[i]; + SF2_ChaserEntity chaser = SF2_ChaserEntity(ent); + bossTarget = chaser.Target.index; + state = chaser.State; if ((state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) && ((bossTarget && bossTarget != INVALID_ENT_REFERENCE && (bossTarget == client || ClientGetDistanceFromEntity(client, bossTarget) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) @@ -726,33 +745,28 @@ static void Hook_SprintThink(int client) if (player.InCondition(TFCond_SpeedBuffAlly)) { walkSpeed += (walkSpeed * 0.115); - sprintSpeed += (sprintSpeed * 0.165); + sprintSpeed += (sprintSpeed * 0.125); } } else { if (player.InCondition(TFCond_SpeedBuffAlly)) { - walkSpeed += (walkSpeed * 0.105); - sprintSpeed += (sprintSpeed * 0.14); + walkSpeed += (walkSpeed * 0.11); + sprintSpeed += (sprintSpeed * 0.11); } } if (inDanger) { - if (!IsClassConfigsValid()) + if (g_PlayerScareSprintBoost.BoolValue) { - if (class != TFClass_Spy && class != TFClass_Pyro) - { - walkSpeed *= 1.34; - sprintSpeed *= 1.34; - } - else + if (!IsClassConfigsValid()) { - if (class == TFClass_Spy) + if (class != TFClass_Spy && class != TFClass_Pyro) { - walkSpeed *= 1.28; - sprintSpeed *= 1.28; + walkSpeed *= 1.34; + sprintSpeed *= 1.34; } else { @@ -766,7 +780,7 @@ static void Hook_SprintThink(int client) } int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) // Powerjack { walkSpeed *= 1.32; sprintSpeed *= 1.32; @@ -779,47 +793,43 @@ static void Hook_SprintThink(int client) } } } - } - else - { - float multiplier = g_ClassDangerSpeedMultipler[classToInt]; - if (class == TFClass_Pyro) + else { - weaponEnt = INVALID_ENT_REFERENCE; - for (int slot = 0; slot <= 5; slot++) + float multiplier = g_ClassDangerSpeedMultipler[classToInt]; + if (class == TFClass_Pyro) { - weaponEnt = player.GetWeaponSlot(slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = 0; slot <= 5; slot++) { - continue; - } + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - multiplier -= 0.02; + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) // Powerjack + { + multiplier -= 0.02; + } } } + walkSpeed *= multiplier; + sprintSpeed *= multiplier; } - walkSpeed *= multiplier; - sprintSpeed *= multiplier; } - if (!g_PlayerHints[player.index][PlayerHint_Sprint]) + if (!player.HasHint(PlayerHint_Sprint)) { player.ShowHint(PlayerHint_Sprint); } } - float sprintSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.425); - float walkSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.3); - if (player.GetSprintPoints() > 8) - { - sprintSpeedSubtract -= sprintSpeedSubtract * (player.GetSprintPoints() != 0 ? (float(player.GetSprintPoints()) / 100.0) : 0.0); - sprintSpeed -= sprintSpeedSubtract; - } - else + if (player.GetSprintPoints() <= 5) { + float sprintSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.425); + float walkSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.3); + sprintSpeedSubtract += 125; sprintSpeed -= sprintSpeedSubtract; walkSpeedSubtract += 25; @@ -845,12 +855,12 @@ static void Hook_SprintThink(int client) player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed/2.5); } } - player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed * 0.5); } else { player.SetPropFloat(Prop_Send, "m_flMaxspeed", 520.0); - player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed * 0.5); } } else @@ -863,13 +873,13 @@ static void Hook_SprintThink(int client) { if (SF_IsBoxingMap() || SF_IsRaidMap()) { - player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed*2.5); + player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed * 2.5); } else { - player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed/2.5); + player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed / 2.5); } } - player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", walkSpeed-20.0); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", walkSpeed * 0.5); } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/static.sp b/addons/sourcemod/scripting/sf2/client/static.sp index 2e1c9023..4071a368 100644 --- a/addons/sourcemod/scripting/sf2/client/static.sp +++ b/addons/sourcemod/scripting/sf2/client/static.sp @@ -53,7 +53,7 @@ static void OnPlayerEscape(SF2_BasePlayer client) ClientResetStatic(client.index); } -static void OnBossRemoved(int bossIndex) +static void OnBossRemoved(SF2NPC_BaseNPC npc) { for (int i = 1; i <= MaxClients; i++) { @@ -62,7 +62,7 @@ static void OnBossRemoved(int bossIndex) continue; } - if (NPCGetUniqueID(bossIndex) == g_PlayerStaticMaster[i]) + if (npc.UniqueID == g_PlayerStaticMaster[i]) { g_PlayerStaticMaster[i] = -1; diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index b413228a..8486d3f1 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -17,9 +17,18 @@ void Hook_ClientPreThink(int client) ClientProcessStaticShake(client); ClientProcessViewAngles(client); + if ((g_PlayerTrapped[client] || g_PlayerLatchedByTongue[client]) && !IsClientInGhostMode(client)) + { + TF2Attrib_SetByName(client, "increased jump height", 0.0); + } + else + { + TF2Attrib_SetByName(client, "increased jump height", 1.0); + } + if (IsClientInGhostMode(client)) { - SetEntityFlags(client, GetEntityFlags(client)^FL_EDICT_ALWAYS); + SetEntityFlags(client, GetEntityFlags(client) ^ FL_EDICT_ALWAYS); SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 520.0); SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); @@ -56,12 +65,12 @@ void Hook_ClientPreThink(int client) TF2_RemoveCondition(client, TFCond_Disguised); } - if (TF2_IsPlayerInCondition(client, TFCond_Taunting) && g_PlayerTrapped[client]) + if (TF2_IsPlayerInCondition(client, TFCond_Taunting) && (g_PlayerTrapped[client] || g_PlayerLatchedByTongue[client])) { TF2_RemoveCondition(client, TFCond_Taunting); } - if (TF2_IsPlayerInCondition(client,TFCond_Taunting) && class == TFClass_Soldier) + if (TF2_IsPlayerInCondition(client, TFCond_Taunting) && class == TFClass_Soldier) { int weaponEnt = GetPlayerWeaponSlot(client, TFWeaponSlot_Melee); if (weaponEnt && weaponEnt != INVALID_ENT_REFERENCE) @@ -76,14 +85,7 @@ void Hook_ClientPreThink(int client) if (roundState == 4) { - if (g_PlayerTrapped[client]) - { - TF2Attrib_SetByName(client, "increased jump height", 0.0); - } - else - { - TF2Attrib_SetByName(client, "increased jump height", 1.0); - } + } } else if (g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Blue) @@ -171,7 +173,7 @@ void Hook_ClientPreThink(int client) } } } - if (g_PlayerEliminated[client] && IsClientInPvP(client)) + if (g_PlayerEliminated[client] && (IsClientInPvP(client) || IsClientInPvE(client))) { SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); @@ -179,29 +181,26 @@ void Hook_ClientPreThink(int client) SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); if (IsClientInKart(client)) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); } } if (IsRoundInWarmup() || (IsRoundInIntro() && !g_PlayerEliminated[client]) || IsRoundEnding()) //I told you, stop breaking my plugin { if (IsClientInKart(client)) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); } } // Calculate player stress levels. if (GetGameTime() >= g_PlayerStressNextUpdateTime[client]) { - //float flPagePercent = g_PageMax != 0 ? float(g_PageCount) / float(g_PageMax) : 0.0; - //float flPageCountPercent = g_PageMax != 0? float(g_PlayerPageCount[client]) / float(g_PageMax) : 0.0; - g_PlayerStressNextUpdateTime[client] = GetGameTime() + 0.33; ClientAddStress(client, -0.01); @@ -230,18 +229,20 @@ void Hook_ClientPreThink(int client) ClientProcessVisibility(client); } -Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &damage,int &damagetype,int &weapon, float damageForce[3], float damagePosition[3],int damagecustom) +Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { if (!g_Enabled) { return Plugin_Continue; } + SF2_BasePlayer victimPlayer = SF2_BasePlayer(victim); + Action action = Plugin_Continue; float damage2 = damage; Call_StartForward(g_OnClientTakeDamageFwd); - Call_PushCell(victim); + Call_PushCell(victimPlayer.index); Call_PushCellRef(attacker); Call_PushCellRef(inflictor); Call_PushFloatRef(damage2); @@ -253,7 +254,7 @@ Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &d return Plugin_Changed; } - TFClassType class = TF2_GetPlayerClass(victim); + TFClassType class = victimPlayer.Class; int classToInt = view_as(class); if (IsRoundInWarmup() && IsValidClient(attacker)) @@ -287,22 +288,52 @@ Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &d TF2_RemoveCondition(victim, TFCond_Gas); } - if (IsValidClient(attacker) && IsValidClient(victim) && IsClientInPvP(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) + if (TF2_IsPlayerInCondition(victim, TFCond_Gas) && SF2_ChaserEntity(attacker).IsValid()) + { + TF2_IgnitePlayer(victim, victim); + TF2_RemoveCondition(victim, TFCond_Gas); + } + + if (IsValidClient(attacker) && IsValidClient(victim) && (IsClientInPvP(victim) || IsClientInPvE(victim)) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) { damage = 0.0; return Plugin_Changed; } - if (IsValidClient(attacker) && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && g_PlayerTrapped[victim]) + if (IsValidClient(attacker) && victimPlayer.IsValid && victimPlayer.Team == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && (victimPlayer.IsTrapped || victimPlayer.IsLatched)) { - if (!g_PlayerEliminated[attacker] && !g_PlayerEliminated[victim]) + if (!g_PlayerEliminated[attacker] && !victimPlayer.IsEliminated && (damagetype & 0x80) != 0) { - if (damagetype & 0x80) // 0x80 == melee damage + victimPlayer.IsTrapped = false; + if (victimPlayer.IsLatched) { - g_PlayerTrapped[victim] = false; - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); + victimPlayer.ChangeCondition(TFCond_Dazed, true); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + if (victimPlayer.Latcher != npc.Index) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + + chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString("break tongue"); + } } + victimPlayer.IsLatched = false; + victimPlayer.LatchCount = 0; + TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); + TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); } } @@ -321,12 +352,12 @@ Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &d } } - if (IsEntityAProjectile(inflictor)) + if (!SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) { int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); if (npcIndex != -1) { - bool attackEliminated = !!(NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS); + bool attackEliminated = (NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS) != 0; if (!attackEliminated && (GetClientTeam(victim) == TFTeam_Blue) && IsValidClient(victim) ) { damage = 0.0; @@ -347,11 +378,24 @@ Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &d } } + bool canDamage = false; if (attacker != victim && IsValidClient(attacker)) { + if (IsClientInPvP(victim) && IsClientInPvP(attacker)) + { + canDamage = true; + } + if (IsClientLeavingPvP(victim) && !IsClientInPvP(attacker)) + { + canDamage = true; + } + if (IsRoundInWarmup()) + { + canDamage = true; + } if (!IsRoundEnding()) { - if (IsRoundInWarmup() || (IsClientInPvP(victim) && IsClientInPvP(attacker))) + if (canDamage) { if (attacker == inflictor) { @@ -755,6 +799,7 @@ void ClientOnButtonRelease(SF2_BasePlayer client, int button) #define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD "" #define SF2_PLAYER_HUD_INFINITY_SYMBOL "∞" #define SF2_PLAYER_HUD_SPRINT_SYMBOL "»" +#define SF2_PLAYER_HUD_HEALTH_SYMBOL "♥" Action Timer_ClientAverageUpdate(Handle timer) { @@ -781,11 +826,12 @@ Action Timer_ClientAverageUpdate(Handle timer) for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i)) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) { continue; } - if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) + if (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud) { hudColorHealthy = { 50, 255, 50 }; } @@ -794,60 +840,44 @@ Action Timer_ClientAverageUpdate(Handle timer) hudColorHealthy = { 150, 255, 150 }; } - if (IsPlayerAlive(i) && !IsClientInDeathCam(i)) + if (player.IsAlive && !player.IsInDeathCam) { - if (!g_PlayerEliminated[i]) + if (!player.IsEliminated) { - if (DidClientEscape(i)) + if (player.HasEscaped) { continue; } int maxBars = 12; int bars; + char buffer2[64]; if (!SF_IsRaidMap() && !SF_IsBoxingMap()) { - bars = RoundToCeil(float(maxBars) * ClientGetBlinkMeter(i)); + float percent = float(player.Health) / float(player.MaxHealth); + if (percent > 1.0) + { + percent = 1.0; + } + bars = RoundToCeil(float(maxBars) * percent); if (bars > maxBars) { bars = maxBars; } - - if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) + FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_HEALTH_SYMBOL); + for (int i2 = 0; i2 < maxBars; i2++) { - if (bars != 0) + if (i2 < bars) { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_ON); + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); } else { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OFF); - } - } - else - { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OLD); - } - - if (IsInfiniteBlinkEnabled()) - { - StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); - } - else - { - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); } } } + if (!SF_SpecialRound(SPECIALROUND_LIGHTSOUT) && !SF_IsRaidMap() && !SF_IsBoxingMap()) { bars = RoundToCeil(float(maxBars) * ClientGetFlashlightBatteryLife(i)); @@ -856,7 +886,6 @@ Action Timer_ClientAverageUpdate(Handle timer) bars = maxBars; } - char buffer2[64]; FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL); StrCat(buffer, sizeof(buffer), buffer2); @@ -870,23 +899,22 @@ Action Timer_ClientAverageUpdate(Handle timer) { if (i2 < bars) { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); } else { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); } } } } - bars = RoundToCeil(float(maxBars) * (float(ClientGetSprintPoints(i)) / 100.0)); + bars = RoundToCeil(float(maxBars) * (float(player.GetSprintPoints()) / 100.0)); if (bars > maxBars) { bars = maxBars; } - char buffer2[64]; FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_SPRINT_SYMBOL); StrCat(buffer, sizeof(buffer), buffer2); @@ -900,17 +928,16 @@ Action Timer_ClientAverageUpdate(Handle timer) { if (i2 < bars) { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); } else { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); } } } - float healthRatio = float(GetEntProp(i, Prop_Send, "m_iHealth")) / float(SDKCall(g_SDKGetMaxHealth, i)); - + float healthRatio = float(player.Health) / float(player.MaxHealth); int color[3]; for (int i2 = 0; i2 < 3; i2++) { @@ -942,15 +969,63 @@ Action Timer_ClientAverageUpdate(Handle timer) 0.07, 0.5); } - ShowSyncHudText(i, g_HudSync2, buffer); + ShowSyncHudText(player.index, g_HudSync2, buffer); + if (!SF_IsRaidMap() && !SF_IsBoxingMap() && !IsInfiniteBlinkEnabled() && player.HasStartedBlinking) + { + bars = RoundToCeil(float(maxBars) * player.BlinkMeter); + if (bars > maxBars) + { + bars = maxBars; + } + + if (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud) + { + if (bars != 0) + { + FormatEx(buffer, sizeof(buffer), "\n%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_ON); + } + else + { + FormatEx(buffer, sizeof(buffer), "\n%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OFF); + } + } + else + { + FormatEx(buffer, sizeof(buffer), "\n%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OLD); + } + + for (int i2 = 0; i2 < maxBars; i2++) + { + if (i2 < bars) + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + } + else + { + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + } + } + + SetHudTextParams(-1.0, 0.65, + 0.3, + color[0], + color[1], + color[2], + 40, + _, + 1.0, + 0.07, + 0.5); + ShowSyncHudText(player.index, g_HudSync4, buffer); + } buffer[0] = '\0'; } else { - if (g_PlayerProxy[i]) + if (player.IsProxy) { int maxBars = 12; - int bars = RoundToCeil(float(maxBars) * (float(g_PlayerProxyControl[i]) / 100.0)); + int bars = RoundToCeil(float(maxBars) * (float(player.ProxyControl) / 100.0)); if (bars > maxBars) { bars = maxBars; @@ -962,11 +1037,11 @@ Action Timer_ClientAverageUpdate(Handle timer) { if (i2 < bars) { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); } else { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); + StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[player.index].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); } } @@ -980,12 +1055,12 @@ Action Timer_ClientAverageUpdate(Handle timer) 1.0, 0.07, 0.5); - ShowSyncHudText(i, g_HudSync2, buffer); + ShowSyncHudText(player.index, g_HudSync2, buffer); } } } - ClientUpdateListeningFlags(i); - ClientUpdateMusicSystem(i); + player.UpdateListeningFlags(); + player.UpdateMusicSystem(); } return Plugin_Continue; diff --git a/addons/sourcemod/scripting/sf2/client/ultravision.sp b/addons/sourcemod/scripting/sf2/client/ultravision.sp index 694e3e0c..f2a2cd69 100644 --- a/addons/sourcemod/scripting/sf2/client/ultravision.sp +++ b/addons/sourcemod/scripting/sf2/client/ultravision.sp @@ -76,7 +76,7 @@ void ClientActivateUltravision(int client, bool nightVision = false) float eyePos[3]; GetClientEyePosition(client, eyePos); - TeleportEntity(ent, eyePos, view_as({ 90.0, 0.0, 0.0 }), NULL_VECTOR); + TeleportEntity(ent, eyePos, { 90.0, 0.0, 0.0 }, NULL_VECTOR); if (nightVision && !g_PlayerEliminated[client]) { switch (g_NightvisionType) diff --git a/addons/sourcemod/scripting/sf2/debug.sp b/addons/sourcemod/scripting/sf2/debug.sp index e18349c4..e08792a0 100644 --- a/addons/sourcemod/scripting/sf2/debug.sp +++ b/addons/sourcemod/scripting/sf2/debug.sp @@ -20,8 +20,9 @@ #define DEBUG_BOSS_ANIMATION (1 << 10) #define DEBUG_EVENT (1 << 11) #define DEBUG_KILLICONS (1 << 12) -#define DEBUG_ARRAYLIST (1 << 13) #define DEBUG_BOSS_IDLE (1 << 14) +#define DEBUG_BOSS_EYES (1 << 15) +#define DEBUG_BOSS_PATH (1 << 16) int g_PlayerDebugFlags[MAXTF2PLAYERS] = { 0, ... }; @@ -45,8 +46,9 @@ void InitializeDebug() RegAdminCmd("sm_sf2_debug_ghost_mode", Command_DebugGhostMode, ADMFLAG_CHEATS); RegAdminCmd("sm_sf2_debug_events", Command_DebugEvent, ADMFLAG_CHEATS); RegAdminCmd("sm_sf2_debug_kill_icons", Command_DebugKillIcons, ADMFLAG_CHEATS); - RegAdminCmd("sm_sf2_debug_arraylists", Command_DebugArrayLists, ADMFLAG_CHEATS); RegAdminCmd("sm_sf2_debug_boss_idle", Command_DebugBossIdle, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_debug_boss_eyes", Command_DebugBossEyes, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_debug_boss_path", Command_DebugBossPath, ADMFLAG_CHEATS); } void InitializeDebugLogging() @@ -71,9 +73,9 @@ void DebugMessage(const char[] message, any ...) LogToFile(g_DebugLogFilePath, debugMessage); } -void SendDebugMessageToPlayer(int client,int debugFlags,int type, const char[] message, any ...) +void SendDebugMessageToPlayer(int client, int debugFlags, int type, const char[] message, any ...) { - if (!IsClientInGameEx(client) || IsFakeClient(client)) + if (!IsValidClient(client) || IsFakeClient(client)) { return; } @@ -101,14 +103,14 @@ void SendDebugMessageToPlayer(int client,int debugFlags,int type, const char[] m } } -void SendDebugMessageToPlayers(int debugFlags,int type, const char[] message, any ...) +void SendDebugMessageToPlayers(int debugFlags, int type, const char[] message, any ...) { char msg[1024]; VFormat(msg, sizeof(msg), message, 4); for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGameEx(i) || IsFakeClient(i)) + if (!IsValidClient(i) || IsFakeClient(i)) { continue; } @@ -134,14 +136,14 @@ void SendDebugMessageToPlayers(int debugFlags,int type, const char[] message, an } } -static Action Command_DebugBossTeleport(int client,int args) +static Action Command_DebugBossTeleport(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_TELEPORTATION); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_TELEPORTATION) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_TELEPORTATION; @@ -156,14 +158,14 @@ static Action Command_DebugBossTeleport(int client,int args) return Plugin_Handled; } -static Action Command_DebugBossChase(int client,int args) +static Action Command_DebugBossChase(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_CHASE); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_CHASE) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_CHASE; @@ -178,14 +180,14 @@ static Action Command_DebugBossChase(int client,int args) return Plugin_Handled; } -static Action Command_DebugBossIdle(int client,int args) +static Action Command_DebugBossIdle(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_IDLE); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_IDLE) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_IDLE; @@ -200,14 +202,58 @@ static Action Command_DebugBossIdle(int client,int args) return Plugin_Handled; } -static Action Command_DebugBossAnimation(int client,int args) +static Action Command_DebugBossEyes(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_ANIMATION); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_EYES) != 0; + if (!inMode) + { + g_PlayerDebugFlags[client] |= DEBUG_BOSS_EYES; + PrintToChat(client, "Enabled debugging boss eyes."); + } + else + { + g_PlayerDebugFlags[client] &= ~DEBUG_BOSS_EYES; + PrintToChat(client, "Disabled debugging boss eyes."); + } + + return Plugin_Handled; +} + +static Action Command_DebugBossPath(int client, int args) +{ + if (client < 1 || client > MaxClients) + { + return Plugin_Handled; + } + + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_PATH) != 0; + if (!inMode) + { + g_PlayerDebugFlags[client] |= DEBUG_BOSS_PATH; + PrintToChat(client, "Enabled debugging boss pathing."); + } + else + { + g_PlayerDebugFlags[client] &= ~DEBUG_BOSS_PATH; + PrintToChat(client, "Disabled debugging boss pathing."); + } + + return Plugin_Handled; +} + +static Action Command_DebugBossAnimation(int client, int args) +{ + if (client < 1 || client > MaxClients) + { + return Plugin_Handled; + } + + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_ANIMATION) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_ANIMATION; @@ -222,14 +268,14 @@ static Action Command_DebugBossAnimation(int client,int args) return Plugin_Handled; } -static Action Command_DebugNextbot(int client,int args) +static Action Command_DebugNextbot(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_NEXTBOT); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_NEXTBOT) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_NEXTBOT; @@ -251,7 +297,7 @@ static Action Command_DebugPlayerStress(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_PLAYER_STRESS); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_PLAYER_STRESS) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_PLAYER_STRESS; @@ -273,7 +319,7 @@ static Action Command_DebugBossProxies(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_PROXIES); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_PROXIES) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_PROXIES; @@ -294,7 +340,7 @@ static Action Command_DebugHitbox(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_HITBOX); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_HITBOX) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_HITBOX; @@ -316,7 +362,7 @@ static Action Command_DebugStun(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_BOSS_STUN); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_BOSS_STUN) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_BOSS_STUN; @@ -338,7 +384,7 @@ static Action Command_DebugGhostMode(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_GHOSTMODE); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_GHOSTMODE) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_GHOSTMODE; @@ -360,7 +406,7 @@ static Action Command_DebugEntity(int client, int args) return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_ENTITIES); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_ENTITIES) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_ENTITIES; @@ -375,14 +421,14 @@ static Action Command_DebugEntity(int client, int args) return Plugin_Handled; } -static Action Command_DebugEvent(int client,int args) +static Action Command_DebugEvent(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_EVENT); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_EVENT) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_EVENT; @@ -397,14 +443,14 @@ static Action Command_DebugEvent(int client,int args) return Plugin_Handled; } -static Action Command_DebugKillIcons(int client,int args) +static Action Command_DebugKillIcons(int client, int args) { if (client < 1 || client > MaxClients) { return Plugin_Handled; } - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_KILLICONS); + bool inMode = (g_PlayerDebugFlags[client] & DEBUG_KILLICONS) != 0; if (!inMode) { g_PlayerDebugFlags[client] |= DEBUG_KILLICONS; @@ -419,24 +465,49 @@ static Action Command_DebugKillIcons(int client,int args) return Plugin_Handled; } -static Action Command_DebugArrayLists(int client,int args) +void TE_DrawBox(int client, float origin[3], float endOrigin[3], float mins[3], float maxs[3], float duration = 0.1, int laserIndex, int color[4]) { - if (client < 1 || client > MaxClients) - { - return Plugin_Handled; - } - - bool inMode = !!(g_PlayerDebugFlags[client] & DEBUG_ARRAYLIST); - if (!inMode) + if( mins[0] == maxs[0] && mins[1] == maxs[1] && mins[2] == maxs[2] ) { - g_PlayerDebugFlags[client] |= DEBUG_ARRAYLIST; - PrintToChat(client, "Enabled debugging array lists."); + mins = {-15.0, -15.0, -15.0}; + maxs = {15.0, 15.0, 15.0}; } else { - g_PlayerDebugFlags[client] &= ~DEBUG_ARRAYLIST; - PrintToChat(client, "Disabled debugging array lists."); - } + AddVectors(endOrigin, maxs, maxs); + AddVectors(origin, mins, mins); + } + + float pos1[3], pos2[3], pos3[3], pos4[3], pos5[3], pos6[3]; + pos1 = maxs; + pos1[0] = mins[0]; + pos2 = maxs; + pos2[1] = mins[1]; + pos3 = maxs; + pos3[2] = mins[2]; + pos4 = mins; + pos4[0] = maxs[0]; + pos5 = mins; + pos5[1] = maxs[1]; + pos6 = mins; + pos6[2] = maxs[2]; + + TE_SendBeam(client, maxs, pos1, duration, laserIndex, color); + TE_SendBeam(client, maxs, pos2, duration, laserIndex, color); + TE_SendBeam(client, maxs, pos3, duration, laserIndex, color); + TE_SendBeam(client, pos6, pos1, duration, laserIndex, color); + TE_SendBeam(client, pos6, pos2, duration, laserIndex, color); + TE_SendBeam(client, pos6, mins, duration, laserIndex, color); + TE_SendBeam(client, pos4, mins, duration, laserIndex, color); + TE_SendBeam(client, pos5, mins, duration, laserIndex, color); + TE_SendBeam(client, pos5, pos1, duration, laserIndex, color); + TE_SendBeam(client, pos5, pos3, duration, laserIndex, color); + TE_SendBeam(client, pos4, pos3, duration, laserIndex, color); + TE_SendBeam(client, pos4, pos2, duration, laserIndex, color); +} - return Plugin_Handled; +void TE_SendBeam(int client, float mins[3], float maxs[3], float duration = 0.1, int laserIndex, int color[4]) +{ + TE_SetupBeamPoints(mins, maxs, laserIndex, laserIndex, 0, 30, duration, 1.0, 1.0, 1, 0.0, color, 30); + TE_SendToClient(client); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index 955591df..780094cd 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -8,10 +8,30 @@ static EffectEvent g_EntityEffectEvent[2049]; static EffectType g_EntityEffectType[2049]; static ArrayList g_NpcEffectsArray[MAX_BOSSES]; -static float g_EffectSpotlightEndLength[2049]; -static int g_EffectSpotlightEndEntity[2049] = { INVALID_ENT_REFERENCE, ... }; -void SlenderSpawnEffects(int bossIndex) +void InitializeEffects() +{ + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (g_NpcEffectsArray[i] == null) + { + continue; + } + + int index = g_NpcEffectsArray[i].FindValue(ent.index); + if (index != -1) + { + g_NpcEffectsArray[i].Erase(index); + } + } +} + +void SlenderSpawnEffects(ArrayList effects, int bossIndex, bool nonEffects = true, float overridePos[3] = NULL_VECTOR, float overrideAng[3] = NULL_VECTOR, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) { if (bossIndex < 0 || bossIndex >= MAX_BOSSES) { @@ -24,8 +44,6 @@ void SlenderSpawnEffects(int bossIndex) return; } - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int slenderEnt = NPCGetEntIndex(bossIndex); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -36,127 +54,126 @@ void SlenderSpawnEffects(int bossIndex) g_NpcEffectsArray[bossIndex] = new ArrayList(); } - if (NPCGetDiscoModeState(bossIndex)) + if (nonEffects) { - int light = CreateEntityByName("light_dynamic"); - int particle = CreateEntityByName("info_particle_system"); - if (light != -1) + if (NPCGetDiscoModeState(bossIndex)) { - float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); - effectPos[2] = 50.0; - VectorTransform(effectPos, basePosX, baseAngX, effectPos); - AddVectors(effectAng, baseAngX, effectAng); - TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); - SetVariantInt(5); - AcceptEntityInput(light, "Brightness"); - SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); - AcceptEntityInput(light, "Distance"); - SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); - AcceptEntityInput(light, "spotlight_radius"); - SetVariantInt(1); - AcceptEntityInput(light, "cone"); - DispatchKeyValue(light, "spawnflags", "0"); - int rChase = GetRandomInt(75, 250); - int gChase = GetRandomInt(75, 250); - int bChase = GetRandomInt(75, 250); - SetEntityRenderColor(light, rChase, gChase, bChase, 255); - DispatchSpawn(light); - ActivateEntity(light); - AcceptEntityInput(light, "TurnOn"); - SetVariantString("!activator"); - AcceptEntityInput(light, "SetParent", slenderEnt); - CreateTimer(0.1, Timer_DiscoLight, EntIndexToEntRef(light), TIMER_REPEAT); - SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); - g_EntityEffectType[light] = EffectType_DynamicLight; - g_NpcEffectsArray[bossIndex].Push(light); - } + int light = CreateEntityByName("light_dynamic"); + int particle = CreateEntityByName("info_particle_system"); + if (light != -1) + { + float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; + GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); + GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); + effectPos[2] = 50.0; + VectorTransform(effectPos, basePosX, baseAngX, effectPos); + AddVectors(effectAng, baseAngX, effectAng); + TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); + SetVariantInt(5); + AcceptEntityInput(light, "Brightness"); + SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); + AcceptEntityInput(light, "Distance"); + SetVariantFloat(GetRandomFloat(NPCGetDiscoModeRadiusMin(bossIndex), NPCGetDiscoModeRadiusMax(bossIndex))); + AcceptEntityInput(light, "spotlight_radius"); + SetVariantInt(1); + AcceptEntityInput(light, "cone"); + DispatchKeyValue(light, "spawnflags", "0"); + int rChase = GetRandomInt(75, 250); + int gChase = GetRandomInt(75, 250); + int bChase = GetRandomInt(75, 250); + SetEntityRenderColor(light, rChase, gChase, bChase, 255); + DispatchSpawn(light); + ActivateEntity(light); + AcceptEntityInput(light, "TurnOn"); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", slenderEnt); + CreateTimer(0.1, Timer_DiscoLight, EntIndexToEntRef(light), TIMER_REPEAT); + SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); + g_EntityEffectType[light] = EffectType_DynamicLight; + g_NpcEffectsArray[bossIndex].Push(light); + } - if (particle != -1) - { - float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); - CopyVector(NPCGetDiscoModePos(bossIndex), effectPos); - VectorTransform(effectPos, basePosX, baseAngX, effectPos); - AddVectors(effectAng, baseAngX, effectAng); - TeleportEntity(particle, effectPos, effectAng, NULL_VECTOR); - - DispatchKeyValue(particle, "effect_name", "utaunt_disco_party"); - DispatchSpawn(particle); - ActivateEntity(particle); - AcceptEntityInput(particle, "start"); - SetVariantString("!activator"); - AcceptEntityInput(particle, "SetParent", slenderEnt); - SDKHook(particle, SDKHook_SetTransmit, Hook_EffectTransmitX); - g_EntityEffectType[particle] = EffectType_Particle; - g_NpcEffectsArray[bossIndex].Push(particle); + if (particle != -1) + { + float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; + GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); + GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); + CopyVector(NPCGetDiscoModePos(bossIndex), effectPos); + VectorTransform(effectPos, basePosX, baseAngX, effectPos); + AddVectors(effectAng, baseAngX, effectAng); + TeleportEntity(particle, effectPos, effectAng, NULL_VECTOR); + + DispatchKeyValue(particle, "effect_name", "utaunt_disco_party"); + DispatchSpawn(particle); + ActivateEntity(particle); + AcceptEntityInput(particle, "start"); + SetVariantString("!activator"); + AcceptEntityInput(particle, "SetParent", slenderEnt); + SDKHook(particle, SDKHook_SetTransmit, Hook_EffectTransmitX); + g_EntityEffectType[particle] = EffectType_Particle; + g_NpcEffectsArray[bossIndex].Push(particle); + } } - } - if (NPCGetFestiveLightState(bossIndex)) - { - int light = CreateEntityByName("light_dynamic"); - if (light != -1) + if (NPCGetFestiveLightState(bossIndex)) { - float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); - CopyVector(NPCGetFestiveLightPosition(bossIndex), effectPos); - CopyVector(NPCGetFestiveLightAngle(bossIndex), effectAng); - VectorTransform(effectPos, basePosX, baseAngX, effectPos); - AddVectors(effectAng, baseAngX, effectAng); - TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); - SetVariantInt(NPCGetFestiveLightBrightness(bossIndex)); - AcceptEntityInput(light, "Brightness"); - SetVariantFloat(NPCGetFestiveLightDistance(bossIndex)); - AcceptEntityInput(light, "Distance"); - SetVariantFloat(NPCGetFestiveLightRadius(bossIndex)); - AcceptEntityInput(light, "spotlight_radius"); - SetVariantInt(1); - AcceptEntityInput(light, "cone"); - DispatchKeyValue(light, "spawnflags", "0"); - int funnyFestive = GetRandomInt(1, 3); - switch (funnyFestive) + int light = CreateEntityByName("light_dynamic"); + if (light != -1) { - case 1: - { - SetEntityRenderColor(light, 230, 37, 37, 255); - } - case 2: + float effectPos[3], effectAng[3], basePosX[3], baseAngX[3]; + GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePosX); + GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAngX); + CopyVector(NPCGetFestiveLightPosition(bossIndex), effectPos); + CopyVector(NPCGetFestiveLightAngle(bossIndex), effectAng); + VectorTransform(effectPos, basePosX, baseAngX, effectPos); + AddVectors(effectAng, baseAngX, effectAng); + TeleportEntity(light, effectPos, effectAng, NULL_VECTOR); + SetVariantInt(NPCGetFestiveLightBrightness(bossIndex)); + AcceptEntityInput(light, "Brightness"); + SetVariantFloat(NPCGetFestiveLightDistance(bossIndex)); + AcceptEntityInput(light, "Distance"); + SetVariantFloat(NPCGetFestiveLightRadius(bossIndex)); + AcceptEntityInput(light, "spotlight_radius"); + SetVariantInt(1); + AcceptEntityInput(light, "cone"); + DispatchKeyValue(light, "spawnflags", "0"); + int funnyFestive = GetRandomInt(1, 3); + switch (funnyFestive) { - SetEntityRenderColor(light, 94, 227, 79, 255); - } - case 3: - { - SetEntityRenderColor(light, 235, 235, 235, 255); + case 1: + { + SetEntityRenderColor(light, 230, 37, 37, 255); + } + case 2: + { + SetEntityRenderColor(light, 79, 227, 79, 255); + } + case 3: + { + SetEntityRenderColor(light, 255, 255, 255, 255); + } } + DispatchSpawn(light); + ActivateEntity(light); + CreateTimer(0.5, Timer_FestiveLight, EntIndexToEntRef(light), TIMER_REPEAT); + AcceptEntityInput(light, "TurnOn"); + SetVariantString("!activator"); + AcceptEntityInput(light, "SetParent", slenderEnt); + SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); + g_EntityEffectType[light] = EffectType_Particle; + g_NpcEffectsArray[bossIndex].Push(light); } - DispatchSpawn(light); - ActivateEntity(light); - CreateTimer(0.5, Timer_FestiveLight, EntIndexToEntRef(light), TIMER_REPEAT); - AcceptEntityInput(light, "TurnOn"); - SetVariantString("!activator"); - AcceptEntityInput(light, "SetParent", slenderEnt); - SDKHook(light, SDKHook_SetTransmit, Hook_EffectTransmitX); - g_EntityEffectType[light] = EffectType_Particle; - g_NpcEffectsArray[bossIndex].Push(light); } } - ArrayList array = GetBossProfileEffectsArray(profile); - char sectionName[64]; - - if (array == null) + if (effects == null) { return; } - float basePos[3], baseAng[3]; SF2BossProfileBaseEffectInfo effectsInfo; - for (int i = 0, size = array.Length; i < size; i++) + for (int i = 0, size = effects.Length; i < size; i++) { - array.GetArray(i, effectsInfo, sizeof(effectsInfo)); + effects.GetArray(i, effectsInfo, sizeof(effectsInfo)); // Validate effect event and type. Check to see if it matches with ours. if (effectsInfo.Event != EffectEvent_Invalid) @@ -166,387 +183,444 @@ void SlenderSpawnEffects(int bossIndex) // Check base position behavior. if (!slenderEnt || slenderEnt == INVALID_ENT_REFERENCE) { - LogError("Could not spawn effect %s for boss %d: unable to read position due to boss entity not in game!"); + LogError("Could not spawn effect %s for boss %d: unable to read position and angles due to boss entity not in game!"); continue; } - GetEntPropVector(slenderEnt, Prop_Data, "m_vecAbsOrigin", basePos); - - if (!slenderEnt || slenderEnt == INVALID_ENT_REFERENCE) + if (effectsInfo.Delay > 0.0) { - LogError("Could not spawn effect %s for boss %d: unable to read angles due to boss entity not in game!"); - continue; + DataPack pack; + CreateDataTimer(effectsInfo.Delay, Timer_SpawnEffect, pack, TIMER_FLAG_NO_MAPCHANGE); + pack.WriteCellArray(effectsInfo, sizeof(effectsInfo)); + pack.WriteCell(bossIndex); + pack.WriteCellArray(overridePos, sizeof(overridePos)); + pack.WriteCellArray(overrideAng, sizeof(overrideAng)); + pack.WriteCell(output); + pack.WriteCell(entityOverride); + } + else + { + SpawnEffect(effectsInfo, bossIndex, overridePos, overrideAng, output, entityOverride); } + } + else + { + LogError("Could not spawn effect %s for boss %d: invalid type!", effectsInfo.SectionName, bossIndex); + } + } + } +} - GetEntPropVector(slenderEnt, Prop_Data, "m_angAbsRotation", baseAng); +static Action Timer_SpawnEffect(Handle timer, DataPack pack) +{ + pack.Reset(); + SF2BossProfileBaseEffectInfo effect; + pack.ReadCellArray(effect, sizeof(effect)); + int bossIndex = pack.ReadCell(); + float overridePos[3], overrideAng[3]; + pack.ReadCellArray(overridePos, sizeof(overridePos)); + pack.ReadCellArray(overrideAng, sizeof(overrideAng)); + ArrayList output = pack.ReadCell(); + int entityOverride = pack.ReadCell(); + SpawnEffect(effect, bossIndex, overridePos, overrideAng, output, entityOverride); + return Plugin_Stop; +} - int difficultyIndex = effectsInfo.DifficultyIndexes; - char indexes[8], currentIndex[2]; - FormatEx(indexes, sizeof(indexes), "%d", difficultyIndex); - FormatEx(currentIndex, sizeof(currentIndex), "%d", g_DifficultyConVar.IntValue); - char number = currentIndex[0]; - int difficultyNumber = 0; - if (FindCharInString(indexes, number) != -1) +static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, float overridePos[3] = NULL_VECTOR, float overrideAng[3] = NULL_VECTOR, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) +{ + int slenderEnt = NPCGetEntIndex(bossIndex); + int attacher = slenderEnt; + if (entityOverride != INVALID_ENT_REFERENCE && effectsInfo.AttachPlayer) + { + attacher = entityOverride; + } + int difficulty = GetLocalGlobalDifficulty(bossIndex); + float basePos[3], baseAng[3]; + if (!IsNullVector(overridePos)) + { + basePos = overridePos; + } + else + { + GetEntPropVector(attacher, Prop_Data, "m_vecAbsOrigin", basePos); + } + if (!IsNullVector(overrideAng)) + { + baseAng = overrideAng; + } + else + { + GetEntPropVector(attacher, Prop_Data, "m_angAbsRotation", baseAng); + } + + int difficultyIndex = effectsInfo.DifficultyIndexes; + char indexes[8], currentIndex[2]; + FormatEx(indexes, sizeof(indexes), "%d", difficultyIndex); + FormatEx(currentIndex, sizeof(currentIndex), "%d", g_DifficultyConVar.IntValue); + char number = currentIndex[0]; + int difficultyNumber = 0; + if (FindCharInString(indexes, number) != -1) + { + difficultyNumber += g_DifficultyConVar.IntValue; + } + if (indexes[0] != '\0' && currentIndex[0] != '\0' && difficultyNumber != -1) + { + int currentIntegerIndex = StringToInt(currentIndex); + if (difficultyNumber != currentIntegerIndex) + { + return; + } + } + + int entity = -1; + bool isEntity = true; + + switch (effectsInfo.Type) + { + case EffectType_Steam: + { + entity = CreateEntityByName("env_steam"); + } + case EffectType_DynamicLight: + { + entity = CreateEntityByName("light_dynamic"); + } + case EffectType_Particle: + { + entity = CreateEntityByName("info_particle_system"); + } + case EffectType_Trail: + { + entity = CreateEntityByName("env_spritetrail"); + } + case EffectType_PropDynamic: + { + entity = CreateEntityByName("prop_dynamic"); + } + case EffectType_PointSpotlight: + { + entity = CreateEntityByName("sf2_point_spotlight"); + } + case EffectType_Sprite: + { + entity = CreateEntityByName("env_sprite"); + } + case EffectType_TempEntBeamRing, EffectType_TempEntParticle, EffectType_Sound: + { + isEntity = false; + } + } + + float effectPos[3], effectAng[3]; + + effectPos = effectsInfo.Origin; + effectAng = effectsInfo.Angles; + + VectorTransform(effectPos, basePos, baseAng, effectPos); + AddVectors(effectAng, baseAng, effectAng); + + if (entity != -1 && isEntity) + { + TeleportEntity(entity, effectPos, effectAng, NULL_VECTOR); + char value[PLATFORM_MAX_PATH]; + DispatchKeyValueInt(entity, "renderamt", effectsInfo.FadeAlpha); + DispatchKeyValueInt(entity, "rendermode", view_as(effectsInfo.RenderModes)); + DispatchKeyValueInt(entity, "renderfx", view_as(effectsInfo.RenderEffects)); + DispatchKeyValueInt(entity, "spawnflags", effectsInfo.SpawnFlags); + + switch (effectsInfo.Type) + { + case EffectType_Steam: + { + DispatchKeyValueInt(entity, "SpreadSpeed", effectsInfo.SteamSpreadSpeed); + DispatchKeyValueInt(entity, "Speed", effectsInfo.SteamSpeed); + DispatchKeyValueInt(entity, "StartSize", effectsInfo.SteamStartSize); + DispatchKeyValueInt(entity, "EndSize", effectsInfo.SteamEndSize); + DispatchKeyValueInt(entity, "Rate", effectsInfo.SteamRate); + DispatchKeyValueInt(entity, "Jetlength", effectsInfo.SteamJetLength); + DispatchKeyValueFloat(entity, "RollSpeed", effectsInfo.SteamRollSpeed); + DispatchKeyValueInt(entity, "type", effectsInfo.SteamType); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_DynamicLight: + { + SetVariantInt(effectsInfo.LightBrightness); + AcceptEntityInput(entity, "Brightness"); + SetVariantFloat(effectsInfo.LightMaxDistance); + AcceptEntityInput(entity, "Distance"); + SetVariantFloat(effectsInfo.LightMaxDistance); + AcceptEntityInput(entity, "spotlight_radius"); + SetVariantInt(effectsInfo.LightCone); + AcceptEntityInput(entity, "cone"); + DispatchSpawn(entity); + ActivateEntity(entity); + + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); + + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + SetEntProp(entity, Prop_Data, "m_LightStyle", effectsInfo.LightStyle); + } + case EffectType_Particle: + { + DispatchKeyValue(entity, "effect_name", effectsInfo.ParticleName); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_Trail: + { + DispatchKeyValueFloat(entity, "lifetime", effectsInfo.TrailTime); + DispatchKeyValueFloat(entity, "startwidth", effectsInfo.TrailStartWidth); + DispatchKeyValueFloat(entity, "endwidth", effectsInfo.TrailEndWidth); + DispatchKeyValue(entity, "spritename", effectsInfo.TrailName); + SetEntPropFloat(entity, Prop_Send, "m_flTextureRes", 0.05); + + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); + + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_PropDynamic: + { + DispatchKeyValue(entity, "model", effectsInfo.ModelName); + float modelScale = effectsInfo.ModelScale; + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES) && modelScale != GetEntPropFloat(attacher, Prop_Send, "m_flModelScale") && !effectsInfo.AttachPlayer) { - difficultyNumber += g_DifficultyConVar.IntValue; + modelScale *= 0.5; } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && difficultyNumber != -1) + DispatchKeyValueFloat(entity, "modelscale", modelScale); + SetEntProp(entity, Prop_Send, "m_nSkin", effectsInfo.ModelSkin); + SetEntProp(entity, Prop_Send, "m_fEffects", EF_BONEMERGE|EF_PARENT_ANIMATES); + if (effectsInfo.ModelAnimation[0] != '\0') { - int currentIntegerIndex = StringToInt(currentIndex); - if (difficultyNumber != currentIntegerIndex) - { - continue; - } + SetVariantString(value); + AcceptEntityInput(entity, "SetAnimation"); } - int entity = -1; + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - switch (effectsInfo.Type) - { - case EffectType_Steam: - { - entity = CreateEntityByName("env_steam"); - } - case EffectType_DynamicLight: - { - entity = CreateEntityByName("light_dynamic"); - } - case EffectType_Particle: - { - entity = CreateEntityByName("info_particle_system"); - } - case EffectType_Trail: - { - entity = CreateEntityByName("env_spritetrail"); - } - case EffectType_PropDynamic: - { - entity = CreateEntityByName("prop_dynamic"); - } - case EffectType_PointSpotlight: - { - entity = CreateEntityByName("env_beam"); - } - case EffectType_Sprite: - { - entity = CreateEntityByName("env_sprite"); - } - } + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + DispatchSpawn(entity); + ActivateEntity(entity); + } + case EffectType_PointSpotlight: + { + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - if (entity != -1) + SF2PointSpotlightEntity spotlight = SF2PointSpotlightEntity(entity); + if (effectsInfo.AttachmentName[0] != '\0') { - char value[PLATFORM_MAX_PATH]; - DispatchKeyValueInt(entity, "renderamt", effectsInfo.FadeAlpha); - DispatchKeyValueInt(entity, "rendermode", view_as(effectsInfo.RenderModes)); - DispatchKeyValueInt(entity, "renderfx", view_as(effectsInfo.RenderEffects)); - DispatchKeyValueInt(entity, "spawnflags", effectsInfo.SpawnFlags); - - float effectPos[3], effectAng[3]; - - effectPos = effectsInfo.Origin; - effectAng = effectsInfo.Angles; + SetVariantString("!activator"); + spotlight.Start.AcceptInput("ClearParent"); + SetVariantString("!activator"); + spotlight.Start.AcceptInput("SetParent", attacher); + SetVariantString(effectsInfo.AttachmentName); + spotlight.Start.AcceptInput("SetParentAttachmentMaintainOffset"); + SetVariantString("!activator"); + spotlight.End.AcceptInput("ClearParent"); + SetVariantString("!activator"); + spotlight.End.AcceptInput("SetParent", attacher); + SetVariantString(effectsInfo.AttachmentName); + spotlight.End.AcceptInput("SetParentAttachmentMaintainOffset"); + } + spotlight.SetRenderColor(renderColor[0], renderColor[1], renderColor[2], renderColor[3]); + spotlight.Length = effectsInfo.SpotlightLength; + spotlight.Width = effectsInfo.SpotlightWidth; + spotlight.EndWidth = spotlight.Width * 2.0; + spotlight.Brightness = effectsInfo.LightBrightness; + spotlight.Distance = effectsInfo.LightMaxDistance; + spotlight.SpotlightRadius = effectsInfo.LightMaxDistance; + spotlight.Cone = effectsInfo.LightCone; + spotlight.HaloScale = effectsInfo.SpotlightHaloScale; + + spotlight.Spawn(); + spotlight.Activate(); + + spotlight.TurnOn(); + + //SDKHook(spotlight.Spotlight.index, SDKHook_SetTransmit, Hook_FlashlightSpotlightEffectSetTransmit); + } + case EffectType_Sprite: + { + DispatchKeyValue(entity, "classname", "env_sprite"); + DispatchKeyValue(entity, "model", effectsInfo.SpriteName); + DispatchKeyValueFloat(entity, "scale", effectsInfo.SpriteScale); - VectorTransform(effectPos, basePos, baseAng, effectPos); - AddVectors(effectAng, baseAng, effectAng); - TeleportEntity(entity, effectPos, effectAng, NULL_VECTOR); + int renderColor[4]; + effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - switch (effectsInfo.Type) - { - case EffectType_Steam: - { - DispatchKeyValueInt(entity, "SpreadSpeed", effectsInfo.SteamSpreadSpeed); - DispatchKeyValueInt(entity, "Speed", effectsInfo.SteamSpeed); - DispatchKeyValueInt(entity, "StartSize", effectsInfo.SteamStartSize); - DispatchKeyValueInt(entity, "EndSize", effectsInfo.SteamEndSize); - DispatchKeyValueInt(entity, "Rate", effectsInfo.SteamRate); - DispatchKeyValueInt(entity, "Jetlength", effectsInfo.SteamJetLength); - DispatchKeyValueFloat(entity, "RollSpeed", effectsInfo.SteamRollSpeed); - DispatchKeyValueInt(entity, "type", effectsInfo.SteamType); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_DynamicLight: - { - SetVariantInt(effectsInfo.LightBrightness); - AcceptEntityInput(entity, "Brightness"); - SetVariantFloat(effectsInfo.LightMaxDistance); - AcceptEntityInput(entity, "Distance"); - SetVariantFloat(effectsInfo.LightMaxDistance); - AcceptEntityInput(entity, "spotlight_radius"); - SetVariantInt(effectsInfo.LightCone); - AcceptEntityInput(entity, "cone"); - DispatchSpawn(entity); - ActivateEntity(entity); - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - SetEntProp(entity, Prop_Data, "m_LightStyle", effectsInfo.LightStyle); - } - case EffectType_Particle: - { - DispatchKeyValue(entity, "effect_name", effectsInfo.ParticleName); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_Trail: - { - DispatchKeyValueFloat(entity, "lifetime", effectsInfo.TrailTime); - DispatchKeyValueFloat(entity, "startwidth", effectsInfo.TrailStartWidth); - DispatchKeyValueFloat(entity, "endwidth", effectsInfo.TrailEndWidth); - DispatchKeyValue(entity, "spritename", effectsInfo.TrailName); - SetEntPropFloat(entity, Prop_Send, "m_flTextureRes", 0.05); - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_PropDynamic: - { - DispatchKeyValue(entity, "model", effectsInfo.ModelName); - float modelScale = effectsInfo.ModelScale; - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES) && modelScale != GetEntPropFloat(slenderEnt, Prop_Send, "m_flModelScale")) - { - modelScale *= 0.5; - } - DispatchKeyValueFloat(entity, "modelscale", modelScale); - SetEntProp(entity, Prop_Send, "m_nSkin", effectsInfo.ModelSkin); - SetEntProp(entity, Prop_Send, "m_fEffects", EF_BONEMERGE|EF_PARENT_ANIMATES); - if (effectsInfo.ModelAnimation[0] != '\0') - { - SetVariantString(value); - AcceptEntityInput(entity, "SetAnimation"); - } - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - DispatchSpawn(entity); - ActivateEntity(entity); - } - case EffectType_PointSpotlight: - { - int startEnt = CreateEntityByName("info_target"); - int endEnt = CreateEntityByName("info_target"); - if (startEnt != -1) // Start - { - SetEntPropString(startEnt, Prop_Data, "m_iClassname", "sf2_boss_spotlight_start"); - SetEntProp(startEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(startEnt, effectPos, effectAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(startEnt, "SetParent", slenderEnt); - if (effectsInfo.Attachment && effectsInfo.AttachmentName[0] != '\0') - { - SetVariantString(effectsInfo.AttachmentName); - AcceptEntityInput(startEnt, "SetParentAttachmentMaintainOffset"); - } - - DispatchSpawn(startEnt); - SetEntityOwner(startEnt, slenderEnt); - - SetEntityTransmitState(startEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectShouldTransmit); - } - if (endEnt != -1) // End - { - SetEntPropString(endEnt, Prop_Data, "m_iClassname", "sf2_boss_spotlight_end"); - SetEntProp(endEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(endEnt, effectPos, effectAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(endEnt, "SetParent", slenderEnt); - if (effectsInfo.Attachment && effectsInfo.AttachmentName[0] != '\0') - { - SetVariantString(effectsInfo.AttachmentName); - AcceptEntityInput(endEnt, "SetParentAttachmentMaintainOffset"); - } - - DispatchSpawn(endEnt); - SetEntityOwner(endEnt, slenderEnt); - - SetEntityTransmitState(endEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectShouldTransmit); - } - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - SetEntityModel(entity, SF2_FLASHLIGHT_BEAM_MATERIAL); - SetEntityRenderMode(entity, effectsInfo.RenderModes); - SetEntPropFloat(entity, Prop_Data, "m_life", 0.0); - - DispatchSpawn(entity); - ActivateEntity(entity); - - SetEntPropEnt(entity, Prop_Send, "m_hAttachEntity", startEnt, 0); - SetEntPropEnt(entity, Prop_Send, "m_hAttachEntity", endEnt, 1); - SetEntProp(entity, Prop_Send, "m_nNumBeamEnts", 2); - SetEntProp(entity, Prop_Send, "m_nBeamType", 2); - - float width = effectsInfo.SpotlightWidth; - SetEntPropFloat(entity, Prop_Send, "m_fWidth", width); - - float endWidth = width * 2.0; - SetEntPropFloat(entity, Prop_Data, "m_fEndWidth", endWidth); - - SetEntPropFloat(entity, Prop_Send, "m_fFadeLength", 0.0); - SetEntProp(entity, Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); - SetEntPropFloat(entity, Prop_Send, "m_fHaloScale", 40.0); - SetEntProp(entity, Prop_Send, "m_nBeamFlags", 0x80 | 0x200); - SetEntProp(entity, Prop_Data, "m_spawnflags", 0x8000); - g_EffectSpotlightEndEntity[entity] = EntIndexToEntRef(endEnt); - - g_EffectSpotlightEndLength[entity] = effectsInfo.SpotlightLength; - - AcceptEntityInput(entity, "TurnOn"); - - SetEntityOwner(entity, slenderEnt); - - SetEntityTransmitState(entity, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, entity, Hook_SpotlightEffectShouldTransmit); - } - case EffectType_Sprite: - { - DispatchKeyValue(entity, "classname", "env_sprite"); - DispatchKeyValue(entity, "model", effectsInfo.SpriteName); - DispatchKeyValueFloat(entity, "scale", effectsInfo.SpriteScale); - - int renderColor[4]; - effectsInfo.Colors.GetArray(difficulty, renderColor, sizeof(renderColor)); - - SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - - DispatchSpawn(entity); - ActivateEntity(entity); - } - } + SetEntityRenderColor(entity, renderColor[0], renderColor[1], renderColor[2], renderColor[3]); - float lifeTime = effectsInfo.LifeTime; - if (lifeTime > 0.0) - { - CreateTimer(lifeTime, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); - } + DispatchSpawn(entity); + ActivateEntity(entity); + } + } - if (!slenderEnt || slenderEnt == INVALID_ENT_REFERENCE) - { - LogError("Could not parent effect %s of boss %d to itself: boss entity does not exist!", sectionName, bossIndex); - continue; - } + float lifeTime = effectsInfo.LifeTime; + if (lifeTime > 0.0) + { + CreateTimer(lifeTime, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); + } - SetVariantString("!activator"); - AcceptEntityInput(entity, "SetParent", slenderEnt); - if (effectsInfo.Attachment) - { - if (effectsInfo.AttachmentName[0] != '\0') - { - SetVariantString(effectsInfo.AttachmentName); - if (effectsInfo.Type != EffectType_PropDynamic && effectsInfo.Type != EffectType_PointSpotlight) - { - AcceptEntityInput(entity, "SetParentAttachment"); - } - else - { - AcceptEntityInput(entity, "SetParentAttachmentMaintainOffset"); - } - } - } + if (!attacher || attacher == INVALID_ENT_REFERENCE) + { + LogError("Could not parent effect %s of boss %d to itself: boss entity does not exist!", effectsInfo.SectionName, bossIndex); + return; + } - switch (effectsInfo.Type) - { - case EffectType_Steam, - EffectType_DynamicLight: - { - AcceptEntityInput(entity, "TurnOn"); - } - case EffectType_Particle: - { - AcceptEntityInput(entity, "start"); - } - case EffectType_Trail: - { - AcceptEntityInput(entity, "showsprite"); - } - case EffectType_PointSpotlight: - { - AcceptEntityInput(entity, "TurnOn"); - } - case EffectType_Sprite: - { - AcceptEntityInput(entity, "ShowSprite"); - } - } - SDKHook(entity, SDKHook_SetTransmit, Hook_EffectTransmit); - g_EntityEffectType[entity] = effectsInfo.Type; - g_EntityEffectEvent[entity] = effectsInfo.Event; - g_NpcEffectsArray[bossIndex].Push(entity); + SetVariantString("!activator"); + AcceptEntityInput(entity, "SetParent", attacher); + if (effectsInfo.Attachment) + { + if (effectsInfo.AttachmentName[0] != '\0') + { + SetVariantString(effectsInfo.AttachmentName); + if (effectsInfo.Type != EffectType_PropDynamic && effectsInfo.Type != EffectType_PointSpotlight) + { + AcceptEntityInput(entity, "SetParentAttachment"); + } + else + { + AcceptEntityInput(entity, "SetParentAttachmentMaintainOffset"); } } - else + } + + switch (effectsInfo.Type) + { + case EffectType_Steam, + EffectType_DynamicLight: + { + AcceptEntityInput(entity, "TurnOn"); + } + case EffectType_Particle: + { + AcceptEntityInput(entity, "start"); + } + case EffectType_Trail: + { + AcceptEntityInput(entity, "showsprite"); + } + case EffectType_PointSpotlight: { - LogError("Could not spawn effect %s for boss %d: invalid type!", sectionName, bossIndex); + AcceptEntityInput(entity, "TurnOn"); + } + case EffectType_Sprite: + { + AcceptEntityInput(entity, "ShowSprite"); } } - } + SDKHook(entity, SDKHook_SetTransmit, Hook_EffectTransmit); + g_EntityEffectType[entity] = effectsInfo.Type; + g_EntityEffectEvent[entity] = effectsInfo.Event; + g_NpcEffectsArray[bossIndex].Push(entity); - if (g_NpcEffectsArray[bossIndex] != null && g_NpcEffectsArray[bossIndex].Length > 0) + if (output != null) + { + output.Push(EntIndexToEntRef(entity)); + } + } + else { - for (int effects = 0; effects < g_NpcEffectsArray[bossIndex].Length; effects++) + switch (effectsInfo.Type) { - int ent = g_NpcEffectsArray[bossIndex].Get(effects); - if (!IsValidEntity(ent)) + case EffectType_TempEntBeamRing: { - continue; + char colorString[32]; + strcopy(colorString, sizeof(colorString), effectsInfo.BeamRingColor); + char keys[4][4]; + ExplodeString(colorString, " ", keys, sizeof(keys), sizeof(keys)); + int color[4]; + for (int i2 = 0; i2 < 4; i2++) + { + color[i2] = StringToInt(keys[i2]); + } + + TE_SetupBeamRingPoint(effectPos, effectsInfo.BeamRingStartRadius, effectsInfo.BeamRingEndRadius, effectsInfo.BeamRingBeamModel, effectsInfo.BeamRingHaloModel, effectsInfo.BeamRingStartFrame, effectsInfo.BeamRingFrameRate, effectsInfo.LifeTime, effectsInfo.BeamRingWidth, effectsInfo.BeamRingAmplitude, color, effectsInfo.BeamRingSpeed, effectsInfo.BeamRingFlags); + TE_SendToAll(effectsInfo.Delay); } - if (g_EntityEffectType[ent] == EffectType_PointSpotlight) + case EffectType_TempEntParticle: { - SDKHook(slenderEnt, SDKHook_ThinkPost, SlenderSpotlightThink); - break; + if (effectsInfo.ParticleName[0] == '\0') + { + return; + } + + int particle = PrecacheParticleSystem(effectsInfo.ParticleName); + if (particle == -1) + { + return; + } + + int attachment = LookupEntityAttachment(attacher, effectsInfo.AttachmentName); + + float start[3], pos[3], ang[3]; + start = effectsInfo.TEParticleStartPos; + if (effectsInfo.TEParticleAttachType == 2) + { + VectorTransform(start, basePos, baseAng, start); + pos = effectsInfo.Origin; + ang = effectsInfo.Angles; + } + else + { + pos = effectPos; + ang = effectAng; + } + TE_Particle(particle, pos, start, ang, attacher, effectsInfo.TEParticleAttachType, attachment, effectsInfo.TEParticleReset, effectsInfo.TEParticleHasControlPoint, effectsInfo.TEParticleControlPointAttachType, effectsInfo.TEParticleControlPointOffset); + TE_SendToAll(effectsInfo.Delay); + } + case EffectType_Sound: + { + effectsInfo.SoundSounds.EmitSound(_, attacher); } } } } -static Action Hook_EffectTransmit(int ent,int other) +static Action Hook_EffectTransmit(int ent, int other) { if (!g_Enabled) { return Plugin_Continue; } - int slender = GetEntPropEnt(ent, Prop_Send, "moveparent"); - int bossIndex = NPCGetFromEntIndex(slender); + SF2_ChaserEntity slender = SF2_ChaserEntity(GetEntPropEnt(ent, Prop_Send, "moveparent")); - if (bossIndex != -1 && NPCChaserIsCloaked(bossIndex)) + if (slender.IsValid() && slender.HasCloaked) { return Plugin_Handled; } - if (g_EntityEffectEvent[ent] == EffectEvent_PlayerSeesBoss && IsValidClient(other) && bossIndex != -1 && !g_PlayerEliminated[other] && !IsClientInGhostMode(other) && - !DidClientEscape(other) && !PlayerCanSeeSlender(other, bossIndex, true)) + + SF2NPC_Chaser controller = slender.Controller; + + SF2_BasePlayer player = SF2_BasePlayer(other); + if (player.IsValid && !player.IsEliminated && !player.IsInGhostMode && !player.HasEscaped) { - return Plugin_Handled; + if (g_EntityEffectEvent[ent] == EffectEvent_PlayerSeesBoss && controller.PlayerCanSee(player.index)) + { + return Plugin_Handled; + } } return Plugin_Continue; } -static Action Hook_EffectTransmitX(int ent,int other) +static Action Hook_EffectTransmitX(int ent, int other) { if (!g_Enabled) { return Plugin_Continue; } - int slender = GetEntPropEnt(ent,Prop_Send,"moveparent"); - int bossIndex = NPCGetFromEntIndex(slender); + SF2_ChaserEntity slender = SF2_ChaserEntity(GetEntPropEnt(ent, Prop_Send, "moveparent")); - if (bossIndex != -1 && NPCChaserIsCloaked(bossIndex)) + if (slender.IsValid() && slender.HasCloaked) { return Plugin_Handled; } @@ -554,24 +628,12 @@ static Action Hook_EffectTransmitX(int ent,int other) return Plugin_Continue; } -MRESReturn Hook_SpotlightEffectUpdateTransmitState(int entity, DHookReturn returnHook) -{ - returnHook.Value = SetEntityTransmitState(entity, FL_EDICT_FULLCHECK); - return MRES_Supercede; -} - -MRESReturn Hook_SpotlightEffectShouldTransmit(int entity, DHookReturn returnHook, DHookParam params) -{ - returnHook.Value = FL_EDICT_ALWAYS; - return MRES_Supercede; -} - -void SlenderToggleParticleEffects(int slenderEnt,bool reverse=false) +void SlenderToggleParticleEffects(int slenderEnt, bool reverse = false) { int effect = -1; - while((effect = FindEntityByClassname(effect, "info_particle_system")) > MaxClients) + while ((effect = FindEntityByClassname(effect, "info_particle_system")) > MaxClients) { - if (GetEntPropEnt(effect,Prop_Send,"moveparent") == slenderEnt) + if (GetEntPropEnt(effect, Prop_Send, "moveparent") == slenderEnt) { if (!reverse) { @@ -587,14 +649,8 @@ void SlenderToggleParticleEffects(int slenderEnt,bool reverse=false) } } -void SlenderRemoveEffects(int slenderEnt,bool kill=false) +void SlenderRemoveEffects(int bossIndex, bool kill = false) { - int bossIndex = NPCGetFromEntIndex(slenderEnt); - if (bossIndex == -1) - { - return; - } - if (g_NpcEffectsArray[bossIndex] == null) { return; @@ -647,47 +703,6 @@ void SlenderRemoveEffects(int slenderEnt,bool kill=false) delete g_NpcEffectsArray[bossIndex]; } -static void SlenderSpotlightThink(int slenderEnt) -{ - if (IsValidEntity(slenderEnt)) - { - int bossIndex = NPCGetFromEntIndex(slenderEnt); - if (bossIndex != -1 && g_NpcEffectsArray[bossIndex] != null && g_NpcEffectsArray[bossIndex].Length > 0) - { - for (int effect = 0; effect < g_NpcEffectsArray[bossIndex].Length; effect++) - { - int ent = g_NpcEffectsArray[bossIndex].Get(effect); - if (!IsValidEntity(ent)) - { - continue; - } - if (g_EntityEffectType[ent] != EffectType_PointSpotlight) - { - continue; - } - float entPos[3], entRot[3], endPos[3]; - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", entPos); - GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", entRot); - GetAngleVectors(entRot, entRot, NULL_VECTOR, NULL_VECTOR); - endPos = entRot; - ScaleVector(endPos, g_EffectSpotlightEndLength[ent]); - AddVectors(endPos, entPos, endPos); - - CBaseEntity spotlightEnd = CBaseEntity(EntRefToEntIndex(g_EffectSpotlightEndEntity[ent])); - if (spotlightEnd.IsValid()) - { - TR_TraceRayFilter(entPos, endPos, MASK_SOLID_BRUSHONLY, RayType_EndPoint, TraceRayDontHitEntity, ent); - - float hitPos[3]; - TR_GetEndPosition(hitPos); - - spotlightEnd.SetAbsOrigin(hitPos); - } - } - } - } -} - static Action Timer_DiscoLight(Handle timer, any effect) { if (effect == -1) @@ -750,11 +765,11 @@ static Action Timer_FestiveLight(Handle timer, any effect) } case 2: { - SetEntityRenderColor(ent, 94, 227, 79, 255); + SetEntityRenderColor(ent, 79, 227, 79, 255); } case 3: { - SetEntityRenderColor(ent, 235, 235, 235, 255); + SetEntityRenderColor(ent, 255, 255, 255, 255); } } diff --git a/addons/sourcemod/scripting/sf2/entities/initialize.sp b/addons/sourcemod/scripting/sf2/entities/initialize.sp new file mode 100644 index 00000000..684eff08 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/initialize.sp @@ -0,0 +1,14 @@ +#include "sf2_base_projectile.sp" + +void InitializeCustomEntities() +{ + SF2_ProjectileBase.Initialize(); + SF2_ProjectileRocket.Initialize(); + SF2_ProjectileFireball.Initialize(); + SF2_ProjectileIceball.Initialize(); + SF2_ProjectileSentryRocket.Initialize(); + SF2_ProjectileCowMangler.Initialize(); + SF2_ProjectileArrow.Initialize(); + SF2_ProjectileGrenade.Initialize(); + SF2_ProjectileBaseball.Initialize(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp new file mode 100644 index 00000000..2b7adbac --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp @@ -0,0 +1,165 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_arrow"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileArrow < SF2_ProjectileBase +{ + public SF2_ProjectileArrow(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_Touched") + .EndDataMapDesc(); + g_Factory.Install(); + } + + property bool Touched + { + public get() + { + return this.GetProp(Prop_Data, "m_Touched") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_Touched", value); + } + } + + public static SF2_ProjectileArrow Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] trail, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileArrow arrow = SF2_ProjectileArrow(CreateEntityByName(g_EntityClassname)); + if (!arrow.IsValid()) + { + return SF2_ProjectileArrow(-1); + } + + arrow.Type = SF2BossProjectileType_Arrow; + arrow.Speed = speed; + arrow.Damage = damage; + if (arrow.IsCrits) + { + CBaseEntity critParticle = arrow.CreateParticle("critical_rocket_blue"); + arrow.CritEntity = critParticle; + } + arrow.AttackWaiters = attackWaiters; + arrow.SetImpactSound(impactSound); + SetEntityOwner(arrow.index, owner.index); + arrow.SetModel(model); + arrow.KeyValue("solid", "2"); + + arrow.Spawn(); + arrow.Activate(); + arrow.SetMoveType(MOVETYPE_FLYGRAVITY); + arrow.SetProp(Prop_Send, "m_usSolidFlags", 12); + arrow.Teleport(pos, ang, NULL_VECTOR); + arrow.CreateTrail(true, "effects/arrowtrail_red.vmt", "255", "1"); + arrow.SetVelocity(); + + SDKHook(arrow.index, SDKHook_StartTouch, StartTouch); + } +} + +static void StartTouch(int entity, int other) +{ + SF2_ProjectileArrow projectile = SF2_ProjectileArrow(entity); + + if (other == 0) + { + RemoveEntity(projectile.index); + return; + } + + bool hit = true; + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + if (otherPlayer.IsValid) + { + if (otherPlayer.IsInGhostMode || (otherPlayer.IsProxy && !projectile.AttackWaiters)) + { + return; + } + } + else + { + int hitIndex = NPCGetFromEntIndex(other); + if (hitIndex != -1) + { + hit = false; + } + } + + if (projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity") == other) + { + hit = false; + } + + if (SF2_ProjectileBase(other).IsValid()) + { + hit = false; + } + + if (SF2_BasePlayer(other).IsValid) + { + hit = true; + } + + if (hit) + { + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + int flags = DMG_BULLET; + float pos[3]; + projectile.GetAbsOrigin(pos); + if (projectile.IsCrits) + { + flags |= DMG_ACID; + } + SF2_BasePlayer player = SF2_BasePlayer(other); + if (player.IsValid) + { + if (!projectile.AttackWaiters && player.IsEliminated) + { + return; + } + + player.TakeDamage(_, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(player); + Call_PushCell(projectile); + Call_Finish(); + } + else + { + SDKHooks_TakeDamage(other, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + } + EmitSoundToAll(projectile.GetImpactSound(), projectile.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + RemoveEntity(projectile.index); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp new file mode 100644 index 00000000..309ec7e7 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp @@ -0,0 +1,160 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_baseball"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileBaseball < SF2_ProjectileGrenade +{ + public SF2_ProjectileBaseball(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileGrenade.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_TravelTime") + .EndDataMapDesc(); + g_Factory.Install(); + } + + property float TravelTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_TravelTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_TravelTime", value); + } + } + + public static SF2_ProjectileBaseball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileBaseball ball = SF2_ProjectileBaseball(CreateEntityByName(g_EntityClassname)); + if (!ball.IsValid()) + { + return SF2_ProjectileBaseball(-1); + } + + ball.Type = SF2BossProjectileType_Baseball; + ball.Speed = speed; + ball.Damage = damage; + if (ball.IsCrits) + { + CBaseEntity critParticle = ball.CreateParticle("critical_rocket_blue"); + ball.CritEntity = critParticle; + } + ball.AttackWaiters = attackWaiters; + SetEntityOwner(ball.index, owner.index); + ball.SetModel(model); + ball.SetProp(Prop_Send, "m_nSkin", 1); + ball.SetProp(Prop_Send, "m_usSolidFlags", 12); + ball.KeyValue("solid", "2"); + ball.KeyValue("spawnflags", "4"); + ball.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + ball.SetProp(Prop_Send, "m_usSolidFlags", 0); + + ball.Spawn(); + ball.Activate(); + ball.Teleport(pos, ang, NULL_VECTOR); + ball.CreateTrail(true, "effects/baseballtrail_blu.vmt", "255", "1"); + ball.SetVelocity(); + + ball.Timer = GetGameTime() + 12.0; + + SDKHook(ball.index, SDKHook_VPhysicsUpdate, Think); + } +} + +static void Think(int entity) +{ + SF2_ProjectileBaseball projectile = SF2_ProjectileBaseball(entity); + + if (projectile.Timer < GetGameTime()) + { + RemoveEdict(projectile.index); + } + + if (projectile.Touched) + { + return; + } + + projectile.TravelTime += GetGameFrameTime(); + + float pos[3], mins[3], maxs[3]; + projectile.GetAbsOrigin(pos); + projectile.GetPropVector(Prop_Send, "m_vecMins", mins); + projectile.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, projectile.index); + + int hitIndex = TR_GetEntityIndex(); + if (hitIndex == 0) + { + projectile.Touched = true; + return; + } + else + { + SF2_BasePlayer player = SF2_BasePlayer(hitIndex); + if (player.IsValid) + { + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + int flags = DMG_CLUB; + if (projectile.IsCrits) + { + flags |= DMG_ACID; + } + player.TakeDamage(_, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + float ratio = projectile.TravelTime / 1.0; + if (ratio > 0.1) + { + float time = 6.0 * ratio; + float stun = 0.5; + if (projectile.IsCrits) + { + time += 2.0; + } + + if (ratio >= 1.0) + { + time += 1.0; + } + + if (player.GetProp(Prop_Send, "m_nWaterLevel") < 3) + { + player.Stun(time, stun, TF_STUNFLAG_SLOWDOWN, player.index); + } + } + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(player); + Call_PushCell(projectile); + Call_Finish(); + projectile.Touched = true; + } + } +} diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp new file mode 100644 index 00000000..a82669f3 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp @@ -0,0 +1,50 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_cowmangler"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileCowMangler < SF2_ProjectileRocket +{ + public SF2_ProjectileCowMangler(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileRocket.GetFactory()); + g_Factory.Install(); + } + + public static SF2_ProjectileCowMangler Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool attackWaiters = false) + { + SF2_ProjectileCowMangler rocket = SF2_ProjectileCowMangler(CreateEntityByName(g_EntityClassname)); + if (!rocket.IsValid()) + { + return SF2_ProjectileCowMangler(-1); + } + + rocket.InitializeProjectile(SF2BossProjectileType_Mangler, owner, pos, ang, speed, damage, blastRadius, false, "drg_cow_rockettrail_normal_blue", "drg_cow_explosioncore_normal_blue", ROCKET_IMPACT, "models/roller.mdl", attackWaiters); + + return rocket; + } +} diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp new file mode 100644 index 00000000..ac127fd5 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp @@ -0,0 +1,67 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_fireball"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileFireball < SF2_ProjectileBase +{ + public SF2_ProjectileFireball(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.Install(); + g_OnPlayerDamagedByProjectilePFwd.AddFunction(null, OnPlayerDamagedByProjectile); + } + + public void OnPlayerDamaged(SF2_BasePlayer player) + { + player.Ignite(true); + } + + public static SF2_ProjectileFireball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const bool attackWaiters = false) + { + SF2_ProjectileFireball fireball = SF2_ProjectileFireball(CreateEntityByName(g_EntityClassname)); + if (!fireball.IsValid()) + { + return SF2_ProjectileFireball(-1); + } + + fireball.InitializeProjectile(SF2BossProjectileType_Fireball, owner, pos, ang, speed, damage, blastRadius, + false, "spell_fireball_small_red", "bombinomicon_burningdebris", impactSound, "models/roller.mdl", attackWaiters); + + return fireball; + } +} + +static void OnPlayerDamagedByProjectile(SF2_BasePlayer player, SF2_ProjectileBase projectile) +{ + if (projectile.Type == SF2BossProjectileType_Fireball) + { + player.Ignite(true); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp new file mode 100644 index 00000000..f09987e4 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -0,0 +1,356 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_grenade"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileGrenade < SF2_ProjectileBase +{ + public SF2_ProjectileGrenade(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromClass("prop_physics_override"); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_TrailEntity") + .DefineEntityField("m_ParticleEntity") + .DefineEntityField("m_CritEntity") + .DefineIntField("m_Type") + .DefineFloatField("m_Speed") + .DefineFloatField("m_Damage") + .DefineBoolField("m_IsCrits") + .DefineFloatField("m_BlastRadius") + .DefineStringField("m_TrailName") + .DefineStringField("m_ExplosionParticle") + .DefineStringField("m_ImpactSound") + .DefineBoolField("m_AttackWaiters") + .DefineBoolField("m_Touched") + .DefineFloatField("m_Timer") + .EndDataMapDesc(); + g_Factory.Install(); + } + + public static CEntityFactory GetFactory() + { + return g_Factory; + } + + property CBaseEntity TrailEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_TrailEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_TrailEntity", entity.index); + } + } + + property CBaseEntity ParticleEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_ParticleEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_ParticleEntity", entity.index); + } + } + + property CBaseEntity CritEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_CritEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_CritEntity", entity.index); + } + } + + property int Type + { + public get() + { + return this.GetProp(Prop_Data, "m_Type"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Type", value); + } + } + + property float Speed + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Speed"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Speed", value); + } + } + + property float Damage + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Damage"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Damage", value); + } + } + + property bool IsCrits + { + public get() + { + return this.GetProp(Prop_Data, "m_IsCrits") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsCrits", value); + } + } + + property float BlastRadius + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_BlastRadius"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_BlastRadius", value); + } + } + + public char[] GetTrailName() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_TrailName", buffer, sizeof(buffer)); + return buffer; + } + + public void SetTrailName(const char[] value) + { + this.SetPropString(Prop_Data, "m_TrailName", value); + } + + public char[] GetExplosionParticle() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_ExplosionParticle", buffer, sizeof(buffer)); + return buffer; + } + + public void SetExplosionParticle(const char[] value) + { + this.SetPropString(Prop_Data, "m_ExplosionParticle", value); + } + + public char[] GetImpactSound() + { + char buffer[PLATFORM_MAX_PATH]; + this.GetPropString(Prop_Data, "m_ImpactSound", buffer, sizeof(buffer)); + return buffer; + } + + public void SetImpactSound(const char[] value) + { + this.SetPropString(Prop_Data, "m_ImpactSound", value); + } + + property bool AttackWaiters + { + public get() + { + return this.GetProp(Prop_Data, "m_AttackWaiters") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_AttackWaiters", value); + } + } + + property bool Touched + { + public get() + { + return this.GetProp(Prop_Data, "m_Touched") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_Touched", value); + } + } + + property float Timer + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Timer"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Timer", value); + } + } + + public static SF2_ProjectileGrenade Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileGrenade grenade = SF2_ProjectileGrenade(CreateEntityByName(g_EntityClassname)); + if (!grenade.IsValid()) + { + return SF2_ProjectileGrenade(-1); + } + + grenade.Type = SF2BossProjectileType_Grenade; + grenade.Speed = speed; + grenade.Damage = damage; + grenade.BlastRadius = blastRadius; + if (grenade.IsCrits) + { + CBaseEntity critParticle = grenade.CreateParticle("critical_grenade_blue"); + grenade.CritEntity = critParticle; + } + grenade.AttackWaiters = attackWaiters; + grenade.SetTrailName(trail); + grenade.SetImpactSound(impactSound); + grenade.SetExplosionParticle(explosion); + SetEntityOwner(grenade.index, owner.index); + grenade.SetModel(model); + grenade.SetProp(Prop_Send, "m_nSkin", 1); + grenade.SetProp(Prop_Send, "m_usSolidFlags", 12); + grenade.KeyValue("solid", "2"); + grenade.KeyValue("spawnflags", "4"); + grenade.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + grenade.SetProp(Prop_Send, "m_usSolidFlags", 0); + + CBaseEntity particle = grenade.CreateParticle(grenade.GetTrailName()); + grenade.ParticleEntity = particle; + + grenade.Spawn(); + grenade.Activate(); + grenade.CreateTrail(); + grenade.Teleport(pos, ang, NULL_VECTOR); + grenade.SetVelocity(); + + grenade.Timer = GetGameTime() + 2.0; + + SDKHook(grenade.index, SDKHook_VPhysicsUpdate, Think); + } +} + +static void Think(int entity) +{ + SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade(entity); + + if (projectile.Timer < GetGameTime()) + { + projectile.DoExplosion(); + if (projectile.IsValid()) + { + RemoveEdict(projectile.index); + } + } + + if (projectile.Touched) + { + return; + } + + float pos[3], mins[3], maxs[3]; + projectile.GetAbsOrigin(pos); + projectile.GetPropVector(Prop_Send, "m_vecMins", mins); + projectile.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, projectile.index); + + int hitIndex = TR_GetEntityIndex(); + if (hitIndex == 0) + { + projectile.Touched = true; + return; + } + else + { + SF2_BasePlayer player = SF2_BasePlayer(hitIndex); + if (player.IsValid) + { + projectile.DoExplosion(); + } + } +} + +bool TraceRayGrenade(int entity, int mask, any data) +{ + SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade(data); + if (entity == data) + { + return false; + } + + SF2_BasePlayer player = SF2_BasePlayer(entity); + if (player.IsValid) + { + if (player.IsInGhostMode) + { + return false; + } + + if (player.IsEliminated && !projectile.AttackWaiters) + { + return false; + } + } + + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) + { + return false; + } + + return true; +} diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp new file mode 100644 index 00000000..1da050fa --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp @@ -0,0 +1,118 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_iceball"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileIceball < SF2_ProjectileBase +{ + public SF2_ProjectileIceball(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_SlowDuration") + .DefineFloatField("m_SlowMultiplier") + .DefineStringField("m_FreezeSound") + .EndDataMapDesc(); + g_Factory.Install(); + g_OnPlayerDamagedByProjectilePFwd.AddFunction(null, OnPlayerDamagedByProjectile); + } + + property float SlowDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SlowDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SlowDuration", value); + } + } + + property float SlowMultiplier + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SlowMultiplier"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SlowMultiplier", value); + } + } + + public char[] GetFreezeSound() + { + char buffer[PLATFORM_MAX_PATH]; + this.GetPropString(Prop_Data, "m_FreezeSound", buffer, sizeof(buffer)); + return buffer; + } + + public void SetFreezeSound(const char[] value) + { + this.SetPropString(Prop_Data, "m_FreezeSound", value); + } + + public void OnPlayerDamaged(SF2_BasePlayer player) + { + EmitSoundToClient(player.index, this.GetFreezeSound(), _, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + player.Stun(this.SlowDuration, this.SlowMultiplier, TF_STUNFLAG_SLOWDOWN, player.index); + } + + public static SF2_ProjectileIceball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const float slowDuration, + const float slowMultiplier, + const char[] freezeSound, + const bool attackWaiters = false) + { + SF2_ProjectileIceball iceball = SF2_ProjectileIceball(CreateEntityByName(g_EntityClassname)); + if (!iceball.IsValid()) + { + return SF2_ProjectileIceball(-1); + } + + iceball.SlowDuration = slowDuration; + iceball.SlowMultiplier = slowMultiplier; + iceball.SetFreezeSound(freezeSound); + iceball.InitializeProjectile(SF2BossProjectileType_Iceball, owner, pos, ang, speed, damage, blastRadius, + false, "spell_fireball_small_blue", "spell_batball_impact_blue", impactSound, "models/roller.mdl", attackWaiters); + return iceball; + } +} + +static void OnPlayerDamagedByProjectile(SF2_BasePlayer player, SF2_ProjectileBase projectile) +{ + if (projectile.Type == SF2BossProjectileType_Iceball) + { + SF2_ProjectileIceball iceball = SF2_ProjectileIceball(projectile.index); + EmitSoundToClient(player.index, iceball.GetFreezeSound(), _, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + player.Stun(iceball.SlowDuration, iceball.SlowMultiplier, TF_STUNFLAG_SLOWDOWN, player.index); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp new file mode 100644 index 00000000..631dc0a4 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp @@ -0,0 +1,55 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_rocket"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileRocket < SF2_ProjectileBase +{ + public SF2_ProjectileRocket(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileBase.GetFactory()); + g_Factory.Install(); + } + + public static SF2_ProjectileRocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + SF2_ProjectileRocket rocket = SF2_ProjectileRocket(CreateEntityByName(g_EntityClassname)); + if (!rocket.IsValid()) + { + return SF2_ProjectileRocket(-1); + } + + rocket.InitializeProjectile(SF2BossProjectileType_Rocket, owner, pos, ang, speed, damage, blastRadius, isCrits, trail, explosion, impactSound, model, attackWaiters); + + return rocket; + } +} diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp new file mode 100644 index 00000000..42a828e3 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp @@ -0,0 +1,53 @@ +#pragma semicolon 1 + +static const char g_EntityClassname[] = "sf2_projectile_sentryrocket"; + +static CEntityFactory g_Factory; + +methodmap SF2_ProjectileSentryRocket < SF2_ProjectileRocket +{ + public SF2_ProjectileSentryRocket(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public bool IsValid() + { + if (!CBaseAnimating(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_EntityClassname); + g_Factory.DeriveFromFactory(SF2_ProjectileRocket.GetFactory()); + g_Factory.Install(); + } + + public static SF2_ProjectileSentryRocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const bool attackWaiters = false) + { + SF2_ProjectileSentryRocket rocket = SF2_ProjectileSentryRocket(CreateEntityByName(g_EntityClassname)); + if (!rocket.IsValid()) + { + return SF2_ProjectileSentryRocket(-1); + } + + rocket.InitializeProjectile(SF2BossProjectileType_SentryRocket, owner, pos, ang, speed, damage, blastRadius, isCrits, ROCKET_TRAIL, ROCKET_EXPLODE_PARTICLE, ROCKET_IMPACT, "models/buildables/sentry3_rockets.mdl", attackWaiters); + rocket.ResetSequence(rocket.LookupSequence("idle")); + rocket.SetProp(Prop_Data, "m_bSequenceLoops", true); + + return rocket; + } +} diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp new file mode 100644 index 00000000..a59c3778 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -0,0 +1,498 @@ +#pragma semicolon 1 + +static CEntityFactory g_Factory; + +PrivateForward g_OnPlayerDamagedByProjectilePFwd; + +methodmap SF2_ProjectileBase < CBaseAnimating +{ + public SF2_ProjectileBase(int entIndex) + { + return view_as(CBaseAnimating(entIndex)); + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_projectile_base", _, OnRemove); + g_Factory.DeriveFromClass("prop_dynamic_override"); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_TrailEntity") + .DefineEntityField("m_ParticleEntity") + .DefineEntityField("m_CritEntity") + .DefineIntField("m_Type") + .DefineFloatField("m_Speed") + .DefineFloatField("m_Damage") + .DefineBoolField("m_IsCrits") + .DefineFloatField("m_BlastRadius") + .DefineStringField("m_TrailName") + .DefineStringField("m_ExplosionParticle") + .DefineStringField("m_ImpactSound") + .DefineBoolField("m_AttackWaiters") + .EndDataMapDesc(); + g_Factory.IsAbstract = true; + g_Factory.Install(); + + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnPlayerDamagedByProjectilePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + } + + public static CEntityFactory GetFactory() + { + return g_Factory; + } + + property CBaseEntity TrailEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_TrailEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_TrailEntity", entity.index); + } + } + + property CBaseEntity ParticleEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_ParticleEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_ParticleEntity", entity.index); + } + } + + property CBaseEntity CritEntity + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_CritEntity")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_CritEntity", entity.index); + } + } + + property int Type + { + public get() + { + return this.GetProp(Prop_Data, "m_Type"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Type", value); + } + } + + property float Speed + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Speed"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Speed", value); + } + } + + property float Damage + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Damage"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Damage", value); + } + } + + property bool IsCrits + { + public get() + { + return this.GetProp(Prop_Data, "m_IsCrits") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsCrits", value); + } + } + + property float BlastRadius + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_BlastRadius"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_BlastRadius", value); + } + } + + public char[] GetTrailName() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_TrailName", buffer, sizeof(buffer)); + return buffer; + } + + public void SetTrailName(const char[] value) + { + this.SetPropString(Prop_Data, "m_TrailName", value); + } + + public char[] GetExplosionParticle() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_ExplosionParticle", buffer, sizeof(buffer)); + return buffer; + } + + public void SetExplosionParticle(const char[] value) + { + this.SetPropString(Prop_Data, "m_ExplosionParticle", value); + } + + public char[] GetImpactSound() + { + char buffer[PLATFORM_MAX_PATH]; + this.GetPropString(Prop_Data, "m_ImpactSound", buffer, sizeof(buffer)); + return buffer; + } + + public void SetImpactSound(const char[] value) + { + this.SetPropString(Prop_Data, "m_ImpactSound", value); + } + + property bool AttackWaiters + { + public get() + { + return this.GetProp(Prop_Data, "m_AttackWaiters") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_AttackWaiters", value); + } + } + + public void InitializeProjectile( + const int type, + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false) + { + this.Type = type; + this.Speed = speed; + this.Damage = damage; + this.BlastRadius = blastRadius; + this.IsCrits = isCrits; + if (this.IsCrits) + { + CBaseEntity critParticle = this.CreateParticle("critical_rocket_blue"); + this.CritEntity = critParticle; + } + this.AttackWaiters = attackWaiters; + this.SetTrailName(trail); + this.SetImpactSound(impactSound); + this.SetExplosionParticle(explosion); + SetEntityOwner(this.index, owner.index); + this.SetModel(model); + if (type == SF2BossProjectileType_Fireball || type == SF2BossProjectileType_Iceball || type == SF2BossProjectileType_Mangler) + { + this.SetRenderMode(RENDER_TRANSCOLOR); + this.SetRenderColor(0, 0, 0, 0); + } + this.KeyValue("solid", "2"); + + CBaseEntity particle = this.CreateParticle(this.GetTrailName()); + this.ParticleEntity = particle; + + this.Spawn(); + this.Activate(); + this.SetMoveType(MOVETYPE_FLY); + this.SetProp(Prop_Send, "m_usSolidFlags", 12); + this.CreateTrail(); + this.Teleport(pos, ang, NULL_VECTOR); + this.SetVelocity(); + + SDKHook(this.index, SDKHook_StartTouch, StartTouch); + } + + public void CreateTrail(bool teleport = false, const char[] override = "sprites/laserbeam.vmt", const char[] alpha = "0", const char[] mode = "10") + { + CBaseEntity trail = CBaseEntity(CreateEntityByName("env_spritetrail")); + if (trail.IsValid()) + { + if (teleport) + { + float pos[3], ang[3]; + this.GetAbsOrigin(pos); + this.GetAbsAngles(ang); + trail.Teleport(pos, ang, NULL_VECTOR); + } + trail.KeyValue("spritetrail", override); + trail.KeyValue("renderamt", alpha); + trail.KeyValue("rendermode", mode); + trail.KeyValueFloat("lifetime", 0.5); + trail.KeyValueFloat("startwidth", 1.0); + trail.KeyValueFloat("endwidth", 1.0); + trail.Spawn(); + trail.Activate(); + SetVariantString("!activator"); + trail.AcceptInput("SetParent", this.index); + this.TrailEntity = trail; + } + } + + public CBaseEntity CreateParticle(const char[] particleName) + { + CBaseEntity particle = CBaseEntity(CreateEntityByName("info_particle_system")); + if (!particle.IsValid()) + { + return CBaseEntity(-1); + } + + particle.KeyValue("effect_name", particleName); + particle.Spawn(); + particle.Activate(); + SetVariantString("!activator"); + particle.AcceptInput("SetParent", this.index); + particle.AcceptInput("Start"); + return particle; + } + + public void SetVelocity() + { + float angle[3], velocity[3], fwd[3]; + this.GetAbsAngles(angle); + GetAngleVectors(angle, fwd, NULL_VECTOR, NULL_VECTOR); + + velocity[0] = fwd[0] * this.Speed; + velocity[1] = fwd[1] * this.Speed; + velocity[2] = fwd[2] * this.Speed; + + this.Teleport(NULL_VECTOR, NULL_VECTOR, velocity); + } + + public float CalculateFallOff(CBaseEntity target) + { + float myPos[3], targetPos[3]; + this.GetAbsOrigin(myPos); + target.WorldSpaceCenter(targetPos); + + float distance = GetVectorDistance(myPos, targetPos, true) / Pow(this.BlastRadius, 2.0); + if (distance > 1.0) + { + distance = 1.0; + } + if (distance < 0.5) + { + distance = 0.5; + } + + float returnValue = this.Damage / distance; + if (returnValue > this.Damage) + { + returnValue = this.Damage; + } + return returnValue; + } + + public void GetDamageForce(CBaseEntity target, float buffer[3]) + { + target.GetAbsVelocity(buffer); + NormalizeVector(buffer, buffer); + ScaleVector(buffer, 2.0); + } + + public void OnPlayerDamaged(SF2_BasePlayer player) + { + // Do nothing + } + + public void DoExplosion() + { + int owner = this.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + float pos[3]; + this.GetAbsOrigin(pos); + ArrayList hitList = new ArrayList(); + TR_EnumerateEntitiesSphere(pos, this.BlastRadius, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, hitList); + + for (int i = 0; i < hitList.Length; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(hitList.Get(i)); + if (!this.AttackWaiters && player.IsEliminated) + { + continue; + } + + float targetPos[3]; + player.GetEyePosition(targetPos); + TR_TraceRayFilter(pos, targetPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_GRATE, + RayType_EndPoint, TraceRayDontHitAnyEntity, this.index); + + if (!TR_DidHit() || TR_GetEntityIndex() == player.index) + { + float damage = this.CalculateFallOff(player); + int flags = DMG_BLAST; + if (this.IsCrits) + { + flags |= DMG_ACID; + } + float force[3]; + this.GetDamageForce(player, force); + player.TakeDamage(_, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, damage, flags, _, force, pos); + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(player); + Call_PushCell(this); + Call_Finish(); + } + } + + delete hitList; + if (this.Type != SF2BossProjectileType_Mangler) + { + EmitSoundToAll(this.GetImpactSound(), this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + else + { + int random = GetRandomInt(0, 2); + char sound[PLATFORM_MAX_PATH]; + switch (random) + { + case 0: + { + strcopy(sound, sizeof(sound), MANGLER_EXPLODE1); + } + + case 1: + { + strcopy(sound, sizeof(sound), MANGLER_EXPLODE2); + } + + case 2: + { + strcopy(sound, sizeof(sound), MANGLER_EXPLODE3); + } + } + EmitSoundToAll(sound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + CBaseEntity particle = CBaseEntity(CreateEntityByName("info_particle_system")); + if (particle.IsValid()) + { + particle.KeyValue("effect_name", this.GetExplosionParticle()); + particle.Teleport(pos, NULL_VECTOR, NULL_VECTOR); + particle.Spawn(); + particle.Activate(); + particle.AcceptInput("Start"); + CreateTimer(0.1, Timer_KillEntity, EntIndexToEntRef(particle.index), TIMER_FLAG_NO_MAPCHANGE); + } + RemoveEntity(this.index); + } +} + +static void OnRemove(SF2_ProjectileBase projectile) +{ + if (projectile.TrailEntity.IsValid()) + { + RemoveEntity(projectile.TrailEntity.index); + } + if (projectile.ParticleEntity.IsValid()) + { + RemoveEntity(projectile.ParticleEntity.index); + } + if (projectile.CritEntity.IsValid()) + { + RemoveEntity(projectile.CritEntity.index); + } +} + +static void MapStart() +{ + PrecacheModel("models/roller.mdl"); + PrecacheModel("models/buildables/sentry3_rockets.mdl"); + PrecacheModel("models/weapons/w_models/w_grenade_grenadelauncher.mdl"); + PrecacheModel("models/weapons/w_models/w_arrow.mdl"); + PrecacheSound("weapons/fx/rics/arrow_impact_flesh2.wav"); +} + +static void StartTouch(int entity, int other) +{ + SF2_ProjectileBase projectile = SF2_ProjectileBase(entity); + + bool hit = true; + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + if (otherPlayer.IsValid) + { + if (otherPlayer.IsInGhostMode || (otherPlayer.IsProxy && !projectile.AttackWaiters)) + { + return; + } + } + else + { + int hitIndex = NPCGetFromEntIndex(other); + if (hitIndex != -1) + { + hit = false; + } + } + + if (projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity") == other) + { + hit = false; + } + + if (SF2_ProjectileBase(other).IsValid()) + { + hit = false; + } + + if (other == 0) + { + hit = true; + } + + if (hit) + { + projectile.DoExplosion(); + } +} + +#include "projectiles/sf2_fireball_projectile.sp" +#include "projectiles/sf2_iceball_projectile.sp" +#include "projectiles/sf2_rocket_projectile.sp" +#include "projectiles/sf2_sentry_rocket_projectile.sp" +#include "projectiles/sf2_cow_mangler_projectile.sp" +#include "projectiles/sf2_grenade_projectile.sp" +#include "projectiles/sf2_arrow_projectile.sp" +#include "projectiles/sf2_baseball_projectile.sp" \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 16c4b86f..6b2e3d11 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -72,6 +72,7 @@ public void OnPluginStart() g_Pages = new ArrayList(sizeof(SF2PageEntityData)); g_PageMusicRanges = new ArrayList(3); + g_EmptySpawnPagePoints = new ArrayList(); char valueToString[32]; @@ -118,7 +119,7 @@ public void OnPluginStart() g_PlayerShakeFrequencyMaxConVar = CreateConVar("sf2_player_shake_frequency_max", "255", "Maximum frequency value of the shake. Should be a value between 1-255.", _, true, 1.0, true, 255.0); g_PlayerShakeAmplitudeMaxConVar = CreateConVar("sf2_player_shake_amplitude_max", "5", "Maximum amplitude value of the shake. Should be a value between 1-16.", _, true, 1.0, true, 16.0); - g_PlayerBlinkRateConVar = CreateConVar("sf2_player_blink_rate", "0.33", "How long (in seconds) each bar on the player's Blink meter lasts.", _, true, 0.0); + g_PlayerBlinkRateConVar = CreateConVar("sf2_player_blink_rate", "1.0", "How long (in seconds) each bar on the player's Blink meter lasts.", _, true, 0.0); g_PlayerBlinkHoldTimeConVar = CreateConVar("sf2_player_blink_holdtime", "0.15", "How long (in seconds) a player will stay in Blink mode when he or she blinks.", _, true, 0.0); g_UltravisionEnabledConVar = CreateConVar("sf2_player_ultravision_enabled", "1", "Enable/Disable player Ultravision. This helps players see in the dark when their Flashlight is off or unavailable.", _, true, 0.0, true, 1.0); @@ -218,11 +219,13 @@ public void OnPluginStart() g_DefaultLegacyHudConVar = CreateConVar("sf2_default_legacy_hud", "0", "Set to 1 if the server should enable the legacy hud by default in their settings.", _, true, 0.0, true, 1.0); - g_DifficultyVoteOptionsConVar = CreateConVar("sf2_difficulty_vote_options", "1,2,3", "What vote options will appear on the Difficulty vote. 1 = Normal, 2 = Hard, 3 = Insane, 6 = Random"); + g_DifficultyVoteOptionsConVar = CreateConVar("sf2_difficulty_vote_options", "1,2,3", "What vote options will appear on the Difficulty vote. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon, 6 = Random"); g_DifficultyVoteRevoteConVar = CreateConVar("sf2_difficulty_vote_runoff", "0.0", "If the winning vote has less precentage of player votes, do a run-off vote.", _, true, 0.0, true, 1.0); g_DifficultyVoteRandomConVar = CreateConVar("sf2_difficulty_random_vote", "1", "If random vote will use a random vote option instead of a random difficulty.", _, true, 0.0, true, 1.0); g_DifficultyNoGracePageConVar = CreateConVar("sf2_difficulty_no_grace_pages", "", "On what difficulties will players be unable to collect pages while grace period is active. 1 = Normal, 2 = Hard, 3 = Insane, 4 = Nightmare, 5 = Apollyon"); + g_HighDifficultyPercentConVar = CreateConVar("sf2_high_difficulty_vote_percentage", "0.0", "The required percentage needed for Nightmare and Apollyon to vote whenever they show up in the vote pool."); + g_FileCheckConVar = CreateConVar("sf2_debug_file_checks", "0", "Determines if the gamemode should look for missing files when loading all the bosses. Note that turning this on leads to longer boss loading times.", _, true, 0.0, true, 1.0); g_MaxRoundsConVar = FindConVar("mp_maxrounds"); @@ -230,15 +233,12 @@ public void OnPluginStart() g_HudSync = CreateHudSynchronizer(); g_HudSync2 = CreateHudSynchronizer(); g_HudSync3 = CreateHudSynchronizer(); + g_HudSync4 = CreateHudSynchronizer(); g_RoundTimerSync = CreateHudSynchronizer(); g_Cookie = RegClientCookie("sf2_newcookies", "", CookieAccess_Private); switch (g_DifficultyConVar.IntValue) { - case Difficulty_Easy: - { - g_RoundDifficultyModifier = DIFFICULTYMODIFIER_NORMAL; - } case Difficulty_Hard: { g_RoundDifficultyModifier = DIFFICULTYMODIFIER_HARD; @@ -276,7 +276,6 @@ public void OnPluginStart() RegConsoleCmd("sm_sltuto", Command_Tutorial); RegConsoleCmd("sm_sf2tutorial", Command_Tutorial); RegConsoleCmd("sm_sf2tuto", Command_Tutorial); - RegConsoleCmd("sm_slupdate", Command_Update); RegConsoleCmd("sm_slpack", Command_Pack); RegConsoleCmd("sm_sf2pack", Command_Pack); RegConsoleCmd("sm_slnextpack", Command_NextPack); @@ -391,6 +390,8 @@ public void OnPluginStart() HookUserMessage(GetUserMessageId("SayText2"), Hook_BlockUserMessageEx, true); //HookUserMessage(GetUserMessageId("TextMsg"), Hook_BlockUserMessage, true); + g_OnGamemodeStartPFwd = new PrivateForward(ET_Ignore); + g_OnGamemodeEnd = new PrivateForward(ET_Ignore); g_OnMapStartPFwd = new PrivateForward(ET_Ignore); g_OnGameFramePFwd = new PrivateForward(ET_Ignore); g_OnRoundStartPFwd = new PrivateForward(ET_Ignore); @@ -405,10 +406,14 @@ public void OnPluginStart() g_OnPlayerEscapePFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerTeamPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_OnPlayerClassPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerLookAtBossPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_OnBossRemovedPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnChaserGetAttackActionPFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_CellByRef); + g_OnChaserGetCustomAttackPossibleStatePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); + g_OnChaserUpdatePosturePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); // Hook sounds. - AddNormalSoundHook(view_as(Hook_NormalSound)); + AddNormalSoundHook(Hook_NormalSound); AddTempEntHook("Fire Bullets", Hook_TEFireBullets); @@ -424,6 +429,10 @@ public void OnPluginStart() SetupMenus(); + InitializeChangelog(); + + InitializeEffects(); + SetupAntiCamping(); SetupBlink(); SetupBreathing(); @@ -442,9 +451,14 @@ public void OnPluginStart() SetupPlayerGroups(); PvP_Initialize(); + PvE_Initialize(); SetupCustomMapEntities(); + SetupEntityActions(); + + InitializeCustomEntities(); + SetupSpecialRounds(); SetupRenevantMode(); @@ -509,7 +523,7 @@ static Action Command_Help(int client, int args) return Plugin_Continue; } - DisplayMenu(g_MenuHelp, client, 30); + g_MenuHelp.Display(client, 30); return Plugin_Handled; } @@ -520,7 +534,7 @@ static Action Command_Settings(int client, int args) return Plugin_Continue; } - DisplayMenu(g_MenuSettings, client, 30); + g_MenuSettings.Display(client, 30); return Plugin_Handled; } @@ -534,13 +548,13 @@ static Action Command_MenuSwitchHud(int client, int args) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Hud Version Title", client); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Use the new HUD"); - DrawPanelItem(panel, "Use the legacy HUD"); + panel.DrawItem("Use the new HUD"); + panel.DrawItem("Use the legacy HUD"); - SendPanelToClient(panel, client, Panel_SettingsHudVersion, 30); + panel.Send(client, Panel_SettingsHudVersion, 30); delete panel; return Plugin_Handled; } @@ -553,11 +567,11 @@ static Action Command_MenuViewBob(int client, int args) } char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings View Bobbing Toggle Title", client); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); - DrawPanelItem(panel, "Enable View Bobbing"); - DrawPanelItem(panel, "Disable View Bobbing"); - SendPanelToClient(panel, client, Panel_SettingsViewBobbing, 30); + Panel panel = new Panel(); + panel.SetTitle(buffer); + panel.DrawItem("Enable View Bobbing"); + panel.DrawItem("Disable View Bobbing"); + panel.Send(client, Panel_SettingsViewBobbing, 30); delete panel; return Plugin_Handled; } @@ -569,7 +583,7 @@ static Action Command_Credits(int client, int args) return Plugin_Continue; } - DisplayMenu(g_MenuCredits, client, MENU_TIME_FOREVER); + g_MenuCredits.Display(client, MENU_TIME_FOREVER); return Plugin_Handled; } @@ -744,7 +758,7 @@ static Action Command_NoPointsAdmin(int client, int args) { char arg2[32]; GetCmdArg(2, arg2, sizeof(arg2)); - mode = !!StringToInt(arg2); + mode = StringToInt(arg2) != 0; } for (int i = 0; i < target_count; i++) @@ -777,7 +791,7 @@ static Action Command_MainMenu(int client, int args) { return Plugin_Continue; } - DisplayMenu(g_MenuMain, client, 30); + g_MenuMain.Display(client, 30); return Plugin_Handled; } @@ -791,16 +805,6 @@ static Action Command_Tutorial(int client, int args) return Plugin_Handled; } -static Action Command_Update(int client, int args) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - DisplayMenu(g_MenuUpdate, client, 30); - return Plugin_Handled; -} - static Action Command_Next(int client, int args) { if (!g_Enabled) @@ -884,7 +888,7 @@ static Action Command_GhostMode(int client, int args) return Plugin_Continue; } - if (IsRoundEnding() || IsRoundInWarmup() || !g_PlayerEliminated[client] || !IsClientParticipating(client) || g_PlayerProxy[client] || IsClientInPvP(client) || IsClientInKart(client) || TF2_IsPlayerInCondition(client,TFCond_Taunting)|| TF2_IsPlayerInCondition(client,TFCond_Charging) || g_LastCommandTime[client] > GetEngineTime()) + if (IsRoundEnding() || IsRoundInWarmup() || !g_PlayerEliminated[client] || !IsClientParticipating(client) || g_PlayerProxy[client] || IsClientInPvP(client) || IsClientInPvE(client) || IsClientInKart(client) || TF2_IsPlayerInCondition(client,TFCond_Taunting)|| TF2_IsPlayerInCondition(client,TFCond_Charging) || g_LastCommandTime[client] > GetEngineTime()) { CPrintToChat(client, "{red}%T", "SF2 Ghost Mode Not Allowed", client); return Plugin_Handled; @@ -906,7 +910,7 @@ static Action Command_GhostMode(int client, int args) CPrintToChat(client, "{dodgerblue}%T", "SF2 Ghost Mode Disabled", client); } - g_LastCommandTime[client] = GetEngineTime()+0.5; + g_LastCommandTime[client] = GetEngineTime() + 0.5; return Plugin_Handled; } @@ -1211,12 +1215,20 @@ static Action Command_SpawnSlender(int client, int args) char arg1[32]; GetCmdArg(1, arg1, sizeof(arg1)); - SF2NPC_BaseNPC Npc = view_as(StringToInt(arg1)); - if (NPCGetUniqueID(Npc.Index) == -1) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(StringToInt(arg1)); + if (!npc.IsValid()) { return Plugin_Handled; } + SF2BossProfileData data; + data = npc.GetProfileData(); + if (data.IsPvEBoss) + { + ReplyToCommand(client, "You may not spawn PvE bosses!"); + return Plugin_Handled; + } + float eyePos[3], eyeAng[3], endPos[3]; GetClientEyePosition(client, eyePos); GetClientEyeAngles(client, eyeAng); @@ -1225,10 +1237,10 @@ static Action Command_SpawnSlender(int client, int args) TR_GetEndPosition(endPos, trace); delete trace; - SpawnSlender(Npc, endPos); + npc.Spawn(endPos); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.GetProfile(profile, sizeof(profile)); + npc.GetProfile(profile, sizeof(profile)); CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Spawned Boss", client); @@ -1253,7 +1265,7 @@ static Action Command_SpawnAllSlenders(int client, int args) char arg1[32]; GetCmdArg(1, arg1, sizeof(arg1)); - bool doTimer = !!StringToInt(arg1); + bool doTimer = StringToInt(arg1) != 0; if (!doTimer) { @@ -1266,13 +1278,19 @@ static Action Command_SpawnAllSlenders(int client, int args) TR_GetEndPosition(endPos, trace); delete trace; - SF2NPC_BaseNPC Npc; - for(int npcIndex;npcIndex<=MAX_BOSSES;npcIndex++) + SF2NPC_BaseNPC npc; + for (int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - Npc = view_as(npcIndex); - if (Npc.IsValid()) + npc = SF2NPC_BaseNPC(npcIndex); + if (npc.IsValid()) { - SpawnSlender(Npc, endPos); + SF2BossProfileData data; + data = npc.GetProfileData(); + if (data.IsPvEBoss) + { + continue; + } + npc.Spawn(endPos); } } } @@ -1305,7 +1323,7 @@ static Action Timer_SpawnAllSlenders(Handle timer, any userid) { return Plugin_Stop; } - if (g_SpawnAllBossesCount >= 64) + if (g_SpawnAllBossesCount >= MAX_BOSSES) { CPrintToChat(client.index, "{royalblue}%t {default}Spawned all bosses at your locations.", "SF2 Prefix"); g_SpawnAllBossesCount = 0; @@ -1320,11 +1338,15 @@ static Action Timer_SpawnAllSlenders(Handle timer, any userid) TR_GetEndPosition(endPos, trace); delete trace; - SF2NPC_BaseNPC Npc; - Npc = view_as(g_SpawnAllBossesCount); - if (Npc.IsValid()) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(g_SpawnAllBossesCount); + if (npc.IsValid()) { - SpawnSlender(Npc, endPos); + SF2BossProfileData data; + data = npc.GetProfileData(); + if (!data.IsPvEBoss) + { + npc.Spawn(endPos); + } } g_SpawnAllBossesCount++; return Plugin_Continue; @@ -1355,6 +1377,14 @@ static Action Command_RemoveSlender(int client, int args) char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + ReplyToCommand(client, "You may not remove PvE bosses!"); + return Plugin_Handled; + } + if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(bossIndex)) { g_SlenderBoxingBossCount -= 1; @@ -1383,11 +1413,19 @@ static Action Command_RemoveAllSlenders(int client, int args) { for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { - if (NPCGetUniqueID(npcIndex) == -1) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(npcIndex); + if (!npc.IsValid()) + { + continue; + } + + SF2BossProfileData data; + data = npc.GetProfileData(); + if (data.IsPvEBoss) { continue; } - NPCRemove(npcIndex); + npc.Remove(); } CPrintToChat(client, "{royalblue}%t {default}Removed all bosses.", "SF2 Prefix", client); } @@ -1500,8 +1538,8 @@ static Action Command_SlenderAttackWaiters(int client, int args) int bossFlags = NPCGetFlags(bossIndex); - bool state = !!StringToInt(arg2); - bool oldState = !!(bossFlags & SFF_ATTACKWAITERS); + bool state = StringToInt(arg2) != 0; + bool oldState = (bossFlags & SFF_ATTACKWAITERS) != 0; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -1553,8 +1591,8 @@ static Action Command_SlenderNoTeleport(int client, int args) int bossFlags = NPCGetFlags(bossIndex); - bool state = !!StringToInt(arg2); - bool oldState = !!(bossFlags & SFF_NOTELEPORT); + bool state = StringToInt(arg2) != 0; + bool oldState = (bossFlags & SFF_NOTELEPORT) != 0; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -1798,7 +1836,7 @@ static Action Command_ForceProxy(int client, int args) char name[MAX_NAME_LENGTH]; if (IsClientSourceTV(target)) { - continue;//Exclude the sourcetv bot + continue; // Exclude the sourcetv bot } FormatEx(name, sizeof(name), "%N", target); @@ -1810,7 +1848,7 @@ static Action Command_ForceProxy(int client, int args) if (g_PlayerProxy[target]) { - continue; //Exclude any active proxies + continue; // Exclude any active proxies } float intPos[3]; @@ -1899,15 +1937,15 @@ static Action Command_ForceDifficulty(int client, int args) int newDifficulty = StringToInt(arg1); - if (newDifficulty < 1) + if (newDifficulty < Difficulty_Normal) { - newDifficulty = 1; + newDifficulty = Difficulty_Normal; } - else if (newDifficulty > 5) + else if (newDifficulty > Difficulty_Apollyon) { - newDifficulty = 5; + newDifficulty = Difficulty_Apollyon; } - else if (newDifficulty > 0 && newDifficulty < 6) + else if (newDifficulty > Difficulty_Easy && newDifficulty < Difficulty_Max) { g_DifficultyConVar.SetInt(newDifficulty); } @@ -1998,9 +2036,9 @@ static Action Command_ForceSpecialRound(int client, int args) { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Bacon Spray.", "SF2 Prefix", client); } - case SPECIALROUND_DOOMBOX: + case SPECIALROUND_SILENTSLENDER: { - CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Stealth Box of Doom.", "SF2 Prefix", client); + CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Silent Slender {default}(Here you die).", "SF2 Prefix", client); } case SPECIALROUND_NOGRACE: { @@ -2090,10 +2128,6 @@ static Action Command_ForceSpecialRound(int client, int args) { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Class Scramble.", "SF2 Prefix", client); } - case SPECIALROUND_2DOOM: - { - CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Silent Slender.", "SF2 Prefix", client); - } case SPECIALROUND_PAGEREWARDS: { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Page Rewards.", "SF2 Prefix", client); @@ -2130,6 +2164,10 @@ static Action Command_ForceSpecialRound(int client, int args) { CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Single Player.", "SF2 Prefix", client); } + case SPECIALROUND_BEATBOX: + { + CPrintToChatAll("{royalblue}%t {collectors}%N {default}set the next special round to {lightblue}Beatbox.", "SF2 Prefix", client); + } } return Plugin_Handled; @@ -2157,8 +2195,16 @@ static Action Command_AddSlender(int client, int args) return Plugin_Handled; } - SF2NPC_BaseNPC Npc = AddProfile(profile); - if (Npc.IsValid()) + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + ReplyToCommand(client, "You may not spawn PvE bosses!"); + return Plugin_Handled; + } + + SF2NPC_BaseNPC npc = AddProfile(profile); + if (npc.IsValid()) { float eyePos[3], eyeAng[3], pos[3]; GetClientEyePosition(client, eyePos); @@ -2168,16 +2214,17 @@ static Action Command_AddSlender(int client, int args) TR_GetEndPosition(pos, trace); delete trace; - SpawnSlender(Npc, pos); + npc.Spawn(pos); - if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(Npc.Index)) + if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(npc.Index)) { - g_SlenderBoxingBossCount += 1; + g_SlenderBoxingBossCount++; } } return Plugin_Handled; } + static void NPCSpawn(const char[] output, int ent, int activator, float delay) { if (!g_Enabled) @@ -2200,19 +2247,18 @@ static void NPCSpawn(const char[] output, int ent, int activator, float delay) return; } char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - SF2NPC_BaseNPC Npc; - for(int npcIndex = 0; npcIndex<=MAX_BOSSES; npcIndex++) + SF2NPC_BaseNPC npc; + for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - Npc = view_as(npcIndex); - if (Npc.IsValid()) + npc = SF2NPC_BaseNPC(npcIndex); + if (npc.IsValid()) { - Npc.GetProfile(profile,sizeof(profile)); - if (strcmp(profile,targetName) == 0) + npc.GetProfile(profile, sizeof(profile)); + if (strcmp(profile, targetName) == 0) { - Npc.UnSpawn(); float pos[3]; GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); - SpawnSlender(Npc, pos); + npc.Spawn(pos); break; } } @@ -2228,7 +2274,7 @@ static void NPCSpawn(const char[] output, int ent, int activator, float delay) ent = -1; if (spawnPoint.Length > 0) { - ent = spawnPoint.Get(GetRandomInt(0,spawnPoint.Length-1)); + ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); } delete spawnPoint; if (IsValidEntity(ent)) @@ -2237,17 +2283,16 @@ static void NPCSpawn(const char[] output, int ent, int activator, float delay) GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - SF2NPC_BaseNPC Npc = view_as(npcIndex); - if (!Npc.IsValid()) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(npcIndex); + if (!npc.IsValid()) { continue; } - if (Npc.Flags & SFF_NOTELEPORT) + if (npc.Flags & SFF_NOTELEPORT) { continue; } - Npc.UnSpawn(); - SpawnSlender(Npc, pos); + npc.Spawn(pos); break; } } @@ -2278,8 +2323,16 @@ static Action Command_AddSlenderFake(int client, int args) return Plugin_Handled; } - SF2NPC_BaseNPC Npc = AddProfile(profile, SFF_FAKE); - if (Npc.IsValid()) + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + ReplyToCommand(client, "You may not spawn PvE bosses!"); + return Plugin_Handled; + } + + SF2NPC_BaseNPC npc = AddProfile(profile, SFF_FAKE); + if (npc.IsValid()) { float eyePos[3], eyeAng[3], pos[3]; GetClientEyePosition(client, eyePos); @@ -2289,7 +2342,7 @@ static Action Command_AddSlenderFake(int client, int args) TR_GetEndPosition(pos, trace); delete trace; - SpawnSlender(Npc, pos); + npc.Spawn(pos); } return Plugin_Handled; @@ -2366,7 +2419,7 @@ static Action Command_ForceState(int client, int args) ClientSetGhostModeState(target, false); TF2_RespawnPlayer(target); TF2_RemoveCondition(target, TFCond_StealthedUserBuffFade); - g_LastCommandTime[target] = GetEngineTime()+0.5; + g_LastCommandTime[target] = GetEngineTime() + 0.5; CreateTimer(0.25, Timer_ForcePlayer, GetClientUserId(target), TIMER_FLAG_NO_MAPCHANGE); } else @@ -2604,7 +2657,7 @@ static Action Command_MaxPlayersOverride(int client, int args) static Action Command_SpecialRoundMode(int client, int args) { g_SpecialRoundBehaviorConVar.BoolValue = !g_SpecialRoundBehaviorConVar.BoolValue; - CPrintToChat(client, "{royalblue}%t {default}Set the special rounds to %s.", "SF2 Prefix", g_SpecialRoundBehaviorConVar.BoolValue ? "always reset upon the next round" : "keep going until all players have played a special round"); + CPrintToChat(client, "{royalblue}%t {default}Set the special rounds to %s.", "SF2 Prefix", g_BlockSuicideDuringRoundConVar.BoolValue ? "always reset upon the next round" : "keep going until all players have played a special round"); return Plugin_Handled; } diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 10e790a5..363a07a2 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -50,10 +50,6 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) // Refresh players. for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i)) - { - continue; - } ClientSetGhostModeState(i, false); g_PlayerPlaying[i] = false; @@ -306,7 +302,6 @@ Action Event_PlayerTeam(Handle event, const char[] name, bool dB) { if (!g_PlayerChoseTeam[client]) { - AFK_SetAFK(client); g_PlayerChoseTeam[client] = true; if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) @@ -384,11 +379,6 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) if (GetClientTeam(client) > 1) { g_LastVisibilityProcess[client] = GetGameTime(); - if (!g_SeeUpdateMenu[client]) - { - g_SeeUpdateMenu[client] = true; - DisplayMenu(g_MenuUpdate, client, 30); - } } if (!IsClientParticipating(client)) { @@ -431,12 +421,9 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } g_PlayerPostWeaponsTimer[client] = null; - g_PlayerIgniteTimer[client] = null; g_PlayerPageRewardCycleTimer[client] = null; g_PlayerFireworkTimer[client] = null; - g_PlayerBossKillSubject[client] = INVALID_ENT_REFERENCE; - g_PlayerGettingPageReward[client] = false; g_PlayerHitsToCrits[client] = 0; g_PlayerHitsToHeads[client] = 0; @@ -444,6 +431,10 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) g_PlayerTrapped[client] = false; g_PlayerTrapCount[client] = 0; + g_PlayerLatchedByTongue[client] = false; + g_PlayerLatchCount[client] = 0; + g_PlayerLatcher[client] = -1; + g_PlayerRandomClassNumber[client] = 1; if (IsPlayerAlive(client) && IsClientParticipating(client)) @@ -507,6 +498,10 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) if (!g_PlayerEliminated[client]) { + if (IsFakeClient(client)) + { + TF2_SetPlayerClass(client, TFClass_Sniper); + } if ((SF_IsRaidMap() || SF_IsBoxingMap()) && !IsRoundPlaying()) { TF2Attrib_SetByDefIndex(client, 10, 7.0); @@ -522,7 +517,6 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) CreateTimer(0.1, Timer_StopAirDash, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - ClientStartDrainingBlinkMeter(client); ClientSetScareBoostEndTime(client, -1.0); HandlePlayerIntroState(client); @@ -547,11 +541,6 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) int red[4] = { 184, 56, 59, 255 }; ClientEnableConstantGlow(client, red); } - - if (SF_IsRenevantMap() && g_RenevantMarkForDeath && !DidClientEscape(client)) - { - TF2_AddCondition(client, TFCond_MarkedForDeathSilent, -1.0); - } } else { @@ -676,8 +665,7 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) int inflictor = event.GetInt("inflictor_entindex"); int owner = inflictor; - // If this player was killed by a boss, play a sound. - if (IsEntityAProjectile(inflictor)) + if (!SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) { owner = GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity"); } @@ -685,7 +673,6 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) if (npcIndex != -1) { int target = GetClientOfUserId(g_SourceTVUserID); - int attackIndex = NPCGetCurrentAttackIndex(npcIndex); if (!IsValidClient(target) || !IsClientSourceTV(target)) //If the server has a source TV bot uses to print boss' name in kill feed. { target = GetClientForDeath(client); @@ -710,65 +697,33 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) SetEntPropString(target, Prop_Data, "m_szNetname", bossName); event.SetString("assister_fallback", ""); - if (!IsEntityAProjectile(inflictor)) + if ((NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) || (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS)) { - if ((NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) || (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS)) + if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLS && SF2_ChaserEntity(owner).IsValid()) { - if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLS) - { - char weaponType[PLATFORM_MAX_PATH]; - int weaponNum = NPCChaserGetAttackWeaponTypeInt(npcIndex, attackIndex); - GetChaserProfileAttackWeaponTypeString(profile, attackIndex, weaponType, sizeof(weaponType)); - event.SetString("weapon_logclassname", weaponType); - event.SetString("weapon", weaponType); - event.SetInt("customkill", weaponNum); - } - else if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS) - { - char weaponType[PLATFORM_MAX_PATH]; - int weaponNum = GetBossProfileWeaponInt(profile); - GetBossProfileWeaponString(profile, weaponType, sizeof(weaponType)); - event.SetString("weapon_logclassname", weaponType); - event.SetString("weapon", weaponType); - event.SetInt("customkill", weaponNum); - } + SF2_ChaserEntity chaser = SF2_ChaserEntity(owner); + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(npcIndex); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(chaser.GetAttackName(), attackData); + event.SetString("weapon_logclassname", attackData.WeaponString); + event.SetString("weapon", attackData.WeaponString); + event.SetInt("customkill", attackData.WeaponInt); } - else + else if (NPCGetFlags(npcIndex) & SFF_WEAPONKILLSONRADIUS) { - event.SetString("weapon", ""); - event.SetString("weapon_logclassname", ""); + char weaponType[PLATFORM_MAX_PATH]; + int weaponNum = GetBossProfileWeaponInt(profile); + GetBossProfileWeaponString(profile, weaponType, sizeof(weaponType)); + event.SetString("weapon_logclassname", weaponType); + event.SetString("weapon", weaponType); + event.SetInt("customkill", weaponNum); } } else { - switch (ProjectileGetFlags(inflictor)) - { - case PROJ_ROCKET: - { - event.SetString("weapon_logclassname", "tf_projectile_rocket"); - event.SetString("weapon", "tf_projectile_rocket"); - } - case PROJ_MANGLER: - { - event.SetString("weapon_logclassname", "cow_mangler"); - event.SetString("weapon", "cow_mangler"); - } - case PROJ_GRENADE: - { - event.SetString("weapon_logclassname", "tf_projectile_pipe"); - event.SetString("weapon", "tf_projectile_pipe"); - } - case PROJ_FIREBALL, PROJ_ICEBALL, PROJ_FIREBALL_ATTACK, PROJ_ICEBALL_ATTACK: - { - event.SetString("weapon_logclassname", "spellbook_fireball"); - event.SetString("weapon", "spellbook_fireball"); - } - case PROJ_SENTRYROCKET: - { - event.SetString("weapon_logclassname", "obj_sentrygun3"); - event.SetString("weapon", "obj_sentrygun3"); - } - } + event.SetString("weapon", ""); + event.SetString("weapon_logclassname", ""); } int userid = GetClientUserId(target); @@ -794,54 +749,44 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) } } } - if (MAX_BOSSES > npcIndex >= 0 && (g_SlenderHasAshKillEffect[npcIndex] || g_SlenderHasCloakKillEffect[npcIndex] - || g_SlenderHasDecapKillEffect[npcIndex] || g_SlenderHasDeleteKillEffect[npcIndex] - || g_SlenderHasDissolveRagdollOnKill[npcIndex] - || g_SlenderHasElectrocuteKillEffect[npcIndex] || g_SlenderHasGoldKillEffect[npcIndex] - || g_SlenderHasIceKillEffect[npcIndex] || g_SlenderHasPlasmaRagdollOnKill[npcIndex] - || g_SlenderHasPushRagdollOnKill[npcIndex] || g_SlenderHasResizeRagdollOnKill[npcIndex] - || g_SlenderHasBurnKillEffect[npcIndex] || g_SlenderHasGibKillEffect[npcIndex])) - { - CreateTimer(0.01, Timer_ModifyRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - if (MAX_BOSSES > npcIndex >= 0 && g_SlenderPlayerCustomDeathFlag[npcIndex]) - { - event.SetInt("death_flags", g_SlenderPlayerSetDeathFlag[npcIndex]); - } - if (MAX_BOSSES > npcIndex >= 0 && g_SlenderHasDecapOrGibKillEffect[npcIndex]) - { - CreateTimer(0.01, Timer_DeGibRagdoll, GetClientUserId(client)); - } - if (MAX_BOSSES > npcIndex >= 0 && g_SlenderHasMultiKillEffect[npcIndex]) - { - CreateTimer(0.01, Timer_MultiRagdoll, GetClientUserId(client)); - } - if (IsEntityAProjectile(inflictor)) + if (npcIndex != -1) { - switch (ProjectileGetFlags(inflictor)) + SF2BossProfileData data; + data = NPCGetProfileData(npcIndex); + g_PlayerBossKillSubject[client] = npcIndex; + + if (data.AshRagdoll || data.CloakRagdoll || data.DecapRagdoll || data.DeleteRagdoll || data.DissolveRagdoll || + data.ElectrocuteRagdoll || data.GoldRagdoll || data.IceRagdoll || data.PlasmaRagdoll || data.PushRagdoll || + data.ResizeRagdoll || data.BurnRagdoll) { - case PROJ_MANGLER: - { - CreateTimer(0.01, Timer_ManglerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - case PROJ_ICEBALL: - { - CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - case PROJ_ICEBALL_ATTACK: - { - CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } + CreateTimer(0.01, Timer_ModifyRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - } - if (MAX_BOSSES > npcIndex >= 0) - { - float value = NPCGetAttributeValue(npcIndex, SF2Attribute_IgnitePlayerOnDeath); - if (MAX_BOSSES > npcIndex >= 0 && value > 0.0) + + if (data.CustomDeathFlag) + { + event.SetInt("death_flags", data.CustomDeathFlagType); + } + + if (data.DecapOrGibRagdoll) { - TF2_IgnitePlayer(client, client); + CreateTimer(0.01, Timer_DeGibRagdoll, GetClientUserId(client)); } + + if (data.MultiEffectRagdoll) + { + CreateTimer(0.01, Timer_MultiRagdoll, GetClientUserId(client)); + } + } + + if (SF2_ProjectileIceball(inflictor).IsValid()) + { + CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + + if (SF2_ProjectileCowMangler(inflictor).IsValid()) + { + CreateTimer(0.01, Timer_ManglerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } #if defined DEBUG @@ -932,7 +877,7 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } #endif - bool fake = !!(event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER); + bool fake = (event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0; int inflictor = event.GetInt("inflictor_entindex"); #if defined DEBUG @@ -966,16 +911,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { g_NpcChaseOnLookTarget[npcIndex].Erase(foundClient); } - switch (NPCGetType(npcIndex)) - { - case SF2BossType_Chaser: - { - if (g_SlenderState[npcIndex] == STATE_CHASE && EntRefToEntIndex(g_SlenderTarget[npcIndex]) == client) - { - g_SlenderGiveUp[npcIndex] = true; - } - } - } } if (IsRoundInWarmup()) @@ -986,10 +921,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { if (!g_PlayerEliminated[client]) { - if (IsFakeClient(client)) - { - TF2_SetPlayerClass(client, TFClass_Sniper); - } if (IsRoundInIntro() || (!IsRoundPlaying() && !IsRoundEnding()) || DidClientEscape(client) || (SF_SpecialRound(SPECIALROUND_1UP) && g_PlayerIn1UpCondition[client] && !g_PlayerDied1Up[client])) { CreateTimer(0.3, Timer_RespawnPlayer, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); @@ -1006,83 +937,8 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { } - int owner = inflictor; - if (IsEntityAProjectile(inflictor)) - { - owner = GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity"); - } - - // If this player was killed by a boss, play a sound, or print a message. - int npcIndex = NPCGetFromEntIndex(owner); - if (npcIndex != -1) - { - int slender = NPCGetEntIndex(npcIndex); - if (slender && slender != INVALID_ENT_REFERENCE) - { - g_PlayerBossKillSubject[client] = EntIndexToEntRef(slender); - } - - char npcProfile[SF2_MAX_PROFILE_NAME_LENGTH], buffer[PLATFORM_MAX_PATH], bossName[SF2_MAX_NAME_LENGTH]; - NPCGetProfile(npcIndex, npcProfile, sizeof(npcProfile)); - NPCGetBossName(npcIndex, bossName, sizeof(bossName)); - - #if defined _store_included - int difficulty = GetLocalGlobalDifficulty(npcIndex); - if (NPCGetDrainCreditState(npcIndex)) - { - Store_SetClientCredits(client, Store_GetClientCredits(client) - NPCGetDrainCreditAmount(npcIndex, difficulty)); - CPrintToChat(client, "{valve}%s{default} has stolen {green}%i credits{default} from you.", bossName, NPCGetDrainCreditAmount(npcIndex, difficulty)); - } - #endif - - ArrayList soundList; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileAttackKilledClientSounds(npcProfile, soundInfo); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0' && g_PlayerEliminated[client]) - { - EmitSoundToClient(client, buffer, _, SNDCHAN_STATIC, SNDLEVEL_HELICOPTER); - } - } - - GetChaserProfileAttackKilledAllSounds(npcProfile, soundInfo); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0' && g_PlayerEliminated[client]) - { - EmitSoundToAll(buffer, _, SNDCHAN_STATIC, SNDLEVEL_HELICOPTER); - } - } - soundList = null; - - SlenderPrintChatMessage(npcIndex, client); - - SlenderPerformVoice(npcIndex, _, SF2BossSound_AttackKilled); - } - CreateTimer(0.2, Timer_CheckRoundWinConditions, _, TIMER_FLAG_NO_MAPCHANGE); - // Notify to other bosses that this player has died. - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (EntRefToEntIndex(g_SlenderTarget[i]) == client) - { - g_SlenderInterruptConditions[i] |= COND_CHASETARGETINVALIDATED; - GetClientAbsOrigin(client, g_SlenderChaseDeathPosition[i]); - g_BossPathFollower[i].Invalidate(); - } - } - if (g_IgnoreRedPlayerDeathSwapConVar.BoolValue && GetClientTeam(client) == TFTeam_Red) { g_PlayerEliminated[client] = false; @@ -1118,7 +974,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) } g_PlayerPostWeaponsTimer[client] = null; - g_PlayerIgniteTimer[client] = null; g_PlayerPageRewardCycleTimer[client] = null; g_PlayerFireworkTimer[client] = null; @@ -1129,6 +984,10 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) g_PlayerTrapped[client] = false; g_PlayerTrapCount[client] = 0; + g_PlayerLatchedByTongue[client] = false; + g_PlayerLatchCount[client] = 0; + g_PlayerLatcher[client] = -1; + g_PlayerRandomClassNumber[client] = 1; } if (!IsRoundEnding() && !g_RoundWaitingForPlayers) diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 139e3507..89f7bf68 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -13,28 +13,24 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max { RegPluginLibrary("sf2"); - g_OnBossAddedFwd = new GlobalForward("SF2_OnBossAdded", ET_Ignore, Param_Cell); - g_OnBossSpawnFwd = new GlobalForward("SF2_OnBossSpawn", ET_Ignore, Param_Cell); - g_OnBossDespawnFwd = new GlobalForward("SF2_OnBossDespawn", ET_Ignore, Param_Cell); g_OnBossChangeStateFwd = new GlobalForward("SF2_OnBossChangeState", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); - g_OnBossAnimationUpdateFwd = new GlobalForward("SF2_OnBossAnimationUpdate", ET_Hook, Param_Cell); g_OnBossGetSpeedFwd = new GlobalForward("SF2_OnBossGetSpeed", ET_Hook, Param_Cell, Param_FloatByRef); g_OnBossGetWalkSpeedFwd = new GlobalForward("SF2_OnBossGetWalkSpeed", ET_Hook, Param_Cell, Param_FloatByRef); g_OnBossHearEntityFwd = new GlobalForward("SF2_OnBossHearEntity", ET_Hook, Param_Cell, Param_Cell, Param_Cell); g_OnBossSeeEntityFwd = new GlobalForward("SF2_OnBossSeeEntity", ET_Hook, Param_Cell, Param_Cell); - g_OnBossRemovedFwd = new GlobalForward("SF2_OnBossRemoved", ET_Ignore, Param_Cell); g_OnBossStunnedFwd = new GlobalForward("SF2_OnBossStunned", ET_Ignore, Param_Cell, Param_Cell); + g_OnBossKilledFwd = new GlobalForward("SF2_OnBossKilled", ET_Ignore, Param_Cell, Param_Cell); g_OnBossCloakedFwd = new GlobalForward("SF2_OnBossCloaked", ET_Ignore, Param_Cell); g_OnBossDecloakedFwd = new GlobalForward("SF2_OnBossDecloaked", ET_Ignore, Param_Cell); - g_OnBossFinishSpawningFwd = new GlobalForward("SF2_OnBossFinishSpawning", ET_Ignore, Param_Cell); g_OnBossPreAttackFwd = new GlobalForward("SF2_OnBossPreAttack", ET_Ignore, Param_Cell, Param_Cell); - g_OnBossAttackedFwd = new GlobalForward("SF2_OnBossAttacked", ET_Ignore, Param_Cell, Param_Cell); + g_OnBossPreTakeDamageFwd = new GlobalForward("SF2_OnBossPreTakeDamage", ET_Hook, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + g_OnBossPreFlashlightDamageFwd = new GlobalForward("SF2_OnBossPreFlashlightDamage", ET_Hook, Param_Cell, Param_Cell); + g_OnChaserBossGetSuspendActionFwd = new GlobalForward("SF2_OnChaserBossGetSuspendAction", ET_Hook, Param_Cell, Param_CellByRef); g_OnPagesSpawnedFwd = new GlobalForward("SF2_OnPagesSpawned", ET_Ignore); g_OnRoundStateChangeFwd = new GlobalForward("SF2_OnRoundStateChange", ET_Ignore, Param_Cell, Param_Cell); g_OnClientCollectPageFwd = new GlobalForward("SF2_OnClientCollectPage", ET_Ignore, Param_Cell, Param_Cell); g_OnClientBlinkFwd = new GlobalForward("SF2_OnClientBlink", ET_Ignore, Param_Cell); g_OnClientScareFwd = new GlobalForward("SF2_OnClientScare", ET_Ignore, Param_Cell, Param_Cell); - g_OnClientCaughtByBossFwd = new GlobalForward("SF2_OnClientCaughtByBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnClientGiveQueuePointsFwd = new GlobalForward("SF2_OnClientGiveQueuePoints", ET_Hook, Param_Cell, Param_CellByRef); g_OnClientActivateFlashlightFwd = new GlobalForward("SF2_OnClientActivateFlashlight", ET_Ignore, Param_Cell); g_OnClientDeactivateFlashlightFwd = new GlobalForward("SF2_OnClientDeactivateFlashlight", ET_Ignore, Param_Cell); @@ -45,6 +41,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnClientLooksAtBossFwd = new GlobalForward("SF2_OnClientLooksAtBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnClientLooksAwayFromBossFwd = new GlobalForward("SF2_OnClientLooksAwayFromBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnClientStartDeathCamFwd = new GlobalForward("SF2_OnClientStartDeathCam", ET_Ignore, Param_Cell, Param_Cell); + g_OnClientPreKillDeathCamFwd = new GlobalForward("SF2_OnClientPreKillDeathCam", ET_Ignore, Param_Cell, Param_Cell); g_OnClientEndDeathCamFwd = new GlobalForward("SF2_OnClientEndDeathCam", ET_Ignore, Param_Cell, Param_Cell); g_OnClientGetDefaultWalkSpeedFwd = new GlobalForward("SF2_OnClientGetDefaultWalkSpeed", ET_Hook, Param_Cell, Param_CellByRef); g_OnClientGetDefaultSprintSpeedFwd = new GlobalForward("SF2_OnClientGetDefaultSprintSpeed", ET_Hook, Param_Cell, Param_CellByRef); @@ -52,19 +49,21 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnClientSpawnedAsProxyFwd = new GlobalForward("SF2_OnClientSpawnedAsProxy", ET_Ignore, Param_Cell); g_OnClientDamagedByBossFwd = new GlobalForward("SF2_OnClientDamagedByBoss", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Cell); g_OnGroupGiveQueuePointsFwd = new GlobalForward("SF2_OnGroupGiveQueuePoints", ET_Hook, Param_Cell, Param_CellByRef); - g_OnRenevantTriggerWaveFwd = new GlobalForward("SF2_OnRenevantWaveTrigger", ET_Ignore, Param_Cell); g_OnBossPackVoteStartFwd = new GlobalForward("SF2_OnBossPackVoteStart", ET_Ignore); g_OnDifficultyChangeFwd = new GlobalForward("SF2_OnDifficultyChanged", ET_Ignore, Param_Cell, Param_Cell); g_OnClientEnterGameFwd = new GlobalForward("SF2_OnClientEnterGame", ET_Hook, Param_Cell); g_OnGroupEnterGameFwd = new GlobalForward("SF2_OnGroupEnterGame", ET_Hook, Param_Cell); g_OnEverythingLoadedFwd = new GlobalForward("SF2_OnEverythingLoaded", ET_Ignore); g_OnDifficultyVoteFinishedFwd = new GlobalForward("SF2_OnDifficultyVoteFinished", ET_Ignore, Param_Cell, Param_Cell); + g_OnIsBossCustomAttackPossibleFwd = new GlobalForward("SF2_OnIsBossCustomAttackPossible", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell); + g_OnBossGetCustomAttackActionFwd = new GlobalForward("SF2_OnBossGetCustomAttackAction", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell, Param_CellByRef); CreateNative("SF2_GetConfig", Native_GetConfig); CreateNative("SF2_IsRunning", Native_IsRunning); CreateNative("SF2_GetRoundState", Native_GetRoundState); CreateNative("SF2_IsRoundInGracePeriod", Native_IsRoundInGracePeriod); CreateNative("SF2_GetCurrentDifficulty", Native_GetCurrentDifficulty); + CreateNative("SF2_GetBossDifficulty", Native_GetBossDifficulty); CreateNative("SF2_GetDifficultyModifier", Native_GetDifficultyModifier); CreateNative("SF2_IsInSpecialRound", Native_IsInSpecialRound); @@ -72,6 +71,12 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("SF2_GetClientQueuePoints", Native_GetClientQueuePoints); CreateNative("SF2_SetClientQueuePoints", Native_SetClientQueuePoints); + CreateNative("SF2_IsValidClient", Native_IsValidClient); + CreateNative("SF2_IsClientCritBoosted", Native_IsClientCritBoosted); + CreateNative("SF2_IsClientMiniCritBoosted", Native_IsClientMiniCritBoosted); + CreateNative("SF2_IsClientUbercharged", Native_IsClientUbercharged); + CreateNative("SF2_IsClientInKart", Native_IsClientInKart); + CreateNative("SF2_IsClientInCondition", Native_IsClientInCondition); CreateNative("SF2_IsClientEliminated", Native_IsClientEliminated); CreateNative("SF2_IsClientInGhostMode", Native_IsClientInGhostMode); CreateNative("SF2_IsClientProxy", Native_IsClientProxy); @@ -110,105 +115,44 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("SF2_ClientForceProxy", Native_ClientForceProxy); CreateNative("SF2_CollectAsPage", Native_CollectAsPage); - - CreateNative("SF2_GetMaxBossCount", Native_GetMaxBosses); - CreateNative("SF2_EntIndexToBossIndex", Native_EntIndexToBossIndex); - CreateNative("SF2_BossIndexToEntIndex", Native_BossIndexToEntIndex); - CreateNative("SF2_BossIndexToEntIndexEx", Native_BossIndexToEntIndexEx); - CreateNative("SF2_BossIDToBossIndex", Native_BossIDToBossIndex); - CreateNative("SF2_BossIndexToBossID", Native_BossIndexToBossID); - - CreateNative("SF2_UpdateBossAnimation", Native_UpdateBossAnimation); + CreateNative("SF2_GetEmptyPageSpawnPoints", Native_GetEmptyPageSpawnPoints); CreateNative("SF2_ForceBossJump", Native_ForceBossJump); - CreateNative("SF2_AddBoss", Native_AddBoss); - CreateNative("SF2_RemoveBoss", Native_RemoveBoss); - - CreateNative("SF2_GetBossName", Native_GetBossName); - CreateNative("SF2_GetBossType", Native_GetBossType); - - CreateNative("SF2_GetBossFlags", Native_GetBossFlags); - CreateNative("SF2_SetBossFlags", Native_SetBossFlags); - - CreateNative("SF2_SpawnBoss", Native_SpawnBoss); - CreateNative("SF2_IsBossSpawning", Native_IsBossSpawning); - CreateNative("SF2_DespawnBoss", Native_DespawnBoss); - - CreateNative("SF2_PerformBossVoice", Native_PerformBossVoice); - CreateNative("SF2_GetBossModelEntity", Native_GetBossModelEntity); CreateNative("SF2_GetBossTarget", Native_GetBossTarget); CreateNative("SF2_SetBossTarget", Native_SetBossTarget); - CreateNative("SF2_GetBossPathFollower", Native_GetBossPathFollower); - CreateNative("SF2_GetBossMaster", Native_GetBossMaster); - CreateNative("SF2_GetBossIdleLifetime", Native_GetBossIdleLifetime); - CreateNative("SF2_GetBossState", Native_GetBossState); - CreateNative("SF2_SetBossState", Native_SetBossState); - - CreateNative("SF2_GetBossEyePosition", Native_GetBossEyePosition); - CreateNative("SF2_GetBossEyePositionOffset", Native_GetBossEyePositionOffset); - CreateNative("SF2_GetBossFOV", Native_GetBossFOV); - - CreateNative("SF2_GetBossTimeUntilAlert", Native_GetBossTimeUntilAlert); - CreateNative("SF2_GetBossTimeUntilNoPersistence", Native_GetBossTimeUntilNoPersistence); - CreateNative("SF2_GetBossTimeUntilIdle", Native_GetBossTimeUntilIdle); - CreateNative("SF2_SetBossTimeUntilAlert", Native_SetBossTimeUntilAlert); - CreateNative("SF2_SetBossTimeUntilNoPersistence", Native_SetBossTimeUntilNoPersistence); - CreateNative("SF2_SetBossTimeUntilIdle", Native_SetBossTimeUntilIdle); - CreateNative("SF2_IsBossStunnable", Native_IsBossStunnable); CreateNative("SF2_IsBossStunnableByFlashlight", Native_IsBossStunnableByFlashlight); CreateNative("SF2_IsBossCloaked", Native_IsBossCloaked); CreateNative("SF2_GetBossStunHealth", Native_GetBossStunHealth); CreateNative("SF2_SetBossStunHealth", Native_SetBossStunHealth); - CreateNative("SF2_GetBossNextStunTime", Native_GetBossNextStunTime); - CreateNative("SF2_SetBossNextStunTime", Native_SetBossNextStunTime); - CreateNative("SF2_ForceBossGiveUp", Native_ForceBossGiveUp); CreateNative("SF2_GetBossGoalPosition", Native_GetBossGoalPosition); - CreateNative("SF2_CanBossHearClient", Native_CanBossHearClient); - CreateNative("SF2_CreateBossSoundHint", Native_CreateBossSoundHint); - - CreateNative("SF2_GetBossProjectileType", Native_GetBossProjectileType); - - CreateNative("SF2_GetBossCurrentAttackIndex", Native_GetBossCurrentAttackIndex); - CreateNative("SF2_SetBossCurrentAttackIndex", Native_SetBossCurrentAttackIndex); - CreateNative("SF2_GetBossMaxAttackIndexes", Native_GetBossMaxAttackIndexes); - CreateNative("SF2_GetBossAttackRunDurationTime", Native_GetBossAttackRunDurationTime); - CreateNative("SF2_SetBossAttackRunDurationTime", Native_SetBossAttackRunDurationTime); - CreateNative("SF2_GetBossAttackRunDelayTime", Native_GetBossAttackRunDelayTime); - CreateNative("SF2_SetBossAttackRunDelayTime", Native_SetBossAttackRunDelayTime); - CreateNative("SF2_GetBossAttackIndexDamageType", Native_GetBossAttackIndexDamageType); - CreateNative("SF2_GetBossAttackIndexDamage", Native_GetBossAttackIndexDamage); - CreateNative("SF2_GetBossAttackIndexSpread", Native_GetBossAttackIndexSpread); - CreateNative("SF2_GetBossAttackIndexRange", Native_GetBossAttackIndexRange); - CreateNative("SF2_GetBossAttackIndexType", Native_GetBossAttackIndexType); - - CreateNative("SF2_GetBossTeleportThinkTimer", Native_GetBossTeleportThinkTimer); - CreateNative("SF2_SetBossTeleportThinkTimer", Native_SetBossTeleportThinkTimer); - CreateNative("SF2_GetBossTeleportTarget", Native_GetBossTeleportTarget); - - CreateNative("SF2_GetBossThinkTimer", Native_GetBossThinkTimer); - CreateNative("SF2_SetBossThinkTimer", Native_SetBossThinkTimer); - CreateNative("SF2_UnhookBossThinkHook", Native_UnhookBossThinkHook); CreateNative("SF2_GetVectorSquareMagnitude", Native_GetVectorSquareMagnitude); CreateNative("SF2_InitiateBossPackVote", Native_InitiateBossPackVote); - CreateNative("SF2_GetProjectileFlags", Native_GetProjectileFlags); - CreateNative("SF2_SetProjectileFlags", Native_SetProjectileFlags); CreateNative("SF2_IsSurvivalMap", Native_IsSurvivalMap); CreateNative("SF2_IsBoxingMap", Native_IsBoxingMap); CreateNative("SF2_IsRaidMap", Native_IsRaidMap); CreateNative("SF2_IsProxyMap", Native_IsProxyMap); - CreateNative("SF2_IsRenevantMap", Native_IsRenevantMap); CreateNative("SF2_IsSlaughterRunMap", Native_IsSlaughterRunMap); + SF2_BaseBoss.SetupAPI(); + SF2_PlaySequenceAndWait.SetupAPI(); + NPCChaser_InitializeAPI(); + NPCStatue_InitializeAPI(); + PvP_InitializeAPI(); + PvE_InitializeAPI(); + + Renevant_InitializeAPI(); SpecialRoundInitializeAPI(); + SetupMethodmapAPI(); + SetupBossProfileNatives(); #if defined _steamtools_included @@ -334,7 +278,7 @@ void SDK_Init() PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CBaseTrigger::PassesTriggerFilters"); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); - if ((g_SDKPassesTriggerFilters = EndPrepSDKCall()) == INVALID_HANDLE) + if ((g_SDKPassesTriggerFilters = EndPrepSDKCall()) == null) { SetFailState("Failed to setup CBaseTrigger::PassesTriggerFilters call from gamedata!"); } @@ -347,6 +291,26 @@ void SDK_Init() SetFailState("Couldn't find CBaseEntity::GetSmoothedVelocity offset from SF2 gamedata!"); }*/ + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CBaseAnimating::LookupBone"); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + if ((g_SDKLookupBone = EndPrepSDKCall()) == null) + { + LogError("Failed to setup CBaseAnimating::LookupBone call from gamedata!"); + } + + // void CBaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CBaseAnimating::GetBonePosition"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef, .encflags = VENCODE_FLAG_COPYBACK); + PrepSDKCall_AddParameter(SDKType_QAngle, SDKPass_ByRef, .encflags = VENCODE_FLAG_COPYBACK); + if ((g_SDKGetBonePosition = EndPrepSDKCall()) == null) + { + LogError("Failed to setup CBaseAnimating::GetBonePosition call from gamedata"); + } + //Hook_ClientWantsLagCompensationOnEntity int offset = gameData.GetOffset("CTFPlayer::WantsLagCompensationOnEntity"); g_DHookWantsLagCompensationOnEntity = new DynamicHook(offset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity); @@ -423,21 +387,22 @@ static any Native_GetCurrentDifficulty(Handle plugin, int numParams) return g_DifficultyConVar.IntValue; } +static any Native_GetBossDifficulty(Handle plugin, int numParams) +{ + return GetLocalGlobalDifficulty(GetNativeCell(1)); +} + static any Native_GetDifficultyModifier(Handle plugin, int numParams) { int difficulty = GetNativeCell(1); - if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + if (difficulty < Difficulty_Normal || difficulty >= Difficulty_Max) { - LogError("Difficulty parameter can only be from %d to %d!", Difficulty_Easy, Difficulty_Max - 1); + LogError("Difficulty parameter can only be from %d to %d!", Difficulty_Normal, Difficulty_Max - 1); return 1; } switch (difficulty) { - case Difficulty_Easy: - { - return DIFFICULTYMODIFIER_NORMAL; - } case Difficulty_Hard: { return DIFFICULTYMODIFIER_HARD; @@ -480,6 +445,36 @@ static any Native_SetClientQueuePoints(Handle plugin, int numParams) return 0; } +static any Native_IsValidClient(Handle plugin, int numParams) +{ + return IsValidClient(GetNativeCell(1)); +} + +static any Native_IsClientCritBoosted(Handle plugin, int numParams) +{ + return IsClientCritBoosted(GetNativeCell(1)); +} + +static any Native_IsClientMiniCritBoosted(Handle plugin, int numParams) +{ + return TF2_IsMiniCritBuffed(GetNativeCell(1)); +} + +static any Native_IsClientUbercharged(Handle plugin, int numParams) +{ + return IsClientCritUbercharged(GetNativeCell(1)); +} + +static any Native_IsClientInKart(Handle plugin, int numParams) +{ + return IsClientInKart(GetNativeCell(1)); +} + +static any Native_IsClientInCondition(Handle plugin, int numParams) +{ + return TF2_IsPlayerInCondition(GetNativeCell(1), GetNativeCell(2)); +} + static any Native_IsClientEliminated(Handle plugin, int numParams) { return g_PlayerEliminated[GetNativeCell(1)]; @@ -591,7 +586,7 @@ static any Native_GetClientSprintPoints(Handle plugin, int numParams) static any Native_SetClientSprintPoints(Handle plugin, int numParams) { - g_PlayerSprintPoints[GetNativeCell(1)] = GetNativeCell(2); + ClientSetSprintPoints(GetNativeCell(1), GetNativeCell(2)); return 0; } @@ -651,40 +646,9 @@ static any Native_CollectAsPage(Handle plugin, int numParams) return 0; } -static any Native_GetMaxBosses(Handle plugin, int numParams) -{ - return MAX_BOSSES; -} - -static any Native_EntIndexToBossIndex(Handle plugin, int numParams) -{ - return NPCGetFromEntIndex(GetNativeCell(1)); -} - -static any Native_BossIndexToEntIndex(Handle plugin, int numParams) -{ - return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); -} - -static any Native_BossIndexToEntIndexEx(Handle plugin, int numParams) +static any Native_GetEmptyPageSpawnPoints(Handle plugin, int numParams) { - return NPCGetEntIndex(GetNativeCell(1)); -} - -static any Native_BossIDToBossIndex(Handle plugin, int numParams) -{ - return NPCGetFromUniqueID(GetNativeCell(1)); -} - -static any Native_BossIndexToBossID(Handle plugin, int numParams) -{ - return NPCGetUniqueID(GetNativeCell(1)); -} - -static any Native_UpdateBossAnimation(Handle plugin, int numParams) -{ - NPCChaserUpdateBossAnimation(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); - return 0; + return g_EmptySpawnPagePoints; } static any Native_ForceBossJump(Handle plugin, int numParams) @@ -696,93 +660,6 @@ static any Native_ForceBossJump(Handle plugin, int numParams) return 0; } -static any Native_AddBoss(Handle plugin, int numParams) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - GetNativeString(1, profile, sizeof(profile)); - - int flags = GetNativeCell(2); - bool spawnCompanions = GetNativeCell(3); - bool playSpawnSound = GetNativeCell(4); - - return AddProfile(profile, flags, _, spawnCompanions, playSpawnSound); -} - -static any Native_RemoveBoss(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - RemoveProfile(boss.Index); - return 0; -} - -static any Native_DespawnBoss(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - RemoveSlender(boss.Index); - return 0; -} - -static any Native_PerformBossVoice(Handle plugin, int numParams) -{ - SlenderPerformVoice(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3)); - return 0; -} - -static any Native_GetBossName(Handle plugin, int numParams) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(GetNativeCell(1), profile, sizeof(profile)); - - SetNativeString(2, profile, GetNativeCell(3)); - return 0; -} - -static any Native_GetBossType(Handle plugin, int numParams) -{ - return NPCGetType(GetNativeCell(1)); -} - -static any Native_GetBossFlags(Handle plugin, int numParams) -{ - return NPCGetFlags(GetNativeCell(1)); -} - -static any Native_SetBossFlags(Handle plugin, int numParams) -{ - NPCSetFlags(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - -static any Native_SpawnBoss(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - float position[3]; - GetNativeArray(2, position, 3); - - SpawnSlender(boss, position); - return 0; -} - -static any Native_IsBossSpawning(Handle plugin, int numParams) -{ - return g_SlenderSpawning[GetNativeCell(1)]; -} - static any Native_GetBossModelEntity(Handle plugin, int numParams) { return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); @@ -790,340 +667,57 @@ static any Native_GetBossModelEntity(Handle plugin, int numParams) static any Native_GetBossTarget(Handle plugin, int numParams) { - return EntRefToEntIndex(g_SlenderTarget[GetNativeCell(1)]); + SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + return boss.Target.index; } static any Native_SetBossTarget(Handle plugin, int numParams) { - g_SlenderTarget[GetNativeCell(1)] = EntIndexToEntRef(GetNativeCell(2)); - return 0; -} - -static any Native_GetBossPathFollower(Handle plugin, int numParams) -{ - return g_BossPathFollower[GetNativeCell(1)]; -} - -static any Native_GetBossMaster(Handle plugin, int numParams) -{ - return g_SlenderCopyMaster[GetNativeCell(1)]; -} - -static any Native_GetBossIdleLifetime(Handle plugin, int numParams) -{ - return NPCGetIdleLifetime(GetNativeCell(1), GetNativeCell(2)); -} - -static any Native_GetBossState(Handle plugin, int numParams) -{ - return g_SlenderState[GetNativeCell(1)]; -} - -static any Native_SetBossState(Handle plugin, int numParams) -{ - g_SlenderState[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossEyePosition(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid() || !IsValidEntity(boss.EntIndex)) - { - return 0; - } - - float eyePos[3]; - boss.GetEyePosition(eyePos); - - SetNativeArray(2, eyePos, 3); - return 0; -} - -static any Native_GetBossEyePositionOffset(Handle plugin, int numParams) -{ - SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); - if (!boss.IsValid()) - { - return 0; - } - - float eyePos[3]; - boss.GetEyePositionOffset(eyePos); - - SetNativeArray(2, eyePos, 3); - return 0; -} - -static any Native_GetBossFOV(Handle plugin, int numParams) -{ - return NPCGetFOV(GetNativeCell(1)); -} - -static any Native_GetBossTimeUntilNoPersistence(Handle plugin, int numParams) -{ - return g_SlenderTimeUntilNoPersistence[GetNativeCell(1)]; -} - -static any Native_SetBossTimeUntilNoPersistence(Handle plugin, int numParams) -{ - g_SlenderTimeUntilNoPersistence[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossTimeUntilIdle(Handle plugin, int numParams) -{ - return g_SlenderTimeUntilIdle[GetNativeCell(1)]; -} - -static any Native_SetBossTimeUntilIdle(Handle plugin, int numParams) -{ - g_SlenderTimeUntilIdle[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossTimeUntilAlert(Handle plugin, int numParams) -{ - return g_SlenderTimeUntilAlert[GetNativeCell(1)]; -} - -static any Native_SetBossTimeUntilAlert(Handle plugin, int numParams) -{ - g_SlenderTimeUntilAlert[GetNativeCell(1)] = GetNativeCell(2); + SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + boss.Target = CBaseEntity(GetNativeCell(2)); return 0; } static any Native_IsBossStunnable(Handle plugin, int numParams) { - return SF2NPC_Chaser(GetNativeCell(1)).StunEnabled; + return SF2NPC_Chaser(GetNativeCell(1)).GetProfileData().StunEnabled; } static any Native_IsBossStunnableByFlashlight(Handle plugin, int numParams) { - return SF2NPC_Chaser(GetNativeCell(1)).StunByFlashlightEnabled; + SF2ChaserBossProfileData data; + data = NPCChaserGetProfileData(GetNativeCell(1)); + return data.FlashlightStun[1]; } static any Native_IsBossCloaked(Handle plugin, int numParams) { - return g_NpcHasCloaked[GetNativeCell(1)]; + return SF2_ChaserEntity(NPCGetEntIndex(GetNativeCell(1))).HasCloaked; } static any Native_GetBossStunHealth(Handle plugin, int numParams) { - return SF2NPC_Chaser(GetNativeCell(1)).StunHealth; + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(GetNativeCell(1))); + if (!chaser.IsValid()) + { + return 0; + } + return chaser.StunHealth; } static any Native_SetBossStunHealth(Handle plugin, int numParams) { - SF2NPC_Chaser(GetNativeCell(1)).StunHealth = GetNativeCell(2); - return 0; -} - -static any Native_GetBossNextStunTime(Handle plugin, int numParams) -{ - return g_SlenderNextStunTime[GetNativeCell(1)]; -} - -static any Native_SetBossNextStunTime(Handle plugin, int numParams) -{ - g_SlenderNextStunTime[GetNativeCell(1)] = GetNativeCell(2); - return 0; -} - -static any Native_ForceBossGiveUp(Handle plugin, int numParams) -{ - g_SlenderGiveUp[GetNativeCell(1)] = false; - return 0; -} - -static any Native_GetBossGoalPosition(Handle plugin, int numParams) -{ - SetNativeArray(2, g_SlenderGoalPos[GetNativeCell(1)], 3); - return 0; -} - -static any Native_CanBossHearClient(Handle plugin, int numParams) -{ - return SlenderCanHearPlayer(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3)); -} - -static any Native_CreateBossSoundHint(Handle plugin, int numParams) -{ - SF2NPC_Chaser boss = SF2NPC_Chaser(GetNativeCell(1)); - if (!boss.IsValid() || boss.Type != SF2BossType_Chaser || !IsValidEntity(boss.EntIndex)) + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(GetNativeCell(1))); + if (!chaser.IsValid()) { return 0; } - - SoundType soundType = GetNativeCell(2); - - float position[3]; - GetNativeArray(3, position, 3); - - int difficulty = GetNativeCell(4); - - switch (soundType) - { - case SoundType_Footstep, SoundType_LoudFootstep, SoundType_QuietFootstep: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDFOOTSTEP); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddFootstep(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - case SoundType_Voice: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDVOICE); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddVoice(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - case SoundType_Weapon: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDWEAPON); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddWeapon(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - case SoundType_Flashlight: - { - CopyVector(position, g_SlenderTargetSoundTempPos[boss.Index]); - g_SlenderInterruptConditions[boss.Index] |= (COND_HEARDSUSPICIOUSSOUND | COND_HEARDFLASHLIGHT); - if (boss.State == STATE_ALERT && NPCChaserIsAutoChaseEnabled(boss.Index) && g_SlenderAutoChaseCooldown[boss.Index] <= GetGameTime()) - { - g_SlenderAutoChaseCount[boss.Index] += NPCChaserAutoChaseAddWeapon(boss.Index, difficulty); - g_SlenderAutoChaseCooldown[boss.Index] = GetGameTime() + 0.3; - } - } - } - return 0; -} - -static any Native_GetBossProjectileType(Handle plugin, int numParams) -{ - return NPCChaserGetProjectileType(GetNativeCell(1)); -} - -static any Native_SetBossCurrentAttackIndex(Handle plugin, int numParams) -{ - NPCSetCurrentAttackIndex(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - -static any Native_GetBossCurrentAttackIndex(Handle plugin, int numParams) -{ - return NPCGetCurrentAttackIndex(GetNativeCell(1)); -} - -static any Native_GetBossMaxAttackIndexes(Handle plugin, int numParams) -{ - return NPCChaserGetAttackCount(GetNativeCell(1)); -} - -static any Native_GetBossAttackRunDurationTime(Handle plugin, int numParams) -{ - return g_NpcBaseAttackRunDurationTime[GetNativeCell(1)][GetNativeCell(2)]; -} - -static any Native_SetBossAttackRunDurationTime(Handle plugin, int numParams) -{ - g_NpcBaseAttackRunDurationTime[GetNativeCell(1)][GetNativeCell(2)] = GetNativeCell(3); + chaser.StunHealth = GetNativeCell(2); return 0; } -static any Native_GetBossAttackRunDelayTime(Handle plugin, int numParams) -{ - return g_NpcBaseAttackRunDelayTime[GetNativeCell(1)][GetNativeCell(2)]; -} - -static any Native_SetBossAttackRunDelayTime(Handle plugin, int numParams) -{ - g_NpcBaseAttackRunDelayTime[GetNativeCell(1)][GetNativeCell(2)] = GetNativeCell(3); - return 0; -} - -static any Native_GetBossAttackIndexDamageType(Handle plugin, int numParams) -{ - return NPCChaserGetAttackDamageType(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); -} - -static any Native_GetBossAttackIndexDamage(Handle plugin, int numParams) -{ - return NPCChaserGetAttackDamage(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3)); -} - -static any Native_GetBossAttackIndexSpread(Handle plugin, int numParams) -{ - return NPCChaserGetAttackSpread(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); -} - -static any Native_GetBossAttackIndexRange(Handle plugin, int numParams) -{ - return NPCChaserGetAttackRange(GetNativeCell(1), GetNativeCell(2), Difficulty_Normal); -} - -static any Native_GetBossAttackIndexType(Handle plugin, int numParams) -{ - return NPCChaserGetAttackType(GetNativeCell(1), GetNativeCell(2)); -} - -static any Native_GetBossTeleportThinkTimer(Handle plugin, int numParams) -{ - return g_SlenderThink[GetNativeCell(1)]; -} - -static any Native_SetBossTeleportThinkTimer(Handle plugin, int numParams) -{ - int bossIndex = GetNativeCell(1); - g_SlenderThink[bossIndex] = GetNativeCell(2); - return 0; -} - -static any Native_GetBossTeleportTarget(Handle plugin, int numParams) -{ - return EntRefToEntIndex(g_SlenderTeleportTarget[GetNativeCell(1)]); -} - -static any Native_GetBossThinkTimer(Handle plugin, int numParams) -{ - return g_SlenderEntityThink[GetNativeCell(1)]; -} - -static any Native_SetBossThinkTimer(Handle plugin, int numParams) -{ - int bossIndex = GetNativeCell(1); - g_SlenderEntityThink[bossIndex] = GetNativeCell(2); - return 0; -} - -static any Native_UnhookBossThinkHook(Handle plugin, int numParams) +static any Native_GetBossGoalPosition(Handle plugin, int numParams) { - int bossIndex = GetNativeCell(1); - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return 0; - } - switch (NPCGetType(bossIndex)) - { - case SF2BossType_Statue: - { - SDKUnhook(slender, SDKHook_Think, SlenderStatueBossProcessMovement); - } - case SF2BossType_Chaser: - { - SDKUnhook(slender, SDKHook_Think, SlenderChaseBossProcessMovement); - } - } return 0; } @@ -1141,17 +735,6 @@ static any Native_InitiateBossPackVote(Handle plugin, int numParams) return 0; } -static any Native_GetProjectileFlags(Handle plugin, int numParams) -{ - return ProjectileGetFlags(GetNativeCell(1)); -} - -static any Native_SetProjectileFlags(Handle plugin, int numParams) -{ - ProjectileSetFlags(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - static any Native_IsSurvivalMap(Handle plugin, int numParams) { return SF_IsSurvivalMap(); diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_flashlight.sp b/addons/sourcemod/scripting/sf2/functionclients/client_flashlight.sp deleted file mode 100644 index 281af18c..00000000 --- a/addons/sourcemod/scripting/sf2/functionclients/client_flashlight.sp +++ /dev/null @@ -1,1083 +0,0 @@ -#if defined _sf2_client_flashlight_included - #endinput -#endif -#define _sf2_client_flashlight_included - -#define SF2_ULTRAVISION_CONE 180.0 - -#pragma semicolon 1 - -static bool g_PlayerHasFlashlight[MAXTF2PLAYERS] = { false, ... }; -static bool g_PlayerFlashlightBroken[MAXTF2PLAYERS] = { false, ... }; -static float g_PlayerFlashlightBatteryLife[MAXTF2PLAYERS] = { 1.0, ... }; -static Handle g_PlayerFlashlightBatteryTimer[MAXTF2PLAYERS] = { null, ... }; -static float g_PlayerFlashlightNextInputTime[MAXTF2PLAYERS] = { -1.0, ... }; -static int g_PlayerFlashlightEnt[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerFlashlightEntAng[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_ClientFlashlightStartEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_ClientFlashlightEndEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; - -static Action Timer_DrainFlashlight(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerFlashlightBatteryTimer[client]) - { - return Plugin_Stop; - } - - if (!IsInfiniteFlashlightEnabled()) - { - ClientSetFlashlightBatteryLife(client, ClientGetFlashlightBatteryLife(client) - 0.01); - } - - if (ClientGetFlashlightBatteryLife(client) <= 0.0) - { - // Break the player's flashlight, but also start recharging. - ClientBreakFlashlight(client); - ClientStartRechargingFlashlightBattery(client); - ClientActivateUltravision(client); - return Plugin_Stop; - } - else - { - ClientHandleFlashlightFlickerState(client); - } - - return Plugin_Continue; -} - -static Action Timer_RechargeFlashlight(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerFlashlightBatteryTimer[client]) - { - return Plugin_Stop; - } - - ClientSetFlashlightBatteryLife(client, ClientGetFlashlightBatteryLife(client) + 0.01); - - if (IsClientFlashlightBroken(client) && ClientGetFlashlightBatteryLife(client) >= SF2_FLASHLIGHT_ENABLEAT) - { - // Repair the flashlight. - g_PlayerFlashlightBroken[client] = false; - } - - if (ClientGetFlashlightBatteryLife(client) >= 1.0) - { - // I am fully charged! - ClientSetFlashlightBatteryLife(client, 1.0); - g_PlayerFlashlightBatteryTimer[client] = null; - - return Plugin_Stop; - } - - return Plugin_Continue; -} - -bool IsClientUsingFlashlight(int client) -{ - return g_PlayerHasFlashlight[client]; -} - -float ClientGetFlashlightBatteryLife(int client) -{ - return g_PlayerFlashlightBatteryLife[client]; -} - -void ClientSetFlashlightBatteryLife(int client, float flPercent) -{ - g_PlayerFlashlightBatteryLife[client] = flPercent; -} - -/** - * Called in Hook_ClientPreThink, this makes sure the flashlight is oriented correctly on the player. - */ -void ClientProcessFlashlightAngles(int client) -{ - if (!IsClientInGame(client)) - { - return; - } - - if (IsPlayerAlive(client)) - { - int fl; - - if (IsClientUsingFlashlight(client)) - { - fl = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); - if (fl && fl != INVALID_ENT_REFERENCE) - { - TeleportEntity(fl, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 }), NULL_VECTOR); - } - } - } -} - -/** - * Handles whether or not the player's flashlight should be "flickering", a sign of a dying flashlight battery. - */ -void ClientHandleFlashlightFlickerState(int client) -{ - if (!IsClientInGame(client) || !IsPlayerAlive(client)) - { - return; - } - - if (IsClientUsingFlashlight(client)) - { - bool flicker = ClientGetFlashlightBatteryLife(client) <= SF2_FLASHLIGHT_FLICKERAT; - - int fl = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); - if (fl && fl != INVALID_ENT_REFERENCE) - { - if (flicker) - { - SetEntProp(fl, Prop_Data, "m_LightStyle", 10); - } - else - { - SetEntProp(fl, Prop_Data, "m_LightStyle", 0); - } - } - - fl = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); - if (fl && fl != INVALID_ENT_REFERENCE) - { - if (flicker) - { - SetEntityRenderFx(fl, view_as(13)); - } - else - { - SetEntityRenderFx(fl, view_as(0)); - } - } - } -} - -bool IsClientFlashlightBroken(int client) -{ - return g_PlayerFlashlightBroken[client]; -} - -float ClientGetFlashlightNextInputTime(int client) -{ - return g_PlayerFlashlightNextInputTime[client]; -} - -/** - * Breaks the player's flashlight. Nothing else. - */ -void ClientBreakFlashlight(int client) -{ - if (IsClientFlashlightBroken(client)) - { - return; - } - - ClientDeactivateUltravision(client); - - g_PlayerFlashlightBroken[client] = true; - - ClientSetFlashlightBatteryLife(client, 0.0); - ClientTurnOffFlashlight(client); - - ClientAddStress(client, 0.2); - - EmitSoundToAll(FLASHLIGHT_BREAKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); - - Call_StartForward(g_OnClientBreakFlashlightFwd); - Call_PushCell(client); - Call_Finish(); -} - -/** - * Resets everything of the player's flashlight. - */ -void ClientResetFlashlight(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetFlashlight(%d)", client); - } - #endif - - ClientTurnOffFlashlight(client); - ClientSetFlashlightBatteryLife(client, 1.0); - g_PlayerFlashlightBroken[client] = false; - g_PlayerFlashlightBatteryTimer[client] = null; - g_PlayerFlashlightNextInputTime[client] = -1.0; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetFlashlight(%d)", client); - } - #endif -} - -static Action Hook_FlashlightSetTransmit(int ent,int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - if (EntRefToEntIndex(g_PlayerFlashlightEnt[other]) != ent) - { - return Plugin_Handled; - } - - // We've already checked for flashlight ownership in the last statement. So we can do just this. - if (g_PlayerPreferences[other].PlayerPreference_ProjectedFlashlight) - { - return Plugin_Handled; - } - - return Plugin_Continue; -} - -/*static Action Hook_Flashlight2SetTransmit(int ent,int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); - if (!IsValidClient(ownerEntity)) - { - return Plugin_Continue; - } - - if (ownerEntity == other) - { - if (!!(GetEntProp(ownerEntity, Prop_Send, "m_nForceTauntCam")) && !TF2_IsPlayerInCondition(ownerEntity, TFCond_Taunting)) - { - return Plugin_Handled; - } - } - else - { - if (GetEntPropEnt(other, Prop_Send, "m_hObserverTarget") == ownerEntity && GetEntProp(other, Prop_Send, "m_iObserverMode") == 4) - { - return Plugin_Handled; - } - - if (!DidClientEscape(ownerEntity) && !g_PlayerEliminated[ownerEntity]) - { - if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) - { - if (!g_PlayerEliminated[other] && !DidClientEscape(other)) - { - return Plugin_Handled; - } - } - } - } - return Plugin_Continue; -}*/ - -/** - * Turns on the player's flashlight. Nothing else. - */ -void ClientTurnOnFlashlight(int client) -{ - if (!IsClientInGame(client) || !IsPlayerAlive(client)) - { - return; - } - - if (IsClientUsingFlashlight(client)) - { - return; - } - - g_PlayerHasFlashlight[client] = true; - - float eyePos[3], eyeAng[3]; - - float length = SF2_FLASHLIGHT_LENGTH; - float doubleLength = SF2_FLASHLIGHT_LENGTH*3.0; - float radius = SF2_FLASHLIGHT_WIDTH; - float doubleRadius = SF2_FLASHLIGHT_WIDTH*2.0; - GetClientEyePosition(client, eyePos); - GetClientEyeAngles(client, eyeAng); - - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) - { - // If the player is using the projected flashlight, just set effect flags. - int effects = GetEntProp(client, Prop_Send, "m_fEffects"); - if (!(effects & (1 << 2))) - { - SetEntProp(client, Prop_Send, "m_fEffects", effects | (1 << 2)); - } - } - else - { - // Spawn the light which only the user will see. - int ent = CreateEntityByName("light_dynamic"); - if (ent != -1) - { - TeleportEntity(ent, eyePos, eyeAng, NULL_VECTOR); - DispatchKeyValue(ent, "targetname", "WUBADUBDUBMOTHERBUCKERS"); - switch (g_PlayerPreferences[client].PlayerPreference_FlashlightTemperature) - { - case 1: - { - DispatchKeyValue(ent, "rendercolor", "255 150 50"); - } - case 2: - { - DispatchKeyValue(ent, "rendercolor", "255 210 100"); - } - case 3: - { - DispatchKeyValue(ent, "rendercolor", "255 255 120"); - } - case 4: - { - DispatchKeyValue(ent, "rendercolor", "255 255 185"); - } - case 5: - { - DispatchKeyValue(ent, "rendercolor", "255 255 210"); - } - case 6: - { - DispatchKeyValue(ent, "rendercolor", "255 255 255"); - } - case 7: - { - DispatchKeyValue(ent, "rendercolor", "210 255 255"); - } - case 8: - { - DispatchKeyValue(ent, "rendercolor", "185 255 255"); - } - case 9: - { - DispatchKeyValue(ent, "rendercolor", "150 255 255"); - } - case 10: - { - DispatchKeyValue(ent, "rendercolor", "125 255 255"); - } - } - if (!IsClassConfigsValid()) - { - if (class != TFClass_Engineer) - { - SetVariantFloat(radius); - } - else - { - SetVariantFloat(doubleRadius); - } - } - else - { - float customRadius = radius * g_ClassFlashlightRadius[classToInt]; - SetVariantFloat(customRadius); - } - AcceptEntityInput(ent, "spotlight_radius"); - if (!IsClassConfigsValid()) - { - if (class != TFClass_Engineer) - { - SetVariantFloat(length); - } - else - { - SetVariantFloat(doubleLength); - } - } - else - { - float customLength = length * g_ClassFlashlightLength[classToInt]; - SetVariantFloat(customLength); - } - AcceptEntityInput(ent, "distance"); - if (!IsClassConfigsValid()) - { - SetVariantInt(SF2_FLASHLIGHT_BRIGHTNESS); - } - else - { - int customBrightness = SF2_FLASHLIGHT_BRIGHTNESS + g_ClassFlashlightBrightness[classToInt]; - SetVariantInt(customBrightness); - } - AcceptEntityInput(ent, "brightness"); - - // Convert WU to inches. - float cone = 55.0; - cone *= 0.75; - - SetVariantInt(RoundToFloor(cone)); - AcceptEntityInput(ent, "_inner_cone"); - SetVariantInt(RoundToFloor(cone)); - AcceptEntityInput(ent, "_cone"); - DispatchSpawn(ent); - ActivateEntity(ent); - SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", client); - AcceptEntityInput(ent, "TurnOn"); - - g_PlayerFlashlightEnt[client] = EntIndexToEntRef(ent); - - SDKHook(ent, SDKHook_SetTransmit, Hook_FlashlightSetTransmit); - } - } - - // Spawn the light that only everyone else will see. - /*int ent = CreateEntityByName("env_beam"); - if (ent != -1) - { - int startEnt = CreateEntityByName("info_target"); - int endEnt = CreateEntityByName("info_target"); - if (startEnt != -1) // Start - { - SetEntPropString(startEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_start"); - SetEntProp(startEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(startEnt, eyePos, eyeAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(startEnt, "SetParent", client); - - DispatchSpawn(startEnt); - SetEntityOwner(startEnt, client); - - g_ClientFlashlightStartEntity[client] = EntIndexToEntRef(startEnt); - - SetEntityTransmitState(startEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, startEnt, Hook_SpotlightEffectShouldTransmit); - } - if (endEnt != -1) // End - { - SetEntPropString(endEnt, Prop_Data, "m_iClassname", "sf2_flashlight_spotlight_end"); - SetEntProp(endEnt, Prop_Data, "m_spawnflags", 1); - TeleportEntity(endEnt, eyePos, eyeAng, NULL_VECTOR); - SetVariantString("!activator"); - AcceptEntityInput(endEnt, "SetParent", client); - - DispatchSpawn(endEnt); - SetEntityOwner(endEnt, client); - - g_ClientFlashlightEndEntity[client] = EntIndexToEntRef(endEnt); - - SetEntityTransmitState(endEnt, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, endEnt, Hook_SpotlightEffectShouldTransmit); - } - - switch (g_PlayerPreferences[client].PlayerPreference_FlashlightTemperature) - { - case 1: - { - SetEntityRenderColor(ent, 255, 150, 50, 64); - } - case 2: - { - SetEntityRenderColor(ent, 255, 210, 100, 64); - } - case 3: - { - SetEntityRenderColor(ent, 255, 255, 120, 64); - } - case 4: - { - SetEntityRenderColor(ent, 255, 255, 185, 64); - } - case 5: - { - SetEntityRenderColor(ent, 255, 255, 210, 64); - } - case 6: - { - SetEntityRenderColor(ent, 255, 255, 255, 64); - } - case 7: - { - SetEntityRenderColor(ent, 210, 255, 255, 64); - } - case 8: - { - SetEntityRenderColor(ent, 185, 255, 255, 64); - } - case 9: - { - SetEntityRenderColor(ent, 150, 255, 255, 64); - } - case 10: - { - SetEntityRenderColor(ent, 125, 255, 255, 64); - } - } - - SetEntityModel(ent, SF2_FLASHLIGHT_BEAM_MATERIAL); - SetEntityRenderMode(ent, RENDER_TRANSTEXTURE); - SetEntPropFloat(ent, Prop_Data, "m_life", 0.0); - - TeleportEntity(ent, eyePos, eyeAng, NULL_VECTOR); - - DispatchSpawn(ent); - ActivateEntity(ent); - - SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", startEnt, 0); - SetEntPropEnt(ent, Prop_Send, "m_hAttachEntity", endEnt, 1); - SetEntProp(ent, Prop_Send, "m_nNumBeamEnts", 2); - SetEntProp(ent, Prop_Send, "m_nBeamType", 2); - - SetEntPropFloat(ent, Prop_Send, "m_fWidth", 40.0); - SetEntPropFloat(ent, Prop_Data, "m_fEndWidth", 102.0); - - SetEntPropFloat(ent, Prop_Send, "m_fFadeLength", 0.0); - SetEntProp(ent, Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); - SetEntPropFloat(ent, Prop_Send, "m_fHaloScale", 40.0); - SetEntProp(ent, Prop_Send, "m_nBeamFlags", 0x80 | 0x200); - SetEntProp(ent, Prop_Data, "m_spawnflags", 0x8000); - - AcceptEntityInput(ent, "TurnOn"); - - SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", client); - - SetEntityOwner(ent, client); - - SDKHook(ent, SDKHook_SetTransmit, Hook_Flashlight2SetTransmit); - SetEntityTransmitState(ent, FL_EDICT_FULLCHECK); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectUpdateTransmitState); - g_DHookShouldTransmit.HookEntity(Hook_Pre, ent, Hook_SpotlightEffectShouldTransmit); - - g_PlayerFlashlightEntAng[client] = EntIndexToEntRef(ent); - }*/ - - Call_StartForward(g_OnClientActivateFlashlightFwd); - Call_PushCell(client); - Call_Finish(); -} - -void Hook_OnFlashlightThink(int client) -{ - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - float length; - if (!IsClassConfigsValid()) - { - if (class != TFClass_Engineer) - { - length = SF2_FLASHLIGHT_LENGTH; - } - else - { - length = SF2_FLASHLIGHT_LENGTH * 3.0; - } - } - else - { - length = SF2_FLASHLIGHT_LENGTH * g_ClassFlashlightLength[classToInt]; - } - if (IsClientUsingFlashlight(client)) - { - int endEnt = EntRefToEntIndex(g_ClientFlashlightEndEntity[client]); - if (IsValidEntity(endEnt)) - { - float entPos[3], entRot[3], tempRot[3], endPos[3]; - GetClientEyePosition(client, entPos); - GetClientEyeAngles(client, entRot); - GetEntPropVector(client, Prop_Data, "m_angAbsRotation", tempRot); - tempRot[0] = 0.0; - tempRot[2] = 0.0; - AddVectors(entRot, tempRot, entRot); - GetAngleVectors(entRot, entRot, NULL_VECTOR, NULL_VECTOR); - endPos = entRot; - ScaleVector(endPos, length); - AddVectors(endPos, entPos, endPos); - - CBaseEntity spotlightEnd = CBaseEntity(endEnt); - if (spotlightEnd.IsValid()) - { - TR_TraceRayFilter(entPos, endPos, MASK_SOLID_BRUSHONLY, RayType_EndPoint, TraceRayDontHitEntity, client); - - float hitPos[3]; - TR_GetEndPosition(hitPos); - - hitPos[2] += 20.0; - - spotlightEnd.SetAbsOrigin(hitPos); - } - } - } -} - -/** - * Turns off the player's flashlight. Nothing else. - */ -void ClientTurnOffFlashlight(int client) -{ - if (!IsClientUsingFlashlight(client)) - { - return; - } - - g_PlayerHasFlashlight[client] = false; - g_PlayerFlashlightBatteryTimer[client] = null; - - // Remove user-only light. - int ent = EntRefToEntIndex(g_PlayerFlashlightEnt[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - AcceptEntityInput(ent, "TurnOff"); - RemoveEntity(ent); - } - - // Remove everyone-else-only light. - ent = EntRefToEntIndex(g_PlayerFlashlightEntAng[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - AcceptEntityInput(ent, "TurnOff"); - CreateTimer(0.1, Timer_KillEntity, g_PlayerFlashlightEntAng[client], TIMER_FLAG_NO_MAPCHANGE); - } - ent = EntRefToEntIndex(g_ClientFlashlightStartEntity[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - CreateTimer(0.1, Timer_KillEntity, g_ClientFlashlightStartEntity[client], TIMER_FLAG_NO_MAPCHANGE); - } - ent = EntRefToEntIndex(g_ClientFlashlightEndEntity[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - CreateTimer(0.1, Timer_KillEntity, g_ClientFlashlightEndEntity[client], TIMER_FLAG_NO_MAPCHANGE); - } - - g_PlayerFlashlightEnt[client] = INVALID_ENT_REFERENCE; - g_PlayerFlashlightEntAng[client] = INVALID_ENT_REFERENCE; - g_ClientFlashlightStartEntity[client] = INVALID_ENT_REFERENCE; - g_ClientFlashlightEndEntity[client] = INVALID_ENT_REFERENCE; - - if (IsClientInGame(client)) - { - if (g_PlayerPreferences[client].PlayerPreference_ProjectedFlashlight) - { - int effects = GetEntProp(client, Prop_Send, "m_fEffects"); - if (effects & (1 << 2)) - { - SetEntProp(client, Prop_Send, "m_fEffects", effects &= ~(1 << 2)); - } - } - } - - Call_StartForward(g_OnClientDeactivateFlashlightFwd); - Call_PushCell(client); - Call_Finish(); -} - -void ClientStartRechargingFlashlightBattery(int client) -{ - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - float rechargeRate = SF2_FLASHLIGHT_RECHARGE_RATE; - if (!IsClassConfigsValid()) - { - if (class == TFClass_Engineer) - { - rechargeRate *= 0.8; - } - } - else - { - rechargeRate *= g_ClassFlashlightRechargeRate[classToInt]; - } - - g_PlayerFlashlightBatteryTimer[client] = CreateTimer(rechargeRate, Timer_RechargeFlashlight, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} - -void ClientStartDrainingFlashlightBattery(int client) -{ - float drainRate = SF2_FLASHLIGHT_DRAIN_RATE; - - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - if (!IsClassConfigsValid()) - { - if (class == TFClass_Engineer) - { - // Engineers have a 50% longer battery life and 20% decreased recharge rate, basically. - drainRate *= 1.5; - } - } - else - { - drainRate *= g_ClassFlashlightDrainRate[classToInt]; - } - - g_PlayerFlashlightBatteryTimer[client] = CreateTimer(drainRate, Timer_DrainFlashlight, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); -} - -void ClientHandleFlashlight(int client) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client) || (TF2_IsPlayerInCondition(client, TFCond_Taunting) && !IsClientUsingFlashlight(client))) - { - return; - } - - bool nightVision = (g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)); - - if (IsClientUsingFlashlight(client) || TF2_IsPlayerInCondition(client, TFCond_Taunting)) - { - ClientTurnOffFlashlight(client); - ClientStartRechargingFlashlightBattery(client); - ClientDeactivateUltravision(client); - ClientActivateUltravision(client); - - g_PlayerFlashlightNextInputTime[client] = GetGameTime() + SF2_FLASHLIGHT_COOLDOWN; - - if (!nightVision) - { - if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) - { - EmitSoundToAll(FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); - } - else - { - EmitSoundToClient(client, FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); - } - } - } - else - { - // Only players in the "game" can use the flashlight. - if (!g_PlayerEliminated[client]) - { - bool canUseFlashlight = true; - if (SF_SpecialRound(SPECIALROUND_LIGHTSOUT) || SF_IsRaidMap() || SF_IsBoxingMap()) - { - // Unequip the flashlight please. - canUseFlashlight = false; - } - - if (!IsClientFlashlightBroken(client) && canUseFlashlight) - { - ClientDeactivateUltravision(client); - if (nightVision) - { - ClientActivateUltravision(client, nightVision); - } - else - { - ClientTurnOnFlashlight(client); - } - ClientStartDrainingFlashlightBattery(client); - - g_PlayerFlashlightNextInputTime[client] = GetGameTime(); - if (!SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) - { - EmitSoundToAll((nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); - } - else - { - EmitSoundToClient(client, (nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_STATIC, SNDLEVEL_DRYER); - } - } - else - { - EmitSoundToClient(client, FLASHLIGHT_NOSOUND, _, SNDCHAN_ITEM, SNDLEVEL_NONE); - } - } - } -} - -bool IsClientUsingUltravision(int client) -{ - return g_PlayerHasUltravision[client]; -} - -void ClientActivateUltravision(int client, bool nightVision = false) -{ - if (!IsClientInGame(client) || IsClientUsingUltravision(client)) - { - return; - } - - if (!g_PlayerEliminated[client] && (SF_SpecialRound(SPECIALROUND_NOULTRAVISION) && !nightVision)) - { - return; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientActivateUltravision(%d)", client); - } - #endif - - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - g_PlayerHasUltravision[client] = true; - g_PlayerHasFlashlight[client] = nightVision; - - int ent = CreateEntityByName("light_dynamic"); - if (ent != -1) - { - float eyePos[3]; - GetClientEyePosition(client, eyePos); - - TeleportEntity(ent, eyePos, view_as({ 90.0, 0.0, 0.0 }), NULL_VECTOR); - if (nightVision && !g_PlayerEliminated[client]) - { - switch (g_NightvisionType) - { - case 0: - { - DispatchKeyValue(ent, "rendercolor", "50 255 50"); - } - case 1: - { - DispatchKeyValue(ent, "rendercolor", "255 50 50"); - } - } - } - else - { - DispatchKeyValue(ent, "rendercolor", "100 200 255"); - } - - float radius = 0.0; - if (g_PlayerEliminated[client]) - { - radius = g_UltravisionRadiusBlueConVar.FloatValue; - } - else - { - radius = g_UltravisionRadiusRedConVar.FloatValue; - if (nightVision) - { - radius = g_NightvisionRadiusConVar.FloatValue; - if (!IsClassConfigsValid()) - { - radius = g_NightvisionRadiusConVar.FloatValue; - } - else - { - radius = g_NightvisionRadiusConVar.FloatValue * g_ClassNightvisionRadiusMultiplier[classToInt]; - } - } - if (!nightVision && class == TFClass_Sniper && !IsClassConfigsValid()) - { - radius *= 2.0; - } - else if (!nightVision && IsClassConfigsValid()) - { - radius *= g_ClassUltravisionRadiusMultiplier[classToInt]; - } - } - - SetVariantFloat(radius); - AcceptEntityInput(ent, "spotlight_radius"); - SetVariantFloat(radius); - AcceptEntityInput(ent, "distance"); - - SetVariantInt(-15); // Start dark, then fade in via the Timer_UltravisionFadeInEffect timer func. - AcceptEntityInput(ent, "brightness"); - if (nightVision && !g_PlayerEliminated[client]) - { - if (!IsClassConfigsValid()) - { - SetVariantInt(5); - AcceptEntityInput(ent, "brightness"); - } - else - { - int roundedBrightness = RoundToNearest(5.0 * g_ClassNightvisionBrightnessMultiplier[classToInt]); - SetVariantInt(roundedBrightness); - AcceptEntityInput(ent, "brightness"); - } - } - - // Convert WU to inches. - float cone = SF2_ULTRAVISION_CONE; - cone *= 0.75; - - SetVariantInt(RoundToFloor(cone)); - AcceptEntityInput(ent, "_inner_cone"); - SetVariantInt(0); - AcceptEntityInput(ent, "_cone"); - DispatchSpawn(ent); - ActivateEntity(ent); - SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", client); - AcceptEntityInput(ent, "TurnOn"); - //SetEntityRenderFx(ent, RENDERFX_SOLID_FAST); - SetEntityRenderColor(ent, 150, 225, 255, 255); - if (nightVision && !g_PlayerEliminated[client]) - { - switch (g_NightvisionType) - { - case 0: - { - DispatchKeyValue(ent, "rendercolor", "50 255 50"); - } - case 1: - { - DispatchKeyValue(ent, "rendercolor", "255 50 50"); - } - } - } - - g_PlayerUltravisionEnt[client] = EntIndexToEntRef(ent); - - SDKHook(ent, SDKHook_SetTransmit, Hook_UltravisionSetTransmit); - - // Fade in effect. - if (!IsClassConfigsValid()) - { - if (class != TFClass_Engineer || IsClientInGhostMode(client)) - { - CreateTimer(0.0, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - else if (class == TFClass_Engineer && !IsClientInGhostMode(client)) - { - CreateTimer(0.15, Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - } - else - { - CreateTimer(g_ClassUltravisionFadeInTimer[classToInt], Timer_UltravisionFadeInEffect, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientActivateUltravision(%d)", client); - } - #endif -} - -static Action Timer_UltravisionFadeInEffect(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); - if (!ent || ent == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - int brightness = GetEntProp(ent, Prop_Send, "m_Exponent"); - int maxBrightness = g_UltravisionBrightnessConVar.IntValue; - if (!IsClassConfigsValid()) - { - if (class == TFClass_Sniper) - { - maxBrightness = RoundToNearest(float(maxBrightness) * 0.75); - } - } - else - { - maxBrightness = RoundToNearest(float(maxBrightness) * g_ClassUltravisionBrightnessMultiplier[classToInt]); - } - if (brightness >= maxBrightness) - { - return Plugin_Stop; - } - - brightness++; - SetVariantInt(brightness); - AcceptEntityInput(ent, "brightness"); - - return Plugin_Continue; -} - -void ClientDeactivateUltravision(int client) -{ - if (!IsClientUsingUltravision(client)) - { - return; - } - - g_PlayerHasUltravision[client] = false; - - int ent = EntRefToEntIndex(g_PlayerUltravisionEnt[client]); - if (ent != INVALID_ENT_REFERENCE) - { - AcceptEntityInput(ent, "TurnOff"); - RemoveEntity(ent); - } - - g_PlayerUltravisionEnt[client] = INVALID_ENT_REFERENCE; -} - -static Action Hook_UltravisionSetTransmit(int ent,int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - if (!g_UltravisionEnabledConVar.BoolValue || EntRefToEntIndex(g_PlayerUltravisionEnt[other]) != ent || !IsPlayerAlive(other)) - { - return Plugin_Handled; - } - return Plugin_Continue; -} -/* -void ClientSDKFlashlightTurnOn(int client) -{ - if (!IsValidClient(client)) - { - return; - } - - int effects = GetEntProp(client, Prop_Send, "m_fEffects"); - if (effects & EF_DIMLIGHT) - { - return; - } - - effects |= EF_DIMLIGHT; - - SetEntProp(client, Prop_Send, "m_fEffects", effects); -} - -void ClientSDKFlashlightTurnOff(int client) -{ - if (!IsValidClient(client)) - { - return; - } - - int effects = GetEntProp(client, Prop_Send, "m_fEffects"); - if (!(effects & EF_DIMLIGHT)) - { - return; - } - - effects &= ~EF_DIMLIGHT; - - SetEntProp(client, Prop_Send, "m_fEffects", effects); -} -*/ \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_hints.sp b/addons/sourcemod/scripting/sf2/functionclients/client_hints.sp deleted file mode 100644 index d571f8a7..00000000 --- a/addons/sourcemod/scripting/sf2/functionclients/client_hints.sp +++ /dev/null @@ -1,69 +0,0 @@ -#if defined _sf2_client_hints_included - #endinput -#endif -#define _sf2_client_hints_included - -bool g_PlayerHints[MAXTF2PLAYERS][PlayerHint_MaxNum]; - -// Hint data. -enum -{ - PlayerHint_Sprint = 0, - PlayerHint_Flashlight, - PlayerHint_MainMenu, - PlayerHint_Blink, - PlayerHint_Trap, - PlayerHint_MaxNum -}; - - -void ClientResetHints(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetHints(%d)", client); - } - #endif - - for (int i = 0; i < PlayerHint_MaxNum; i++) - { - g_PlayerHints[client][i] = false; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetHints(%d)", client); - } - #endif -} - -void ClientShowHint(int client,int hint) -{ - g_PlayerHints[client][hint] = true; - - switch (hint) - { - case PlayerHint_Sprint: - { - PrintHintText(client, "%T", "SF2 Hint Sprint", client); - } - case PlayerHint_Flashlight: - { - PrintHintText(client, "%T", "SF2 Hint Flashlight", client); - } - case PlayerHint_Blink: - { - PrintHintText(client, "%T", "SF2 Hint Blink", client); - } - case PlayerHint_MainMenu: - { - PrintHintText(client, "%T", "SF2 Hint Main Menu", client); - } - case PlayerHint_Trap: - { - PrintHintText(client, "%T", "SF2 Hint Trap", client); - } - } -} diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_music.sp b/addons/sourcemod/scripting/sf2/functionclients/client_music.sp deleted file mode 100644 index cc5ee42b..00000000 --- a/addons/sourcemod/scripting/sf2/functionclients/client_music.sp +++ /dev/null @@ -1,1693 +0,0 @@ -#if defined _sf2_client_music_included - #endinput -#endif -#define _sf2_client_music_included - -#pragma semicolon 1 - -void ClientUpdateMusicSystem(int client, bool initialize=false) -{ - int oldPageMusicMaster = EntRefToEntIndex(g_PlayerPageMusicMaster[client]); - int oldPageMusicActiveIndex = g_PageMusicActiveIndex[client]; - int oldMusicFlags = g_PlayerMusicFlags[client]; - int chasingBoss = -1; - int chasingSeeBoss = -1; - int alertBoss = -1; - int idleBoss = -1; - - if (IsRoundEnding() || !IsClientInGame(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) - { - g_PlayerMusicFlags[client] = 0; - g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; - g_PageMusicActiveIndex[client] = -1; - - if (MusicActive()) //A boss is overriding the music. - { - char path[PLATFORM_MAX_PATH]; - GetBossMusic(path,sizeof(path)); - if (path[0] != '\0') - { - StopSound(client, MUSIC_CHAN, path); - } - } - } - else - { - bool playMusicOnEscape = !g_RoundStopPageMusicOnEscape; - - // Page music first. - int pageRange = 0; - - if (g_PageMusicRanges.Length > 0) // Map has its own defined page music? - { - for (int i = 0, size = g_PageMusicRanges.Length; i < size; i++) - { - int ent = EntRefToEntIndex(g_PageMusicRanges.Get(i)); - if (!ent || ent == INVALID_ENT_REFERENCE) - { - continue; - } - - int min = g_PageMusicRanges.Get(i, 1); - int max = g_PageMusicRanges.Get(i, 2); - - if (g_PageCount >= min && g_PageCount <= max) - { - g_PlayerPageMusicMaster[client] = g_PageMusicRanges.Get(i); - g_PageMusicActiveIndex[client] = i; - break; - } - } - } - else // Nope. Use old system instead. - { - g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; - g_PageMusicActiveIndex[client] = -1; - - float percent = g_PageMax > 0 ? (float(g_PageCount) / float(g_PageMax)) : 0.0; - if (percent > 0.0 && percent <= 0.25) - { - pageRange = 1; - } - else if (percent > 0.25 && percent <= 0.5) - { - pageRange = 2; - } - else if (percent > 0.5 && percent <= 0.75) - { - pageRange = 3; - } - else if (percent > 0.75) - { - pageRange = 4; - } - - if (pageRange == 1) - { - ClientAddMusicFlag(client, MUSICF_PAGES1PERCENT); - } - else if (pageRange == 2) - { - ClientAddMusicFlag(client, MUSICF_PAGES25PERCENT); - } - else if (pageRange == 3) - { - ClientAddMusicFlag(client, MUSICF_PAGES50PERCENT); - } - else if (pageRange == 4) - { - ClientAddMusicFlag(client, MUSICF_PAGES75PERCENT); - } - } - - if (pageRange != 1) - { - ClientRemoveMusicFlag(client, MUSICF_PAGES1PERCENT); - } - if (pageRange != 2) - { - ClientRemoveMusicFlag(client, MUSICF_PAGES25PERCENT); - } - if (pageRange != 3) - { - ClientRemoveMusicFlag(client, MUSICF_PAGES50PERCENT); - } - if (pageRange != 4) - { - ClientRemoveMusicFlag(client, MUSICF_PAGES75PERCENT); - } - - if (IsRoundInEscapeObjective() && !playMusicOnEscape) - { - ClientRemoveMusicFlag(client, MUSICF_PAGES75PERCENT); - g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; - g_PageMusicActiveIndex[client] = -1; - } - - int oldChasingBoss = g_PlayerChaseMusicMaster[client]; - int oldChasingSeeBoss = g_PlayerChaseMusicSeeMaster[client]; - int oldAlertBoss = g_PlayerAlertMusicMaster[client]; - int oldIdleBoss = g_PlayerIdleMusicMaster[client]; - - float buffer[3], buffer2[3], buffer3[3]; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (NPCGetEntIndex(i) == INVALID_ENT_REFERENCE) - { - continue; - } - - NPCGetProfile(i, profile, sizeof(profile)); - - int bossType = NPCGetType(i); - - switch (bossType) - { - case SF2BossType_Chaser: - { - SF2BossProfileSoundInfo soundInfo; - ArrayList soundList; - GetClientAbsOrigin(client, buffer); - SlenderGetAbsOrigin(i, buffer3); - - int target = EntRefToEntIndex(g_SlenderTarget[i]); - if (target != -1) - { - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", buffer2); - - if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && - !(NPCGetFlags(i) & SFF_MARKEDASFAKE)) - { - GetChaserProfileChaseMusics(profile, soundInfo); - - if ((target == client || GetVectorSquareMagnitude(buffer, buffer2) <= SquareFloat(soundInfo.Radius) || - GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius))) - { - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - chasingBoss = i; - } - - if ((g_SlenderState[i] == STATE_CHASE || g_SlenderState[i] == STATE_ATTACK || g_SlenderState[i] == STATE_STUN) && - PlayerCanSeeSlender(client, i, false)) - { - GetChaserProfileChaseVisibleMusics(profile, soundInfo); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - chasingSeeBoss = i; - } - } - } - } - } - - if (g_SlenderState[i] == STATE_ALERT) - { - GetChaserProfileAlertMusics(profile, soundInfo); - soundList = soundInfo.Paths; - if (soundList == null || soundList.Length <= 0) - { - continue; - } - - if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) - { - if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) - { - alertBoss = i; - } - } - } - - if (g_SlenderState[i] == STATE_IDLE || g_SlenderState[i] == STATE_WANDER) - { - GetChaserProfileIdleMusics(profile, soundInfo); - soundList = soundInfo.Paths; - if (soundList == null || soundList.Length <= 0) - { - continue; - } - - if (!(NPCGetFlags(i) & SFF_MARKEDASFAKE)) - { - if (GetVectorSquareMagnitude(buffer, buffer3) <= SquareFloat(soundInfo.Radius) || GetVectorSquareMagnitude(buffer, g_SlenderGoalPos[i]) <= SquareFloat(soundInfo.Radius)) - { - idleBoss = i; - } - } - } - soundList = null; - } - } - } - - if (chasingBoss != oldChasingBoss) - { - if (chasingBoss != -1) - { - ClientAddMusicFlag(client, MUSICF_CHASE); - } - else - { - ClientRemoveMusicFlag(client, MUSICF_CHASE); - } - } - - if (chasingSeeBoss != oldChasingSeeBoss) - { - if (chasingSeeBoss != -1) - { - ClientAddMusicFlag(client, MUSICF_CHASEVISIBLE); - } - else - { - ClientRemoveMusicFlag(client, MUSICF_CHASEVISIBLE); - } - } - - if (alertBoss != oldAlertBoss) - { - if (alertBoss != -1) - { - ClientAddMusicFlag(client, MUSICF_ALERT); - } - else - { - ClientRemoveMusicFlag(client, MUSICF_ALERT); - } - } - - if (idleBoss != oldIdleBoss) - { - if (idleBoss != -1) - { - ClientAddMusicFlag(client, MUSICF_IDLE); - } - else - { - ClientRemoveMusicFlag(client, MUSICF_IDLE); - } - } - } - - if (IsValidClient(client)) - { - bool wasChase = ClientHasMusicFlag2(oldMusicFlags, MUSICF_CHASE); - bool inChase = ClientHasMusicFlag(client, MUSICF_CHASE); - bool wasChaseSee = ClientHasMusicFlag2(oldMusicFlags, MUSICF_CHASEVISIBLE); - bool inChaseSee = ClientHasMusicFlag(client, MUSICF_CHASEVISIBLE); - bool inAlert = ClientHasMusicFlag(client, MUSICF_ALERT); - bool wasAlert = ClientHasMusicFlag2(oldMusicFlags, MUSICF_ALERT); - bool inIdle = ClientHasMusicFlag(client, MUSICF_IDLE); - bool wasIdle = ClientHasMusicFlag2(oldMusicFlags, MUSICF_IDLE); - char path[PLATFORM_MAX_PATH]; - - if (IsRoundEnding() || !IsClientInGame(client) || IsFakeClient(client) || DidClientEscape(client) || (g_PlayerEliminated[client] && !IsClientInGhostMode(client) && !g_PlayerProxy[client])) - { - } - else if (MusicActive()) //A boss is overriding the music. - { - GetBossMusic(path,sizeof(path)); - ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME); - return; - } - - // Custom system. - if (g_PageMusicRanges.Length > 0) - { - int master = EntRefToEntIndex(g_PlayerPageMusicMaster[client]); - int pageMusicActiveIndex = g_PageMusicActiveIndex[client]; - - if (pageMusicActiveIndex != oldPageMusicActiveIndex) - { - // Page music was changed. - - // Stop the old music. - if (oldPageMusicActiveIndex != -1 && oldPageMusicMaster && oldPageMusicMaster != INVALID_ENT_REFERENCE) - { - int oldPageRangeMin = g_PageMusicRanges.Get(oldPageMusicActiveIndex, 1); - - SF2PageMusicEntity oldPageMusic = SF2PageMusicEntity(oldPageMusicMaster); - if (oldPageMusic.IsValid()) - { - oldPageMusic.GetRangeMusic(oldPageRangeMin, path, sizeof(path)); - } - else - { - GetEntPropString(oldPageMusicMaster, Prop_Data, "m_iszSound", path, sizeof(path)); - } - - if (path[0] != '\0') - { - StopSound(client, MUSIC_CHAN, path); - } - } - - // Play new music. - if (pageMusicActiveIndex != -1 && master && master != INVALID_ENT_REFERENCE) - { - int pageRangeMin = g_PageMusicRanges.Get(pageMusicActiveIndex, 1); - int pageRangeMax = g_PageMusicRanges.Get(pageMusicActiveIndex, 2); - - SF2PageMusicEntity pageMusic = SF2PageMusicEntity(master); - if (pageMusic.IsValid()) - { - pageMusic.GetRangeMusic(g_PageCount, path, sizeof(path)); - } - else - { - GetEntPropString(master, Prop_Data, "m_iszSound", path, sizeof(path)); - } - - // Play the new music. - if (path[0] == '\0') - { - LogError("Could not play music of page range %d - %d: no valid sound path specified!", pageRangeMin, pageRangeMax); - } - else - { - ClientMusicStart(client, path, _, MUSIC_PAGE_VOLUME, inChase || inAlert); - } - } - } - } - else - { - // Old system. - if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES1PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES1PERCENT)) - { - StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES1_SOUND); - } - else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES1PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES1PERCENT)) - { - ClientMusicStart(client, MUSIC_GOTPAGES1_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); - } - - if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES25PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES25PERCENT)) - { - StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES2_SOUND); - } - else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES25PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES25PERCENT)) - { - ClientMusicStart(client, MUSIC_GOTPAGES2_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); - } - - if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES50PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES50PERCENT)) - { - StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES3_SOUND); - } - else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES50PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES50PERCENT)) - { - ClientMusicStart(client, MUSIC_GOTPAGES3_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); - } - - if ((initialize || ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES75PERCENT)) && !ClientHasMusicFlag(client, MUSICF_PAGES75PERCENT)) - { - StopSound(client, MUSIC_CHAN, MUSIC_GOTPAGES4_SOUND); - } - else if ((initialize || !ClientHasMusicFlag2(oldMusicFlags, MUSICF_PAGES75PERCENT)) && ClientHasMusicFlag(client, MUSICF_PAGES75PERCENT)) - { - ClientMusicStart(client, MUSIC_GOTPAGES4_SOUND, _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); - } - } - - int mainMusicState = 0; - - if (inIdle != wasIdle || idleBoss != g_PlayerIdleMusicMaster[client]) - { - if (inIdle && !inAlert && !inChase) - { - ClientIdleMusicStart(client, idleBoss); - if (!wasIdle) - { - mainMusicState = -1; - } - } - else - { - ClientIdleMusicStop(client, g_PlayerIdleMusicMaster[client]); - if (!inChase && !inAlert && wasIdle) - { - mainMusicState = 1; - } - } - } - - if (inAlert != wasAlert || alertBoss != g_PlayerAlertMusicMaster[client]) - { - if (inAlert && !inChase) - { - ClientAlertMusicStart(client, alertBoss); - if (!wasAlert) - { - mainMusicState = -1; - } - } - else - { - ClientAlertMusicStop(client, g_PlayerAlertMusicMaster[client]); - if (!inChase && wasAlert) - { - mainMusicState = 1; - } - } - } - - if (inChase != wasChase || chasingBoss != g_PlayerChaseMusicMaster[client]) - { - if (inChase) - { - ClientMusicChaseStart(client, chasingBoss); - - if (!wasChase) - { - mainMusicState = -1; - - if (inAlert) - { - ClientAlertMusicStop(client, g_PlayerAlertMusicMaster[client]); - } - else if (inIdle) - { - ClientIdleMusicStop(client, g_PlayerIdleMusicMaster[client]); - } - } - } - else - { - ClientMusicChaseStop(client, g_PlayerChaseMusicMaster[client]); - if (wasChase) - { - if (inAlert) - { - ClientAlertMusicStart(client, alertBoss); - } - else if (inIdle) - { - ClientIdleMusicStart(client, idleBoss); - } - else - { - mainMusicState = 1; - } - } - } - } - - if (inChaseSee != wasChaseSee || chasingSeeBoss != g_PlayerChaseMusicSeeMaster[client]) - { - if (inChaseSee) - { - ClientMusicChaseSeeStart(client, chasingSeeBoss); - } - else - { - ClientMusicChaseSeeStop(client, g_PlayerChaseMusicSeeMaster[client]); - } - } - - if (mainMusicState == 1) - { - ClientMusicStart(client, g_PlayerMusicString[client], _, MUSIC_PAGE_VOLUME, inChase || inAlert || inIdle); - } - else if (mainMusicState == -1) - { - ClientMusicStop(client); - } - - if (inChase || inAlert) //Lets not add an idle variable, this whole if statement just adds stress. - { - int bossToUse = -1; - if (inChase) - { - bossToUse = chasingBoss; - } - else - { - bossToUse = alertBoss; - } - - if (bossToUse != -1) - { - // We got some alert/chase music going on! The player's excitement will no doubt go up! - // Excitement, though, really depends on how close the boss is in relation to the - // player. - - float bossDist = NPCGetDistanceFromEntity(bossToUse, client); - float scalar = (bossDist / SquareFloat(700.0)); - if (scalar > 1.0) - { - scalar = 1.0; - } - float stressAdd = 0.1 * (1.0 - scalar); - - ClientAddStress(client, stressAdd); - } - } - } -} - -void ClientMusicReset(int client) -{ - char oldMusic[PLATFORM_MAX_PATH]; - strcopy(oldMusic, sizeof(oldMusic), g_PlayerMusicString[client]); - g_PlayerMusicString[client][0] = '\0'; - if (IsValidClient(client) && oldMusic[0] != '\0') - { - StopSound(client, MUSIC_CHAN, oldMusic); - } - - g_PlayerMusicFlags[client] = 0; - g_PlayerMusicVolume[client] = 0.0; - g_PlayerMusicTargetVolume[client] = 0.0; - - g_PlayerMusicTimer[client] = null; - g_PlayerPageMusicMaster[client] = INVALID_ENT_REFERENCE; - g_PageMusicActiveIndex[client] = -1; -} - -void ClientMusicStart(int client, const char[] newMusic, float volume=-1.0, float targetVolume=-1.0, bool copyOnly=false) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client)) - { - return; - } - if (newMusic[0] == '\0') - { - return; - } - - char oldMusic[PLATFORM_MAX_PATH]; - strcopy(oldMusic, sizeof(oldMusic), g_PlayerMusicString[client]); - - if (strcmp(oldMusic, newMusic, false) != 0 && oldMusic[0] != '\0') - { - StopSound(client, MUSIC_CHAN, oldMusic); - } - strcopy(g_PlayerMusicString[client], sizeof(g_PlayerMusicString[]), newMusic); - if (MusicActive())//A boss is overriding the music. - { - GetBossMusic(g_PlayerMusicString[client],sizeof(g_PlayerMusicString[])); - } - if (volume >= 0.0) - { - g_PlayerMusicVolume[client] = volume; - } - if (targetVolume >= 0.0) - { - g_PlayerMusicTargetVolume[client] = targetVolume; - } - - if (!copyOnly) - { - bool playMusicOnEscape = !g_RoundStopPageMusicOnEscape; - - if (g_PageCount < g_PageMax || playMusicOnEscape) - { - g_PlayerMusicTimer[client] = CreateTimer(0.01, Timer_PlayerFadeInMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerMusicTimer[client], true); - } - } - else - { - g_PlayerMusicTimer[client] = null; - } -} - -void ClientMusicStop(int client) -{ - g_PlayerMusicTimer[client] = CreateTimer(0.01, Timer_PlayerFadeOutMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerMusicTimer[client], true); -} - -void ClientAlertMusicReset(int client) -{ - g_PlayerAlertMusicMaster[client] = -1; - g_PlayerAlertMusicOldMaster[client] = -1; - ClientRemoveMusicFlag(client, MUSICF_ALERT); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - for (int i = 0; i < MAX_BOSSES; i++) - { - g_PlayerAlertMusicTimer[client][i] = null; - g_PlayerAlertMusicVolumes[client][i] = 0.0; - g_PlayerAlertMusicString[client][i][0] = '\0'; - - if (NPCGetUniqueID(i) != -1) - { - if (IsValidClient(client)) - { - NPCGetProfile(i, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileAlertMusics(profile, soundInfo); - soundInfo.StopAllSounds(client); - } - } - } -} - -void ClientAlertMusicStart(int client,int bossIndex) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client)) - { - return; - } - - int oldMaster = g_PlayerAlertMusicMaster[client]; - if (oldMaster == bossIndex) - { - return; - } - - g_PlayerAlertMusicOldMaster[client] = oldMaster; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char buffer[PLATFORM_MAX_PATH]; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileAlertMusics(profile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); - } - - if (buffer[0] == '\0') - { - return; - } - - g_PlayerAlertMusicMaster[client] = bossIndex; - if (g_PlayerAlertMusicVolumes[client][bossIndex] <= 0.0) - { - strcopy(g_PlayerAlertMusicString[client][bossIndex], sizeof(g_PlayerAlertMusicString[][]), buffer); - } - if (oldMaster != -1 && g_PlayerAlertMusicString[client][oldMaster][0] != '\0' && - strcmp(g_PlayerAlertMusicString[client][oldMaster], g_PlayerAlertMusicString[client][bossIndex]) == 0) - { - if (g_PlayerAlertMusicVolumes[client][bossIndex] < 1.0) - { - g_PlayerAlertMusicVolumes[client][bossIndex] = g_PlayerAlertMusicVolumes[client][oldMaster]; - g_PlayerAlertMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInAlertMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerAlertMusicTimer[client][bossIndex], true); - } - return; - } - g_PlayerAlertMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInAlertMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerAlertMusicTimer[client][bossIndex], true); - - if (oldMaster != -1) - { - ClientAlertMusicStop(client, oldMaster); - } -} - -void ClientAlertMusicStop(int client,int bossIndex) -{ - if (!IsValidClient(client)) - { - return; - } - if (bossIndex == -1) - { - return; - } - - if (bossIndex == g_PlayerAlertMusicMaster[client]) - { - g_PlayerAlertMusicMaster[client] = -1; - } - - g_PlayerAlertMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutAlertMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerAlertMusicTimer[client][bossIndex], true); -} - -void ClientIdleMusicReset(int client) -{ - g_PlayerIdleMusicMaster[client] = -1; - g_PlayerIdleMusicOldMaster[client] = -1; - ClientRemoveMusicFlag(client, MUSICF_IDLE); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - for (int i = 0; i < MAX_BOSSES; i++) - { - g_PlayerIdleMusicTimer[client][i] = null; - g_PlayerIdleMusicVolumes[client][i] = 0.0; - g_PlayerIdleMusicString[client][i][0] = '\0'; - - if (NPCGetUniqueID(i) != -1) - { - if (IsValidClient(client)) - { - NPCGetProfile(i, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileIdleMusics(profile, soundInfo); - soundInfo.StopAllSounds(client); - } - } - } -} - -void ClientIdleMusicStart(int client,int bossIndex) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client)) - { - return; - } - - int oldMaster = g_PlayerIdleMusicMaster[client]; - if (oldMaster == bossIndex) - { - return; - } - - g_PlayerIdleMusicOldMaster[client] = oldMaster; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char buffer[PLATFORM_MAX_PATH]; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileIdleMusics(profile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); - } - - if (buffer[0] == '\0') - { - return; - } - - g_PlayerIdleMusicMaster[client] = bossIndex; - if (g_PlayerIdleMusicVolumes[client][bossIndex] <= 0.0 && strcmp(buffer, g_PlayerIdleMusicString[client][bossIndex], true) != 0) - { - strcopy(g_PlayerIdleMusicString[client][bossIndex], sizeof(g_PlayerIdleMusicString[][]), buffer); - } - if (oldMaster != -1 && g_PlayerIdleMusicString[client][oldMaster][0] != '\0' && - strcmp(g_PlayerIdleMusicString[client][oldMaster], g_PlayerIdleMusicString[client][bossIndex]) == 0) - { - if (g_PlayerIdleMusicVolumes[client][bossIndex] < 1.0) - { - g_PlayerIdleMusicVolumes[client][bossIndex] = g_PlayerIdleMusicVolumes[client][oldMaster]; - g_PlayerIdleMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInIdleMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerIdleMusicTimer[client][bossIndex], true); - } - return; - } - g_PlayerIdleMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInIdleMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerIdleMusicTimer[client][bossIndex], true); - - if (oldMaster != -1) - { - ClientIdleMusicStop(client, oldMaster); - } -} - -void ClientIdleMusicStop(int client,int bossIndex) -{ - if (!IsValidClient(client)) - { - return; - } - if (bossIndex == -1) - { - return; - } - - if (bossIndex == g_PlayerIdleMusicMaster[client]) - { - g_PlayerIdleMusicMaster[client] = -1; - } - - g_PlayerIdleMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutIdleMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerIdleMusicTimer[client][bossIndex], true); -} - -void ClientChaseMusicReset(int client) -{ - g_PlayerChaseMusicMaster[client] = -1; - g_PlayerChaseMusicOldMaster[client] = -1; - ClientRemoveMusicFlag(client, MUSICF_CHASE); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - for (int i = 0; i < MAX_BOSSES; i++) - { - g_PlayerChaseMusicVolumes[client][i] = 0.0; - g_PlayerChaseMusicTimer[client][i] = null; - g_PlayerChaseMusicString[client][i][0] = '\0'; - - if (NPCGetUniqueID(i) != -1) - { - if (IsValidClient(client)) - { - NPCGetProfile(i, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileChaseMusics(profile, soundInfo); - soundInfo.StopAllSounds(client); - } - } - } -} - -void ClientMusicChaseStart(int client,int bossIndex) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client)) - { - return; - } - - int oldMaster = g_PlayerChaseMusicMaster[client]; - if (oldMaster == bossIndex) - { - return; - } - - g_PlayerChaseMusicOldMaster[client] = oldMaster; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char buffer[PLATFORM_MAX_PATH]; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileChaseMusics(profile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); - } - - if (buffer[0] == '\0') - { - return; - } - - g_PlayerChaseMusicMaster[client] = bossIndex; - if (g_PlayerChaseMusicVolumes[client][bossIndex] <= 0.0) - { - strcopy(g_PlayerChaseMusicString[client][bossIndex], sizeof(g_PlayerChaseMusicString[][]), buffer); - } - if (oldMaster != -1 && g_PlayerChaseMusicString[client][oldMaster][0] != '\0' && - strcmp(g_PlayerChaseMusicString[client][oldMaster], g_PlayerChaseMusicString[client][bossIndex]) == 0) - { - if (g_PlayerChaseMusicVolumes[client][bossIndex] < 1.0) - { - g_PlayerChaseMusicVolumes[client][bossIndex] = g_PlayerChaseMusicVolumes[client][oldMaster]; - g_PlayerChaseMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerChaseMusicTimer[client][bossIndex], true); - } - return; - } - if (MusicActive())//A boss is overriding the music. - { - GetBossMusic(g_PlayerChaseMusicString[client][bossIndex],sizeof(g_PlayerChaseMusicString[][])); - } - g_PlayerChaseMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerChaseMusicTimer[client][bossIndex], true); - - if (oldMaster != -1) - { - ClientMusicChaseStop(client, oldMaster); - } -} - -void ClientMusicChaseStop(int client,int bossIndex) -{ - if (!IsValidClient(client)) - { - return; - } - if (bossIndex == -1) - { - return; - } - - if (bossIndex == g_PlayerChaseMusicMaster[client]) - { - g_PlayerChaseMusicMaster[client] = -1; - } - - g_PlayerChaseMusicTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutChaseMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerChaseMusicTimer[client][bossIndex], true); -} - -void ClientChaseMusicSeeReset(int client) -{ - g_PlayerChaseMusicSeeMaster[client] = -1; - g_PlayerChaseMusicSeeOldMaster[client] = -1; - ClientRemoveMusicFlag(client, MUSICF_CHASEVISIBLE); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - - for (int i = 0; i < MAX_BOSSES; i++) - { - g_PlayerChaseMusicSeeTimer[client][i] = null; - g_PlayerChaseMusicSeeVolumes[client][i] = 0.0; - g_PlayerChaseMusicSeeString[client][i][0] = '\0'; - - if (NPCGetUniqueID(i) != -1) - { - if (IsValidClient(client)) - { - NPCGetProfile(i, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileChaseVisibleMusics(profile, soundInfo); - soundInfo.StopAllSounds(client); - } - } - } -} - -void ClientMusicChaseSeeStart(int client,int bossIndex) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client)) - { - return; - } - - int oldMaster = g_PlayerChaseMusicSeeMaster[client]; - if (oldMaster == bossIndex) - { - return; - } - - g_PlayerChaseMusicSeeOldMaster[client] = oldMaster; - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char buffer[PLATFORM_MAX_PATH]; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileChaseVisibleMusics(profile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, sizeof(buffer)); - } - - if (buffer[0] == '\0') - { - return; - } - - g_PlayerChaseMusicSeeMaster[client] = bossIndex; - if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] <= 0.0) - { - strcopy(g_PlayerChaseMusicSeeString[client][bossIndex], sizeof(g_PlayerChaseMusicSeeString[][]), buffer); - } - if (oldMaster != -1 && g_PlayerChaseMusicSeeString[client][oldMaster][0] != '\0' && - strcmp(g_PlayerChaseMusicSeeString[client][oldMaster], g_PlayerChaseMusicSeeString[client][bossIndex]) == 0) - { - if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] < 1.0) - { - g_PlayerChaseMusicSeeVolumes[client][bossIndex] = g_PlayerChaseMusicSeeVolumes[client][oldMaster]; - g_PlayerChaseMusicSeeTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusicSee, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerChaseMusicSeeTimer[client][bossIndex], true); - } - return; - } - g_PlayerChaseMusicSeeTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeInChaseMusicSee, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerChaseMusicSeeTimer[client][bossIndex], true); - - if (oldMaster != -1) - { - ClientMusicChaseSeeStop(client, oldMaster); - } -} - -void ClientMusicChaseSeeStop(int client,int bossIndex) -{ - if (!IsValidClient(client)) - { - return; - } - if (bossIndex == -1) - { - return; - } - - if (bossIndex == g_PlayerChaseMusicSeeMaster[client]) - { - g_PlayerChaseMusicSeeMaster[client] = -1; - } - - g_PlayerChaseMusicSeeTimer[client][bossIndex] = CreateTimer(0.01, Timer_PlayerFadeOutChaseMusicSee, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerChaseMusicSeeTimer[client][bossIndex], true); -} - -void Client90sMusicReset(int client) -{ - char oldMusic[PLATFORM_MAX_PATH]; - strcopy(oldMusic, sizeof(oldMusic), g_Player90sMusicString[client]); - g_Player90sMusicString[client][0] = '\0'; - if (IsValidClient(client) && oldMusic[0] != '\0') - { - StopSound(client, MUSIC_CHAN, oldMusic); - } - - g_Player90sMusicTimer[client] = null; - g_Player90sMusicVolumes[client] = 0.0; - - if (IsValidClient(client)) - { - oldMusic = NINETYSMUSIC; - if (oldMusic[0] != '\0') - { - StopSound(client, MUSIC_CHAN, oldMusic); - } - } -} - -void Client90sMusicStart(int client) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client)) - { - return; - } - - char buffer[PLATFORM_MAX_PATH]; - buffer = NINETYSMUSIC; - - if (buffer[0] == '\0') - { - return; - } - - strcopy(g_Player90sMusicString[client], sizeof(g_Player90sMusicString[]), buffer); - g_Player90sMusicTimer[client] = CreateTimer(0.01, Timer_PlayerFadeIn90sMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_Player90sMusicTimer[client], true); -} - -void Client90sMusicStop(int client) -{ - if (!IsValidClient(client)) - { - return; - } - - if (!IsClientSprinting(client)) - { - g_Player90sMusicString[client][0] = '\0'; - } - - g_Player90sMusicTimer[client]= CreateTimer(0.01, Timer_PlayerFadeOut90sMusic, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_Player90sMusicTimer[client], true); -} - -Action Timer_PlayerFadeInMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerMusicTimer[client]) - { - return Plugin_Stop; - } - - g_PlayerMusicVolume[client] += 0.07; - if (g_PlayerMusicVolume[client] > g_PlayerMusicTargetVolume[client]) - { - g_PlayerMusicVolume[client] = g_PlayerMusicTargetVolume[client]; - } - - if (g_PlayerMusicString[client][0] != '\0') - { - EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); - } - - if (g_PlayerMusicVolume[client] >= g_PlayerMusicTargetVolume[client]) - { - g_PlayerMusicTimer[client] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeOutMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerMusicTimer[client]) - { - return Plugin_Stop; - } - - g_PlayerMusicVolume[client] -= 0.07; - if (g_PlayerMusicVolume[client] < 0.0) - { - g_PlayerMusicVolume[client] = 0.0; - } - - if (g_PlayerMusicString[client][0] != '\0') - { - EmitSoundToClient(client, g_PlayerMusicString[client], _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, g_PlayerMusicVolume[client], 100); - } - - if (g_PlayerMusicVolume[client] <= 0.0) - { - g_PlayerMusicTimer[client] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeInAlertMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerAlertMusicTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - /*int oldBoss = g_PlayerAlertMusicOldMaster[client]; - - if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerAlertMusicString[client][bossIndex], g_PlayerAlertMusicString[client][oldBoss], false) == 0) - { - g_PlayerAlertMusicTimer[client][bossIndex] = null; - g_PlayerAlertMusicVolumes[client][bossIndex] = 1.0; - return Plugin_Stop; - }*/ - - g_PlayerAlertMusicVolumes[client][bossIndex] += 0.07; - if (g_PlayerAlertMusicVolumes[client][bossIndex] > 1.0) - { - g_PlayerAlertMusicVolumes[client][bossIndex] = 1.0; - } - - if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); - } - - if (g_PlayerAlertMusicVolumes[client][bossIndex] >= 1.0) - { - g_PlayerAlertMusicTimer[client][bossIndex] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeOutAlertMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerAlertMusicTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - int oldBoss = g_PlayerAlertMusicOldMaster[client]; - int newBoss = g_PlayerAlertMusicMaster[client]; - - if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerAlertMusicString[client][newBoss], g_PlayerAlertMusicString[client][oldBoss], false) == 0) - { - g_PlayerAlertMusicTimer[client][oldBoss] = null; - g_PlayerAlertMusicString[client][oldBoss][0] = '\0'; - g_PlayerAlertMusicVolumes[client][oldBoss] = 0.0; - g_PlayerAlertMusicOldMaster[client] = -1; - return Plugin_Stop; - } - - g_PlayerAlertMusicVolumes[client][bossIndex] -= 0.07; - if (g_PlayerAlertMusicVolumes[client][bossIndex] < 0.0) - { - g_PlayerAlertMusicVolumes[client][bossIndex] = 0.0; - } - - if (g_PlayerAlertMusicString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerAlertMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerAlertMusicVolumes[client][bossIndex], 100); - } - - if (g_PlayerAlertMusicVolumes[client][bossIndex] <= 0.0) - { - g_PlayerAlertMusicTimer[client][bossIndex] = null; - g_PlayerAlertMusicString[client][bossIndex][0] = '\0'; - g_PlayerAlertMusicOldMaster[client] = -1; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeInIdleMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerIdleMusicTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - /*int oldBoss = g_PlayerIdleMusicOldMaster[client]; - - if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerIdleMusicString[client][bossIndex], g_PlayerIdleMusicString[client][oldBoss], false) == 0) - { - g_PlayerIdleMusicTimer[client][bossIndex] = null; - g_PlayerIdleMusicVolumes[client][bossIndex] = 1.0; - return Plugin_Stop; - }*/ - - g_PlayerIdleMusicVolumes[client][bossIndex] += 0.07; - if (g_PlayerIdleMusicVolumes[client][bossIndex] > 1.0) - { - g_PlayerIdleMusicVolumes[client][bossIndex] = 1.0; - } - - if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); - } - - if (g_PlayerIdleMusicVolumes[client][bossIndex] >= 1.0) - { - g_PlayerIdleMusicTimer[client][bossIndex] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeOutIdleMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerIdleMusicTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - int oldBoss = g_PlayerIdleMusicOldMaster[client]; - int newBoss = g_PlayerIdleMusicMaster[client]; - - if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerIdleMusicString[client][newBoss], g_PlayerIdleMusicString[client][oldBoss], false) == 0) - { - g_PlayerIdleMusicTimer[client][oldBoss] = null; - g_PlayerIdleMusicString[client][oldBoss][0] = '\0'; - g_PlayerIdleMusicVolumes[client][oldBoss] = 0.0; - g_PlayerIdleMusicOldMaster[client] = -1; - return Plugin_Stop; - } - - g_PlayerIdleMusicVolumes[client][bossIndex] -= 0.07; - if (g_PlayerIdleMusicVolumes[client][bossIndex] < 0.0) - { - g_PlayerIdleMusicVolumes[client][bossIndex] = 0.0; - } - - if (g_PlayerIdleMusicString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerIdleMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerIdleMusicVolumes[client][bossIndex], 100); - } - - if (g_PlayerIdleMusicVolumes[client][bossIndex] <= 0.0) - { - g_PlayerIdleMusicTimer[client][bossIndex] = null; - g_PlayerIdleMusicString[client][bossIndex][0] = '\0'; - g_PlayerIdleMusicOldMaster[client] = -1; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeInChaseMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerChaseMusicTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - /*int oldBoss = g_PlayerChaseMusicOldMaster[client]; - - if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicString[client][bossIndex], g_PlayerChaseMusicString[client][oldBoss], false) == 0) - { - g_PlayerChaseMusicTimer[client][bossIndex] = null; - g_PlayerChaseMusicVolumes[client][bossIndex] = 1.0; - return Plugin_Stop; - }*/ - - g_PlayerChaseMusicVolumes[client][bossIndex] += 0.07; - if (g_PlayerChaseMusicVolumes[client][bossIndex] > 1.0) - { - g_PlayerChaseMusicVolumes[client][bossIndex] = 1.0; - } - - if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); - } - - if (g_PlayerChaseMusicVolumes[client][bossIndex] >= 1.0) - { - g_PlayerChaseMusicTimer[client][bossIndex] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeInChaseMusicSee(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerChaseMusicSeeTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - /*int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; - - if (oldBoss != -1 && bossIndex != oldBoss && strcmp(g_PlayerChaseMusicSeeString[client][bossIndex], g_PlayerChaseMusicSeeString[client][oldBoss], false) == 0) - { - g_PlayerChaseMusicSeeTimer[client][bossIndex] = null; - g_PlayerChaseMusicSeeVolumes[client][bossIndex] = 1.0; - return Plugin_Stop; - }*/ - - g_PlayerChaseMusicSeeVolumes[client][bossIndex] += 0.07; - if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] > 1.0) - { - g_PlayerChaseMusicSeeVolumes[client][bossIndex] = 1.0; - } - - if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); - } - - if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] >= 1.0) - { - g_PlayerChaseMusicSeeTimer[client][bossIndex] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeOutChaseMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerChaseMusicTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - int oldBoss = g_PlayerChaseMusicOldMaster[client]; - int newBoss = g_PlayerChaseMusicMaster[client]; - - if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerChaseMusicString[client][newBoss], g_PlayerChaseMusicString[client][oldBoss], false) == 0) - { - g_PlayerChaseMusicTimer[client][oldBoss] = null; - g_PlayerChaseMusicString[client][oldBoss][0] = '\0'; - g_PlayerChaseMusicVolumes[client][oldBoss] = 0.0; - g_PlayerChaseMusicOldMaster[client] = -1; - return Plugin_Stop; - } - - g_PlayerChaseMusicVolumes[client][bossIndex] -= 0.07; - if (g_PlayerChaseMusicVolumes[client][bossIndex] < 0.0) - { - g_PlayerChaseMusicVolumes[client][bossIndex] = 0.0; - } - - if (g_PlayerChaseMusicString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerChaseMusicString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicVolumes[client][bossIndex], 100); - } - - if (g_PlayerChaseMusicVolumes[client][bossIndex] <= 0.0) - { - g_PlayerChaseMusicTimer[client][bossIndex] = null; - g_PlayerChaseMusicString[client][bossIndex][0] = '\0'; - g_PlayerChaseMusicOldMaster[client] = -1; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeOutChaseMusicSee(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = -1; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (g_PlayerChaseMusicSeeTimer[client][i] == timer) - { - bossIndex = i; - break; - } - } - - if (bossIndex == -1) - { - return Plugin_Stop; - } - - int oldBoss = g_PlayerChaseMusicSeeOldMaster[client]; - int newBoss = g_PlayerChaseMusicSeeMaster[client]; - - if (oldBoss != -1 && newBoss != -1 && newBoss != oldBoss && strcmp(g_PlayerChaseMusicSeeString[client][newBoss], g_PlayerChaseMusicSeeString[client][oldBoss], false) == 0) - { - g_PlayerChaseMusicSeeTimer[client][oldBoss] = null; - g_PlayerChaseMusicSeeString[client][oldBoss][0] = '\0'; - g_PlayerChaseMusicSeeVolumes[client][oldBoss] = 0.0; - g_PlayerChaseMusicSeeOldMaster[client] = -1; - return Plugin_Stop; - } - - g_PlayerChaseMusicSeeVolumes[client][bossIndex] -= 0.07; - if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] < 0.0) - { - g_PlayerChaseMusicSeeVolumes[client][bossIndex] = 0.0; - } - - if (g_PlayerChaseMusicSeeString[client][bossIndex][0] != '\0') - { - EmitSoundToClient(client, g_PlayerChaseMusicSeeString[client][bossIndex], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_PlayerChaseMusicSeeVolumes[client][bossIndex], 100); - } - - if (g_PlayerChaseMusicSeeVolumes[client][bossIndex] <= 0.0) - { - g_PlayerChaseMusicSeeTimer[client][bossIndex] = null; - g_PlayerChaseMusicSeeString[client][bossIndex][0] = '\0'; - g_PlayerChaseMusicSeeOldMaster[client] = -1; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeIn90sMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (g_Player90sMusicTimer[client] != timer) - { - return Plugin_Stop; - } - - g_Player90sMusicVolumes[client] += 0.28; - if (g_Player90sMusicVolumes[client] > 0.5) - { - g_Player90sMusicVolumes[client] = 0.5; - } - - if (g_Player90sMusicString[client][0] != '\0') - { - EmitSoundToClient(client, g_Player90sMusicString[client], _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); - } - - if (g_Player90sMusicVolumes[client] >= 0.5) - { - g_Player90sMusicTimer[client] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_PlayerFadeOut90sMusic(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (g_Player90sMusicTimer[client] != timer) - { - return Plugin_Stop; - } - - char buffer[PLATFORM_MAX_PATH]; - buffer = NINETYSMUSIC; - - if (strcmp(buffer, g_Player90sMusicString[client], false) == 0) - { - g_Player90sMusicTimer[client] = null; - return Plugin_Stop; - } - - g_Player90sMusicVolumes[client] -= 0.28; - if (g_Player90sMusicVolumes[client] < 0.0) - { - g_Player90sMusicVolumes[client] = 0.0; - } - - if (buffer[0] != '\0') - { - EmitSoundToClient(client, buffer, _, MUSIC_CHAN, _, SND_CHANGEVOL, g_Player90sMusicVolumes[client], 100); - } - - if (g_Player90sMusicVolumes[client] <= 0.0) - { - g_Player90sMusicTimer[client] = null; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -bool ClientHasMusicFlag(int client,int flag) -{ - return !!(g_PlayerMusicFlags[client] & flag); -} - -bool ClientHasMusicFlag2(int value,int flag) -{ - return !!(value & flag); -} - -void ClientAddMusicFlag(int client,int flag) -{ - if (!ClientHasMusicFlag(client, flag)) - { - g_PlayerMusicFlags[client] |= flag; - } -} - -void ClientRemoveMusicFlag(int client,int flag) -{ - if (ClientHasMusicFlag(client, flag)) - { - g_PlayerMusicFlags[client] &= ~flag; - } -} diff --git a/addons/sourcemod/scripting/sf2/functionclients/client_proxy_functions.sp b/addons/sourcemod/scripting/sf2/functionclients/client_proxy_functions.sp deleted file mode 100644 index de19032f..00000000 --- a/addons/sourcemod/scripting/sf2/functionclients/client_proxy_functions.sp +++ /dev/null @@ -1,2018 +0,0 @@ -#if defined _sf2_client_proxy_functions_included - #endinput -#endif -#define _sf2_client_proxy_functions_included - -#pragma semicolon 1 - -static int g_ActionItemIndexes[] = { 57, 231 }; - -//Proxy model -static char g_ClientProxyModel[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -static char g_ClientProxyModelHard[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -static char g_ClientProxyModelInsane[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -static char g_ClientProxyModelNightmare[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; -static char g_ClientProxyModelApollyon[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; - -void ClientResetProxy(int client, bool resetFull=true) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetProxy(%d)", client); - } - #endif - - int oldMaster = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); - char oldProfileName[SF2_MAX_PROFILE_NAME_LENGTH]; - if (oldMaster >= 0) - { - NPCGetProfile(oldMaster, oldProfileName, sizeof(oldProfileName)); - } - - bool oldProxy = g_PlayerProxy[client]; - if (resetFull) - { - g_PlayerProxy[client] = false; - g_PlayerProxyMaster[client] = -1; - } - - g_PlayerProxyControl[client] = 0; - g_PlayerProxyControlTimer[client] = null; - g_PlayerProxyControlRate[client] = 0.0; - g_PlayerProxyVoiceTimer[client] = null; - - if (IsClientInGame(client)) - { - if (oldProxy) - { - ClientStartProxyAvailableTimer(client); - - if (resetFull) - { - ClientDisableConstantGlow(client); - SetVariantString(""); - AcceptEntityInput(client, "SetCustomModel"); - } - - if (oldProfileName[0] != '\0') - { - SF2BossProfileSoundInfo soundInfo; - GetBossProfileProxySpawnSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - GetBossProfileProxyHurtSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - GetBossProfileProxyIdleSounds(oldProfileName, soundInfo); - soundInfo.StopAllSounds(client); - } - } - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetProxy(%d)", client); - } - #endif -} - -void ClientStartProxyAvailableTimer(int client) -{ - g_PlayerProxyAvailable[client] = false; - float cooldown = g_PlayerProxyWaitTimeConVar.FloatValue; - if (g_InProxySurvivalRageMode) - { - cooldown -= 10.0; - } - if (cooldown <= 0.0) - { - cooldown = 0.0; - } - - g_PlayerProxyAvailableTimer[client] = CreateTimer(cooldown, Timer_ClientProxyAvailable, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); -} - -void ClientStartProxyForce(int client, int slenderID, const float pos[3], int spawnPoint) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientStartProxyForce(%d, %d, pos)", client, slenderID); - } - #endif - - g_PlayerProxyAskMaster[client] = slenderID; - for (int i = 0; i < 3; i++) - { - g_PlayerProxyAskPosition[client][i] = pos[i]; - } - g_PlayerProxyAskSpawnPoint[client] = EnsureEntRef(spawnPoint); - - g_PlayerProxyAvailableCount[client] = 0; - g_PlayerProxyAvailableInForce[client] = true; - g_PlayerProxyAvailableTimer[client] = CreateTimer(1.0, Timer_ClientForceProxy, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - TriggerTimer(g_PlayerProxyAvailableTimer[client], true); - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientStartProxyForce(%d, %d, pos)", client, slenderID); - } - #endif -} - -void ClientStopProxyForce(int client) -{ - g_PlayerProxyAvailableCount[client] = 0; - g_PlayerProxyAvailableInForce[client] = false; - g_PlayerProxyAvailableTimer[client] = null; -} - -Action Timer_ClientForceProxy(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerProxyAvailableTimer[client]) - { - return Plugin_Stop; - } - - if (!IsRoundEnding()) - { - int bossIndex = NPCGetFromUniqueID(g_PlayerProxyAskMaster[client]); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; - int numProxies = 0; - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || !g_PlayerEliminated[i]) - { - continue; - } - if (!g_PlayerProxy[i]) - { - continue; - } - if (NPCGetFromUniqueID(g_PlayerProxyMaster[i]) != bossIndex) - { - continue; - } - - numProxies++; - } - - if (numProxies < maxProxies) - { - if (g_PlayerProxyAvailableCount[client] > 0) - { - g_PlayerProxyAvailableCount[client]--; - - SetHudTextParams(-1.0, 0.25, - 1.0, - 255, 255, 255, 255, - _, - _, - 0.25, 1.25); - - ShowSyncHudText(client, g_HudSync, "%T", "SF2 Proxy Force Message", client, g_PlayerProxyAvailableCount[client]); - - return Plugin_Continue; - } - else - { - ClientEnableProxy(client, bossIndex, g_PlayerProxyAskPosition[client], g_PlayerProxyAskSpawnPoint[client]); - } - } - else - { - //PrintToChat(client, "%T", "SF2 Too Many Proxies", client); - } - } - } - - ClientStopProxyForce(client); - return Plugin_Stop; -} - -void DisplayProxyAskMenu(int client, int askMaster, const float pos[3], int spawnPoint) -{ - if (IsRoundEnding() || IsRoundInIntro() || IsRoundInWarmup()) - { - return; - } - char buffer[512]; - Handle menu = CreateMenu(Menu_ProxyAsk); - SetMenuTitle(menu, "%T\n \n%T\n \n", "SF2 Proxy Ask Menu Title", client, "SF2 Proxy Ask Menu Description", client); - - FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "1", buffer); - FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "0", buffer); - - g_PlayerProxyAskMaster[client] = askMaster; - for (int i = 0; i < 3; i++) - { - g_PlayerProxyAskPosition[client][i] = pos[i]; - } - g_PlayerProxyAskSpawnPoint[client] = EnsureEntRef(spawnPoint); - - DisplayMenu(menu, client, 15); -} - -int Menu_ProxyAsk(Handle menu, MenuAction action,int param1,int param2) -{ - switch (action) - { - case MenuAction_End: - { - delete menu; - } - case MenuAction_Select: - { - if (!IsRoundEnding() && !IsRoundInIntro() && !IsRoundInWarmup()) - { - int bossIndex = NPCGetFromUniqueID(g_PlayerProxyAskMaster[param1]); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; - int numProxies; - - for (int client = 1; client <= MaxClients; client++) - { - if (!IsClientInGame(client) || !g_PlayerEliminated[client]) - { - continue; - } - if (!g_PlayerProxy[client]) - { - continue; - } - if (NPCGetFromUniqueID(g_PlayerProxyMaster[client]) != bossIndex) - { - continue; - } - - numProxies++; - } - - if (numProxies < maxProxies) - { - if (param2 == 0) - { - bool ignoreVisibility = false; - int spawnPointEnt = g_PlayerProxyAskSpawnPoint[param1]; - float spawnPos[3]; - - if (IsValidEntity(spawnPointEnt)) - { - GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", spawnPos); - - SF2PlayerProxySpawnEntity spawnPoint = SF2PlayerProxySpawnEntity(spawnPointEnt); - if (spawnPoint.IsValid()) - { - ignoreVisibility = spawnPoint.IgnoreVisibility; - } - } - else - { - for (int i = 0; i < 3; i++) - { - spawnPos[i] = g_PlayerProxyAskPosition[param1][i]; - } - } - - if (ignoreVisibility || !IsPointVisibleToAPlayer(spawnPos, _, false)) - { - ClientEnableProxy(param1, bossIndex, spawnPos, spawnPointEnt); - } - else - { - CPrintToChat(param1, "%T", "SF2 Too Much Time", param1); - } - } - else - { - ClientStartProxyAvailableTimer(param1); - } - } - else - { - PrintToChat(param1, "%T", "SF2 Too Many Proxies", param1); - } - } - } - } - } - return 0; -} - -Action Timer_ClientProxyAvailable(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerProxyAvailableTimer[client]) - { - return Plugin_Stop; - } - - g_PlayerProxyAvailable[client] = true; - g_PlayerProxyAvailableTimer[client] = null; - - return Plugin_Stop; -} - -/** - * Respawns a player as a proxy. - * - * @noreturn - */ -void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnPointEnt=-1) -{ - if (NPCGetUniqueID(bossIndex) == -1) - { - return; - } - if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) - { - return; - } - if (GetClientTeam(client) != TFTeam_Blue) - { - return; - } - if (g_PlayerProxy[client]) - { - return; - } - - TF2_RemovePlayerDisguise(client); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - ClientSetGhostModeState(client, false); - - ClientStopProxyForce(client); - - if (IsClientInKart(client)) - { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); - } - - g_PlayerProxy[client] = true; - ChangeClientTeamNoSuicide(client, TFTeam_Blue); - PvP_SetPlayerPvPState(client, false, true, false); - TF2_RespawnPlayer(client); - - // Speed recalculation. Props to the creators of FF2/VSH for this snippet. - TF2_AddCondition(client, TFCond_SpeedBuffAlly, 0.001); - - g_PlayerProxy[client] = true; - g_PlayerProxyMaster[client] = NPCGetUniqueID(bossIndex); - g_PlayerProxyControl[client] = 100; - g_PlayerProxyControlRate[client] = g_SlenderProxyControlDrainRate[bossIndex][difficulty]; - g_PlayerProxyControlTimer[client] = CreateTimer(g_PlayerProxyControlRate[client], Timer_ClientProxyControl, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - g_PlayerProxyAvailable[client] = false; - g_PlayerProxyAvailableTimer[client] = null; - - char allowedClasses[512]; - GetBossProfileProxyClasses(profile, allowedClasses, sizeof(allowedClasses)); - - char className[64]; - TF2_GetClassName(TF2_GetPlayerClass(client), className, sizeof(className)); - if (allowedClasses[0] != '\0' && className[0] != '\0' && StrContains(allowedClasses, className, false) == -1) - { - // Pick the first class that's allowed. - char allowedClassesList[32][32]; - int classCount = ExplodeString(allowedClasses, " ", allowedClassesList, 32, 32); - if (classCount) - { - TF2_SetPlayerClass(client, TF2_GetClass(allowedClassesList[0]), _, false); - - int maxHealth = GetEntProp(client, Prop_Send, "m_iHealth"); - TF2_RegeneratePlayer(client); - SetEntProp(client, Prop_Data, "m_iHealth", maxHealth); - SetEntProp(client, Prop_Send, "m_iHealth", maxHealth); - } - } - - UTIL_ScreenFade(client, 200, 1, FFADE_IN, 255, 255, 255, 100); - EmitSoundToClient(client, "weapons/teleporter_send.wav", _, SNDCHAN_STATIC); - - ClientDisableConstantGlow(client); - ClientActivateUltravision(client); - - TF2Attrib_SetByDefIndex(client, 28, 1.0); - - CreateTimer(0.33, Timer_ApplyCustomModel, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - - if (NPCHasProxyWeapons(bossIndex)) - { - CreateTimer(1.0, Timer_GiveWeaponAll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - if (g_SlenderInDeathcam[npcIndex]) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } - } - - //SDKHook(client, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); - - SF2PlayerProxySpawnEntity spawnPoint = SF2PlayerProxySpawnEntity(spawnPointEnt); - if (spawnPoint.IsValid()) - { - float spawnPos[3]; float ang[3]; - GetEntPropVector(spawnPointEnt, Prop_Data, "m_vecAbsOrigin", spawnPos); - GetEntPropVector(spawnPointEnt, Prop_Data, "m_angAbsRotation", ang); - TeleportEntity(client, spawnPos, ang, view_as({ 0.0, 0.0, 0.0 })); - spawnPoint.FireOutput("OnSpawn", client); - } - else - { - TeleportEntity(client, pos, NULL_VECTOR, view_as({ 0.0, 0.0, 0.0 })); - } - - if (NPCGetProxySpawnEffectState(bossIndex)) - { - char spawnEffect[PLATFORM_MAX_PATH]; - GetBossProfileProxySpawnEffectName(profile, spawnEffect, sizeof(spawnEffect)); - CreateGeneralParticle(client, spawnEffect, NPCGetProxySpawnEffectZOffset(bossIndex)); - } - - Call_StartForward(g_OnClientSpawnedAsProxyFwd); - Call_PushCell(client); - Call_Finish(); -} - -static Action Timer_GiveWeaponAll(Handle timer, any userid) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); - - if (g_PlayerProxy[client] && bossIndex != -1) - { - if (!NPCHasProxyWeapons(bossIndex)) - { - return Plugin_Stop; - } - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int weaponIndex, weaponSlot; - char weaponName[PLATFORM_MAX_PATH], weaponStats[PLATFORM_MAX_PATH]; - int classIndex = view_as(TF2_GetPlayerClass(client)); - classIndex--; - ArrayList weaponArray = GetBossProfileProxyWeaponClassNames(profile); - if (weaponArray == null) - { - return Plugin_Stop; - } - weaponArray.GetString(classIndex, weaponName, sizeof(weaponName)); - weaponArray = GetBossProfileProxyWeaponStats(profile); - if (weaponArray == null) - { - return Plugin_Stop; - } - weaponArray.GetString(classIndex, weaponStats, sizeof(weaponStats)); - weaponIndex = GetBossProfileProxyWeaponIndexes(profile, classIndex + 1); - weaponSlot = GetBossProfileProxyWeaponSlots(profile, classIndex + 1); - - switch (weaponSlot) - { - case 0: - { - TF2_RemoveWeaponSlot(client, TFWeaponSlot_Primary); - } - case 1: - { - TF2_RemoveWeaponSlot(client, TFWeaponSlot_Secondary); - } - case 2: - { - TF2_RemoveWeaponSlot(client, TFWeaponSlot_Melee); - } - } - Handle weaponHandle = PrepareItemHandle(weaponName, weaponIndex, 0, 0, weaponStats); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - EquipPlayerWeapon(client, entity); - SetEntProp(entity, Prop_Send, "m_bValidatedAttachedEntity", 1); - } - return Plugin_Stop; -} - -bool Hook_ClientProxyShouldCollide(int ent,int collisiongroup,int contentsmask, bool originalResult) -{ - if (!g_Enabled || !g_PlayerProxy[ent] || IsClientInPvP(ent)) - { - SDKUnhook(ent, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); - return originalResult; - } - if ((contentsmask & MASK_RED)) - { - return true; - } - //To-do add no collision proxy-boss here, the collision boss-proxy is done, see npc_chaser.sp - return originalResult; -} -//RequestFrame// -void ProxyDeathAnimation(any client) -{ - if (client != -1) - { - if (g_ClientFrame[client] >= g_ClientMaxFrameDeathAnim[client]) - { - g_ClientFrame[client]--; - KillClient(client); - } - else - { - g_ClientFrame[client]++; - RequestFrame(ProxyDeathAnimation,client); - } - } -} - -Action Timer_ClientProxyControl(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerProxyControlTimer[client]) - { - return Plugin_Stop; - } - - g_PlayerProxyControl[client]--; - if (TF2_IsPlayerInCondition(client, TFCond_Taunting)) - { - g_PlayerProxyControl[client] -= 5; - } - if (g_PlayerProxyControl[client] <= 0) - { - // ForcePlayerSuicide isn't really dependable, since the player doesn't suicide until several seconds after spawning has passed. - SDKHooks_TakeDamage(client, client, client, 9001.0, DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); - return Plugin_Stop; - } - - g_PlayerProxyControlTimer[client] = CreateTimer(g_PlayerProxyControlRate[client], Timer_ClientProxyControl, userid, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Stop; -} - -Action Timer_ApplyCustomModel(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - SetEntProp(client, Prop_Send, "m_iAirDash", 99999); - - TF2Attrib_SetByName(client, "crit mod disabled", 0.0); - - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); - - if (g_PlayerProxy[client] && master != -1) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(master, profile, sizeof(profile)); - - int difficulty = GetLocalGlobalDifficulty(master); - - // Set custom model, if any. - char buffer[PLATFORM_MAX_PATH]; - - TF2_RegeneratePlayer(client); - - char className[64]; - TFClassType playerClass = TF2_GetPlayerClass(client); - int classToInt = view_as(playerClass); - TF2_GetClassName(playerClass, className, sizeof(className)); - - ArrayList modelsArray; - - if (GetBossProfileProxyDifficultyModelsState(profile)) - { - switch (difficulty) - { - case Difficulty_Easy, Difficulty_Normal: - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); - } - modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); - strcopy(g_ClientProxyModel[client],sizeof(g_ClientProxyModel[]),buffer); - } - case Difficulty_Hard: - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 1); - } - } - } - modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); - strcopy(g_ClientProxyModelHard[client],sizeof(g_ClientProxyModelHard[]),buffer); - } - case Difficulty_Insane: - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 2); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 2); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 1); - } - } - } - } - } - modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); - strcopy(g_ClientProxyModelInsane[client],sizeof(g_ClientProxyModelInsane[]),buffer); - } - case Difficulty_Nightmare: - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 3); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 3); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 2); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 2); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 1); - } - } - } - } - } - } - } - modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); - strcopy(g_ClientProxyModelNightmare[client],sizeof(g_ClientProxyModelNightmare[]),buffer); - } - case Difficulty_Apollyon: - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, difficulty); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 4); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 4); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 3); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 3); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 2); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 2); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 1); - } - } - } - } - } - } - } - } - } - modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); - strcopy(g_ClientProxyModelApollyon[client],sizeof(g_ClientProxyModelApollyon[]),buffer); - } - } - - if (buffer[0] != '\0') - { - SetVariantString(buffer); - AcceptEntityInput(client, "SetCustomModel"); - SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); - - CreateTimer(0.5, ClientCheckProxyModel, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - } - else - { - modelsArray = GetBossProfileProxyModels(profile, classToInt, 1); - if (modelsArray == null) - { - modelsArray = GetBossProfileProxyModels(profile, 0, 1); - } - if (modelsArray != null) - { - modelsArray.GetString(GetRandomInt(0, modelsArray.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0') - { - SetVariantString(buffer); - AcceptEntityInput(client, "SetCustomModel"); - SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); - strcopy(g_ClientProxyModel[client],sizeof(g_ClientProxyModel[]),buffer); - strcopy(g_ClientProxyModelHard[client],sizeof(g_ClientProxyModelHard[]),buffer); - strcopy(g_ClientProxyModelInsane[client],sizeof(g_ClientProxyModelInsane[]),buffer); - strcopy(g_ClientProxyModelNightmare[client],sizeof(g_ClientProxyModelNightmare[]),buffer); - strcopy(g_ClientProxyModelApollyon[client],sizeof(g_ClientProxyModelApollyon[]),buffer); - //Prevent plugins like Model manager to override proxy model. - CreateTimer(0.5, ClientCheckProxyModel, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - } - } - } - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } - } - - if (IsPlayerAlive(client)) - { - g_PlayerProxyNextVoiceSound[client] = GetGameTime(); - SF2BossProfileSoundInfo soundInfo; - GetBossProfileProxySpawnSounds(profile, soundInfo); - // Play any sounds, if any. - soundInfo.EmitSound(_, client); - - bool zombie = GetBossProfileProxyZombiesState(profile); - if (zombie) - { - int value = g_ForcedHolidayConVar.IntValue; - if (value != 9 && value != 2) - { - g_ForcedHolidayConVar.SetInt(9); //Full-Moon - } - int index; - TFClassType class = TF2_GetPlayerClass(client); - switch (class) - { - case TFClass_Scout: - { - index = 5617; - } - case TFClass_Soldier: - { - index = 5618; - } - case TFClass_Pyro: - { - index = 5624; - } - case TFClass_DemoMan: - { - index = 5620; - } - case TFClass_Engineer: - { - index = 5621; - } - case TFClass_Heavy: - { - index = 5619; - } - case TFClass_Medic: - { - index = 5622; - } - case TFClass_Sniper: - { - index = 5625; - } - case TFClass_Spy: - { - index = 5623; - } - } - Handle zombieSoul = PrepareItemHandle("tf_wearable", index, 100, 7,"448 ; 1.0 ; 450 ; 1"); - int entity = TF2Items_GiveNamedItem(client, zombieSoul); - delete zombieSoul; - zombieSoul = null; - if (IsValidEdict(entity)) - { - SDK_EquipWearable(client, entity); - } - if (TF2_GetPlayerClass(client) == TFClass_Spy) - { - SetEntProp(client, Prop_Send, "m_nForcedSkin", 23); - } - else - { - SetEntProp(client, Prop_Send, "m_nForcedSkin", 5); - } - } - - ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - } - } - return Plugin_Stop; -} - -Action ClientCheckProxyModel(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - if (!IsValidClient(client)) - { - return Plugin_Stop; - } - if (!IsPlayerAlive(client)) - { - return Plugin_Stop; - } - if (!g_PlayerProxy[client]) - { - return Plugin_Stop; - } - int difficulty = g_DifficultyConVar.IntValue; - - char model[PLATFORM_MAX_PATH]; - GetEntPropString(client, Prop_Data, "m_ModelName", model, sizeof(model)); - switch (difficulty) - { - case Difficulty_Normal: - { - if (strcmp(model,g_ClientProxyModel[client]) != 0 && g_ClientProxyModel[client][0] != '\0') - { - SetVariantString(g_ClientProxyModel[client]); - AcceptEntityInput(client, "SetCustomModel"); - SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); - } - } - case Difficulty_Hard: - { - if (strcmp(model,g_ClientProxyModelHard[client]) != 0 && g_ClientProxyModelHard[client][0] != '\0') - { - SetVariantString(g_ClientProxyModelHard[client]); - AcceptEntityInput(client, "SetCustomModel"); - SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); - } - } - case Difficulty_Insane: - { - if (strcmp(model,g_ClientProxyModelInsane[client]) != 0 && g_ClientProxyModelInsane[client][0] != '\0') - { - SetVariantString(g_ClientProxyModelInsane[client]); - AcceptEntityInput(client, "SetCustomModel"); - SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); - } - } - case Difficulty_Nightmare: - { - if (strcmp(model,g_ClientProxyModelNightmare[client]) != 0 && g_ClientProxyModelNightmare[client][0] != '\0') - { - SetVariantString(g_ClientProxyModelNightmare[client]); - AcceptEntityInput(client, "SetCustomModel"); - SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); - } - } - case Difficulty_Apollyon: - { - if (strcmp(model,g_ClientProxyModelApollyon[client]) != 0 && g_ClientProxyModelApollyon[client][0] != '\0') - { - SetVariantString(g_ClientProxyModelApollyon[client]); - AcceptEntityInput(client, "SetCustomModel"); - SetEntProp(client, Prop_Send, "m_bUseClassAnimations", true); - } - } - } - return Plugin_Continue; -} - -void SF2_RefreshRestrictions() -{ - for(int client=1;client <=MaxClients;client++) - { - if (IsValidClient(client) && (!g_PlayerEliminated[client] || !IsClientInPvP(client))) - { - ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - g_PlayerPostWeaponsTimer[client]=CreateTimer(1.0,Timer_ClientPostWeapons,GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - } -} -Action Timer_ClientPostWeapons(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (IsClientInGame(client) && !IsPlayerAlive(client)) - { - return Plugin_Stop; - } - - if (!IsValidClient(client)) - { - return Plugin_Stop; - } - - if (timer != g_PlayerPostWeaponsTimer[client]) - { - return Plugin_Stop; - } - - g_PlayerHasRegenerationItem[client] = false; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 0) - { - DebugMessage("START Timer_ClientPostWeapons(%d)", client); - } - - int oldWeaponItemIndexes[6] = { -1, ... }; - int newWeaponItemIndexes[6] = { -1, ... }; - - for (int i = 0; i <= 5; i++) - { - if (IsClientInGame(client) && IsValidClient(client)) - { - int weaponEnt = GetPlayerWeaponSlot(client, i); - if (!IsValidEdict(weaponEnt)) - { - continue; - } - - oldWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - } - } - #endif - - bool removeWeapons = true; - bool keepUtilityItems = false; - bool restrictWeapons = true; - bool useStock = false; - bool removeWearables = false; - bool preventAttack = false; - - if (IsRoundEnding()) - { - if (!g_PlayerEliminated[client]) - { - removeWeapons = false; - restrictWeapons = false; - keepUtilityItems = false; - } - } - - if (g_PlayerEliminated[client] && g_PlayerKeepWeaponsConVar.BoolValue) - { - removeWeapons = false; - restrictWeapons = false; - keepUtilityItems = false; - preventAttack = true; - } - - // pvp - if (IsClientInPvP(client)) - { - removeWeapons = false; - restrictWeapons = false; - keepUtilityItems = false; - preventAttack = false; - } - - if (g_PlayerProxy[client]) - { - restrictWeapons = true; - removeWeapons = true; - useStock = true; - removeWearables = true; - keepUtilityItems = false; - } - - if (IsRoundInWarmup()) - { - removeWeapons = false; - restrictWeapons = false; - keepUtilityItems = false; - preventAttack = false; - } - - if (IsClientInGhostMode(client)) - { - removeWeapons = true; - } - - if (SF_IsRaidMap() && !g_PlayerEliminated[client]) - { - removeWeapons = false; - restrictWeapons = false; - keepUtilityItems = false; - preventAttack = false; - } - - if (SF_IsBoxingMap() && !g_PlayerEliminated[client] && !IsRoundEnding()) - { - restrictWeapons = false; - keepUtilityItems = true; - preventAttack = false; - } - - if (removeWeapons && !keepUtilityItems) - { - for (int i = 0; i <= 5; i++) - { - if (i == TFWeaponSlot_Melee && !IsClientInGhostMode(client)) - { - continue; - } - TF2_RemoveWeaponSlotAndWearables(client, i); - } - - int ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - - ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_wearable_demoshield")) != -1) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - - ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - } - - if (keepUtilityItems) - { - for (int i = 0; i <= 5; i++) - { - if ((i == TFWeaponSlot_Melee || i == TFWeaponSlot_Secondary) && !IsClientInGhostMode(client)) - { - continue; - } - TF2_RemoveWeaponSlotAndWearables(client, i); - } - - int ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - - int weaponEnt = INVALID_ENT_REFERENCE; - weaponEnt = GetPlayerWeaponSlot(client, TFWeaponSlot_Secondary); - - if (IsValidEdict(weaponEnt)) - { - int itemIndex = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - switch (itemIndex) - { - case 163, 129, 226, 354, 1001, 131, 406, 1099, 42, 159, 311, 433, 863, 1002, 1190: - { - //Do nothing - } - default: - { - TF2_RemoveWeaponSlotAndWearables(client, TFWeaponSlot_Secondary); - } - } - } - - ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - } - - if (removeWearables) - { - TF2_StripWearables(client); - } - - TFClassType playerClass = TF2_GetPlayerClass(client); - int classToInt = view_as(playerClass); - - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) - { - int ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) - { - int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); - - if (642 == itemIndex) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - } - } - - if (restrictWeapons) - { - int health = GetEntProp(client, Prop_Send, "m_iHealth"); - - int ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) - { - int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); - - for (int i = 0; i < sizeof(g_ActionItemIndexes); i++) - { - if (g_ActionItemIndexes[i] == itemIndex) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - } - } - - ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_wearable_razorback")) != -1) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - - if (g_RestrictedWeaponsConfig != null) - { - int weaponEnt = INVALID_ENT_REFERENCE; - for (int slot = 0; slot <= 5; slot++) - { - Handle itemHandle = null; - weaponEnt = GetPlayerWeaponSlot(client, slot); - - if (IsValidEdict(weaponEnt)) - { - if (useStock || IsWeaponRestricted(playerClass, GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"))) - { - TF2_RemoveWeaponSlotAndWearables(client, slot); - switch (slot) - { - case TFWeaponSlot_Primary: - { - switch (playerClass) - { - case TFClass_Scout: - { - itemHandle = PrepareItemHandle("tf_weapon_scattergun", 13, 0, 0, ""); - } - case TFClass_Sniper: - { - itemHandle = PrepareItemHandle("tf_weapon_sniperrifle", 14, 0, 0, ""); - } - case TFClass_Soldier: - { - itemHandle = PrepareItemHandle("tf_weapon_rocketlauncher", 18, 0, 0, ""); - } - case TFClass_DemoMan: - { - itemHandle = PrepareItemHandle("tf_weapon_grenadelauncher", 19, 0, 0, ""); - } - case TFClass_Heavy: - { - itemHandle = PrepareItemHandle("tf_weapon_minigun", 15, 0, 0, ""); - } - case TFClass_Medic: - { - itemHandle = PrepareItemHandle("tf_weapon_syringegun_medic", 17, 0, 0, ""); - } - case TFClass_Pyro: - { - itemHandle = PrepareItemHandle("tf_weapon_flamethrower", 21, 0, 0, "254 ; 4.0"); - } - case TFClass_Spy: - { - itemHandle = PrepareItemHandle("tf_weapon_revolver", 24, 0, 0, ""); - } - case TFClass_Engineer: - { - itemHandle = PrepareItemHandle("tf_weapon_shotgun", 9, 0, 0, ""); - } - } - } - case TFWeaponSlot_Secondary: - { - switch (playerClass) - { - case TFClass_Scout: - { - itemHandle = PrepareItemHandle("tf_weapon_pistol", 23, 0, 0, ""); - } - case TFClass_Sniper: - { - itemHandle = PrepareItemHandle("tf_weapon_smg", 16, 0, 0, ""); - } - case TFClass_Soldier: - { - itemHandle = PrepareItemHandle("tf_weapon_shotgun", 10, 0, 0, ""); - } - case TFClass_DemoMan: - { - itemHandle = PrepareItemHandle("tf_weapon_pipebomblauncher", 20, 0, 0, ""); - } - case TFClass_Heavy: - { - itemHandle = PrepareItemHandle("tf_weapon_shotgun", 11, 0, 0, ""); - } - case TFClass_Medic: - { - itemHandle = PrepareItemHandle("tf_weapon_medigun", 29, 0, 0, ""); - } - case TFClass_Pyro: - { - itemHandle = PrepareItemHandle("tf_weapon_shotgun", 12, 0, 0, ""); - } - case TFClass_Engineer: - { - itemHandle = PrepareItemHandle("tf_weapon_pistol", 22, 0, 0, ""); - } - } - } - case TFWeaponSlot_Melee: - { - switch (playerClass) - { - case TFClass_Scout: - { - itemHandle = PrepareItemHandle("tf_weapon_bat", 0, 0, 0, ""); - } - case TFClass_Sniper: - { - itemHandle = PrepareItemHandle("tf_weapon_club", 3, 0, 0, ""); - } - case TFClass_Soldier: - { - itemHandle = PrepareItemHandle("tf_weapon_shovel", 6, 0, 0, ""); - } - case TFClass_DemoMan: - { - itemHandle = PrepareItemHandle("tf_weapon_bottle", 1, 0, 0, ""); - } - case TFClass_Heavy: - { - itemHandle = PrepareItemHandle("tf_weapon_fists", 5, 0, 0, ""); - } - case TFClass_Medic: - { - itemHandle = PrepareItemHandle("tf_weapon_bonesaw", 8, 0, 0, ""); - } - case TFClass_Pyro: - { - itemHandle = PrepareItemHandle("tf_weapon_fireaxe", 2, 0, 0, ""); - } - case TFClass_Spy: - { - itemHandle = PrepareItemHandle("tf_weapon_knife", 4, 0, 0, ""); - } - case TFClass_Engineer: - { - itemHandle = PrepareItemHandle("tf_weapon_wrench", 7, 0, 0, ""); - } - } - } - case 4: - { - switch (playerClass) - { - case TFClass_Spy: - { - itemHandle = PrepareItemHandle("tf_weapon_invis", 297, 0, 0, ""); - } - } - } - } - - if (itemHandle != null) - { - int newWeapon = TF2Items_GiveNamedItem(client, itemHandle); - if (IsValidEntity(newWeapon)) - { - EquipPlayerWeapon(client, newWeapon); - } - } - } - else - { - if (!g_PlayerHasRegenerationItem[client]) - g_PlayerHasRegenerationItem[client] = IsRegenWeapon(weaponEnt); - } - } - delete itemHandle; - itemHandle = null; - } - } - - // Fixes the Pretty Boy's Pocket Pistol glitch. - int maxHealth = SDKCall(g_SDKGetMaxHealth, client); - if (health > maxHealth) - { - SetEntProp(client, Prop_Data, "m_iHealth", maxHealth); - SetEntProp(client, Prop_Send, "m_iHealth", maxHealth); - } - } - - // Change stats on some weapons. - if (!g_PlayerEliminated[client] || g_PlayerProxy[client]) - { - int weaponEnt = INVALID_ENT_REFERENCE; - Handle weaponHandle = null; - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - switch (itemDef) - { - case 214: // Powerjack - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 214, 0, 0, "180 ; 12.0 ; 412 ; 1.2"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 310: //The Warrior's Spirit - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fists", 310, 0, 0, "2 ; 1.3 ; 412 ; 1.3"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 326: // The Back Scratcher - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 326, 0, 0, "2 ; 1.25 ; 412 ; 1.25 ; 69 ; 0.25 ; 108 ; 1.25"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 304: // Amputator - { - TF2_RemoveWeaponSlot(client, slot); - - if (!SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) - { - weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "200 ; 0.0 ; 57 ; 2 ; 1 ; 0.8"); - } - else - { - weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "1 ; 0.8"); - } - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 239: //GRU - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fists", 239, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - if (!IsRoundPlaying()) - { - SetEntityHealth(client, 300); - } - } - case 1084: //Festive GRU - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fists", 1084, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - if (!IsRoundPlaying()) - { - SetEntityHealth(client, 300); - } - } - case 1100: //Bread Bite - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fists", 1100, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - if (!IsRoundPlaying()) - { - SetEntityHealth(client, 300); - } - } - case 426: //Eviction Notice - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_fists", 426, 0, 0, "6 ; 0.6 ; 107 ; 1.15 ; 737 ; 4.0 ; 1 ; 0.4 ; 412 ; 1.2"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - if (!IsRoundPlaying()) - { - SetEntityHealth(client, 300); - } - } - case 775: //The Escape Plan (Its like, real buggy on wearer) - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_shovel", 775, 0, 0, "414 ; 1 ; 734 ; 0.1"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 452: //Three Rune Blade - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_bat", 452, 0, 0, ""); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 325: //Boston Basher - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_bat", 325, 0, 0, ""); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 450: //Atomizer - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_bat", 450, 0, 0, ""); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 225: //Your Eternal Reward - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_knife", 225, 0, 0, ""); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 649: //Spy-cicle - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_knife", 649, 0, 0, ""); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - case 574: //Spy-cicle - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_knife", 574, 0, 0, ""); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - } - } - delete weaponHandle; - } - - if (preventAttack) - { - int weaponEnt = INVALID_ENT_REFERENCE; - while ((weaponEnt = FindEntityByClassname(weaponEnt, "tf_wearable_demoshield")) != INVALID_ENT_REFERENCE) - { - if (GetEntPropEnt(weaponEnt, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(weaponEnt); - } - } - - for (int slot = 0; slot <= 5; slot++) - { - if (slot == TFWeaponSlot_Melee) - { - continue; - } - - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - switch (itemDef) - { - case 30, 212, 59, 60, 297, 947, 1101: // Invis Watch, Base Jumper - { - TF2_RemoveWeaponSlotAndWearables(client, slot); - } - default: - { - SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextPrimaryAttack", 99999999.9); - SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextSecondaryAttack", 99999999.9); - } - } - } - } - - //Remove the teleport ability - if (IsClientInPvP(client) || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !g_PlayerEliminated[client])) //DidClientEscape(client) - { - int weaponEnt = INVALID_ENT_REFERENCE; - Handle weaponHandle = null; - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - switch (itemDef) - { - case 589: // Eureka Effect - { - TF2_RemoveWeaponSlot(client, slot); - - weaponHandle = PrepareItemHandle("tf_weapon_wrench", 589, 0, 0, "93 ; 0.5 ; 732 ; 0.5"); - int entity = TF2Items_GiveNamedItem(client, weaponHandle); - delete weaponHandle; - weaponHandle = null; - EquipPlayerWeapon(client, entity); - } - } - } - delete weaponHandle; - } - //Force them to take their melee wep, it prevents the civilian bug. - ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); - - // Remove all hats. - if (IsClientInGhostMode(client)) - { - int ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - ent = -1; - while ((ent = FindEntityByClassname(ent, "tf_wearable_campaign_item")) != -1) - { - if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) - { - RemoveEntity(ent); - } - } - } - - float healthFromPack = 1.0; - if (!IsClassConfigsValid()) - { - if (!g_PlayerEliminated[client] && !SF_IsBoxingMap()) - { - if (g_PlayerHasRegenerationItem[client]) - { - healthFromPack = 0.40; - } - if (TF2_GetPlayerClass(client) == TFClass_Medic) - { - healthFromPack = 0.0; - } - } - } - else - { - if (!g_PlayerEliminated[client] && !SF_IsBoxingMap()) - { - healthFromPack = g_ClassHealthPickupMultiplier[classToInt]; - if (g_PlayerHasRegenerationItem[client]) - { - healthFromPack -= 0.6; - } - if (healthFromPack <= 0.0) - { - healthFromPack = 0.0; - } - } - } - - TF2Attrib_SetByDefIndex(client, 109, healthFromPack); - - #if defined DEBUG - int weaponEnt = INVALID_ENT_REFERENCE; - - for (int i = 0; i <= 5; i++) - { - weaponEnt = GetPlayerWeaponSlot(client, i); - if (!IsValidEdict(weaponEnt)) - { - continue; - } - - newWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - } - - if (g_DebugDetailConVar.IntValue > 0) - { - for (int i = 0; i <= 5; i++) - { - DebugMessage("-> slot %d: %d (old: %d)", i, newWeaponItemIndexes[i], oldWeaponItemIndexes[i]); - } - - DebugMessage("END Timer_ClientPostWeapons(%d) -> remove = %d, restrict = %d", client, removeWeapons, restrictWeapons); - } - #endif - return Plugin_Stop; -} - -public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int itemDefinitionIndex, Handle &itemHandle) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - /*if (itemDefinitionIndex == 649) - { - RequestFrame(Frame_ReplaceSpyCicle, client); - return Plugin_Handled; - }*/ - switch (itemDefinitionIndex) - { - case 642: - { - Handle itemOverride = PrepareItemHandle("tf_wearable", 642, 0, 0, "376 ; 1.0 ; 377 ; 0.2 ; 57 ; 2 ; 412 ; 1.10"); - - if (itemOverride != null) - { - itemHandle = itemOverride; - - return Plugin_Changed; - } - delete itemOverride; - itemOverride = null; - } - } - - return Plugin_Continue; -} - -void Frame_ClientHealArrow(int client) -{ - if (IsClientInGame(client) && IsClientInPvP(client)) - { - int entity = -1; - while ((entity = FindEntityByClassname(entity, "tf_projectile_healing_bolt")) != -1) - { - int throwerOffset = FindDataMapInfo(entity, "m_hThrower"); - int ownerEntity = GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity"); - if (ownerEntity != client && throwerOffset != -1) - { - ownerEntity = GetEntDataEnt2(entity, throwerOffset); - } - if (ownerEntity == client) - { - SetEntProp(entity, Prop_Data, "m_iInitialTeamNum", GetClientTeam(client)); - SetEntProp(entity, Prop_Send, "m_iTeamNum", GetClientTeam(client)); - } - } - } -} - -bool IsRegenWeapon(int weaponEnt) -{ - Address attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 1003); - if (attribRegen != Address_Null) - { - return true; - } - attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 490); - if (attribRegen != Address_Null) - { - return true; - } - attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 190); - if (attribRegen != Address_Null) - { - return true; - } - attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 130); - if (attribRegen != Address_Null) - { - return true; - } - attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 57); - if (attribRegen != Address_Null) - { - return true; - } - attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 220); - if (attribRegen != Address_Null) - { - return true; - } - return false; -} - -bool IsWeaponRestricted(TFClassType class,int itemDefInt) -{ - if (g_RestrictedWeaponsConfig == null) - { - return false; - } - - bool returnBool = false; - - char itemDef[32]; - FormatEx(itemDef, sizeof(itemDef), "%d", itemDefInt); - - g_RestrictedWeaponsConfig.Rewind(); - bool proxyBoss = false; - for (int i = 0; i < MAX_BOSSES; i++) - { - SF2NPC_BaseNPC Npc = view_as(i); - if (!Npc.IsValid()) - { - continue; - } - if (Npc.Flags & SFF_PROXIES) - { - proxyBoss = true; - break; - } - } - if (g_RestrictedWeaponsConfig.JumpToKey("all")) - { - //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef)); - //view_as bool value turn to 2 into a true value. - if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) - { - returnBool=true; - } - if (proxyBoss && !returnBool) - { - int proxyRestricted = g_RestrictedWeaponsConfig.GetNum(itemDef, 0); - if (proxyRestricted==2) - { - returnBool = true; - } - } - } - - bool bFoundSection = false; - g_RestrictedWeaponsConfig.Rewind(); - - switch (class) - { - case TFClass_Scout: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("scout"); - } - case TFClass_Soldier: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("soldier"); - } - case TFClass_Sniper: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("sniper"); - } - case TFClass_DemoMan: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("demoman"); - } - case TFClass_Heavy: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("heavy"); - - if (!bFoundSection) - { - g_RestrictedWeaponsConfig.Rewind(); - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("heavyweapons"); - } - } - case TFClass_Medic: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("medic"); - } - case TFClass_Spy: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("spy"); - } - case TFClass_Pyro: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("pyro"); - } - case TFClass_Engineer: - { - bFoundSection = g_RestrictedWeaponsConfig.JumpToKey("engineer"); - } - } - - if (bFoundSection) - { - //returnBool = !!(g_RestrictedWeaponsConfig.GetNum(itemDef, returnBool)); - if (g_RestrictedWeaponsConfig.GetNum(itemDef)==1) - { - returnBool = true; - } - if (proxyBoss && !returnBool) - { - int proxyRestricted = g_RestrictedWeaponsConfig.GetNum(itemDef, 0); - if (proxyRestricted==2) - { - returnBool = true; - } - } - } - - return returnBool; -} diff --git a/addons/sourcemod/scripting/sf2/functionclients/clients_think.sp b/addons/sourcemod/scripting/sf2/functionclients/clients_think.sp deleted file mode 100644 index 430e1777..00000000 --- a/addons/sourcemod/scripting/sf2/functionclients/clients_think.sp +++ /dev/null @@ -1,1718 +0,0 @@ -#if defined _sf2_clients_think_included - #endinput -#endif -#define _sf2_clients_think_included - -#pragma semicolon 1 - -void Hook_ClientPreThink(int client) -{ - if (!g_Enabled) - { - return; - } - - ClientProcessFlashlightAngles(client); - ClientProcessInteractiveGlow(client); - ClientProcessStaticShake(client); - ClientProcessViewAngles(client); - - if (IsClientInDeathCam(client) && !IsClientInGhostMode(client)) - { - int ent = EntRefToEntIndex(g_PlayerDeathCamEnt[client]); - if (ent && ent != INVALID_ENT_REFERENCE && g_CameraInDeathCamAdvanced[ent]) - { - float camPos[3], camAngs[3]; - GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", camAngs); - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", camPos); - - camPos[0] -= g_CameraPlayerOffsetBackward[ent]; - camPos[2] -= g_CameraPlayerOffsetDownward[ent]; - - CBaseEntity player = CBaseEntity(client); - - player.SetLocalOrigin(camPos); - player.SetLocalAngles(camAngs); - } - } - - if (IsClientInGhostMode(client)) - { - SetEntityFlags(client,GetEntityFlags(client)^FL_EDICT_ALWAYS); - SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 520.0); - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - if (IsClientInKart(client) || !TF2_IsPlayerInCondition(client, TFCond_Stealthed)) - { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); - TF2_RemoveCondition(client, TFCond_Taunting); - ClientHandleGhostMode(client, true); - } - TF2_RemoveCondition(client, TFCond_Taunting); - } - else if (!g_PlayerEliminated[client] || g_PlayerProxy[client]) - { - if (!IsRoundEnding() && !IsRoundInWarmup() && !DidClientEscape(client)) - { - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - - int roundState = view_as(GameRules_GetRoundState()); - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - - if (!g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Red) - { - if (TF2_IsPlayerInCondition(client,TFCond_Disguised)) - { - TF2_RemoveCondition(client,TFCond_Disguised); - } - - if (TF2_IsPlayerInCondition(client,TFCond_Taunting) && g_PlayerTrapped[client]) - { - TF2_RemoveCondition(client,TFCond_Taunting); - } - - if (TF2_IsPlayerInCondition(client,TFCond_Taunting) && class == TFClass_Soldier) - { - int weaponEnt = GetPlayerWeaponSlot(client, TFWeaponSlot_Melee); - if (weaponEnt && weaponEnt != INVALID_ENT_REFERENCE) - { - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 775 || itemDefInt == 128) - { - TF2_RemoveCondition(client,TFCond_Taunting); //Stop suiciding... - } - } - } - - if (roundState == 4) - { - bool inDanger = false; - - if (!inDanger) - { - int state; - int bossTarget; - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (NPCGetType(i) == SF2BossType_Chaser) - { - bossTarget = EntRefToEntIndex(g_SlenderTarget[i]); - state = g_SlenderState[i]; - - if ((state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) && - ((bossTarget && bossTarget != INVALID_ENT_REFERENCE && (bossTarget == client || ClientGetDistanceFromEntity(client, bossTarget) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) - { - inDanger = true; - ClientSetScareBoostEndTime(client, GetGameTime() + 5.0); - - // Induce client stress levels. - float unComfortZoneDist = 512.0; - float stressScalar = ((SquareFloat(unComfortZoneDist) / NPCGetDistanceFromEntity(i, client))); - ClientAddStress(client, 0.025 * stressScalar); - - break; - } - } - } - } - - if (g_PlayerStaticAmount[client] > 0.4) - { - inDanger = true; - } - if (GetGameTime() < ClientGetScareBoostEndTime(client)) - { - inDanger = true; - } - - if (!inDanger) - { - int state; - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if (NPCGetType(i) == SF2BossType_Chaser) - { - if (state == STATE_ALERT) - { - if (PlayerCanSeeSlender(client, i)) - { - inDanger = true; - ClientSetScareBoostEndTime(client, GetGameTime() + 5.0); - } - } - } - } - } - - if (!inDanger) - { - float curTime = GetGameTime(); - float scareSprintDuration = 3.0; - if (!IsClassConfigsValid()) - { - if (class == TFClass_DemoMan) - { - scareSprintDuration *= 1.667; - } - } - else - { - scareSprintDuration *= g_ClassScareSprintDurationMultipler[classToInt]; - } - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - if ((curTime - g_PlayerScareLastTime[client][i]) <= scareSprintDuration) - { - inDanger = true; - break; - } - } - } - - float walkSpeed, sprintSpeed; - if (!IsClassConfigsValid()) - { - walkSpeed = ClientGetDefaultWalkSpeed(client); - sprintSpeed = ClientGetDefaultSprintSpeed(client); - } - else - { - walkSpeed = g_ClassWalkSpeed[classToInt]; - sprintSpeed = g_ClassRunSpeed[classToInt]; - } - - // Check for weapon speed changes. - int weaponEnt = INVALID_ENT_REFERENCE; - - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - switch (itemDefInt) - { - case 172: // Scotsman's Skullcutter - { - if (GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - sprintSpeed -= (sprintSpeed * 0.05); - walkSpeed -= (walkSpeed * 0.05); - } - } - case 214: // The Powerjack - { - if (GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - sprintSpeed += (sprintSpeed * 0.03); - } - } - case 239: // Gloves of Running Urgently - { - if (GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - sprintSpeed += (sprintSpeed * 0.075); - } - } - case 775: // Escape Plan - { - float health = float(GetEntProp(client, Prop_Send, "m_iHealth")); - float maxHealth = float(SDKCall(g_SDKGetMaxHealth, client)); - float percentage = health / maxHealth; - - if (percentage < 0.805 && percentage >= 0.605) - { - walkSpeed += (walkSpeed * 0.05); - sprintSpeed += (sprintSpeed * 0.05); - } - else if (percentage < 0.605 && percentage >= 0.405) - { - walkSpeed += (walkSpeed * 0.1); - sprintSpeed += (sprintSpeed * 0.1); - } - else if (percentage < 0.405 && percentage >= 0.205) - { - walkSpeed += (walkSpeed * 0.15); - sprintSpeed += (sprintSpeed * 0.15); - } - else if (percentage < 0.205) - { - walkSpeed += (walkSpeed * 0.2); - sprintSpeed += (sprintSpeed * 0.2); - } - } - } - } - - // Speed buff - if (!SF_IsSlaughterRunMap()) - { - if (TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly)) - { - walkSpeed += (walkSpeed * 0.115); - sprintSpeed += (sprintSpeed * 0.165); - } - } - else - { - if (TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly)) - { - walkSpeed += (walkSpeed * 0.105); - sprintSpeed += (sprintSpeed * 0.14); - } - } - - if (inDanger) - { - if (!IsClassConfigsValid()) - { - if (class != TFClass_Spy && class != TFClass_Pyro) - { - walkSpeed *= 1.34; - sprintSpeed *= 1.34; - } - else - { - if (class == TFClass_Spy) - { - walkSpeed *= 1.28; - sprintSpeed *= 1.28; - } - else - { - weaponEnt = INVALID_ENT_REFERENCE; - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - walkSpeed *= 1.32; - sprintSpeed *= 1.32; - } - else - { - walkSpeed *= 1.34; - sprintSpeed *= 1.34; - } - } - } - } - } - else - { - float multiplier = g_ClassDangerSpeedMultipler[classToInt]; - if (class == TFClass_Pyro) - { - weaponEnt = INVALID_ENT_REFERENCE; - for (int slot = 0; slot <= 5; slot++) - { - weaponEnt = GetPlayerWeaponSlot(client, slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon") == weaponEnt) - { - multiplier -= 0.02; - } - } - } - walkSpeed *= multiplier; - sprintSpeed *= multiplier; - } - - if (!g_PlayerHints[client][PlayerHint_Sprint]) - { - ClientShowHint(client, PlayerHint_Sprint); - } - } - - float sprintSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.425); - float walkSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.3); - if (g_PlayerSprintPoints[client] > 8) - { - sprintSpeedSubtract -= sprintSpeedSubtract * (g_PlayerSprintPoints[client] != 0 ? (float(g_PlayerSprintPoints[client]) / 100.0) : 0.0); - sprintSpeed -= sprintSpeedSubtract; - } - else - { - sprintSpeedSubtract += 125; - sprintSpeed -= sprintSpeedSubtract; - walkSpeedSubtract += 25; - walkSpeed -= walkSpeedSubtract; - } - - if (IsClientSprinting(client)) - { - if (!g_PlayerTrapped[client]) - { - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - if (!TF2_IsPlayerInCondition(client, TFCond_Charging)) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", sprintSpeed); - } - else - { - if (SF_IsBoxingMap() || SF_IsRaidMap()) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", sprintSpeed*2.5); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", sprintSpeed/2.5); - } - } - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 520.0); - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed-170.0); - } - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 0.1); - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", 0.1); - } - } - else - { - if (!g_PlayerTrapped[client]) - { - if (!TF2_IsPlayerInCondition(client, TFCond_Charging)) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", walkSpeed); - } - else - { - if (SF_IsBoxingMap() || SF_IsRaidMap()) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", walkSpeed*2.5); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", walkSpeed/2.5); - } - } - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", walkSpeed-20.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 0.01); - SetEntPropFloat(client, Prop_Send, "m_flCurrentTauntMoveSpeed", 0.01); - } - } - - if (ClientCanBreath(client) && !g_PlayerBreath[client]) - { - ClientStartBreathing(client); - } - - if (g_PlayerTrapped[client]) - { - TF2Attrib_SetByName(client, "increased jump height", 0.0); - } - else - { - TF2Attrib_SetByName(client, "increased jump height", 1.0); - } - } - } - else if (g_PlayerProxy[client] && GetClientTeam(client) == TFTeam_Blue) - { - bool speedup = TF2_IsPlayerInCondition(client, TFCond_SpeedBuffAlly); - - switch (class) - { - case TFClass_Scout: - { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 390.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } - } - case TFClass_Medic: - { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } - } - case TFClass_Spy: - { - if (speedup || g_InProxySurvivalRageMode) - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 370.0); - } - else - { - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 300.0); - } - } - default: - { - if (g_InProxySurvivalRageMode) - { - float rageSpeed = ClientGetDefaultSprintSpeed(client)+30.0; - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", rageSpeed); - } - } - } - } - } - } - if (g_PlayerEliminated[client] && IsClientInPvP(client)) - { - SetEntPropFloat(client, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(client, Prop_Send, "m_flHandScale", 1.0); - if (IsClientInKart(client)) - { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); - } - } - if (IsRoundInWarmup() || (IsRoundInIntro() && !g_PlayerEliminated[client]) || IsRoundEnding()) //I told you, stop breaking my plugin - { - if (IsClientInKart(client)) - { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); - } - } - - // Calculate player stress levels. - if (GetGameTime() >= g_PlayerStressNextUpdateTime[client]) - { - //float flPagePercent = g_PageMax != 0 ? float(g_PageCount) / float(g_PageMax) : 0.0; - //float flPageCountPercent = g_PageMax != 0? float(g_PlayerPageCount[client]) / float(g_PageMax) : 0.0; - - g_PlayerStressNextUpdateTime[client] = GetGameTime() + 0.33; - ClientAddStress(client, -0.01); - - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_PLAYER_STRESS, 1, "g_PlayerStressAmount[%d]: %0.1f", client, g_PlayerStressAmount[client]); - #endif - } - - // Process screen shake, if enabled. - if (g_IsPlayerShakeEnabled) - { - bool doShake = false; - - if (IsPlayerAlive(client)) - { - int staticMaster = NPCGetFromUniqueID(g_PlayerStaticMaster[client]); - if (staticMaster != -1 && NPCGetFlags(staticMaster) & SFF_HASVIEWSHAKE) - { - doShake = true; - } - } - - if (doShake) - { - float percent = g_PlayerStaticAmount[client]; - - float amplitudeMax = g_PlayerShakeAmplitudeMaxConVar.FloatValue; - float amplitude = amplitudeMax * percent; - - float frequencyMax = g_PlayerShakeFrequencyMaxConVar.FloatValue; - float frequency = frequencyMax * percent; - - UTIL_ClientScreenShake(client, amplitude, 0.5, frequency); - } - } - - if (g_LastVisibilityProcess[client] + 0.30 >= GetGameTime()) - { - return; - } - - /*if (!g_PlayerEliminated[client]) - { - CNavArea targetArea = SDK_GetLastKnownArea(client);//SDK_GetLastKnownArea(client) =/= g_lastNavArea[client], SDK_GetLastKnownArea() retrives the nav area stored by the server - if (targetArea != INVALID_NAV_AREA) - { - ClientNavAreaUpdate(client, g_lastNavArea[client], targetArea); - g_lastNavArea[client] = targetArea; - } - }*/ - - g_LastVisibilityProcess[client] = GetGameTime(); - - ClientProcessVisibility(client); -} - -Action Hook_ClientOnTakeDamage(int victim,int &attacker,int &inflictor, float &damage,int &damagetype,int &weapon, float damageForce[3], float damagePosition[3],int damagecustom) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - Action action = Plugin_Continue; - - float damage2 = damage; - Call_StartForward(g_OnClientTakeDamageFwd); - Call_PushCell(victim); - Call_PushCellRef(attacker); - Call_PushCellRef(inflictor); - Call_PushFloatRef(damage2); - Call_Finish(action); - - if (action == Plugin_Changed) - { - damage = damage2; - return Plugin_Changed; - } - - TFClassType class = TF2_GetPlayerClass(victim); - int classToInt = view_as(class); - - if (IsRoundInWarmup() && IsValidClient(attacker)) - { - float modelScale = GetEntPropFloat(attacker, Prop_Send, "m_flModelScale"); - float headScale = GetEntPropFloat(attacker, Prop_Send, "m_flHeadScale"); - float torsoScale = GetEntPropFloat(attacker, Prop_Send, "m_flTorsoScale"); - float handScale = GetEntPropFloat(attacker, Prop_Send, "m_flHandScale"); - if (modelScale < 1.0 || modelScale > 1.0 || headScale < 1.0 || headScale > 1.0 || torsoScale < 1.0 || torsoScale > 1.0 || handScale < 1.0 || handScale > 1.0) - { - damage = 0.0; //So how does it feel? - return Plugin_Changed; - } - } - - if (IsClientInKart(victim) && (attacker == -1 || inflictor == -1)) - { - damage = 0.0; - return Plugin_Changed; - } - - char inflictorClass[32]; - if (inflictor >= 0) - { - GetEdictClassname(inflictor, inflictorClass, sizeof(inflictorClass)); - } - - if (IsValidClient(attacker) && IsValidClient(victim) && g_PlayerProxy[attacker] && GetClientTeam(victim) == TFTeam_Red && TF2_IsPlayerInCondition(victim, TFCond_Gas)) - { - TF2_IgnitePlayer(victim, victim); - TF2_RemoveCondition(victim, TFCond_Gas); - } - - if (IsValidClient(attacker) && IsValidClient(victim) && IsClientInPvP(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) - { - damage = 0.0; - return Plugin_Changed; - } - - if (IsValidClient(attacker) && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && g_PlayerTrapped[victim]) - { - if (!g_PlayerEliminated[attacker] && !g_PlayerEliminated[victim]) - { - if (damagetype & 0x80) // 0x80 == melee damage - { - g_PlayerTrapped[victim] = false; - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); - } - } - } - - if (IsValidClient(attacker) && !g_PlayerEliminated[attacker] && !DidClientEscape(attacker) && class == TFClass_Soldier && !(GetEntityFlags(attacker) & FL_ONGROUND)) - { - int weaponEnt = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(weaponEnt)) - { - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - float zVelocity[3]; - GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); - if (itemDefInt == 416 && zVelocity[2] < 0.0 && weaponEnt == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) //A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. - { - damagetype |= DMG_ACID; - } - } - } - - if (IsEntityAProjectile(inflictor)) - { - int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); - if (npcIndex != -1) - { - bool attackEliminated = !!(NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS); - if (!attackEliminated && (GetClientTeam(victim) == TFTeam_Blue) && IsValidClient(victim) ) - { - damage = 0.0; - return Plugin_Changed; - } - } - } - - // Prevent telefrags - if ((damagetype & DMG_CRUSH) && damage > 500.0) - { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(attacker)); - if (Npc != SF2_INVALID_NPC && IsValidClient(victim)) - { - damage = 0.0; - Npc.UnSpawn(); - return Plugin_Changed; - } - } - - if (attacker != victim && IsValidClient(attacker)) - { - if (!IsRoundEnding()) - { - if (IsRoundInWarmup() || (IsClientInPvP(victim) && IsClientInPvP(attacker))) - { - if (attacker == inflictor) - { - if (IsValidEdict(weapon)) - { - char weaponClass[64]; - GetEdictClassname(weapon, weaponClass, sizeof(weaponClass)); - - // Backstab check! - if ((strcmp(weaponClass, "tf_weapon_knife") == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy") == 0)) && - (damagecustom != TF_CUSTOM_TAUNT_FENCING)) - { - float myPos[3], hisPos[3], myDirection[3]; - GetClientAbsOrigin(victim, myPos); - GetClientAbsOrigin(attacker, hisPos); - GetClientEyeAngles(victim, myDirection); - GetAngleVectors(myDirection, myDirection, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(myDirection, myDirection); - ScaleVector(myDirection, 32.0); - AddVectors(myDirection, myPos, myDirection); - - float p[3], s[3]; - MakeVectorFromPoints(myPos, hisPos, p); - MakeVectorFromPoints(myPos, myDirection, s); - if (GetVectorDotProduct(p, s) <= 0.0)//We can backstab him m8 - { - if (GetClientTeam(victim) == GetClientTeam(attacker) && class == TFClass_Sniper) - { - //look if the player has a razorback - int wearableEnt = INVALID_ENT_REFERENCE; - while ((wearableEnt = FindEntityByClassname(wearableEnt, "tf_wearable")) != -1) - { - if (GetEntPropEnt(wearableEnt, Prop_Send, "m_hOwnerEntity") == victim && GetEntProp(wearableEnt, Prop_Send, "m_iItemDefinitionIndex") == 57) - { - RemoveEntity(wearableEnt); - damage = 0.0; - EmitSoundToClient(victim, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - - SetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); - int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); - if (vm > MaxClients) - { - int meleeIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); - int anim = 41; - switch (meleeIndex) - { - case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: - { - anim = 15; - } - case 638: - { - anim = 31; - } - } - SetEntProp(vm, Prop_Send, "m_nSequence", anim); - } - return Plugin_Changed; - } - } - } - if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. - { - damage = 120.0; - if (damagetype & DMG_ACID) - { - damage = 120.0; - } - } - - if (g_WeaponCriticalsConVar != null && g_WeaponCriticalsConVar.BoolValue) - { - damagetype |= DMG_ACID; - } - - if (!IsClientCritUbercharged(victim)) - { - if (GetClientTeam(victim) == GetClientTeam(attacker)) - { - int pistol = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); - if (pistol > MaxClients && GetEntProp(pistol, Prop_Send, "m_iItemDefinitionIndex") == 525) //Give one crit fort the backstab - { - int crits = GetEntProp(attacker, Prop_Send, "m_iRevengeCrits"); - crits++; - SetEntProp(attacker, Prop_Send, "m_iRevengeCrits", crits); - } - } - if (GetEntProp(victim, Prop_Send, "m_iHealth") <= 120) - { - g_PlayerBackStabbed[victim] = true; - } - else - { - g_PlayerBackStabbed[victim] = false; - } - } - return Plugin_Changed; - } - } - } - } - } - else if (g_PlayerProxy[victim] || g_PlayerProxy[attacker]) - { - if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) - { - damage = 0.0; - return Plugin_Changed; - } - - if (attacker == victim)//Don't allow proxy to self regenerate control. - { - return Plugin_Continue; - } - - if (g_PlayerProxy[attacker]) - { - int maxHealth = SDKCall(g_SDKGetMaxHealth, victim); - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[attacker]); - if (master != -1) - { - int difficulty = GetLocalGlobalDifficulty(master); - if (damagecustom == TF_CUSTOM_TAUNT_GRAND_SLAM || - damagecustom == TF_CUSTOM_TAUNT_FENCING || - damagecustom == TF_CUSTOM_TAUNT_ARROW_STAB || - damagecustom == TF_CUSTOM_TAUNT_GRENADE || - damagecustom == TF_CUSTOM_TAUNT_BARBARIAN_SWING || - damagecustom == TF_CUSTOM_TAUNT_ENGINEER_ARM || - damagecustom == TF_CUSTOM_TAUNT_ARMAGEDDON) - { - if (damage >= float(maxHealth)) - { - damage = float(maxHealth) * 0.5; - } - else - { - damage = 0.0; - } - } - else if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. - { - damage = float(maxHealth) * g_SlenderProxyDamageVsBackstab[master][difficulty]; - if (damagetype & DMG_ACID) - { - damage /= 2.0; - } - } - - g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitEnemy[master][difficulty]; - if (g_PlayerProxyControl[attacker] > 100) - { - g_PlayerProxyControl[attacker] = 100; - } - - float originalPercentage = g_SlenderProxyDamageVsEnemy[master][difficulty]; - float additionPercentage = 0.15; - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - damage *= (originalPercentage + additionPercentage); - } - else - { - damage *= originalPercentage; - } - } - else - { - damage *= originalPercentage + g_ClassProxyDamageVulnerability[classToInt]; - } - } - return Plugin_Changed; - } - else if (g_PlayerProxy[victim]) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[victim]); - if (master != -1) - { - NPCGetProfile(master, profile, sizeof(profile)); - int difficulty = GetLocalGlobalDifficulty(master); - g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitByEnemy[master][difficulty]; - if (g_PlayerProxyControl[attacker] > 100) - { - g_PlayerProxyControl[attacker] = 100; - } - - damage *= g_SlenderProxyDamageVsSelf[master][difficulty]; - } - if (TF2_IsPlayerInCondition(victim, view_as(87))) - { - damage = 0.0; - } - if (damage * (damagetype & DMG_CRIT ? 3.0 : 1.0) >= float(GetClientHealth(victim)) && !TF2_IsPlayerInCondition(victim, view_as(87)))//The proxy is about to die - { - char buffer[PLATFORM_MAX_PATH]; - int classIndex = view_as(TF2_GetPlayerClass(victim)); - ArrayList deathAnims = GetBossProfileProxyDeathAnimations(profile); - if (deathAnims != null) - { - deathAnims.GetString(classIndex, buffer, sizeof(buffer)); - if (buffer[0] == '\0') - { - deathAnims.GetString(0, buffer, sizeof(buffer)); - } - if (buffer[0] != '\0') - { - g_ClientMaxFrameDeathAnim[victim]=GetBossProfileProxyDeathAnimFrames(profile, classIndex); - if (g_ClientMaxFrameDeathAnim[victim] == 0) - { - g_ClientMaxFrameDeathAnim[victim] = GetBossProfileProxyDeathAnimFrames(profile, 0); - } - if (g_ClientMaxFrameDeathAnim[victim] > 0) - { - // Cancel out any other taunts. - if (TF2_IsPlayerInCondition(victim, TFCond_Taunting)) - { - TF2_RemoveCondition(victim, TFCond_Taunting); - } - //The model has a death anim play it. - SDK_PlaySpecificSequence(victim,buffer); - g_ClientFrame[victim] = 0; - RequestFrame(ProxyDeathAnimation,victim); - TF2_AddCondition(victim, view_as(87), 5.0); - //Prevent death, and show the damage to the attacker. - TF2_AddCondition(victim, view_as(70), 0.5); - return Plugin_Changed; - } - } - } - - //the player has no death anim leave him die. - } - return Plugin_Changed; - } - } - else - { - damage = 0.0; - return Plugin_Changed; - } - } - else - { - if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) - { - damage = 0.0; - return Plugin_Changed; - } - } - - if (IsClientInGhostMode(victim)) - { - damage = 0.0; - return Plugin_Changed; - } - } - - return Plugin_Continue; -} - -Action Timer_ClientSprinting(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerSprintTimer[client]) - { - return Plugin_Stop; - } - - if (!IsClientSprinting(client)) - { - return Plugin_Stop; - } - - if (g_PlayerSprintPoints[client] <= 0) - { - ClientStopSprint(client); - g_PlayerSprintPoints[client] = 0; - return Plugin_Stop; - } - - if (IsClientReallySprinting(client)) - { - int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; - if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0) - { - g_PlayerSprintPoints[client]--; - } - } - - ClientSprintTimer(client); - - return Plugin_Stop; -} - -void Hook_ClientSprintingPreThink(int client) -{ - if (!IsClientReallySprinting(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - return; - } - - int fov = GetEntData(client, g_PlayerDefaultFOVOffset); - - int targetFov = g_PlayerDesiredFOV[client] + 10; - - if (fov < targetFov) - { - int diff = RoundFloat(FloatAbs(float(fov - targetFov))); - if (diff >= 1) - { - ClientSetFOV(client, fov + 1); - } - else - { - ClientSetFOV(client, targetFov); - } - } - else if (fov >= targetFov) - { - ClientSetFOV(client, targetFov); - //SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - } -} - -void Hook_ClientRechargeSprintPreThink(int client) -{ - if (IsClientReallySprinting(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - return; - } - - int fov = GetEntData(client, g_PlayerDefaultFOVOffset); - if (fov > g_PlayerDesiredFOV[client]) - { - int diff = RoundFloat(FloatAbs(float(fov - g_PlayerDesiredFOV[client]))); - if (diff >= 1) - { - ClientSetFOV(client, fov - 1); - } - else - { - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } - } - else if (fov <= g_PlayerDesiredFOV[client]) - { - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } -} - -Action Timer_ClientRechargeSprint(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - float velSpeed[3]; - GetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", velSpeed); - float speed = GetVectorLength(velSpeed, true); - - if (timer != g_PlayerSprintTimer[client]) - { - return Plugin_Stop; - } - - if (IsClientSprinting(client)) - { - g_PlayerSprintTimer[client] = null; - return Plugin_Stop; - } - - if (g_PlayerSprintPoints[client] >= 100) - { - g_PlayerSprintPoints[client] = 100; - g_PlayerSprintTimer[client] = null; - return Plugin_Stop; - } - if ((!GetEntProp(client, Prop_Send, "m_bDucking") && !GetEntProp(client, Prop_Send, "m_bDucked")) || (GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked") && IsClientReallySprinting(client) || speed > 0.0)) - { - g_PlayerSprintPoints[client]++; - } - else if ((GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked")) && !IsClientReallySprinting(client) && speed == 0.0) - { - if (!SF_SpecialRound(SPECIALROUND_COFFEE)) - { - g_PlayerSprintPoints[client] += 2; - } - else - { - g_PlayerSprintPoints[client] += 1; - } - } - ClientSprintTimer(client, true); - return Plugin_Stop; -} - -void ClientOnButtonPress(int client,int button) -{ - switch (button) - { - case IN_ATTACK2: - { - if (IsPlayerAlive(client)) - { - if (!IsRoundInWarmup() && - !IsRoundInIntro() && - !IsRoundEnding() && - !DidClientEscape(client)) - { - if (GetGameTime() >= ClientGetFlashlightNextInputTime(client)) - { - ClientHandleFlashlight(client); - } - } - } - } - case IN_ATTACK3: - { - ClientHandleSprint(client, true); - } - case IN_RELOAD: - { - if (IsPlayerAlive(client)) - { - if (!g_PlayerEliminated[client]) - { - if (!IsRoundEnding() && - !IsRoundInWarmup() && - !IsRoundInIntro() && - !DidClientEscape(client)) - { - g_PlayerHoldingBlink[client] = true; - ClientBlink(client); - } - } - } - } - case IN_JUMP: - { - if (IsPlayerAlive(client) && !(GetEntityFlags(client) & FL_FROZEN)) - { - if (!(GetEntProp(client, Prop_Send, "m_bDucked")) && - (GetEntityFlags(client) & FL_ONGROUND) && - GetEntProp(client, Prop_Send, "m_nWaterLevel") < 2) - { - ClientOnJump(client); - } - } - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 0.0001); - } - else - { - SetEntityGravity(client, 1.0); - } - } - case IN_DUCK: - { - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 4.0); - } - else - { - SetEntityGravity(client, 1.0); - } - } - } -} - -void ClientOnButtonRelease(int client,int button) -{ - switch (button) - { - case IN_ATTACK3: - { - ClientHandleSprint(client, false); - } - case IN_DUCK: - { - ClientEndPeeking(client); - - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 0.5); - } - else - { - SetEntityGravity(client, 1.0); - } - } - case IN_JUMP: - { - if (IsClientInGhostMode(client)) - { - SetEntityGravity(client, 0.5); - } - else - { - SetEntityGravity(client, 1.0); - } - } - case IN_RELOAD: - { - if (IsPlayerAlive(client)) - { - if (!g_PlayerEliminated[client]) - { - if (!IsRoundEnding() && - !IsRoundInWarmup() && - !IsRoundInIntro() && - !DidClientEscape(client)) - { - g_PlayerHoldingBlink[client] = false; - } - } - } - } - } -} - -void ClientOnJump(int client) -{ - if (!g_PlayerEliminated[client]) - { - if (!IsRoundEnding() && !IsRoundInWarmup() && !DidClientEscape(client)) - { - int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; - if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0 && !g_PlayerTrapped[client]) - { - if (g_PlayerSprintPoints[client] >= 2) - { - TFClassType classType = TF2_GetPlayerClass(client); - int classToInt = view_as(classType); - if (!IsClassConfigsValid()) - { - if (classType != TFClass_Soldier || g_PlayerSprintPoints[client] <= 10 || IsClientSprinting(client)) - { - g_PlayerSprintPoints[client] -= 7; - } - } - else - { - int sprintPointsLoss = g_ClassSprintPointLossJumping[classToInt]; - if (g_PlayerSprintPoints[client] <= 10 || IsClientSprinting(client)) - { - sprintPointsLoss = 7; - } - g_PlayerSprintPoints[client] -= sprintPointsLoss; - } - if (g_PlayerSprintPoints[client] <= 0) - { - g_PlayerSprintPoints[client] = 0; - } - } - } - - if (!IsClientSprinting(client)) - { - if (g_PlayerSprintTimer[client] == null) - { - // If the player hasn't sprinted recently, force us to regenerate the stamina. - ClientSprintTimer(client, true); - } - } - if (g_PlayerTrapped[client]) - { - g_PlayerTrapCount[client] -= 1; - } - if (g_PlayerTrapped[client] && g_PlayerTrapCount[client] <= 1) - { - g_PlayerTrapped[client] = false; - g_PlayerTrapCount[client] = 0; - } - } - } -} - -Action Timer_GhostModeConnectionCheck(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerGhostModeConnectionCheckTimer[client]) - { - return Plugin_Stop; - } - - if (!IsFakeClient(client) && IsClientTimingOut(client)) - { - float bootTime = g_PlayerGhostModeConnectionBootTime[client]; - bool checkBool = g_GhostModeConnectionConVar.BoolValue; - if (bootTime < 0.0 && !checkBool) - { - bootTime = GetGameTime() + g_GhostModeConnectionToleranceConVar.FloatValue; - g_PlayerGhostModeConnectionBootTime[client] = bootTime; - g_PlayerGhostModeConnectionTimeOutTime[client] = GetGameTime(); - } - - if (GetGameTime() >= bootTime || checkBool) - { - ClientSetGhostModeState(client, false); - TF2_RespawnPlayer(client); - - char authString[128]; - GetClientAuthId(client,AuthId_Engine, authString, sizeof(authString)); - - LogSF2Message("Removed %N (%s) from ghost mode due to timing out for %f seconds", client, authString, g_GhostModeConnectionToleranceConVar.FloatValue); - - float timeOutTime = g_PlayerGhostModeConnectionTimeOutTime[client]; - CPrintToChat(client, "\x08FF4040FF%T", "SF2 Ghost Mode Bad Connection", client, RoundFloat(bootTime - timeOutTime)); - - return Plugin_Stop; - } - } - else - { - // Player regained connection; reset. - g_PlayerGhostModeConnectionBootTime[client] = -1.0; - } - - return Plugin_Continue; -} - -Action Timer_ClientCheckCamp(Handle timer, any userid) -{ - if (IsRoundInWarmup()) - { - return Plugin_Stop; - } - - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerCampingTimer[client]) - { - return Plugin_Stop; - } - - if (IsRoundEnding() || !IsPlayerAlive(client) || g_PlayerEliminated[client] || DidClientEscape(client)) - { - return Plugin_Stop; - } - - if (!g_IsPlayerCampingFirstTime[client]) - { - bool isCamping = false; - float pos[3], maxs[3], mins[3]; - GetClientAbsOrigin(client, pos); - GetEntPropVector(client, Prop_Send, "m_vecMins", mins); - GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); - - // Only do something if the player is NOT stuck. - float distFromLastPosition = GetVectorSquareMagnitude(g_PlayerCampingLastPosition[client], pos); - float distFromClosestBoss = 9999999.0; - int closestBoss = -1; - - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - - int slender = NPCGetEntIndex(i); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - continue; - } - - float slenderPos[3]; - SlenderGetAbsOrigin(i, slenderPos); - - float flDist = GetVectorSquareMagnitude(slenderPos, pos); - if (flDist < distFromClosestBoss) - { - closestBoss = i; - distFromClosestBoss = flDist; - } - } - /*if (IsSpaceOccupiedIgnorePlayers(pos, mins, maxs, client)) - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is stuck, no actions taken", client, client);*/ - if (!SF_IsBoxingMap() && g_CampingEnabledConVar.BoolValue && - IsRoundPlaying() && - g_PlayerStaticAmount[client] <= g_CampingNoStrikeSanityConVar.FloatValue && - (closestBoss == -1 || distFromClosestBoss >= g_CampingNoStrikeBossDistanceConVar.FloatValue) && - distFromLastPosition <= SquareFloat(g_CampingMinDistanceConVar.FloatValue)) - { - isCamping = true; - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk, or camping", client, client); - } - if (isCamping) - { - g_PlayerCampingStrikes[client]++; - if (g_PlayerCampingStrikes[client] < g_CampingMaxStrikesConVar.IntValue) - { - if (g_PlayerCampingStrikes[client] >= g_CampingStrikesWarnConVar.IntValue) - { - CPrintToChat(client, "{red}%T", "SF2 Camping System Warning", client, (g_CampingMaxStrikesConVar.IntValue - g_PlayerCampingStrikes[client]) * 5); - } - } - else - { - g_PlayerCampingStrikes[client] = 0; - ClientStartDeathCam(client, 0, pos, true); - } - } - else - { - // Forgiveness. - if (g_PlayerCampingStrikes[client] > 0) - { - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is forgiven of one strike.", client, client); - g_PlayerCampingStrikes[client]--; - } - } - - g_PlayerCampingLastPosition[client][0] = pos[0]; - g_PlayerCampingLastPosition[client][1] = pos[1]; - g_PlayerCampingLastPosition[client][2] = pos[2]; - } - else - { - g_IsPlayerCampingFirstTime[client] = false; - //LogSF2Message("[SF2 AFK TIMER] Client %i (%N) is afk/camping for the 1st time since the reset, don't take any actions for now.....", client, client); - } - - return Plugin_Continue; -} - -#define SF2_PLAYER_HUD_BLINK_SYMBOL_ON "O" -#define SF2_PLAYER_HUD_BLINK_SYMBOL_OFF "Ɵ" -#define SF2_PLAYER_HUD_BLINK_SYMBOL_OLD "B" -#define SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL "ϟ" -#define SF2_PLAYER_HUD_BAR_SYMBOL "█" -#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL "░" -#define SF2_PLAYER_HUD_BAR_SYMBOL_OLD "|" -#define SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD "" -#define SF2_PLAYER_HUD_INFINITY_SYMBOL "∞" -#define SF2_PLAYER_HUD_SPRINT_SYMBOL "»" - -Action Timer_ClientAverageUpdate(Handle timer) -{ - if (timer != g_ClientAverageUpdateTimer) - { - return Plugin_Stop; - } - - if (!g_Enabled) - { - return Plugin_Stop; - } - - if (IsRoundInWarmup() || IsRoundEnding()) - { - return Plugin_Continue; - } - - // First, process through HUD stuff. - char buffer[256]; - - static int hudColorHealthy[3]; - static int hudColorCritical[3] = { 255, 10, 10 }; - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i)) - { - continue; - } - if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) - { - hudColorHealthy = { 50, 255, 50 }; - } - else - { - hudColorHealthy = { 150, 255, 150 }; - } - - if (IsPlayerAlive(i) && !IsClientInDeathCam(i)) - { - if (!g_PlayerEliminated[i]) - { - if (DidClientEscape(i)) - { - continue; - } - - int maxBars = 12; - int bars; - if (!SF_IsRaidMap() && !SF_IsBoxingMap()) - { - bars = RoundToCeil(float(maxBars) * ClientGetBlinkMeter(i)); - if (bars > maxBars) - { - bars = maxBars; - } - - if (!g_PlayerPreferences[i].PlayerPreference_LegacyHud) - { - if (bars != 0) - { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_ON); - } - else - { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OFF); - } - } - else - { - FormatEx(buffer, sizeof(buffer), "%s ", SF2_PLAYER_HUD_BLINK_SYMBOL_OLD); - } - - if (IsInfiniteBlinkEnabled()) - { - StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); - } - else - { - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - } - } - if (!SF_SpecialRound(SPECIALROUND_LIGHTSOUT) && !SF_IsRaidMap() && !SF_IsBoxingMap()) - { - bars = RoundToCeil(float(maxBars) * ClientGetFlashlightBatteryLife(i)); - if (bars > maxBars) - { - bars = maxBars; - } - - char buffer2[64]; - FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_FLASHLIGHT_SYMBOL); - StrCat(buffer, sizeof(buffer), buffer2); - - if (IsInfiniteFlashlightEnabled()) - { - StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); - } - else - { - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - } - } - - bars = RoundToCeil(float(maxBars) * (float(ClientGetSprintPoints(i)) / 100.0)); - if (bars > maxBars) - { - bars = maxBars; - } - - char buffer2[64]; - FormatEx(buffer2, sizeof(buffer2), "\n%s ", SF2_PLAYER_HUD_SPRINT_SYMBOL); - StrCat(buffer, sizeof(buffer), buffer2); - - if (IsInfiniteSprintEnabled()) - { - StrCat(buffer, sizeof(buffer), SF2_PLAYER_HUD_INFINITY_SYMBOL); - } - else - { - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - } - - float healthRatio = float(GetEntProp(i, Prop_Send, "m_iHealth")) / float(SDKCall(g_SDKGetMaxHealth, i)); - - int color[3]; - for (int i2 = 0; i2 < 3; i2++) - { - color[i2] = RoundFloat(float(hudColorHealthy[i2]) + (float(hudColorCritical[i2] - hudColorHealthy[i2]) * (1.0 - healthRatio))); - } - if (!SF_IsRaidMap() && !SF_IsBoxingMap()) - { - SetHudTextParams(0.035, 0.83, - 0.3, - color[0], - color[1], - color[2], - 40, - _, - 1.0, - 0.07, - 0.5); - } - else if (SF_IsRaidMap() || SF_IsBoxingMap()) - { - SetHudTextParams(0.035, 0.43, - 0.3, - color[0], - color[1], - color[2], - 40, - _, - 1.0, - 0.07, - 0.5); - } - ShowSyncHudText(i, g_HudSync2, buffer); - buffer[0] = '\0'; - } - else - { - if (g_PlayerProxy[i]) - { - int maxBars = 12; - int bars = RoundToCeil(float(maxBars) * (float(g_PlayerProxyControl[i]) / 100.0)); - if (bars > maxBars) - { - bars = maxBars; - } - - strcopy(buffer, sizeof(buffer), "CONTROL\n"); - - for (int i2 = 0; i2 < maxBars; i2++) - { - if (i2 < bars) - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_SYMBOL : SF2_PLAYER_HUD_BAR_SYMBOL_OLD)); - } - else - { - StrCat(buffer, sizeof(buffer), (!g_PlayerPreferences[i].PlayerPreference_LegacyHud ? SF2_PLAYER_HUD_BAR_MISSING_SYMBOL : SF2_PLAYER_HUD_BAR_MISSING_SYMBOL_OLD)); - } - } - - SetHudTextParams(-1.0, 0.83, - 0.3, - SF2_HUD_TEXT_COLOR_R, - SF2_HUD_TEXT_COLOR_G, - SF2_HUD_TEXT_COLOR_B, - 40, - _, - 1.0, - 0.07, - 0.5); - ShowSyncHudText(i, g_HudSync2, buffer); - } - } - } - ClientUpdateListeningFlags(i); - ClientUpdateMusicSystem(i); - } - - return Plugin_Continue; -} diff --git a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp similarity index 93% rename from addons/sourcemod/scripting/sf2/extras/renevant_mode.sp rename to addons/sourcemod/scripting/sf2/gamemodes/renevant.sp index 94c3e8ec..61ae09fa 100644 --- a/addons/sourcemod/scripting/sf2/extras/renevant_mode.sp +++ b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp @@ -5,10 +5,21 @@ #pragma semicolon 1 +static GlobalForward g_OnRenevantTriggerWaveFwd; + void SetupRenevantMode() { g_OnRoundEndPFwd.AddFunction(null, OnRoundEnd); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); +} + +void Renevant_InitializeAPI() +{ + g_OnRenevantTriggerWaveFwd = new GlobalForward("SF2_OnRenevantWaveTrigger", ET_Ignore, Param_Cell); + + CreateNative("SF2_IsRenevantMap", Native_IsRenevantMap); } static void OnRoundEnd() @@ -32,12 +43,28 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo } } +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (SF_IsRenevantMap() && g_RenevantMarkForDeath && !client.HasEscaped) + { + client.ChangeCondition(TFCond_MarkedForDeathSilent, _, -1.0); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + if (SF_IsRenevantMap() && g_RenevantMarkForDeath) + { + client.ChangeCondition(TFCond_MarkedForDeathSilent, true); + } +} + bool SF_IsRenevantMap() { - return !!(g_IsRenevantMap || (g_RenevantMapConVar.IntValue == 1)); + return (g_IsRenevantMap || (g_RenevantMapConVar.IntValue == 1)); } -static bool Renevant_TryAddBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], int profileLen, char[] name, int nameLen, bool playSpawnSound = true, bool invincible = false) +static bool Renevant_TryAddBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH], int profileLen, char[] name, int nameLen, bool playSpawnSound = true) { if (!GetRandomRenevantBossProfile(profile, profileLen)) { @@ -49,7 +76,7 @@ static bool Renevant_TryAddBossProfile(char profile[SF2_MAX_PROFILE_NAME_LENGTH] { strcopy(name, nameLen, profile); } - AddProfile(profile, _, _, _, playSpawnSound, invincible); + AddProfile(profile, _, _, _, playSpawnSound); return true; } @@ -340,7 +367,7 @@ static void Renevant_DoWaveAction(RenevantWave action) { char bufferSingle[SF2_MAX_PROFILE_NAME_LENGTH], singleBossName[SF2_MAX_NAME_LENGTH]; g_DefaultRenevantBossConVar.GetString(bufferSingle, sizeof(bufferSingle)); - if (Renevant_TryAddBossProfile(bufferSingle, sizeof(bufferSingle), singleBossName, sizeof(singleBossName), _, true)) + if (Renevant_TryAddBossProfile(bufferSingle, sizeof(bufferSingle), singleBossName, sizeof(singleBossName))) { addedBossCount++; } @@ -513,7 +540,7 @@ static void Renevant_SpawnApollyon() GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", teleportPos); for(int npcIndex = 0; npcIndex <= MAX_BOSSES; npcIndex++) { - SF2NPC_BaseNPC Npc = view_as(npcIndex); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(npcIndex); if (!Npc.IsValid()) { continue; @@ -528,7 +555,7 @@ static void Renevant_SpawnApollyon() } } -int Native_IsRenevantMap(Handle plugin, int numParams) +static int Native_IsRenevantMap(Handle plugin, int numParams) { return view_as(SF_IsRenevantMap()); } diff --git a/addons/sourcemod/scripting/sf2/mapentities.sp b/addons/sourcemod/scripting/sf2/mapentities.sp index 24080771..6539d827 100644 --- a/addons/sourcemod/scripting/sf2/mapentities.sp +++ b/addons/sourcemod/scripting/sf2/mapentities.sp @@ -34,13 +34,20 @@ methodmap SF2TriggerMapEntity < CBaseEntity public bool PassesTriggerFilters(int entity) { - return !!SDKCall(g_SDKPassesTriggerFilters, this.index, entity); + return SDKCall(g_SDKPassesTriggerFilters, this.index, entity) != 0; } property bool IsDisabled { - public get() { return !!this.GetProp(Prop_Data, "m_bDisabled"); } - public set(bool value) { this.SetProp(Prop_Data, "m_bDisabled", value); } + public get() + { + return this.GetProp(Prop_Data, "m_bDisabled") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_bDisabled", value); + } } } @@ -91,6 +98,7 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) #include "sf2/mapentities/sf2_trigger_escape.sp" #include "sf2/mapentities/sf2_info_player_escapespawn.sp" #include "sf2/mapentities/sf2_trigger_pvp.sp" +#include "sf2/mapentities/sf2_trigger_pve.sp" #include "sf2/mapentities/sf2_info_player_pvpspawn.sp" #include "sf2/mapentities/sf2_info_player_proxyspawn.sp" #include "sf2/mapentities/sf2_info_boss_spawn.sp" @@ -105,6 +113,7 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) #include "sf2/mapentities/sf2_logic_arena.sp" #include "sf2/mapentities/sf2_logic_boxing.sp" #include "sf2/mapentities/sf2_logic_slaughter.sp" +#include "sf2/mapentities/sf2_point_spotlight.sp" void SetupCustomMapEntities() { @@ -139,6 +148,9 @@ void SetupCustomMapEntities() // sf2_trigger_pvp SF2TriggerPvPEntity.Initialize(); + // sf2_trigger_pve + SF2TriggerPvEEntity.Initialize(); + // sf2_info_player_pvpspawn SF2PlayerPvPSpawnEntity.Initialize(); @@ -176,6 +188,9 @@ void SetupCustomMapEntities() // sf2_logic_slaughter SF2LogicSlaughterEntity.Initialize(); + + // sf2_point_spotlight + SF2PointSpotlightEntity.Initialize(); } static void MapStart() diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp index eac9e17e..7b6733d1 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp @@ -56,6 +56,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return view_as(this.GetProp(Prop_Data, "sf2_hBosses")); } + public set(ArrayList value) { this.SetProp(Prop_Data, "sf2_hBosses", value); @@ -68,6 +69,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetProp(Prop_Data, "sf2_iSpawnCount"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iSpawnCount", value); @@ -80,6 +82,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flSpawnRadius"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flSpawnRadius", value); @@ -92,6 +95,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetProp(Prop_Data, "sf2_nMaxLiveChildren"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_nMaxLiveChildren", value); @@ -104,6 +108,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropEnt(Prop_Data, "sf2_iDestinationEntity"); } + public set(int entity) { this.SetPropEnt(Prop_Data, "sf2_iDestinationEntity", IsValidEntity(entity) ? entity : INVALID_ENT_REFERENCE); @@ -136,6 +141,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flSpawnAnimRate"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flSpawnAnimRate", value); @@ -148,6 +154,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flSpawnAnimDuration"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flSpawnAnimDuration", value); @@ -209,9 +216,9 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity pos[1] += vec[1]; } - SpawnSlender(view_as(bossIndex), pos); + SpawnSlender(SF2NPC_BaseNPC(bossIndex), pos); - CBaseAnimating bossEntity = CBaseAnimating(NPCGetEntIndex(bossIndex)); + CBaseCombatCharacter bossEntity = CBaseCombatCharacter(NPCGetEntIndex(bossIndex)); if (bossEntity.IsValid()) { if (!(spawnFlags & SF_SF2_BOSS_MAKER_NODROP)) @@ -245,15 +252,8 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity if (spawnAnim[0] != '\0') { - float playbackRate = this.SpawnAnimationPlaybackRate; - float duration = this.SpawnAnimationDuration; - - EntitySetAnimation(bossEntity.index, spawnAnim, playbackRate); - EntitySetAnimation(bossEntity.index, spawnAnim, playbackRate); //Fix an issue where an anim could start on the wrong frame. - - g_SlenderSpawning[bossIndex] = true; - g_SlenderSpawnTimer[bossIndex] = CreateTimer(duration, Timer_SlenderSpawnTimer, EntIndexToEntRef(bossEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_SlenderEntityThink[bossIndex] = null; + SF2_ChaserEntity chaser = SF2_ChaserEntity(bossEntity.index); + chaser.SetOverrideSpawnAnimation(spawnAnim, this.SpawnAnimationDuration, this.SpawnAnimationPlaybackRate); } } @@ -400,7 +400,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity if ((spawnFlags & SF_SF2_BOSS_MAKER_NOCOPIES)) { - NPCSetFlags(bossIndex, NPCGetFlags(bossIndex) & ~SFF_COPIES); + NPCSetFlags(bossIndex, NPCGetFlags(bossIndex) | SFF_NOCOPIES); } if (!(spawnFlags & SF_SF2_BOSS_MAKER_ADDONLY)) diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp index cd27d124..7bc10f88 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_game_text.sp @@ -45,12 +45,12 @@ methodmap SF2GameTextEntity < CBaseEntity { if (StrContains(buffer, "%d") != -1) { - char sName[64]; - this.GetPropString(Prop_Data, "m_iName", sName, sizeof(sName)); + char name[64]; + this.GetPropString(Prop_Data, "m_iName", name, sizeof(name)); char[] message = new char[bufferSize]; strcopy(message, bufferSize, buffer); ReplaceString(message, bufferSize, "%d", "%%d"); - LogError("sf2_game_text (%s): %%d formatting parameters are NOT ALLOWED! Use the and variables! Please report this to the map creator.\nOffending message: %s", sName, message); + LogError("sf2_game_text (%s): %%d formatting parameters are NOT ALLOWED! Use the and variables! Please report this to the map creator.\nOffending message: %s", name, message); return false; } @@ -68,19 +68,22 @@ methodmap SF2GameTextEntity < CBaseEntity if (StrContains(buffer, "") != -1) { - char arg[16]; IntToString(g_PageMax, arg, sizeof(arg)); + char arg[16]; + IntToString(g_PageMax, arg, sizeof(arg)); ReplaceString(buffer, bufferSize, "", arg); } if (StrContains(buffer, "") != -1) { - char arg[16]; IntToString(g_PageCount, arg, sizeof(arg)); + char arg[16]; + IntToString(g_PageCount, arg, sizeof(arg)); ReplaceString(buffer, bufferSize, "", arg); } if (StrContains(buffer, "") != -1) { - char arg[16]; IntToString(g_PageMax - g_PageCount, arg, sizeof(arg)); + char arg[16]; + IntToString(g_PageMax - g_PageCount, arg, sizeof(arg)); ReplaceString(buffer, bufferSize, "", arg); } } @@ -115,6 +118,7 @@ methodmap SF2GameTextEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flNextIntroTextDelay"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flNextIntroTextDelay", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp index 1294effb..715596be 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp @@ -34,6 +34,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iMaxPlayers"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iMaxPlayers", value); @@ -46,6 +47,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iMaxPages"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iMaxPages", value); @@ -84,6 +86,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iInitialTimeLimit"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iInitialTimeLimit", value); @@ -96,6 +99,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iPageCollectAddTime"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iPageCollectAddTime", value); @@ -118,6 +122,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iPageCollectSoundPitch"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iPageCollectSoundPitch", value); @@ -128,8 +133,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bInfiniteFlashlight"); + return this.GetProp(Prop_Data, "sf2_bInfiniteFlashlight") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bInfiniteFlashlight", value); @@ -140,8 +146,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bInfiniteSprint"); + return this.GetProp(Prop_Data, "sf2_bInfiniteSprint") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bInfiniteSprint", value); @@ -152,8 +159,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bInfiniteBlink"); + return this.GetProp(Prop_Data, "sf2_bInfiniteBlink") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bInfiniteBlink", value); @@ -164,8 +172,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bBossesChaseEndlessly"); + return this.GetProp(Prop_Data, "sf2_bBossesChaseEndlessly") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bBossesChaseEndlessly", value); @@ -176,8 +185,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bEscapeToWin"); + return this.GetProp(Prop_Data, "sf2_bEscapeToWin") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bEscapeToWin", value); @@ -190,6 +200,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iEscapeTimeLimit"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iEscapeTimeLimit", value); @@ -226,8 +237,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bStopPageMusicOnEscape"); + return this.GetProp(Prop_Data, "sf2_bStopPageMusicOnEscape") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bStopPageMusicOnEscape", value); @@ -238,8 +250,9 @@ methodmap SF2GamerulesEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bSurvive"); + return this.GetProp(Prop_Data, "sf2_bSurvive") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bSurvive", value); @@ -252,6 +265,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iSurviveUntilTime"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iSurviveUntilTime", value); @@ -294,6 +308,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flIntroFadeHoldTime"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flIntroFadeHoldTime", value); @@ -306,6 +321,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flIntroFadeTime"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flIntroFadeTime", value); @@ -344,6 +360,7 @@ methodmap SF2GamerulesEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flIntroTextDelay"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flIntroTextDelay", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp index a9f54cda..f181526b 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_boss_spawn.sp @@ -42,6 +42,7 @@ methodmap SF2BossSpawnEntity < SF2SpawnPointBaseEntity { return this.GetProp(Prop_Data, "sf2_iMaxBosses"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iMaxBosses", value); @@ -77,7 +78,7 @@ methodmap SF2BossSpawnEntity < SF2SpawnPointBaseEntity NPCGetProfile(bossIndex, profile, sizeof(profile)); if (strcmp(profile, targetProfile) == 0) { - SpawnSlender(view_as(bossIndex), pos); + SpawnSlender(SF2NPC_BaseNPC(bossIndex), pos); count++; int bossEntIndex = NPCGetEntIndex(bossIndex); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp index ebf69dbf..f52702fc 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_music.sp @@ -39,8 +39,9 @@ methodmap SF2PageMusicEntity < CBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bLayered"); + return this.GetProp(Prop_Data, "sf2_bLayered") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bLayered", value); @@ -53,6 +54,7 @@ methodmap SF2PageMusicEntity < CBaseEntity { return view_as(this.GetProp(Prop_Data, "sf2_hRanges")); } + public set(ArrayList value) { this.SetProp(Prop_Data, "sf2_hRanges", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp index f3381611..d63e840c 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_page_spawn.sp @@ -40,6 +40,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iSkin"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iSkin", value); @@ -52,6 +53,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { return this.GetPropFloat(Prop_Data, "sf2_flModelScale"); } + public set(float value) { this.SetPropFloat(Prop_Data, "sf2_flModelScale", value); @@ -64,6 +66,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iBodyGroup"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iBodyGroup", value); @@ -106,6 +109,7 @@ methodmap SF2PageSpawnEntity < CBaseEntity { this.GetProp(Prop_Data, "sf2_iCollectSoundPitch"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iCollectSoundPitch", value); @@ -160,13 +164,13 @@ static void OnSpawn(int entity) char buffer[PLATFORM_MAX_PATH]; thisEnt.GetPageModel(buffer, sizeof(buffer)); - if (buffer[0]) + if (buffer[0] != '\0') { PrecacheModel(buffer); } thisEnt.GetPageCollectSound(buffer, sizeof(buffer)); - if (buffer[0]) + if (buffer[0] != '\0') { PrecacheSound(buffer); } diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp index 79446c5d..4f9fe452 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_escapespawn.sp @@ -27,6 +27,7 @@ methodmap SF2PlayerEscapeSpawnEntity < SF2SpawnPointBaseEntity { return !this.GetProp(Prop_Data, "sf2_bDisabled"); } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bDisabled", !value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp index 9c890c09..2a1fbcbc 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_proxyspawn.sp @@ -27,6 +27,7 @@ methodmap SF2PlayerProxySpawnEntity < SF2SpawnPointBaseEntity { return !this.GetProp(Prop_Data, "sf2_bDisabled"); } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bDisabled", !value); @@ -37,8 +38,9 @@ methodmap SF2PlayerProxySpawnEntity < SF2SpawnPointBaseEntity { public get() { - return !!this.GetProp(Prop_Data, "sf2_bIgnoreVisibility"); + return this.GetProp(Prop_Data, "sf2_bIgnoreVisibility") != 0; } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bIgnoreVisibility", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp index a4cc3168..3a39cf87 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_info_player_pvpspawn.sp @@ -27,6 +27,7 @@ methodmap SF2PlayerPvPSpawnEntity < SF2SpawnPointBaseEntity { return !this.GetProp(Prop_Data, "sf2_bDisabled"); } + public set(bool value) { this.SetProp(Prop_Data, "sf2_bDisabled", !value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp index 164f1e13..2d3f7434 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_logic_arena.sp @@ -32,6 +32,7 @@ methodmap SF2LogicRenevantEntity < CBaseEntity { return this.GetProp(Prop_Data, "sf2_iFinaleTime"); } + public set(int value) { this.SetProp(Prop_Data, "sf2_iFinaleTime", value); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_point_spotlight.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_point_spotlight.sp new file mode 100644 index 00000000..3bff8c78 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_point_spotlight.sp @@ -0,0 +1,398 @@ + +static const char g_Classname[] = "sf2_point_spotlight"; + +static CEntityFactory g_Factory; + +methodmap SF2PointSpotlightEntity < CBaseEntity +{ + public SF2PointSpotlightEntity(int entIndex) + { + return view_as(CBaseEntity(entIndex)); + } + + public bool IsValid() + { + if (!CBaseEntity(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory(g_Classname, OnCreate, OnRemove); + g_Factory.DeriveFromClass("env_beam"); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_Start") + .DefineEntityField("m_End") + .DefineFloatField("m_HaloScale") + .DefineIntField("m_Brightness") + .DefineFloatField("m_Distance") + .DefineFloatField("m_SpotlightRadius") + .DefineIntField("m_Cone") + .DefineBoolField("m_IsOn") + .DefineFloatField("m_Length") + .DefineInputFunc("LightOn", InputFuncValueType_Void, InputLightOn) + .DefineInputFunc("LightOff", InputFuncValueType_Void, InputLightOff) + .EndDataMapDesc(); + g_Factory.Install(); + } + + property CBaseEntity Start + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_Start")); + } + + public set(CBaseEntity value) + { + this.SetPropEnt(Prop_Data, "m_Start", value.index); + } + } + + property CBaseEntity End + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_End")); + } + + public set(CBaseEntity value) + { + this.SetPropEnt(Prop_Data, "m_End", value.index); + } + } + + property float HaloScale + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_HaloScale"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_HaloScale", value); + } + } + + property int Brightness + { + public get() + { + return this.GetProp(Prop_Data, "m_Brightness"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Brightness", value); + } + } + + property float Distance + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Distance"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Distance", value); + } + } + + property float SpotlightRadius + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SpotlightRadius"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SpotlightRadius", value); + } + } + + property int Cone + { + public get() + { + return this.GetProp(Prop_Data, "m_Cone"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_Cone", value); + } + } + + property bool IsOn + { + public get() + { + return this.GetProp(Prop_Data, "m_IsOn") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsOn", value); + } + } + + property float Length + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_Length"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_Length", value); + } + } + + property float Width + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_fWidth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_fWidth", value); + } + } + + property float EndWidth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_fEndWidth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_fEndWidth", value); + } + } + + public void TurnOn() + { + if (this.IsOn) + { + return; + } + + this.IsOn = true; + + this.AcceptInput("TurnOn"); + this.End.AcceptInput("TurnOn"); + } + + public void TurnOff() + { + if (!this.IsOn) + { + return; + } + + this.IsOn = false; + + this.AcceptInput("TurnOff"); + this.End.AcceptInput("TurnOff"); + } + + public void SetLightSettings() + { + SetVariantInt(this.Brightness); + this.End.AcceptInput("Brightness"); + SetVariantFloat(this.Distance); + this.End.AcceptInput("Distance"); + SetVariantFloat(this.SpotlightRadius); + this.End.AcceptInput("spotlight_radius"); + SetVariantInt(this.Cone); + this.End.AcceptInput("cone"); + } +} + +static void OnCreate(SF2PointSpotlightEntity ent) +{ + ent.SetModel(SF2_FLASHLIGHT_BEAM_MATERIAL); + ent.SetPropFloat(Prop_Data, "m_life", 0.0); + + float pos[3], ang[3]; + ent.GetAbsOrigin(pos); + ent.GetAbsAngles(ang); + + CBaseEntity start = ent.Start = CBaseEntity(CreateEntityByName("info_target")); + CBaseEntity end = ent.End = CBaseEntity(CreateEntityByName("light_dynamic")); + + start.SetPropString(Prop_Data, "m_iClassname", "sf2_point_spotlight_start"); + end.SetPropString(Prop_Data, "m_iClassname", "sf2_point_spotlight_end"); + start.Teleport(pos, ang); + end.Teleport(pos, ang); + SetVariantString("!activator"); + start.AcceptInput("SetParent", ent.index); + SetVariantString("!activator"); + end.AcceptInput("SetParent", ent.index); + start.Spawn(); + end.Spawn(); + end.Activate(); + + SDKHook(ent.index, SDKHook_SpawnPost, OnSpawn); +} + +static void OnSpawn(int entIndex) +{ + SF2PointSpotlightEntity entity = SF2PointSpotlightEntity(entIndex); + + entity.SetPropEnt(Prop_Send, "m_hAttachEntity", entity.Start.index, 0); + entity.SetPropEnt(Prop_Send, "m_hAttachEntity", entity.End.index, 1); + entity.SetProp(Prop_Send, "m_nNumBeamEnts", 2); + entity.SetProp(Prop_Send, "m_nBeamType", 2); + + entity.SetPropFloat(Prop_Send, "m_fFadeLength", 12.0); + entity.SetPropFloat(Prop_Data, "m_fEndWidth", entity.EndWidth); + entity.SetProp(Prop_Send, "m_nHaloIndex", g_FlashlightHaloModel); + entity.SetPropFloat(Prop_Send, "m_fHaloScale", entity.HaloScale); + entity.SetProp(Prop_Send, "m_nBeamFlags", 0x80 | 0x200); + entity.SetProp(Prop_Data, "m_spawnflags", 0x8000); + + entity.SetLightSettings(); + + SetEntityTransmitState(entity.index, FL_EDICT_FULLCHECK); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity.index, SpotlightEffectUpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, entity.index, SpotlightEffectShouldTransmit); + SetEntityTransmitState(entity.End.index, FL_EDICT_FULLCHECK); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity.End.index, SpotlightEffectUpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, entity.End.index, SpotlightEffectShouldTransmit); + + RequestFrame(SpotlightThink, EnsureEntRef(entIndex)); +} + +static void UpdateSpotlight(SF2PointSpotlightEntity entity) +{ + CBaseEntity spotlightEnd = entity.End; + if (spotlightEnd.IsValid()) + { + float pos[3], dir[3]; + entity.GetAbsOrigin(pos); + entity.GetAbsAngles(dir); + float endPos[3]; + endPos[0] = entity.Length; + VectorTransform(endPos, pos, dir, endPos); + + TR_TraceRayFilter(pos, endPos, MASK_SOLID_BRUSHONLY, RayType_EndPoint, Trace, entity.index); + + float hitPos[3]; + TR_GetEndPosition(hitPos); +/* + int color[4] = { 255, 0, 0, 255 }; + TE_SetupBeamPoints(pos, + endPos, + g_ShockwaveBeam, + g_ShockwaveHalo, + 0, + 30, + 0.1, + 5.0, + 5.0, + 5, + 0.0, + color, + 1); + TE_SendToAll(); +*/ + spotlightEnd.SetAbsOrigin(hitPos); + } + +} + +static void SpotlightThink(SF2PointSpotlightEntity entity) +{ + if (!entity.IsValid()) + { + return; + } + + if (entity.IsOn) + { + UpdateSpotlight(entity); + } + + RequestFrame(SpotlightThink, entity); +} + +static bool Trace(int entity, int mask, any data) +{ + if (entity == data) + { + return false; + } + if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) + { + return false; + } + if (SF2_BasePlayer(entity).IsValid) + { + return false; + } + return true; +} + +static void OnRemove(SF2PointSpotlightEntity entity) +{ + if (entity.Start.IsValid()) + { + RemoveEntity(entity.Start.index); + } + + if (entity.End.IsValid()) + { + RemoveEntity(entity.End.index); + } +} + +static MRESReturn SpotlightEffectUpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = SetEntityTransmitState(entIndex, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn SpotlightEffectShouldTransmit(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + // We can return FL_EDICT_ALWAYS here because SetTransmit does get called if ShouldTransmit() + // returns this. + // https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/gameinterface.cpp#L2481 + ret.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} + +static void InputLightOn(int entIndex, int activator, int caller) +{ + SF2PointSpotlightEntity entity = SF2PointSpotlightEntity(entIndex); + entity.TurnOn(); +} + +static void InputLightOff(int entIndex, int activator, int caller) +{ + SF2PointSpotlightEntity entity = SF2PointSpotlightEntity(entIndex); + entity.TurnOff(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp new file mode 100644 index 00000000..bd005687 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp @@ -0,0 +1,78 @@ +// sf2_trigger_pve + +#pragma semicolon 1 + +static CEntityFactory g_EntityFactory; + +/** + * Interface that exposes public methods for interacting with the entity. + */ +methodmap SF2TriggerPvEEntity < SF2TriggerMapEntity +{ + public SF2TriggerPvEEntity(int entIndex) + { + return view_as(SF2TriggerMapEntity(entIndex)); + } + + public bool IsValid() + { + if (!CBaseEntity(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_EntityFactory; + } + + property bool IsBossPvE + { + public get() + { + return this.GetProp(Prop_Data, "m_IsBossPvE") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsBossPvE", value); + } + } + + public static void Initialize() + { + Initialize(); + } +} + +static void Initialize() +{ + g_EntityFactory = new CEntityFactory("sf2_trigger_pve", OnCreate); + g_EntityFactory.DeriveFromClass("trigger_multiple"); + g_EntityFactory.BeginDataMapDesc() + .DefineFloatField("m_IsBossPvE", _, "bosspve") + .EndDataMapDesc(); + + g_EntityFactory.Install(); +} + +static void OnCreate(int entity) +{ + SDKHook(entity, SDKHook_SpawnPost, OnSpawn); + SDKHook(entity, SDKHook_StartTouchPost, OnStartTouchPost); + SDKHook(entity, SDKHook_EndTouchPost, OnEndTouchPost); +} + +static void OnSpawn(int entity) +{ + int spawnFlags = GetEntProp(entity, Prop_Data, "m_spawnflags"); + SetEntProp(entity, Prop_Data, "m_spawnflags", spawnFlags | TRIGGER_CLIENTS); +} + +static void OnStartTouchPost(int entity, int toucher) +{ + PvE_OnTriggerStartTouch(entity, toucher); +} + +static void OnEndTouchPost(int entity, int toucher) +{ + PvE_OnTriggerEndTouch(entity, toucher); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index fd5dc0ba..6b35906d 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -6,171 +6,167 @@ #pragma semicolon 1 -Handle g_MenuMain; +Menu g_MenuMain; Menu g_MenuVoteDifficulty; -Handle g_MenuHelp; -Handle g_MenuHelpObjective; -Handle g_MenuHelpObjective2; -Handle g_MenuHelpCommands; -Handle g_MenuHelpSprinting; -Handle g_MenuHelpControls; -Handle g_MenuHelpClasinfo; -Handle g_MenuHelpGhostMode; -Handle g_MenuSettings; -Handle g_MenuSettingsFlashlightTemp1; -Handle g_MenuCredits; -Handle g_MenuCredits1; -Handle g_MenuCredits2; -Handle g_MenuCredits3; -Handle g_MenuCredits4; -Handle g_MenuCredits5; -Handle g_MenuUpdate; +Menu g_MenuHelp; +Menu g_MenuHelpObjective; +Menu g_MenuHelpObjective2; +Menu g_MenuHelpCommands; +Menu g_MenuHelpSprinting; +Menu g_MenuHelpControls; +Menu g_MenuHelpClassInfo; +Menu g_MenuHelpGhostMode; +Menu g_MenuSettings; +Menu g_MenuSettingsFlashlightTemp1; +Menu g_MenuCredits; +Menu g_MenuCredits1; +Menu g_MenuCredits2; +Menu g_MenuCredits3; +Menu g_MenuCredits4; +Menu g_MenuCredits5; static ArrayList g_Voters; static bool g_IsRunOff; #include "sf2/playergroups/menus.sp" #include "sf2/pvp/menus.sp" +#include "sf2/changelog.sp" void SetupMenus() { char buffer[512]; // Create menus. - g_MenuMain = CreateMenu(Menu_Main); - SetMenuTitle(g_MenuMain, "%t%t\n \n", "SF2 Prefix", "SF2 Main Menu Title"); + g_MenuMain = new Menu(Menu_Main); + g_MenuMain.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Main Menu Title"); FormatEx(buffer, sizeof(buffer), "%t (!slhelp)", "SF2 Help Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slnext)", "SF2 Queue Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slgroup)", "SF2 Group Main Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slghost)", "SF2 Ghost Mode Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slpack)", "SF2 Boss Pack Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slnextpack)", "SF2 Boss Next Pack Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slsettings)", "SF2 Settings Menu Title"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); strcopy(buffer, sizeof(buffer), "Credits (!slcredits)"); - AddMenuItem(g_MenuMain, "0", buffer); + g_MenuMain.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t (!slbosslist)", "SF2 Boss View On List Title"); - AddMenuItem(g_MenuMain, "0", buffer); - - g_MenuVoteDifficulty = CreateMenu(Menu_VoteDifficulty); - SetMenuTitle(g_MenuVoteDifficulty, "%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Normal Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "1", buffer); - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Hard Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "2", buffer); - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Insane Difficulty"); - AddMenuItem(g_MenuVoteDifficulty, "3", buffer); - - g_MenuHelp = CreateMenu(Menu_Help); - SetMenuTitle(g_MenuHelp, "%t%t\n \n", "SF2 Prefix", "SF2 Help Menu Title"); + g_MenuMain.AddItem("0", buffer); + + g_MenuHelp = new Menu(Menu_Help); + g_MenuHelp.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Help Menu Title"); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Objective Menu Title"); - AddMenuItem(g_MenuHelp, "0", buffer); + g_MenuHelp.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Commands Menu Title"); - AddMenuItem(g_MenuHelp, "1", buffer); + g_MenuHelp.AddItem("1", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Class Info Menu Title"); - AddMenuItem(g_MenuHelp, "2", buffer); + g_MenuHelp.AddItem("2", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Ghost Mode Menu Title"); - AddMenuItem(g_MenuHelp, "3", buffer); + g_MenuHelp.AddItem("3", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Sprinting And Stamina Menu Title"); - AddMenuItem(g_MenuHelp, "4", buffer); + g_MenuHelp.AddItem("4", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Controls Menu Title"); - AddMenuItem(g_MenuHelp, "5", buffer); - SetMenuExitBackButton(g_MenuHelp, true); + g_MenuHelp.AddItem("5", buffer); + g_MenuHelp.ExitBackButton = true; - g_MenuHelpObjective = CreateMenu(Menu_HelpObjective); - SetMenuTitle(g_MenuHelpObjective, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description"); - AddMenuItem(g_MenuHelpObjective, "0", "Next"); - AddMenuItem(g_MenuHelpObjective, "1", "Back"); + g_MenuHelpObjective = new Menu(Menu_HelpObjective); + g_MenuHelpObjective.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description"); + g_MenuHelpObjective.AddItem("0", "Next"); + g_MenuHelpObjective.AddItem("1", "Back"); - g_MenuHelpObjective2 = CreateMenu(Menu_HelpObjective2); - SetMenuTitle(g_MenuHelpObjective2, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description 2"); - AddMenuItem(g_MenuHelpObjective2, "0", "Back"); + g_MenuHelpObjective2 = new Menu(Menu_HelpObjective2); + g_MenuHelpObjective2.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Objective Menu Title", "SF2 Help Objective Description 2"); + g_MenuHelpObjective2.AddItem("0", "Back"); - g_MenuHelpCommands = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpCommands, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Commands Menu Title", "SF2 Help Commands Description"); - AddMenuItem(g_MenuHelpCommands, "0", "Back"); + g_MenuHelpCommands = new Menu(Menu_BackButtonOnly); + g_MenuHelpCommands.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Commands Menu Title", "SF2 Help Commands Description"); + g_MenuHelpCommands.AddItem("0", "Back"); - g_MenuHelpGhostMode = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpGhostMode, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Ghost Mode Menu Title", "SF2 Help Ghost Mode Description"); - AddMenuItem(g_MenuHelpGhostMode, "0", "Back"); + g_MenuHelpGhostMode = new Menu(Menu_BackButtonOnly); + g_MenuHelpGhostMode.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Ghost Mode Menu Title", "SF2 Help Ghost Mode Description"); + g_MenuHelpGhostMode.AddItem("0", "Back"); - g_MenuHelpSprinting = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpSprinting, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Sprinting And Stamina Menu Title", "SF2 Help Sprinting And Stamina Description"); - AddMenuItem(g_MenuHelpSprinting, "0", "Back"); + g_MenuHelpSprinting = new Menu(Menu_BackButtonOnly); + g_MenuHelpSprinting.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Sprinting And Stamina Menu Title", "SF2 Help Sprinting And Stamina Description"); + g_MenuHelpSprinting.AddItem("0", "Back"); - g_MenuHelpControls = CreateMenu(Menu_BackButtonOnly); - SetMenuTitle(g_MenuHelpControls, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Controls Menu Title", "SF2 Help Controls Description"); - AddMenuItem(g_MenuHelpControls, "0", "Back"); + g_MenuHelpControls = new Menu(Menu_BackButtonOnly); + g_MenuHelpControls.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Controls Menu Title", "SF2 Help Controls Description"); + g_MenuHelpControls.AddItem("0", "Back"); - g_MenuHelpClasinfo = CreateMenu(Menu_ClassInfo); - SetMenuTitle(g_MenuHelpClasinfo, "%t%t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Class Info Menu Title", "SF2 Help Class Info Description"); + g_MenuHelpClassInfo = new Menu(Menu_ClassInfo); + g_MenuHelpClassInfo.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", "SF2 Help Class Info Menu Title", "SF2 Help Class Info Description"); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Scout Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Scout", buffer); + g_MenuHelpClassInfo.AddItem("Scout", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Sniper Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Sniper", buffer); + g_MenuHelpClassInfo.AddItem("Sniper", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Soldier Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Soldier", buffer); + g_MenuHelpClassInfo.AddItem("Soldier", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Demoman Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Demoman", buffer); + g_MenuHelpClassInfo.AddItem("Demoman", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Heavy Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Heavy", buffer); + g_MenuHelpClassInfo.AddItem("Heavy", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Medic Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Medic", buffer); + g_MenuHelpClassInfo.AddItem("Medic", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Pyro Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Pyro", buffer); + g_MenuHelpClassInfo.AddItem("Pyro", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Spy Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Spy", buffer); + g_MenuHelpClassInfo.AddItem("Spy", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Help Engineer Class Info Menu Title"); - AddMenuItem(g_MenuHelpClasinfo, "Engineer", buffer); - SetMenuExitBackButton(g_MenuHelpClasinfo, true); + g_MenuHelpClassInfo.AddItem("Engineer", buffer); + g_MenuHelpClassInfo.ExitBackButton = true; - g_MenuSettings = CreateMenu(Menu_Settings); - SetMenuTitle(g_MenuSettings, "%t%t\n \n", "SF2 Prefix", "SF2 Settings Menu Title"); + g_MenuSettings = new Menu(Menu_Settings); + g_MenuSettings.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Settings Menu Title"); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings PvP Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Hints Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Mute Mode Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Film Grain Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); - FormatEx(buffer, sizeof(buffer), "Toggle camera view bobbing"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings View Bobbing Title"); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Hud Version Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Proxy Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Music Volume Title"); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Flashlight Temperature Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Ghost Mode Teleport Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Ghost Mode Toggle State Title"); - AddMenuItem(g_MenuSettings, "0", buffer); + g_MenuSettings.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Proxy Ask Menu Title"); - AddMenuItem(g_MenuSettings, "0", buffer); - SetMenuExitBackButton(g_MenuSettings, true); - - g_MenuSettingsFlashlightTemp1 = CreateMenu(Menu_Settings_Flashlighttemp1); - SetMenuTitle(g_MenuSettingsFlashlightTemp1, buffer); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "0", "1000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "1", "2000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "2", "3000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "3", "4000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "4", "5000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "5", "6000 Kelvin (Default)"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "6", "7000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "7", "8000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "8", "9000 Kelvin"); - AddMenuItem(g_MenuSettingsFlashlightTemp1, "9", "10000 Kelvin"); - SetMenuExitBackButton(g_MenuSettingsFlashlightTemp1, true); - - g_MenuCredits = CreateMenu(Menu_Credits); + g_MenuSettings.AddItem("0", buffer); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings PvE Menu Title"); + g_MenuSettings.AddItem("0", buffer); + g_MenuSettings.ExitBackButton = true; + + g_MenuSettingsFlashlightTemp1 = new Menu(Menu_Settings_Flashlighttemp1); + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Settings Flashlight Temperature Title"); + g_MenuSettingsFlashlightTemp1.SetTitle(buffer); + g_MenuSettingsFlashlightTemp1.AddItem("0", "1k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("1", "2k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("2", "3k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("3", "4k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("4", "5k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("5", "6k Kelvin (Default)"); + g_MenuSettingsFlashlightTemp1.AddItem("6", "7k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("7", "8k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("8", "9k Kelvin"); + g_MenuSettingsFlashlightTemp1.AddItem("9", "10k Kelvin"); + g_MenuSettingsFlashlightTemp1.ExitBackButton = true; + + g_MenuCredits = new Menu(Menu_Credits); FormatEx(buffer, sizeof(buffer), "Credits\n"); StrCat(buffer, sizeof(buffer), "Coders: KitRifty, Kenzzer, Mentrillum, The Gaben\n"); @@ -180,11 +176,11 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Mammoth Mogul - For being a GREAT test subject\n"); StrCat(buffer, sizeof(buffer), "Egosins - For offering to host this publicly\n"); - SetMenuTitle(g_MenuCredits, buffer); - AddMenuItem(g_MenuCredits, "0", "Next"); - AddMenuItem(g_MenuCredits, "1", "Back"); + g_MenuCredits.SetTitle(buffer); + g_MenuCredits.AddItem("0", "Next"); + g_MenuCredits.AddItem("1", "Back"); - g_MenuCredits1 = CreateMenu(Menu_Credits1); + g_MenuCredits1 = new Menu(Menu_Credits1); FormatEx(buffer, sizeof(buffer), "Credits\n"); StrCat(buffer, sizeof(buffer), "Glubbable - For working on a ton of maps\n"); @@ -192,14 +188,14 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Omi-Box - Materials, maps, current Slender Man model, and more\n"); StrCat(buffer, sizeof(buffer), "Narry Gewman - Imported first Slender Man model\n"); StrCat(buffer, sizeof(buffer), "Simply Delicious - For the awesome camera overlay\n"); - StrCat(buffer, sizeof(buffer), "Jason278 -Page models"); + StrCat(buffer, sizeof(buffer), "Jason278 - Page models"); StrCat(buffer, sizeof(buffer), "Dj-Rec0il - Running In the 90s Remix composer\n"); - SetMenuTitle(g_MenuCredits1, buffer); - AddMenuItem(g_MenuCredits1, "0", "Next"); - AddMenuItem(g_MenuCredits1, "1", "Back"); + g_MenuCredits1.SetTitle(buffer); + g_MenuCredits1.AddItem("0", "Next"); + g_MenuCredits1.AddItem("1", "Back"); - g_MenuCredits2 = CreateMenu(Menu_Credits2); + g_MenuCredits2 = new Menu(Menu_Credits2); FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "To all the peeps who alpha-tested this thing!\n \n"); @@ -214,11 +210,11 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Pinkle D Lies\n"); StrCat(buffer, sizeof(buffer), "Ultimatefry\n \n"); - SetMenuTitle(g_MenuCredits2, buffer); - AddMenuItem(g_MenuCredits2, "0", "Next"); - AddMenuItem(g_MenuCredits2, "1", "Back"); + g_MenuCredits2.SetTitle(buffer); + g_MenuCredits2.AddItem("0", "Next"); + g_MenuCredits2.AddItem("1", "Back"); - g_MenuCredits3 = CreateMenu(Menu_Credits3); + g_MenuCredits3 = new Menu(Menu_Credits3); FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Credits to all peeps who gave special round suggestions!\n \n"); @@ -233,11 +229,11 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Spooky Pyro\n"); StrCat(buffer, sizeof(buffer), "Firedudeet\n \n"); - SetMenuTitle(g_MenuCredits3, buffer); - AddMenuItem(g_MenuCredits3, "0", "Next"); - AddMenuItem(g_MenuCredits3, "1", "Back"); + g_MenuCredits3.SetTitle(buffer); + g_MenuCredits3.AddItem("0", "Next"); + g_MenuCredits3.AddItem("1", "Back"); - g_MenuCredits4 = CreateMenu(Menu_Credits4); + g_MenuCredits4 = new Menu(Menu_Credits4); FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Major special thanks to all official Modified server owners!\n \n"); @@ -248,43 +244,30 @@ void SetupMenus() StrCat(buffer, sizeof(buffer), "Fire\n"); StrCat(buffer, sizeof(buffer), "[NxN]Nameless\n"); - SetMenuTitle(g_MenuCredits4, buffer); - AddMenuItem(g_MenuCredits4, "0", "Next"); - AddMenuItem(g_MenuCredits4, "1", "Back"); + g_MenuCredits4.SetTitle(buffer); + g_MenuCredits4.AddItem("0", "Next"); + g_MenuCredits4.AddItem("1", "Back"); - g_MenuCredits5 = CreateMenu(Menu_Credits5); + g_MenuCredits5 = new Menu(Menu_Credits5); FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "And finally to all of these people that helped out this version one way or another!\n \n"); - StrCat(buffer, sizeof(buffer), "Dookster\n"); + StrCat(buffer, sizeof(buffer), "KitRifty\n"); StrCat(buffer, sizeof(buffer), "Spook\n"); StrCat(buffer, sizeof(buffer), "Rorek\n"); StrCat(buffer, sizeof(buffer), "Chillax\n"); - StrCat(buffer, sizeof(buffer), "Staff from Disc-FF (EllieDear, Arrow Skye, tocks, and Pasta Stalin)\n"); + StrCat(buffer, sizeof(buffer), "Staff from Disc-FF (EllieDear, Arrow Skye, tocks, Pasta Stalin, etc)\n"); StrCat(buffer, sizeof(buffer), "Basically everyone at Disc-FF\n"); StrCat(buffer, sizeof(buffer), "And you for playing this new way of SF2!\n \n"); - SetMenuTitle(g_MenuCredits5, buffer); - AddMenuItem(g_MenuCredits5, "0", "Back"); - - g_MenuUpdate = CreateMenu(Menu_Update); - FormatEx(buffer, sizeof(buffer), "%tSlender Fortress\n \n", "SF2 Prefix"); - StrCat(buffer, sizeof(buffer), "Coders: KitRifty, Kenzzer, Mentrillum, The Gaben\n"); - StrCat(buffer, sizeof(buffer), "Version: "); - StrCat(buffer, sizeof(buffer), PLUGIN_VERSION); - StrCat(buffer, sizeof(buffer), "\n \n"); - Format(buffer, sizeof(buffer), "%s%t\n", buffer, "SF2 Recent Changes"); - Format(buffer, sizeof(buffer), "%s%t\n", buffer, "SF2 Change Log"); - StrCat(buffer, sizeof(buffer), "\n \n"); - - SetMenuTitle(g_MenuUpdate, buffer); - - AddMenuItem(g_MenuUpdate, "0", "Display main menu"); + g_MenuCredits5.SetTitle(buffer); + g_MenuCredits5.AddItem("0", "Back"); PvP_SetupMenus(); + PvE_SetupMenus(); } -void RandomizeVoteMenu() +void RandomizeVoteMenu(bool excludeHigh = false) { char buffer[512]; @@ -309,8 +292,16 @@ void RandomizeVoteMenu() bool normal = StrContains(buffer, "1") != -1; bool hard = StrContains(buffer, "2") != -1; bool insane = StrContains(buffer, "3") != -1; + bool nightmare = StrContains(buffer, "4") != -1; + bool apollyon = StrContains(buffer, "5") != -1; bool random = StrContains(buffer, "6") != -1; + if (excludeHigh) + { + nightmare = false; + apollyon = false; + } + // WHOA there is a better way, and I'm not tired ArrayList options = new ArrayList(ByteCountToCells(64)); ArrayList indicies = new ArrayList(); @@ -339,6 +330,22 @@ void RandomizeVoteMenu() index++; } + if (nightmare) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); + options.PushString(buffer); + indicies.Push(4); + index++; + } + + if (apollyon) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); + options.PushString(buffer); + indicies.Push(5); + index++; + } + int participating = 0; for (int i = 1; i <= MaxClients; i++) { @@ -350,37 +357,46 @@ void RandomizeVoteMenu() participating++; } - for (int i = 1; i <= MaxClients; i++) + if (!excludeHigh) { - if (!IsValidClient(i) || g_PlayerEliminated[i]) - { - continue; - } - - int group = ClientGetPlayerGroup(i); - if (!IsPlayerGroupValid(group) || !IsPlayerGroupOptInHarder(group)) + for (int i = 1; i <= MaxClients; i++) { - continue; - } + if (!IsValidClient(i) || g_PlayerEliminated[i]) + { + continue; + } - if (GetPlayerGroupMemberCount(group) < participating) - { - if (GetPlayerGroupMemberCount(group) < GetMaxPlayersForRound()) + int group = ClientGetPlayerGroup(i); + if (!IsPlayerGroupValid(group) || !IsPlayerGroupOptInHarder(group)) { continue; } - } - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); - options.PushString(buffer); - indicies.Push(4); - index++; + if (GetPlayerGroupMemberCount(group) < participating) + { + if (GetPlayerGroupMemberCount(group) < GetMaxPlayersForRound()) + { + continue; + } + } - FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); - options.PushString(buffer); - indicies.Push(5); - index++; - break; + if (!nightmare) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Nightmare Difficulty"); + options.PushString(buffer); + indicies.Push(4); + index++; + } + + if (!apollyon) + { + FormatEx(buffer, sizeof(buffer), "%t", "SF2 Apollyon Difficulty"); + options.PushString(buffer); + indicies.Push(5); + index++; + } + break; + } } if (random) @@ -425,7 +441,7 @@ void RandomizeVoteMenu() } } -static int Menu_Main(Handle menu, MenuAction action, int param1, int param2) +static int Menu_Main(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -433,7 +449,7 @@ static int Menu_Main(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } case 1: { @@ -457,11 +473,11 @@ static int Menu_Main(Handle menu, MenuAction action, int param1, int param2) } case 6: { - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } case 7: { - DisplayMenu(g_MenuCredits, param1, MENU_TIME_FOREVER); + g_MenuCredits.Display(param1, MENU_TIME_FOREVER); } case 8: { @@ -592,6 +608,44 @@ static int Menu_VoteNoneDifficulty(Menu menu, MenuAction action,int param1,int p return 0; } +static Action Timer_HighVoteDifficulty(Handle timer) +{ + if (timer != g_VoteTimer || IsRoundEnding()) + { + return Plugin_Stop; + } + + if (NativeVotes_IsVoteInProgress() || IsVoteInProgress()) + { + return Plugin_Continue; // There's another vote in progess. Wait. + } + + int clients[MAXTF2PLAYERS] = { -1, ... }; + int clientsNum; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || g_PlayerEliminated[i]) + { + continue; + } + + clients[clientsNum] = i; + clientsNum++; + } + + RandomizeVoteMenu(true); + VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, 10); + if (GetMenuItemCount(g_MenuVoteDifficulty) == 1) + { + for (int i = 0; i < clientsNum; i++) + { + FakeClientCommand(clients[i], "menuselect 1"); + } + } + + return Plugin_Stop; +} + static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) @@ -621,7 +675,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par char info[64], display[256], color[32], nightmareDisplay[256]; menu.GetItem(param1, info, sizeof(info), _, display, sizeof(display)); - bool rng = false; + bool rng = false, change = false; if (IsSpecialRoundRunning() && (SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) || SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_WALLHAX) || SF_SpecialRound(SPECIALROUND_ESCAPETICKETS))) { @@ -648,6 +702,61 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par } else if (info[0] != '\0') { + if (g_HighDifficultyPercentConVar.FloatValue > 0.0 && (strcmp(info, "4") == 0 || strcmp(info, "5") == 0)) + { + float nrm, hrd, ins, ngt, apl; + for (int i = 0; i < g_Voters.Length; i++) + { + switch (g_Voters.Get(i, 1)) + { + case Difficulty_Normal: + { + nrm += 1.0; + } + case Difficulty_Hard: + { + hrd += 1.0; + } + case Difficulty_Insane: + { + ins += 1.0; + } + case Difficulty_Nightmare: + { + ngt += 1.0; + } + case Difficulty_Apollyon: + { + apl += 1.0; + } + } + } + float values = (ngt + apl) / g_Voters.Length; + if (values < g_HighDifficultyPercentConVar.FloatValue) + { + if (values > 0.5) + { + CPrintToChatAll("%t", "SF2 Difficulty Vote Finished Unsuccessful Runoff", RoundToFloor(g_HighDifficultyPercentConVar.FloatValue * 100.0)); + g_VoteTimer = CreateTimer(1.0, Timer_HighVoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + return 0; + } + else + { + char winner[2] = "1"; + if (hrd > ins && hrd > nrm) + { + winner = "2"; + } + else if (ins > hrd && ins > nrm) + { + winner = "3"; + } + strcopy(info, sizeof(info), winner); + change = true; + } + } + } + g_DifficultyConVar.SetString(info); } else @@ -659,15 +768,43 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par bool normal = StrContains(info, "1") != -1; bool hard = StrContains(info, "2") != -1; bool insane = StrContains(info, "3") != -1; + bool nightmare = StrContains(info, "4") != -1; + bool apollyon = StrContains(info, "5") != -1; - int count = ((normal ? 1 : 0) + (hard ? 1 : 0) + (insane ? 1 : 0)); + int count = ((normal ? 1 : 0) + (hard ? 1 : 0) + (insane ? 1 : 0) + (nightmare ? 1 : 0) + (apollyon ? 1 : 0)); int rand = GetRandomInt(1, count); switch (rand) { + case 5: + { + g_DifficultyConVar.SetInt(Difficulty_Apollyon); + } + case 4: + { + if (nightmare) + { + g_DifficultyConVar.SetInt(Difficulty_Nightmare); + } + else + { + g_DifficultyConVar.SetInt(Difficulty_Apollyon); + } + } case 3: { - g_DifficultyConVar.SetInt(Difficulty_Insane); + if (insane) + { + g_DifficultyConVar.SetInt(Difficulty_Insane); + } + else if (nightmare) + { + g_DifficultyConVar.SetInt(Difficulty_Nightmare); + } + else + { + g_DifficultyConVar.SetInt(Difficulty_Apollyon); + } } case 2: { @@ -675,10 +812,18 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par { g_DifficultyConVar.SetInt(Difficulty_Hard); } - else + else if (insane) { g_DifficultyConVar.SetInt(Difficulty_Insane); } + else if (nightmare) + { + g_DifficultyConVar.SetInt(Difficulty_Nightmare); + } + else + { + g_DifficultyConVar.SetInt(Difficulty_Apollyon); + } } case 1: { @@ -690,10 +835,18 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par { g_DifficultyConVar.SetInt(Difficulty_Hard); } - else + else if (insane) { g_DifficultyConVar.SetInt(Difficulty_Insane); } + else if (nightmare) + { + g_DifficultyConVar.SetInt(Difficulty_Nightmare); + } + else + { + g_DifficultyConVar.SetInt(Difficulty_Apollyon); + } } } } @@ -786,7 +939,14 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par } } - CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished", color, display); + if (!change) + { + CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished", color, display); + } + else + { + CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished Unsuccessful", RoundToFloor(g_HighDifficultyPercentConVar.FloatValue * 100.0), color, display); + } char checker[64]; g_DifficultyVoteRandomConVar.GetString(checker, sizeof(checker)); if (g_MenuVoteDifficulty.ItemCount > 1) @@ -800,7 +960,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par return 0; } -static int Menu_Help(Handle menu, MenuAction action, int param1, int param2) +static int Menu_Help(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -808,27 +968,27 @@ static int Menu_Help(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuHelpObjective, param1, 30); + g_MenuHelpObjective.Display(param1, 30); } case 1: { - DisplayMenu(g_MenuHelpCommands, param1, 30); + g_MenuHelpCommands.Display(param1, 30); } case 2: { - DisplayMenu(g_MenuHelpClasinfo, param1, 30); + g_MenuHelpClassInfo.Display(param1, 30); } case 3: { - DisplayMenu(g_MenuHelpGhostMode, param1, 30); + g_MenuHelpGhostMode.Display(param1, 30); } case 4: { - DisplayMenu(g_MenuHelpSprinting, param1, 30); + g_MenuHelpSprinting.Display(param1, 30); } case 5: { - DisplayMenu(g_MenuHelpControls, param1, 30); + g_MenuHelpControls.Display(param1, 30); } } } @@ -836,13 +996,13 @@ static int Menu_Help(Handle menu, MenuAction action, int param1, int param2) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } return 0; } -static int Menu_HelpObjective(Handle menu, MenuAction action, int param1, int param2) +static int Menu_HelpObjective(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -850,18 +1010,18 @@ static int Menu_HelpObjective(Handle menu, MenuAction action, int param1, int pa { case 0: { - DisplayMenu(g_MenuHelpObjective2, param1, 30); + g_MenuHelpObjective2.Display(param1, 30); } case 1: { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } } } return 0; } -static int Menu_HelpObjective2(Handle menu, MenuAction action, int param1, int param2) +static int Menu_HelpObjective2(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -869,14 +1029,14 @@ static int Menu_HelpObjective2(Handle menu, MenuAction action, int param1, int p { case 0: { - DisplayMenu(g_MenuHelpObjective, param1, 30); + g_MenuHelpObjective.Display(param1, 30); } } } return 0; } -static int Menu_BackButtonOnly(Handle menu, MenuAction action, int param1, int param2) +static int Menu_BackButtonOnly(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -884,14 +1044,14 @@ static int Menu_BackButtonOnly(Handle menu, MenuAction action, int param1, int p { case 0: { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } } } return 0; } -static int Menu_Credits(Handle menu, MenuAction action, int param1, int param2) +static int Menu_Credits(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -899,18 +1059,18 @@ static int Menu_Credits(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuCredits1, param1, MENU_TIME_FOREVER); + g_MenuCredits1.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } } return 0; } -static int Menu_Credits1(Handle menu, MenuAction action, int param1, int param2) +static int Menu_Credits1(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -918,45 +1078,45 @@ static int Menu_Credits1(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuCredits2, param1, MENU_TIME_FOREVER); + g_MenuCredits2.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits, param1, MENU_TIME_FOREVER); + g_MenuCredits.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_ClassInfo(Handle menu, MenuAction action, int param1, int param2) +static int Menu_ClassInfo(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuHelp, param1, 30); + g_MenuHelp.Display(param1, 30); } } else if (action == MenuAction_Select) { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); - Handle menuHandle = CreateMenu(Menu_ClassInfoBackOnly); + Menu menuHandle = new Menu(Menu_ClassInfoBackOnly); char title[64], description[64]; FormatEx(title, sizeof(title), "SF2 Help %s Class Info Menu Title", info); FormatEx(description, sizeof(description), "SF2 Help %s Class Info Description", info); - SetMenuTitle(menuHandle, "%t%t\n \n%t\n \n", "SF2 Prefix", title, description); - AddMenuItem(menuHandle, "0", "Back"); - DisplayMenu(menuHandle, param1, 30); + menuHandle.SetTitle("%t%t\n \n%t\n \n", "SF2 Prefix", title, description); + menuHandle.AddItem("0", "Back"); + menuHandle.Display(param1, 30); } return 0; } -static int Menu_ClassInfoBackOnly(Handle menu, MenuAction action, int param1, int param2) +static int Menu_ClassInfoBackOnly(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_End) { @@ -964,12 +1124,12 @@ static int Menu_ClassInfoBackOnly(Handle menu, MenuAction action, int param1, in } else if (action == MenuAction_Select) { - DisplayMenu(g_MenuHelpClasinfo, param1, 30); + g_MenuHelpClassInfo.Display(param1, 30); } return 0; } -static int Menu_Settings(Handle menu, MenuAction action, int param1, int param2) +static int Menu_Settings(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -977,22 +1137,22 @@ static int Menu_Settings(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuSettingsPvP, param1, 30); + g_MenuSettingsPvP.Display(param1, 30); } case 1: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Hints Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsHints, 30); + panel.Send(param1, Panel_SettingsHints, 30); delete panel; } case 2: @@ -1000,14 +1160,14 @@ static int Menu_Settings(Handle menu, MenuAction action, int param1, int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Mute Mode Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Normal"); - DrawPanelItem(panel, "Mute opposing team"); - DrawPanelItem(panel, "Mute opposing team except when I'm a proxy"); + panel.DrawItem("Normal"); + panel.DrawItem("Mute opposing team"); + panel.DrawItem("Mute opposing team except when I'm a proxy"); - SendPanelToClient(panel, param1, Panel_SettingsMuteMode, 30); + panel.Send(param1, Panel_SettingsMuteMode, 30); delete panel; } case 3: @@ -1015,15 +1175,15 @@ static int Menu_Settings(Handle menu, MenuAction action, int param1, int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Film Grain Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsFilmGrain, 30); + panel.Send(param1, Panel_SettingsFilmGrain, 30); delete panel; } case 4: @@ -1035,13 +1195,13 @@ static int Menu_Settings(Handle menu, MenuAction action, int param1, int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Hud Version Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Use the new HUD"); - DrawPanelItem(panel, "Use the legacy HUD"); + panel.DrawItem("Use the new HUD"); + panel.DrawItem("Use the legacy HUD"); - SendPanelToClient(panel, param1, Panel_SettingsHudVersion, 30); + panel.Send(param1, Panel_SettingsHudVersion, 30); delete panel; } case 6: @@ -1049,77 +1209,98 @@ static int Menu_Settings(Handle menu, MenuAction action, int param1, int param2) char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Proxy Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); FormatEx(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsProxy, 30); + panel.Send(param1, Panel_SettingsProxy, 30); delete panel; } case 7: { - DisplayMenu(g_MenuSettingsFlashlightTemp1, param1, 30); + char buffer[512]; + Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Music Volume Title", param1); + + Panel panel = new Panel(); + panel.SetTitle(buffer); + + panel.DrawItem("0%"); + panel.DrawItem("25%"); + panel.DrawItem("50%"); + panel.DrawItem("75%"); + panel.DrawItem("100% (Default)"); + + panel.Send(param1, Panel_SettingsMusicVolume, 30); + delete panel; } case 8: + { + g_MenuSettingsFlashlightTemp1.Display(param1, 30); + } + case 9: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Ghost Mode Teleport Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Teleport to only players"); - DrawPanelItem(panel, "Teleport to only bosses"); + panel.DrawItem("Teleport to only players"); + panel.DrawItem("Teleport to only bosses"); - SendPanelToClient(panel, param1, Panel_SettingsGhostModeTeleport, 30); + panel.Send(param1, Panel_SettingsGhostModeTeleport, 30); delete panel; } - case 9: + case 10: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Ghost Mode Toggle State Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Default state"); - DrawPanelItem(panel, "Enable ghost mode upon grace period ends"); - DrawPanelItem(panel, "Enable ghost mode upon death on RED"); + panel.DrawItem("Default state"); + panel.DrawItem("Enable ghost mode upon grace period ends"); + panel.DrawItem("Enable ghost mode upon death on RED"); - SendPanelToClient(panel, param1, Panel_SettingsGhostModeToggleState, 30); + panel.Send(param1, Panel_SettingsGhostModeToggleState, 30); delete panel; } - case 10: + case 11: { char buffer[512]; FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Proxy Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "Enable Ask Message"); - DrawPanelItem(panel, "Disable Ask Message"); + panel.DrawItem("Enable Ask Message"); + panel.DrawItem("Disable Ask Message"); - SendPanelToClient(panel, param1, Panel_SettingsProxyAskMenu, 30); + panel.Send(param1, Panel_SettingsProxyAskMenu, 30); delete panel; } + case 12: + { + GetPvEMenu().Display(param1, 30); + } } } else if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } return 0; } -static int Menu_Settings_Flashlighttemp1(Handle menu, MenuAction action, int param1, int param2) +static int Menu_Settings_Flashlighttemp1(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1176,18 +1357,19 @@ static int Menu_Settings_Flashlighttemp1(Handle menu, MenuAction action, int par ClientSaveCookies(param1); } } + CPrintToChat(param1, "%t", "SF2 Flashlight Temperature Changed", g_PlayerPreferences[param1].PlayerPreference_FlashlightTemperature); } else if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } return 0; } -static int Panel_SettingsFilmGrain(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsFilmGrain(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1207,12 +1389,12 @@ static int Panel_SettingsFilmGrain(Handle menu, MenuAction action, int param1, i } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsHints(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsHints(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1232,12 +1414,12 @@ static int Panel_SettingsHints(Handle menu, MenuAction action, int param1, int p } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsProxy(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsProxy(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1257,12 +1439,12 @@ static int Panel_SettingsProxy(Handle menu, MenuAction action, int param1, int p } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsProxyAskMenu(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsProxyAskMenu(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1282,12 +1464,12 @@ static int Panel_SettingsProxyAskMenu(Handle menu, MenuAction action, int param1 } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsMuteMode(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsMuteMode(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1316,12 +1498,12 @@ static int Panel_SettingsMuteMode(Handle menu, MenuAction action, int param1, in } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsGhostModeTeleport(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsGhostModeTeleport(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1343,12 +1525,12 @@ static int Panel_SettingsGhostModeTeleport(Handle menu, MenuAction action, int p } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsGhostModeToggleState(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsGhostModeToggleState(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1377,12 +1559,30 @@ static int Panel_SettingsGhostModeToggleState(Handle menu, MenuAction action, in } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -int Panel_SettingsHudVersion(Handle menu, MenuAction action, int param1, int param2) +static int Panel_SettingsMusicVolume(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select) + { + float volume; + for (int i = 1; i < param2; i++) + { + volume += 0.25; + } + g_PlayerPreferences[param1].PlayerPreference_MusicVolume = volume; + CPrintToChat(param1, "%t", "SF2 Music Volum Changed", RoundToNearest(volume * 100.0)); + ClientSaveCookies(param1); + + g_MenuSettings.Display(param1, 30); + } + return 0; +} + +int Panel_SettingsHudVersion(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1402,12 +1602,12 @@ int Panel_SettingsHudVersion(Handle menu, MenuAction action, int param1, int par } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -int Panel_SettingsViewBobbing(Handle menu, MenuAction action, int param1, int param2) +int Panel_SettingsViewBobbing(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1427,12 +1627,12 @@ int Panel_SettingsViewBobbing(Handle menu, MenuAction action, int param1, int pa } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Menu_Credits2(Handle menu, MenuAction action, int param1, int param2) +static int Menu_Credits2(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1440,17 +1640,18 @@ static int Menu_Credits2(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuCredits3, param1, MENU_TIME_FOREVER); + g_MenuCredits3.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits1, param1, MENU_TIME_FOREVER); + g_MenuCredits1.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Credits3(Handle menu, MenuAction action, int param1, int param2) + +static int Menu_Credits3(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1458,17 +1659,18 @@ static int Menu_Credits3(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuCredits4, param1, MENU_TIME_FOREVER); + g_MenuCredits4.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits2, param1, MENU_TIME_FOREVER); + g_MenuCredits2.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Credits4(Handle menu, MenuAction action, int param1, int param2) + +static int Menu_Credits4(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1476,17 +1678,18 @@ static int Menu_Credits4(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuCredits5, param1, MENU_TIME_FOREVER); + g_MenuCredits5.Display(param1, MENU_TIME_FOREVER); } case 1: { - DisplayMenu(g_MenuCredits3, param1, MENU_TIME_FOREVER); + g_MenuCredits3.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Credits5(Handle menu, MenuAction action, int param1, int param2) + +static int Menu_Credits5(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -1494,26 +1697,16 @@ static int Menu_Credits5(Handle menu, MenuAction action, int param1, int param2) { case 0: { - DisplayMenu(g_MenuCredits4, param1, MENU_TIME_FOREVER); + g_MenuCredits4.Display(param1, MENU_TIME_FOREVER); } } } return 0; } -static int Menu_Update(Handle menu, MenuAction action, int param1, int param2) -{ - if (action == MenuAction_Select) - { - if (param2 == 0) - { - DisplayMenu(g_MenuMain, param1, 30); - } - } - return 0; -} + void DisplayQueuePointsMenu(int client) { - Handle menu = CreateMenu(Menu_QueuePoints); + Menu menu = new Menu(Menu_QueuePoints); ArrayList queueList = GetQueueList(); char buffer[256]; @@ -1521,7 +1714,7 @@ void DisplayQueuePointsMenu(int client) if (queueList.Length) { FormatEx(buffer, sizeof(buffer), "%T\n \n", "SF2 Reset Queue Points Option", client, g_PlayerQueuePoints[client]); - AddMenuItem(menu, "ponyponypony", buffer); + menu.AddItem("deathcorridor", buffer); int index; char groupName[SF2_MAX_PLAYER_GROUP_NAME_LENGTH]; @@ -1535,7 +1728,7 @@ void DisplayQueuePointsMenu(int client) FormatEx(buffer, sizeof(buffer), "%N - %d", index, g_PlayerQueuePoints[index]); FormatEx(info, sizeof(info), "player_%d", GetClientUserId(index)); - AddMenuItem(menu, info, buffer, g_PlayerPlaying[index] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem(info, buffer, g_PlayerPlaying[index] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); } else { @@ -1546,11 +1739,11 @@ void DisplayQueuePointsMenu(int client) FormatEx(buffer, sizeof(buffer), "[GROUP] %s - %d", groupName, GetPlayerGroupQueuePoints(index)); FormatEx(info, sizeof(info), "group_%d", index); - AddMenuItem(menu, info, buffer, IsPlayerGroupPlaying(index) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem(info, buffer, IsPlayerGroupPlaying(index) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); } else { - for (int i2 = 1; i2 < MaxClients; i2++) + for (int i2 = 1; i2 <= MaxClients; i2++) { if (!IsValidClient(i2)) { @@ -1560,7 +1753,7 @@ void DisplayQueuePointsMenu(int client) { FormatEx(buffer, sizeof(buffer), "%N - %d", i2, g_PlayerQueuePoints[i2]); FormatEx(info, sizeof(info), "player_%d", GetClientUserId(i2)); - AddMenuItem(menu, "player", buffer, g_PlayerPlaying[i2] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + menu.AddItem("player", buffer, g_PlayerPlaying[i2] ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); break; } } @@ -1571,9 +1764,9 @@ void DisplayQueuePointsMenu(int client) delete queueList; - SetMenuTitle(menu, "%t%T\n \n", "SF2 Prefix", "SF2 Queue Menu Title", client); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.SetTitle("%t%T\n \n", "SF2 Prefix", "SF2 Queue Menu Title", client); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) @@ -1587,18 +1780,15 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) } ArrayList playersArray = new ArrayList(); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been created for playersArray in DisplayViewGroupMembersQueueMenu.", playersArray); - #endif - for (int i = 1; i < MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) { continue; } - int iTempGroup = ClientGetPlayerGroup(i); - if (!IsPlayerGroupActive(iTempGroup) || iTempGroup != groupIndex) + int tempGroup = ClientGetPlayerGroup(i); + if (!IsPlayerGroupActive(tempGroup) || tempGroup != groupIndex) { continue; } @@ -1612,8 +1802,8 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) char groupName[SF2_MAX_PLAYER_GROUP_NAME_LENGTH]; GetPlayerGroupName(groupIndex, groupName, sizeof(groupName)); - Handle menuHandle = CreateMenu(Menu_ViewGroupMembersQueue); - SetMenuTitle(menuHandle, "%t%T (%s)\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, groupName); + Menu menuHandle = new Menu(Menu_ViewGroupMembersQueue); + menuHandle.SetTitle("%t%T (%s)\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, groupName); char userId[32]; char name[MAX_NAME_LENGTH * 2]; @@ -1628,11 +1818,11 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) StrCat(name, sizeof(name), " (LEADER)"); } - AddMenuItem(menuHandle, userId, name); + menuHandle.AddItem(userId, name); } - SetMenuExitBackButton(menuHandle, true); - DisplayMenu(menuHandle, client, MENU_TIME_FOREVER); + menuHandle.ExitBackButton = true; + menuHandle.Display(client, MENU_TIME_FOREVER); } else { @@ -1641,12 +1831,9 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) } delete playersArray; - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for playersArray in DisplayViewGroupMembersQueueMenu.", playersArray); - #endif } -static int Menu_ViewGroupMembersQueue(Handle menu, MenuAction action, int param1, int param2) +static int Menu_ViewGroupMembersQueue(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1673,44 +1860,45 @@ void DisplayResetQueuePointsMenu(int client) { char buffer[256]; - Handle menu = CreateMenu(Menu_ResetQueuePoints); + Menu menu = new Menu(Menu_ResetQueuePoints); FormatEx(buffer, sizeof(buffer), "%T", "Yes", client); - AddMenuItem(menu, "0", buffer); + menu.AddItem("0", buffer); FormatEx(buffer, sizeof(buffer), "%T", "No", client); - AddMenuItem(menu, "1", buffer); - SetMenuTitle(menu, "%T\n \n", "SF2 Should Reset Queue Points", client); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.AddItem("1", buffer); + menu.SetTitle("%T\n \n", "SF2 Should Reset Queue Points", client); + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_QueuePoints(Handle menu, MenuAction action, int param1, int param2) +static int Menu_QueuePoints(Menu menu, MenuAction action, int param1, int param2) { switch (action) { case MenuAction_Select: { char info[64]; - GetMenuItem(menu, param2, info, sizeof(info)); + menu.GetItem(param2, info, sizeof(info)); - if (strcmp(info, "ponyponypony", false) == 0) + if (strcmp(info, "deathcorridor", false) == 0) { DisplayResetQueuePointsMenu(param1); } else if (!StrContains(info, "player_")) { + // Do nothing } else if (!StrContains(info, "group_")) { - char sIndex[64]; - strcopy(sIndex, sizeof(sIndex), info); - ReplaceString(sIndex, sizeof(sIndex), "group_", ""); - DisplayViewGroupMembersQueueMenu(param1, StringToInt(sIndex)); + char index[64]; + strcopy(index, sizeof(index), info); + ReplaceString(index, sizeof(index), "group_", ""); + DisplayViewGroupMembersQueueMenu(param1, StringToInt(index)); } } case MenuAction_Cancel: { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } case MenuAction_End: @@ -1721,7 +1909,7 @@ static int Menu_QueuePoints(Handle menu, MenuAction action, int param1, int para return 0; } -static int Menu_ResetQueuePoints(Handle menu, MenuAction action, int param1, int param2) +static int Menu_ResetQueuePoints(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1762,7 +1950,7 @@ static int Menu_ResetQueuePoints(Handle menu, MenuAction action, int param1, int void DisplayBossList(int client) { - Handle menu = CreateMenu(Menu_BossList); + Menu menu = new Menu(Menu_BossList); ArrayList bossList = GetBossProfileList(); @@ -1778,15 +1966,15 @@ void DisplayBossList(int client) { strcopy(displayName, sizeof(displayName), profile); } - AddMenuItem(menu, profile, displayName); + menu.AddItem(profile, displayName); } } - SetMenuTitle(menu, "%t%T\n \n", "SF2 Prefix", "SF2 Boss List Menu Title", client); - SetMenuExitBackButton(menu, true); - DisplayMenu(menu, client, MENU_TIME_FOREVER); + menu.SetTitle("%t%T\n \n", "SF2 Prefix", "SF2 Boss List Menu Title", client); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); } -static int Menu_BossList(Handle menu, MenuAction action, int param1, int param2) +static int Menu_BossList(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -1794,7 +1982,7 @@ static int Menu_BossList(Handle menu, MenuAction action, int param1, int param2) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } case MenuAction_End: @@ -1803,4 +1991,4 @@ static int Menu_BossList(Handle menu, MenuAction action, int param1, int param2) } } return 0; -} \ No newline at end of file +} diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index f763c48a..337a9b75 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -61,9 +61,14 @@ methodmap SF2NPC_BaseNPC } } - public static SF2NPC_BaseNPC FromUniqueId(int iUniqueId) + public static SF2NPC_BaseNPC FromUniqueId(int uniqueID) { - return SF2NPC_BaseNPC(NPCGetFromUniqueID(iUniqueId)); + return SF2NPC_BaseNPC(NPCGetFromUniqueID(uniqueID)); + } + + public static SF2NPC_BaseNPC FromEntity(int entity) + { + return SF2NPC_BaseNPC(NPCGetFromEntIndex(entity)); } property int EntRef @@ -82,12 +87,31 @@ methodmap SF2NPC_BaseNPC } } + public SF2BossProfileData GetProfileData() + { + return NPCGetProfileData(this.Index); + } + + property int Difficulty + { + public get() + { + return GetLocalGlobalDifficulty(this.Index); + } + + public set(int value) + { + NPCChaserSetBoxingDifficulty(this.Index, value); + } + } + property int Flags { public get() { return NPCGetFlags(this.Index); } + public set(int flags) { NPCSetFlags(this.Index, flags); @@ -115,7 +139,12 @@ methodmap SF2NPC_BaseNPC return GetSlenderModel(this.Index, modelState, buffer, bufferLen); } - property int RaidHitbox + public int GetMaxCopies(int difficulty) + { + return g_SlenderMaxCopies[this.Index][difficulty]; + } + + property bool RaidHitbox { public get() { @@ -123,49 +152,54 @@ methodmap SF2NPC_BaseNPC } } - property float TurnRate + property float ScareRadius { public get() { - return NPCGetTurnRate(this.Index); + return NPCGetScareRadius(this.Index); } } - property float FOV + property float ScareCooldown { public get() { - return NPCGetFOV(this.Index); + return NPCGetScareCooldown(this.Index); } } - property float ScareRadius + property SF2NPC_BaseNPC CopyMaster { public get() { - return NPCGetScareRadius(this.Index); + return NPCGetCopyMaster(this); + } + + public set(SF2NPC_BaseNPC value) + { + g_SlenderCopyMaster[this.Index] = value.Index; } } - property float ScareCooldown + property bool IsCopy { public get() { - return NPCGetScareCooldown(this.Index); + return this.CopyMaster.IsValid(); } } - property float InstantKillRadius + property SF2NPC_BaseNPC CompanionMaster { public get() { - return NPCGetInstantKillRadius(this.Index); + return NPCGetCompanionMaster(this); } - } - public float GetInstantKillCooldown(int difficulty) - { - return NPCGetInstantKillCooldown(this.Index, difficulty); + public set(SF2NPC_BaseNPC value) + { + g_SlenderCompanionMaster[this.Index] = value.Index; + } } property int TeleportType @@ -212,6 +246,7 @@ methodmap SF2NPC_BaseNPC { return NPCHasDeathCamEnabled(this.Index); } + public set(bool state) { NPCSetDeathCamEnabled(this.Index, state); @@ -230,7 +265,10 @@ methodmap SF2NPC_BaseNPC public void UnSpawn() { - RemoveSlender(this.Index); + if (IsValidEntity(this.EntIndex)) + { + RemoveEntity(this.EntIndex); + } } public void Remove() @@ -242,7 +280,7 @@ methodmap SF2NPC_BaseNPC { public get() { - SlenderCanRemove(this.Index); + return SlenderCanRemove(this.Index); } } @@ -266,6 +304,11 @@ methodmap SF2NPC_BaseNPC NPCSetProfile(this.Index, profileName); } + public void GetName(char[] buffer, int bufferLen) + { + NPCGetBossName(this.Index, buffer, bufferLen); + } + public void RemoveFromGame() { RemoveProfile(this.Index); @@ -276,11 +319,6 @@ methodmap SF2NPC_BaseNPC NPCGetDistanceFromEntity(this.Index, entity); } - public float GetSpeed(int difficulty) - { - return NPCGetSpeed(this.Index, difficulty); - } - public float GetAddSpeed() { return NPCGetAddSpeed(this.Index); @@ -291,31 +329,6 @@ methodmap SF2NPC_BaseNPC NPCSetAddSpeed(this.Index, value); } - public float GetMaxSpeed(int difficulty) - { - return NPCGetMaxSpeed(this.Index, difficulty); - } - - public float GetAddMaxSpeed() - { - return NPCGetAddMaxSpeed(this.Index); - } - - public void SetAddMaxSpeed(float value) - { - NPCSetAddMaxSpeed(this.Index, value); - } - - public bool GetAbsOrigin(float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) - { - return SlenderGetAbsOrigin(this.Index, buffer, defaultValue); - } - - public float GetAcceleration(int difficulty) - { - return NPCGetAcceleration(this.Index, difficulty); - } - public float GetAddAcceleration() { return NPCGetAddAcceleration(this.Index); @@ -332,6 +345,7 @@ methodmap SF2NPC_BaseNPC { NPCGetAddAcceleration(this.Index); } + public set(float amount) { NPCSetAddAcceleration(this.Index, amount); @@ -379,7 +393,7 @@ methodmap SF2NPC_BaseNPC return NPCGetAttributeValue(this.Index, attributeIndex); } - public bool CanBeSeen(bool fov = true, bool blink = false) + public bool CanBeSeen(bool fov = true, bool blink = false, bool checkEliminated = true) { return PeopleCanSeeSlender(this.Index, fov, blink); } @@ -393,6 +407,16 @@ methodmap SF2NPC_BaseNPC { NPCAddCompanions(this); } + + public bool IsAffectedBySight() + { + return NPCGetAffectedBySightState(this.Index); + } + + public void SetAffectedBySight(bool state) + { + NPCSetAffectedBySightState(this.Index, state); + } } methodmap SF2_BasePlayer < CBaseCombatCharacter @@ -466,7 +490,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { public get() { - return !!GetEntProp(this.index, Prop_Send, "m_nForceTauntCam"); + return GetEntProp(this.index, Prop_Send, "m_nForceTauntCam") != 0; } } @@ -478,6 +502,14 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public bool IsMoving() + { + int buttons = this.Buttons; + return buttons & IN_FORWARD || buttons & IN_BACK || buttons & IN_RIGHT || + buttons & IN_LEFT || buttons & IN_MOVERIGHT || buttons & IN_MOVELEFT || + buttons & IN_JUMP; + } + property int Health { public get() @@ -542,6 +574,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerHasRegenerationItem[this.index]; } + public set(bool state) { g_PlayerHasRegenerationItem[this.index] = state; @@ -615,7 +648,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter GetEntDataVector(this.index, offset, buffer); } - public void SetDataVector(int offset, const float buffer[3], bool state=false) + public void SetDataVector(int offset, const float buffer[3], bool state = false) { SetEntDataVector(this.index, offset, buffer, state); } @@ -645,16 +678,6 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter UTIL_ClientScreenShake(this.index, amp, duration, freq); } - public int ShowHudText(Handle hudSync, const char[] buffer) - { - return ShowSyncHudText(this.index, hudSync, buffer); - } - - public any SDK_Call(Handle handle) - { - return SDKCall(handle, this.index); - } - public void ViewPunch(const float punchVel[3]) { ClientViewPunch(this.index, punchVel); @@ -685,24 +708,36 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + public bool GetName(char[] name, int length) + { + return GetClientName(this.index, name, length); + } + property int LastButtons { public get() { return g_PlayerLastButtons[this.index]; } + public set(int value) { g_PlayerLastButtons[this.index] = value; } } + public void ScreenFade(int duration, int time, int flags, int r, int g, int b, int a) + { + UTIL_ScreenFade(this.index, duration, time, flags, r, g, b, a); + } + property bool IsEliminated { public get() { return g_PlayerEliminated[this.index]; } + public set(bool state) { g_PlayerEliminated[this.index] = state; @@ -728,6 +763,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerProxy[this.index]; } + public set(bool state) { g_PlayerProxy[this.index] = state; @@ -740,6 +776,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerProxyControl[this.index]; } + public set(int value) { if (value < 0) @@ -760,6 +797,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerProxyMaster[this.index]; } + public set(int value) { g_PlayerProxyMaster[this.index] = value; @@ -774,6 +812,14 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + property bool IsInPvE + { + public get() + { + return IsClientInPvE(this.index); + } + } + property bool IsInDeathCam { public get() @@ -824,6 +870,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return ClientGetFlashlightBatteryLife(this.index); } + public set(float value) { ClientSetFlashlightBatteryLife(this.index, value); @@ -876,6 +923,14 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter ClientSetSprintPoints(this.index, value); } + property bool HasStartedBlinking + { + public get() + { + return ClientHasStartedBlinking(this.index); + } + } + property bool IsBlinking { public get() @@ -900,6 +955,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return ClientGetBlinkMeter(this.index); } + public set(float amount) { ClientSetBlinkMeter(this.index, amount); @@ -935,12 +991,18 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerPageCount[this.index]; } + public set(int amount) { g_PlayerPageCount[this.index] = amount; } } + public bool HasHint(int hint) + { + return ClientHasHint(this.index, hint); + } + public void ShowHint(int hint) { ClientShowHint(this.index, hint); @@ -952,6 +1014,7 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerTrapped[this.index]; } + public set(bool state) { g_PlayerTrapped[this.index] = state; @@ -964,12 +1027,52 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { return g_PlayerTrapCount[this.index]; } + public set(int amount) { g_PlayerTrapCount[this.index] = amount; } } + property bool IsLatched + { + public get() + { + return g_PlayerLatchedByTongue[this.index]; + } + + public set(bool state) + { + g_PlayerLatchedByTongue[this.index] = state; + } + } + + property int LatchCount + { + public get() + { + return g_PlayerLatchCount[this.index]; + } + + public set(int amount) + { + g_PlayerLatchCount[this.index] = amount; + } + } + + property int Latcher + { + public get() + { + return g_PlayerLatcher[this.index]; + } + + public set(int value) + { + g_PlayerLatcher[this.index] = value; + } + } + public void UpdateMusicSystem(bool initialize = false) { ClientUpdateMusicSystem(this.index, initialize); @@ -1007,470 +1110,1575 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter { AFK_CheckTime(this.index); } -} - -methodmap SF2NPC_Chaser < SF2NPC_BaseNPC -{ - property float WakeRadius - { - public get() - { - return NPCChaserGetWakeRadius(this.Index); - } - } - property bool StunEnabled + public bool ShouldBeForceChased(SF2NPC_BaseNPC controller) { - public get() - { - return NPCChaserIsStunEnabled(this.Index); - } + return ShouldClientBeForceChased(controller, this); } - property bool StunByFlashlightEnabled + public void SetForceChaseState(SF2NPC_BaseNPC controller, bool value) { - public get() - { - return NPCChaserIsStunByFlashlightEnabled(this.Index); - } + SetClientForceChaseState(controller, this, value); } - property float StunFlashlightDamage + public bool IsLookingAtBoss(SF2NPC_BaseNPC controller) { - public get() - { - return NPCChaserGetStunFlashlightDamage(this.Index); - } + return g_PlayerSeesSlender[this.index][controller.Index]; } +} - property float StunCooldown +methodmap SF2NPC_Chaser < SF2NPC_BaseNPC +{ + public SF2ChaserBossProfileData GetProfileData() { - public get() - { - return NPCChaserGetStunCooldown(this.Index); - } + return NPCChaserGetProfileData(this.Index); } - property float StunHealth + public float GetInitialDeathHealth(int difficulty) { - public get() - { - return NPCChaserGetStunHealth(this.Index); - } - public set(float amount) - { - NPCChaserSetStunHealth(this.Index, amount); - } + return NPCChaserGetDeathInitialHealth(this.Index, difficulty); } - property float InitialStunHealth + public float GetDeathHealth(int difficulty) { - public get() - { - return NPCChaserGetStunInitialHealth(this.Index); - } - public set(float amount) - { - NPCChaserSetStunInitialHealth(this.Index, amount); - } + return NPCChaserGetDeathHealth(this.Index, difficulty); } - public void TriggerStun(int entity, char profile[SF2_MAX_PROFILE_NAME_LENGTH], float pos[3]) + public void SetDeathHealth(int difficulty, float amount) { - NPCBossTriggerStun(this.Index, entity, profile, pos); + NPCChaserSetDeathHealth(this.Index, difficulty, amount); } - property bool HasDamageParticles + property float StunHealthAdd { public get() { - return NPCChaserDamageParticlesEnabled(this.Index); + return NPCChaserGetAddStunHealth(this.Index); } - } - property bool UseShootGesture - { - public get() + public set(float value) { - return NPCChaserUseShootGesture(this.Index); + NPCChaserSetAddStunHealth(this.Index, value); } } - property bool CloakEnabled + property ArrayList ChaseOnLookTargets { public get() { - return NPCChaserIsCloakEnabled(this.Index); + return NPCChaserGetAutoChaseTargets(this.Index); } } - public float GetCloakCooldown(int difficulty) + public SF2NPC_Chaser(int index) { - return NPCChaserGetCloakCooldown(this.Index, difficulty); + return view_as(SF2NPC_BaseNPC(index)); } +} - public float GetCloakRange(int difficulty) +methodmap SF2NPC_Statue < SF2NPC_BaseNPC +{ + public SF2NPC_Statue(int index) { - return NPCChaserGetCloakRange(this.Index, difficulty); + return view_as(SF2NPC_BaseNPC(index)); } - public float GetDecloakRange(int difficulty) + public SF2StatueBossProfileData GetProfileData() { - return NPCChaserGetDecloakRange(this.Index, difficulty); + return NPCStatueGetProfileData(this.Index); } - public float GetCloakSpeedMultiplier(int difficulty) + public float GetIdleLifetime(int difficulty) { - return NPCChaserGetCloakSpeedMultiplier(this.Index, difficulty); + return NPCStatueGetIdleLifetime(this.Index, difficulty); } +} + +void SetupMethodmapAPI() +{ + CreateNative("SF2_Player.UserID.get", Native_GetClientUserID); + CreateNative("SF2_Player.IsValid.get", Native_GetIsValidClient); + CreateNative("SF2_Player.IsAlive.get", Native_GetIsClientAlive); + CreateNative("SF2_Player.IsInGame.get", Native_GetIsClientInGame); + CreateNative("SF2_Player.IsBot.get", Native_GetIsClientBot); + CreateNative("SF2_Player.IsSourceTV.get", Native_GetIsClientSourceTV); + CreateNative("SF2_Player.IsReplay.get", Native_GetIsClientReplay); + CreateNative("SF2_Player.InThirdPerson.get", Native_GetIsClientInThirdPerson); + CreateNative("SF2_Player.Buttons.get", Native_GetClientButtons); + CreateNative("SF2_Player.IsMoving", Native_GetIsClientMoving); + CreateNative("SF2_Player.Health.get", Native_GetClientHealth); + CreateNative("SF2_Player.MaxHealth.get", Native_GetClientMaxHealth); + CreateNative("SF2_Player.Ducking.get", Native_GetIsClientDucking); + CreateNative("SF2_Player.Ducked.get", Native_GetIsClientDucked); + CreateNative("SF2_Player.GetDataEnt", Native_GetClientDataEnt); + CreateNative("SF2_Player.Class.get", Native_GetClientClass); + CreateNative("SF2_Player.Team.get", Native_GetClientTeam); + CreateNative("SF2_Player.HasRegenItem.get", Native_GetClientHasRegenItem); + CreateNative("SF2_Player.HasRegenItem.set", Native_SetClientHasRegenItem); + CreateNative("SF2_Player.InCondition", Native_GetClientInCondition); + CreateNative("SF2_Player.ChangeCondition", Native_ClientChangeCondition); + CreateNative("SF2_Player.IsCritBoosted", Native_GetClientIsCritBoosted); + CreateNative("SF2_Player.IsMiniCritBoosted", Native_GetClientIsMiniCritBoosted); + CreateNative("SF2_Player.Ignite", Native_ClientIgnite); + CreateNative("SF2_Player.Bleed", Native_ClientBleed); + CreateNative("SF2_Player.Stun", Native_ClientStun); + CreateNative("SF2_Player.Regenerate", Native_ClientRegenerate); + CreateNative("SF2_Player.SetClass", Native_ClientSetClass); + CreateNative("SF2_Player.GetEyePosition", Native_GetClientEyePosition); + CreateNative("SF2_Player.GetEyeAngles", Native_GetClientEyeAngles); + CreateNative("SF2_Player.GetDataVector", Native_GetClientDataVector); + CreateNative("SF2_Player.SetDataVector", Native_SetClientDataVector); + CreateNative("SF2_Player.GetDistanceFromEntity", Native_GetClientDistanceFromEntity); + CreateNative("SF2_Player.GetWeaponSlot", Native_GetClientWeaponSlot); + CreateNative("SF2_Player.SwitchToWeaponSlot", Native_ClientSwitchToWeaponSlot); + CreateNative("SF2_Player.RemoveWeaponSlot", Native_ClientRemoveWeaponSlot); + CreateNative("SF2_Player.ScreenShake", Native_ClientScreenShake); + CreateNative("SF2_Player.ViewPunch", Native_ClientViewPunch); + CreateNative("SF2_Player.TakeDamage", Native_ClientTakeDamage); + CreateNative("SF2_Player.Respawn", Native_ClientRespawn); + CreateNative("SF2_Player.UpdateListeningFlags", Native_ClientUpdateListeningFlags); + CreateNative("SF2_Player.IsParticipating.get", Native_GetClientIsParticipating); + CreateNative("SF2_Player.GetName", Native_GetClientName); + CreateNative("SF2_Player.LastButtons.get", Native_GetClientLastButtons); + CreateNative("SF2_Player.LastButtons.set", Native_SetClientLastButtons); + CreateNative("SF2_Player.ScreenFade", Native_ClientScreenFade); + CreateNative("SF2_Player.IsEliminated.get", Native_GetClientIsEliminated); + CreateNative("SF2_Player.IsEliminated.set", Native_SetClientIsEliminated); + CreateNative("SF2_Player.IsInGhostMode.get", Native_GetClientIsInGhostMode); + CreateNative("SF2_Player.SetGhostState", Native_SetClientGhostState); + CreateNative("SF2_Player.IsProxy.get", Native_GetClientIsProxy); + CreateNative("SF2_Player.IsProxy.set", Native_SetClientIsProxy); + CreateNative("SF2_Player.ProxyControl.get", Native_GetClientProxyControl); + CreateNative("SF2_Player.ProxyControl.set", Native_SetClientProxyControl); + CreateNative("SF2_Player.ProxyMaster.get", Native_GetClientProxyMaster); + CreateNative("SF2_Player.ProxyMaster.set", Native_SetClientProxyMaster); + CreateNative("SF2_Player.IsInPvP.get", Native_GetClientIsInPvP); + CreateNative("SF2_Player.IsInPvE.get", Native_GetClientIsInPvE); + CreateNative("SF2_Player.IsInDeathCam.get", Native_GetClientIsInDeathCam); + CreateNative("SF2_Player.StartDeathCam", Native_ClientStartDeathCam); + CreateNative("SF2_Player.HasEscaped.get", Native_GetClientHasEscaped); + CreateNative("SF2_Player.Escape", Native_ClientEscape); + CreateNative("SF2_Player.TeleportToEscapePoint", Native_ClientTeleportToEscapePoint); + CreateNative("SF2_Player.ForceEscape", Native_ClientForceEscape); + CreateNative("SF2_Player.UsingFlashlight.get", Native_GetClientUsingFlashlight); + CreateNative("SF2_Player.HandleFlashlight", Native_ClientHandleFlashlight); + CreateNative("SF2_Player.FlashlightBatteryLife.get", Native_GetClientFlashlightBatteryLife); + CreateNative("SF2_Player.FlashlightBatteryLife.set", Native_SetClientFlashlightBatteryLife); + CreateNative("SF2_Player.ResetFlashlight", Native_ClientResetFlashlight); + CreateNative("SF2_Player.GetFlashlightNextInputTime", Native_GetClientFlashlightNextInputTime); + CreateNative("SF2_Player.IsSprinting.get", Native_GetClientIsSprinting); + CreateNative("SF2_Player.IsReallySprinting.get", Native_GetClientIsReallySprinting); + CreateNative("SF2_Player.HandleSprint", Native_ClientHandleSprint); + CreateNative("SF2_Player.SetSprintTimer", Native_SetClientSprintTimer); + CreateNative("SF2_Player.GetSprintPoints", Native_GetClientSprintPoints); + CreateNative("SF2_Player.SetSprintPoints", Native_SetClientSprintPoints); + CreateNative("SF2_Player.HasStartedBlinking.get", Native_GetClientHasStartedBlinking); + CreateNative("SF2_Player.IsBlinking.get", Native_GetClientIsBlinking); + CreateNative("SF2_Player.IsHoldingBlink", Native_GetClientIsHoldingBlink); + CreateNative("SF2_Player.SetHoldingBlink", Native_SetClientIsHoldingBlink); + CreateNative("SF2_Player.BlinkMeter.get", Native_GetClientBlinkMeter); + CreateNative("SF2_Player.BlinkMeter.set", Native_SetClientBlinkMeter); + CreateNative("SF2_Player.BlinkCount.get", Native_GetClientBlinkCount); + CreateNative("SF2_Player.Blink", Native_ClientBlink); + CreateNative("SF2_Player.StartPeeking", Native_ClientStartPeeking); + CreateNative("SF2_Player.EndPeeking", Native_ClientEndPeeking); + CreateNative("SF2_Player.PageCount.get", Native_GetClientPageCount); + CreateNative("SF2_Player.PageCount.set", Native_SetClientPageCount); + CreateNative("SF2_Player.ShowHint", Native_ClientShowHint); + CreateNative("SF2_Player.IsTrapped.get", Native_GetClientIsTrapped); + CreateNative("SF2_Player.IsTrapped.set", Native_SetClientIsTrapped); + CreateNative("SF2_Player.TrapCount.get", Native_GetClientTrapCount); + CreateNative("SF2_Player.TrapCount.set", Native_SetClientTrapCount); + CreateNative("SF2_Player.IsLatched.get", Native_GetClientIsLatched); + CreateNative("SF2_Player.IsLatched.set", Native_SetClientIsLatched); + CreateNative("SF2_Player.LatchCount.get", Native_GetClientLatchCount); + CreateNative("SF2_Player.LatchCount.set", Native_SetClientLatchCount); + CreateNative("SF2_Player.Latcher.get", Native_GetClientLatcher); + CreateNative("SF2_Player.Latcher.set", Native_SetClientLatcher); + CreateNative("SF2_Player.UpdateMusicSystem", Native_ClientUpdateMusicSystem); + CreateNative("SF2_Player.HasConstantGlow.get", Native_GetClientHasConstantGlow); + CreateNative("SF2_Player.SetPlayState", Native_SetClientPlayState); + CreateNative("SF2_Player.CanSeeSlender", Native_GetClientCanSeeSlender); + CreateNative("SF2_Player.SetAFKTime", Native_SetClientAFKTime); + CreateNative("SF2_Player.SetAFKState", Native_SetClientAFKState); + CreateNative("SF2_Player.CheckAFKTime", Native_CheckClientAFKTime); + CreateNative("SF2_Player.ShouldBeForceChased", Native_GetClientShouldBeForceChased); + CreateNative("SF2_Player.SetForceChaseState", Native_SetClientForceChaseState); + CreateNative("SF2_Player.IsLookingAtBoss", Native_GetClientIsLookingAtBoss); +} - public float GetCrawlSpeedMultiplier(int difficulty) +static any Native_GetClientUserID(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetCrawlSpeedMultiplier(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool HasKeyDrop + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.UserID; +} + +static any Native_GetIsValidClient(Handle plugin, int numParams) +{ + // WOW, the one instance where I don't check if the client is valid but we return it + SF2_BasePlayer player = SF2_BasePlayer(GetNativeCell(1)); + return player.IsValid; +} + +static any Native_GetIsClientAlive(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaseHasKeyDrop(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool ProjectileEnabled + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsAlive; +} + +static any Native_GetIsClientInGame(Handle plugin, int numParams) +{ + SF2_BasePlayer player = SF2_BasePlayer(GetNativeCell(1)); + return player.IsInGame; +} + +static any Native_GetIsClientBot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserIsProjectileEnabled(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool ProjectileUsesAmmo + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsBot; +} + +static any Native_GetIsClientSourceTV(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserUseProjectileAmmo(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property int ProjectileType + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsSourceTV; +} + +static any Native_GetIsClientReplay(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetProjectileType(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetProjectileCooldownMin(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsReplay; +} + +static any Native_GetIsClientInThirdPerson(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileCooldownMin(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetProjectileCooldownMax(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.InThirdPerson; +} + +static any Native_GetClientButtons(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileCooldownMax(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetProjectileSpeed(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Buttons; +} + +static any Native_GetIsClientMoving(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileSpeed(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetProjectileDamage(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsMoving(); +} + +static any Native_GetClientHealth(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileDamage(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetProjectileRadius(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Health; +} + +static any Native_GetClientMaxHealth(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileRadius(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetProjectileReloadTime(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.MaxHealth; +} + +static any Native_GetIsClientDucking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetProjectileReloadTime(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool AdvancedDamageEffectsEnabled + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Ducking; +} + +static any Native_GetIsClientDucked(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserUseAdvancedDamageEffects(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool AttachDamageEffectsParticle + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Ducked; +} + +static any Native_GetClientDataEnt(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserAttachDamageParticle(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool JaratePlayerOnHit - { - public get() - { - return NPCChaserJaratePlayerOnHit(this.Index); - } + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetDataEnt(GetNativeCell(2)); +} + +static any Native_GetClientClass(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Class; +} + +static any Native_GetClientTeam(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Team; +} + +static any Native_GetClientHasRegenItem(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.HasRegenItem; +} + +static any Native_SetClientHasRegenItem(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.HasRegenItem = GetNativeCell(2); + return 0; +} + +static any Native_GetClientInCondition(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.InCondition(GetNativeCell(2)); +} + +static any Native_ClientChangeCondition(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ChangeCondition(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5)); + return 0; +} + +static any Native_GetClientIsCritBoosted(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsCritBoosted(); +} + +static any Native_GetClientIsMiniCritBoosted(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsMiniCritBoosted(); +} + +static any Native_ClientIgnite(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Ignite(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_ClientBleed(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Bleed(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_ClientStun(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Stun(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5)); + return 0; +} + +static any Native_ClientRegenerate(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Regenerate(); + return 0; +} + +static any Native_ClientSetClass(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetClass(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_GetClientEyePosition(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + player.GetEyePosition(buffer); + SetNativeArray(2, buffer, 3); + return 0; +} + +static any Native_GetClientEyeAngles(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + player.GetEyeAngles(buffer); + SetNativeArray(2, buffer, 3); + return 0; +} + +static any Native_GetClientDataVector(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + player.GetDataVector(GetNativeCell(2), buffer); + SetNativeArray(3, buffer, 3); + return 0; +} + +static any Native_SetClientDataVector(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + GetNativeArray(3, buffer, 3); + player.SetDataVector(GetNativeCell(2), buffer); + return 0; +} + +static any Native_GetClientDistanceFromEntity(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetDistanceFromEntity(GetNativeCell(2)); +} + +static any Native_GetClientWeaponSlot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetWeaponSlot(GetNativeCell(2)); +} + +static any Native_ClientSwitchToWeaponSlot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SwitchToWeaponSlot(GetNativeCell(2)); + return 0; +} + +static any Native_ClientRemoveWeaponSlot(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.RemoveWeaponSlot(GetNativeCell(2)); + return 0; +} + +static any Native_ClientScreenShake(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ScreenShake(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +static any Native_ClientViewPunch(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + GetNativeArray(2, buffer, 3); + player.ViewPunch(buffer); + return 0; +} + +static any Native_ClientTakeDamage(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + float force[3], position[3]; + GetNativeArray(8, force, 3); + GetNativeArray(9, position, 3); + SF2_BasePlayer player = SF2_BasePlayer(client); + player.TakeDamage(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), + GetNativeCell(7), force, position, GetNativeCell(10)); + return 0; +} + +static any Native_ClientRespawn(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Respawn(); + return 0; +} + +static any Native_ClientUpdateListeningFlags(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.UpdateListeningFlags(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientIsParticipating(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsParticipating; +} + +static any Native_GetClientName(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + int size; + GetNativeStringLength(2, size); + size++; + char[] buffer = new char[size]; + GetNativeString(2, buffer, size); + SF2_BasePlayer player = SF2_BasePlayer(client); + player.GetName(buffer, size); + SetNativeString(2, buffer, size); + return player.IsParticipating; +} + +static any Native_GetClientLastButtons(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.LastButtons; +} + +static any Native_SetClientLastButtons(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.LastButtons = GetNativeCell(2); + return 0; +} + +static any Native_ClientScreenFade(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ScreenFade(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), GetNativeCell(8)); + return 0; +} + +static any Native_GetClientIsEliminated(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsEliminated; +} + +static any Native_SetClientIsEliminated(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsEliminated = GetNativeCell(2); + return 0; +} + +static any Native_GetClientIsInGhostMode(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInGhostMode; +} + +static any Native_SetClientGhostState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetGhostState(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientIsProxy(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsProxy; +} + +static any Native_SetClientIsProxy(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsProxy = GetNativeCell(2); + return 0; +} + +static any Native_GetClientProxyControl(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.ProxyControl; +} + +static any Native_SetClientProxyControl(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ProxyControl = GetNativeCell(2); + return 0; +} + +static any Native_GetClientProxyMaster(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.ProxyMaster; +} + +static any Native_SetClientProxyMaster(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ProxyMaster = GetNativeCell(2); + return 0; +} + +static any Native_GetClientIsInPvP(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInPvP; +} + +static any Native_GetClientIsInPvE(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetJarateDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInPvE; +} + +static any Native_GetClientIsInDeathCam(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetJarateDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool MilkPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsInDeathCam; +} + +static any Native_ClientStartDeathCam(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserMilkPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetMilkDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + float buffer[3]; + GetNativeArray(3, buffer, 3); + player.StartDeathCam(GetNativeCell(2), buffer, GetNativeCell(4)); + return 0; +} + +static any Native_GetClientHasEscaped(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetMilkDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool GasPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.HasEscaped; +} + +static any Native_ClientEscape(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGasPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetGasDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Escape(); + return 0; +} + +static any Native_ClientTeleportToEscapePoint(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetGasDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool MarkPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + player.TeleportToEscapePoint(); + return 0; +} + +static any Native_ClientForceEscape(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserMarkPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetMarkDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Escape(); + player.TeleportToEscapePoint(); + return 0; +} + +static any Native_GetClientUsingFlashlight(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetMarkDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool IgnitePlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.UsingFlashlight; +} + +static any Native_ClientHandleFlashlight(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserIgnitePlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetIgniteDelay(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.HandleFlashlight(); + return 0; +} + +static any Native_GetClientFlashlightBatteryLife(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetIgniteDelay(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool StunPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.FlashlightBatteryLife; +} + +static any Native_SetClientFlashlightBatteryLife(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserStunPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetStunAttackDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.FlashlightBatteryLife = GetNativeCell(3); + return 0; +} + +static any Native_ClientResetFlashlight(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetStunAttackDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetStunAttackSlowdown(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.ResetFlashlight(); + return 0; +} + +static any Native_GetClientFlashlightNextInputTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetStunAttackSlowdown(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool BleedPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetFlashlightNextInputTime(); +} + +static any Native_GetClientIsSprinting(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserBleedPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetBleedDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsSprinting; +} + +static any Native_GetClientIsReallySprinting(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetBleedDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool ElectricPlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsReallySprinting; +} + +static any Native_ClientHandleSprint(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserElectricPlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetElectricDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.HandleSprint(GetNativeCell(2)); + return 0; +} + +static any Native_SetClientSprintTimer(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetElectricDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetElectricSlowdown(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetSprintTimer(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientSprintPoints(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetElectricSlowdown(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool SmitePlayerOnHit + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.GetSprintPoints(); +} + +static any Native_SetClientSprintPoints(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserSmitePlayerOnHit(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property int State + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetSprintPoints(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientHasStartedBlinking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetState(this.Index); - } - public set(int state) - { - NPCChaserSetState(this.Index, state); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool HasTraps + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.HasStartedBlinking; +} + +static any Native_GetClientIsBlinking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetTrapState(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property int TrapType + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsBlinking; +} + +static any Native_GetClientIsHoldingBlink(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetTrapType(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetTrapCooldown(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsHoldingBlink(); +} + +static any Native_SetClientIsHoldingBlink(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetTrapSpawnTime(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool HasCriticalRockets + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetHoldingBlink(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientBlinkMeter(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserHasCriticalRockets(this.Index); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetWalkSpeed(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.BlinkMeter; +} + +static any Native_SetClientBlinkMeter(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetWalkSpeed(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetMaxWalkSpeed(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.BlinkMeter = GetNativeCell(2); + return 0; +} + +static any Native_GetClientBlinkCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserGetMaxWalkSpeed(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property float StunHealthAdd + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.BlinkCount; +} + +static any Native_ClientBlink(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return NPCChaserGetAddStunHealth(this.Index); - } - public set(float value) - { - NPCChaserSetAddStunHealth(this.Index, value); - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Blink(); + return 0; +} + +static any Native_ClientStartPeeking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void AddStunHealth(float amount) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.StartPeeking(); + return 0; +} + +static any Native_ClientEndPeeking(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - NPCChaserAddStunHealth(this.Index, amount); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public bool CanDisappearOnStun() + SF2_BasePlayer player = SF2_BasePlayer(client); + player.EndPeeking(); + return 0; +} + +static any Native_GetClientPageCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCChaserCanDisappearOnStun(this.Index); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool AutoChaseEnabled + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.PageCount; +} + +static any Native_SetClientPageCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return g_SlenderHasAutoChaseEnabled[this.Index]; - } - public set(bool autoChase) - { - g_SlenderHasAutoChaseEnabled[this.Index] = autoChase; - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - property bool ChasesEndlessly + SF2_BasePlayer player = SF2_BasePlayer(client); + player.PageCount = GetNativeCell(2); + return 0; +} + +static any Native_ClientShowHint(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - public get() - { - return g_SlenderChasesEndlessly[this.Index]; - } - public set(bool chaseEndlessly) - { - g_SlenderChasesEndlessly[this.Index] = chaseEndlessly; - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void SetGoalPos(float newPos[3]) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.EndPeeking(); + return 0; +} + +static any Native_GetClientIsTrapped(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - CopyVector(newPos, g_SlenderGoalPos[this.Index]); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void GetGoalPos(float dest[3]) + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsTrapped; +} + +static any Native_SetClientIsTrapped(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - CopyVector(g_SlenderGoalPos[this.Index], dest); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public void UpdateAnimation(int entity, int state) - { - NPCChaserUpdateBossAnimation(this.Index, entity, state); - } + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsTrapped = GetNativeCell(2); + return 0; +} - public SF2NPC_Chaser(int index) +static any Native_GetClientTrapCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return view_as(SF2NPC_BaseNPC(index)); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.TrapCount; } -methodmap SF2NPC_Statue < SF2NPC_BaseNPC +static any Native_SetClientTrapCount(Handle plugin, int numParams) { - public SF2NPC_Statue(int index) + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return view_as(SF2NPC_BaseNPC(index)); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetIdleLifetime(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.TrapCount = GetNativeCell(2); + return 0; +} + +static any Native_GetClientIsLatched(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCStatueGetIdleLifetime(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.IsLatched; +} + +static any Native_SetClientIsLatched(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.IsLatched = GetNativeCell(2); + return 0; +} + +static any Native_GetClientLatchCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.LatchCount; +} + +static any Native_SetClientLatchCount(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float GetChaseDuration(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + player.LatchCount = GetNativeCell(2); + return 0; +} + +static any Native_GetClientLatcher(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.Latcher; +} + +static any Native_SetClientLatcher(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.Latcher = GetNativeCell(2); + return 0; +} + +static any Native_ClientUpdateMusicSystem(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.UpdateMusicSystem(GetNativeCell(2)); + return 0; +} + +static any Native_GetClientHasConstantGlow(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.HasConstantGlow; +} + +static any Native_SetClientPlayState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetPlayState(GetNativeCell(2), GetNativeCell(3)); + return 0; +} + +static any Native_GetClientCanSeeSlender(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.CanSeeSlender(GetNativeCell(2), GetNativeCell(3), GetNativeCell(4), GetNativeCell(5)); +} + +static any Native_SetClientAFKTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetPlayState(GetNativeCell(2)); + return 0; +} + +static any Native_SetClientAFKState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + player.SetPlayState(GetNativeCell(2), GetNativeCell(3)); + return 0; +} + +static any Native_CheckClientAFKTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + } + + SF2_BasePlayer player = SF2_BasePlayer(client); + return player.CheckAFKTime(); +} + +static any Native_GetClientShouldBeForceChased(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCStatueChaseDuration(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float AddChaseDurationMin(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(GetNativeCell(2)); + return player.ShouldBeForceChased(npc); +} + +static any Native_SetClientForceChaseState(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCStatueGetChaseDurationAddMin(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - public float AddChaseDurationMax(int difficulty) + SF2_BasePlayer player = SF2_BasePlayer(client); + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(GetNativeCell(2)); + player.SetForceChaseState(npc, GetNativeCell(3)); + return 0; +} + +static any Native_GetClientIsLookingAtBoss(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (!IsValidClient(client)) { - return NPCStatueGetChaseDurationAddMax(this.Index, difficulty); + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } + + SF2_BasePlayer player = SF2_BasePlayer(client); + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(GetNativeCell(2)); + return player.IsLookingAtBoss(npc); } diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 85b0cfc9..4244a9e3 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -5,7 +5,7 @@ #pragma semicolon 1 -#define SF2_BOSS_PAGE_CALCULATION 0.3 +#define SF2_BOSS_PAGE_CALCULATION 512.0 #define SF2_BOSS_COPY_SPAWN_MIN_DISTANCE 800.0 // The default minimum distance boss copies can spawn from each other. #define SF2_BOSS_ATTACK_MELEE 0 @@ -25,12 +25,12 @@ static int g_NpcModelSkinMax[MAX_BOSSES] = { 0, ... }; static int g_NpcModelBodyGroups[MAX_BOSSES] = { 0, ... }; static int g_NpcModelBodyGroupsDifficulty[MAX_BOSSES][Difficulty_Max]; static int g_NpcModelBodyGroupsMax[MAX_BOSSES] = { 0, ... }; -int g_NpcRaidHitbox[MAX_BOSSES] = { 0, ... }; +bool g_NpcRaidHitbox[MAX_BOSSES] = { false, ... }; int g_NpcGlowEntity[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; static float g_NpcSoundMusicLoop[MAX_BOSSES][Difficulty_Max]; static int g_NpcAllowMusicOnDifficulty[MAX_BOSSES]; static char g_NpcName[MAX_BOSSES][Difficulty_Max][SF2_MAX_PROFILE_NAME_LENGTH]; -static bool g_NpcHasFakeCopiesEnabled[MAX_BOSSES]; +static bool g_NpcHasFakeCopiesEnabled[MAX_BOSSES][Difficulty_Max]; static float g_NpcBlinkLookRate[MAX_BOSSES]; static float g_NpcBlinkStaticRate[MAX_BOSSES]; static float g_NpcStepSize[MAX_BOSSES]; @@ -49,8 +49,6 @@ static float g_NpcFestiveLightPos[MAX_BOSSES][3]; static float g_NpcFestiveLightAng[MAX_BOSSES][3]; static float g_NpcFieldOfView[MAX_BOSSES] = { 0.0, ... }; -static float g_NpcTurnRate[MAX_BOSSES] = { 0.0, ... }; -static float g_NpcBackstabFOV[MAX_BOSSES] = { 0.0, ... }; static bool g_NpcHasTeleportAllowed[MAX_BOSSES][Difficulty_Max]; static float g_NpcTeleportTimeMin[MAX_BOSSES][Difficulty_Max]; @@ -64,14 +62,10 @@ static float g_NpcJumpscareDistance[MAX_BOSSES][Difficulty_Max]; static float g_NpcJumpscareDuration[MAX_BOSSES][Difficulty_Max]; static float g_NpcJumpscareCooldown[MAX_BOSSES][Difficulty_Max]; static bool g_NpcHasJumpscareOnScare[MAX_BOSSES]; -bool g_NpcIgnoreNonMarkedForChase[MAX_BOSSES]; -g_SlenderEnt[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; +int g_SlenderEnt[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; -static float g_NpcSpeed[MAX_BOSSES][Difficulty_Max]; -static float g_NpcMaxSpeed[MAX_BOSSES][Difficulty_Max]; static float g_NpcAddSpeed[MAX_BOSSES]; -static float g_NpcAddMaxSpeed[MAX_BOSSES]; static float g_NpcAddAcceleration[MAX_BOSSES]; static float g_NpcIdleLifetime[MAX_BOSSES][Difficulty_Max]; @@ -89,20 +83,10 @@ static int g_NpcPlayerScareReplenishSprintAmount[MAX_BOSSES]; static int g_NpcTeleportType[MAX_BOSSES] = { -1, ... }; -static float g_NpcSearchRadius[MAX_BOSSES][Difficulty_Max]; -static float g_NpcHearingRadius[MAX_BOSSES][Difficulty_Max]; -static float g_NpcTauntAlertRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcInstantKillRadius[MAX_BOSSES] = { 0.0, ... }; -static float g_NpcInstantKillCooldown[MAX_BOSSES][Difficulty_Max]; - static bool g_NpcHasDeathCamEnabled[MAX_BOSSES] = { false, ... }; static bool g_NpcHasProxyWeaponsEnabled[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasHealthbarEnabled[MAX_BOSSES] = { false, ... }; - -static int g_NpcEnemy[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; - static float g_NpcProxySpawnChanceMin[MAX_BOSSES][Difficulty_Max]; static float g_NpcProxySpawnChanceMax[MAX_BOSSES][Difficulty_Max]; static float g_NpcProxySpawnChanceThreshold[MAX_BOSSES][Difficulty_Max]; @@ -121,16 +105,11 @@ static int g_NpcDrainCreditAmount[MAX_BOSSES][Difficulty_Max]; static bool g_NpcHasProxySpawnEffectEnabled[MAX_BOSSES]; static float g_NpcProxySpawnEffectZOffset[MAX_BOSSES]; -Handle timerMusic = null;//Planning to add a bosses array on. +static bool g_NpcShouldBeAffectedBySight[MAX_BOSSES]; -float g_LastStuckTime[MAX_BOSSES] = {-1.0, ...}; -bool g_NpcHasCloaked[MAX_BOSSES] = { false, ... }; -bool g_NpcUsedRage1[MAX_BOSSES] = { false, ... }; -bool g_NpcUsedRage2[MAX_BOSSES] = { false, ... }; -bool g_NpcHasUsedRage3[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesRageAnimation[MAX_BOSSES][3]; +Handle timerMusic = null;//Planning to add a bosses array on. -bool NPCGetBossName(int npcIndex = -1, char[] buffer,int bufferLen, char profile[SF2_MAX_PROFILE_NAME_LENGTH] = "") +bool NPCGetBossName(int npcIndex = -1, char[] buffer, int bufferLen, char profile[SF2_MAX_PROFILE_NAME_LENGTH] = "") { if (npcIndex == -1 && profile[0] == '\0') { @@ -212,8 +191,152 @@ void NPCSetDeathCamEnabled(int npcIndex, bool state) void NPCInitialize() { + g_OnBossAddedFwd = new GlobalForward("SF2_OnBossAdded", ET_Ignore, Param_Cell); + g_OnBossSpawnFwd = new GlobalForward("SF2_OnBossSpawn", ET_Ignore, Param_Cell); + g_OnBossDespawnFwd = new GlobalForward("SF2_OnBossDespawn", ET_Ignore, Param_Cell); + g_OnBossRemovedFwd = new GlobalForward("SF2_OnBossRemoved", ET_Ignore, Param_Cell); + g_OnBossFinishSpawningFwd = new GlobalForward("SF2_OnBossFinishSpawning", ET_Ignore, Param_Cell); + g_OnClientCaughtByBossFwd = new GlobalForward("SF2_OnClientCaughtByBoss", ET_Ignore, Param_Cell, Param_Cell); + + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + + CreateNative("SF2_GetMaxBossCount", Native_GetMaxBosses); + CreateNative("SF2_EntIndexToBossIndex", Native_EntIndexToBossIndex); + CreateNative("SF2_BossIndexToEntIndex", Native_BossIndexToEntIndex); + CreateNative("SF2_BossIndexToEntIndexEx", Native_BossIndexToEntIndexEx); + CreateNative("SF2_BossIDToBossIndex", Native_BossIDToBossIndex); + CreateNative("SF2_BossIndexToBossID", Native_BossIndexToBossID); + + CreateNative("SF2_AddBoss", Native_AddBoss); + CreateNative("SF2_RemoveBoss", Native_RemoveBoss); + + CreateNative("SF2_GetBossName", Native_GetBossName); + CreateNative("SF2_GetBossType", Native_GetBossType); + + CreateNative("SF2_GetBossFlags", Native_GetBossFlags); + CreateNative("SF2_SetBossFlags", Native_SetBossFlags); + + CreateNative("SF2_SpawnBoss", Native_SpawnBoss); + CreateNative("SF2_IsBossSpawning", Native_IsBossSpawning); + CreateNative("SF2_DespawnBoss", Native_DespawnBoss); + + CreateNative("SF2_GetBossPathFollower", Native_GetBossPathFollower); + CreateNative("SF2_GetBossMaster", Native_GetBossMaster); + CreateNative("SF2_GetBossIdleLifetime", Native_GetBossIdleLifetime); + CreateNative("SF2_GetBossState", Native_GetBossState); + CreateNative("SF2_SetBossState", Native_SetBossState); + + CreateNative("SF2_GetBossEyePosition", Native_GetBossEyePosition); + CreateNative("SF2_GetBossEyePositionOffset", Native_GetBossEyePositionOffset); + + CreateNative("SF2_GetBossTeleportThinkTimer", Native_GetBossTeleportThinkTimer); + CreateNative("SF2_SetBossTeleportThinkTimer", Native_SetBossTeleportThinkTimer); + CreateNative("SF2_GetBossTeleportTarget", Native_GetBossTeleportTarget); + + CreateNative("SF2_GetProfileFromBossIndex", Native_GetProfileData); + CreateNative("SF2_GetProfileFromName", Native_GetProfileDataEx); + + CreateNative("SF2_SpawnBossEffects", Native_SpawnBossEffects); + NPCChaserInitialize(); - SetupEntityActions(); +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC.FromEntity(ent.index); + if (controller.IsValid()) + { + NPCOnDespawn(controller, ent); + } +} + +void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + controller.GetProfile(profile, sizeof(profile)); + int bossIndex = controller.Index; + SF2BossProfileData data; + data = controller.GetProfileData(); + Call_StartForward(g_OnBossDespawnFwd); + Call_PushCell(bossIndex); + Call_Finish(); + + //Turn off all slender's effects in order to prevent some bugs. + SlenderRemoveEffects(bossIndex, true); + + if (controller.Type == SF2BossType_Statue) + { + Despawn_Statue(SF2NPC_Statue(bossIndex), entity); + } + else + { + Despawn_Chaser(bossIndex); + } + + if (g_BossPathFollower[bossIndex].IsValid()) + { + g_BossPathFollower[bossIndex].Invalidate(); + } + + int glowEnt = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); + if (glowEnt && glowEnt != INVALID_ENT_REFERENCE) + { + RemoveEntity(glowEnt); + } + g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; + + if (GetBossProfileDespawnParticleState(profile)) + { + SlenderCreateParticleSpawnEffect(bossIndex, true); + } + + if (data.EngineSound[0] != '\0') + { + StopSound(entity.index, SNDCHAN_STATIC, data.EngineSound); + } + + if (NPCGetFlags(bossIndex) & SFF_HASSTATICLOOPLOCALSOUND) + { + char loopSound[PLATFORM_MAX_PATH]; + GetBossProfileStaticLocalSound(profile, loopSound, sizeof(loopSound)); + + if (loopSound[0] != '\0') + { + StopSound(entity.index, SNDCHAN_STATIC, loopSound); + } + } + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || data.Healthbar) + { + controller.Flags = controller.Flags & ~SFF_NOTELEPORT; + } + + g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; + if (data.Healthbar && g_HealthBar != -1) + { + int npcIndex = 0; + for (npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) + { + if (NPCGetUniqueID(npcIndex) == -1) + { + continue; + } + data = NPCGetProfileData(npcIndex); + if (data.Healthbar) + { + int tempSlender = NPCGetEntIndex(npcIndex); + + if (tempSlender && tempSlender != INVALID_ENT_REFERENCE) + { + UpdateHealthBar(npcIndex); + break; + } + } + } + if (npcIndex == MAX_BOSSES) + { + UpdateHealthBar(bossIndex, 0); + } + } } void NPCOnConfigsExecuted() @@ -231,16 +354,16 @@ int NPCGetUniqueID(int npcIndex) return g_NpcUniqueID[npcIndex]; } -int NPCGetFromUniqueID(int iNPCUniqueID) +int NPCGetFromUniqueID(int NPCUniqueID) { - if (iNPCUniqueID == -1) + if (NPCUniqueID == -1) { return -1; } for (int i = 0; i < MAX_BOSSES; i++) { - if (NPCGetUniqueID(i) == iNPCUniqueID) + if (NPCGetUniqueID(i) == NPCUniqueID) { return i; } @@ -307,12 +430,22 @@ int NPCGetUniqueProfileIndex(int npcIndex) return g_NpcUniqueProfileIndex[npcIndex]; } -bool NPCGetProfile(int npcIndex, char[] buffer,int bufferLen) +bool NPCGetProfile(int npcIndex, char[] buffer, int bufferLen) { strcopy(buffer, bufferLen, g_SlenderProfile[npcIndex]); return true; } +SF2BossProfileData NPCGetProfileData(int npcIndex) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(npcIndex, profile, sizeof(profile)); + + SF2BossProfileData profileData; + g_BossProfileData.GetArray(profile, profileData, sizeof(profileData)); + return profileData; +} + void NPCSetProfile(int npcIndex, const char[] profile) { strcopy(g_SlenderProfile[npcIndex], sizeof(g_SlenderProfile[]), profile); @@ -327,6 +460,7 @@ void NPCRemove(int npcIndex) RemoveProfile(npcIndex); } + void NPCStopMusic() { //Stop the music timer @@ -335,7 +469,7 @@ void NPCStopMusic() delete timerMusic; } //Stop the music for all players. - for(int i = 1;i<=MaxClients;i++) + for (int i = 1; i <= MaxClients; i++) { if (IsValidClient(i)) { @@ -366,7 +500,7 @@ void CheckIfMusicValid() currentMusicTrackApollyon[0] = '\0'; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(i, profile, sizeof(profile)); - for(int client = 1;client <=MaxClients;client ++) + for(int client = 1;client <= MaxClients; client++) { if (IsValidClient(client) && (!g_PlayerEliminated[client] || IsClientInGhostMode(client))) { @@ -503,25 +637,25 @@ void CheckIfMusicValid() switch (g_DifficultyConVar.IntValue) { - case Difficulty_Normal, Difficulty_Easy: + case Difficulty_Normal: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNormal); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNormal); } case Difficulty_Hard: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackHard); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackHard); } case Difficulty_Insane: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackInsane); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackInsane); } case Difficulty_Nightmare: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNightmare); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNightmare); } case Difficulty_Apollyon: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackApollyon); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackApollyon); } } if (currentMusicTrack[0] != '\0') @@ -548,24 +682,16 @@ void CheckIfMusicValid() ClientUpdateMusicSystem(client); break; } - else - { - - } } } break; } - else - { - - } } } bool MusicActive() { - if (timerMusic!=null) + if (timerMusic != null) { return true; } @@ -778,6 +904,7 @@ bool BossHasMusic(char[] profile) } return false; } + bool BossMatchesCurrentMusic(char[] profile) { if (!IsProfileValid(profile)) @@ -890,6 +1017,7 @@ bool BossMatchesCurrentMusic(char[] profile) return false; } + void GetBossMusic(char[] buffer,int bufferLen) { int difficulty = g_DifficultyConVar.IntValue; @@ -897,28 +1025,28 @@ void GetBossMusic(char[] buffer,int bufferLen) { case Difficulty_Normal: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNormal); - strcopy(buffer,bufferLen,currentMusicTrackNormal); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNormal); + strcopy(buffer, bufferLen, currentMusicTrackNormal); } case Difficulty_Hard: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackHard); - strcopy(buffer,bufferLen,currentMusicTrackHard); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackHard); + strcopy(buffer, bufferLen, currentMusicTrackHard); } case Difficulty_Insane: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackInsane); - strcopy(buffer,bufferLen,currentMusicTrackInsane); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackInsane); + strcopy(buffer, bufferLen, currentMusicTrackInsane); } case Difficulty_Nightmare: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackNightmare); - strcopy(buffer,bufferLen,currentMusicTrackNightmare); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNightmare); + strcopy(buffer, bufferLen, currentMusicTrackNightmare); } case Difficulty_Apollyon: { - strcopy(currentMusicTrack,sizeof(currentMusicTrack),currentMusicTrackApollyon); - strcopy(buffer,bufferLen,currentMusicTrackApollyon); + strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackApollyon); + strcopy(buffer, bufferLen, currentMusicTrackApollyon); } } } @@ -932,7 +1060,7 @@ static Action BossMusic(Handle timer,any bossIndex) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - for(int i = 1;i<=MaxClients;i++) + for (int i = 1; i <= MaxClients; i++) { if (IsValidClient(i) && currentMusicTrack[0] != '\0') { @@ -947,6 +1075,7 @@ static Action BossMusic(Handle timer,any bossIndex) timerMusic = null; return Plugin_Continue; } + void NPCRemoveAll() { for (int i = 0; i < MAX_BOSSES; i++) @@ -1010,7 +1139,7 @@ int NPCGetModelBodyGroupsMax(int npcIndex) return g_NpcModelBodyGroupsMax[npcIndex]; } -int NPCGetRaidHitbox(int npcIndex) +bool NPCGetRaidHitbox(int npcIndex) { return g_NpcRaidHitbox[npcIndex]; } @@ -1110,31 +1239,11 @@ float NPCGetRainbowOutlineCycleRate(int npcIndex) return g_SlenderRainbowCycleRate[npcIndex]; } -float NPCGetSpeed(int npcIndex,int difficulty) -{ - return g_NpcSpeed[npcIndex][difficulty]; -} - -float NPCGetMaxSpeed(int npcIndex,int difficulty) -{ - return g_NpcMaxSpeed[npcIndex][difficulty]; -} - -float NPCGetAcceleration(int npcIndex,int difficulty) -{ - return g_SlenderAcceleration[npcIndex][difficulty]; -} - void NPCSetAddSpeed(int npcIndex, float amount) { g_NpcAddSpeed[npcIndex] += amount; } -void NPCSetAddMaxSpeed(int npcIndex, float amount) -{ - g_NpcAddMaxSpeed[npcIndex] += amount; -} - void NPCSetAddAcceleration(int npcIndex, float amount) { g_NpcAddAcceleration[npcIndex] += amount; @@ -1145,11 +1254,6 @@ float NPCGetAddSpeed(int npcIndex) return g_NpcAddSpeed[npcIndex]; } -float NPCGetAddMaxSpeed(int npcIndex) -{ - return g_NpcAddMaxSpeed[npcIndex]; -} - float NPCGetAddAcceleration(int npcIndex) { return g_NpcAddAcceleration[npcIndex]; @@ -1200,6 +1304,16 @@ int NPCGetTeleporter(int bossIndex, int teleporterNumber) return g_NpcTeleporters[bossIndex][teleporterNumber]; } +SF2NPC_BaseNPC NPCGetCopyMaster(SF2NPC_BaseNPC controller) +{ + return SF2NPC_BaseNPC(g_SlenderCopyMaster[controller.Index]); +} + +SF2NPC_BaseNPC NPCGetCompanionMaster(SF2NPC_BaseNPC controller) +{ + return SF2NPC_BaseNPC(g_SlenderCompanionMaster[controller.Index]); +} + float NPCGetJumpscareDistance(int npcIndex, int difficulty) { return g_NpcJumpscareDistance[npcIndex][difficulty]; @@ -1220,21 +1334,6 @@ bool NPCGetJumpscareOnScare(int npcIndex) return g_NpcHasJumpscareOnScare[npcIndex]; } -float NPCGetTurnRate(int npcIndex) -{ - return g_NpcTurnRate[npcIndex]; -} - -float NPCGetFOV(int npcIndex) -{ - return g_NpcFieldOfView[npcIndex]; -} - -float NPCGetBackstabFOV(int npcIndex) -{ - return g_NpcBackstabFOV[npcIndex]; -} - float NPCGetIdleLifetime(int npcIndex,int difficulty) { return g_NpcIdleLifetime[npcIndex][difficulty]; @@ -1247,21 +1346,6 @@ void NPCGetEyePositionOffset(int npcIndex, float buffer[3]) buffer[2] = g_SlenderEyePosOffset[npcIndex][2]; } -float NPCGetSearchRadius(int npcIndex, int difficulty) -{ - return g_NpcSearchRadius[npcIndex][difficulty]; -} - -float NPCGetHearingRadius(int npcIndex, int difficulty) -{ - return g_NpcHearingRadius[npcIndex][difficulty]; -} - -float NPCGetTauntAlertRange(int npcIndex, int difficulty) -{ - return g_NpcTauntAlertRange[npcIndex][difficulty]; -} - float NPCGetScareRadius(int npcIndex) { return g_NpcScareRadius[npcIndex]; @@ -1302,29 +1386,14 @@ int NPCGetScareReplenishSprintAmount(int npcIndex) return g_NpcPlayerScareReplenishSprintAmount[npcIndex]; } -float NPCGetInstantKillRadius(int npcIndex) -{ - return g_NpcInstantKillRadius[npcIndex]; -} - -float NPCGetInstantKillCooldown(int npcIndex, int difficulty) -{ - return g_NpcInstantKillCooldown[npcIndex][difficulty]; -} - int NPCGetTeleportType(int npcIndex) { return g_NpcTeleportType[npcIndex]; } -bool NPCGetHealthbarState(int npcIndex) -{ - return g_NpcHasHealthbarEnabled[npcIndex]; -} - -bool NPCGetFakeCopyState(int npcIndex) +bool NPCGetFakeCopyState(int npcIndex, int difficulty) { - return g_NpcHasFakeCopiesEnabled[npcIndex]; + return g_NpcHasFakeCopiesEnabled[npcIndex][difficulty]; } #if defined _store_included @@ -1384,16 +1453,14 @@ float NPCGetProxySpawnCooldownMax(int npcIndex, int difficulty) return g_NpcProxySpawnCooldownMax[npcIndex][difficulty]; } -bool NPCIsRaging(int npcIndex) +bool NPCGetAffectedBySightState(int npcIndex) { - for (int i = 0; i < 3; i++) - { - if (g_NpcUsesRageAnimation[npcIndex][i]) - { - return true; - } - } - return false; + return g_NpcShouldBeAffectedBySight[npcIndex]; +} + +void NPCSetAffectedBySightState(int npcIndex, bool state) +{ + g_NpcShouldBeAffectedBySight[npcIndex] = state; } bool NPCShouldSeeEntity(int npcIndex, int entity) @@ -1455,7 +1522,7 @@ bool NPCAreAvailablePlayersAlive() /** * Returns the boss's eye position (eye pos offset + absorigin). */ -bool NPCGetEyePosition(int npcIndex, float buffer[3], const float defaultValue[3]={ 0.0, 0.0, 0.0 }) +void NPCGetEyePosition(int npcIndex, float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) { buffer[0] = defaultValue[0]; buffer[1] = defaultValue[1]; @@ -1463,22 +1530,20 @@ bool NPCGetEyePosition(int npcIndex, float buffer[3], const float defaultValue[3 if (!NPCIsValid(npcIndex)) { - return false; + return; } int npcEnt = NPCGetEntIndex(npcIndex); if (!npcEnt || npcEnt == INVALID_ENT_REFERENCE) { - return false; + return; } - // @TODO: Replace SlenderGetAbsOrigin with GetEntPropVector float pos[3], eyePosOffset[3]; - SlenderGetAbsOrigin(npcIndex, pos); + CBaseEntity(npcEnt).GetAbsOrigin(pos); NPCGetEyePositionOffset(npcIndex, eyePosOffset); AddVectors(pos, eyePosOffset, buffer); - return true; } bool NPCHasAttribute(int npcIndex, int attribute) @@ -1538,14 +1603,17 @@ bool SlenderCanRemove(int bossIndex) int difficulty = GetLocalGlobalDifficulty(bossIndex); + int slender = NPCGetEntIndex(bossIndex); + switch (teleportType) { case 0: { - if (NPCGetFlags(bossIndex) & SFF_STATICONRADIUS) + if (NPCGetFlags(bossIndex) & SFF_STATICONRADIUS && slender != INVALID_ENT_REFERENCE && slender) { float slenderPos[3], buffer[3]; - SlenderGetAbsOrigin(bossIndex, slenderPos); + CBaseEntity boss = CBaseEntity(slender); + boss.GetAbsOrigin(slenderPos); for (int i = 1; i <= MaxClients; i++) { @@ -1580,17 +1648,32 @@ bool SlenderCanRemove(int bossIndex) } case 2: { - int state = g_SlenderState[bossIndex]; - if (state != STATE_ALERT && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) + SF2_ChaserEntity chaser = SF2_ChaserEntity(slender); + if (chaser.IsValid()) { + if (chaser.State == STATE_ALERT || chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN || chaser.State == STATE_DEATH) + { + return false; + } + if (GetGameTime() < g_SlenderTimeUntilKill[bossIndex]) { return false; } } - else + + SF2_StatueEntity statue = SF2_StatueEntity(slender); + if (statue.IsValid()) { - return false; + if (statue.IsMoving) + { + return false; + } + + if (GetGameTime() < g_SlenderStatueIdleLifeTime[bossIndex]) + { + return false; + } } } } @@ -1598,366 +1681,279 @@ bool SlenderCanRemove(int bossIndex) return true; } -bool SlenderGetAbsOrigin(int bossIndex, float buffer[3], const float defaultValue[3]={ 0.0, 0.0, 0.0 }) -{ - for (int i = 0; i < 3; i++) - { - buffer[i] = defaultValue[i]; - } - - if (bossIndex < 0 || NPCGetUniqueID(bossIndex) == -1) - { - return false; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return false; - } - - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", buffer); - - return true; -} - -bool SlenderGetEyePosition(int bossIndex, float buffer[3], const float defaultValue[3]={ 0.0, 0.0, 0.0 }) -{ - return NPCGetEyePosition(bossIndex, buffer, defaultValue); -} - -bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFlags=0,SF2NPC_BaseNPC NpcCopyMaster=view_as(SF2_INVALID_NPC), bool spawnCompanions=true, bool playSpawnSound=true, bool invincible = false) +bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true) { if (!IsProfileValid(profile)) { - if (!NpcCopyMaster.IsValid()) + if (!npcCopyMaster.IsValid()) { - LogSF2Message("Could not select profile for boss %d: profile %s is invalid!", Npc.Index, profile); + LogSF2Message("Could not select profile for boss %d: profile %s is invalid!", npc.Index, profile); return false; } - /*else//Wait my copy master is valid but not my profil wut???? - { - char sNewProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - NpcCopyMaster.GetProfile(sNewProfile,sizeof(sNewProfile)); - //Add me again - SelectProfile(Npc, sNewProfile, additionalBossFlags, NpcCopyMaster, spawnCompanions, playSpawnSound); - }*/ } - Npc.Remove(); + npc.Remove(); - Npc.SetProfile(profile); + npc.SetProfile(profile); - int bossType = GetBossProfileType(profile); + SF2BossProfileData profileData; + profileData = npc.GetProfileData(); - g_NpcUniqueID[Npc.Index] = g_NpcGlobalUniqueID++; - g_NpcType[Npc.Index] = bossType; + int bossType = GetBossProfileType(profile); - g_NpcModelScale[Npc.Index] = GetBossProfileModelScale(profile); + g_NpcUniqueID[npc.Index] = g_NpcGlobalUniqueID++; + g_NpcType[npc.Index] = bossType; - g_NpcModelSkin[Npc.Index] = GetBossProfileSkin(profile); + g_NpcModelScale[npc.Index] = GetBossProfileModelScale(profile); - g_NpcModelSkinMax[Npc.Index] = GetBossProfileSkinMax(profile); + g_NpcModelSkin[npc.Index] = GetBossProfileSkin(profile); - g_NpcModelBodyGroups[Npc.Index] = GetBossProfileBodyGroups(profile); + g_NpcModelSkinMax[npc.Index] = GetBossProfileSkinMax(profile); - g_NpcModelBodyGroupsMax[Npc.Index] = GetBossProfileBodyGroupsMax(profile); + g_NpcModelBodyGroups[npc.Index] = GetBossProfileBodyGroups(profile); - g_NpcRaidHitbox[Npc.Index] = GetBossProfileRaidHitbox(profile); + g_NpcModelBodyGroupsMax[npc.Index] = GetBossProfileBodyGroupsMax(profile); - g_NpcHasIgnoreNavPrefer[Npc.Index] = GetBossProfileIgnoreNavPrefer(profile); + g_NpcRaidHitbox[npc.Index] = GetBossProfileRaidHitbox(profile); - g_NpcHasFakeCopiesEnabled[Npc.Index] = GetBossProfileFakeCopies(profile); + g_NpcHasIgnoreNavPrefer[npc.Index] = GetBossProfileIgnoreNavPrefer(profile); - g_NpcBlinkLookRate[Npc.Index] = GetBossProfileBlinkLookRate(profile); - g_NpcBlinkStaticRate[Npc.Index] = GetBossProfileBlinkStaticRate(profile); + g_NpcBlinkLookRate[npc.Index] = GetBossProfileBlinkLookRate(profile); + g_NpcBlinkStaticRate[npc.Index] = GetBossProfileBlinkStaticRate(profile); - g_NpcStepSize[Npc.Index] = GetBossProfileStepSize(profile); + g_NpcStepSize[npc.Index] = GetBossProfileStepSize(profile); - g_NpcHasDiscoMode[Npc.Index] = GetBossProfileDiscoModeState(profile); - g_NpcDiscoRadiusMin[Npc.Index] = GetBossProfileDiscoRadiusMin(profile); - g_NpcDiscoRadiusMax[Npc.Index] = GetBossProfileDiscoRadiusMax(profile); + g_NpcHasDiscoMode[npc.Index] = GetBossProfileDiscoModeState(profile); + g_NpcDiscoRadiusMin[npc.Index] = GetBossProfileDiscoRadiusMin(profile); + g_NpcDiscoRadiusMax[npc.Index] = GetBossProfileDiscoRadiusMax(profile); - g_NpcHasFestiveLights[Npc.Index] = GetBossProfileFestiveLightState(profile); - g_NpcFestiveLightBrightness[Npc.Index] = GetBossProfileFestiveLightBrightness(profile); - g_NpcFestiveLightDistance[Npc.Index] = GetBossProfileFestiveLightDistance(profile); - g_NpcFestiveLightRadius[Npc.Index] = GetBossProfileFestiveLightRadius(profile); - GetBossProfileDiscoPosition(profile, g_NpcDiscoModePos[Npc.Index]); - GetBossProfileFestiveLightPosition(profile, g_NpcFestiveLightPos[Npc.Index]); - GetBossProfileFestiveLightAngles(profile, g_NpcFestiveLightAng[Npc.Index]); + g_NpcHasFestiveLights[npc.Index] = GetBossProfileFestiveLightState(profile); + g_NpcFestiveLightBrightness[npc.Index] = GetBossProfileFestiveLightBrightness(profile); + g_NpcFestiveLightDistance[npc.Index] = GetBossProfileFestiveLightDistance(profile); + g_NpcFestiveLightRadius[npc.Index] = GetBossProfileFestiveLightRadius(profile); + GetBossProfileDiscoPosition(profile, g_NpcDiscoModePos[npc.Index]); + GetBossProfileFestiveLightPosition(profile, g_NpcFestiveLightPos[npc.Index]); + GetBossProfileFestiveLightAngles(profile, g_NpcFestiveLightAng[npc.Index]); - g_SlenderUseCustomOutlines[Npc.Index] = GetBossProfileCustomOutlinesState(profile); - g_SlenderOutlineColorR[Npc.Index] = GetBossProfileOutlineColorR(profile); - g_SlenderOutlineColorG[Npc.Index] = GetBossProfileOutlineColorG(profile); - g_SlenderOutlineColorB[Npc.Index] = GetBossProfileOutlineColorB(profile); - g_SlenderOutlineTransparency[Npc.Index] = GetBossProfileOutlineTransparency(profile); - g_SlenderUseRainbowOutline[Npc.Index] = GetBossProfileRainbowOutlineState(profile); - g_SlenderRainbowCycleRate[Npc.Index] = GetBossProfileRainbowCycleRate(profile); + g_SlenderUseCustomOutlines[npc.Index] = GetBossProfileCustomOutlinesState(profile); + g_SlenderOutlineColorR[npc.Index] = GetBossProfileOutlineColorR(profile); + g_SlenderOutlineColorG[npc.Index] = GetBossProfileOutlineColorG(profile); + g_SlenderOutlineColorB[npc.Index] = GetBossProfileOutlineColorB(profile); + g_SlenderOutlineTransparency[npc.Index] = GetBossProfileOutlineTransparency(profile); + g_SlenderUseRainbowOutline[npc.Index] = GetBossProfileRainbowOutlineState(profile); + g_SlenderRainbowCycleRate[npc.Index] = GetBossProfileRainbowCycleRate(profile); - g_NpcHasProxyWeaponsEnabled[Npc.Index] = GetBossProfileProxyWeapons(profile); + g_NpcHasProxyWeaponsEnabled[npc.Index] = GetBossProfileProxyWeapons(profile); - g_NpcHasDrainCreditsState[Npc.Index] = GetBossProfileDrainCreditState(profile); - g_NpcHasProxySpawnEffectEnabled[Npc.Index] = GetBossProfileProxySpawnEffectState(profile); - g_NpcProxySpawnEffectZOffset[Npc.Index] = GetBossProfileProxySpawnEffectZOffset(profile); + g_NpcHasDrainCreditsState[npc.Index] = GetBossProfileDrainCreditState(profile); + g_NpcHasProxySpawnEffectEnabled[npc.Index] = GetBossProfileProxySpawnEffectState(profile); + g_NpcProxySpawnEffectZOffset[npc.Index] = GetBossProfileProxySpawnEffectZOffset(profile); if (SF_IsSlaughterRunMap()) { - NPCSetFlags(Npc.Index, GetBossProfileFlags(profile) | additionalBossFlags | SFF_NOTELEPORT); + NPCSetFlags(npc.Index, GetBossProfileFlags(profile) | additionalBossFlags | SFF_NOTELEPORT); } else { - NPCSetFlags(Npc.Index, GetBossProfileFlags(profile) | additionalBossFlags); + NPCSetFlags(npc.Index, GetBossProfileFlags(profile) | additionalBossFlags); } - GetBossProfileEyePositionOffset(profile, g_SlenderEyePosOffset[Npc.Index]); - GetBossProfileEyeAngleOffset(profile, g_SlenderEyeAngOffset[Npc.Index]); + GetBossProfileEyePositionOffset(profile, g_SlenderEyePosOffset[npc.Index]); + GetBossProfileEyeAngleOffset(profile, g_SlenderEyeAngOffset[npc.Index]); - GetBossProfileHullMins(profile, g_SlenderDetectMins[Npc.Index]); - GetBossProfileHullMaxs(profile, g_SlenderDetectMaxs[Npc.Index]); + GetBossProfileHullMins(profile, g_SlenderDetectMins[npc.Index]); + GetBossProfileHullMaxs(profile, g_SlenderDetectMaxs[npc.Index]); - GetBossProfileRenderColor(profile, g_SlenderRenderColor[Npc.Index]); + GetBossProfileRenderColor(profile, g_SlenderRenderColor[npc.Index]); - g_SlenderRenderFX[Npc.Index] = GetBossProfileRenderFX(profile); - g_SlenderRenderMode[Npc.Index] = GetBossProfileRenderMode(profile); + g_SlenderRenderFX[npc.Index] = GetBossProfileRenderFX(profile); + g_SlenderRenderMode[npc.Index] = GetBossProfileRenderMode(profile); - g_SlenderCopyMaster[Npc.Index] = -1; - g_SlenderCompanionMaster[Npc.Index] = -1; + npc.CopyMaster = SF2_INVALID_NPC; + npc.CompanionMaster = SF2_INVALID_NPC; + + g_NpcShouldBeAffectedBySight[npc.Index] = false; for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - g_NpcSoundMusicLoop[Npc.Index][difficulty] = GetBossProfileSoundMusicLoop(profile, difficulty); - g_NpcSpeed[Npc.Index][difficulty] = GetBossProfileSpeed(profile, difficulty); - g_NpcMaxSpeed[Npc.Index][difficulty] = GetBossProfileMaxSpeed(profile, difficulty); - g_SlenderAcceleration[Npc.Index][difficulty] = GetBossProfileAcceleration(profile, difficulty); - g_NpcIdleLifetime[Npc.Index][difficulty] = GetBossProfileIdleLifetime(profile, difficulty); - g_SlenderStaticRadius[Npc.Index][difficulty] = GetBossProfileStaticRadius(profile, difficulty); - g_SlenderStaticRate[Npc.Index][difficulty] = GetBossProfileStaticRate(profile, difficulty); - g_SlenderStaticRateDecay[Npc.Index][difficulty] = GetBossProfileStaticRateDecay(profile, difficulty); - g_SlenderStaticGraceTime[Npc.Index][difficulty] = GetBossProfileStaticGraceTime(profile, difficulty); - g_NpcSearchRadius[Npc.Index][difficulty] = GetBossProfileSearchRadius(profile, difficulty); - g_NpcHearingRadius[Npc.Index][difficulty] = GetBossProfileHearRadius(profile, difficulty); - g_NpcTauntAlertRange[Npc.Index][difficulty] = GetBossProfileTauntAlertRange(profile, difficulty); - g_SlenderTeleportMinRange[Npc.Index][difficulty] = GetBossProfileTeleportRangeMin(profile, difficulty); - g_SlenderTeleportMaxRange[Npc.Index][difficulty] = GetBossProfileTeleportRangeMax(profile, difficulty); - g_NpcHasTeleportAllowed[Npc.Index][difficulty] = GetBossProfileTeleportAllowed(profile, difficulty); - g_NpcTeleportTimeMin[Npc.Index][difficulty] = GetBossProfileTeleportTimeMin(profile, difficulty); - g_NpcTeleportTimeMax[Npc.Index][difficulty] = GetBossProfileTeleportTimeMax(profile, difficulty); - g_NpcTeleportRestPeriod[Npc.Index][difficulty] = GetBossProfileTeleportTargetRestPeriod(profile, difficulty); - g_NpcTeleportStressMin[Npc.Index][difficulty] = GetBossProfileTeleportTargetStressMin(profile, difficulty); - g_NpcTeleportStressMax[Npc.Index][difficulty] = GetBossProfileTeleportTargetStressMax(profile, difficulty); - g_NpcTeleportPersistencyPeriod[Npc.Index][difficulty] = GetBossProfileTeleportTargetPersistencyPeriod(profile, difficulty); - g_NpcJumpscareDistance[Npc.Index][difficulty] = GetBossProfileJumpscareDistance(profile, difficulty); - g_NpcJumpscareDuration[Npc.Index][difficulty] = GetBossProfileJumpscareDuration(profile, difficulty); - g_NpcJumpscareCooldown[Npc.Index][difficulty] = GetBossProfileJumpscareCooldown(profile, difficulty); - g_NpcModelSkinDifficulty[Npc.Index][difficulty] = GetBossProfileSkinDifficulty(profile, difficulty); - g_NpcModelBodyGroupsDifficulty[Npc.Index][difficulty] = GetBossProfileBodyGroupsDifficulty(profile, difficulty); - g_SlenderMaxCopies[Npc.Index][difficulty] = GetBossProfileMaxCopies(profile, difficulty); - g_NpcInstantKillCooldown[Npc.Index][difficulty] = GetBossProfileInstantKillCooldown(profile, difficulty); - - g_SlenderProxyDamageVsEnemy[Npc.Index][difficulty] = GetBossProfileProxyDamageVsEnemy(profile, difficulty); - g_SlenderProxyDamageVsBackstab[Npc.Index][difficulty] = GetBossProfileProxyDamageVsBackstab(profile, difficulty); - g_SlenderProxyDamageVsSelf[Npc.Index][difficulty] = GetBossProfileProxyDamageVsSelf(profile, difficulty); - g_SlenderProxyControlGainHitEnemy[Npc.Index][difficulty] = GetBossProfileProxyControlGainHitEnemy(profile, difficulty); - g_SlenderProxyControlGainHitByEnemy[Npc.Index][difficulty] = GetBossProfileProxyControlGainHitByEnemy(profile, difficulty); - g_SlenderProxyControlDrainRate[Npc.Index][difficulty] = GetBossProfileProxyControlDrainRate(profile, difficulty); - g_SlenderMaxProxies[Npc.Index][difficulty] = GetBossProfileMaxProxies(profile, difficulty); - g_NpcProxySpawnChanceMin[Npc.Index][difficulty] = GetBossProfileProxySpawnChanceMin(profile, difficulty); - g_NpcProxySpawnChanceMax[Npc.Index][difficulty] = GetBossProfileProxySpawnChanceMax(profile, difficulty); - g_NpcProxySpawnChanceThreshold[Npc.Index][difficulty] = GetBossProfileProxySpawnChanceThreshold(profile, difficulty); - g_NpcProxySpawnNumMin[Npc.Index][difficulty] = GetBossProfileProxySpawnNumberMin(profile, difficulty); - g_NpcProxySpawnNumMax[Npc.Index][difficulty] = GetBossProfileProxySpawnNumberMax(profile, difficulty); - g_NpcProxySpawnCooldownMin[Npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMin(profile, difficulty); - g_NpcProxySpawnCooldownMax[Npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMax(profile, difficulty); - g_SlenderProxyTeleportMinRange[Npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMin(profile, difficulty); - g_SlenderProxyTeleportMaxRange[Npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMax(profile, difficulty); - g_NpcDrainCreditAmount[Npc.Index][difficulty] = GetBossProfileDrainCreditAmount(profile, difficulty); + g_NpcHasFakeCopiesEnabled[npc.Index][difficulty] = GetBossProfileFakeCopies(profile, difficulty); + g_NpcSoundMusicLoop[npc.Index][difficulty] = GetBossProfileSoundMusicLoop(profile, difficulty); + g_NpcIdleLifetime[npc.Index][difficulty] = GetBossProfileIdleLifetime(profile, difficulty); + g_SlenderStaticRadius[npc.Index][difficulty] = GetBossProfileStaticRadius(profile, difficulty); + g_SlenderStaticRate[npc.Index][difficulty] = GetBossProfileStaticRate(profile, difficulty); + g_SlenderStaticRateDecay[npc.Index][difficulty] = GetBossProfileStaticRateDecay(profile, difficulty); + g_SlenderStaticGraceTime[npc.Index][difficulty] = GetBossProfileStaticGraceTime(profile, difficulty); + g_SlenderTeleportMinRange[npc.Index][difficulty] = GetBossProfileTeleportRangeMin(profile, difficulty); + g_SlenderTeleportMaxRange[npc.Index][difficulty] = GetBossProfileTeleportRangeMax(profile, difficulty); + g_NpcHasTeleportAllowed[npc.Index][difficulty] = GetBossProfileTeleportAllowed(profile, difficulty); + g_NpcTeleportTimeMin[npc.Index][difficulty] = GetBossProfileTeleportTimeMin(profile, difficulty); + g_NpcTeleportTimeMax[npc.Index][difficulty] = GetBossProfileTeleportTimeMax(profile, difficulty); + g_NpcTeleportRestPeriod[npc.Index][difficulty] = GetBossProfileTeleportTargetRestPeriod(profile, difficulty); + g_NpcTeleportStressMin[npc.Index][difficulty] = GetBossProfileTeleportTargetStressMin(profile, difficulty); + g_NpcTeleportStressMax[npc.Index][difficulty] = GetBossProfileTeleportTargetStressMax(profile, difficulty); + g_NpcTeleportPersistencyPeriod[npc.Index][difficulty] = GetBossProfileTeleportTargetPersistencyPeriod(profile, difficulty); + g_NpcJumpscareDistance[npc.Index][difficulty] = GetBossProfileJumpscareDistance(profile, difficulty); + g_NpcJumpscareDuration[npc.Index][difficulty] = GetBossProfileJumpscareDuration(profile, difficulty); + g_NpcJumpscareCooldown[npc.Index][difficulty] = GetBossProfileJumpscareCooldown(profile, difficulty); + g_NpcModelSkinDifficulty[npc.Index][difficulty] = GetBossProfileSkinDifficulty(profile, difficulty); + g_NpcModelBodyGroupsDifficulty[npc.Index][difficulty] = GetBossProfileBodyGroupsDifficulty(profile, difficulty); + g_SlenderMaxCopies[npc.Index][difficulty] = GetBossProfileMaxCopies(profile, difficulty); + + g_SlenderProxyDamageVsEnemy[npc.Index][difficulty] = GetBossProfileProxyDamageVsEnemy(profile, difficulty); + g_SlenderProxyDamageVsBackstab[npc.Index][difficulty] = GetBossProfileProxyDamageVsBackstab(profile, difficulty); + g_SlenderProxyDamageVsSelf[npc.Index][difficulty] = GetBossProfileProxyDamageVsSelf(profile, difficulty); + g_SlenderProxyControlGainHitEnemy[npc.Index][difficulty] = GetBossProfileProxyControlGainHitEnemy(profile, difficulty); + g_SlenderProxyControlGainHitByEnemy[npc.Index][difficulty] = GetBossProfileProxyControlGainHitByEnemy(profile, difficulty); + g_SlenderProxyControlDrainRate[npc.Index][difficulty] = GetBossProfileProxyControlDrainRate(profile, difficulty); + g_SlenderMaxProxies[npc.Index][difficulty] = GetBossProfileMaxProxies(profile, difficulty); + g_NpcProxySpawnChanceMin[npc.Index][difficulty] = GetBossProfileProxySpawnChanceMin(profile, difficulty); + g_NpcProxySpawnChanceMax[npc.Index][difficulty] = GetBossProfileProxySpawnChanceMax(profile, difficulty); + g_NpcProxySpawnChanceThreshold[npc.Index][difficulty] = GetBossProfileProxySpawnChanceThreshold(profile, difficulty); + g_NpcProxySpawnNumMin[npc.Index][difficulty] = GetBossProfileProxySpawnNumberMin(profile, difficulty); + g_NpcProxySpawnNumMax[npc.Index][difficulty] = GetBossProfileProxySpawnNumberMax(profile, difficulty); + g_NpcProxySpawnCooldownMin[npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMin(profile, difficulty); + g_NpcProxySpawnCooldownMax[npc.Index][difficulty] = GetBossProfileProxySpawnCooldownMax(profile, difficulty); + g_SlenderProxyTeleportMinRange[npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMin(profile, difficulty); + g_SlenderProxyTeleportMaxRange[npc.Index][difficulty] = GetBossProfileProxyTeleportRangeMax(profile, difficulty); + g_NpcDrainCreditAmount[npc.Index][difficulty] = GetBossProfileDrainCreditAmount(profile, difficulty); } ArrayList arrayNames; arrayNames = GetBossProfileNames(profile); - arrayNames.GetString(Difficulty_Normal, g_NpcName[Npc.Index][1], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Normal, g_NpcName[npc.Index][1], sizeof(g_NpcName[][])); - strcopy(g_NpcName[Npc.Index][0], sizeof(g_NpcName[][]), g_NpcName[Npc.Index][1]); + strcopy(g_NpcName[npc.Index][0], sizeof(g_NpcName[][]), g_NpcName[npc.Index][1]); - arrayNames.GetString(Difficulty_Hard, g_NpcName[Npc.Index][2], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Hard, g_NpcName[npc.Index][2], sizeof(g_NpcName[][])); - arrayNames.GetString(Difficulty_Insane, g_NpcName[Npc.Index][3], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Insane, g_NpcName[npc.Index][3], sizeof(g_NpcName[][])); - arrayNames.GetString(Difficulty_Nightmare, g_NpcName[Npc.Index][4], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Nightmare, g_NpcName[npc.Index][4], sizeof(g_NpcName[][])); - arrayNames.GetString(Difficulty_Apollyon, g_NpcName[Npc.Index][5], sizeof(g_NpcName[][])); + arrayNames.GetString(Difficulty_Apollyon, g_NpcName[npc.Index][5], sizeof(g_NpcName[][])); arrayNames = null; - g_NpcHasJumpscareOnScare[Npc.Index] = GetBossProfileJumpscareOnScare(profile); - - g_NpcTurnRate[Npc.Index] = GetBossProfileTurnRate(profile); - if (!SF_IsSlaughterRunMap()) - { - g_NpcFieldOfView[Npc.Index] = GetBossProfileFOV(profile); - } - else - { - g_NpcFieldOfView[Npc.Index] = 360.0; - } - g_NpcBackstabFOV[Npc.Index] = 180.0; - - g_NpcScareRadius[Npc.Index] = GetBossProfileScareRadius(profile); - g_NpcScareCooldown[Npc.Index] = GetBossProfileScareCooldown(profile); - - g_NpcHasPlayerScareSpeedBoost[Npc.Index] = GetBossProfileSpeedBoostOnScare(profile); - g_NpcPlayerSpeedBoostDuration[Npc.Index] = GetBossProfileScareSpeedBoostDuration(profile); - - g_NpcHasPlayerScareReaction[Npc.Index] = GetBossProfileScareReactionState(profile); - g_NpcPlayerScareReactionType[Npc.Index] = GetBossProfileScareReactionType(profile); + g_NpcHasJumpscareOnScare[npc.Index] = GetBossProfileJumpscareOnScare(profile); - g_NpcHasPlayerScareReplenishSprint[Npc.Index] = GetBossProfileScareReplenishState(profile); - g_NpcPlayerScareReplenishSprintAmount[Npc.Index] = GetBossProfileScareReplenishAmount(profile); + g_NpcScareRadius[npc.Index] = GetBossProfileScareRadius(profile); + g_NpcScareCooldown[npc.Index] = GetBossProfileScareCooldown(profile); - g_NpcInstantKillRadius[Npc.Index] = GetBossProfileInstantKillRadius(profile); + g_NpcHasPlayerScareSpeedBoost[npc.Index] = GetBossProfileSpeedBoostOnScare(profile); + g_NpcPlayerSpeedBoostDuration[npc.Index] = GetBossProfileScareSpeedBoostDuration(profile); - g_NpcTeleportType[Npc.Index] = GetBossProfileTeleportType(profile); + g_NpcHasPlayerScareReaction[npc.Index] = GetBossProfileScareReactionState(profile); + g_NpcPlayerScareReactionType[npc.Index] = GetBossProfileScareReactionType(profile); - g_SlenderTeleportIgnoreChases[Npc.Index] = GetBossProfileTeleportIgnoreChases(profile); + g_NpcHasPlayerScareReplenishSprint[npc.Index] = GetBossProfileScareReplenishState(profile); + g_NpcPlayerScareReplenishSprintAmount[npc.Index] = GetBossProfileScareReplenishAmount(profile); - g_SlenderTeleportIgnoreVis[Npc.Index] = GetBossProfileTeleportIgnoreVis(profile); + g_NpcTeleportType[npc.Index] = GetBossProfileTeleportType(profile); - g_NpcHasHealthbarEnabled[Npc.Index] = GetBossProfileHealthbarState(profile); + g_SlenderTeleportIgnoreChases[npc.Index] = GetBossProfileTeleportIgnoreChases(profile); - g_SlenderProxiesAllowNormalVoices[Npc.Index] = GetBossProfileProxyAllowNormalVoices(profile); + g_SlenderTeleportIgnoreVis[npc.Index] = GetBossProfileTeleportIgnoreVis(profile); - g_NpcDeathMessageDifficultyIndexes[Npc.Index] = GetBossProfileChatDeathMessageDifficultyIndexes(profile); + g_SlenderProxiesAllowNormalVoices[npc.Index] = GetBossProfileProxyAllowNormalVoices(profile); - g_NpcAddSpeed[Npc.Index] = 0.0; - g_NpcAddMaxSpeed[Npc.Index] = 0.0; - g_NpcAddAcceleration[Npc.Index] = 0.0; + g_NpcDeathMessageDifficultyIndexes[npc.Index] = GetBossProfileChatDeathMessageDifficultyIndexes(profile); - g_NpcEnemy[Npc.Index] = INVALID_ENT_REFERENCE; - - g_NpcPlayerScareVictin[Npc.Index] = INVALID_ENT_REFERENCE; - g_NpcChasingScareVictin[Npc.Index] = false; - g_NpcLostChasingScareVictim[Npc.Index] = false; + g_NpcAddSpeed[npc.Index] = 0.0; + g_NpcAddAcceleration[npc.Index] = 0.0; // Deathcam values. - Npc.DeathCamEnabled = GetBossProfileDeathCamState(profile); - g_SlenderDeathCamScareSound[Npc.Index] = GetBossProfileDeathCamScareSound(profile); - g_SlenderPublicDeathCam[Npc.Index] = GetBossProfilePublicDeathCamState(profile); - g_SlenderPublicDeathCamSpeed[Npc.Index] = GetBossProfilePublicDeathCamSpeed(profile); - g_SlenderPublicDeathCamAcceleration[Npc.Index] = GetBossProfilePublicDeathCamAcceleration(profile); - g_SlenderPublicDeathCamDeceleration[Npc.Index] = GetBossProfilePublicDeathCamDeceleration(profile); - g_SlenderPublicDeathCamBackwardOffset[Npc.Index] = GetBossProfilePublicDeathCamBackwardOffset(profile); - g_SlenderPublicDeathCamDownwardOffset[Npc.Index] = GetBossProfilePublicDeathCamDownwardOffset(profile); - g_SlenderDeathCamOverlay[Npc.Index] = GetBossProfileDeathCamOverlayState(profile); - g_SlenderDeathCamOverlayTimeStart[Npc.Index] = GetBossProfileDeathCamOverlayStartTime(profile); - g_SlenderDeathCamTime[Npc.Index] = GetBossProfileDeathCamTime(profile); - - g_SlenderFakeTimer[Npc.Index] = null; - g_SlenderEntityThink[Npc.Index] = null; - g_SlenderAttackTimer[Npc.Index] = null; - g_SlenderLaserTimer[Npc.Index] = null; - g_SlenderBackupAtkTimer[Npc.Index] = null; - g_SlenderChaseInitialTimer[Npc.Index] = null; - g_SlenderRage1Timer[Npc.Index] = null; - g_SlenderRage2Timer[Npc.Index] = null; - g_SlenderRage3Timer[Npc.Index] = null; - g_SlenderHealTimer[Npc.Index] = null; - g_SlenderHealDelayTimer[Npc.Index] = null; - g_SlenderHealEventTimer[Npc.Index] = null; - g_SlenderStartFleeTimer[Npc.Index] = null; - g_SlenderSpawnTimer[Npc.Index] = null; - g_SlenderDeathCamTimer[Npc.Index] = null; - g_SlenderDeathCamTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderNextTeleportTime[Npc.Index] = 0.0; - g_SlenderLastKill[Npc.Index] = 0.0; - g_SlenderTimeUntilKill[Npc.Index] = -1.0; - g_SlenderNextJumpScare[Npc.Index] = -1.0; - g_SlenderNextStunTime[Npc.Index] = -1.0; - g_SlenderNextCloakTime[Npc.Index] = -1.0; - g_NpcHasCloaked[Npc.Index] = false; - g_NpcUsedRage1[Npc.Index] = false; - g_NpcUsedRage2[Npc.Index] = false; - g_NpcHasUsedRage3[Npc.Index] = false; - g_SlenderTimeUntilNextProxy[Npc.Index] = -1.0; - g_NpcVelocityCancel[Npc.Index] = false; - g_SlenderBurnTimer[Npc.Index] = null; - g_SlenderBleedTimer[Npc.Index] = null; - g_SlenderMarkedTimer[Npc.Index] = null; - g_SlenderStopBurningTimer[Npc.Index] = 0.0; - g_SlenderStopBleedingTimer[Npc.Index] = 0.0; - g_SlenderIsBurning[Npc.Index] = false; - g_SlenderIsMarked[Npc.Index] = false; - g_SlenderSoundTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderSeeTarget[Npc.Index] = INVALID_ENT_REFERENCE; - - g_SlenderHasBurnKillEffect[Npc.Index] = GetBossProfileBurnRagdoll(profile); - g_SlenderHasCloakKillEffect[Npc.Index] = GetBossProfileCloakRagdoll(profile); - g_SlenderHasDecapKillEffect[Npc.Index] = GetBossProfileDecapRagdoll(profile); - g_SlenderHasGibKillEffect[Npc.Index] = GetBossProfileGibRagdoll(profile); - g_SlenderHasGoldKillEffect[Npc.Index] = GetBossProfileGoldRagdoll(profile); - g_SlenderHasIceKillEffect[Npc.Index] = GetBossProfileIceRagdoll(profile); - g_SlenderHasElectrocuteKillEffect[Npc.Index] = GetBossProfileElectrocuteRagdoll(profile); - g_SlenderHasAshKillEffect[Npc.Index] = GetBossProfileAshRagdoll(profile); - g_SlenderHasDeleteKillEffect[Npc.Index] = GetBossProfileDeleteRagdoll(profile); - g_SlenderHasPushRagdollOnKill[Npc.Index] = GetBossProfilePushRagdoll(profile); - - g_SlenderHasDissolveRagdollOnKill[Npc.Index] = GetBossProfileDissolveRagdoll(profile); - g_SlenderDissolveRagdollType[Npc.Index] = GetBossProfileDissolveRagdollType(profile); - - g_SlenderHasPlasmaRagdollOnKill[Npc.Index] = GetBossProfilePlasmaRagdoll(profile); - - g_SlenderHasResizeRagdollOnKill[Npc.Index] = GetBossProfileResizeRagdoll(profile); - g_SlenderResizeRagdollHands[Npc.Index] = GetBossProfileResizeRagdollHands(profile); - g_SlenderResizeRagdollHead[Npc.Index] = GetBossProfileResizeRagdollHead(profile); - g_SlenderResizeRagdollTorso[Npc.Index] = GetBossProfileResizeRagdollTorso(profile); - - g_SlenderHasDecapOrGibKillEffect[Npc.Index] = GetBossProfileDecapOrGibRagdoll(profile); - g_SlenderHasMultiKillEffect[Npc.Index] = GetBossProfileMultieffectRagdoll(profile); - - g_SlenderHasSilentKill[Npc.Index] = GetBossProfileSilentKill(profile); - - g_SlenderPlayerCustomDeathFlag[Npc.Index] = GetBossProfileCustomDeathFlag(profile); - g_SlenderPlayerSetDeathFlag[Npc.Index] = GetBossProfileCustomDeathFlagType(profile); - - g_SlenderCustomOutroSong[Npc.Index] = GetBossProfileOutroMusicState(profile); + npc.DeathCamEnabled = GetBossProfileDeathCamState(profile); + g_SlenderDeathCamScareSound[npc.Index] = GetBossProfileDeathCamScareSound(profile); + g_SlenderPublicDeathCam[npc.Index] = GetBossProfilePublicDeathCamState(profile); + g_SlenderPublicDeathCamSpeed[npc.Index] = GetBossProfilePublicDeathCamSpeed(profile); + g_SlenderPublicDeathCamAcceleration[npc.Index] = GetBossProfilePublicDeathCamAcceleration(profile); + g_SlenderPublicDeathCamDeceleration[npc.Index] = GetBossProfilePublicDeathCamDeceleration(profile); + g_SlenderPublicDeathCamBackwardOffset[npc.Index] = GetBossProfilePublicDeathCamBackwardOffset(profile); + g_SlenderPublicDeathCamDownwardOffset[npc.Index] = GetBossProfilePublicDeathCamDownwardOffset(profile); + g_SlenderDeathCamOverlay[npc.Index] = GetBossProfileDeathCamOverlayState(profile); + g_SlenderDeathCamOverlayTimeStart[npc.Index] = GetBossProfileDeathCamOverlayStartTime(profile); + g_SlenderDeathCamTime[npc.Index] = GetBossProfileDeathCamTime(profile); + + g_SlenderFakeTimer[npc.Index] = null; + g_SlenderEntityThink[npc.Index] = null; + g_SlenderDeathCamTimer[npc.Index] = null; + g_SlenderDeathCamTarget[npc.Index] = INVALID_ENT_REFERENCE; + g_SlenderNextTeleportTime[npc.Index] = 0.0; + g_SlenderTimeUntilKill[npc.Index] = -1.0; + g_SlenderNextJumpScare[npc.Index] = -1.0; + g_SlenderTimeUntilNextProxy[npc.Index] = -1.0; + + g_SlenderCustomOutroSong[npc.Index] = profileData.OutroMusic; for (int i = 1; i <= MaxClients; i++) { - g_PlayerLastChaseBossEncounterTime[i][Npc.Index] = -1.0; - g_SlenderTeleportPlayersRestTime[Npc.Index][i] = -1.0; + g_SlenderTeleportPlayersRestTime[npc.Index][i] = -1.0; } - g_SlenderTeleportTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderProxyTarget[Npc.Index] = INVALID_ENT_REFERENCE; - g_SlenderTeleportMaxTargetStress[Npc.Index] = 9999.0; - g_SlenderTeleportMaxTargetTime[Npc.Index] = -1.0; - g_SlenderNextTeleportTime[Npc.Index] = -1.0; - g_SlenderTeleportTargetTime[Npc.Index] = -1.0; - - g_SlenderAddCompanionsOnDifficulty[Npc.Index] = false; + g_SlenderTeleportTarget[npc.Index] = INVALID_ENT_REFERENCE; + g_SlenderProxyTarget[npc.Index] = INVALID_ENT_REFERENCE; + g_SlenderTeleportMaxTargetStress[npc.Index] = 9999.0; + g_SlenderTeleportMaxTargetTime[npc.Index] = -1.0; + g_SlenderNextTeleportTime[npc.Index] = -1.0; + g_SlenderTeleportTargetTime[npc.Index] = -1.0; - GetBossProfileEngineSound(profile, g_SlenderEngineSound[Npc.Index], sizeof(g_SlenderEngineSound[])); + g_SlenderAddCompanionsOnDifficulty[npc.Index] = false; - g_SlenderThink[Npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, Npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + if (!profileData.IsPvEBoss) + { + g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } switch (bossType) { case SF2BossType_Chaser: { - NPCChaserOnSelectProfile(Npc.Index, invincible); + NPCChaserOnSelectProfile(npc.Index); } case SF2BossType_Statue: { - NPCStatueOnSelectProfile(profile, Npc.Index); + NPCStatueOnSelectProfile(npc.Index); } } - if (NpcCopyMaster.IsValid()) + if (npcCopyMaster.IsValid()) { - g_SlenderCopyMaster[Npc.Index] = NpcCopyMaster.Index; - g_SlenderNextJumpScare[Npc.Index] = g_SlenderNextJumpScare[NpcCopyMaster.Index]; + npc.CopyMaster = npcCopyMaster; + g_SlenderNextJumpScare[npc.Index] = g_SlenderNextJumpScare[npcCopyMaster.Index]; } else { + if (profileData.IsPvEBoss && profileData.PvESpawnMessagesArray != null && profileData.PvESpawnMessagesArray.Length > 0) + { + char prefix[PLATFORM_MAX_PATH], message[PLATFORM_MAX_PATH]; + strcopy(prefix, sizeof(prefix), profileData.PvESpawnMessagePrefix); + if (prefix[0] == '\0') + { + prefix = "[SF2]"; + } + int messageIndex = GetRandomInt(0, profileData.PvESpawnMessagesArray.Length - 1); + profileData.PvESpawnMessagesArray.GetString(messageIndex, message, sizeof(message)); + if (StrContains(message, "[BOSS]", true) != -1) + { + ReplaceString(message, sizeof(message), "[BOSS]", g_NpcName[npc.Index][1]); + } + int chatLength = strlen(prefix) + strlen(message); + if (chatLength > 255) + { + LogSF2Message("WARNING! PvE spawn message %i has greater than 255 characters on boss index %i, shorten the length of this message.", messageIndex + 1, npc.Index); + } + else + { + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || !g_PlayerEliminated[i]) + { + continue; + } + CPrintToChat(i, "{royalblue}%s {default}%s", prefix, message); + } + } + } if (playSpawnSound) { SF2BossProfileSoundInfo soundInfo; @@ -1968,24 +1964,28 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl { continue; } + if (profileData.IsPvEBoss && !g_PlayerEliminated[i]) + { + continue; + } soundInfo.EmitSound(true, i); } } - if (timerMusic == null) + if (!profileData.IsPvEBoss && timerMusic == null) { bool allowMusic = false; float time; for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - if (g_NpcSoundMusicLoop[Npc.Index][difficulty] > 0.0) + if (g_NpcSoundMusicLoop[npc.Index][difficulty] > 0.0) { allowMusic = true; - g_NpcAllowMusicOnDifficulty[Npc.Index] |= difficulty; + g_NpcAllowMusicOnDifficulty[npc.Index] |= difficulty; } } if (allowMusic) { - time = g_NpcSoundMusicLoop[Npc.Index][g_DifficultyConVar.IntValue]; + time = g_NpcSoundMusicLoop[npc.Index][g_DifficultyConVar.IntValue]; currentMusicTrackNormal[0] = '\0'; currentMusicTrackHard[0] = '\0'; currentMusicTrackInsane[0] = '\0'; @@ -2123,9 +2123,9 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl } soundList = null; - if ((g_NpcAllowMusicOnDifficulty[Npc.Index] & g_DifficultyConVar.IntValue) && time > 0.0) + if ((g_NpcAllowMusicOnDifficulty[npc.Index] & g_DifficultyConVar.IntValue) && time > 0.0) { - timerMusic = CreateTimer(time, BossMusic, Npc.Index, TIMER_FLAG_NO_MAPCHANGE); + timerMusic = CreateTimer(time, BossMusic, npc.Index, TIMER_FLAG_NO_MAPCHANGE); for(int client = 1; client <= MaxClients; client++) { if (IsValidClient(client) && (!g_PlayerEliminated[client] || IsClientInGhostMode(client))) @@ -2148,7 +2148,7 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl } switch (g_DifficultyConVar.IntValue) { - case Difficulty_Normal, Difficulty_Easy: + case Difficulty_Normal: { strcopy(currentMusicTrack, sizeof(currentMusicTrack), currentMusicTrackNormal); } @@ -2199,10 +2199,10 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl companionInfo.Bosses.GetString(i, compProfile, sizeof(compProfile)); if (IsProfileValid(compProfile)) { - SF2NPC_BaseNPC NpcCompanion = AddProfile(compProfile, _, _, false, false); - if (NpcCompanion.IsValid()) + SF2NPC_BaseNPC npcCompanion = AddProfile(compProfile, _, _, false, false); + if (npcCompanion.IsValid()) { - g_SlenderCompanionMaster[NpcCompanion.Index] = Npc.Index; + npcCompanion.CompanionMaster = npc; } } else @@ -2216,34 +2216,30 @@ bool SelectProfile(SF2NPC_BaseNPC Npc, const char[] profile,int additionalBossFl { if (StrContains(spawnType, "on_difficulty_change", false) != -1) { - g_SlenderAddCompanionsOnDifficulty[Npc.Index] = true; + g_SlenderAddCompanionsOnDifficulty[npc.Index] = true; } if (StrContains(spawnType, "on_spawn", false) != -1) { - Npc.AddCompanions(); + npc.AddCompanions(); } } } - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_ARRAYLIST, 0, "Array list %b has been deleted for companions..", companions); - #endif } } Call_StartForward(g_OnBossAddedFwd); - Call_PushCell(Npc.Index); + Call_PushCell(npc.Index); Call_Finish(); - if (g_SlenderCopyMaster[Npc.Index] == -1) + if (!npc.IsCopy) { LogSF2Message("Boss profile %s has been added to the game.", profile); } return true; } -//SF2NPC_BaseNPC NpcCopyMaster=view_as(SF2_INVALID_NPC) <= Bug? -SF2NPC_BaseNPC AddProfile(const char[] strName,int additionalBossFlags=0,SF2NPC_BaseNPC NpcCopyMaster=view_as(SF2_INVALID_NPC), bool spawnCompanions=true, bool playSpawnSound=true, bool invincible = false) + +SF2NPC_BaseNPC AddProfile(const char[] name, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true) { if (SF_IsRenevantMap() && GetRoundState() != SF2RoundState_Escape) { @@ -2251,12 +2247,12 @@ SF2NPC_BaseNPC AddProfile(const char[] strName,int additionalBossFlags=0,SF2NPC_ } for (int i = 0; i < MAX_BOSSES; i++) { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); - if (!Npc.IsValid()) + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) { - if (SelectProfile(Npc, strName, additionalBossFlags, NpcCopyMaster, spawnCompanions, playSpawnSound, invincible)) + if (SelectProfile(npc, name, additionalBossFlags, npcCopyMaster, spawnCompanions, playSpawnSound)) { - return Npc; + return npc; } break; @@ -2266,10 +2262,10 @@ SF2NPC_BaseNPC AddProfile(const char[] strName,int additionalBossFlags=0,SF2NPC_ return SF2_INVALID_NPC; } -void NPCAddCompanions(SF2NPC_BaseNPC Npc) +void NPCAddCompanions(SF2NPC_BaseNPC npc) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.GetProfile(profile, sizeof(profile)); + npc.GetProfile(profile, sizeof(profile)); ArrayList companions = GetBossProfileCompanions(profile); if (companions == null) { @@ -2283,7 +2279,7 @@ void NPCAddCompanions(SF2NPC_BaseNPC Npc) { SF2BossProfileCompanionsInfo companionInfo; companions.GetArray(i, companionInfo, sizeof(companionInfo)); - maxWeight += companionInfo.Weight[GetLocalGlobalDifficulty(Npc.Index)]; + maxWeight += companionInfo.Weight[npc.Difficulty]; } float randomWeight = GetRandomFloat(0.0, maxWeight); @@ -2296,7 +2292,7 @@ void NPCAddCompanions(SF2NPC_BaseNPC Npc) continue; } - float weight = companionInfo.Weight[GetLocalGlobalDifficulty(Npc.Index)]; + float weight = companionInfo.Weight[npc.Difficulty]; if (weight <= 0.0) { continue; @@ -2321,10 +2317,10 @@ void NPCAddCompanions(SF2NPC_BaseNPC Npc) companionsToAdd.GetString(i, compProfile, sizeof(compProfile)); if (IsProfileValid(compProfile)) { - SF2NPC_BaseNPC NpcCompanion = AddProfile(compProfile, _, _, false, false); - if (NpcCompanion.IsValid()) + SF2NPC_BaseNPC npcCompanion = AddProfile(compProfile, _, _, false, false); + if (npcCompanion.IsValid()) { - g_SlenderCompanionMaster[NpcCompanion.Index] = Npc.Index; + npcCompanion.CompanionMaster = npc; } } else @@ -2377,60 +2373,6 @@ bool GetSlenderModel(int bossIndex, int modelState = 0, char[] buffer, int buffe } } } - case 1: - { - modelsArray = GetStatueProfileModelsAverageDist(profile); - switch (difficulty) - { - case Difficulty_Normal: - { - modelsArray.GetString(Difficulty_Normal, buffer, bufferLen); - } - case Difficulty_Hard: - { - modelsArray.GetString(Difficulty_Hard, buffer, bufferLen); - } - case Difficulty_Insane: - { - modelsArray.GetString(Difficulty_Insane, buffer, bufferLen); - } - case Difficulty_Nightmare: - { - modelsArray.GetString(Difficulty_Nightmare, buffer, bufferLen); - } - case Difficulty_Apollyon: - { - modelsArray.GetString(Difficulty_Apollyon, buffer, bufferLen); - } - } - } - case 2: - { - modelsArray = GetStatueProfileModelsCloseDist(profile); - switch (difficulty) - { - case Difficulty_Normal: - { - modelsArray.GetString(Difficulty_Normal, buffer, bufferLen); - } - case Difficulty_Hard: - { - modelsArray.GetString(Difficulty_Hard, buffer, bufferLen); - } - case Difficulty_Insane: - { - modelsArray.GetString(Difficulty_Insane, buffer, bufferLen); - } - case Difficulty_Nightmare: - { - modelsArray.GetString(Difficulty_Nightmare, buffer, bufferLen); - } - case Difficulty_Apollyon: - { - modelsArray.GetString(Difficulty_Apollyon, buffer, bufferLen); - } - } - } } return true; } @@ -2454,11 +2396,7 @@ void ChangeAllSlenderModels() NPCGetProfile(npcIndex, profile, sizeof(profile)); GetSlenderModel(npcIndex, _, buffer, sizeof(buffer)); SetEntityModel(slender, buffer); - int glow = EntRefToEntIndex(g_NpcGlowEntity[npcIndex]); - if (glow && glow != INVALID_ENT_REFERENCE) - { - SetEntityModel(EntRefToEntIndex(g_NpcGlowEntity[npcIndex]), buffer); - } + SetEntityModel(EntRefToEntIndex(g_NpcGlowEntity[npcIndex]), buffer); if (NPCGetModelSkinMax(npcIndex) > 0) { int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(npcIndex)); @@ -2494,8 +2432,7 @@ void ChangeAllSlenderModels() if (NPCGetType(npcIndex) == SF2BossType_Chaser) { float tempHitboxMins[3]; - NPCChaserUpdateBossAnimation(npcIndex, slender, g_SlenderState[npcIndex]); - if (NPCGetRaidHitbox(npcIndex) == 1) + if (NPCGetRaidHitbox(npcIndex)) { CopyVector(g_SlenderDetectMins[npcIndex], tempHitboxMins); tempHitboxMins[2] = 10.0; @@ -2505,7 +2442,7 @@ void ChangeAllSlenderModels() SetEntPropVector(slender, Prop_Send, "m_vecMinsPreScaled", tempHitboxMins); SetEntPropVector(slender, Prop_Send, "m_vecMaxsPreScaled", g_SlenderDetectMaxs[npcIndex]); } - else if (NPCGetRaidHitbox(npcIndex) == 0) + else { CopyVector(HULL_HUMAN_MINS, tempHitboxMins); tempHitboxMins[2] = 10.0; @@ -2521,33 +2458,25 @@ void ChangeAllSlenderModels() void RemoveProfile(int bossIndex) { - RemoveSlender(bossIndex); + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(bossIndex); + controller.UnSpawn(); // Call our forward. Call_StartForward(g_OnBossRemovedFwd); - Call_PushCell(bossIndex); + Call_PushCell(controller.Index); Call_Finish(); Call_StartForward(g_OnBossRemovedPFwd); - Call_PushCell(bossIndex); + Call_PushCell(controller); Call_Finish(); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - NPCChaserOnRemoveProfile(bossIndex); + controller.GetProfile(profile, sizeof(profile)); // Clean up on the clients. for (int i = 1; i <= MaxClients; i++) { - g_SlenderLastFoundPlayer[bossIndex][i] = -1.0; - g_PlayerLastChaseBossEncounterTime[i][bossIndex] = -1.0; g_SlenderTeleportPlayersRestTime[bossIndex][i] = -1.0; - - for (int i2 = 0; i2 < 3; i2++) - { - g_SlenderLastFoundPlayerPos[bossIndex][i][i2] = 0.0; - } } g_NpcTeleportType[bossIndex] = -1; @@ -2558,46 +2487,39 @@ void RemoveProfile(int bossIndex) g_SlenderNextTeleportTime[bossIndex] = -1.0; g_SlenderTeleportTargetTime[bossIndex] = -1.0; g_SlenderTimeUntilKill[bossIndex] = -1.0; - g_NpcHasHealthbarEnabled[bossIndex] = false; // Remove all copies associated with me. for (int i = 0; i < MAX_BOSSES; i++) { - if (i == bossIndex || NPCGetUniqueID(i) == -1) + SF2NPC_BaseNPC check = SF2NPC_BaseNPC(i); + if (check == controller || !check.IsValid()) { continue; } - if (g_SlenderCopyMaster[i] == bossIndex) + if (check.CopyMaster == controller) { - LogMessage("Removed boss index %d because it is a copy of boss index %d", i, bossIndex); - NPCRemove(i); + LogMessage("Removed boss index %d because it is a copy of boss index %d", check.Index, controller.Index); + check.Remove(); } } for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - g_NpcSearchRadius[bossIndex][difficulty] = 0.0; - g_NpcHearingRadius[bossIndex][difficulty] = 0.0; - g_NpcTauntAlertRange[bossIndex][difficulty] = 0.0; - g_NpcInstantKillCooldown[bossIndex][difficulty] = 0.0; g_SlenderProxyTeleportMinRange[bossIndex][difficulty] = 0.0; g_SlenderProxyTeleportMaxRange[bossIndex][difficulty] = 0.0; } - NPCSetProfile(bossIndex, ""); g_NpcType[bossIndex] = -1; g_NpcProfileIndex[bossIndex] = -1; g_NpcUniqueProfileIndex[bossIndex] = -1; - NPCSetFlags(bossIndex, 0); + controller.Flags = 0; g_NpcFieldOfView[bossIndex] = 0.0; - g_NpcBackstabFOV[bossIndex] = 0.0; - g_NpcAddSpeed[bossIndex] = 0.0; - g_NpcAddMaxSpeed[bossIndex] = 0.0; - g_NpcAddAcceleration[bossIndex] = 0.0; + controller.SetAddSpeed(0.0); + controller.SetAddAcceleration(0.0); g_NpcStepSize[bossIndex] = 0.0; g_NpcHasDiscoMode[bossIndex] = false; @@ -2609,50 +2531,21 @@ void RemoveProfile(int bossIndex) g_NpcFestiveLightDistance[bossIndex] = 0.0; g_NpcFestiveLightRadius[bossIndex] = 0.0; - g_NpcEnemy[bossIndex] = INVALID_ENT_REFERENCE; - NPCSetDeathCamEnabled(bossIndex, false); - g_SlenderCopyMaster[bossIndex] = -1; - g_SlenderCompanionMaster[bossIndex] = -1; + controller.CopyMaster = SF2_INVALID_NPC; + controller.CompanionMaster = SF2_INVALID_NPC; g_NpcUniqueID[bossIndex] = -1; - g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; - g_SlenderAttackTimer[bossIndex] = null; - g_SlenderLaserTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_SlenderChaseInitialTimer[bossIndex] = null; - g_SlenderRage1Timer[bossIndex] = null; - g_SlenderRage2Timer[bossIndex] = null; - g_SlenderRage3Timer[bossIndex] = null; - g_SlenderHealTimer[bossIndex] = null; - g_SlenderHealDelayTimer[bossIndex] = null; - g_SlenderHealEventTimer[bossIndex] = null; - g_SlenderStartFleeTimer[bossIndex] = null; - g_SlenderSpawnTimer[bossIndex] = null; g_SlenderDeathCamTimer[bossIndex] = null; g_SlenderDeathCamTarget[bossIndex] = INVALID_ENT_REFERENCE; g_SlenderThink[bossIndex] = null; g_SlenderEntityThink[bossIndex] = null; - g_SlenderBurnTimer[bossIndex] = null; - g_SlenderBleedTimer[bossIndex] = null; - g_SlenderMarkedTimer[bossIndex] = null; - g_SlenderStopBurningTimer[bossIndex] = 0.0; - g_SlenderStopBleedingTimer[bossIndex] = 0.0; - g_SlenderIsBurning[bossIndex] = false; - g_SlenderIsMarked[bossIndex] = false; - g_SlenderSoundTarget[bossIndex] = INVALID_ENT_REFERENCE; - g_SlenderSeeTarget[bossIndex] = INVALID_ENT_REFERENCE; g_SlenderCustomOutroSong[bossIndex] = false; g_SlenderFakeTimer[bossIndex] = null; - g_SlenderLastKill[bossIndex] = -1.0; - g_SlenderState[bossIndex] = STATE_IDLE; - g_SlenderTarget[bossIndex] = INVALID_ENT_REFERENCE; - g_SlenderTargetIsVisible[bossIndex] = false; g_SlenderModel[bossIndex] = INVALID_ENT_REFERENCE; g_SlenderBoxingBossIsKilled[bossIndex] = false; g_SlenderTimeUntilNextProxy[bossIndex] = -1.0; - g_NpcInstantKillRadius[bossIndex] = 0.0; g_NpcScareRadius[bossIndex] = 0.0; g_NpcHasPlayerScareSpeedBoost[bossIndex] = false; g_NpcPlayerSpeedBoostDuration[bossIndex] = 0.0; @@ -2660,10 +2553,6 @@ void RemoveProfile(int bossIndex) g_NpcPlayerScareReactionType[bossIndex] = 0; g_NpcHasPlayerScareReplenishSprint[bossIndex] = false; g_NpcPlayerScareReplenishSprintAmount[bossIndex] = 0; - g_NpcPlayerScareVictin[bossIndex] = INVALID_ENT_REFERENCE; - g_NpcChasingScareVictin[bossIndex] = false; - g_NpcLostChasingScareVictim[bossIndex] = false; - g_NpcVelocityCancel[bossIndex] = false; g_SlenderRenderFX[bossIndex] = 0; g_SlenderRenderMode[bossIndex] = 0; @@ -2681,24 +2570,31 @@ void RemoveProfile(int bossIndex) { g_SlenderRenderColor[bossIndex][i] = 0; } + + controller.SetProfile(""); } -void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) +void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) { + if (!IsRoundPlaying()) + { + //return; + } + if (SF_IsRenevantMap() && GetRoundState() != SF2RoundState_Escape) { return; // Stop spawning bosses before all pages are picked up in Renevant. } char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - Npc.UnSpawn(); - Npc.GetProfile(profile,sizeof(profile)); + npc.UnSpawn(); + npc.GetProfile(profile,sizeof(profile)); float truePos[3], trueAng[3]; trueAng[1] = GetRandomFloat(0.0, 360.0); AddVectors(truePos, pos, truePos); - int bossIndex = Npc.Index; + int bossIndex = npc.Index; char buffer[PLATFORM_MAX_PATH]; @@ -2710,11 +2606,12 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) { case SF2BossType_Statue: { - entity = Spawn_Statue(Npc, truePos, trueAng); + entity = view_as(Spawn_Statue(npc, truePos, trueAng)); } case SF2BossType_Chaser: { - CBaseNPC npcBoss = CBaseNPC(); + entity = view_as(Spawn_Chaser(npc, truePos, trueAng)); + /*CBaseNPC npcBoss = CBaseNPC(); CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npcBoss.GetEntity()); CBaseNPC_Locomotion locomotion = npcBoss.GetLocomotion(); npcEntity.Hook_HandleAnimEvent(CBaseAnimating_HandleAnimEvent); @@ -2744,9 +2641,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) npcBoss.flWalkSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; npcBoss.flRunSpeed = g_SlenderCalculatedSpeed[bossIndex]; - npcBoss.flMaxYawRate = NPCGetTurnRate(bossIndex); - - locomotion.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); if (!SF_IsBoxingMap()) { locomotion.SetCallback(LocomotionCallback_IsAbleToJumpAcrossGaps, CanJumpAcrossGaps); @@ -2755,11 +2649,10 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) locomotion.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); } - float pathingBoxMin[3], pathingBoxMax[3]; - if (NPCGetRaidHitbox(bossIndex) == 1) + if (NPCGetRaidHitbox(bossIndex)) { - pathingBoxMin = g_SlenderDetectMins[bossIndex]; - pathingBoxMax = g_SlenderDetectMaxs[bossIndex]; + npcBoss.SetBodyMins(g_SlenderDetectMins[bossIndex]); + npcBoss.SetBodyMaxs(g_SlenderDetectMaxs[bossIndex]); npcEntity.SetPropVector(Prop_Send, "m_vecMins", g_SlenderDetectMins[bossIndex]); npcEntity.SetPropVector(Prop_Send, "m_vecMaxs", g_SlenderDetectMaxs[bossIndex]); @@ -2767,10 +2660,10 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) npcEntity.SetPropVector(Prop_Send, "m_vecMinsPreScaled", g_SlenderDetectMins[bossIndex]); npcEntity.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", g_SlenderDetectMaxs[bossIndex]); } - else if (NPCGetRaidHitbox(bossIndex) == 0) + else { - pathingBoxMin = HULL_HUMAN_MINS; - pathingBoxMax = HULL_HUMAN_MAXS; + npcBoss.SetBodyMins(HULL_HUMAN_MINS); + npcBoss.SetBodyMaxs(HULL_HUMAN_MAXS); npcEntity.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); npcEntity.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); @@ -2778,8 +2671,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) npcEntity.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); npcEntity.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); } - npcBoss.SetBodyMins(pathingBoxMin); - npcBoss.SetBodyMaxs(pathingBoxMax); if (SF_IsBoxingMap()) { @@ -2811,7 +2702,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) g_SlenderTargetSoundType[bossIndex] = SoundType_None; g_SlenderInvestigatingSound[bossIndex] = false; g_SlenderNextStunTime[bossIndex] = -1.0; - g_SlenderNextCloakTime[bossIndex] = -1.0; g_NpcHasCloaked[bossIndex] = false; g_SlenderLastHeardFootstep[bossIndex] = 0.0; g_SlenderLastHeardVoice[bossIndex] = 0.0; @@ -2851,7 +2741,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) g_SlenderIsBurning[bossIndex] = false; g_SlenderIsMarked[bossIndex] = false; g_NpcAddSpeed[bossIndex] = 0.0; - g_NpcAddMaxSpeed[bossIndex] = 0.0; g_NpcAddAcceleration[bossIndex] = 0.0; g_SlenderAutoChaseCount[bossIndex] = 0; g_SlenderAutoChaseCooldown[bossIndex] = 0.0; @@ -2906,10 +2795,6 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) NPCSetFlags(bossIndex,NPCGetFlags(bossIndex)|SFF_NOTELEPORT); UpdateHealthBar(bossIndex); } - /*if (SF_BossesChaseEndlessly() || SF_IsRaidMap()) - { - NPCSetFlags(bossIndex,NPCGetFlags(bossIndex)|SFF_NOTELEPORT); - }*/ //Stun Health float maxHealth = NPCChaserGetStunInitialHealth(bossIndex); @@ -2932,20 +2817,20 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) if (SF_IsBoxingMap() && TF2_GetPlayerClass(client) == TFClass_Scout) { NPCSetAddSpeed(bossIndex, 10.0); - NPCSetAddMaxSpeed(bossIndex, 15.0); } } } NPCChaserSetStunInitialHealth(bossIndex, maxHealth); - NPCChaserSetStunHealth(bossIndex, NPCChaserGetStunInitialHealth(bossIndex)); int entHealth = RoundToCeil(maxHealth + 1500000000.0); npcEntity.SetProp(Prop_Data, "m_iHealth", entHealth); npcEntity.SetProp(Prop_Data, "m_iMaxHealth", entHealth); - entity = npcEntity; + entity = npcEntity;*/ } } + entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + int difficulty = GetLocalGlobalDifficulty(bossIndex); g_BossPathFollower[bossIndex].SetMinLookAheadDistance(GetBossProfileNodeDistanceLookAhead(profile)); @@ -2983,16 +2868,21 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) } } - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, entity.iEnt, Hook_BossUpdateTransmitState); - SetEntityFlags(entity.iEnt, FL_NPC); - SetEntityTransmitState(entity.iEnt, FL_EDICT_ALWAYS); + entity.AddFlag(FL_NPC); + SF2BossProfileData data; + data = npc.GetProfileData(); + if (data.IsPvEBoss) + { + entity.AddFlag(FL_NOTARGET); + } + //SetEntityTransmitState(entity.index, FL_EDICT_ALWAYS); - g_SlenderEnt[bossIndex] = EntIndexToEntRef(entity.iEnt); + g_SlenderEnt[bossIndex] = EntIndexToEntRef(entity.index); - if (g_SlenderEngineSound[bossIndex][0] != '\0') + if (data.EngineSound[0] != '\0') { - EmitSoundToAll(g_SlenderEngineSound[bossIndex], entity.iEnt, SNDCHAN_STATIC, GetBossProfileEngineSoundLevel(profile), - _, GetBossProfileEngineSoundVolume(profile)); + EmitSoundToAll(data.EngineSound, entity.index, SNDCHAN_STATIC, data.EngineSoundLevel, + _, data.EngineSoundVolume); } if (GetBossProfileSpawnParticleState(profile)) @@ -3000,64 +2890,67 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) SlenderCreateParticleSpawnEffect(bossIndex); } - if (NPCGetCustomOutlinesState(bossIndex)) + if (!data.IsPvEBoss) { - if (!NPCGetRainbowOutlineState(bossIndex)) + if (NPCGetCustomOutlinesState(bossIndex)) { - int color[4]; - color[0] = NPCGetOutlineColorR(bossIndex); - color[1] = NPCGetOutlineColorG(bossIndex); - color[2] = NPCGetOutlineColorB(bossIndex); - color[3] = NPCGetOutlineTransparency(bossIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) + if (!NPCGetRainbowOutlineState(bossIndex)) { - color[2] = 255; + int color[4]; + color[0] = NPCGetOutlineColorR(bossIndex); + color[1] = NPCGetOutlineColorG(bossIndex); + color[2] = NPCGetOutlineColorB(bossIndex); + color[3] = NPCGetOutlineTransparency(bossIndex); + if (color[0] < 0) + { + color[0] = 0; + } + if (color[1] < 0) + { + color[1] = 0; + } + if (color[2] < 0) + { + color[2] = 0; + } + if (color[3] < 0) + { + color[3] = 0; + } + if (color[0] > 255) + { + color[0] = 255; + } + if (color[1] > 255) + { + color[1] = 255; + } + if (color[2] > 255) + { + color[2] = 255; + } + if (color[3] > 255) + { + color[3] = 255; + } + SlenderAddGlow(bossIndex, color); } - if (color[3] > 255) + else { - color[3] = 255; + SlenderAddGlow(bossIndex, {0, 0, 0, 0}); } - SlenderAddGlow(bossIndex, color); } else { - SlenderAddGlow(bossIndex, view_as({0, 0, 0, 0})); + int purple[4] = {150, 0, 255, 255}; + SlenderAddGlow(bossIndex, purple); } } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(bossIndex, purple); - } int master = g_SlenderCopyMaster[bossIndex]; int flags = NPCGetFlags(bossIndex); - if (MAX_BOSSES > master >= 0 && NPCGetFakeCopyState(bossIndex)) + if (MAX_BOSSES > master >= 0 && NPCGetFakeCopyState(bossIndex, difficulty)) { if (!SF_SpecialRound(SPECIALROUND_DREAMFAKEBOSSES)) { @@ -3065,13 +2958,14 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) } } - SlenderSpawnEffects(bossIndex); + ArrayList effects = data.EffectsArray; + SlenderSpawnEffects(effects, bossIndex); - if (IsValidEntity(g_SlenderEnt[bossIndex])) + if (entity.IsValid()) { SF2BossProfileSoundInfo soundInfo; GetBossProfileSpawnLocalSounds(profile, soundInfo); - soundInfo.EmitSound(_, EntRefToEntIndex(g_SlenderEnt[bossIndex])); + soundInfo.EmitSound(_, entity.index); } // Call our forward. @@ -3080,51 +2974,15 @@ void SpawnSlender(SF2NPC_BaseNPC Npc, const float pos[3]) Call_Finish(); } -static MRESReturn Hook_BossUpdateTransmitState(int bossEntity, DHookReturn hookReturn) -{ - if (!g_Enabled || !IsValidEntity(bossEntity) || NPCGetFromEntIndex(bossEntity) == -1) - { - return MRES_Ignored; - } - - hookReturn.Value = SetEntityTransmitState(bossEntity, FL_EDICT_ALWAYS); - return MRES_Supercede; -} - -static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) +bool ClimbUpCBase(CBaseNPC_Locomotion loco, const float goal[3], const float fwd[3], int entity) { - if (IsValidEntity(other)) + INextBot bot = loco.GetBot(); + SF2_BaseBoss boss = SF2_BaseBoss(bot.GetEntity()); + if (boss.IsValid()) { - char class[32]; - GetEdictClassname(other, class, sizeof(class)); - if (strcmp(class, "player") == 0) - { - if (!SF_IsBoxingMap() && !g_PlayerProxy[other] && !IsClientInGhostMode(other) && GetClientTeam(other) != TFTeam_Blue && !IsClientInDeathCam(other)) - { - return true; - } - } - if (IsEntityAProjectile(other)) - { - return false; - } + boss.IsJumping = true; } - return loco.CallBaseFunction(other); -} - -bool CanJumpAcrossGaps(CBaseNPC_Locomotion loco) -{ - return false; -} - -void JumpAcrossGapsCBase(CBaseNPC_Locomotion loco, const float landingGoal[3], const float landingForward[3]) -{ - return; -} - -bool ClimbUpCBase(CBaseNPC_Locomotion loco, const float goal[3], const float fwd[3], int entity) -{ - return false; + return loco.CallBaseFunction(goal, fwd, entity); } MRESReturn CBaseAnimating_HandleAnimEvent(int thisInt, DHookParam params) @@ -3144,111 +3002,43 @@ MRESReturn CBaseAnimating_HandleAnimEvent(int thisInt, DHookParam params) return MRES_Ignored; } -void RemoveSlender(int bossIndex) +void UpdateHealthBar(int bossIndex, int optionalSetPercent = -1) { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int bossEnt = NPCGetEntIndex(bossIndex); - - if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(bossIndex)); + if (!chaser.IsValid()) { - Call_StartForward(g_OnBossDespawnFwd); - Call_PushCell(bossIndex); - Call_Finish(); - - //Turn off all slender's effects in order to prevent some bugs. - SlenderRemoveEffects(bossEnt, true); - - if (NPCGetType(bossIndex) == SF2BossType_Statue) - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderStatueBossProcessMovement); - // Stop all possible looping sounds. - SF2BossProfileSoundInfo soundInfo; - GetStatueProfileMoveSounds(profile, soundInfo); - soundInfo.StopAllSounds(bossEnt); - } - else - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderChaseBossProcessMovement); - SDKUnhook(bossEnt, SDKHook_OnTakeDamageAlive, Hook_SlenderOnTakeDamage); - Despawn_Chaser(bossIndex); - } - - if (g_BossPathFollower[bossIndex].IsValid()) - { - g_BossPathFollower[bossIndex].Invalidate(); - } - - int glowEnt = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - if (glowEnt && glowEnt != INVALID_ENT_REFERENCE) - { - RemoveEntity(glowEnt); - g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; - } - - if (GetBossProfileDespawnParticleState(profile)) - { - SlenderCreateParticleSpawnEffect(bossIndex, true); - } - - if (g_SlenderEngineSound[bossIndex][0] != '\0') - { - StopSound(bossEnt, SNDCHAN_STATIC, g_SlenderEngineSound[bossIndex]); - } - - if (NPCGetFlags(bossIndex) & SFF_HASSTATICLOOPLOCALSOUND) - { - char loopSound[PLATFORM_MAX_PATH]; - GetBossProfileStaticLocalSound(profile, loopSound, sizeof(loopSound)); - - if (loopSound[0] != '\0') - { - StopSound(bossEnt, SNDCHAN_STATIC, loopSound); - } - } - int bossFlags = NPCGetFlags(bossIndex); - if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || !NPCGetHealthbarState(bossIndex)) - { - NPCSetFlags(bossIndex, bossFlags & ~SFF_NOTELEPORT); - } - - g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; - RemoveEntity(bossEnt); + return; } - else + float maxHealth = chaser.MaxHealth; + float health = float(chaser.GetProp(Prop_Data, "m_iHealth")); + if (chaser.GetProp(Prop_Data, "m_takedamage") == DAMAGE_EVENTS_ONLY) { - g_SlenderEnt[bossIndex] = INVALID_ENT_REFERENCE; - g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; + health = chaser.StunHealth; + maxHealth = chaser.MaxStunHealth; } - if (NPCGetHealthbarState(bossIndex) && g_HealthBar != -1) + if (g_HealthBar == -1) { - int npcIndex = 0; - for (npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - if (NPCGetHealthbarState(npcIndex)) - { - int tempSlender = NPCGetEntIndex(npcIndex); - - if (tempSlender && tempSlender != INVALID_ENT_REFERENCE) - { - UpdateHealthBar(npcIndex); - break; - } - } - } - if (npcIndex == MAX_BOSSES) - { - UpdateHealthBar(bossIndex, 0); - } + return; } + int healthPercent; + SetEntProp(g_HealthBar, Prop_Send, "m_iBossState", 0); + healthPercent = RoundToCeil((health / maxHealth) * float(255)); + if (healthPercent > 255) + { + healthPercent = 255; + } + else if (healthPercent <= 0) + { + healthPercent = 0; + } + if (optionalSetPercent > -1) + { + healthPercent = optionalSetPercent; + } + SetEntProp(g_HealthBar, Prop_Send, "m_iBossHealthPercentageByte", healthPercent); } -Action Timer_BossBurn(Handle timer, any entref) +/*Action Timer_BossBurn(Handle timer, any entref) { if (!g_Enabled) { @@ -3278,7 +3068,7 @@ Action Timer_BossBurn(Handle timer, any entref) g_SlenderIsBurning[bossIndex] = false; return Plugin_Stop; } - int state = g_SlenderState[bossIndex]; + //int state = g_SlenderState[bossIndex]; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -3297,7 +3087,7 @@ Action Timer_BossBurn(Handle timer, any entref) { float myPos[3]; GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - NPCBossTriggerStun(bossIndex, slender, profile, myPos); + //NPCBossTriggerStun(bossIndex, slender, profile, myPos); Call_StartForward(g_OnBossStunnedFwd); Call_PushCell(bossIndex); Call_PushCell(-1); @@ -3341,7 +3131,7 @@ Action Timer_BossBleed(Handle timer, any entref) g_SlenderBleedTimer[bossIndex] = null; return Plugin_Stop; } - int state = g_SlenderState[bossIndex]; + //int state = g_SlenderState[bossIndex]; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -3359,7 +3149,7 @@ Action Timer_BossBleed(Handle timer, any entref) { float myPos[3]; GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - NPCBossTriggerStun(bossIndex, slender, profile, myPos); + //NPCBossTriggerStun(bossIndex, slender, profile, myPos); Call_StartForward(g_OnBossStunnedFwd); Call_PushCell(bossIndex); Call_PushCell(-1); @@ -3407,7 +3197,7 @@ Action Timer_BossMarked(Handle timer, any entref) g_SlenderIsMarked[bossIndex] = false; return Plugin_Stop; -} +}*/ static Action Hook_SlenderGlowSetTransmit(int entity,int other) { @@ -3434,646 +3224,185 @@ static Action Hook_SlenderGlowSetTransmit(int entity,int other) return Plugin_Handled; } -bool SlenderCanHearPlayer(int bossIndex,int client, SoundType soundType) +bool SlenderUsesBlink(int bossIndex) { - if (!IsValidClient(client) || !IsPlayerAlive(client)) + if (NPCGetType(bossIndex) == SF2BossType_Statue) { - return false; + return true; } + return false; +} - int difficulty = GetLocalGlobalDifficulty(bossIndex); +void SlenderPrintChatMessage(int bossIndex, int player) +{ + if (bossIndex == -1) + { + return; + } int slender = NPCGetEntIndex(bossIndex); if (!slender || slender == INVALID_ENT_REFERENCE) { - return false; + return; } - TFClassType class = TF2_GetPlayerClass(client); - int classToInt = view_as(class); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - float hisPos[3], myPos[3]; - GetClientAbsOrigin(client, hisPos); - SlenderGetAbsOrigin(bossIndex, myPos); - - float hearRadius = NPCGetHearingRadius(bossIndex, difficulty); - if (hearRadius <= 0.0) + ArrayList deathMessages = GetBossProfileChatDeathMessages(profile); + if (deathMessages == null) { - return false; + return; } - float distance = GetVectorSquareMagnitude(hisPos, myPos); - - // Trace check. - Handle trace = null; - bool traceHit = false; - - float myEyePos[3]; - SlenderGetEyePosition(bossIndex, myEyePos); - - float cooldown = 0.0; + int difficultyIndex = g_NpcDeathMessageDifficultyIndexes[bossIndex]; - switch (soundType) + char indexes[8], currentIndex[2]; + FormatEx(indexes, sizeof(indexes), "%d", difficultyIndex); + FormatEx(currentIndex, sizeof(currentIndex), "%d", g_DifficultyConVar.IntValue); + char number = currentIndex[0]; + int difficultyNumber = 0; + if (FindCharInString(indexes, number) != -1) + { + difficultyNumber += g_DifficultyConVar.IntValue; + } + if (indexes[0] != '\0' && currentIndex[0] != '\0' && difficultyNumber != -1) { - case SoundType_Footstep, SoundType_LoudFootstep, SoundType_QuietFootstep: + int currentAtkIndex = StringToInt(currentIndex); + if (difficultyNumber == currentAtkIndex) //WHOA, legacy system actually won't be legacy. { - if (!(GetEntityFlags(client) & FL_ONGROUND)) + char buffer[PLATFORM_MAX_PATH], prefix[PLATFORM_MAX_PATH], name[SF2_MAX_NAME_LENGTH], time[PLATFORM_MAX_PATH]; + int roundTime = RoundToNearest(g_RoundTimeMessage); + int randomMessage = GetRandomInt(0, deathMessages.Length - 1); + GetBossProfileChatDeathMessagePrefix(profile, prefix, sizeof(prefix)); + deathMessages.GetString(randomMessage, buffer, sizeof(buffer)); + NPCGetBossName(bossIndex, name, sizeof(name)); + FormatEx(time, sizeof(time), "%d", roundTime); + char playerName[32], replacePlayer[64]; + FormatEx(playerName, sizeof(playerName), "%N", player); + if (prefix[0] == '\0') { - return false; + prefix = "[SF2]"; } - - if (soundType == SoundType_QuietFootstep) + if (buffer[0] != '\0' && GetClientTeam(player) == 2) { - if (GetChaserProfileQuietFootstepAddThreshold(profile, difficulty) <= 0) + if (StrContains(buffer, "[PLAYER]", true) != -1) { - return false; + FormatEx(replacePlayer, sizeof(replacePlayer), "{red}%s{default}", playerName); + ReplaceString(buffer, sizeof(buffer), "[PLAYER]", replacePlayer); } - cooldown = GetChaserProfileHearQuietFootstepCooldown(profile, difficulty); - distance *= 1.85; - } - else if (soundType == SoundType_LoudFootstep) - { - if (GetChaserProfileLoudFootstepAddThreshold(profile, difficulty) <= 0) + if (StrContains(buffer, "[BOSS]", true) != -1) { - return false; + ReplaceString(buffer, sizeof(buffer), "[BOSS]", name); } - cooldown = GetChaserProfileHearLoudFootstepCooldown(profile, difficulty); - distance *= 0.66; - } - else - { - if (GetChaserProfileFootstepAddThreshold(profile, difficulty) <= 0) + if (StrContains(buffer, "[ROUNDTIME]", true) != -1) { - return false; - } - cooldown = GetChaserProfileHearFootstepCooldown(profile, difficulty); - } - - trace = TR_TraceRayFilterEx(myPos, hisPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, slender); - traceHit = TR_DidHit(trace); - delete trace; - } - case SoundType_Voice, SoundType_Flashlight: - { - float hisEyePos[3]; - GetClientEyePosition(client, hisEyePos); - - trace = TR_TraceRayFilterEx(myEyePos, hisEyePos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, slender); - traceHit = TR_DidHit(trace); - delete trace; - - if (!IsClassConfigsValid() || soundType == SoundType_Voice) - { - distance *= 0.5; - } - else if (IsClassConfigsValid() && soundType == SoundType_Flashlight) - { - distance *= g_ClassFlashlightSoundRadius[classToInt]; - } - - if (soundType == SoundType_Voice) - { - if (GetChaserProfileVoiceAddThreshold(profile, difficulty) <= 0) - { - return false; - } - cooldown = GetChaserProfileHearVoiceCooldown(profile, difficulty); - } - else if (soundType == SoundType_Flashlight) - { - if (GetChaserProfileFlashlightAddThreshold(profile, difficulty) <= 0) - { - return false; - } - cooldown = GetChaserProfileHearFlashlightCooldown(profile, difficulty); - } - } - case SoundType_Weapon: - { - if (GetChaserProfileWeaponAddThreshold(profile, difficulty) <= 0) - { - return false; - } - - float hisMins[3], hisMaxs[3]; - GetEntPropVector(client, Prop_Send, "m_vecMins", hisMins); - GetEntPropVector(client, Prop_Send, "m_vecMaxs", hisMaxs); - - float middle[3]; - for (int i = 0; i < 2; i++) - { - middle[i] = (hisMins[i] + hisMaxs[i]) / 2.0; - } - - float endPos[3]; - GetClientAbsOrigin(client, endPos); - AddVectors(hisPos, middle, endPos); - - trace = TR_TraceRayFilterEx(myEyePos, endPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, slender); - traceHit = TR_DidHit(trace); - delete trace; - - distance *= 0.66; - - cooldown = GetChaserProfileHearWeaponCooldown(profile, difficulty); - } - } - - delete trace; - - if (traceHit) - { - distance *= 1.66; - } - - if (!IsClassConfigsValid()) - { - if (TF2_GetPlayerClass(client) == TFClass_Spy) - { - distance *= 1.3; - } - - if (TF2_GetPlayerClass(client) == TFClass_Scout) - { - distance *= 0.8; - } - } - else - { - distance *= g_ClassBossHearingSensitivity[classToInt]; - } - - if (distance > SquareFloat(hearRadius)) - { - return false; - } - - if (g_SlenderSoundPositionSetCooldown[bossIndex] > GetGameTime() && cooldown > 0.0) - { - return false; - } - - g_SlenderSoundPositionSetCooldown[bossIndex] = GetGameTime() + cooldown; - - return true; -} - -bool SlenderUsesBlink(int bossIndex) -{ - if (NPCGetType(bossIndex) == SF2BossType_Statue) - { - return true; - } - return false; -} - -void SlenderPrintChatMessage(int bossIndex, int player) -{ - if (bossIndex == -1) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - ArrayList deathMessages = GetBossProfileChatDeathMessages(profile); - if (deathMessages == null) - { - return; - } - - int difficultyIndex = g_NpcDeathMessageDifficultyIndexes[bossIndex]; - - char indexes[8], currentIndex[2]; - FormatEx(indexes, sizeof(indexes), "%d", difficultyIndex); - FormatEx(currentIndex, sizeof(currentIndex), "%d", g_DifficultyConVar.IntValue); - char number = currentIndex[0]; - int difficultyNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - difficultyNumber += g_DifficultyConVar.IntValue; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && difficultyNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (difficultyNumber == currentAtkIndex) //WHOA, legacy system actually won't be legacy. - { - char buffer[PLATFORM_MAX_PATH], prefix[PLATFORM_MAX_PATH], name[SF2_MAX_NAME_LENGTH], time[PLATFORM_MAX_PATH]; - int roundTime = RoundToNearest(g_RoundTimeMessage); - int randomMessage = GetRandomInt(0, deathMessages.Length - 1); - GetBossProfileChatDeathMessagePrefix(profile, prefix, sizeof(prefix)); - deathMessages.GetString(randomMessage, buffer, sizeof(buffer)); - NPCGetBossName(bossIndex, name, sizeof(name)); - FormatEx(time, sizeof(time), "%d", roundTime); - char playerName[32], replacePlayer[64]; - FormatEx(playerName, sizeof(playerName), "%N", player); - if (prefix[0] == '\0') - { - prefix = "[SF2]"; - } - if (buffer[0] != '\0' && GetClientTeam(player) == 2) - { - if (StrContains(buffer, "[PLAYER]", true) != -1) - { - FormatEx(replacePlayer, sizeof(replacePlayer), "{red}%s{default}", playerName); - ReplaceString(buffer, sizeof(buffer), "[PLAYER]", replacePlayer); - } - if (StrContains(buffer, "[BOSS]", true) != -1) - { - ReplaceString(buffer, sizeof(buffer), "[BOSS]", name); - } - if (StrContains(buffer, "[ROUNDTIME]", true) != -1) - { - ReplaceString(buffer, sizeof(buffer), "[ROUNDTIME]", time); - } - int chatLength = strlen(prefix) + strlen(buffer); - if (chatLength > 255) - { - LogSF2Message("WARNING! Death message %i has greater than 255 characters on boss index %i, shorten the length of this message.", randomMessage + 1, bossIndex); - } - else - { - CPrintToChatAll("{royalblue}%s{default} %s", prefix, buffer); - } - } - } - } -} - -void SlenderPerformVoice(int bossIndex, const int attackIndex=-1, int soundType = -1) -{ - if (bossIndex == -1 || soundType == -1) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - ArrayList soundList; - switch (soundType) - { - case SF2BossSound_Idle: - { - GetChaserProfileIdleSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Alert: - { - GetChaserProfileAlertSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Chasing: - { - GetChaserProfileChasingSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_ChaseInitial: - { - GetChaserProfileChaseInitialSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Stun: - { - GetChaserProfileStunSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_Attack: - { - ArrayList attackSounds = GetChaserProfileAttackSounds(profile); - if (attackSounds != null && attackSounds.Length > 0) - { - int length = attackSounds.Length; - attackSounds.GetArray(length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundList = soundInfo.Paths; - if (soundList != null && soundList.Length > 0) - { - SlenderPerformVoiceCooldown(bossIndex, slender, soundInfo, soundList); - } - } - return; - } - case SF2BossSound_AttackKilled: - { - GetChaserProfileAttackKilledSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_RageAll: - { - GetChaserProfileRageAllSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_RageTwo: - { - GetChaserProfileRageTwoSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_RageThree: - { - GetChaserProfileRageThreeSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - case SF2BossSound_SelfHeal: - { - GetChaserProfileSelfHealSounds(profile, soundInfo); - soundList = soundInfo.Paths; - } - } - if (soundList != null && soundList.Length > 0) - { - SlenderPerformVoiceCooldown(bossIndex, slender, soundInfo, soundList); - } -} - -void SlenderPerformVoiceCooldown(int bossIndex, int slender, SF2BossProfileSoundInfo soundInfo, ArrayList soundList) -{ - char buffer[512]; - soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); - if (buffer[0] != '\0') - { - float threshold = GetRandomFloat(0.0, 1.0); - float cooldown = GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); - if (threshold > soundInfo.Chance) - { - g_SlenderNextVoiceSound[bossIndex] = GetGameTime() + cooldown; - return; - } - soundInfo.EmitSound(_, slender, _, _, SF_SpecialRound(SPECIALROUND_TINYBOSSES) ? 25 : 0); - g_SlenderNextVoiceSound[bossIndex] = GetGameTime() + cooldown; - } -} - -void SlenderCastFootstep(int bossIndex) -{ - if (bossIndex == -1) - { - return; - } - - if (g_SlenderFootstepTime[bossIndex] <= 0.0) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileFootstepSounds(profile, soundInfo); - if (soundInfo.Paths == null || soundInfo.Paths.Length <= 0) - { - return; - } - - float myPos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - g_SlenderNextFootstepSound[bossIndex] = GetGameTime() + g_SlenderFootstepTime[bossIndex]; - soundInfo.EmitSound(_, slender); - if (NPCChaserGetEarthquakeFootstepsState(bossIndex)) - { - UTIL_ScreenShake(myPos, NPCChaserGetEarthquakeFootstepsAmplitude(bossIndex), - NPCChaserGetEarthquakeFootstepsFrequency(bossIndex), NPCChaserGetEarthquakeFootstepsDuration(bossIndex), - NPCChaserGetEarthquakeFootstepsRadius(bossIndex), 0, NPCChaserGetEarthquakeFootstepsAirShakeState(bossIndex)); - } -} - -void SlenderCastFootstepAnimEvent(int bossIndex, int event, int slender) -{ - if (bossIndex == -1) - { - return; - } - - if (!IsValidEntity(slender)) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - ArrayList arraySounds = GetBossProfileFootstepEventSounds(profile); - ArrayList arrayIndexes = GetBossProfileFootstepEventIndexes(profile); - - if (arraySounds == null || arrayIndexes == null) - { - return; - } - - int foundIndex = arrayIndexes.FindValue(event); - if (foundIndex == -1) - { - return; - } - - SF2BossProfileSoundInfo soundInfo; - arraySounds.GetArray(foundIndex, soundInfo, sizeof(soundInfo)); - - ArrayList soundPaths = soundInfo.Paths; - if (soundPaths == null) - { - return; - } - - float myPos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - soundInfo.EmitSound(_, slender); - if (NPCChaserGetEarthquakeFootstepsState(bossIndex)) - { - UTIL_ScreenShake(myPos, NPCChaserGetEarthquakeFootstepsAmplitude(bossIndex), - NPCChaserGetEarthquakeFootstepsFrequency(bossIndex), NPCChaserGetEarthquakeFootstepsDuration(bossIndex), - NPCChaserGetEarthquakeFootstepsRadius(bossIndex), 0, NPCChaserGetEarthquakeFootstepsAirShakeState(bossIndex)); - } -} - -void SlenderCastAnimEvent(int bossIndex, int event, int slender) -{ - if (bossIndex == -1) - { - return; - } - - if (!IsValidEntity(slender)) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - ArrayList arraySounds = GetBossProfileEventSounds(profile); - ArrayList arrayIndexes = GetBossProfileEventIndexes(profile); - - if (!arraySounds || arrayIndexes == null) - { - return; - } - - int foundIndex = arrayIndexes.FindValue(event); - if (foundIndex == -1) - { - return; - } - - SF2BossProfileSoundInfo soundInfo; - arraySounds.GetArray(foundIndex, soundInfo, sizeof(soundInfo)); - - ArrayList soundPaths = soundInfo.Paths; - if (soundPaths == null) - { - return; - } - soundInfo.EmitSound(_, slender); -} - -void SlenderCreateParticle(int bossIndex, const char[] sectionName, float particleZPos) -{ - if (bossIndex == -1) - { - return; - } - - if (g_RestartSessionEnabled) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - float slenderPosition[3], slenderAngles[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", slenderAngles); - slenderPosition[2] += particleZPos; - - if (!DispatchParticleEffect(slender, sectionName, slenderPosition, slenderAngles, slenderPosition)) - { - int particleEnt = CreateEntityByName("info_particle_system"); - - if (IsValidEntity(particleEnt)) - { - TeleportEntity(particleEnt, slenderPosition, slenderAngles, NULL_VECTOR); - - DispatchKeyValue(particleEnt, "targetname", "tf2particle"); - DispatchKeyValue(particleEnt, "effect_name", sectionName); - DispatchSpawn(particleEnt); - ActivateEntity(particleEnt); - AcceptEntityInput(particleEnt, "start"); - CreateTimer(0.1, Timer_KillEntity, particleEnt, TIMER_FLAG_NO_MAPCHANGE); + ReplaceString(buffer, sizeof(buffer), "[ROUNDTIME]", time); + } + int chatLength = strlen(prefix) + strlen(buffer); + if (chatLength > 255) + { + LogSF2Message("WARNING! Death message %i has greater than 255 characters on boss index %i, shorten the length of this message.", randomMessage + 1, bossIndex); + } + else + { + CPrintToChatAll("{royalblue}%s{default} %s", prefix, buffer); + } + } } } } -void SlenderCreateParticleAttach(int bossIndex, const char[] sectionName, float particleZPos, SF2_BasePlayer client) +void SlenderCastFootstepAnimEvent(int bossIndex, int event, int slender) { if (bossIndex == -1) { return; } - if (g_RestartSessionEnabled) + if (!IsValidEntity(slender)) { return; } - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + ArrayList arraySounds = GetBossProfileFootstepEventSounds(profile); + ArrayList arrayIndexes = GetBossProfileFootstepEventIndexes(profile); + + if (arraySounds == null || arrayIndexes == null) { return; } - float playerPosition[3], playerAngles[3]; - if (client.IsValid) - { - client.GetAbsOrigin(playerPosition); - client.GetEyeAngles(playerAngles); - } - else + int foundIndex = arrayIndexes.FindValue(event); + if (foundIndex == -1) { - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", playerPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", playerAngles); + return; } - playerPosition[2] += particleZPos; - if (!DispatchParticleEffect(slender, sectionName, playerPosition, playerAngles, playerPosition)) + SF2BossProfileSoundInfo soundInfo; + arraySounds.GetArray(foundIndex, soundInfo, sizeof(soundInfo)); + + ArrayList soundPaths = soundInfo.Paths; + if (soundPaths == null) { - int particleEnt = CreateEntityByName("info_particle_system"); + return; + } - if (IsValidEntity(particleEnt)) - { - TeleportEntity(particleEnt, playerPosition, playerAngles, NULL_VECTOR); + float myPos[3]; + GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - DispatchKeyValue(particleEnt, "targetname", "tf2particle"); - DispatchKeyValue(particleEnt, "effect_name", sectionName); - DispatchSpawn(particleEnt); - ActivateEntity(particleEnt); - AcceptEntityInput(particleEnt, "start"); - CreateTimer(0.1, Timer_KillEntity, particleEnt, TIMER_FLAG_NO_MAPCHANGE); - } + soundInfo.EmitSound(_, slender); + SF2ChaserBossProfileData data; + data = SF2NPC_Chaser(bossIndex).GetProfileData(); + if (data.EarthquakeFootsteps) + { + UTIL_ScreenShake(myPos, data.EarthquakeFootstepAmplitude, + data.EarthquakeFootstepFrequency, data.EarthquakeFootstepDuration, + data.EarthquakeFootstepRadius, 0, data.EarthquakeFootstepAirShake); } } -void SlenderCreateParticleBeamClient(int bossIndex, const char[] sectionName, float particleZPos, SF2_BasePlayer client) +void SlenderCastAnimEvent(int bossIndex, int event, int slender) { if (bossIndex == -1) { return; } - if (g_RestartSessionEnabled) + if (!IsValidEntity(slender)) { return; } - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(bossIndex, profile, sizeof(profile)); + + ArrayList arraySounds = GetBossProfileEventSounds(profile); + ArrayList arrayIndexes = GetBossProfileEventIndexes(profile); + + if (!arraySounds || arrayIndexes == null) { return; } - float slenderPosition[3], slenderAngles[3], playerPosition[3]; - if (client.IsValid) + int foundIndex = arrayIndexes.FindValue(event); + if (foundIndex == -1) { - client.GetAbsOrigin(playerPosition); + return; } - else + + SF2BossProfileSoundInfo soundInfo; + arraySounds.GetArray(foundIndex, soundInfo, sizeof(soundInfo)); + + ArrayList soundPaths = soundInfo.Paths; + if (soundPaths == null) { - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", playerPosition); + return; } - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", slenderAngles); - slenderPosition[2] += particleZPos; - playerPosition[2] += particleZPos; - - DispatchParticleEffectBeam(slender, sectionName, slenderPosition, slenderAngles, playerPosition); + soundInfo.EmitSound(_, slender); } void SlenderCreateParticleSpawnEffect(int bossIndex, bool despawn = false) @@ -4145,7 +3474,6 @@ void SlenderCreateParticleSpawnEffect(int bossIndex, bool despawn = false) // in a rest period. // Teleport progressively closer as time passes in attempt to increase the target's stress level. -// Maximum minimum range is capped by the boss's anger level. static Action Timer_SlenderTeleportThink(Handle timer, any id) { @@ -4178,26 +3506,36 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) int difficulty = GetLocalGlobalDifficulty(bossIndex); - if (!NPCIsTeleportAllowed(bossIndex, difficulty)) + // Check to see if anyone's looking at me before doing anything. + if (PeopleCanSeeSlender(bossIndex, _, false)) { return Plugin_Continue; } + int bossEnt = controller.EntIndex; + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) + { + SF2_BaseBoss boss = SF2_BaseBoss(bossEnt); + if (boss.IsKillingSomeone) + { + return Plugin_Continue; + } + } - // Check to see if anyone's looking at me before doing anything. - if (PeopleCanSeeSlender(bossIndex, _, false)) + if (!NPCIsTeleportAllowed(bossIndex, difficulty) && (!bossEnt || bossEnt == INVALID_ENT_REFERENCE)) { return Plugin_Continue; } - int bossEnt = controller.EntIndex; + if (controller.TeleportType == 2) { if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) { if (NPCGetType(bossIndex) == SF2BossType_Chaser) { + SF2_ChaserEntity chaser = SF2_ChaserEntity(bossEnt); // Check to see if it's a good time to teleport away. - int state = g_SlenderState[bossIndex]; - if (state == STATE_IDLE || state == STATE_WANDER) + int state = chaser.State; + if (state == STATE_IDLE && chaser.IsAllowedToDespawn) { if (GetGameTime() < g_SlenderTimeUntilKill[bossIndex]) { @@ -4230,6 +3568,11 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) { if (GetGameTime() >= g_SlenderNextTeleportTime[bossIndex]) { + if (!NPCIsTeleportAllowed(bossIndex, difficulty) && bossEnt && bossEnt != INVALID_ENT_REFERENCE) + { + controller.UnSpawn(); + return Plugin_Continue; + } float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); g_SlenderNextTeleportTime[bossIndex] = GetGameTime() + teleportTime; bool ignoreFuncNavPrefer = g_NpcHasIgnoreNavPrefer[bossIndex]; @@ -4241,7 +3584,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) // We don't have any good targets. Remove myself for now. if (SlenderCanRemove(bossIndex)) { - RemoveSlender(bossIndex); + controller.UnSpawn(); } #if defined DEBUG @@ -4282,6 +3625,12 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) { continue; } + + if (view_as(collector.Get(i)).HasAttributeTF(NO_SPAWNING)) + { + continue; + } + if (collector.Get(i).GetCostSoFar() >= teleportMinRange) { areaArray.Set(validAreaCount, i); @@ -4301,15 +3650,51 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) randomArea = areaArray.Get(randomCell); area = collector.Get(randomArea); area.GetCenter(spawnPos); + /*float cornerPosition[3]; // Will revisit later + ArrayList corners = new ArrayList(); + for (int i = 0; i < 4; i++) + { + corners.Push(view_as(i)); + } + NavCornerType cornerA = view_as(GetRandomInt(0, 3)); + NavCornerType invert = cornerA; + switch (cornerA) + { + case NORTH_WEST: + { + invert = SOUTH_EAST; + } + case NORTH_EAST: + { + invert = SOUTH_WEST; + } + case SOUTH_EAST: + { + invert = NORTH_WEST; + } + case SOUTH_WEST: + { + invert = NORTH_EAST; + } + } + corners.Erase(corners.FindValue(cornerA)); + corners.Erase(corners.FindValue(invert)); + NavCornerType cornerB = corners.Get(GetRandomInt(0, corners.Length - 1)); + area.GetCorner(cornerA, spawnPos); + area.GetCorner(cornerB, cornerPosition); + LerpVectors(spawnPos, cornerPosition, spawnPos, GetURandomFloat()); + area.GetCorner(invert, cornerPosition); + LerpVectors(spawnPos, cornerPosition, spawnPos, GetURandomFloat()); + delete corners;*/ float traceMins[3]; - traceMins[0] = g_SlenderDetectMins[bossIndex][0] - 5.0; - traceMins[1] = g_SlenderDetectMins[bossIndex][1] - 5.0; + traceMins[0] = g_SlenderDetectMins[bossIndex][0]; + traceMins[1] = g_SlenderDetectMins[bossIndex][1]; traceMins[2] = 0.0; float traceMaxs[3]; - traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0] + 5.0; - traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1] + 5.0; + traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0]; + traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1]; traceMaxs[2] = g_SlenderDetectMaxs[bossIndex][2]; TR_TraceHullFilter(spawnPos, spawnPos, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitEntity); @@ -4353,7 +3738,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) } // Check visibility. - if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false)) + if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false, _, true)) { area = NULL_AREA; validAreaCount--; @@ -4367,7 +3752,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) AddVectors(spawnPos, g_SlenderEyePosOffset[bossIndex], spawnPos); - if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false)) + if (!g_SlenderTeleportIgnoreVis[bossIndex] && IsPointVisibleToAPlayer(spawnPos, !shouldBeBehindObstruction, false, _, true)) { area = NULL_AREA; validAreaCount--; @@ -4382,9 +3767,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) SubtractVectors(spawnPos, g_SlenderEyePosOffset[bossIndex], spawnPos); // Look for copies - if (NPCGetFlags(bossIndex) & SFF_COPIES && canSpawn) + if (controller.GetProfileData().CopiesInfo.Enabled[difficulty] && canSpawn && (controller.Flags & SFF_NOCOPIES) == 0) { - float minDistBetweenBosses = GetBossProfileTeleportCopyDistance(profile); + float minDistBetweenBosses = GetBossProfileTeleportCopyDistance(profile, difficulty); for (int bossCheck = 0; bossCheck < MAX_BOSSES; bossCheck++) { @@ -4402,7 +3787,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) } float tempPos[3]; - SlenderGetAbsOrigin(bossCheck, tempPos); + CBaseEntity(bossEntCheck).GetAbsOrigin(tempPos); if (GetVectorSquareMagnitude(spawnPos, tempPos) <= SquareFloat(minDistBetweenBosses)) { @@ -4421,7 +3806,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) if (canSpawn) { - SpawnSlender(controller, spawnPos); + controller.Spawn(spawnPos); if (g_PlayerIsExitCamping[teleportTarget]) { @@ -4468,7 +3853,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) if (validAreaCount <= 0) { - RemoveSlender(bossIndex); + controller.UnSpawn(); } delete collector; @@ -4490,7 +3875,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) bool SlenderMarkAsFake(int bossIndex) { int bossFlags = NPCGetFlags(bossIndex); - if (bossFlags & SFF_MARKEDASFAKE) + if ((bossFlags & SFF_MARKEDASFAKE) != 0) { return false; } @@ -4501,8 +3886,8 @@ bool SlenderMarkAsFake(int bossIndex) NPCSetFlags(bossIndex, bossFlags | SFF_MARKEDASFAKE); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); g_SlenderFakeTimer[bossIndex] = CreateTimer(3.0, Timer_SlenderMarkedAsFake, bossIndex, TIMER_FLAG_NO_MAPCHANGE); @@ -4521,13 +3906,13 @@ bool SlenderMarkAsFake(int bossIndex) } SetEntProp(slender, Prop_Send, "m_usSolidFlags", flags); - if (g_SlenderEngineSound[bossIndex][0] != '\0') + if (data.EngineSound[0] != '\0') { - StopSound(slender, SNDCHAN_STATIC, g_SlenderEngineSound[bossIndex]); + StopSound(slender, SNDCHAN_STATIC, data.EngineSound); } SetEntPropFloat(slender, Prop_Send, "m_flPlaybackRate", 0.0); - SetEntityRenderFx(slender, RENDERFX_FADE_FAST); + SetEntityRenderFx(slender, RENDERFX_FADE_SLOW); } return true; @@ -4556,7 +3941,7 @@ int SpawnSlenderModel(int bossIndex, const float pos[3], bool deathCam = false) char buffer[PLATFORM_MAX_PATH], profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - float playbackRate, tempFootsteps, cycle; + float playbackRate, cycle; int difficulty = GetLocalGlobalDifficulty(bossIndex); GetSlenderModel(bossIndex, _, buffer, sizeof(buffer)); @@ -4588,18 +3973,16 @@ int SpawnSlenderModel(int bossIndex, const float pos[3], bool deathCam = false) slenderModel.Activate(); SF2BossProfileMasterAnimationsData animData; GetBossProfileAnimationsData(profile, animData); - float tempDuration; - int tempIndex; if (!deathCam) { - animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, tempDuration, cycle, g_SlenderFootstepTime[bossIndex], tempIndex); + animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, _, cycle); } else { - bool animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, buffer, sizeof(buffer), playbackRate, tempDuration, cycle, tempFootsteps, tempIndex); + bool animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, buffer, sizeof(buffer), playbackRate, _, cycle); if (!animationFound || strcmp(buffer,"") <= 0) { - animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, tempDuration, cycle, g_SlenderFootstepTime[bossIndex], tempIndex); + animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, buffer, sizeof(buffer), playbackRate, _, cycle); } } if (buffer[0] != '\0') @@ -4671,12 +4054,12 @@ int SpawnSlenderModel(int bossIndex, const float pos[3], bool deathCam = false) return slenderModel.index; } -bool PlayerCanSeeSlender(int client,int bossIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) +bool PlayerCanSeeSlender(int client, int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { return IsNPCVisibleToPlayer(bossIndex, client, checkFOV, checkBlink, checkEliminated); } -bool PeopleCanSeeSlender(int bossIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated = true) +bool PeopleCanSeeSlender(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { return IsNPCVisibleToAPlayer(bossIndex, checkFOV, checkBlink, checkEliminated); } @@ -4733,7 +4116,7 @@ void SlenderRemoveGlow(int bossIndex) } } -bool IsNPCVisibleToPlayer(int npcIndex,int client, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) +bool IsNPCVisibleToPlayer(int npcIndex, int client, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { if (!NPCIsValid(npcIndex)) { @@ -4751,7 +4134,7 @@ bool IsNPCVisibleToPlayer(int npcIndex,int client, bool checkFOV=true, bool chec return false; } -bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV=true, bool checkBlink=false, bool checkEliminated=true) +bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { for (int client = 1; client <= MaxClients; client++) { @@ -4764,58 +4147,13 @@ bool IsNPCVisibleToAPlayer(int npcIndex, bool checkFOV=true, bool checkBlink=fal return false; } -bool CanNPCSeePlayerNonTransparent(int npcIndex, SF2_BasePlayer client) -{ - if (!NPCIsValid(npcIndex)) - { - return false; - } - - if (!client.IsValid) - { - return false; - } - - bool attackEliminated = !!(NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS); - - if (!IsTargetValidForSlender(client, attackEliminated)) - { - return false; - } - - int npcEnt = NPCGetEntIndex(npcIndex); - if (npcEnt && npcEnt != INVALID_ENT_REFERENCE) - { - float eyePos[3], clientPos[3]; - NPCGetEyePosition(npcIndex, eyePos); - client.GetEyePosition(clientPos); - Handle trace = TR_TraceRayFilterEx(eyePos, - clientPos, - MASK_NPCSOLID, - RayType_EndPoint, - TraceRayBossVisibility, - npcEnt); - - bool isVisible = !TR_DidHit(trace); - if (!isVisible && TR_GetEntityIndex(trace) == client.index) - { - isVisible = true; - } - delete trace; - - - return isVisible; - } - return false; -} - float NPCGetDistanceFromPoint(int npcIndex, const float point[3]) { int npcEnt = NPCGetEntIndex(npcIndex); if (npcEnt && npcEnt != INVALID_ENT_REFERENCE) { float pos[3]; - SlenderGetAbsOrigin(npcIndex, pos); + CBaseEntity(npcEnt).GetAbsOrigin(pos); return GetVectorSquareMagnitude(pos, point); } @@ -4927,7 +4265,7 @@ bool TraceRayDontHitCharactersOrEntity(int entity,int mask, any data) return true; } -bool TraceRayDontHitAnything(int entity,int mask, any data) +bool TraceRayDontHitAnything(int entity, int mask, any data) { return false; } @@ -5115,7 +4453,7 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint } if (IsSpaceOccupiedPlayer(traceHitPos, HULL_TF2PLAYER_MINS, HULL_TF2PLAYER_MAXS, client)) { - traceHitPos[2] +=5.0; + traceHitPos[2] += 5.0; if (IsSpaceOccupiedPlayer(traceHitPos, HULL_TF2PLAYER_MINS, HULL_TF2PLAYER_MAXS, client)) { #if defined DEBUG @@ -5181,7 +4519,7 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint } // Check travel distance and put in the appropriate arrays. - float dist = view_as(areaArray.Get(i2, 1)); + float dist = areaArray.Get(i2, 1); if (dist > rangeSectionMin && dist < rangeSectionMax) { int index = -1; @@ -5224,25 +4562,25 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint { arrayIndex = GetRandomInt(0, areaArrayClose.Length - 1); teleportAreaIndex = areaArrayClose.Get(arrayIndex); - teleportPos[0] = view_as(areaArrayClose.Get(arrayIndex, 1)); - teleportPos[1] = view_as(areaArrayClose.Get(arrayIndex, 2)); - teleportPos[2] = view_as(areaArrayClose.Get(arrayIndex, 3)); + teleportPos[0] = areaArrayClose.Get(arrayIndex, 1); + teleportPos[1] = areaArrayClose.Get(arrayIndex, 2); + teleportPos[2] = areaArrayClose.Get(arrayIndex, 3); } else if (areaArrayAverage.Length) { arrayIndex = GetRandomInt(0, areaArrayAverage.Length - 1); teleportAreaIndex = areaArrayAverage.Get(arrayIndex); - teleportPos[0] = view_as(areaArrayAverage.Get(arrayIndex, 1)); - teleportPos[1] = view_as(areaArrayAverage.Get(arrayIndex, 2)); - teleportPos[2] = view_as(areaArrayAverage.Get(arrayIndex, 3)); + teleportPos[0] = areaArrayAverage.Get(arrayIndex, 1); + teleportPos[1] = areaArrayAverage.Get(arrayIndex, 2); + teleportPos[2] = areaArrayAverage.Get(arrayIndex, 3); } else if (areaArrayFar.Length) { arrayIndex = GetRandomInt(0, areaArrayFar.Length - 1); teleportAreaIndex = areaArrayFar.Get(arrayIndex); - teleportPos[0] = view_as(areaArrayFar.Get(arrayIndex, 1)); - teleportPos[1] = view_as(areaArrayFar.Get(arrayIndex, 2)); - teleportPos[2] = view_as(areaArrayFar.Get(arrayIndex, 3)); + teleportPos[0] = areaArrayFar.Get(arrayIndex, 1); + teleportPos[1] = areaArrayFar.Get(arrayIndex, 2); + teleportPos[2] = areaArrayFar.Get(arrayIndex, 3); } delete areaArrayClose; delete areaArrayAverage; @@ -5261,5 +4599,229 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint } #include "sf2/npc/npc_chaser.sp" -#include "sf2/npc/npc_chaser_takedamage.sp" -#include "sf2/npc/entities/initialize.sp" \ No newline at end of file +//#include "sf2/npc/npc_chaser_takedamage.sp" +#include "sf2/npc/entities/initialize.sp" + +static any Native_GetMaxBosses(Handle plugin, int numParams) +{ + return MAX_BOSSES; +} + +static any Native_EntIndexToBossIndex(Handle plugin, int numParams) +{ + return NPCGetFromEntIndex(GetNativeCell(1)); +} + +static any Native_BossIndexToEntIndex(Handle plugin, int numParams) +{ + return EntRefToEntIndex(g_SlenderEnt[GetNativeCell(1)]); +} + +static any Native_BossIndexToEntIndexEx(Handle plugin, int numParams) +{ + return NPCGetEntIndex(GetNativeCell(1)); +} + +static any Native_BossIDToBossIndex(Handle plugin, int numParams) +{ + return NPCGetFromUniqueID(GetNativeCell(1)); +} + +static any Native_BossIndexToBossID(Handle plugin, int numParams) +{ + return NPCGetUniqueID(GetNativeCell(1)); +} + +static any Native_AddBoss(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetNativeString(1, profile, sizeof(profile)); + + int flags = GetNativeCell(2); + bool spawnCompanions = GetNativeCell(3); + bool playSpawnSound = GetNativeCell(4); + + return AddProfile(profile, flags, _, spawnCompanions, playSpawnSound); +} + +static any Native_RemoveBoss(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + RemoveProfile(boss.Index); + return 0; +} + +static any Native_GetBossName(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(GetNativeCell(1), profile, sizeof(profile)); + + SetNativeString(2, profile, GetNativeCell(3)); + return 0; +} + +static any Native_GetBossType(Handle plugin, int numParams) +{ + return NPCGetType(GetNativeCell(1)); +} + +static any Native_GetBossFlags(Handle plugin, int numParams) +{ + return NPCGetFlags(GetNativeCell(1)); +} + +static any Native_SetBossFlags(Handle plugin, int numParams) +{ + NPCSetFlags(GetNativeCell(1), GetNativeCell(2)); + return 0; +} + +static any Native_SpawnBoss(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + float position[3]; + GetNativeArray(2, position, 3); + + SpawnSlender(boss, position); + return 0; +} + +static any Native_IsBossSpawning(Handle plugin, int numParams) +{ + return false; +} + +static any Native_DespawnBoss(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + boss.UnSpawn(); + return 0; +} + +static any Native_GetBossPathFollower(Handle plugin, int numParams) +{ + return g_BossPathFollower[GetNativeCell(1)]; +} + +static any Native_GetBossMaster(Handle plugin, int numParams) +{ + return g_SlenderCopyMaster[GetNativeCell(1)]; +} + +static any Native_GetBossIdleLifetime(Handle plugin, int numParams) +{ + return NPCGetIdleLifetime(GetNativeCell(1), GetNativeCell(2)); +} + +static any Native_GetBossState(Handle plugin, int numParams) +{ + SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + return boss.State; +} + +static any Native_SetBossState(Handle plugin, int numParams) +{ + SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + boss.State = GetNativeCell(2); + return 0; +} + +static any Native_GetBossEyePosition(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid() || !IsValidEntity(boss.EntIndex)) + { + return 0; + } + + float eyePos[3]; + boss.GetEyePosition(eyePos); + + SetNativeArray(2, eyePos, 3); + return 0; +} + +static any Native_GetBossEyePositionOffset(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC boss = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!boss.IsValid()) + { + return 0; + } + + float eyePos[3]; + boss.GetEyePositionOffset(eyePos); + + SetNativeArray(2, eyePos, 3); + return 0; +} + +static any Native_GetBossTeleportThinkTimer(Handle plugin, int numParams) +{ + return g_SlenderThink[GetNativeCell(1)]; +} + +static any Native_SetBossTeleportThinkTimer(Handle plugin, int numParams) +{ + int bossIndex = GetNativeCell(1); + g_SlenderThink[bossIndex] = GetNativeCell(2); + return 0; +} + +static any Native_GetBossTeleportTarget(Handle plugin, int numParams) +{ + return EntRefToEntIndex(g_SlenderTeleportTarget[GetNativeCell(1)]); +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2BossProfileData data; + data = controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_GetProfileDataEx(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetNativeString(1, profile, sizeof(profile)); + SF2BossProfileData data; + if (!g_BossProfileData.GetArray(profile, data, sizeof(data))) + { + return false; + } + + SetNativeArray(2, data, sizeof(data)); + return true; +} + +static any Native_SpawnBossEffects(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(3, pos, 3); + GetNativeArray(4, ang, 3); + ArrayList output = GetNativeCellRef(5); + SlenderSpawnEffects(GetNativeCell(1), GetNativeCell(2), false, pos, ang, output, GetNativeCell(6)); + return 0; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/deathcam.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/deathcam.sp new file mode 100644 index 00000000..8cbc11ee --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/deathcam.sp @@ -0,0 +1,71 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_DeathCamAction < NextBotAction +{ + public SF2_DeathCamAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("SF2_Deathcam"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnOtherKilled, OnOtherKilled); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_DeathCamAction action, SF2_BaseBoss actor) +{ + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + npc.flWalkSpeed = 0.0; + npc.flRunSpeed = 0.0; + actor.IsKillingSomeone = true; + float duration = 0.0, rate = 1.0, cycle = 0.0; + SF2NPC_BaseNPC controller = actor.Controller; + SF2BossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileSoundInfo info; + info = data.LocalDeathCamSounds; + info.EmitSound(_, actor.index); + + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + + return NULL_ACTION; +} + +static int Update(SF2_DeathCamAction action, SF2_BaseBoss actor, NextBotAction priorAction) +{ + if (!actor.KillTarget.IsValid()) + { + return action.Done(); + } + + SF2_BasePlayer player = SF2_BasePlayer(actor.KillTarget.index); + if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) + { + return action.Done(); + } + + return action.Continue(); +} + +static void OnEnd(SF2_DeathCamAction action, SF2_BaseBoss actor) +{ + actor.IsKillingSomeone = false; +} + +static void OnOtherKilled(SF2_DeathCamAction action, SF2_BaseBoss actor, CBaseCombatCharacter victim, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damagetype) +{ + if (actor.KillTarget.index == victim.index) + { + actor.KillTarget = CBaseEntity(-1); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp new file mode 100644 index 00000000..5b537301 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp @@ -0,0 +1,151 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_PlaySequenceAndWait < NextBotAction +{ + public SF2_PlaySequenceAndWait(int sequence, float duration = 0.0, float rate = 1.0, float cycle = 0.0) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("SF2_PlaySequenceAndWait"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_Sequence") + .DefineFloatField("m_Duration") + .DefineFloatField("m_Rate") + .DefineFloatField("m_Cycle") + .DefineFloatField("m_EndTime") + .EndDataMapDesc(); + } + + SF2_PlaySequenceAndWait action = view_as(g_Factory.Create()); + + action.Sequence = sequence; + action.Duration = duration; + action.Rate = rate; + action.Cycle = cycle; + + return action; + } + + public static void SetupAPI() + { + CreateNative("SF2_PlaySequenceAndWaitAction.SF2_PlaySequenceAndWaitAction", Native_Create); + } + + property int Sequence + { + public get() + { + return this.GetData("m_Sequence"); + } + + public set(int value) + { + this.SetData("m_Sequence", value); + } + } + + property float Duration + { + public get() + { + return this.GetDataFloat("m_Duration"); + } + + public set(float value) + { + this.SetDataFloat("m_Duration", value); + } + } + + property float Rate + { + public get() + { + return this.GetDataFloat("m_Rate"); + } + + public set(float value) + { + this.SetDataFloat("m_Rate", value); + } + } + + property float Cycle + { + public get() + { + return this.GetDataFloat("m_Cycle"); + } + + public set(float value) + { + this.SetDataFloat("m_Cycle", value); + } + } + + property float EndTime + { + public get() + { + return this.GetDataFloat("m_EndTime"); + } + + public set(float value) + { + this.SetDataFloat("m_EndTime", value); + } + } +} + +static int OnStart(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAction priorAction) +{ + if (action.Sequence == -1) + { + return action.Done("Invalid sequence"); + } + + actor.ResetSequence(action.Sequence); + actor.SetPropFloat(Prop_Send, "m_flCycle", action.Cycle); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", action.Rate); + + float duration = action.Duration; + if (duration <= 0.0) + { + duration = actor.SequenceDuration(action.Sequence) / action.Rate; + duration *= (1.0 - action.Cycle); + } + + action.EndTime = GetGameTime() + duration; + + return action.Continue(); +} + +static int Update(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, float interval) +{ + if (GetGameTime() > action.EndTime) + { + return action.Done(); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAction interruptingAction) +{ + return action.Done(); +} + +static any Native_Create(Handle plugin, int numParams) +{ + int sequence = GetNativeCell(1); + float duration = GetNativeCell(2); + float rate = GetNativeCell(3); + float cycle = GetNativeCell(4); + + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp new file mode 100644 index 00000000..072bd92b --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -0,0 +1,880 @@ +#pragma semicolon 1 + +#include "actions/playsequenceandwait.sp" +#include "actions/deathcam.sp" + +static CEntityFactory g_Factory; + +enum SF2NPCMoveParameterType +{ + SF2NPCMoveParameter_None = 0, + SF2NPCMoveParameter_XY, + SF2NPCMoveParameter_Yaw +} + +methodmap SF2_BaseBoss < CBaseCombatCharacter +{ + public SF2_BaseBoss(int ent) + { + return view_as(ent); + } + + public bool IsValid() + { + if (!CBaseCombatCharacter(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_npc_boss_base", OnCreate); + g_Factory.IsAbstract = true; + g_Factory.DeriveFromNPC(); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_Controller") + .DefineIntField("m_State") + .DefineIntField("m_PreviousState") + .DefineEntityField("m_Target") + .DefineEntityField("m_OldTarget") + .DefineBoolField("m_IsJumping") + .DefineBoolField("m_IsPlayerVisible", MAXTF2PLAYERS) + .DefineBoolField("m_IsPlayerInFOV", MAXTF2PLAYERS) + .DefineBoolField("m_IsPlayerNear", MAXTF2PLAYERS) + .DefineFloatField("m_CurrentChaseDuration") + .DefineFloatField("m_LegacyFootstepInterval") + .DefineFloatField("m_LegacyFootstepTime") + .DefineIntField("m_MovementType") + .DefineIntField("m_MoveParameterType") + .DefineIntField("m_MoveXPoseParameter") + .DefineIntField("m_MoveYPoseParameter") + .DefineIntField("m_MoveScalePoseParameter") + .DefineIntField("m_MoveYawPoseParameter") + .DefineFloatField("m_AnimationPlaybackRate") + .DefineFloatField("m_EstimatedYaw") + .DefineFloatField("m_LastKillTime") + .DefineBoolField("m_DebugShouldGoToPos") + .DefineVectorField("m_ForceWanderPos") + .DefineBoolField("m_IsKillingSomeone") + .DefineEntityField("m_KillTarget") + .DefineBoolField("m_IsAttemptingToMove") + .DefineIntField("m_EyeBoneIndex") + .EndDataMapDesc(); + g_Factory.Install(); + + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + } + + public static CEntityFactory GetFactory() + { + return g_Factory; + } + + property SF2NPC_BaseNPC Controller + { + public get() + { + return SF2NPC_BaseNPC.FromUniqueId(this.GetProp(Prop_Data, "m_Controller")); + } + + public set(SF2NPC_BaseNPC controller) + { + this.SetProp(Prop_Data, "m_Controller", controller.Index == -1 ? -1 : controller.UniqueID); + } + } + + property int State + { + public get() + { + return this.GetProp(Prop_Data, "m_State"); + } + + public set(int value) + { + if (this.Controller.IsValid()) + { + Call_StartForward(g_OnBossChangeStateFwd); + Call_PushCell(this.Controller.Index); + Call_PushCell(this.GetProp(Prop_Data, "m_State")); + Call_PushCell(value); + Call_Finish(); + } + + this.SetProp(Prop_Data, "m_State", value); + } + } + + property int PreviousState + { + public get() + { + return this.GetProp(Prop_Data, "m_PreviousState"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_PreviousState", value); + } + } + + public SF2BossProfileData GetProfileData() + { + return this.Controller.GetProfileData(); + } + + property CBaseEntity Target + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_Target")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_Target", entity.index); + } + } + + property CBaseEntity OldTarget + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_OldTarget")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_OldTarget", entity.index); + } + } + + property bool IsJumping + { + public get() + { + return this.GetProp(Prop_Data, "m_IsJumping") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsJumping", value); + } + } + + public bool GetIsVisible(SF2_BasePlayer player) + { + return this.GetProp(Prop_Data, "m_IsPlayerVisible", player.index) != 0; + } + + public void SetIsVisible(SF2_BasePlayer player, bool value) + { + this.SetProp(Prop_Data, "m_IsPlayerVisible", value, player.index); + } + + public bool GetInFOV(SF2_BasePlayer player) + { + return this.GetProp(Prop_Data, "m_IsPlayerInFOV", player.index) != 0; + } + + public void SetInFOV(SF2_BasePlayer player, bool value) + { + this.SetProp(Prop_Data, "m_IsPlayerInFOV", value, player.index); + } + + public bool GetIsNear(SF2_BasePlayer player) + { + return this.GetProp(Prop_Data, "m_IsPlayerNear", player.index) != 0; + } + + public void SetIsNear(SF2_BasePlayer player, bool value) + { + this.SetProp(Prop_Data, "m_IsPlayerNear", value, player.index); + } + + property float CurrentChaseDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_CurrentChaseDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_CurrentChaseDuration", value); + } + } + + property float LegacyFootstepInterval + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LegacyFootstepInterval"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LegacyFootstepInterval", value); + } + } + + property float LegacyFootstepTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LegacyFootstepTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LegacyFootstepTime", value); + } + } + + property SF2NPCMoveTypes MovementType + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_MovementType")); + } + + public set(SF2NPCMoveTypes value) + { + this.SetProp(Prop_Data, "m_MovementType", value); + } + } + + property SF2NPCMoveParameterType MoveParameterType + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_MoveParameterType")); + } + + public set(SF2NPCMoveParameterType value) + { + this.SetProp(Prop_Data, "m_MoveParameterType", value); + } + } + + property int MoveXParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveXPoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveXPoseParameter", value); + } + } + + property int MoveYParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveYPoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveYPoseParameter", value); + } + } + + property int MoveScaleParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveScalePoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveScalePoseParameter", value); + } + } + + property int MoveYawParameter + { + public get() + { + return this.GetProp(Prop_Data, "m_MoveYawPoseParameter"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_MoveYawPoseParameter", value); + } + } + + property float AnimationPlaybackRate + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AnimationPlaybackRate"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AnimationPlaybackRate", value); + } + } + + property float EstimatedYaw + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_EstimatedYaw"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_EstimatedYaw", value); + } + } + + property float LastKillTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_LastKillTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_LastKillTime", value); + } + } + + property bool DebugShouldGoToPos + { + public get() + { + return this.GetProp(Prop_Data, "m_DebugShouldGoToPos") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_DebugShouldGoToPos", value); + } + } + + public void GetForceWanderPosition(float buffer[3]) + { + this.GetPropVector(Prop_Data, "m_ForceWanderPos", buffer); + } + + public void SetForceWanderPosition(float buffer[3]) + { + this.SetPropVector(Prop_Data, "m_ForceWanderPos", buffer); + } + + property bool IsKillingSomeone + { + public get() + { + return this.GetProp(Prop_Data, "m_IsKillingSomeone") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsKillingSomeone", value); + } + } + + property CBaseEntity KillTarget + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_KillTarget")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_KillTarget", entity.index); + } + } + + property bool IsAttemptingToMove + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAttemptingToMove") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsAttemptingToMove", value); + } + } + + property int EyeBoneIndex + { + public get() + { + return this.GetProp(Prop_Data, "m_EyeBoneIndex"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_EyeBoneIndex", value); + } + } + + public void EyePosition(float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) + { + this.Controller.GetEyePosition(buffer, defaultValue); + } + + public static void SetupAPI() + { + CreateNative("SF2_BaseBossEntity.IsValid.get", Native_GetIsValid); + CreateNative("SF2_BaseBossEntity.Controller.get", Native_GetController); + CreateNative("SF2_BaseBossEntity.Target.get", Native_GetTarget); + CreateNative("SF2_BaseBossEntity.State.get", Native_GetState); + CreateNative("SF2_BaseBossEntity.CurrentChaseDuration.get", Native_GetCurrentChaseDuration); + CreateNative("SF2_BaseBossEntity.EyePosition", Native_EyePosition); + CreateNative("SF2_BaseBossEntity.GetProfileName", Native_GetProfileName); + CreateNative("SF2_BaseBossEntity.GetName", Native_GetName); + CreateNative("SF2_BaseBossEntity.ProfileData", Native_GetProfileData); + CreateNative("SF2_BaseBossEntity.ResetProfileAnimation", Native_ResetProfileAnimation); + } + + public int SelectProfileAnimation(const char[] animType, float &rate = 1.0, float &duration = 0.0, float &cycle = 0.0, float &footstepInterval = 0.0, + int &index = 0, int preDefinedIndex = -1, const char[] preDefinedName = "", const char[] posture = NULL_STRING, bool &overrideLoop = false, bool &loop = false) + { + SF2NPC_BaseNPC controller = this.Controller; + int difficulty = controller.Difficulty; + + char animation[64]; + + bool found = false; + + if (controller.Type == SF2BossType_Chaser && !IsNullString(posture) && strcmp(posture, SF2_PROFILE_CHASER_DEFAULT_POSTURE) != 0) + { + SF2NPC_Chaser chaserController = view_as(controller); + SF2ChaserBossProfileData data; + data = chaserController.GetProfileData(); + SF2ChaserBossProfilePostureInfo postureInfo; + found = data.GetPosture(posture, postureInfo); + if (found) + { + found = postureInfo.Animations.GetAnimation(animType, difficulty, animation, sizeof(animation), + rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, overrideLoop, loop); + if (found) + { + this.AnimationPlaybackRate = rate; + + return LookupProfileAnimation(this.index, animation); + } + } + } + + found = controller.GetProfileData().AnimationData.GetAnimation(animType, difficulty, animation, sizeof(animation), + rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, overrideLoop, loop); + + if (!found) + { + return -1; + } + + int sequence = LookupProfileAnimation(this.index, animation); + + this.AnimationPlaybackRate = rate; + + return sequence; + } + + public int SelectProfileGesture(int definedIndex = -1, const char[] definedName = "", const char[] animType, float &rate = 1.0, float &cycle = 0.0) + { + SF2NPC_BaseNPC controller = view_as(this.Controller); + int difficulty = GetLocalGlobalDifficulty(controller.Index); + + char gesture[64]; + + bool found = controller.GetProfileData().AnimationData.GetGesture(definedIndex, definedName, animType, difficulty, gesture, sizeof(gesture), + rate, cycle); + + if (!found) + { + return -1; + } + + int sequence = LookupProfileAnimation(this.index, gesture); + + return sequence; + } + + public bool ResetProfileAnimation(const char[] animType, int preDefinedIndex = -1, const char[] preDefinedName = "", float &duration = 0.0, const char[] posture = NULL_STRING) + { + if (this.Controller.IsValid() && (this.Controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return false; + } + float rate = 1.0, cycle = 0.0, footstepInterval = 0.0; + bool overrideLoop, loop; + int index = 0; + + int sequence = this.SelectProfileAnimation(animType, rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, posture, overrideLoop, loop); + if (sequence == -1) + { + return false; + } + + if (rate < 0.0) + { + rate = 0.0; + } + if (rate > 12.0) + { + rate = 12.0; + } + + bool isMovement = strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Walk]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Run]) == 0; + + bool shouldLoop = isMovement || strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Idle]) == 0; + this.ResetSequence(sequence); + this.SetPropFloat(Prop_Send, "m_flCycle", cycle); + if (strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Attack]) == 0 && this.MovementType == SF2NPCMoveType_Attack) + { + shouldLoop = true; + } + if (overrideLoop) + { + this.SetProp(Prop_Data, "m_bSequenceLoops", loop); + } + else + { + this.SetProp(Prop_Data, "m_bSequenceLoops", shouldLoop); + } + this.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + + this.LegacyFootstepInterval = footstepInterval; + + return true; + } + + public bool AddGesture(const char[] animType, int definedIndex = -1, const char[] definedName = "") + { + float rate = 1.0, duration = 0.0, cycle = 0.0; + int sequence = this.SelectProfileGesture(definedIndex, definedName, animType, rate, cycle); + if (sequence == -1) + { + return false; + } + + if (rate < 0.0) + { + rate = 0.0; + } + if (rate > 12.0) + { + rate = 12.0; + } + + duration = this.SequenceDuration(sequence); + int layer = this.AddLayeredSequence(sequence, 1); + this.SetLayerDuration(layer, duration); + this.SetLayerPlaybackRate(layer, rate); + this.SetLayerCycle(layer, cycle); + this.SetLayerAutokill(layer, true); + } + + public bool IsLOSClearFromTarget(CBaseEntity target, bool checkGlass = true) + { + if (!target.IsValid()) + { + return false; + } + int traceEnt; + float eyePos[3], targetPos[3]; + this.WorldSpaceCenter(eyePos); + target.WorldSpaceCenter(targetPos); + if (IsValidClient(target.index)) + { + GetClientEyePosition(target.index, targetPos); + } + int flags = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP; + if (checkGlass) + { + flags = flags | CONTENTS_GRATE | CONTENTS_WINDOW; + } + Handle trace = TR_TraceRayFilterEx(eyePos, targetPos, + flags, + RayType_EndPoint, TraceRayDontHitEntity, this.index); + bool visible = !TR_DidHit(trace); + traceEnt = TR_GetEntityIndex(trace); + delete trace; + if (!visible && traceEnt == target.index) + { + visible = true; + } + return visible; + } + + public void CreateParticle(char[] particleName, float pos[3]) + { + CBaseEntity particle = CBaseEntity(CreateEntityByName("info_particle_system")); + if (particle.IsValid()) + { + particle.KeyValue("effect_name", particleName); + particle.Teleport(pos, NULL_VECTOR, NULL_VECTOR); + particle.Spawn(); + particle.Activate(); + particle.AcceptInput("Start"); + CreateTimer(0.1, Timer_KillEntity, EntIndexToEntRef(particle.index), TIMER_FLAG_NO_MAPCHANGE); + } + } + + public void ProcessRainbowOutline() + { + SF2NPC_BaseNPC controller = this.Controller; + int glow = EntRefToEntIndex(g_NpcGlowEntity[controller.Index]); + int color[4]; + color[0] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(controller.Index)) + controller.Index + 0) * 127.5 + 127.5); + color[1] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(controller.Index)) + controller.Index + 2) * 127.5 + 127.5); + color[2] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(controller.Index)) + controller.Index + 4) * 127.5 + 127.5); + color[3] = 255; + if (glow && glow != INVALID_ENT_REFERENCE) + { + SetVariantColor(color); + AcceptEntityInput(glow, "SetGlowColor"); + } + } +} + +static void OnCreate(SF2_BaseBoss boss) +{ + boss.Controller = SF2_INVALID_NPC; + boss.Target = CBaseEntity(-1); + boss.LastKillTime = 0.0; + + SDKHook(boss.index, SDKHook_SpawnPost, SpawnPost); +} + +static void SpawnPost(int entIndex) +{ + SF2_BaseBoss boss = SF2_BaseBoss(entIndex); + + boss.MoveParameterType = SF2NPCMoveParameter_None; + boss.MoveXParameter = boss.LookupPoseParameter("move_x"); + boss.MoveYParameter = boss.LookupPoseParameter("move_y"); + boss.EstimatedYaw = 0.0; + if (boss.MoveXParameter != -1 && boss.MoveYParameter != -1) + { + boss.MoveParameterType = SF2NPCMoveParameter_XY; + } + + if (boss.MoveParameterType == SF2NPCMoveParameter_None) + { + boss.MoveYawParameter = boss.LookupPoseParameter("move_yaw"); + if (boss.MoveYawParameter != -1) + { + boss.MoveParameterType = SF2NPCMoveParameter_Yaw; + } + } + + // This one is independent of move_x, move_y, and move_yaw. + // Some models need move_scale in addition to move_yaw or move_x + move_y. + boss.MoveScaleParameter = boss.LookupPoseParameter("move_scale"); + + boss.SetForceWanderPosition(NULL_VECTOR); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) + { + return; + } + + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(NPCGetFromEntIndex(inflictor)); + if (!controller.IsValid()) + { + return; + } + + if (controller.HasAttribute(SF2Attribute_IgnitePlayerOnDeath)) + { + client.Ignite(true); + } + + #if defined _store_included + char bossName[SF2_MAX_NAME_LENGTH]; + controller.GetName(bossName, sizeof(bossName)); + int difficulty = controller.Difficulty; + if (NPCGetDrainCreditState(controller.Index)) + { + Store_SetClientCredits(client.index, Store_GetClientCredits(client.index) - NPCGetDrainCreditAmount(controller.Index, difficulty)); + CPrintToChat(client.index, "{valve}%s{default} has stolen {green}%i credits{default} from you.", bossName, NPCGetDrainCreditAmount(controller.Index, difficulty)); + } + #endif + + SlenderPrintChatMessage(controller.Index, client.index); +} + +static any Native_GetIsValid(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.IsValid(); +} + +static any Native_GetController(Handle plugin, int numParams) +{ + int ent = GetNativeCell(1); + if (!IsValidEntity(ent)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", ent); + } + + SF2_BaseBoss boss = SF2_BaseBoss(ent); + if (!boss.Controller.IsValid()) + { + return -1; + } + return boss.Controller; +} + +static any Native_GetTarget(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.Target; +} + +static any Native_GetState(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.State; +} + +static any Native_GetCurrentChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.CurrentChaseDuration; +} + +static any Native_EyePosition(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + float buffer[3]; + bossEntity.EyePosition(buffer); + SetNativeArray(2, buffer, 3); + + return 0; +} + +static any Native_GetProfileName(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + if (!bossEntity.Controller.IsValid()) + { + return 0; + } + + char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; + bossEntity.Controller.GetProfile(buffer, sizeof(buffer)); + SetNativeString(2, buffer, sizeof(buffer)); + + return 0; +} + +static any Native_GetName(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + SF2NPC_BaseNPC controller = bossEntity.Controller; + if (!controller.IsValid()) + { + return 0; + } + + char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; + controller.GetName(buffer, sizeof(buffer)); + SetNativeString(2, buffer, sizeof(buffer)); + + return 0; +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + if (!bossEntity.Controller.IsValid()) + { + return 0; + } + + SF2BossProfileData data; + data = bossEntity.Controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_ResetProfileAnimation(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + + char animation[64], preDefinedName[64]; + GetNativeString(2, animation, sizeof(animation)); + GetNativeString(4, preDefinedName, sizeof(preDefinedName)); + float duration = GetNativeCellRef(5); + bossEntity.ResetProfileAnimation(animation, GetNativeCell(3), preDefinedName, duration); + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp new file mode 100644 index 00000000..ad614dff --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp @@ -0,0 +1,486 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAlertAction < NextBotAction +{ + public SF2_ChaserAlertAction(const float pos[3] = NULL_VECTOR, bool shouldRun = false) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Alert"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetEventCallback(EventResponderType_OnMoveToSuccess, OnMoveToSuccess); + g_Factory.SetEventCallback(EventResponderType_OnMoveToFailure, OnMoveToFailure); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_EndTime") + .DefineBoolField("m_IsGoalPosSet") + .DefineVectorField("m_GoalPos") + .DefineFloatField("m_NextWanderTime") + .DefineFloatField("m_NextTurnTime") + .DefineVectorField("m_LookPos") + .DefineBoolField("m_HasReachedAlertPosition") + .DefineFloatField("m_TimeUntilChase") + .DefineBoolField("m_IsRunning") + .EndDataMapDesc(); + } + + SF2_ChaserAlertAction action = view_as(g_Factory.Create()); + + if (!IsNullVector(pos)) + { + action.SetGoalPosition(pos); + } + + action.HasReachedAlertPosition = false; + action.IsRunning = shouldRun; + + return action; + } + + property float EndTime + { + public get() + { + return this.GetDataFloat("m_EndTime"); + } + + public set(float value) + { + this.SetDataFloat("m_EndTime", value); + } + } + + property bool IsGoalPosSet + { + public get() + { + return this.GetData("m_IsGoalPosSet") != 0; + } + + public set(bool value) + { + this.SetData("m_IsGoalPosSet", value); + } + } + + public void GetGoalPosition(float pos[3]) + { + this.GetDataVector("m_GoalPos", pos); + } + + public void SetGoalPosition(const float pos[3]) + { + this.IsGoalPosSet = true; + this.SetDataVector("m_GoalPos", pos); + } + + property float NextWanderTime + { + public get() + { + return this.GetDataFloat("m_NextWanderTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextWanderTime", value); + } + } + + property float NextTurnTime + { + public get() + { + return this.GetDataFloat("m_NextTurnTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextTurnTime", value); + } + } + + public void GetLookPosition(float pos[3]) + { + this.GetDataVector("m_LookPos", pos); + } + + public void SetLookPosition(const float pos[3]) + { + this.SetDataVector("m_LookPos", pos); + } + + property bool HasReachedAlertPosition + { + public get() + { + return this.GetData("m_HasReachedAlertPosition") != 0; + } + + public set(bool value) + { + this.SetData("m_HasReachedAlertPosition", value); + } + } + + property float TimeUntilChase + { + public get() + { + return this.GetDataFloat("m_TimeUntilChase"); + } + + public set(float value) + { + this.SetDataFloat("m_TimeUntilChase", value); + } + } + + property bool IsRunning + { + public get() + { + return this.GetData("m_IsRunning") != 0; + } + + public set(bool value) + { + this.SetData("m_IsRunning", value); + } + } +} + +static int OnStart(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + if (!action.IsRunning) + { + actor.MovementType = SF2NPCMoveType_Walk; + } + else + { + actor.MovementType = SF2NPCMoveType_Run; + } + + action.EndTime = gameTime + data.AlertDuration[difficulty]; + action.TimeUntilChase = gameTime + data.AlertGracetime[difficulty]; + + if (data.AlertOnAlertInfo.OnChangeState[difficulty]) + { + actor.ForceAlertOtherBosses(); + } + + actor.UpdateMovementAnimation(); + + float goalPos[3]; + if (action.IsGoalPosSet) + { + action.GetGoalPosition(goalPos); + } + else + { + actor.GetAbsOrigin(goalPos); + } + + UpdateAlertPosition(action, actor, goalPos, action.IsRunning); + + actor.AlertWithBoss = false; + + return action.Continue(); +} + +static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float interval) +{ + if (IsBeatBoxBeating(2)) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + CBaseEntity target = actor.Target; + int interruptConditions = actor.InterruptConditions; + + PathFollower path = controller.Path; + + if (data.ChaseOnLookData.Enabled[difficulty] && controller.ChaseOnLookTargets.Length > 0) + { + SF2_BasePlayer lookTarget = controller.ChaseOnLookTargets.Get(0); + if (lookTarget.IsValid && !lookTarget.IsEliminated && lookTarget.IsAlive && !lookTarget.IsInGhostMode && !lookTarget.HasEscaped) + { + actor.Target = lookTarget; + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone has looked at me, GET THEM!"); + } + } + + if (gameTime >= action.EndTime) + { + actor.State = STATE_IDLE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + if (actor.FollowedCompanionAlert && actor.Controller.IsValid()) + { + actor.FollowCooldownAlert = GetGameTime() + data.AlertOnAlertInfo.FollowCooldown[difficulty]; + actor.FollowedCompanionAlert = false; + } + actor.AlertWithBoss = false; + return action.ChangeTo(SF2_ChaserIdleAction(), "Huh nothing around here, back to idling."); + } + + if (target.IsValid()) + { + if ((interruptConditions & COND_ENEMYRECHASE) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Wait hold up, someone needs rechasing. GET THEM!"); + } + else if ((interruptConditions & COND_ENEMYNEAR) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone is too close, get them!"); + } + } + + if (target.IsValid() && ((interruptConditions & COND_SAWENEMY) != 0)) + { + if (gameTime >= action.TimeUntilChase) + { + path.Invalidate(); + actor.State = STATE_CHASE; + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "I've got a target, get them!"); + } + } + else if ((interruptConditions & COND_ALERT_TRIGGER) != 0) + { + SF2_BasePlayer alertTarget = actor.AlertTriggerTarget; + if (alertTarget.IsValid && !alertTarget.IsEliminated && alertTarget.IsAlive && !alertTarget.HasEscaped) + { + float pos[3]; + actor.GetAlertTriggerPosition(alertTarget, pos); + + UpdateAlertPosition(action, actor, pos, data.AlertRunOnHearSound[difficulty]); + action.IsRunning = false; + } + } + + if ((interruptConditions & COND_DEBUG) != 0) + { + float pos[3]; + actor.GetForceWanderPosition(pos); + + UpdateAlertPosition(action, actor, pos, data.AlertRunOnWander[difficulty]); + } + + if (action.HasReachedAlertPosition && data.CanWander[difficulty]) + { + if (gameTime >= action.NextWanderTime) + { + float min = data.WanderTimeMin[difficulty]; + float max = data.WanderTimeMax[difficulty]; + + action.NextWanderTime = gameTime + GetRandomFloat(min, max); + + float rangeMin = data.WanderRangeMin[difficulty]; + float rangeMax = data.WanderRangeMax[difficulty]; + float range = GetRandomFloat(rangeMin, rangeMax); + + CNavArea area = actor.GetLastKnownArea(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, range); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) + { + continue; + } + if (collector.Get(i).GetCostSoFar() < rangeMin) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + if (validAreaCount > 0) + { + float wanderPos[3]; + CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); + + if (wanderArea != NULL_AREA) + { + wanderArea.GetCenter(wanderPos); + + UpdateAlertPosition(action, actor, wanderPos, data.AlertRunOnWander[difficulty]); + } + } + + delete collector; + delete areaArray; + } + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + if (actor.IsAttemptingToMove) + { + action.NextTurnTime = -1.0; + } + else + { + float lookPos[3]; + action.GetLookPosition(lookPos); + loco.FaceTowards(lookPos); + if (action.NextTurnTime <= 0.0) + { + action.NextTurnTime = gameTime + GetRandomFloat(1.5, 3.0); + } + } + + if (action.NextTurnTime > 0.0 && action.NextTurnTime <= gameTime) + { + float myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + float lookAt[3]; + lookAt[0] = GetRandomFloat(-100.0, 100.0); + lookAt[1] = GetRandomFloat(-100.0, 100.0); + VectorTransform(lookAt, myPos, myAng, lookAt); + action.SetLookPosition(lookAt); + action.NextTurnTime = gameTime + GetRandomFloat(1.5, 3.0); + } + + if (gameTime >= actor.NextVoiceTime) + { + actor.PerformVoice(SF2BossSound_Alert); + } + + return action.Continue(); +} + +static void UpdateAlertPosition(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, const float newGoalPos[3], bool shouldRun) +{ + INextBot bot = actor.MyNextBotPointer(); + PathFollower path = actor.Controller.Path; + + action.SetGoalPosition(newGoalPos); + + path.ComputeToPos(bot, newGoalPos); + action.HasReachedAlertPosition = false; + + if (!action.IsRunning) + { + if (shouldRun) + { + actor.MovementType = SF2NPCMoveType_Run; + action.IsRunning = true; + } + else + { + actor.MovementType = SF2NPCMoveType_Walk; + } + } +} + +static void OnSuspend(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + actor.IsAttemptingToMove = false; +} + +static void OnEnd(SF2_ChaserAlertAction action, SF2_ChaserEntity actor) +{ + actor.AlertTriggerTarget = SF2_INVALID_PLAYER; + for (int clientIndex = 1; clientIndex <= MaxClients; clientIndex++) + { + SF2_BasePlayer client = SF2_BasePlayer(clientIndex); + if (client.IsValid && !client.IsEliminated && !client.HasEscaped) + { + actor.SetAlertTriggerCount(client, 0); + actor.SetAlertSoundTriggerCooldown(client, 2.0); + } + } +} + +static void OnResume(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.UpdateMovementAnimation(); +} + +static void OnReachedAlertPosition(SF2_ChaserAlertAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + + action.NextTurnTime = gameTime + GetRandomFloat(1.5, 3.0); + action.NextWanderTime = gameTime + GetRandomFloat(data.WanderEnterTimeMin[difficulty], data.WanderEnterTimeMax[difficulty]); +} + +static void OnMoveToSuccess(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, Path path) +{ + if (!action.HasReachedAlertPosition) + { + OnReachedAlertPosition(action, actor); + } + action.HasReachedAlertPosition = true; + action.IsRunning = false; +} + +static void OnMoveToFailure(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, Path path, MoveToFailureType reason) +{ + if (!action.HasReachedAlertPosition) + { + OnReachedAlertPosition(action, actor); + } + action.HasReachedAlertPosition = true; + action.IsRunning = false; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp new file mode 100644 index 00000000..33f6a7a2 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -0,0 +1,415 @@ +#pragma semicolon 1 +// This manages the end time and getting a attack + +#include "attacks/melee.sp" +#include "attacks/bullet.sp" +#include "attacks/projectile.sp" +#include "attacks/laser_beam.sp" +#include "attacks/explosive_dance.sp" +#include "attacks/custom.sp" +#include "attacks/tongue.sp" + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction < NextBotAction +{ + public SF2_ChaserAttackAction(const char[] attackName, float endTime) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackMain"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnOtherKilled, OnOtherKilled); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_OldState") + .DefineStringField("m_AttackName") + .DefineFloatField("m_EndTime") + .DefineBoolField("m_DidEndAnimation") + .DefineBoolField("m_PlayedBeginVoice") + .DefineBoolField("m_ShouldReplayAnimation") + .DefineStringField("m_LoopSound") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction action = view_as(g_Factory.Create()); + + action.SetAttackName(attackName); + action.EndTime = endTime; + + return action; + } + + public static void Initialize() + { + SF2_ChaserAttackAction_Melee.Initialize(); + SF2_ChaserAttackAction_ExplosiveDance.Initialize(); + SF2_ChaserAttackAction_Bullet.Initialize(); + SF2_ChaserAttackAction_Projectile.Initialize(); + SF2_ChaserAttackAction_Laser.Initialize(); + SF2_ChaserAttackAction_Custom.Initialize(); + SF2_ChaserAttackAction_Tongue.Initialize(); + } + + property int OldState + { + public get() + { + return this.GetData("m_OldState"); + } + + public set(int value) + { + this.SetData("m_OldState", value); + } + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float EndTime + { + public get() + { + return this.GetDataFloat("m_EndTime"); + } + + public set(float value) + { + this.SetDataFloat("m_EndTime", value); + } + } + + property bool DidEndAnimation + { + public get() + { + return this.GetData("m_DidEndAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_DidEndAnimation", value); + } + } + + public char[] GetLoopSound() + { + char name[PLATFORM_MAX_PATH]; + this.GetDataString("m_LoopSound", name, sizeof(name)); + return name; + } + + public void SetLoopSound(const char[] name) + { + this.SetDataString("m_LoopSound", name); + } + + property bool PlayedBeginVoice + { + public get() + { + return this.GetData("m_PlayedBeginVoice") != 0; + } + + public set(bool value) + { + this.SetData("m_PlayedBeginVoice", value); + } + } + + property bool ShouldReplayAnimation + { + public get() + { + return this.GetData("m_ShouldReplayAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_ShouldReplayAnimation", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserAttackAction action, SF2_ChaserEntity actor) +{ + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + actor.Controller.Path.Invalidate(); + + NextBotAction attackAction = NULL_ACTION; + + Action result = Plugin_Continue; + Call_StartForward(g_OnChaserGetAttackActionPFwd); + Call_PushCell(actor); + Call_PushString(action.GetAttackName()); + Call_PushCellRef(attackAction); + Call_Finish(result); + + if (result == Plugin_Changed) + { + return attackAction; + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + if (controller.Flags & SFF_FAKE) + { + controller.MarkAsFake(); + return action.Done("I'm a faker, bye"); + } + + if (action.ActiveChild == NULL_ACTION) + { + return action.Done("No attack action was given"); + } + + action.OldState = actor.State; + actor.State = STATE_ATTACK; + + actor.SetAttackName(action.GetAttackName()); + actor.IsAttacking = true; + + actor.RemoveAllGestures(); + CBaseNPC_RemoveAllLayers(actor.index); + + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + SF2ChaserBossProfileData data; + SF2ChaserBossProfileAttackData attackData; + data = controller.GetProfileData(); + data.GetAttack(actor.GetAttackName(), attackData); + actor.AttackRunDelay = gameTime + attackData.RunDelay[difficulty]; + actor.AttackRunDuration = gameTime + attackData.RunDuration[difficulty]; + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + controller.Path.Invalidate(); + + actor.InvokeOnStartAttack(action.GetAttackName()); + + if (attackData.Type == SF2BossAttackType_Custom) + { + return action.Continue(); + } + + if (attackData.RunSpeed[difficulty] > 0.0) + { + actor.IsAttemptingToMove = true; + actor.MovementType = SF2NPCMoveType_Attack; + } + actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + actor.AddGesture(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + + SF2BossProfileSoundInfo info; + if (actor.SearchSoundsWithSectionName(data.AttackBeginSounds, action.GetAttackName(), info)) + { + action.PlayedBeginVoice = actor.PerformVoice(SF2BossSound_AttackBegin, action.GetAttackName()); + } + else + { + actor.PerformVoice(SF2BossSound_Attack, action.GetAttackName()); + action.PlayedBeginVoice = true; + } + + if (attackData.StartEffects != null) + { + SlenderSpawnEffects(attackData.StartEffects, controller.Index, false); + } + + float duration = 0.0; + NextBotAction newAction = actor.IsAttackTransitionPossible(action.GetAttackName(), _, duration); + action.ShouldReplayAnimation = newAction != NULL_ACTION; + if (newAction != NULL_ACTION) + { + action.EndTime += duration; + } + return newAction != NULL_ACTION ? action.SuspendFor(newAction) : action.Continue(); +} + +static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + + if (!controller.IsValid()) + { + return action.Done(); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2ChaserBossProfileData data; + SF2ChaserBossProfileAttackData attackData; + data = controller.GetProfileData(); + data.GetAttack(actor.GetAttackName(), attackData); + bool end = false; + + int difficulty = controller.Difficulty; + + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float myPos[3], targetPos[3]; + actor.GetAbsOrigin(myPos); + target.GetAbsOrigin(targetPos); + float distance = GetVectorSquareMagnitude(myPos, targetPos); + if (attackData.CancelDistance[difficulty] >= 0.0 && distance > Pow(attackData.CancelDistance[difficulty], 2.0)) + { + end = true; + } + + if (attackData.MinCancelDistance[difficulty] >= 0.0 && distance < Pow(attackData.MinCancelDistance[difficulty], 2.0)) + { + end = true; + } + + if (attackData.CancelLos[difficulty] && !actor.IsLOSClearFromTarget(target)) + { + end = true; + } + } + + if (attackData.Type == SF2BossAttackType_Custom) + { + if (action.ActiveChild == NULL_ACTION || end) + { + return action.Done("Done using a custom attack"); + } + return action.Continue(); + } + + SF2BossProfileSoundInfo info; + char value[PLATFORM_MAX_PATH]; + value = action.GetLoopSound(); + if (value[0] == '\0') + { + if (actor.SearchSoundsWithSectionName(data.AttackLoopSounds, action.GetAttackName(), info)) + { + char sound[PLATFORM_MAX_PATH]; + info.EmitSound(_, actor.index, _, _, _, sound); + action.SetLoopSound(sound); + } + else + { + action.SetLoopSound("-"); + } + } + + if (action.ActiveChild == NULL_ACTION || GetGameTime() > action.EndTime || end) + { + NextBotAction newAction = actor.IsAttackTransitionPossible(action.GetAttackName(), true); + if (!action.DidEndAnimation && newAction != NULL_ACTION) + { + actor.PerformVoice(SF2BossSound_AttackEnd, action.GetAttackName()); + action.DidEndAnimation = true; + if (newAction != NULL_ACTION) + { + return action.SuspendFor(newAction, "We're gonna do a attack end action"); + } + } + return action.Done("Attack finished"); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + char value[PLATFORM_MAX_PATH]; + value = action.GetLoopSound(); + if (value[0] != '\0' && strcmp(value, "-") != 0) + { + StopSound(actor.index, SNDCHAN_AUTO, value); + } + if (actor.IsStunned) + { + return action.Done(); + } + if (!action.ShouldReplayAnimation && !action.DidEndAnimation) + { + return action.Done("Suspended"); + } + return action.Continue(); +} + +static int OnResume(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + if (!action.PlayedBeginVoice) + { + actor.PerformVoice(SF2BossSound_Attack, action.GetAttackName()); + action.PlayedBeginVoice = true; + } + if (action.ShouldReplayAnimation) + { + actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + actor.AddGesture(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); + action.ShouldReplayAnimation = false; + } + + if (action.DidEndAnimation) + { + return action.Done("Finished"); + } + return action.Continue(); +} + +static void OnEnd(SF2_ChaserAttackAction action, SF2_ChaserEntity actor) +{ + actor.IsAttacking = false; + actor.IsAttemptingToMove = false; + actor.CancelAttack = false; + actor.MovementType = SF2NPCMoveType_Run; + + char value[PLATFORM_MAX_PATH]; + value = action.GetLoopSound(); + if (value[0] != '\0' && strcmp(value, "-") != 0) + { + StopSound(actor.index, SNDCHAN_AUTO, value); + } + + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + float gameTime = GetGameTime(); + SF2ChaserBossProfileData data; + SF2ChaserBossProfileAttackData attackData; + data = controller.GetProfileData(); + data.GetAttack(actor.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + + actor.SetNextAttackTime(actor.GetAttackName(), gameTime + attackData.Cooldown[difficulty]); + + actor.InvokeOnEndAttack(actor.GetAttackName()); + } + + actor.State = action.OldState; + + actor.SetAttackName(""); +} + +static void OnOtherKilled(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseCombatCharacter victim, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damagetype) +{ + actor.CheckTauntKill(SF2_BasePlayer(victim.index)); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp new file mode 100644 index 00000000..8303a63e --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp @@ -0,0 +1,43 @@ +#pragma semicolon 1 +// This is for detecting attacks + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackLayerAction < NextBotAction +{ + public SF2_ChaserAttackLayerAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackLayer"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.BeginDataMapDesc() + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserAttackLayerAction action, SF2_ChaserEntity actor) +{ + return SF2_ChaserChaseLayerAction(); +} + +static int Update(SF2_ChaserAttackLayerAction action, SF2_ChaserEntity actor) +{ + CBaseEntity target = actor.Target; + if (!actor.IsAttacking && target.IsValid()) + { + if (actor.IsLOSClearFromTarget(target)) + { + NextBotAction attackAction = actor.GetAttackAction(target); + if (attackAction != NULL_ACTION) + { + actor.EndCloak(); + return action.SuspendFor(attackAction, "Time to die!"); + } + } + } + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp new file mode 100644 index 00000000..054ba585 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp @@ -0,0 +1,350 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Bullet < NextBotAction +{ + public SF2_ChaserAttackAction_Bullet(int attackIndex, const char[] attackName, float fireDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackBullet"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Bullet action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Ranged) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Bullet(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Bullet action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Bullet action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer firing bullets"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + float gameTime = GetGameTime(); + int difficulty = controller.Difficulty; + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + DoBulletAttack(actor, action.GetAttackName()); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextFireTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextFireTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextFireTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextFireTime = -1.0; + } + } + return action.Continue(); +} + +static void DoBulletAttack(SF2_ChaserEntity actor, const char[] attackName) +{ + SF2NPC_Chaser controller = actor.Controller; + CBaseEntity target = actor.Target; + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + float spread = attackData.BulletSpread[difficulty]; + ArrayList bulletSounds = data.BulletShootSounds; + SF2BossProfileSoundInfo soundInfo; + if (actor.SearchSoundsWithSectionName(bulletSounds, attackName, soundInfo)) + { + soundInfo.EmitSound(_, actor.index); + } + + float effectPos[3], targetPos[3], basePos[3], baseAng[3]; + float effectAng[3] = {0.0, 0.0, 0.0}; + actor.GetAbsOrigin(basePos); + actor.GetAbsAngles(baseAng); + if (target.IsValid()) + { + target.WorldSpaceCenter(targetPos); + } + else + { + float fwd[3], eyePos[3]; + GetAngleVectors(baseAng, fwd, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(fwd, fwd); + controller.GetEyePosition(eyePos); + targetPos[0] = eyePos[0] + fwd[0] * 9001.0; + targetPos[1] = eyePos[1] + fwd[1] * 9001.0; + targetPos[2] = eyePos[2] + fwd[2] * 9001.0; + } + effectPos = attackData.BulletOffset; + VectorTransform(effectPos, basePos, baseAng, effectPos); + AddVectors(effectAng, baseAng, effectAng); + + float direction[3], angle[3]; + if (!target.IsValid()) + { + angle = baseAng; + } + SubtractVectors(targetPos, effectPos, direction); + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + for (int i = 0; i < attackData.BulletCount[difficulty]; i++) + { + float x, y; + x = GetRandomFloat(-0.5, 0.5) + GetRandomFloat(-0.5, 0.5); + y = GetRandomFloat(-0.5, 0.5) + GetRandomFloat(-0.5, 0.5); + + float fwd[3], right[3], up[3]; + GetAngleVectors(angle, fwd, right, up); + + float dir[3]; + dir[0] = fwd[0] + x * spread * right[0] + y * spread * up[0]; + dir[1] = fwd[1] + x * spread * right[1] + y * spread * up[1]; + dir[2] = fwd[2] + x * spread * right[2] + y * spread * up[2]; + NormalizeVector(dir, dir); + + float end[3]; + end[0] = effectPos[0] + dir[0] * 9001.0; + end[1] = effectPos[1] + dir[1] * 9001.0; + end[2] = effectPos[2] + dir[2] * 9001.0; + + Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); + if (TR_GetFraction(trace) < 1.0) + { + int hitTarget = TR_GetEntityIndex(trace); + if (hitTarget == -1) + { + delete trace; + return; + } + + float endPos[3]; + TR_GetEndPosition(endPos, trace); + + if (hitTarget == 0) + { + float normal[3]; + TR_GetPlaneNormal(trace, normal); + GetVectorAngles(normal, normal); + CreateParticle("impact_concrete", endPos, normal); + } + + SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.BulletDamage[difficulty], DMG_BULLET, _, CalculateBulletDamageForce(dir, 1.0), endPos); + + attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); + + char effect[PLATFORM_MAX_PATH]; + FormatEx(effect, sizeof(effect), "%s", attackData.BulletTrace); + if (attackData.BulletTrace[0] == '\0') + { + delete trace; + return; + } + + int table = FindStringTable("ParticleEffectNames"); + if (table == INVALID_STRING_TABLE) + { + LogError("Could not find string table: ParticleEffectNames"); + delete trace; + return; + } + + char tmp[256]; + int count = GetStringTableNumStrings(table); + int strIndex = INVALID_STRING_INDEX; + for (int i2 = 0; i2 < count; i2++) + { + ReadStringTable(table, i2, tmp, sizeof(tmp)); + if (strcmp(tmp, effect, false) == 0) + { + strIndex = i2; + break; + } + } + if (strIndex == INVALID_STRING_INDEX) + { + LogError("Could not find particle: %s", effect); + delete trace; + return; + } + + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", effectPos[0]); + TE_WriteFloat("m_vecOrigin[1]", effectPos[1]); + TE_WriteFloat("m_vecOrigin[2]", effectPos[2]); + TE_WriteNum("m_iParticleSystemIndex", strIndex); + TE_WriteNum("entindex", actor.index); + TE_WriteNum("m_iAttachType", 2); + TE_WriteNum("m_iAttachmentPointIndex", 0); + TE_WriteNum("m_bResetParticles", false); + TE_WriteNum("m_bControlPoint1", 1); + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); + TE_SendToAll(); + } + delete trace; + } +} + +static float[] CalculateBulletDamageForce(const float bulletDir[3], float scale) +{ + float force[3]; + force = bulletDir; + NormalizeVector(force, force); + ScaleVector(force, g_PhysicsPushScaleConVar.FloatValue); + ScaleVector(force, scale); + return force; +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Bullet action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + DoBulletAttack(actor, action.GetAttackName()); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/custom.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/custom.sp new file mode 100644 index 00000000..4c5a0aba --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/custom.sp @@ -0,0 +1,41 @@ +#pragma semicolon 1 + +methodmap SF2_ChaserAttackAction_Custom < NextBotAction +{ + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + g_OnChaserGetCustomAttackPossibleStatePFwd.AddFunction(null, OnChaserGetCustomAttackPossibleState); + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + if (attackData.Type != SF2BossAttackType_Custom) + { + return Plugin_Continue; + } + + result = chaser.GetCustomAttack(attackName, chaser.Target); + return Plugin_Changed; +} + +static Action OnChaserGetCustomAttackPossibleState(SF2_ChaserEntity chaser, const char[] attackName, CBaseEntity target) +{ + if (!chaser.IsCustomAttackPossible(attackName, target)) + { + return Plugin_Continue; + } + + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/explosive_dance.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/explosive_dance.sp new file mode 100644 index 00000000..8bd5d9c5 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/explosive_dance.sp @@ -0,0 +1,215 @@ +#pragma semicolon 1 + +static char g_ExplosionSounds[][] = +{ + ")weapons/explode1.wav", + ")weapons/explode2.wav", + ")weapons/explode3.wav" +}; + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_ExplosiveDance < NextBotAction +{ + public SF2_ChaserAttackAction_ExplosiveDance(int attackIndex, const char[] attackName, float damageDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackExplosiveDance"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextDamageTime") + .DefineIntField("m_ExplosionCount") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_ExplosiveDance action = view_as(g_Factory.Create()); + + action.NextDamageTime = damageDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + action.ExplosionCount = 0; + + return action; + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextDamageTime + { + public get() + { + return this.GetDataFloat("m_NextDamageTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextDamageTime", value); + } + } + + property int ExplosionCount + { + public get() + { + return this.GetData("m_ExplosionCount"); + } + + public set(int value) + { + this.SetData("m_ExplosionCount", value); + } + } +} + +static void OnGamemodeStart() +{ + for (int i = 0; i < sizeof(g_ExplosionSounds); i++) + { + PrecacheSound(g_ExplosionSounds[i], true); + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_ExplosiveDance) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_ExplosiveDance(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer explosion dancing"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + float gameTime = GetGameTime(); + + if (action.NextDamageTime >= 0.0 && gameTime > action.NextDamageTime && attackData.EventNumber == -1) + { + DoExplosion(action, actor); + } + return action.Continue(); +} + +static void DoExplosion(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + float damage = attackData.Damage[difficulty]; + float radius = attackData.ExplosiveDanceRadius[difficulty]; + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + damage *= 0.5; + } + + float worldPos[3], myEyeAng[3]; + actor.WorldSpaceCenter(worldPos); + actor.GetAbsAngles(myEyeAng); + + action.ExplosionCount++; + if (action.ExplosionCount > 35) + { + action.NextDamageTime = -1.0; + return; + } + + float explosionPos[3]; + + explosionPos = worldPos; + explosionPos[0] = worldPos[0] + GetRandomFloat(-350.0, 350.0); + explosionPos[1] = worldPos[1] + GetRandomFloat(-350.0, 350.0); + Explode(explosionPos, damage, radius, actor.index); + EmitSoundToAll(g_ExplosionSounds[GetRandomInt(0, sizeof(g_ExplosionSounds) - 1)], actor.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + + action.NextDamageTime = GetGameTime() + 0.13; +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_ExplosiveDance action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + DoExplosion(action, actor); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp new file mode 100644 index 00000000..2377b7a2 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp @@ -0,0 +1,269 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Laser < NextBotAction +{ + public SF2_ChaserAttackAction_Laser(int attackIndex, const char[] attackName, float fireDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackLaser"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineFloatField("m_LaserDuration") + .DefineFloatField("m_LaserCooldown") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Laser action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property float LaserDuration + { + public get() + { + return this.GetDataFloat("m_LaserDuration"); + } + + public set(float value) + { + this.SetDataFloat("m_LaserDuration", value); + } + } + + property float LaserCooldown + { + public get() + { + return this.GetDataFloat("m_LaserCooldown"); + } + + public set(float value) + { + this.SetDataFloat("m_LaserCooldown", value); + } + } + +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_LaserBeam) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Laser(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer firing my super laser beam"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + float gameTime = GetGameTime(); + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + if (action.LaserCooldown < gameTime) + { + action.LaserCooldown = gameTime + 0.1; + FireLaser(action, actor, 0.1); + } + + if (action.LaserDuration < gameTime) + { + action.NextFireTime = -1.0; + } + } + + return action.Continue(); +} + +static void FireLaser(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + CBaseEntity target = actor.Target; + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (!target.IsValid()) + { + return; + } + + float targetPos[3]; + target.WorldSpaceCenter(targetPos); + + float basePos[3], baseAng[3], effectAng[3], effectPos[3]; + actor.GetAbsOrigin(basePos); + actor.GetAbsAngles(baseAng); + effectPos = attackData.LaserOffset; + VectorTransform(effectPos, basePos, baseAng, effectPos); + AddVectors(effectAng, baseAng, effectAng); + + float direction[3], angle[3]; + SubtractVectors(targetPos, effectPos, direction); + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + float fwd[3]; + GetAngleVectors(angle, fwd, NULL_VECTOR, NULL_VECTOR); + + NormalizeVector(fwd, fwd); + + float end[3]; + end[0] = effectPos[0] + fwd[0] * 9001.0; + end[1] = effectPos[1] + fwd[1] * 9001.0; + end[2] = effectPos[2] + fwd[2] * 9001.0; + + Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); + + if (TR_GetFraction(trace) < 1.0) + { + int hitTarget = TR_GetEntityIndex(trace); + if (hitTarget == -1) + { + delete trace; + return; + } + + float endPos[3]; + TR_GetEndPosition(endPos, trace); + + int color[3]; + color = attackData.LaserColor; + int actualColor[4]; + actualColor[0] = color[0]; + actualColor[1] = color[1]; + actualColor[2] = color[2]; + actualColor[3] = 255; + + if (attackData.LaserAttachment) + { + int attachmentIndex = actor.LookupAttachment(attackData.LaserAttachmentName); + float targetEntPos[3], tempAng[3]; + actor.GetAttachment(attachmentIndex, targetEntPos, tempAng); + TE_SetupBeamPoints(targetEntPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, interval, attackData.LaserSize, attackData.LaserSize, 5, attackData.LaserNoise, actualColor, 1); + TE_SendToAll(); + } + else + { + TE_SetupBeamPoints(effectPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, interval, attackData.LaserSize, attackData.LaserSize, 5, attackData.LaserNoise, actualColor, 1); + TE_SendToAll(); + } + + SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.LaserDamage[difficulty], DMG_SHOCK|DMG_ALWAYSGIB, _, _, endPos); + + attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); + } +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + FireLaser(action, actor, 0.1); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp new file mode 100644 index 00000000..948ce571 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -0,0 +1,420 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Melee < NextBotAction +{ + public SF2_ChaserAttackAction_Melee(int attackIndex, const char[] attackName, float damageDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackMelee"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextDamageTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Melee action = view_as(g_Factory.Create()); + + action.NextDamageTime = damageDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextDamageTime + { + public get() + { + return this.GetDataFloat("m_NextDamageTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextDamageTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Melee) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Melee(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer melee attacking"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + if (action.NextDamageTime >= 0.0 && gameTime > action.NextDamageTime && attackData.EventNumber == -1) + { + DoMeleeAttack(action, actor); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextDamageTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextDamageTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextDamageTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextDamageTime = -1.0; + } + } + return action.Continue(); +} + +static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + + int difficulty = controller.Difficulty; + + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (originalData.IsPvEBoss) + { + attackEliminated = true; + } + float damage = attackData.Damage[difficulty]; + float damageVsProps = attackData.DamageVsProps; + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + damage *= 0.5; + damageVsProps *= 0.5; + } + int damageType = attackData.DamageType[difficulty]; + + float myPos[3], myEyePos[3], myEyeAng[3]; + actor.GetAbsOrigin(myPos); + controller.GetEyePosition(myEyePos); + actor.GetAbsAngles(myEyeAng); + + AddVectors(g_SlenderEyePosOffset[controller.Index], myEyeAng, myEyeAng); + + float viewPunch[3]; + viewPunch = attackData.PunchVelocity; + + float range = attackData.Range[difficulty]; + float spread = attackData.Spread[difficulty]; + float force = attackData.DamageForce[difficulty]; + + bool hit = false; + + ArrayList targets = new ArrayList(); + + // Sweep the props + TR_EnumerateEntitiesSphere(myEyePos, range, PARTITION_SOLID_EDICTS, EnumerateBreakableEntities, targets); + for (int i = 0; i < targets.Length; i++) + { + CBaseEntity prop = CBaseEntity(targets.Get(i)); + + if (!IsTargetInMeleeChecks(actor, prop, range, spread)) + { + continue; + } + + hit = true; + SDKHooks_TakeDamage(prop.index, actor.index, actor.index, damageVsProps, 64, _, _, myEyePos, false); + } + targets.Clear(); + + // Sweep the players + TR_EnumerateEntitiesSphere(myEyePos, range, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, targets); + for (int i = 0; i < targets.Length; i++) + { + SF2_BasePlayer player = targets.Get(i); + + if (!attackEliminated && player.IsEliminated) + { + continue; + } + + if (!IsTargetInMeleeChecks(actor, player, range, spread)) + { + continue; + } + + hit = true; + + float direction[3], targetPos[3]; + player.WorldSpaceCenter(targetPos); + SubtractVectors(targetPos, myEyePos, direction); + GetVectorAngles(direction, direction); + GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(direction, direction); + ScaleVector(direction, force); + + if (controller.HasAttribute(SF2Attribute_DeathCamOnLowHealth)) + { + float checkDamage = damage; + + if ((damageType & DMG_ACID) && !player.InCondition(TFCond_DefenseBuffed)) + { + checkDamage *= 3.0; + } + else if (((damageType & DMG_POISON) || player.InCondition(TFCond_Jarated) || player.InCondition(TFCond_MarkedForDeath) || player.InCondition(TFCond_MarkedForDeathSilent)) && !player.InCondition(TFCond_DefenseBuffed)) + { + checkDamage *= 1.35; + } + else if (player.InCondition(TFCond_DefenseBuffed)) + { + checkDamage *= 0.65; + } + + if (checkDamage > player.Health) + { + player.StartDeathCam(controller.Index, myPos); + } + } + player.TakeDamage(_, actor.index, actor.index, damage, damageType, _, direction, myEyePos, false); + player.ViewPunch(viewPunch); + + if (player.InCondition(TFCond_UberchargedCanteen) && player.InCondition(TFCond_CritOnFirstBlood) && player.InCondition(TFCond_UberBulletResist) && player.InCondition(TFCond_UberBlastResist) && player.InCondition(TFCond_UberFireResist) && player.InCondition(TFCond_MegaHeal)) //Remove Powerplay + { + player.ChangeCondition(TFCond_UberchargedCanteen, true); + player.ChangeCondition(TFCond_CritOnFirstBlood, true); + player.ChangeCondition(TFCond_UberBulletResist, true); + player.ChangeCondition(TFCond_UberBlastResist, true); + player.ChangeCondition(TFCond_UberFireResist, true); + player.ChangeCondition(TFCond_MegaHeal, true); + TF2_SetPlayerPowerPlay(player.index, false); + } + + attackData.ApplyDamageEffects(player, difficulty, SF2_ChaserBossEntity(actor.index)); + + if (attackData.HitEffects != null) + { + SlenderSpawnEffects(attackData.HitEffects, controller.Index, false, _, _, _, player.index); + } + + // Add stress + float stressScalar = damage / 125.0; + if (stressScalar > 1.0) + { + stressScalar = 1.0; + } + ClientAddStress(player.index, 0.33 * stressScalar); + + Call_StartForward(g_OnClientDamagedByBossFwd); + Call_PushCell(player.index); + Call_PushCell(controller); + Call_PushCell(actor.index); + Call_PushFloat(damage); + Call_PushCell(damageType); + Call_Finish(); + } + + ArrayList hitSounds = hit ? data.HitSounds : data.MissSounds; + SF2BossProfileSoundInfo info; + if (actor.SearchSoundsWithSectionName(hitSounds, action.GetAttackName(), info)) + { + info.EmitSound(_, actor.index); + } + + if (hit) + { + CBaseEntity phys = CBaseEntity(CreateEntityByName("env_physexplosion")); + if (phys.IsValid()) + { + float worldSpace[3]; + actor.WorldSpaceCenter(worldSpace); + phys.Teleport(worldSpace, NULL_VECTOR, NULL_VECTOR); + phys.KeyValue("spawnflags", "1"); + phys.KeyValueFloat("radius", range); + phys.KeyValueFloat("magnitude", force); + phys.Spawn(); + phys.Activate(); + phys.AcceptInput("Explode"); + RemoveEntity(phys.index); + } + + if (attackData.Disappear[difficulty]) + { + controller.UnSpawn(); + } + } + else + { + if (attackData.MissEffects != null) + { + SlenderSpawnEffects(attackData.MissEffects, controller.Index, false); + } + } + + delete targets; + + actor.DoShockwave(action.GetAttackName()); +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + DoMeleeAttack(action, actor); + } +} + +static bool IsTargetInMeleeChecks(SF2_ChaserEntity actor, CBaseEntity target, float range, float fov) +{ + if (!target.IsValid()) + { + return false; + } + + float myPos[3], myEyePos[3], myEyeAng[3]; + actor.GetAbsOrigin(myPos); + actor.Controller.GetEyePosition(myEyePos); + actor.GetAbsAngles(myEyeAng); + + AddVectors(g_SlenderEyePosOffset[actor.Controller.Index], myEyeAng, myEyeAng); + + float targetPos[3]; + target.WorldSpaceCenter(targetPos); + + float distance = actor.MyNextBotPointer().GetRangeSquaredTo(target.index); + if (distance > Pow(range, 2.0)) + { + return false; + } + + float direction[3]; + SubtractVectors(targetPos, myEyePos, direction); + GetVectorAngles(direction, direction); + float difference = FloatAbs(AngleDiff(direction[1], myEyeAng[1])); + if (difference > fov) + { + return false; + } + + TR_TraceRayFilter(myEyePos, targetPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); + + if (TR_DidHit() && TR_GetEntityIndex() != target.index) + { + return false; + } + + return true; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp new file mode 100644 index 00000000..f0d3e6b2 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp @@ -0,0 +1,295 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Projectile < NextBotAction +{ + public SF2_ChaserAttackAction_Projectile(int attackIndex, const char[] attackName, float fireDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackProjectile"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Projectile action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Projectile) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Projectile(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int OnStart(SF2_ChaserAttackAction_Projectile action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Projectile action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer firing projectiles"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + FireProjectile(actor, action.GetAttackName()); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextFireTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextFireTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextFireTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextFireTime = -1.0; + } + } + return action.Continue(); +} + +static void FireProjectile(SF2_ChaserEntity actor, const char[] attackName) +{ + SF2NPC_Chaser controller = actor.Controller; + float targetPos[3], myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + if (actor.Target.IsValid()) + { + actor.Target.WorldSpaceCenter(targetPos); + } + else + { + float fwd[3], eyePos[3]; + GetAngleVectors(myAng, fwd, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(fwd, fwd); + controller.GetEyePosition(eyePos); + targetPos[0] = eyePos[0] + fwd[0] * 9001.0; + targetPos[1] = eyePos[1] + fwd[1] * 9001.0; + targetPos[2] = eyePos[2] + fwd[2] * 9001.0; + } + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + + bool attackWaiters = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + attackWaiters = true; + } + + ArrayList projectileSounds = data.ProjectileShootSounds; + SF2BossProfileSoundInfo soundInfo; + if (actor.SearchSoundsWithSectionName(projectileSounds, attackName, soundInfo)) + { + soundInfo.EmitSound(_, actor.index); + } + + float effectPos[3]; + effectPos = attackData.ProjectileOffset; + VectorTransform(effectPos, myPos, myAng, effectPos); + + for (int i = 0; i < attackData.ProjectileCount[difficulty]; i++) + { + float direction[3], angle[3]; + SubtractVectors(targetPos, effectPos, direction); + float deviation = attackData.ProjectileDeviation[difficulty]; + + if (deviation != 0) + { + direction[0] += GetRandomFloat(-deviation, deviation); + direction[1] += GetRandomFloat(-deviation, deviation); + direction[2] += GetRandomFloat(-deviation, deviation); + } + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + switch (attackData.ProjectileType) + { + case SF2BossProjectileType_Fireball: + { + SF2_ProjectileFireball.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], data.FireballExplodeSound, attackData.FireballTrail, attackWaiters); + } + case SF2BossProjectileType_Iceball: + { + SF2_ProjectileIceball.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], data.FireballExplodeSound, attackData.IceballTrail, attackData.IceballSlowdownDuration[difficulty], attackData.IceballSlowdownPercent[difficulty], data.IceballSlowSound, attackWaiters); + } + case SF2BossProjectileType_Rocket: + { + SF2_ProjectileRocket.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackData.CritProjectiles[difficulty], data.RocketTrail, data.RocketExplodeParticle, data.RocketExplodeSound, attackData.RocketModel, attackWaiters); + } + case SF2BossProjectileType_SentryRocket: + { + SF2_ProjectileSentryRocket.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackData.CritProjectiles[difficulty], attackWaiters); + } + case SF2BossProjectileType_Mangler: + { + SF2_ProjectileCowMangler.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackWaiters); + } + case SF2BossProjectileType_Grenade: + { + SF2_ProjectileGrenade.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.ProjectileRadius[difficulty], attackData.CritProjectiles[difficulty], "pipebombtrail_blue", ROCKET_EXPLODE_PARTICLE, ROCKET_IMPACT, "models/weapons/w_models/w_grenade_grenadelauncher.mdl", attackWaiters); + } + case SF2BossProjectileType_Arrow: + { + SF2_ProjectileArrow.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.CritProjectiles[difficulty], "pipebombtrail_blue", "weapons/fx/rics/arrow_impact_flesh2.wav", "models/weapons/w_models/w_arrow.mdl", attackWaiters); + } + case SF2BossProjectileType_Baseball: + { + SF2_ProjectileBaseball.Create(actor, effectPos, angle, attackData.ProjectileSpeed[difficulty], attackData.ProjectileDamage[difficulty], + attackData.CritProjectiles[difficulty], "models/weapons/w_models/w_baseball.mdl", attackWaiters); + } + } + } +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Projectile action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + FireProjectile(actor, action.GetAttackName()); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp new file mode 100644 index 00000000..55c32814 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp @@ -0,0 +1,640 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_Tongue < NextBotAction +{ + public SF2_ChaserAttackAction_Tongue(int attackIndex, const char[] attackName, float fireDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackTongue"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnCommandString, OnCommandString); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextFireTime") + .DefineEntityField("m_TongueEntity") + .DefineEntityField("m_TongueEntityEnd") + .DefineEntityField("m_TongueEntityTrail") + .DefineBoolField("m_CaughtTarget") + .DefineEntityField("m_LatchedTarget") + .DefineFloatField("m_TongueTimer") + .DefineBoolField("m_InPullAnimation") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_Tongue action = view_as(g_Factory.Create()); + + action.NextFireTime = fireDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnPlayerJumpPFwd.AddFunction(null, OnJump); + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextFireTime + { + public get() + { + return this.GetDataFloat("m_NextFireTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextFireTime", value); + } + } + + property int TongueEntity + { + public get() + { + return this.GetDataEnt("m_TongueEntity"); + } + + public set(int value) + { + this.SetDataEnt("m_TongueEntity", value); + } + } + + property int TongueEntityEnd + { + public get() + { + return this.GetDataEnt("m_TongueEntityEnd"); + } + + public set(int value) + { + this.SetDataEnt("m_TongueEntityEnd", value); + } + } + + property int TongueEntityTrail + { + public get() + { + return this.GetDataEnt("m_TongueEntityTrail"); + } + + public set(int value) + { + this.SetDataEnt("m_TongueEntityTrail", value); + } + } + + property bool CaughtTarget + { + public get() + { + return this.GetData("m_CaughtTarget") != 0; + } + + public set(bool value) + { + this.SetData("m_CaughtTarget", value); + } + } + + property SF2_BasePlayer LatchedTarget + { + public get() + { + return SF2_BasePlayer(this.GetDataEnt("m_LatchedTarget")); + } + + public set(SF2_BasePlayer value) + { + this.SetDataEnt("m_LatchedTarget", value.index); + } + } + + property float TongueTime + { + public get() + { + return this.GetDataFloat("m_TongueTimer"); + } + + public set(float value) + { + this.SetDataFloat("m_TongueTimer", value); + } + } + + property bool InPullAnimation + { + public get() + { + return this.GetData("m_InPullAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_InPullAnimation", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type != SF2BossAttackType_Tongue) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_Tongue(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static void OnJump(SF2_BasePlayer client) +{ + if (client.IsEliminated || IsRoundEnding() || IsRoundInWarmup() || client.HasEscaped) + { + return; + } + + if (client.IsLatched) + { + client.LatchCount--; + if (client.LatchCount <= 0) + { + client.IsLatched = false; + client.LatchCount = 0; + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + if (client.Latcher != npc.Index) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + + chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString("break tongue"); + } + } + } +} + +static int OnStart(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Continue(); +} + +static int Update(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION || action.IsSuspended) + { + return action.Done("No longer firing a tongue"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + if (action.CaughtTarget) + { + SF2_ChaserAttackAction parent = view_as(action.Parent); + parent.EndTime += GetGameFrameTime(); + if (!action.LatchedTarget.IsValid) + { + return action.Done("Nobody is in my tongue"); + } + } + + SF2NPC_Chaser controller = actor.Controller; + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (originalData.IsPvEBoss) + { + attackEliminated = true; + } + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + + if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) + { + FireTongue(actor, action); + + action.NextFireTime = -1.0; + } + + if (action.NextFireTime <= -1.0 && (!IsValidEntity(action.TongueEntity) || !IsValidEntity(action.TongueEntityEnd))) + { + return action.Done("Tongue down"); + } + + if (IsValidEntity(action.TongueEntity) && IsValidEntity(action.TongueEntityEnd) && IsValidEntity(action.TongueEntityTrail) && !action.CaughtTarget) + { + CBaseEntity tongueEnd = CBaseEntity(action.TongueEntityEnd); + float tonguePos[3]; + tongueEnd.GetAbsOrigin(tonguePos); + ArrayList players = new ArrayList(); + TR_EnumerateEntitiesSphere(tonguePos, 75.0, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, players); + + for (int i = 0; i < players.Length; i++) + { + SF2_BasePlayer player = players.Get(i); + + if (!attackEliminated && player.IsEliminated) + { + continue; + } + + float worldSpace[3]; + player.WorldSpaceCenter(worldSpace); + TR_TraceRayFilter(tonguePos, worldSpace, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, tongueEnd.index); + + if (TR_DidHit() && TR_GetEntityIndex() != player.index) + { + continue; + } + + if (!attackData.TongueCanEscape[difficulty]) + { + player.Stun(1.0, 1.0, TF_STUNFLAG_NOSOUNDOREFFECT | TF_STUNFLAG_BONKSTUCK); + } + else + { + player.Stun(2.0, 1.0, TF_STUNFLAG_SLOWDOWN); + } + player.TakeDamage(_, actor.index, actor.index, attackData.Damage[difficulty], DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE); + + tongueEnd.AcceptInput("ClearParent"); + tongueEnd.Teleport(worldSpace, NULL_VECTOR, NULL_VECTOR); + SetVariantString("!activator"); + tongueEnd.AcceptInput("SetParent", player.index); + RemoveEntity(action.TongueEntityTrail); + action.TongueTime = 0.5 + gameTime; + action.LatchedTarget = player; + action.CaughtTarget = true; + attackData.TongueTiedSound.EmitSound(_, tongueEnd.index); + attackData.TongueHitSound.EmitSound(_, tongueEnd.index); + player.IsLatched = true; + player.LatchCount = GetRandomInt(6, 10); + if (!attackData.TongueCanEscape[difficulty]) + { + player.LatchCount = 999999; // I'm lazy + } + player.Latcher = controller.Index; + if (!player.HasHint(PlayerHint_Trap)) + { + player.ShowHint(PlayerHint_Trap); + } + actor.MyNextBotPointer().GetIntentionInterface().OnCommandString("latch tongue"); + break; + } + + delete players; + } + + TongueThink(action, actor); + + return action.Continue(); +} + +static void TongueThink(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor) +{ + if (!IsValidEntity(action.TongueEntity) && !IsValidEntity(action.TongueEntityEnd)) + { + return; + } + + if (!action.CaughtTarget) + { + return; + } + + float gameTime = GetGameTime(); + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + SF2_BasePlayer player = action.LatchedTarget; + if (!player.IsValid || !player.IsAlive) + { + Unlatch(action); + return; + } + + CBaseEntity tongueStart = CBaseEntity(action.TongueEntity); + float tonguePos[3], worldSpace[3]; + tongueStart.GetAbsOrigin(tonguePos); + player.WorldSpaceCenter(worldSpace); + + TR_TraceRayFilter(tonguePos, worldSpace, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, tongueStart.index); + + if (TR_DidHit() && TR_GetEntityIndex() != player.index) + { + Unlatch(action, true); + return; + } + + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + float myPos[3], targetPos[3]; + actor.GetAbsOrigin(myPos); + player.GetAbsOrigin(targetPos); + + if (action.TongueTime > -1.0 && action.TongueTime <= gameTime) + { + SF2_BasePlayer latched = action.LatchedTarget; + if (!attackData.TongueCanEscape[difficulty]) + { + latched.Stun(1.0, 1.0, TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); + } + else + { + latched.Stun(2.0, 1.0, TF_STUNFLAG_SLOWDOWN); + } + action.TongueTime = gameTime + 0.5; + + float velocity[3]; + SubtractVectors(myPos, targetPos, velocity); + velocity[2] = 0.0; + + ScaleVector(velocity, attackData.TonguePullScale[difficulty]); + player.SetPropVector(Prop_Data, "m_vecBaseVelocity", velocity); + + player.TakeDamage(_, actor.index, actor.index, attackData.Damage[difficulty], DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE); + } + + loco.FaceTowards(targetPos); +} + +static void Unlatch(SF2_ChaserAttackAction_Tongue action, bool removeSlow = false) +{ + if (IsValidEntity(action.TongueEntity)) + { + RemoveEntity(action.TongueEntity); + } + + if (IsValidEntity(action.TongueEntityEnd)) + { + RemoveEntity(action.TongueEntityEnd); + } + + if (action.LatchedTarget.IsValid) + { + action.LatchedTarget.IsLatched = false; + action.LatchedTarget.LatchCount = 0; + if (removeSlow) + { + action.LatchedTarget.ChangeCondition(TFCond_Dazed, true); + } + } + + action.LatchedTarget = SF2_BasePlayer(-1); +} + +static void OnEnd(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor) +{ + Unlatch(action); +} + +static void FireTongue(SF2_ChaserEntity actor, SF2_ChaserAttackAction_Tongue action) +{ + CBaseEntity target = actor.Target; + if (!target.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + float effectPos[3], myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + effectPos = attackData.TongueOffset; + VectorTransform(effectPos, myPos, myAng, effectPos); + + CBaseEntity tongue = CBaseEntity(CreateEntityByName("move_rope")); + + /*if (attackData.TongueAttachment[0] == '\0') + { + tongue.Teleport(effectPos, myAng, NULL_VECTOR); + SetVariantString("!activator"); + tongue.AcceptInput("SetParent", actor.index); + }*/ + + tongue.Teleport(effectPos, myAng, NULL_VECTOR); + SetVariantString("!activator"); + tongue.AcceptInput("SetParent", actor.index); + + char buffer[PLATFORM_MAX_PATH]; + tongue.KeyValue("Slack", "50"); + tongue.KeyValue("RopeMaterial", attackData.TongueMaterial); + FormatEx(buffer, sizeof(buffer), "rope_%i", controller.Index); + tongue.KeyValue("NextKey", buffer); + + CBaseEntity tongueEnd = CBaseEntity(CreateEntityByName("move_rope")); + tongueEnd.KeyValue("targetname", buffer); + tongueEnd.KeyValue("Slack", "50"); + tongueEnd.KeyValue("RopeMaterial", attackData.TongueMaterial); + tongueEnd.Teleport(effectPos, myAng, NULL_VECTOR); + tongueEnd.SetMoveType(MOVETYPE_FLY); + + tongue.Spawn(); + tongueEnd.Spawn(); + tongue.Activate(); + tongueEnd.Activate(); + + action.TongueEntity = tongue.index; + action.TongueEntityEnd = tongueEnd.index; + + float velocity[3], shootAng[3], targetPos[3]; + target.WorldSpaceCenter(targetPos); + SubtractVectors(targetPos, effectPos, shootAng); + NormalizeVector(shootAng, shootAng); + GetVectorAngles(shootAng, shootAng); + GetAngleVectors(shootAng, velocity, NULL_VECTOR, NULL_VECTOR); + ScaleVector(velocity, attackData.TongueSpeed[difficulty]); + + attackData.TongueLaunchSound.EmitSound(_, actor.index); + + CBaseEntity trail = CBaseEntity(CreateEntityByName("tf_projectile_grapplinghook")); + trail.SetModel("models/roller.mdl"); + trail.KeyValue("solid", "0"); + trail.Spawn(); + trail.Activate(); + trail.SetProp(Prop_Send, "m_usSolidFlags", 22); + trail.SetProp(Prop_Send, "m_CollisionGroup", 1); + trail.Teleport(effectPos, shootAng, velocity); + SetVariantString("!activator"); + tongueEnd.AcceptInput("SetParent", trail.index); + SetEntityOwner(trail.index, tongueEnd.index); + trail.SetRenderMode(RENDER_TRANSCOLOR); + trail.SetRenderColor(0, 0, 0, 0); + CreateTimer(0.1, Timer_Think, EntIndexToEntRef(trail.index), TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); + action.TongueEntityTrail = trail.index; + SetEntDataFloat(trail.index, GetEntSendPropOffs(trail.index, "m_iProjectileType") + 32, GetGameTime() + 9999.9); + + SetEntityOwner(trail.index, tongue.index); +} + +static Action Timer_Think(Handle timer, any ref) +{ + int entity = EntRefToEntIndex(ref); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } + CBaseEntity trail = CBaseEntity(entity); + int owner = trail.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + if (!IsValidEntity(owner)) + { + RemoveEntity(trail.index); + return Plugin_Stop; + } + float pos[3], mins[3] = {-8.0, ... }, maxs[3] = {8.0, ... }; + trail.GetAbsOrigin(pos); + TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, trail.index); + + if (TR_GetEntityIndex() == 0) + { + if (IsValidEntity(owner)) + { + RemoveEntity(owner); + } + RemoveEntity(trail.index); + return Plugin_Stop; + } + + return Plugin_Continue; +} + +static int OnCommandString(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, const char[] command) +{ + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + if (strcmp(command, "break tongue") == 0) + { + Unlatch(action, true); + if (attackData.Animations.GetAnimation("break", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return action.TrySuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle), RESULT_CRITICAL); + } + } + } + + if (strcmp(command, "latch tongue") == 0 && !action.InPullAnimation) + { + if (attackData.Animations.GetAnimation("pull", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + actor.ResetSequence(sequence); + actor.SetPropFloat(Prop_Send, "m_flCycle", cycle); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + } + } + action.InPullAnimation = true; + } + + return action.TryContinue(); +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + FireTongue(actor, action); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/beatbox_freeze.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/beatbox_freeze.sp new file mode 100644 index 00000000..8ec201b0 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/beatbox_freeze.sp @@ -0,0 +1,88 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserBeatBoxFreezeAction < NextBotAction +{ + public SF2_ChaserBeatBoxFreezeAction(bool wasMoving) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_BeatboxFreeze"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_EndDelay") + .DefineBoolField("m_WasMoving") + .EndDataMapDesc(); + } + SF2_ChaserBeatBoxFreezeAction action = view_as(g_Factory.Create()); + action.WasMoving = wasMoving; + return action; + } + + property float EndDelay + { + public get() + { + return this.GetDataFloat("m_EndDelay"); + } + + public set(float value) + { + this.SetDataFloat("m_EndDelay", value); + } + } + + property bool WasMoving + { + public get() + { + return this.GetData("m_WasMoving") != 0; + } + + public set(bool value) + { + this.SetData("m_WasMoving", value); + } + } +} + +static int OnStart(SF2_ChaserBeatBoxFreezeAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + INextBot bot = actor.MyNextBotPointer(); + actor.IsAttemptingToMove = false; + if (action.WasMoving) + { + actor.UpdateMovementAnimation(); + } + + bot.GetLocomotionInterface().Stop(); + + action.EndDelay = GetGameTime() + 0.5; + + return action.Continue(); +} + +static int Update(SF2_ChaserBeatBoxFreezeAction action, SF2_ChaserEntity actor, float interval) +{ + float gameTime = GetGameTime(); + if (IsBeatBoxBeating()) + { + action.EndDelay = gameTime + 0.5; + } + + if (action.EndDelay < gameTime) + { + return action.Done("I'm done being frozen from Beatbox."); + } + + return action.Continue(); +} + +static void OnEnd(SF2_ChaserBeatBoxFreezeAction action, SF2_ChaserEntity actor) +{ + actor.IsAttemptingToMove = action.WasMoving; + actor.UpdateMovementAnimation(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp new file mode 100644 index 00000000..508154ff --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -0,0 +1,208 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserChaseAction < NextBotAction +{ + public SF2_ChaserChaseAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Chase"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + return SF2_ChaserAttackLayerAction(); +} + +static int OnStart(SF2_ChaserChaseAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.PerformVoice(SF2BossSound_ChaseInitial); + actor.MaintainTarget = true; + + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + CBaseEntity target = actor.Target; + if (!target.IsValid()) + { + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "My target is no longer valid!"); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + + if (data.AlertOnChaseInfo.OnChangeState[difficulty]) + { + actor.ForceChaseOtherBosses(); + } + + actor.AlertWithBoss = false; + + return action.Continue(); +} + +static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + if (IsBeatBoxBeating(2) && actor.State != STATE_ATTACK && !actor.IsInChaseInitial) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + SF2NPC_Chaser controller = actor.Controller; + + if (!controller.IsValid()) + { + return action.Continue(); + } + + CBaseEntity target = actor.Target; + float gameTime = GetGameTime(); + int difficulty = controller.Difficulty; + INextBot bot = actor.MyNextBotPointer(); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + attackEliminated = true; + } + + if (target.IsValid()) + { + actor.ProcessCloak(target); + + if (IsValidClient(target.index)) + { + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.ShouldBeForceChased(controller)) + { + player.SetForceChaseState(controller, false); + } + actor.SetAutoChaseCount(player, 0); + } + + if (!actor.IsAttacking) + { + if (actor.CurrentChaseDuration <= 0.0) + { + actor.State = STATE_ALERT; + return action.ChangeTo(SF2_ChaserAlertAction(_, data.AlertRunOnHearSound[difficulty]), "Oh he got away..."); + } + } + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && player.HasEscaped) + { + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "Our target escaped, that is no good!"); + } + + float maxRange = data.ChaseDurationAddMaxRange[difficulty]; + if (maxRange > 0.0 && player.IsValid && player.CanSeeSlender(controller.Index, false, _, !attackEliminated)) + { + float distanceRatio = bot.GetRangeTo(player.index) / maxRange; + if (distanceRatio < 1.0) + { + float durationTimeAddMin = data.ChaseDurationAddVisibleMin[difficulty]; + float durationTimeAddMax = data.ChaseDurationAddVisibleMax[difficulty]; + float durationAdd = durationTimeAddMin + ((durationTimeAddMax - durationTimeAddMin) * distanceRatio); + + actor.CurrentChaseDuration += durationAdd * GetGameFrameTime(); + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + + actor.RegisterProjectiles(); + } + else + { + actor.CurrentChaseDuration -= GetGameFrameTime(); + } + } + else + { + if (!actor.IsAttacking) + { + if (actor.KillTarget.IsValid()) + { + return action.SuspendFor(SF2_ChaserTauntKillAction(), "Let's taunt the kill victim cause funny."); + } + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "No valid target? No problem!"); + } + } + + if (data.ChaseOnLookData.Enabled[difficulty] && controller.ChaseOnLookTargets.FindValue(target.index) != -1) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + + if (gameTime >= actor.NextVoiceTime && !actor.IsAttacking) + { + actor.PerformVoice(SF2BossSound_Chasing); + } + + #if defined DEBUG + SendDebugMessageToPlayer(target.index, DEBUG_BOSS_CHASE, 1, "actor[%i].CurrentChaseDuration: %f", actor.index, actor.CurrentChaseDuration); + #endif + + return action.Continue(); +} + +static void OnEnd(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + if (!actor.Controller.IsValid()) + { + return; + } + actor.EndCloak(); + PathFollower path = actor.Controller.Path; + float gameTime = GetGameTime(); + if (path.IsValid()) + { + path.Invalidate(); + } + actor.MaintainTarget = false; + int difficulty = actor.Controller.Difficulty; + if (actor.FollowedCompanionChase) + { + SF2ChaserBossProfileData data; + data = actor.Controller.GetProfileData(); + actor.FollowCooldownChase = GetGameTime() + data.AlertOnChaseInfo.FollowCooldown[difficulty]; + } + + SF2ChaserBossProfileData chaserData; + chaserData = actor.Controller.GetProfileData(); + float cooldown = chaserData.AutoChaseAfterChaseCooldown[difficulty]; + if (cooldown > 0.0) + { + float nextTime = gameTime + cooldown; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (nextTime > actor.GetAutoChaseCooldown(client)) + { + actor.SetAutoChaseCooldown(client, gameTime + cooldown); + } + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp new file mode 100644 index 00000000..2660e801 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp @@ -0,0 +1,86 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserChaseInitialAction < NextBotAction +{ + public SF2_ChaserChaseInitialAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_ChaseInitial"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + //g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } + + public static bool IsPossible(SF2_ChaserEntity actor) + { + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + if (!data.AnimationData.HasAnimationSection(g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial])) + { + return false; + } + + return true; + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor) +{ + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + actor.IsInChaseInitial = true; + + if (data.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int Update(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + if (action.ActiveChild == NULL_ACTION) + { + return action.Done("Sequence action finished"); + } + + ILocomotion loco = actor.MyNextBotPointer().GetLocomotionInterface(); + loco.Stop(); + + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_ChaserChaseInitialAction action, SF2_ChaserEntity actor) +{ + actor.IsInChaseInitial = false; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp new file mode 100644 index 00000000..90f8b6e9 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp @@ -0,0 +1,96 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserChaseLayerAction < NextBotAction +{ + public SF2_ChaserChaseLayerAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_ChaseLayer"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + } + return view_as(g_Factory.Create()); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor) +{ + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.MovementType = SF2NPCMoveType_Run; + actor.UpdateMovementAnimation(); + + if (SF2_ChaserChaseInitialAction.IsPossible(actor)) + { + return action.SuspendFor(SF2_ChaserChaseInitialAction()); + } + return action.Continue(); +} + +static int Update(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, float interval) +{ + SF2NPC_Chaser controller = actor.Controller; + int interrputConditions = actor.InterruptConditions; + INextBot bot = actor.MyNextBotPointer(); + CBaseEntity target = actor.Target; + + PathFollower path = controller.Path; + ILocomotion loco = bot.GetLocomotionInterface(); + + bool tooClose = target.IsValid() && + actor.GetIsVisible(SF2_BasePlayer(target.index)) && + bot.IsRangeLessThan(target.index, 8.0); + + if (tooClose && path.IsValid()) + { + path.Invalidate(); + } + else if (target.IsValid()) + { + float pos[3]; + target.GetAbsOrigin(pos); + + if (!bot.IsRangeLessThanEx(pos, 8.0)) + { + if ((interrputConditions & COND_NEWENEMY) != 0 || path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) + { + path.ComputeToPos(bot, pos); + } + } + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + return action.Continue(); +} + +static void OnSuspend(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.MyNextBotPointer().GetLocomotionInterface().Stop(); + actor.IsAttemptingToMove = false; +} + +static int OnResume(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + actor.MovementType = SF2NPCMoveType_Run; + actor.UpdateMovementAnimation(); + return action.Continue(); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp new file mode 100644 index 00000000..18a901a3 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp @@ -0,0 +1,160 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserDeathAction < NextBotAction +{ + public SF2_ChaserDeathAction(CBaseEntity attacker = view_as(-1)) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Death"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_Attacker") + .EndDataMapDesc(); + } + SF2_ChaserDeathAction action = view_as(g_Factory.Create()); + + action.Attacker = attacker.index; + return action; + } + + property int Attacker + { + public get() + { + return this.GetDataEnt("m_Attacker"); + } + + public set(int value) + { + this.SetDataEnt("m_Attacker", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int difficulty = controller.Difficulty; + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + + actor.IsAttemptingToMove = false; + loco.Stop(); + + actor.EndCloak(); + + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + actor.PerformVoice(SF2BossSound_Death); + + actor.State = STATE_DEATH; + + if (originalData.IsPvEBoss) + { + KillPvEBoss(actor.index); + } + + if (originalData.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Death], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Death], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserDeathAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + + if (data.DeathData.KeyDrop) + { + if (SF_IsBoxingMap() && data.BoxingBoss && !g_SlenderBoxingBossIsKilled[controller.Index] && !view_as(controller).GetProfileData().IsPvEBoss) + { + g_SlenderBoxingBossKilled++; + if ((g_SlenderBoxingBossKilled == g_SlenderBoxingBossCount)) + { + NPC_DropKey(controller.Index, data.DeathData.KeyModel, data.DeathData.KeyTrigger); + } + g_SlenderBoxingBossIsKilled[controller.Index] = true; + } + else + { + NPC_DropKey(controller.Index, data.DeathData.KeyModel, data.DeathData.KeyTrigger); + } + } + + actor.DropItem(true); + + actor.RemoveAllGestures(); + CBaseNPC_RemoveAllLayers(actor.index); + + if (data.DeathData.AddHealthPerDeath[difficulty] > 0.0) + { + controller.SetDeathHealth(difficulty, controller.GetDeathHealth(difficulty) + data.DeathData.AddHealthPerDeath[difficulty]); + } + + return action.Continue(); +} + +static int Update(SF2_ChaserDeathAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + return action.Continue(); + } + + return action.Done("I am actually fully dead now"); +} + +static void OnEnd(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + + if (!controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + if (data.DeathData.RemoveOnDeath) + { + controller.Remove(); + } + else if (data.DeathData.DisappearOnDeath) + { + controller.UnSpawn(); + } + else if (data.DeathData.RagdollOnDeath) + { + actor.AcceptInput("BecomeRagdoll"); + } +} + +static void OnAnimationEvent(SF2_ChaserDeathAction action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + actor.CastAnimEvent(event); + actor.CastAnimEvent(event, true); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp new file mode 100644 index 00000000..114f5047 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp @@ -0,0 +1,285 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserFleeToHealAction < NextBotAction +{ + public SF2_ChaserFleeToHealAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_FleeToHeal"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnMoveToSuccess, OnMoveToSuccess); + g_Factory.SetEventCallback(EventResponderType_OnMoveToFailure, OnMoveToFailure); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_HealTime") + .DefineFloatField("m_HealDuration") + .DefineBoolField("m_HasPath") + .DefineBoolField("m_IsHealing") + .DefineBoolField("m_PlayedAnimation") + .DefineFloatField("m_HealAnimationDuration") + .DefineBoolField("m_PlayedSound") + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } + + property float HealTime + { + public get() + { + return this.GetDataFloat("m_HealTime"); + } + + public set(float value) + { + this.SetDataFloat("m_HealTime", value); + } + } + + property float HealDuration + { + public get() + { + return this.GetDataFloat("m_HealDuration"); + } + + public set(float value) + { + this.SetDataFloat("m_HealDuration", value); + } + } + + property bool HasPath + { + public get() + { + return this.GetData("m_HasPath") != 0; + } + + public set(bool value) + { + this.SetData("m_HasPath", value); + } + } + + property bool IsHealing + { + public get() + { + return this.GetData("m_IsHealing") != 0; + } + + public set(bool value) + { + this.SetData("m_IsHealing", value); + } + } + + property bool PlayedAnimation + { + public get() + { + return this.GetData("m_PlayedAnimation") != 0; + } + + public set(bool value) + { + this.SetData("m_PlayedAnimation", value); + } + } + + property float HealAnimationDuration + { + public get() + { + return this.GetDataFloat("m_HealAnimationDuration"); + } + + public set(float value) + { + this.SetDataFloat("m_HealAnimationDuration", value); + } + } + + property bool PlayedSound + { + public get() + { + return this.GetData("m_PlayedSound") != 0; + } + + public set(bool value) + { + this.SetData("m_PlayedSound", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + action.HealDuration = rageInfo.HealDuration; + action.HealTime = rageInfo.HealDelay; + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = controller.Difficulty; + action.IsHealing = false; + action.HasPath = false; + if (rageInfo.Animations.GetAnimation("start", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int Update(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + return action.Continue(); + } + + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + PathFollower path = controller.Path; + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + + if (!action.IsHealing && !action.HasPath) + { + actor.IsRunningAway = true; + if (rageInfo.HealCloak) + { + actor.StartCloak(); + } + CNavArea area = actor.GetLastKnownArea(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, GetRandomFloat(rageInfo.FleeRange[0], rageInfo.FleeRange[1])); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + float navPos[3], myPos[3]; + collector.Get(i).GetCenter(navPos); + actor.GetAbsOrigin(myPos); + if (GetVectorSquareMagnitude(navPos, myPos) <= Pow(256.0, 2.0)) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + if (validAreaCount > 0) + { + float wanderPos[3]; + CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); + + if (wanderArea != NULL_AREA) + { + wanderArea.GetCenter(wanderPos); + + path.ComputeToPos(bot, wanderPos); + + action.HasPath = true; + } + } + + delete collector; + delete areaArray; + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + if (action.IsHealing) + { + if (!action.PlayedAnimation) + { + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + if (rageInfo.Animations.GetAnimation("healing", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + action.HealAnimationDuration = duration; + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + action.HealAnimationDuration = actor.SequenceDuration(sequence) / rate; + action.HealAnimationDuration *= (1.0 - cycle); + actor.ResetSequence(sequence); + actor.SetPropFloat(Prop_Send, "m_flCycle", cycle); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + } + } + action.HealAnimationDuration += gameTime; + action.PlayedAnimation = true; + } + + action.HealTime -= GetGameFrameTime(); + if (action.HealTime <= 0.0 && action.HealDuration > 0.0) + { + if (!action.PlayedSound) + { + actor.PerformVoiceEx(_, _, rageInfo.HealSounds, true); + action.PlayedSound = true; + } + float amount = actor.MaxHealth * rageInfo.HealAmount; + float increase = LerpFloats(0.0, amount, GetGameFrameTime() * (1.0 / rageInfo.HealDuration)); + actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(increase + actor.GetProp(Prop_Data, "m_iHealth"))); + if (float(actor.GetProp(Prop_Data, "m_iHealth")) > actor.MaxHealth) + { + actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(actor.MaxHealth)); + } + action.HealDuration -= GetGameFrameTime(); + } + + if (action.HealAnimationDuration < gameTime) + { + actor.IsSelfHealing = false; + return action.Done(); + } + } + + return action.Continue(); +} + +static void OnMoveToSuccess(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, Path path) +{ + actor.EndCloak(); + actor.IsRunningAway = false; + action.IsHealing = true; + actor.IsSelfHealing = true; +} + +static void OnMoveToFailure(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, Path path, MoveToFailureType reason) +{ + actor.EndCloak(); + actor.IsRunningAway = false; + action.IsHealing = true; + actor.IsSelfHealing = true; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp new file mode 100644 index 00000000..2ad18b44 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp @@ -0,0 +1,365 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserIdleAction < NextBotAction +{ + public SF2_ChaserIdleAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Idle"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_NextWanderTime") + .DefineFloatField("m_NextTurnTime") + .DefineVectorField("m_LookPos") + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } + + property float NextWanderTime + { + public get() + { + return this.GetDataFloat("m_NextWanderTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextWanderTime", value); + } + } + + property float NextTurnTime + { + public get() + { + return this.GetDataFloat("m_NextTurnTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextTurnTime", value); + } + } + + public void GetLookPosition(float pos[3]) + { + this.GetDataVector("m_LookPos", pos); + } + + public void SetLookPosition(const float pos[3]) + { + this.SetDataVector("m_LookPos", pos); + } +} + +static int OnStart(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + INextBot bot = actor.MyNextBotPointer(); + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + + action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + action.NextWanderTime = gameTime + GetRandomFloat(data.WanderEnterTimeMin[difficulty], data.WanderEnterTimeMax[difficulty]); + + bot.GetLocomotionInterface().Stop(); + + actor.MovementType = SF2NPCMoveType_Walk; + actor.IsAttemptingToMove = false; + actor.UpdateMovementAnimation(); + + for (int i = 0; i < MAX_NPCTELEPORTER; i++) + { + if (actor.Controller.GetTeleporter(i) != INVALID_ENT_REFERENCE) + { + actor.Controller.SetTeleporter(i, INVALID_ENT_REFERENCE); + } + } + + actor.SmellCooldown = gameTime + GetRandomFloat(data.SmellData.CooldownMin[difficulty], data.SmellData.CooldownMax[difficulty]); + + g_SlenderTimeUntilKill[controller.Index] = GetGameTime() + NPCGetIdleLifetime(controller.Index, difficulty); + + return action.Continue(); +} + +static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float interval) +{ + if (IsBeatBoxBeating(2)) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = actor.Controller; + if (!controller.IsValid()) + { + return action.Continue(); + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + int interruptConditions = actor.InterruptConditions; + CBaseEntity target = actor.Target; + + PathFollower path = controller.Path; + + if (data.ChaseOnLookData.Enabled[difficulty] && controller.ChaseOnLookTargets.Length > 0) + { + SF2_BasePlayer lookTarget = SF2_BasePlayer(controller.ChaseOnLookTargets.Get(0)); + if (lookTarget.IsValid && !lookTarget.IsEliminated && lookTarget.IsAlive && !lookTarget.IsInGhostMode && !lookTarget.HasEscaped) + { + actor.Target = lookTarget; + actor.OldTarget = lookTarget; + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone has looked at me, GET THEM!"); + } + } + + if (target.IsValid()) + { + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly || + view_as(controller).GetProfileData().IsPvEBoss) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "We must endless chase, GET THEM!"); + } + + if ((interruptConditions & COND_ENEMYRECHASE) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Wait hold up, someone needs rechasing. GET THEM!"); + } + else if ((interruptConditions & COND_ENEMYNEAR) != 0) + { + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "Someone is too close, get them!"); + } + else if ((interruptConditions & COND_SAWENEMY) != 0) + { + float pos[3]; + target.GetAbsOrigin(pos); + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "I saw someone!"); + } + } + + SF2_BasePlayer alertTarget = actor.AlertTriggerTarget; + if ((interruptConditions & COND_ALERT_TRIGGER) != 0 && alertTarget.IsValid) + { + float pos[3]; + actor.GetAlertTriggerPosition(alertTarget, pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Someone made noise, let's go!"); + } + + if ((interruptConditions & COND_DEBUG) != 0) + { + float pos[3]; + actor.GetForceWanderPosition(pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnWander[difficulty]), "An admin told me to go here!"); + } + + if (actor.FollowedCompanionAlert || actor.AlertWithBoss) + { + float pos[3]; + actor.GetForceWanderPosition(pos); + if (actor.AlertWithBoss) + { + actor.AlertWithBoss = false; + } + if (!IsNullVector(pos)) + { + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnWander[difficulty]), "One of my mates found something!"); + } + } + + if (data.SmellData.Enabled[difficulty] && actor.SmellCooldown <= gameTime && actor.SmellPlayerList != null) + { + if (actor.SmellPlayerList.Length >= data.SmellData.PlayerCount[difficulty]) + { + actor.State = STATE_ALERT; + path.Invalidate(); + return action.ChangeTo(SF2_ChaserSmellAction(), "That smelly smell, it is, smelly."); + } + } + + if (actor.DebugShouldGoToPos) + { + float debugPos[3]; + actor.GetForceWanderPosition(debugPos); + path.Invalidate(); + float min = data.WanderTimeMin[difficulty]; + float max = data.WanderTimeMax[difficulty]; + + action.NextWanderTime = gameTime + GetRandomFloat(min, max); + path.ComputeToPos(bot, debugPos); + actor.DebugShouldGoToPos = false; + } + + if ((SF_SpecialRound(SPECIALROUND_BEACON) || g_RenevantBeaconEffect) && !actor.WasInBacon) + { + SF2_BasePlayer player = actor.GetClosestPlayer(); + if (player.IsValid) + { + float pos[3]; + player.GetAbsOrigin(pos); + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + actor.WasInBacon = true; + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "What is this smell of bacon?"); + } + } + + if (data.CanWander[difficulty]) + { + if (gameTime >= action.NextWanderTime && GetRandomFloat(0.0, 1.0) <= 0.25) + { + float min = data.WanderTimeMin[difficulty]; + float max = data.WanderTimeMax[difficulty]; + + action.NextWanderTime = gameTime + GetRandomFloat(min, max); + + float rangeMin = data.WanderRangeMin[difficulty]; + float rangeMax = data.WanderRangeMax[difficulty]; + float range = GetRandomFloat(rangeMin, rangeMax); + + CNavArea area = actor.GetLastKnownArea(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, range); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) + { + continue; + } + if (collector.Get(i).GetCostSoFar() < rangeMin) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + if (validAreaCount > 0) + { + float wanderPos[3]; + CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); + + if (wanderArea != NULL_AREA) + { + wanderArea.GetCenter(wanderPos); + + path.ComputeToPos(bot, wanderPos); + } + } + + delete collector; + delete areaArray; + } + } + + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + + if (actor.IsAttemptingToMove) + { + action.NextTurnTime = -1.0; + } + else + { + float lookPos[3]; + action.GetLookPosition(lookPos); + loco.FaceTowards(lookPos); + if (action.NextTurnTime <= 0.0) + { + action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + } + } + + if (data.IdleData.TurnEnabled[difficulty] && action.NextTurnTime > 0.0 && action.NextTurnTime <= gameTime) + { + float myPos[3], myAng[3]; + actor.GetAbsOrigin(myPos); + actor.GetAbsAngles(myAng); + myAng[1] += GetRandomFloat(-data.IdleData.TurnAngle[difficulty], data.IdleData.TurnAngle[difficulty]); + float lookAt[3]; + lookAt[0] = 50.0; + VectorTransform(lookAt, myPos, myAng, lookAt); + action.SetLookPosition(lookAt); + action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + } + + if (gameTime >= actor.NextVoiceTime) + { + actor.PerformVoice(SF2BossSound_Idle); + } + + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp new file mode 100644 index 00000000..b567d89e --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -0,0 +1,569 @@ +#pragma semicolon 1 +// The purpose of this action is to be the master action + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserMainAction < NextBotAction +{ + public SF2_ChaserMainAction() + { + return view_as(g_Factory.Create()); + } + + public static NextBotActionFactory GetFactory() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Main"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetEventCallback(EventResponderType_OnInjured, OnInjured); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.SetEventCallback(EventResponderType_OnContact, OnContact); + g_Factory.SetEventCallback(EventResponderType_OnKilled, OnKilled); + g_Factory.SetEventCallback(EventResponderType_OnLeaveGround, OnLeaveGround); + g_Factory.SetEventCallback(EventResponderType_OnCommandString, OnCommandString); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_LastStuckTime") + .DefineVectorField("m_LastPos") + .DefineVectorField("m_UnstuckPos") + .EndDataMapDesc(); + } + return g_Factory; + } + + property float LastStuckTime + { + public get() + { + return this.GetDataFloat("m_LastStuckTime"); + } + + public set(float value) + { + this.SetDataFloat("m_LastStuckTime", value); + } + } + + public void GetLastPos(float pos[3]) + { + this.GetDataVector("m_LastPos", pos); + } + + public void SetLastPos(const float pos[3]) + { + this.SetDataVector("m_LastPos", pos); + } + + public void GetUnstuckPos(float pos[3]) + { + this.GetDataVector("m_UnstuckPos", pos); + } + + public void SetUnstuckPos(const float pos[3]) + { + this.SetDataVector("m_UnstuckPos", pos); + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + return SF2_ChaserSpawnAction(); +} + +static void OnStart(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + float pos[3]; + actor.MyNextBotPointer().GetLocomotionInterface().GetFeet(pos); + action.SetLastPos(pos); + action.LastStuckTime = GetGameTime(); +} + +static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid() && (controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return action.Done("I'm a faker"); + } + + if (!controller.IsValid()) + { + return action.Continue(); + } + + float gameTime = GetGameTime(); + + int difficulty = controller.Difficulty; + + actor.FollowOtherBossAlert(); + + actor.FollowOtherBossChase(); + + if (actor.State == STATE_ATTACK) + { + actor.DoAttackMiscConditions(actor.GetAttackName()); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (data.FlashlightStun[difficulty] && actor.CanBeStunned() && actor.CanTakeDamage()) + { + bool inFlashlight = false; + float customDamage = 1.0; + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!IsTargetValidForSlender(player, attackEliminated)) + { + continue; + } + + if (!player.UsingFlashlight) + { + continue; + } + + float hisPos[3], myPos[3], eyeAng[3], requiredAng[3]; + player.GetAbsOrigin(hisPos); + actor.GetAbsOrigin(myPos); + + if (GetVectorDistance(myPos, hisPos, true) > Pow(SF2_FLASHLIGHT_LENGTH, 2.0)) + { + continue; + } + + player.GetEyeAngles(eyeAng); + SubtractVectors(hisPos, myPos, requiredAng); + GetVectorAngles(requiredAng, requiredAng); + + if (FloatAbs(AngleDiff(eyeAng[0], requiredAng[0])) <= 35.0 && FloatAbs(AngleDiff(requiredAng[1], eyeAng[1])) < 145.0) + { + continue; + } + + if (!actor.IsLOSClearFromTarget(player)) + { + continue; + } + + Action fwdAction; + Call_StartForward(g_OnBossPreFlashlightDamageFwd); + Call_PushCell(actor); + Call_PushCell(player); + Call_Finish(fwdAction); + if (fwdAction == Plugin_Stop) + { + continue; + } + + inFlashlight = true; + if (!IsClassConfigsValid()) + { + if (player.Class == TFClass_Engineer) + { + customDamage = 2.0; + } + } + else + { + customDamage = g_ClassFlashlightDamageMultiplier[view_as(player.Class)]; + } + break; + } + + if (SF_SpecialRound(SPECIALROUND_NIGHTVISION) || g_NightvisionEnabledConVar.BoolValue) + { + inFlashlight = false; + } + + if (inFlashlight) + { + actor.StunHealth -= data.FlashlightDamage[difficulty] * customDamage; + if (actor.StunHealth <= 0.0) + { + return action.SuspendFor(SF2_ChaserStunnedAction(), "I was stunned by a flashlight"); + } + } + } + + if (originalData.InstantKillRadius > 0.0) + { + if (gameTime >= actor.LastKillTime && !actor.IsKillingSomeone) + { + float worldSpace[3], myPos[3]; + actor.WorldSpaceCenter(worldSpace); + actor.GetAbsOrigin(myPos); + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (view_as(controller).GetProfileData().IsPvEBoss) + { + attackEliminated = true; + } + ArrayList playersList = new ArrayList(); + TR_EnumerateEntitiesSphere(worldSpace, originalData.InstantKillRadius, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, playersList); + for (int i = 0; i < playersList.Length; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(playersList.Get(i)); + if (!attackEliminated && client.IsEliminated) + { + continue; + } + + if (client.IsInDeathCam) + { + continue; + } + + if (actor.MyNextBotPointer().GetRangeSquaredTo(client.index) > Pow(originalData.InstantKillRadius, 2.0) || + !client.CanSeeSlender(controller.Index, false, _, !attackEliminated)) + { + continue; + } + + actor.LastKillTime = gameTime + originalData.InstantKillCooldown[difficulty]; + client.StartDeathCam(controller.Index, myPos); + actor.CheckTauntKill(SF2_BasePlayer(client.index)); + } + delete playersList; + } + } + + if (actor.IsKillingSomeone) + { + return action.SuspendFor(SF2_DeathCamAction()); + } + + UnstuckCheck(action, actor); + + actor.ProcessTraps(); + + #if defined DEBUG + if (actor.IsAttemptingToMove && controller.Path.IsValid()) + { + PathFollower path = controller.Path; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || (g_PlayerDebugFlags[player.index] & DEBUG_BOSS_PATH) == 0) + { + continue; + } + + Segment init, end; + init = path.FirstSegment(); + while (init != NULL_PATH_SEGMENT) + { + end = path.NextSegment(init); + if (end == NULL_PATH_SEGMENT) + { + break; + } + + float from[3], to[3]; + int color[4] = { 25, 94, 255, 255 }; + init.GetPos(from); + end.GetPos(to); + TE_SetupBeamPoints(from, + to, + g_ShockwaveBeam, + g_ShockwaveHalo, + 0, + 30, + 0.1, + 5.0, + 5.0, + 5, + 0.0, + color, + 1); + + TE_SendToClient(player.index); + + init = end; + } + } + } + #endif + + if (!actor.IsAttacking && (controller.Flags & SFF_ATTACKPROPS) != 0) + { + float myPos[3]; + actor.GetAbsOrigin(myPos); + ArrayList props = new ArrayList(); + TR_EnumerateEntitiesSphere(myPos, 2000.0, PARTITION_SOLID_EDICTS, EnumerateBreakableEntities, props); + for (int i = 0; i < props.Length; i++) + { + if (!actor.IsLOSClearFromTarget(CBaseEntity(props.Get(i)))) + { + continue; + } + + NextBotAction attackAction = actor.GetAttackAction(props.Get(i), true); + if (attackAction != NULL_ACTION) + { + actor.EndCloak(); + delete props; + return action.SuspendFor(attackAction, "Fuck you prop."); + } + } + delete props; + } + + return action.Continue(); +} + +static int OnResume(SF2_ChaserMainAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + action.LastStuckTime = GetGameTime() + 0.75; + actor.UpdateMovementAnimation(); + return action.Continue(); +} + +static int OnInjured(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damageType) +{ + if (actor.CanBeStunned() && IsValidClient(attacker.index) && actor.CanTakeDamage(attacker, inflictor, damage)) + { + actor.StunHealth -= damage; + if (actor.StunHealth <= 0.0) + { + return action.TrySuspendFor(SF2_ChaserStunnedAction(), RESULT_IMPORTANT, "I was stunned by someone"); + } + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int search = actor.RageIndex + 1; + if ((data.BoxingBoss || view_as(controller).GetProfileData().IsPvEBoss) && + actor.CanTakeDamage(attacker, inflictor, damage) && data.Rages != null && search < data.Rages.Length && !actor.IsRaging) + { + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(search, rageInfo, sizeof(rageInfo)); + if (float(actor.GetProp(Prop_Data, "m_iHealth")) <= actor.MaxHealth * rageInfo.PercentageThreshold) + { + return action.TrySuspendFor(SF2_ChaserRageAction(), RESULT_IMPORTANT, "I need to rage!"); + } + } + + return action.TryContinue(); +} + +static void OnAnimationEvent(SF2_ChaserMainAction action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + actor.CastAnimEvent(event); + actor.CastAnimEvent(event, true); +} + +static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) +{ + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = actor.Controller; + PathFollower path = controller.Path; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + float gameTime = GetGameTime(); + + if (!path.IsValid() || !actor.IsAttemptingToMove || loco.GetDesiredSpeed() <= 0.0) + { + action.LastStuckTime = gameTime; + return; + } + + float lastPos[3], myPos[3]; + action.GetLastPos(lastPos); + actor.GetAbsOrigin(myPos); + + if (bot.IsRangeLessThanEx(lastPos, 0.13) || loco.GetGroundSpeed() <= 0.1) + { + if (action.LastStuckTime > gameTime - 0.75) + { + return; + } + float destination[3]; + CNavArea area = TheNavMesh.GetNearestNavArea(lastPos, _, _, _, false); + area.GetCenter(destination); + float tempMaxs[3]; + npc.GetBodyMaxs(tempMaxs); + float traceMins[3]; + traceMins[0] = g_SlenderDetectMins[controller.Index][0] - 5.0; + traceMins[1] = g_SlenderDetectMins[controller.Index][1] - 5.0; + traceMins[2] = 0.0; + + float traceMaxs[3]; + traceMaxs[0] = g_SlenderDetectMaxs[controller.Index][0] + 5.0; + traceMaxs[1] = g_SlenderDetectMaxs[controller.Index][1] + 5.0; + traceMaxs[2] = tempMaxs[2]; + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (GetVectorSquareMagnitude(destination, lastPos) <= SquareFloat(16.0) || TR_DidHit()) + { + CursorData cursor = path.GetCursorData(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).GetCostSoFar() < 16.0) + { + continue; + } + if (cursor.segmentPrior != NULL_PATH_SEGMENT) + { + CNavArea segmentArea = cursor.segmentPrior.area; + if (segmentArea == collector.Get(i)) + { + continue; + } + } + float navPos[3]; + collector.Get(i).GetCenter(navPos); + if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + int randomArea = 0, randomCell = 0; + areaArray.Resize(validAreaCount); + area = NULL_AREA; + while (validAreaCount > 1) + { + randomCell = GetRandomInt(0, validAreaCount - 1); + randomArea = areaArray.Get(randomCell); + area = collector.Get(randomArea); + area.GetCenter(destination); + + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (TR_DidHit()) + { + area = NULL_AREA; + validAreaCount--; + int findValue = areaArray.FindValue(randomCell); + if (findValue != -1) + { + areaArray.Erase(findValue); + } + } + else + { + break; + } + } + + delete collector; + delete areaArray; + } + path.GetClosestPosition(destination, destination, path.FirstSegment(), 128.0); + action.LastStuckTime = gameTime + 0.75; + actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); + } + else + { + loco.GetFeet(lastPos); + action.SetLastPos(lastPos); + action.LastStuckTime += 0.03; + if (action.LastStuckTime > gameTime) + { + action.LastStuckTime = gameTime; + } + } +} + +static void OnContact(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity other, Address result) +{ + if (IsValidClient(other.index)) + { + return; + } + + char classname[64]; + other.GetClassname(classname, sizeof(classname)); + if (strcmp(classname, "obj_sentrygun", false) == 0 || strcmp(classname, "obj_dispenser", false) == 0 || + strcmp(classname, "obj_teleporter", false) == 0 || strcmp(classname, "func_breakable", false) == 0) + { + int health = other.GetProp(Prop_Data, "m_iHealth"); + SDKHooks_TakeDamage(other.index, actor.index, actor.index, health * 4.0); + } + + if (strcmp(classname, "prop_physics") == 0 || strcmp(classname, "prop_dynamic") == 0) + { + if (other.GetProp(Prop_Data, "m_iHealth") > 0) + { + SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); + } + } + if (strncmp(classname, "obj_", 4) == 0) + { + if (other.GetProp(Prop_Data, "m_iHealth") > 0) + { + SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); + } + } +} + +static int OnKilled(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damageType, CBaseEntity weapon, const float damageForce[3], const float damagePosition[3], int damageCustom) +{ + actor.State = STATE_DEATH; + Call_StartForward(g_OnBossKilledFwd); + Call_PushCell(actor.Controller); + Call_PushCell(attacker.index); + Call_Finish(); + return action.TryChangeTo(SF2_ChaserDeathAction(attacker), RESULT_CRITICAL); +} + +static void OnLeaveGround(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity ground) +{ + if (!actor.IsJumping) + { + return; + } + + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + NextBotGroundLocomotion loco = npc.GetLocomotion(); + float velocity[3]; + actor.MyNextBotPointer().GetLocomotionInterface().GetVelocity(velocity); + velocity[2] += loco.GetStepHeight() * 4.0; + loco.SetVelocity(velocity); + actor.IsJumping = false; +} + +static int OnCommandString(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, const char[] command) +{ + if (StrContains(command, "debug attack ") == 0 && !actor.IsAttacking) + { + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + char attack[128]; + strcopy(attack, sizeof(attack), command); + ReplaceString(attack, sizeof(attack), "debug attack ", ""); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attack, attackData); + return action.TrySuspendFor(SF2_ChaserAttackAction(attack, attackData.Duration[difficulty] + GetGameTime()), RESULT_IMPORTANT); + } + + if (strcmp(command, "suspend for action") == 0) + { + NextBotAction nbAction = actor.GetSuspendForAction(); + if (nbAction != NULL_ACTION) + { + return action.TrySuspendFor(nbAction, RESULT_CRITICAL); + } + } + + return action.TryContinue(); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp new file mode 100644 index 00000000..7687cb7a --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp @@ -0,0 +1,108 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserRageAction < NextBotAction +{ + public SF2_ChaserRageAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Rage"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_HasRaged") + .EndDataMapDesc(); + } + return view_as(g_Factory.Create()); + } + + property bool HasRaged + { + public get() + { + return this.GetData("m_HasRaged") != 0; + } + + public set(bool value) + { + this.SetData("m_HasRaged", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserRageAction action, SF2_ChaserEntity actor) +{ + actor.RageIndex++; + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + data = controller.GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = controller.Difficulty; + actor.IsAttemptingToMove = false; + loco.Stop(); + controller.Path.Invalidate(); + + actor.IsRaging = true; + + actor.OverrideInvincible = rageInfo.Invincible; + + actor.PerformVoiceEx(_, _, rageInfo.StartSounds, true); + + actor.EndCloak(); + + if (rageInfo.IsHealing) + { + return SF2_ChaserFleeToHealAction(); + } + + if (rageInfo.Animations.GetAnimation("start", difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = LookupProfileAnimation(actor.index, animName); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int Update(SF2_ChaserRageAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + return action.Continue(); + } + + return action.Done("I am no longer raging"); +} + +static void OnEnd(SF2_ChaserRageAction action, SF2_ChaserEntity actor) +{ + actor.IsRaging = false; + actor.OverrideInvincible = false; + if (actor.Controller.IsValid()) + { + SF2ChaserBossProfileData data; + data = actor.Controller.GetProfileData(); + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); + if (rageInfo.IncreaseDifficulty) + { + actor.Controller.Difficulty++; + if (actor.Controller.Difficulty > Difficulty_Apollyon) + { + actor.Controller.Difficulty = Difficulty_Apollyon; + } + } + actor.UpdateMovementAnimation(); + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp new file mode 100644 index 00000000..37571bab --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp @@ -0,0 +1,177 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserSmellAction < NextBotAction +{ + public SF2_ChaserSmellAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Smell"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } + + public static bool IsPossible(SF2_ChaserEntity actor) + { + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + if (!data.AnimationData.HasAnimationSection(g_SlenderAnimationsList[SF2BossAnimation_Smell])) + { + return false; + } + + return true; + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserSmellAction action, SF2_ChaserEntity actor) +{ + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + actor.IsAttemptingToMove = false; + + actor.PerformVoice(SF2BossSound_Smell); + + if (data.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Smell], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Smell], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.IsAllowedToDespawn = false; + + return action.Continue(); +} + +static int Update(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + PathFollower path = controller.Path; + g_SlenderTimeUntilKill[controller.Index] = GetGameTime() + NPCGetIdleLifetime(controller.Index, difficulty); + if (action.ActiveChild == NULL_ACTION) + { + float bestDistance = Pow(data.SmellData.SmellRange[difficulty], 2.0); + bool found = false; + float pos[3], myPos[3], bestPos[3]; + actor.GetAbsOrigin(myPos); + + CBaseEntity target = CBaseEntity(-1); + for (int i = 0; i < actor.SmellPlayerList.Length; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(actor.SmellPlayerList.Get(i)); + if (!player.IsValid) + { + continue; + } + player.GetAbsOrigin(pos); + float distance = GetVectorSquareMagnitude(pos, myPos); + if (distance <= bestDistance) + { + found = true; + bestDistance = distance; + bestPos = pos; + target = CBaseEntity(player.index); + } + } + if (found) + { + if (!data.SmellData.ShouldChase[difficulty]) + { + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(bestPos, data.AlertRunOnHearSound[difficulty]), "I smelled someone, what is it?"); + } + else + { + actor.Target = target; + SF2_BasePlayer player = SF2_BasePlayer(target.index); + player.SetForceChaseState(controller, true); + actor.State = STATE_CHASE; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserChaseAction(), "I smelled someone and I need to chase them, GO GO GO!"); + } + } + actor.State = STATE_IDLE; + return action.ChangeTo(SF2_ChaserIdleAction(), "Sniffers found nothing, go back to idle."); + } + + int interruptConditions = actor.InterruptConditions; + + ILocomotion loco = actor.MyNextBotPointer().GetLocomotionInterface(); + loco.Stop(); + + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + if ((interruptConditions & COND_SAWENEMY) != 0) + { + float pos[3]; + target.GetAbsOrigin(pos); + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Abort abort, I saw someone!"); + } + } + + SF2_BasePlayer alertTarget = actor.AlertTriggerTarget; + if ((interruptConditions & COND_ALERT_TRIGGER) != 0 && alertTarget.IsValid) + { + float pos[3]; + actor.GetAlertTriggerPosition(alertTarget, pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Stop! I heard someone!"); + } + + return action.Continue(); +} + +static int OnSuspend(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) +{ + actor.IsAllowedToDespawn = true; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/spawn.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/spawn.sp new file mode 100644 index 00000000..48bcc11d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/spawn.sp @@ -0,0 +1,55 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserSpawnAction < NextBotAction +{ + public SF2_ChaserSpawnAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Spawn"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.State = STATE_IDLE; + + float rate = 1.0, duration = 0.0, cycle = 0.0; + if (actor.GetOverrideSpawnAnimation()[0] != '\0') + { + int sequence = LookupProfileAnimation(actor.index, actor.GetOverrideSpawnAnimation()); + if (sequence != -1) + { + rate = actor.GetPropFloat(Prop_Data, "m_OverrideSpawnAnimationRate"); + duration = actor.GetPropFloat(Prop_Data, "m_OverrideSpawnAnimationDuration"); + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + } + + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Spawn], rate, duration, cycle); + if (sequence != -1) + { + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + + return action.ChangeTo(SF2_ChaserIdleAction()); +} + +static int OnResume(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + return action.ChangeTo(SF2_ChaserIdleAction()); +} + +static void OnEnd(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor) +{ + Call_StartForward(g_OnBossFinishSpawningFwd); + Call_PushCell(actor.Controller); + Call_Finish(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp new file mode 100644 index 00000000..facdf8cb --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp @@ -0,0 +1,175 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserStunnedAction < NextBotAction +{ + public SF2_ChaserStunnedAction(CBaseEntity attacker = view_as(-1)) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Stunned"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + g_Factory.BeginDataMapDesc() + .DefineEntityField("m_Attacker") + .DefineIntField("m_OldState") + .EndDataMapDesc(); + } + SF2_ChaserStunnedAction action = view_as(g_Factory.Create()); + + action.Attacker = attacker.index; + return action; + } + + property int Attacker + { + public get() + { + return this.GetDataEnt("m_Attacker"); + } + + public set(int value) + { + this.SetDataEnt("m_Attacker", value); + } + } + + property int OldState + { + public get() + { + return this.GetData("m_OldState"); + } + + public set(int value) + { + this.SetData("m_OldState", value); + } + } +} + +static NextBotAction InitialContainedAction(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) +{ + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + data = baseController.GetProfileData(); + char animName[64]; + float rate = 1.0, duration = 0.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + actor.IsAttemptingToMove = false; + loco.Stop(); + + if (actor.State == STATE_ATTACK) + { + actor.CancelAttack = true; + } + + action.OldState = actor.State; + + actor.IsStunned = true; + + actor.PerformVoice(SF2BossSound_Stun); + + actor.EndCloak(); + + if (data.AnimationData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Stun], difficulty, animName, sizeof(animName), rate, duration, cycle)) + { + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Stun], rate, duration, cycle); + if (sequence != -1) + { + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + } + + return NULL_ACTION; +} + +static int OnStart(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.KeyDrop) + { + if (SF_IsBoxingMap() && data.DisappearOnStun && data.BoxingBoss && !g_SlenderBoxingBossIsKilled[controller.Index] && !view_as(controller).GetProfileData().IsPvEBoss) + { + g_SlenderBoxingBossKilled++; + if ((g_SlenderBoxingBossKilled == g_SlenderBoxingBossCount)) + { + NPC_DropKey(controller.Index, data.KeyModel, data.KeyTrigger); + } + g_SlenderBoxingBossIsKilled[controller.Index] = true; + } + else + { + NPC_DropKey(controller.Index, data.KeyModel, data.KeyTrigger); + } + } + + actor.DropItem(); + + actor.RemoveAllGestures(); + CBaseNPC_RemoveAllLayers(actor.index); + + actor.State = STATE_STUN; + + if (controller.HasAttribute(SF2Attribute_AddStunHealthOnStun)) + { + actor.MaxStunHealth += controller.GetAttributeValue(SF2Attribute_AddStunHealthOnStun); + } + + if (controller.HasAttribute(SF2Attribute_AddSpeedOnStun)) + { + controller.SetAddSpeed(controller.GetAttributeValue(SF2Attribute_AddSpeedOnStun)); + } + + if (controller.HasAttribute(SF2Attribute_AddAccelerationOnStun)) + { + controller.SetAddAcceleration(controller.GetAttributeValue(SF2Attribute_AddAccelerationOnStun)); + } + + Call_StartForward(g_OnBossStunnedFwd); + Call_PushCell(actor.Controller.Index); + Call_PushCell(action.Attacker); + Call_Finish(); + + return action.Continue(); +} + +static int Update(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, float interval) +{ + if (action.ActiveChild != NULL_ACTION) + { + return action.Continue(); + } + + if (actor.Controller.GetProfileData().DisappearOnStun) + { + actor.Controller.UnSpawn(); + } + return action.Done("I am no longer stunned"); +} + +static int OnSuspend(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) +{ + actor.IsStunned = false; + actor.StunHealth = actor.MaxStunHealth; + if (actor.Controller.IsValid()) + { + actor.NextStunTime = GetGameTime() + actor.Controller.GetProfileData().StunCooldown[actor.Controller.Difficulty]; + + actor.UpdateMovementAnimation(); + } + actor.State = action.OldState; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/tauntkill.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/tauntkill.sp new file mode 100644 index 00000000..f4a2b7aa --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/tauntkill.sp @@ -0,0 +1,74 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserTauntKillAction < NextBotAction +{ + public SF2_ChaserTauntKillAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_TauntKill"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + } + return view_as(g_Factory.Create()); + } + + public static bool IsPossible(SF2_ChaserEntity actor) + { + SF2NPC_BaseNPC baseController = view_as(actor.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + if (!data.AnimationData.HasAnimationSection(g_SlenderAnimationsList[SF2BossAnimation_TauntKill])) + { + return false; + } + + return true; + } +} + + +static int OnStart(SF2_ChaserTauntKillAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + float rate = 1.0, duration = 0.0, cycle = 0.0; + + actor.PerformVoice(SF2BossSound_TauntKill); + actor.KillTarget = CBaseEntity(-1); + + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_TauntKill], rate, duration, cycle); + if (sequence != -1) + { + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + } + + return action.Done("I'm done taunting someone after I killed them."); +} + +static int OnResume(SF2_ChaserTauntKillAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + } + + return action.Done("I'm done taunting someone after I killed them."); +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 137158d5..19731094 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -1 +1,4820 @@ -#pragma semicolon 1 \ No newline at end of file +#pragma semicolon 1 + +#include "actions/mainlayer.sp" +#include "actions/idle.sp" +#include "actions/alert.sp" +#include "actions/smell.sp" +#include "actions/chase.sp" +#include "actions/chaseinitial.sp" +#include "actions/attacklayer.sp" +#include "actions/chaselayer.sp" +#include "actions/attack.sp" +#include "actions/stun.sp" +#include "actions/death.sp" +#include "actions/spawn.sp" +#include "actions/tauntkill.sp" +#include "actions/beatbox_freeze.sp" +#include "actions/rage.sp" +#include "actions/flee_to_heal.sp" + +#include "postures/rage_phase.sp" +#include "postures/running_away.sp" +#include "postures/within_bounds.sp" +#include "postures/within_range.sp" +#include "postures/on_look.sp" +#include "postures/on_alert.sp" +#include "postures/on_cloak.sp" + +static CEntityFactory g_Factory; + +methodmap SF2_ChaserEntity < SF2_BaseBoss +{ + public SF2_ChaserEntity(int entity) + { + return view_as(entity); + } + + public bool IsValid() + { + if (!CBaseCombatCharacter(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_Factory; + } + + public static void Initialize() + { + g_Factory = new CEntityFactory("sf2_npc_boss_chaser", OnCreate, OnRemove); + g_Factory.DeriveFromFactory(SF2_BaseBoss.GetFactory()); + g_Factory.SetInitialActionFactory(SF2_ChaserMainAction.GetFactory()); + g_Factory.BeginDataMapDesc() + .DefineBoolField("m_IsAllowedToDespawn") + .DefineStringField("m_OverrideSpawnAnimation") + .DefineFloatField("m_OverrideSpawnAnimationRate") + .DefineFloatField("m_OverrideSpawnAnimationDuration") + .DefineFloatField("m_Override") + .DefineBoolField("m_IsStunned") + .DefineFloatField("m_StunHealth") + .DefineFloatField("m_NextStunTime") + .DefineFloatField("m_MaxStunHealth") + .DefineFloatField("m_MaxHealth") + .DefineBoolField("m_IsInChaseInitial") + .DefineStringField("m_Posture") + .DefineStringField("m_DefaultPosture") + .DefineBoolField("m_IsAttacking") + .DefineBoolField("m_CancelAttack") + .DefineStringField("m_AttackName") + .DefineIntField("m_NextAttackTime") + .DefineFloatField("m_AttackRunDuration") + .DefineFloatField("m_AttackRunDelay") + .DefineFloatField("m_NextVoiceTime") + .DefineIntField("m_MovementType") + .DefineIntField("m_InterruptConditions") + .DefineIntField("m_AlertTriggerCount", MAXTF2PLAYERS) + .DefineVectorField("m_AlertTriggerPosition", MAXTF2PLAYERS) + .DefineEntityField("m_AlertTriggerTarget") + .DefineFloatField("m_AlertSoundTriggerCooldown", MAXTF2PLAYERS) + .DefineIntField("m_AutoChaseCount", MAXTF2PLAYERS) + .DefineFloatField("m_AutoChaseAddCooldown", MAXTF2PLAYERS) + .DefineFloatField("m_AutoChaseCooldown", MAXTF2PLAYERS) + .DefineBoolField("m_MaintainTarget") + .DefineBoolField("m_AlertWithBoss") + .DefineBoolField("m_FollowedCompanionAlert") + .DefineFloatField("m_FollowCooldownAlert") + .DefineBoolField("m_FollowedCompanionChase") + .DefineFloatField("m_FollowCooldownChase") + .DefineIntField("m_SmellPlayerList") + .DefineFloatField("m_SmellCooldown") + .DefineFloatField("m_ProjectileCooldown") + .DefineBoolField("m_IsReloadingProjectiles") + .DefineIntField("m_ProjectileAmmo") + .DefineFloatField("m_ProjectileReloadTime") + .DefineEntityField("m_KillTarget") + .DefineBoolField("m_HasCloaked") + .DefineFloatField("m_CloakTime") + .DefineBoolField("m_PlayCloakAnimation") + .DefineIntField("m_RageIndex") + .DefineBoolField("m_IsRaging") + .DefineBoolField("m_IsRunningAway") + .DefineBoolField("m_IsSelfHealing") + .DefineBoolField("m_OverrideInvincible") + .DefineFloatField("m_TrapCooldown") + .DefineBoolField("m_WasInBacon") + .EndDataMapDesc(); + g_Factory.Install(); + + AddNormalSoundHook(Hook_ChaserSoundHook); + + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + + SF2_ChaserAttackAction.Initialize(); + InitializePostureRagePhase(); + InitializePostureRunningAway(); + InitializePostureWithinBounds(); + InitializePostureWithinRange(); + InitializePostureOnLook(); + InitializePostureOnAlert(); + InitializePostureOnCloak(); + } + + property SF2NPC_Chaser Controller + { + public get() + { + return view_as(view_as(this).Controller); + } + + public set(SF2NPC_Chaser controller) + { + view_as(this).Controller = controller; + } + } + + property bool IsAllowedToDespawn + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAllowedToDespawn") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsAllowedToDespawn", value); + } + } + + public char[] GetOverrideSpawnAnimation() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_OverrideSpawnAnimation", buffer, sizeof(buffer)); + return buffer; + } + + public void SetOverrideSpawnAnimation(const char[] name, float duration = 0.0, float rate = 1.0) + { + this.SetPropString(Prop_Data, "m_OverrideSpawnAnimation", name); + this.SetPropFloat(Prop_Data, "m_OverrideSpawnAnimationRate", rate); + this.SetPropFloat(Prop_Data, "m_OverrideSpawnAnimationDuration", duration); + } + + public bool CanBeStunned() + { + if (!this.Controller.IsValid()) + { + return false; + } + + if (!this.Controller.GetProfileData().StunEnabled) + { + return false; + } + + if (this.IsStunned) + { + return false; + } + + if (this.MaxStunHealth <= 0.0) + { + return false; + } + + if (GetGameTime() < this.NextStunTime) + { + return false; + } + + if (this.IsKillingSomeone) + { + return false; + } + + if (this.OverrideInvincible) + { + return false; + } + + return true; + } + + public bool CanTakeDamage(CBaseEntity attacker = view_as(-1), CBaseEntity inflictor = view_as(-1), float damage = 0.0) + { + if (!this.IsAttacking) + { + return true; + } + + if (this.OverrideInvincible) + { + return false; + } + + if (attacker.IsValid() || inflictor.IsValid()) + { + Action action; + Call_StartForward(g_OnBossPreTakeDamageFwd); + Call_PushCell(this); + Call_PushCell(attacker); + Call_PushCell(inflictor); + Call_PushCell(damage); + Call_Finish(action); + if (action == Plugin_Stop) + { + return false; + } + } + + int difficulty = this.Controller.Difficulty; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(this.GetAttackName(), attackData); + + return !attackData.ImmuneToDamage[difficulty]; + } + + property bool IsStunned + { + public get() + { + return this.GetProp(Prop_Data, "m_IsStunned") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsStunned", value); + } + } + + property float StunHealth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_StunHealth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_StunHealth", value); + } + } + + property float MaxStunHealth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_MaxStunHealth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_MaxStunHealth", value); + } + } + + property float MaxHealth + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_MaxHealth"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_MaxHealth", value); + } + } + + property float NextStunTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_NextStunTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_NextStunTime", value); + } + } + + property bool IsInChaseInitial + { + public get() + { + return this.GetProp(Prop_Data, "m_IsInChaseInitial") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsInChaseInitial", value); + } + } + + public int GetPosture(char[] buffer, int bufferSize) + { + return this.GetPropString(Prop_Data, "m_Posture", buffer, bufferSize); + } + + public void SetPosture(const char[] posture) + { + if (posture[0] == '\0') + { + return; + } + + char currentPosture[64]; + this.GetPosture(currentPosture, sizeof(currentPosture)); + + this.SetPropString(Prop_Data, "m_Posture", posture); + + if (this.Controller.IsValid() && strcmp(currentPosture, posture) != 0 && this.IsAttemptingToMove) + { + this.UpdateMovementAnimation(); + } + } + + public int GetDefaultPosture(char[] buffer, int bufferSize) + { + return this.GetPropString(Prop_Data, "m_DefaultPosture", buffer, bufferSize); + } + + public void SetDefaultPosture(const char[] posture) + { + if (posture[0] == '\0') + { + return; + } + + if (strcmp(posture, SF2_PROFILE_CHASER_DEFAULT_POSTURE) != 0) + { + if (!this.Controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfilePostureInfo postureInfo; + + if (!data.GetPosture(posture, postureInfo)) + { + return; + } + } + + this.SetPropString(Prop_Data, "m_DefaultPosture", posture); + } + + public bool CanUpdatePosture() + { + if (this.State == STATE_DEATH) + { + return false; + } + + if (this.IsAttacking) + { + return false; + } + + if (this.IsStunned) + { + return false; + } + + if (this.IsInChaseInitial) + { + return false; + } + + return true; + } + + property bool IsAttacking + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAttacking") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsAttacking", value); + } + } + + property bool CancelAttack + { + public get() + { + return this.GetProp(Prop_Data, "m_CancelAttack") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_CancelAttack", value); + } + } + + public char[] GetAttackName() + { + char buffer[128]; + this.GetPropString(Prop_Data, "m_AttackName", buffer, sizeof(buffer)); + return buffer; + } + + public void SetAttackName(const char[] name) + { + this.SetPropString(Prop_Data, "m_AttackName", name); + } + + property StringMap NextAttackTime + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_NextAttackTime")); + } + + public set(StringMap value) + { + this.SetProp(Prop_Data, "m_NextAttackTime", value); + } + } + + public float GetNextAttackTime(const char[] attackName) + { + if (this.NextAttackTime == null) + { + return -1.0; + } + + float value = -1.0; + if (!this.NextAttackTime.GetValue(attackName, value)) + { + return -1.0; + } + + return value; + } + + public void SetNextAttackTime(const char[] attackName, float time) + { + if (this.NextAttackTime == null) + { + return; + } + + this.NextAttackTime.SetValue(attackName, time); + } + + property float AttackRunDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AttackRunDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AttackRunDuration", value); + } + } + + property float AttackRunDelay + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AttackRunDelay"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AttackRunDelay", value); + } + } + + property float NextVoiceTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_NextVoiceTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_NextVoiceTime", value); + } + } + + property bool IsAttemptingToMove + { + public get() + { + return this.GetProp(Prop_Data, "m_IsAttemptingToMove") != 0; + } + + public set(bool value) + { + bool oldValue = this.IsAttemptingToMove; + this.SetProp(Prop_Data, "m_IsAttemptingToMove", value); + + if (oldValue != value) + { + this.UpdateMovementAnimation(); + } + } + } + + property SF2NPCMoveTypes MovementType + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_MovementType")); + } + + public set(SF2NPCMoveTypes value) + { + SF2NPCMoveTypes oldType = this.MovementType; + this.SetProp(Prop_Data, "m_MovementType", value); + + if (oldType != value && this.IsAttemptingToMove) + { + this.UpdateMovementAnimation(); + } + } + } + + property int InterruptConditions + { + public get() + { + return this.GetProp(Prop_Data, "m_InterruptConditions"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_InterruptConditions", value); + } + } + + public int GetAlertTriggerCount(SF2_BasePlayer player) + { + return this.GetProp(Prop_Data, "m_AlertTriggerCount", _, player.index); + } + + public void SetAlertTriggerCount(SF2_BasePlayer player, int value) + { + this.SetProp(Prop_Data, "m_AlertTriggerCount", value, _, player.index); + } + + public void GetAlertTriggerPosition(SF2_BasePlayer player, float buffer[3]) + { + this.GetPropVector(Prop_Data, "m_AlertTriggerPosition", buffer, player.index); + } + + public void SetAlertTriggerPosition(SF2_BasePlayer player, float buffer[3]) + { + this.SetPropVector(Prop_Data, "m_AlertTriggerPosition", buffer, player.index); + } + + public float GetAlertSoundTriggerCooldown(SF2_BasePlayer player) + { + return this.GetPropFloat(Prop_Data, "m_AlertSoundTriggerCooldown", player.index); + } + + public void SetAlertSoundTriggerCooldown(SF2_BasePlayer player, float value) + { + this.SetPropFloat(Prop_Data, "m_AlertSoundTriggerCooldown", value, player.index); + } + + public int GetAutoChaseCount(SF2_BasePlayer player) + { + return this.GetProp(Prop_Data, "m_AutoChaseCount", _, player.index); + } + + public void SetAutoChaseCount(SF2_BasePlayer player, int value) + { + this.SetProp(Prop_Data, "m_AutoChaseCount", value, _, player.index); + } + + public float GetAutoChaseAddCooldown(SF2_BasePlayer player) + { + return this.GetPropFloat(Prop_Data, "m_AutoChaseAddCooldown", player.index); + } + + public void SetAutoChaseAddCooldown(SF2_BasePlayer player, float value) + { + this.SetPropFloat(Prop_Data, "m_AutoChaseAddCooldown", value, player.index); + } + + public float GetAutoChaseCooldown(SF2_BasePlayer player) + { + return this.GetPropFloat(Prop_Data, "m_AutoChaseCooldown", player.index); + } + + public void SetAutoChaseCooldown(SF2_BasePlayer player, float value) + { + this.SetPropFloat(Prop_Data, "m_AutoChaseCooldown", value, player.index); + } + + property SF2_BasePlayer AlertTriggerTarget + { + public get() + { + return SF2_BasePlayer(this.GetPropEnt(Prop_Data, "m_AlertTriggerTarget")); + } + + public set(SF2_BasePlayer value) + { + this.SetPropEnt(Prop_Data, "m_AlertTriggerTarget", value.index); + } + } + + property bool MaintainTarget + { + public get() + { + return this.GetProp(Prop_Data, "m_MaintainTarget") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_MaintainTarget", value); + } + } + + property bool AlertWithBoss + { + public get() + { + return this.GetProp(Prop_Data, "m_AlertWithBoss") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_AlertWithBoss", value); + } + } + + property bool FollowedCompanionAlert + { + public get() + { + return this.GetProp(Prop_Data, "m_FollowedCompanionAlert") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_FollowedCompanionAlert", value); + } + } + + property float FollowCooldownAlert + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_FollowCooldownAlert"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_FollowCooldownAlert", value); + } + } + + property bool FollowedCompanionChase + { + public get() + { + return this.GetProp(Prop_Data, "m_FollowedCompanionChase") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_FollowedCompanionChase", value); + } + } + + property float FollowCooldownChase + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_FollowCooldownChase"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_FollowCooldownChase", value); + } + } + + property ArrayList SmellPlayerList + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_SmellPlayerList")); + } + + public set(ArrayList value) + { + this.SetProp(Prop_Data, "m_SmellPlayerList", value); + } + } + + property float SmellCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_SmellCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_SmellCooldown", value); + } + } + + property float ProjectileCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_ProjectileCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_ProjectileCooldown", value); + } + } + + property bool IsReloadingProjectiles + { + public get() + { + return this.GetProp(Prop_Data, "m_IsReloadingProjectiles") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsReloadingProjectiles", value); + } + } + + property int ProjectileAmmo + { + public get() + { + return this.GetProp(Prop_Data, "m_ProjectileAmmo"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_ProjectileAmmo", value); + } + } + + property float ProjectileReloadTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_ProjectileReloadTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_ProjectileReloadTime", value); + } + } + + property CBaseEntity KillTarget + { + public get() + { + return CBaseEntity(this.GetPropEnt(Prop_Data, "m_KillTarget")); + } + + public set(CBaseEntity entity) + { + this.SetPropEnt(Prop_Data, "m_KillTarget", entity.index); + } + } + + property bool HasCloaked + { + public get() + { + return this.GetProp(Prop_Data, "m_HasCloaked") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_HasCloaked", value); + } + } + + property float CloakTime + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_CloakTime"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_CloakTime", value); + } + } + + property bool PlayCloakAnimation + { + public get() + { + return this.GetProp(Prop_Data, "m_PlayCloakAnimation") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_PlayCloakAnimation", value); + } + } + + property int RageIndex + { + public get() + { + return this.GetProp(Prop_Data, "m_RageIndex"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_RageIndex", value); + } + } + + property bool IsRaging + { + public get() + { + return this.GetProp(Prop_Data, "m_IsRaging") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsRaging", value); + } + } + + property bool IsRunningAway + { + public get() + { + return this.GetProp(Prop_Data, "m_IsRunningAway") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsRunningAway", value); + } + } + + property bool IsSelfHealing + { + public get() + { + return this.GetProp(Prop_Data, "m_IsSelfHealing") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsSelfHealing", value); + } + } + + property bool OverrideInvincible + { + public get() + { + return this.GetProp(Prop_Data, "m_OverrideInvincible") != 0; + } + + public set(bool value) + { + bool old = this.GetProp(Prop_Data, "m_OverrideInvincible") != 0; + + this.SetProp(Prop_Data, "m_OverrideInvincible", value); + + if (old != value) + { + if (value) + { + this.SetProp(Prop_Data, "m_takedamage", DAMAGE_EVENTS_ONLY); + } + else + { + this.SetProp(Prop_Data, "m_takedamage", DAMAGE_YES); + } + } + } + } + + property float TrapCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_TrapCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_TrapCooldown", value); + } + } + + property bool WasInBacon + { + public get() + { + return this.GetProp(Prop_Data, "m_WasInBacon") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_WasInBacon", value); + } + } + + public SF2_BasePlayer GetClosestPlayer() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return SF2_INVALID_PLAYER; + } + int difficulty = controller.Difficulty; + float myPos[3]; + this.GetAbsOrigin(myPos); + SF2BossProfileData data; + data = view_as(controller).GetProfileData(); + + SF2_BasePlayer closest = SF2_INVALID_PLAYER; + float range = Pow(data.SearchRange[difficulty], 2.0); + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.IsInGhostMode || client.IsProxy || !client.IsAlive || client.IsEliminated) + { + continue; + } + + float clientPos[3]; + client.GetAbsOrigin(clientPos); + + float distance = GetVectorSquareMagnitude(myPos, clientPos); + + if (distance < range) + { + closest = client; + range = distance; + } + } + + if (closest.IsValid) + { + return closest; + } + + return SF2_INVALID_PLAYER; + } + + public void UpdateMovementAnimation() + { + if (this.IsSelfHealing) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; + } + + char animation[64]; + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_Idle]); + + if (this.IsAttemptingToMove) + { + switch (this.MovementType) + { + case SF2NPCMoveType_Walk: + { + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_Walk]); + } + case SF2NPCMoveType_Run: + { + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_Run]); + } + case SF2NPCMoveType_Attack: + { + return; + } + } + } + if (this.IsKillingSomeone) + { + strcopy(animation, sizeof(animation), g_SlenderAnimationsList[SF2BossAnimation_DeathCam]); + } + + char posture[64]; + this.GetPosture(posture, sizeof(posture)); + this.ResetProfileAnimation(animation, _, _, _, posture); + } + + public bool PerformVoice(int soundType = -1, const char[] attackName = "") + { + SF2BossProfileSoundInfo soundInfo; + return this.PerformVoiceEx(soundType, attackName, soundInfo); + } + + public bool PerformVoiceEx(int soundType = -1, const char[] attackName = "", SF2BossProfileSoundInfo soundInfo, bool isSet = false) + { + if (soundType == -1 && !isSet) + { + return false; + } + + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + ArrayList soundList; + if (!isSet) + { + switch (soundType) + { + case SF2BossSound_Idle: + { + soundInfo = data.IdleSounds; + } + case SF2BossSound_Alert: + { + soundInfo = data.AlertSounds; + } + case SF2BossSound_Chasing: + { + soundInfo = data.ChasingSounds; + } + case SF2BossSound_ChaseInitial: + { + soundInfo = data.ChaseInitialSounds; + } + case SF2BossSound_Stun: + { + soundInfo = data.StunnedSounds; + } + case SF2BossSound_Death: + { + soundInfo = data.DeathSounds; + } + case SF2BossSound_Attack: + { + return this.CheckNestedSoundSection(data.AttackSounds, attackName, soundInfo, soundList); + } + case SF2BossSound_AttackKilled: + { + soundInfo = data.AttackKilledSounds; + } + case SF2BossSound_TauntKill: + { + soundInfo = data.TauntKillSounds; + } + case SF2BossSound_Smell: + { + soundInfo = data.SmellSounds; + } + case SF2BossSound_AttackBegin: + { + return this.CheckNestedSoundSection(data.AttackBeginSounds, attackName, soundInfo, soundList); + } + case SF2BossSound_AttackEnd: + { + return this.CheckNestedSoundSection(data.AttackEndSounds, attackName, soundInfo, soundList); + } + } + } + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + return this.PerformVoiceCooldown(soundInfo, soundList); + } + return false; + } + + public bool CheckNestedSoundSection(ArrayList list, const char[] attackName, SF2BossProfileSoundInfo soundInfo, ArrayList soundList) + { + if (this.SearchSoundsWithSectionName(list, attackName, soundInfo)) + { + soundList = soundInfo.Paths; + if (soundList != null && soundList.Length > 0) + { + return this.PerformVoiceCooldown(soundInfo, soundList); + } + } + return false; + } + + public bool PerformVoiceCooldown(SF2BossProfileSoundInfo soundInfo, ArrayList soundList) + { + char buffer[PLATFORM_MAX_PATH]; + float gameTime = GetGameTime(); + soundList.GetString(GetRandomInt(0, soundList.Length - 1), buffer, sizeof(buffer)); + if (buffer[0] != '\0') + { + float threshold = GetRandomFloat(0.0, 1.0); + float cooldown = GetRandomFloat(soundInfo.CooldownMin, soundInfo.CooldownMax); + if (threshold > soundInfo.Chance) + { + this.NextVoiceTime = gameTime + cooldown; + return false; + } + soundInfo.EmitSound(_, this.index, _, _, SF_SpecialRound(SPECIALROUND_TINYBOSSES) ? 25 : 0); + this.NextVoiceTime = gameTime + cooldown; + return true; + } + return false; + } + + public void CastAnimEvent(int event, bool footstep = false) + { + SF2BossProfileData data; + data = view_as(this.Controller).GetProfileData(); + + ArrayList arraySounds = data.EventSounds; + ArrayList arrayEvents = data.EventIndexes; + + if (footstep) + { + arraySounds = data.FootstepEventSounds; + arrayEvents = data.FootstepEventIndexes; + } + + if (arraySounds == null || arrayEvents == null) + { + return; + } + + int foundIndex = arrayEvents.FindValue(event); + if (foundIndex == -1) + { + return; + } + + SF2BossProfileSoundInfo soundInfo; + arraySounds.GetArray(foundIndex, soundInfo, sizeof(soundInfo)); + + if (soundInfo.Paths == null) + { + return; + } + + soundInfo.EmitSound(_, this.index); + SF2ChaserBossProfileData chaserData; + chaserData = this.Controller.GetProfileData(); + if (footstep && chaserData.EarthquakeFootsteps) + { + float myPos[3]; + this.GetAbsOrigin(myPos); + + UTIL_ScreenShake(myPos, chaserData.EarthquakeFootstepAmplitude, + chaserData.EarthquakeFootstepFrequency, chaserData.EarthquakeFootstepDuration, + chaserData.EarthquakeFootstepRadius, 0, chaserData.EarthquakeFootstepAirShake); + } + } + + public void CastFootstep() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileSoundInfo info; + info = data.FootstepSounds; + + info.EmitSound(_, this.index); + this.LegacyFootstepTime = this.LegacyFootstepInterval + GetGameTime(); + } + + public void UpdateAlertTriggerCount(SF2_BasePlayer player, int amount) + { + if (this.State != STATE_IDLE && this.State != STATE_ALERT) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; + } + + int difficulty = this.Controller.Difficulty; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + int threshold = data.SoundCountToAlert[difficulty]; + + if (threshold <= 0) + { + return; + } + + if (amount > threshold) + { + amount = threshold; + } + + this.SetAlertTriggerCount(player, this.GetAlertTriggerCount(player) + amount); + + if (this.GetAlertTriggerCount(player) >= threshold) + { + this.InterruptConditions |= COND_ALERT_TRIGGER; + + float pos[3]; + player.GetAbsOrigin(pos); + + this.SetAlertTriggerPosition(player, pos); + this.AlertTriggerTarget = player; + } + } + + public void UpdateAutoChaseCount(SF2_BasePlayer player, int amount) + { + if (this.State != STATE_ALERT) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; + } + + if (this.GetAutoChaseCooldown(player) > GetGameTime()) + { + return; + } + + int difficulty = this.Controller.Difficulty; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + + if (data.AutoChaseCount[difficulty] <= 0) + { + return; + } + + this.SetAutoChaseCount(player, amount); + + if (this.GetAutoChaseCount(player) >= data.AutoChaseCount[difficulty]) + { + player.SetForceChaseState(this.Controller, true); + this.SetAutoChaseCount(player, 0); + } + } + + public NextBotAction GetAttackAction(CBaseEntity target, bool props = false) + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return NULL_ACTION; + } + INextBot bot = this.MyNextBotPointer(); + bool canAttack = !this.IsAttacking && !this.IsKillingSomeone; + if (canAttack && !bot.GetLocomotionInterface().IsOnGround()) + { + canAttack = false; + } + + if (!canAttack) + { + return NULL_ACTION; + } + float gameTime = GetGameTime(); + char attackName[64], posture[64]; + this.GetPosture(posture, sizeof(posture)); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + ArrayList arrayAttacks = new ArrayList(); + SF2ChaserBossProfileAttackData attackData; + for (int index = 0; index < data.Attacks.Length; index++) + { + data.GetAttackFromIndex(index, attackData); + + if (attackData.Type == SF2BossAttackType_Invalid) + { + continue; + } + + if (gameTime < this.GetNextAttackTime(attackData.Name)) + { + continue; + } + + if (props && !attackData.CanUseAgainstProps) + { + continue; + } + + if (attackData.DontInterruptChaseInitial[difficulty] && this.IsInChaseInitial) + { + continue; + } + + if (!attackData.CanBeUsedWithPosture(posture)) + { + continue; + } + + if (attackData.Type == SF2BossAttackType_Custom) + { + Action result = Plugin_Continue; + Call_StartForward(g_OnChaserGetCustomAttackPossibleStatePFwd); + Call_PushCell(this); + Call_PushString(attackData.Name); + Call_PushCell(target); + Call_Finish(result); + + if (result != Plugin_Continue) + { + arrayAttacks.Push(index); + } + continue; + } + + if (difficulty < attackData.UseOnDifficulty) + { + continue; + } + + if (difficulty >= attackData.BlockOnDifficulty) + { + continue; + } + + if (!props) + { + float health = float(target.GetProp(Prop_Send, "m_iHealth")); + if (attackData.UseOnHealth != -1.0 && health < attackData.UseOnHealth) + { + continue; + } + if (attackData.BlockOnHealth != -1.0 && health >= attackData.BlockOnHealth) + { + continue; + } + } + + attackName = attackData.Name; + + arrayAttacks.Push(index); + } + + Call_StartForward(g_OnBossPreAttackFwd); + Call_PushCell(controller.Index); + Call_PushCell(arrayAttacks); + Call_Finish(); + + if (arrayAttacks.Length == 0) + { + delete arrayAttacks; + return NULL_ACTION; + } + + float eyePos[3], targetPos[3], direction[3], eyeAng[3]; + this.GetAbsAngles(eyeAng); + controller.GetEyePosition(eyePos); + target.GetAbsOrigin(targetPos); + SubtractVectors(targetPos, eyePos, direction); + GetVectorAngles(direction, direction); + direction[2] = 180.0; + + float distance = bot.GetRangeSquaredTo(target.index); + float fov = FloatAbs(AngleDiff(direction[1], eyeAng[1])); + + arrayAttacks.Sort(Sort_Random, Sort_Integer); + for (int i = 0; i < arrayAttacks.Length; i++) + { + data.GetAttackFromIndex(arrayAttacks.Get(i), attackData); + if (attackData.Type != SF2BossAttackType_Custom) + { + float beginRange = attackData.BeginRange[difficulty]; + float beginFOV = attackData.BeginFOV[difficulty]; + if (distance > Pow(beginRange, 2.0)) + { + continue; + } + if (fov > beginFOV) + { + continue; + } + } + + attackName = attackData.Name; + + return SF2_ChaserAttackAction(attackName, attackData.Duration[difficulty] + gameTime); + } + + delete arrayAttacks; + return NULL_ACTION; + } + + public NextBotAction GetCustomAttack(const char[] attackName, CBaseEntity target) + { + NextBotAction nbAction = NULL_ACTION; + Action action = Plugin_Continue; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + Call_StartForward(g_OnBossGetCustomAttackActionFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_PushArrayEx(attackData, sizeof(SF2ChaserBossProfileAttackData), SM_PARAM_COPYBACK); + Call_PushCell(target); + Call_PushCellRef(nbAction); + Call_Finish(action); + + if (action != Plugin_Continue) + { + return nbAction; + } + + return NULL_ACTION; + } + + public bool IsCustomAttackPossible(const char[] attackName, CBaseEntity target) + { + Action action = Plugin_Continue; + SF2ChaserBossProfileData data; + data = this.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + + Call_StartForward(g_OnIsBossCustomAttackPossibleFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_PushArrayEx(attackData, sizeof(SF2ChaserBossProfileAttackData), SM_PARAM_COPYBACK); + Call_PushCell(target); + Call_Finish(action); + + return action == Plugin_Continue; + } + + public NextBotAction IsAttackTransitionPossible(const char[] attackName, bool end = false, float& duration = 0.0) + { + SF2NPC_BaseNPC baseController = view_as(this.Controller); + SF2BossProfileData data; + data = baseController.GetProfileData(); + char section[32]; + if (!end) + { + strcopy(section, sizeof(section), g_SlenderAnimationsList[SF2BossAnimation_AttackBegin]); + } + else + { + strcopy(section, sizeof(section), g_SlenderAnimationsList[SF2BossAnimation_AttackEnd]); + } + if (!data.AnimationData.HasAnimationSection(section)) + { + return NULL_ACTION; + } + + char animName[64]; + float rate = 1.0, cycle = 0.0; + int difficulty = baseController.Difficulty; + if (!data.AnimationData.GetAnimation(section, difficulty, animName, sizeof(animName), rate, duration, cycle, _, _, _, attackName)) + { + return NULL_ACTION; + } + + int sequence = this.SelectProfileAnimation(section, rate, duration, cycle, _, _, _, attackName); + if (sequence == -1) + { + return NULL_ACTION; + } + + if (duration <= 0.0) + { + duration = this.SequenceDuration(sequence) / rate; + duration *= (1.0 - cycle); + } + + return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); + } + + public void DoAttackMiscConditions(const char[] attackName) + { + INextBot bot = this.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + PathFollower path = controller.Path; + CBaseEntity target = this.Target; + int difficulty = controller.Difficulty; + bool shouldPath = false; + + if (target.IsValid()) + { + bool aimAtTarget = false; + + if ((controller.HasAttribute(SF2Attribute_AlwaysLookAtTarget) || controller.HasAttribute(SF2Attribute_AlwaysLookAtTargetWhileAttacking)) + && !attackData.IgnoreAlwaysLooking[difficulty]) + { + aimAtTarget = true; + } + + if (aimAtTarget) + { + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); + } + + if (attackData.RunSpeed[difficulty] > 0.0) + { + shouldPath = true; + } + + if (shouldPath) + { + float pos[3]; + target.GetAbsOrigin(pos); + + path.ComputeToPos(bot, pos); + } + } + + if (!path.IsValid()) + { + loco.Stop(); + } + else + { + path.Update(bot); + } + } + + public NextBotAction GetSuspendForAction() + { + NextBotAction nbAction = NULL_ACTION; + Action action = Plugin_Continue; + Call_StartForward(g_OnChaserBossGetSuspendActionFwd); + Call_PushCell(this); + Call_PushCellRef(nbAction); + Call_Finish(action); + + if (action != Plugin_Continue) + { + return nbAction; + } + + return NULL_ACTION; + } + + public void DoShockwave(const char[] attackName) + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + SF2ChaserBossProfileShockwaveData shockwaveData; + shockwaveData = attackData.Shockwave; + + if (!shockwaveData.Enabled) + { + return; + } + + int difficulty = controller.Difficulty; + float radius = shockwaveData.Radius[difficulty]; + if (radius <= 0.0) + { + return; + } + + if (shockwaveData.Effects != null) + { + SlenderSpawnEffects(shockwaveData.Effects, controller.Index, false); + } + + float force = shockwaveData.Force[difficulty]; + + float myWorldSpace[3], myPos[3]; + this.WorldSpaceCenter(myWorldSpace); + this.GetAbsOrigin(myPos); + ArrayList hitList = new ArrayList(); + TR_EnumerateEntitiesSphere(myWorldSpace, radius, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, hitList); + bool eliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + eliminated = true; + } + + for (int i = 0; i < hitList.Length; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(hitList.Get(i)); + if (!eliminated && player.IsEliminated) + { + continue; + } + + float clientWorldSpace[3]; + player.WorldSpaceCenter(clientWorldSpace); + + TR_TraceRayFilter(myWorldSpace, clientWorldSpace, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, RayType_EndPoint, + TraceRayDontHitAnyEntity, this.index); + + if (!TR_DidHit() || TR_GetEntityIndex() == player.index) + { + float targetPos[3]; + player.GetAbsOrigin(targetPos); + if (targetPos[2] > myPos[2] + shockwaveData.Height[difficulty]) + { + continue; + } + + if (force > 0.0) + { + float velocity[3]; + MakeVectorFromPoints(myPos, targetPos, velocity); + GetVectorAngles(velocity, velocity); + velocity[0] += 30.0; + GetAngleVectors(velocity, velocity, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(velocity, velocity); + ScaleVector(velocity, force); + velocity[2] += force; + player.SetPropVector(Prop_Data, "m_vecBaseVelocity", velocity); + } + + float amount = shockwaveData.BatteryDrainPercent[difficulty]; + if (amount > 0.0) + { + player.FlashlightBatteryLife -= amount; + } + + int sprintAmount = shockwaveData.StaminaDrainPercent[difficulty]; + if (sprintAmount > 0.0) + { + player.SetSprintPoints(player.GetSprintPoints() - sprintAmount); + } + + shockwaveData.ApplyDamageEffects(player, difficulty, SF2_ChaserBossEntity(this.index)); + } + } + + delete hitList; + } + + public bool SearchSoundsWithSectionName(ArrayList base, const char[] name, SF2BossProfileSoundInfo output) + { + if (base == null || base.Length <= 0) + { + return false; + } + if (base.Length == 1) + { + base.GetArray(0, output, sizeof(output)); + if (output.SectionName[0] == '\0') + { + return true; + } + } + for (int i = 0; i < base.Length; i++) + { + base.GetArray(i, output, sizeof(output)); + if (strcmp(output.SectionName, name) == 0) + { + return true; + } + } + return false; + } + + public void DoAlwaysLookAt(CBaseEntity target) + { + if (!target.IsValid()) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && !player.IsAlive) + { + return; + } + + INextBot bot = this.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + bool tooClose = this.GetIsVisible(player) && bot.IsRangeLessThan(target.index, 16.0) && this.State != STATE_STUN && this.State != STATE_DEATH && !this.IsKillingSomeone; + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + if (!tooClose && !controller.HasAttribute(SF2Attribute_AlwaysLookAtTarget) && !controller.HasAttribute(SF2Attribute_AlwaysLookAtTargetWhileChasing)) + { + return; + } + + if (!this.IsLOSClearFromTarget(target, false)) + { + return; + } + + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); + } + + public void RegisterProjectiles(bool &isFake = false) + { + if (g_RestartSessionEnabled) + { + return; + } + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (!data.ProjectilesEnabled) + { + return; + } + + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + + if (this.ProjectileCooldown > gameTime) + { + return; + } + + if (!data.ProjectileClips) + { + if (controller.Flags & SFF_FAKE) + { + isFake = true; + return; + } + + this.ShootProjectile(); + } + else + { + if (this.ProjectileAmmo != 0) + { + this.ShootProjectile(); + } + else + { + if (!this.IsReloadingProjectiles) + { + this.ProjectileReloadTime = gameTime + data.ProjectileReloadTime[difficulty]; + this.IsReloadingProjectiles = true; + } + else if (this.ProjectileReloadTime <= gameTime && this.IsReloadingProjectiles) + { + this.ProjectileAmmo = data.ProjectileClipSize[difficulty]; + this.IsReloadingProjectiles = false; + } + } + } + } + + public void ShootProjectile() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + float targetPos[3], myPos[3], myAng[3]; + this.GetAbsOrigin(myPos); + this.GetAbsAngles(myAng); + this.Target.WorldSpaceCenter(targetPos); + int difficulty = controller.Difficulty; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int randomPosMin = data.ProjectileRandomPosMin; + int randomPosMax = data.ProjectileRandomPosMax; + ArrayList array = data.ProjectilePosOffsets; + + bool attackWaiters = (controller.Flags & SFF_ATTACKWAITERS) != 0; + if (originalData.IsPvEBoss) + { + attackWaiters = true; + } + + float effectPos[3]; + + if (randomPosMin == randomPosMax) + { + array.GetArray(0, effectPos); + } + else + { + array.GetArray(GetRandomInt(randomPosMin, randomPosMax), effectPos); + } + + VectorTransform(effectPos, myPos, myAng, effectPos); + + for (int i = 0; i < data.ProjectileCount[difficulty]; i++) + { + float direction[3], angle[3]; + SubtractVectors(targetPos, effectPos, direction); + float deviation = data.ProjectileDeviation[difficulty]; + + if (deviation != 0) + { + direction[0] += GetRandomFloat(-deviation, deviation); + direction[1] += GetRandomFloat(-deviation, deviation); + direction[2] += GetRandomFloat(-deviation, deviation); + } + NormalizeVector(direction, direction); + GetVectorAngles(direction, angle); + + switch (data.ProjectileType) + { + case SF2BossProjectileType_Fireball: + { + SF2_ProjectileFireball.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.FireballExplodeSound, data.FireballTrail, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.FireballShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Iceball: + { + SF2_ProjectileIceball.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.FireballExplodeSound, data.IceballTrail, data.IceballSlowDuration[difficulty], data.IceballSlowPercent[difficulty], data.IceballSlowSound, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.FireballShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Rocket: + { + SF2_ProjectileRocket.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.CriticalProjectiles, data.RocketTrail, data.RocketExplodeParticle, data.RocketExplodeSound, data.RocketModel, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.RocketShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_SentryRocket: + { + SF2_ProjectileSentryRocket.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.CriticalProjectiles, attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.SentryRocketShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Mangler: + { + SF2_ProjectileCowMangler.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.ManglerShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Grenade: + { + SF2_ProjectileGrenade.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.ProjectileRadius[difficulty], data.CriticalProjectiles, "pipebombtrail_blue", ROCKET_EXPLODE_PARTICLE, ROCKET_IMPACT, "models/weapons/w_models/w_grenade_grenadelauncher.mdl", attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.GrenadeShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Arrow: + { + SF2_ProjectileArrow.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.CriticalProjectiles, "pipebombtrail_blue", "weapons/fx/rics/arrow_impact_flesh2.wav", "models/weapons/w_models/w_arrow.mdl", attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.ArrowShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + case SF2BossProjectileType_Baseball: + { + SF2_ProjectileBaseball.Create(this, effectPos, angle, data.ProjectileSpeed[difficulty], data.ProjectileDamage[difficulty], + data.CriticalProjectiles, "models/weapons/w_models/w_baseball.mdl", attackWaiters); + if (i == 0) + { + EmitSoundToAll(data.BaseballShootSound, this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + } + } + } + } + + if (data.ShootGestures) + { + this.RemoveAllGestures(); + char gesture[64]; + strcopy(gesture, sizeof(gesture), data.ShootGestureName); + + int sequence = this.LookupSequence(gesture); + if (sequence != -1) + { + this.AddGestureSequence(sequence); + } + } + + this.ProjectileCooldown = GetRandomFloat(data.ProjectileCooldownMin[difficulty], data.ProjectileCooldownMax[difficulty]) + GetGameTime(); + } + + public void CheckTauntKill(SF2_BasePlayer player) + { + if (SF2_ChaserTauntKillAction.IsPossible(this) && player.IsValid) + { + this.KillTarget = player; + } + } + + public void ForceAlertOtherBosses() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + + if (!data.AlertOnAlertInfo.Enabled[difficulty]) + { + return; + } + + if (!data.AlertOnAlertInfo.Copies[difficulty] && !data.AlertOnAlertInfo.Companions[difficulty]) + { + return; + } + + float goalPos[3]; + if (controller.Path.IsValid()) + { + controller.Path.GetEndPosition(goalPos); + } + else + { + this.GetAbsOrigin(goalPos); + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (!otherChaser.IsValid()) + { + continue; + } + + if (otherChaser.State == STATE_ALERT || otherChaser.State == STATE_CHASE || otherChaser.State == STATE_STUN || otherChaser.State == STATE_DEATH || otherChaser.State == STATE_ATTACK) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnAlertInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnAlertInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnAlertInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnAlertInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + otherChaser.SetForceWanderPosition(goalPos); + otherChaser.AlertWithBoss = true; + } + } + + public void FollowOtherBossAlert() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + float gameTime = GetGameTime(); + + if (!data.AlertOnAlertInfo.Enabled[difficulty]) + { + return; + } + + if (!data.AlertOnAlertInfo.Copies[difficulty] && !data.AlertOnAlertInfo.Companions[difficulty]) + { + return; + } + + if (!data.AlertOnAlertInfo.Follow[difficulty] || this.FollowedCompanionAlert || gameTime < this.FollowCooldownAlert) + { + return; + } + + if (this.State == STATE_ALERT || this.State == STATE_CHASE || this.State == STATE_STUN || this.State == STATE_DEATH || this.State == STATE_ATTACK) + { + return; + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (otherChaser.State != STATE_ALERT) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + + if (!otherData.AlertOnAlertInfo.Copies[difficulty] && !otherData.AlertOnAlertInfo.Companions[difficulty]) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnAlertInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnAlertInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnAlertInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnAlertInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + float goalPos[3]; + if (!otherController.Path.IsValid()) + { + otherChaser.GetAbsOrigin(goalPos); + } + else + { + otherController.Path.GetEndPosition(goalPos); + } + this.SetForceWanderPosition(goalPos); + this.FollowedCompanionAlert = true; + otherChaser.FollowedCompanionAlert = true; + return; + } + } + + public void ForceChaseOtherBosses() + { + CBaseEntity target = this.Target; + if (!target.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + + if (!data.AlertOnChaseInfo.Enabled[difficulty]) + { + return; + } + + if (!data.AlertOnChaseInfo.Copies[difficulty] && !data.AlertOnChaseInfo.Companions[difficulty]) + { + return; + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (!otherChaser.IsValid()) + { + continue; + } + + if (otherChaser.State == STATE_CHASE || otherChaser.State == STATE_STUN || otherChaser.State == STATE_DEATH || otherChaser.State == STATE_ATTACK) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnChaseInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnChaseInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnChaseInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnChaseInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid) + { + player.SetForceChaseState(otherController, true); + } + } + } + + public void FollowOtherBossChase() + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + int difficulty = controller.Difficulty; + SF2NPC_BaseNPC copyMaster = controller.CopyMaster; + SF2NPC_BaseNPC companionMaster = controller.CompanionMaster; + SF2ChaserBossProfileData data, otherData; + data = controller.GetProfileData(); + float gameTime = GetGameTime(); + + if (!data.AlertOnChaseInfo.Enabled[difficulty] || !data.AlertOnChaseInfo.Follow[difficulty]) + { + return; + } + + if (!data.AlertOnChaseInfo.Copies[difficulty] && !data.AlertOnChaseInfo.Companions[difficulty]) + { + return; + } + + if (this.FollowedCompanionChase && this.State != STATE_CHASE && this.State != STATE_STUN && this.State != STATE_DEATH && this.State != STATE_ATTACK && gameTime >= this.FollowCooldownChase) + { + this.FollowedCompanionChase = false; + } + + if (this.FollowedCompanionChase) + { + return; + } + + for (int index = 0; index < MAX_BOSSES; index++) + { + if (index == controller.Index) + { + continue; + } + + SF2NPC_BaseNPC otherController = SF2NPC_BaseNPC(index); + if (!otherController.IsValid() || otherController.Type != SF2BossType_Chaser || otherController.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity otherChaser = SF2_ChaserEntity(otherController.EntIndex); + if (otherChaser.State != STATE_CHASE && otherChaser.State != STATE_STUN && otherChaser.State != STATE_ATTACK) + { + continue; + } + + otherData = view_as(otherController).GetProfileData(); + + if (!otherData.AlertOnChaseInfo.Copies[difficulty] && !otherData.AlertOnChaseInfo.Companions[difficulty]) + { + continue; + } + + SF2NPC_BaseNPC otherCopyMaster = otherController.CopyMaster; + SF2NPC_BaseNPC otherCompanionMaster = otherController.CompanionMaster; + bool doContinue = false; + if (data.AlertOnChaseInfo.Copies[difficulty]) + { + if (copyMaster != otherController && otherCopyMaster != copyMaster && otherCopyMaster != controller) + { + doContinue = true; + } + } + + if (data.AlertOnChaseInfo.Companions[difficulty]) + { + SF2NPC_BaseNPC tempCompanionMaster = companionMaster, tempOtherCompanionMaster = otherCompanionMaster; + if (otherController.IsCopy) + { + tempOtherCompanionMaster = otherController.CopyMaster.CompanionMaster; + if (otherController.CopyMaster == controller) + { + doContinue = true; + } + } + + if (controller.IsCopy) + { + tempCompanionMaster = controller.CopyMaster.CompanionMaster; + if (controller.CopyMaster == otherController) + { + doContinue = true; + } + } + + if (!doContinue) + { + if ((tempCompanionMaster != otherController && tempOtherCompanionMaster != tempCompanionMaster && tempOtherCompanionMaster != controller) || + (!tempCompanionMaster.IsValid() && !tempOtherCompanionMaster.IsValid())) + { + tempOtherCompanionMaster = otherController.CopyMaster; + if (tempOtherCompanionMaster != tempCompanionMaster) + { + doContinue = true; + } + else + { + doContinue = false; + } + } + else + { + doContinue = false; + } + } + } + + if (doContinue) + { + continue; + } + + float distance = this.MyNextBotPointer().GetRangeSquaredTo(otherController.EntIndex); + if (distance > Pow(otherData.AlertOnChaseInfo.Radius[difficulty], 2.0)) + { + continue; + } + + if (otherData.AlertOnChaseInfo.ShouldBeVisible[difficulty] && !otherChaser.IsLOSClearFromTarget(this)) + { + continue; + } + + SF2_BasePlayer player = SF2_BasePlayer(otherChaser.Target.index); + if (player.IsValid) + { + player.SetForceChaseState(controller, true); + this.FollowedCompanionChase = true; + this.Target = otherChaser.Target; + return; + } + } + } + + public void ProcessCloak(CBaseEntity target) + { + SF2NPC_Chaser controller = this.Controller; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + float gameTime = GetGameTime(); + if (!data.CloakData.Enabled[difficulty]) + { + return; + } + + if (this.IsInChaseInitial) + { + return; + } + + if (this.IsAttacking) + { + return; + } + + if (!target.IsValid()) + { + return; + } + + float targetPos[3], myPos[3]; + target.GetAbsOrigin(targetPos); + this.GetAbsOrigin(myPos); + if (this.HasCloaked && GetVectorSquareMagnitude(targetPos, myPos) <= Pow(data.CloakData.DecloakRange[difficulty], 2.0)) + { + this.EndCloak(); + return; + } + + if (!this.HasCloaked && GetVectorSquareMagnitude(targetPos, myPos) > Pow(data.CloakData.CloakRange[difficulty], 2.0)) + { + return; + } + + if (this.CloakTime < gameTime) + { + if (this.HasCloaked) + { + this.EndCloak(); + } + else + { + this.StartCloak(); + } + } + } + + public void StartCloak() + { + if (this.HasCloaked) + { + return; + } + + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.CloakData.Enabled[difficulty]) + { + return; + } + + this.SetRenderMode(view_as(data.CloakData.CloakRenderMode)); + this.SetRenderColor(data.CloakData.CloakRenderColor[0], data.CloakData.CloakRenderColor[1], data.CloakData.CloakRenderColor[2], data.CloakData.CloakRenderColor[3]); + this.HasCloaked = true; + this.CloakTime = GetGameTime() + data.CloakData.Duration[difficulty]; + float worldPos[3]; + this.WorldSpaceCenter(worldPos); + SlenderToggleParticleEffects(this.index); + SlenderSpawnEffects(data.CloakData.CloakEffects, controller.Index, false); + Call_StartForward(g_OnBossCloakedFwd); + Call_PushCell(controller.Index); + Call_Finish(); + } + + public void EndCloak() + { + if (!this.HasCloaked) + { + return; + } + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.CloakData.Enabled[difficulty]) + { + return; + } + + this.SetRenderMode(view_as(controller.GetRenderMode)); + this.SetRenderColor(controller.GetRenderColor(0), controller.GetRenderColor(1), controller.GetRenderColor(2), controller.GetRenderColor(3)); + this.HasCloaked = false; + this.CloakTime = GetGameTime() + data.CloakData.Cooldown[difficulty]; + float worldPos[3]; + this.WorldSpaceCenter(worldPos); + SlenderToggleParticleEffects(this.index, true); + SlenderSpawnEffects(data.CloakData.DecloakEffects, controller.Index, false); + Call_StartForward(g_OnBossDecloakedFwd); + Call_PushCell(controller.Index); + Call_Finish(); + } + + public void DropItem(bool death = false) + { + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + bool check = !death ? data.ItemDropOnStun[difficulty] : data.DeathData.ItemDrop[difficulty]; + if (!check) + { + return; + } + + int type = !death ? data.StunItemDropType[difficulty] : data.DeathData.ItemDropType[difficulty]; + char class[64]; + switch (type) + { + case 1: + { + class = "item_ammopack_small"; + } + + case 2: + { + class = "item_ammopack_medium"; + } + + case 3: + { + class = "item_ammopack_full"; + } + + case 4: + { + class = "item_healthkit_small"; + } + + case 5: + { + class = "item_healthkit_medium"; + } + + case 6: + { + class = "item_healthkit_full"; + } + + case 7: + { + class = "item_healthammokit"; + } + } + + float myPos[3]; + this.GetAbsOrigin(myPos); + CBaseEntity item = CBaseEntity(CreateEntityByName(class)); + item.KeyValue("OnPlayerTouch", "!self,Kill,,0,-1"); + item.Spawn(); + item.SetProp(Prop_Send, "m_iTeamNum", 0); + item.Teleport(myPos, NULL_VECTOR, NULL_VECTOR); + } + + public void ProcessTraps() + { + SF2NPC_Chaser controller = this.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.Traps[difficulty]) + { + return; + } + + if (this.State != STATE_IDLE && this.State != STATE_ALERT) + { + return; + } + + float gameTime = GetGameTime(); + if (this.TrapCooldown > gameTime) + { + return; + } + + float myPos[3], myAng[3]; + this.GetAbsOrigin(myPos); + this.GetAbsAngles(myAng); + Trap_SpawnTrap(myPos, myAng, controller); + this.TrapCooldown = gameTime + data.TrapCooldown[difficulty]; + } + + public static SF2_ChaserEntity Create(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(CreateEntityByName("sf2_npc_boss_chaser")); + if (!chaser.IsValid()) + { + return SF2_ChaserEntity(-1); + } + if (controller == SF2_INVALID_NPC) + { + return SF2_ChaserEntity(-1); + } + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + + controller.GetProfile(profile, sizeof(profile)); + chaser.Controller = view_as(controller); + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(chaser.Controller).GetProfileData(); + + char buffer[PLATFORM_MAX_PATH]; + + GetSlenderModel(controller.Index, _, buffer, sizeof(buffer)); + chaser.SetModel(buffer); + chaser.SetRenderMode(view_as(g_SlenderRenderMode[controller.Index])); + chaser.SetRenderFx(view_as(g_SlenderRenderFX[controller.Index])); + chaser.SetRenderColor(g_SlenderRenderColor[controller.Index][0], g_SlenderRenderColor[controller.Index][1], + g_SlenderRenderColor[controller.Index][2], g_SlenderRenderColor[controller.Index][3]); + + chaser.SetDefaultPosture(SF2_PROFILE_CHASER_DEFAULT_POSTURE); + chaser.SetPosture(SF2_PROFILE_CHASER_DEFAULT_POSTURE); + + if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) + { + float scaleModel = controller.ModelScale * 0.5; + chaser.SetPropFloat(Prop_Send, "m_flModelScale", scaleModel); + } + else + { + chaser.SetPropFloat(Prop_Send, "m_flModelScale", controller.ModelScale); + } + + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + CBaseNPC_Locomotion locomotion = npc.GetLocomotion(); + + npc.flStepSize = 18.0; + npc.flGravity = g_Gravity; + npc.flDeathDropHeight = 99999.0; + npc.flJumpHeight = 512.0; + + npc.flMaxYawRate = originalData.TurnRate; + + float addStunHealth = data.StunHealthPerPlayer; + float classAdd; + int count; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (player.IsValid && !player.IsEliminated && !player.HasEscaped) + { + count++; + + switch (player.Class) + { + case TFClass_Scout: + { + classAdd += data.StunHealthPerClass[1]; + } + + case TFClass_Soldier: + { + classAdd += data.StunHealthPerClass[3]; + } + + case TFClass_Pyro: + { + classAdd += data.StunHealthPerClass[7]; + } + + case TFClass_DemoMan: + { + classAdd += data.StunHealthPerClass[4]; + } + + case TFClass_Heavy: + { + classAdd += data.StunHealthPerClass[6]; + } + + case TFClass_Engineer: + { + classAdd += data.StunHealthPerClass[9]; + } + + case TFClass_Medic: + { + classAdd += data.StunHealthPerClass[5]; + } + + case TFClass_Sniper: + { + classAdd += data.StunHealthPerClass[2]; + } + + case TFClass_Spy: + { + classAdd += data.StunHealthPerClass[8]; + } + } + } + } + + addStunHealth *= float(count); + chaser.StunHealth = data.StunHealth[controller.Difficulty] + addStunHealth + classAdd; + chaser.MaxStunHealth = chaser.StunHealth; + + locomotion.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); + locomotion.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); + + float pathingMins[3], pathingMaxs[3]; + + if (controller.RaidHitbox) + { + pathingMins = g_SlenderDetectMins[controller.Index]; + pathingMaxs = g_SlenderDetectMaxs[controller.Index]; + + chaser.SetPropVector(Prop_Send, "m_vecMins", g_SlenderDetectMins[controller.Index]); + chaser.SetPropVector(Prop_Send, "m_vecMaxs", g_SlenderDetectMaxs[controller.Index]); + + chaser.SetPropVector(Prop_Send, "m_vecMinsPreScaled", g_SlenderDetectMins[controller.Index]); + chaser.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", g_SlenderDetectMaxs[controller.Index]); + } + else + { + pathingMins = HULL_HUMAN_MINS; + pathingMaxs = HULL_HUMAN_MAXS; + + chaser.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); + chaser.SetPropVector(Prop_Send, "m_vecMaxs", HULL_HUMAN_MAXS); + + chaser.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); + chaser.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); + } + + npc.SetBodyMins(pathingMins); + npc.SetBodyMaxs(pathingMaxs); + + if (SF_IsBoxingMap() || originalData.IsPvEBoss) + { + chaser.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + } + + for (int difficulty2 = 0; difficulty2 < Difficulty_Max; difficulty2++) + { + g_SlenderTimeUntilKill[controller.Index] = GetGameTime() + NPCGetIdleLifetime(controller.Index, difficulty2); + } + + IVision vision = chaser.MyNextBotPointer().GetVisionInterface(); + vision.SetFieldOfView(originalData.FOV); + vision.ForgetAllKnownEntities(); + + chaser.NextAttackTime = new StringMap(); + + chaser.TrapCooldown = GetGameTime() + data.TrapCooldown[controller.Difficulty]; + + chaser.Teleport(pos, ang, NULL_VECTOR); + + chaser.Spawn(); + chaser.Activate(); + + return chaser; + } + + public void InvokeOnStartAttack(const char[] attackName) + { + Call_StartForward(g_OnChaserBossStartAttackFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_Finish(); + } + + public void InvokeOnEndAttack(const char[] attackName) + { + Call_StartForward(g_OnChaserBossEndAttackFwd); + Call_PushCell(this); + Call_PushString(attackName); + Call_Finish(); + } + + public static void SetupAPI() + { + CreateNative("SF2_ChaserBossEntity.IsValid.get", Native_GetIsValid); + CreateNative("SF2_ChaserBossEntity.IsAttemptingToMove.get", Native_GetIsAttemptingToMove); + CreateNative("SF2_ChaserBossEntity.IsAttacking.get", Native_GetIsAttacking); + CreateNative("SF2_ChaserBossEntity.IsStunned.get", Native_GetIsStunned); + CreateNative("SF2_ChaserBossEntity.StunHealth.get", Native_GetStunHealth); + CreateNative("SF2_ChaserBossEntity.MaxStunHealth.get", Native_GetMaxStunHealth); + CreateNative("SF2_ChaserBossEntity.CanBeStunned.get", Native_GetCanBeStunned); + CreateNative("SF2_ChaserBossEntity.CanTakeDamage.get", Native_GetCanTakeDamage); + CreateNative("SF2_ChaserBossEntity.IsRaging.get", Native_GetIsRaging); + CreateNative("SF2_ChaserBossEntity.IsRunningAway.get", Native_GetIsRunningAway); + CreateNative("SF2_ChaserBossEntity.IsSelfHealing.get", Native_GetIsSelfHealing); + CreateNative("SF2_ChaserBossEntity.ProfileData", Native_GetProfileData); + CreateNative("SF2_ChaserBossEntity.PerformVoice", Native_PerformVoice); + CreateNative("SF2_ChaserBossEntity.GetDefaultPosture", Native_GetDefaultPosture); + CreateNative("SF2_ChaserBossEntity.SetDefaultPosture", Native_SetDefaultPosture); + CreateNative("SF2_ChaserBossEntity.GetAttackName", Native_GetAttackName); + CreateNative("SF2_ChaserBossEntity.GetNextAttackTime", Native_GetNextAttackTime); + CreateNative("SF2_ChaserBossEntity.SetNextAttackTime", Native_SetNextAttackTime); + CreateNative("SF2_ChaserBossEntity.DropItem", Native_DropItem); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + chaser.SetAlertTriggerCount(client, 0); + chaser.SetAlertSoundTriggerCooldown(client, 0.0); + chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); + chaser.SetAutoChaseCount(client, 0); + chaser.SetAutoChaseCooldown(client, -1.0); + } +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) + { + return; + } + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); + } + + SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); + if (!boss.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = boss.Controller; + if (!controller.IsValid()) + { + return; + } + + boss.PerformVoice(SF2BossSound_AttackKilled); + + SF2BossProfileSoundInfo info; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + info = data.AttackKilledClientSounds; + info.EmitSound(true, client.index); + + info = data.AttackKilledAllSounds; + if (info.Paths != null && info.Paths.Length > 0) + { + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + info.EmitSound(true, i); + } + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); + } +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); + } +} + +static void OnCreate(SF2_ChaserEntity ent) +{ + ent.Target = CBaseEntity(INVALID_ENT_REFERENCE); + ent.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + ent.IsAllowedToDespawn = true; + SDKHook(ent.index, SDKHook_Think, Think); + SDKHook(ent.index, SDKHook_ThinkPost, ThinkPost); + SDKHook(ent.index, SDKHook_SpawnPost, SpawnPost); + SDKHook(ent.index, SDKHook_OnTakeDamage, OnTakeDamage); + SDKHook(ent.index, SDKHook_OnTakeDamageAlivePost, OnTakeDamageAlivePost); + SDKHook(ent.index, SDKHook_TraceAttack, TraceOnHit); + SDKHook(ent.index, SDKHook_SetTransmit, SetTransmit); + SetEntityTransmitState(ent.index, FL_EDICT_FULLCHECK); + g_DHookShouldTransmit.HookEntity(Hook_Pre, ent.index, ShouldTransmit); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent.index, UpdateTransmitState); +} + +static void OnRemove(SF2_ChaserEntity ent) +{ + if (ent.SmellPlayerList != null) + { + delete ent.SmellPlayerList; + } + delete ent.NextAttackTime; + + KillPvEBoss(ent.index); +} + +static Action Think(int entIndex) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(entIndex); + int interruptConditions = 0; + CBaseEntity target = ProcessVision(chaser, interruptConditions); + chaser.InterruptConditions |= interruptConditions; + chaser.Target = target; + + if (chaser.State == STATE_CHASE) + { + chaser.DoAlwaysLookAt(chaser.Target); + } + + float gameTime = GetGameTime(); + if (chaser.LegacyFootstepInterval > 0.0 && chaser.LegacyFootstepTime < gameTime) + { + chaser.CastFootstep(); + } + + return Plugin_Continue; +} + +static void ThinkPost(int entIndex) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(entIndex); + + ProcessSpeed(chaser); + + ProcessBody(chaser); + + if (NPCGetCustomOutlinesState(chaser.Controller.Index) && NPCGetRainbowOutlineState(chaser.Controller.Index)) + { + chaser.ProcessRainbowOutline(); + } + + if (chaser.State == STATE_CHASE) + { + chaser.DoAlwaysLookAt(chaser.Target); + } + + chaser.InterruptConditions = 0; + chaser.SetNextThink(GetGameTime()); + + #if defined DEBUG + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || (g_PlayerDebugFlags[player.index] & DEBUG_BOSS_HITBOX) == 0) + { + continue; + } + + float mins[3], maxs[3], origin[3]; + chaser.GetAbsOrigin(origin); + npc.GetBodyMins(mins); + npc.GetBodyMaxs(maxs); + TE_DrawBox(player.index, origin, origin, mins, maxs, 0.1, g_ShockwaveBeam, { 0, 255, 0, 255 }); + } + #endif +} + +static void SpawnPost(int entIndex) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(entIndex); + SF2NPC_Chaser controller = chaser.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int difficulty = controller.Difficulty; + if (!data.DeathData.Enabled[difficulty]) + { + chaser.SetProp(Prop_Data, "m_iHealth", 2000000000); + chaser.SetProp(Prop_Data, "m_takedamage", DAMAGE_EVENTS_ONLY); + } + else + { + chaser.SetProp(Prop_Data, "m_iHealth", RoundToFloor(controller.GetDeathHealth(difficulty))); + chaser.SetProp(Prop_Data, "m_takedamage", DAMAGE_YES); + + float add = 0.0; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + if (originalData.IsPvEBoss) + { + if (!player.IsEliminated) + { + continue; + } + } + else + { + if (player.IsEliminated || !player.IsAlive || player.HasEscaped) + { + continue; + } + } + + switch (player.Class) + { + case TFClass_Scout: + { + add += data.DeathData.AddHealthPerScout[difficulty]; + } + + case TFClass_Soldier: + { + add += data.DeathData.AddHealthPerSoldier[difficulty]; + } + + case TFClass_Pyro: + { + add += data.DeathData.AddHealthPerPyro[difficulty]; + } + + case TFClass_DemoMan: + { + add += data.DeathData.AddHealthPerDemoman[difficulty]; + } + + case TFClass_Heavy: + { + add += data.DeathData.AddHealthPerHeavy[difficulty]; + } + + case TFClass_Engineer: + { + add += data.DeathData.AddHealthPerEngineer[difficulty]; + } + + case TFClass_Medic: + { + add += data.DeathData.AddHealthPerMedic[difficulty]; + } + + case TFClass_Sniper: + { + add += data.DeathData.AddHealthPerSniper[difficulty]; + } + + case TFClass_Spy: + { + add += data.DeathData.AddHealthPerSpy[difficulty]; + } + } + } + + chaser.SetProp(Prop_Data, "m_iHealth", chaser.GetProp(Prop_Data, "m_iHealth") + RoundToFloor(add)); + } + chaser.MaxHealth = float(chaser.GetProp(Prop_Data, "m_iHealth")); + if (SF2_ChaserSmellAction.IsPossible(chaser)) + { + chaser.SmellPlayerList = new ArrayList(); + } + else + { + chaser.SmellPlayerList = null; + } + chaser.RageIndex = -1; + + if (originalData.Healthbar) + { + controller.Flags |= SFF_NOTELEPORT; + UpdateHealthBar(controller.Index); + } +} + +static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damageType, int &weapon, float damageForce[3], float damagePosition[3], int damageCustom) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(victim); + SF2_BasePlayer player = SF2_BasePlayer(attacker); + + if (player.IsValid && player.IsProxy) + { + return Plugin_Handled; + } + + if (!chaser.CanTakeDamage(CBaseEntity(attacker), CBaseEntity(inflictor), damage)) + { + return Plugin_Handled; + } + + return Plugin_Continue; +} + +static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float damage, int damageType, int weaponEntIndex, const float vecDamageForce[3], const float vecDamagePosition[3]) +{ + SF2_ChaserEntity chaser = SF2_ChaserEntity(victim); + SF2_BasePlayer player = SF2_BasePlayer(attacker); + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); + + bool broadcastDamage = false; + + if (!chaser.CanTakeDamage(CBaseEntity(attacker), CBaseEntity(inflictor), damage)) + { + damage = 0.0; + return; + } + + if (player.IsValid) + { + CBaseEntity weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); + if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) + { + switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 447, 426: // Whip + Eviction Notice + { + player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); + } + + case 416: // Market Gardener + { + float zVelocity[3]; + player.GetPropVector(Prop_Data, "m_vecVelocity", zVelocity); + if (zVelocity[2] < 0.0) // A soldier has the market gardener and is currently falling down, like Minecraft with its critical hits. + { + damageType |= DMG_CRIT; + damage *= 2.0; + } + } + } + } + } + + if (damage > 0.0) + { + if (!player.IsValid || !player.IsProxy) + { + if (chaser.CanBeStunned()) + { + broadcastDamage = true; + } + if (chaser.GetProp(Prop_Data, "m_takedamage") == DAMAGE_YES) + { + broadcastDamage = true; + } + } + else + { + damage = 0.0; + } + } + + if (broadcastDamage) + { + bool miniCrit = false; + + if (player.IsValid && player.IsMiniCritBoosted()) + { + miniCrit = true; + } + + if (player.IsValid && player.IsCritBoosted() || (damageType & DMG_CRIT)) + { + miniCrit = false; + } + + Event event = CreateEvent("npc_hurt"); + if (event) + { + event.SetInt("entindex", chaser.index); + event.SetInt("health", chaser.GetProp(Prop_Data, "m_iHealth")); + event.SetInt("damageamount", RoundToFloor(damage)); + event.SetBool("crit", (damageType & DMG_CRIT) ? true : false); + + if (player.IsValid) + { + event.SetInt("attacker_player", player.UserID); + event.SetInt("weaponid", 0); + } + else + { + event.SetInt("attacker_player", 0); + event.SetInt("weaponid", 0); + } + + event.Fire(); + } + + if (player.IsValid) + { + if ((damageType & DMG_CRIT) || miniCrit) + { + float myEyePos[3]; + chaser.GetAbsOrigin(myEyePos); + float myEyePosEx[3]; + chaser.GetPropVector(Prop_Send, "m_vecMaxs", myEyePosEx); + myEyePos[2] += myEyePosEx[2]; + + if ((damageType & DMG_CRIT) && !miniCrit) + { + TE_Particle(g_Particles[CriticalHit], myEyePos, myEyePos); + TE_SendToClient(player.index); + + EmitSoundToClient(player.index, CRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + else if (miniCrit) + { + TE_Particle(g_Particles[MiniCritHit], myEyePos, myEyePos); + TE_SendToClient(player.index); + + EmitSoundToClient(player.index, MINICRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + } + } + } + + if (data.Healthbar) + { + UpdateHealthBar(chaser.Controller.Index); + } +} + +static Action TraceOnHit(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& ammotype, int hitbox, int hitgroup) +{ + if (IsValidClient(attacker)) + { + if ((damagetype & DMG_USE_HITLOCATIONS) && hitgroup == 1) + { + damagetype = damagetype | DMG_CRIT; + return Plugin_Changed; + } + } + return Plugin_Continue; +} + +static Action SetTransmit(int ent, int other) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + SF2_BasePlayer player = SF2_BasePlayer(other); + SF2_ChaserEntity chaser = SF2_ChaserEntity(ent); + + if (player.IsValid && player.IsInDeathCam && chaser.KillTarget != player) + { + return Plugin_Handled; + } + + return Plugin_Continue; +} + +static MRESReturn UpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = SetEntityTransmitState(entIndex, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn ShouldTransmit(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} + +static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditions = 0) +{ + interruptConditions = 0; + IVision vision = chaser.MyNextBotPointer().GetVisionInterface(); + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return CBaseEntity(-1); + } + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + if (originalData.IsPvEBoss) + { + attackEliminated = originalData.IsPvEBoss; + } + + int difficulty = controller.Difficulty; + + float playerDists[MAXTF2PLAYERS]; + int playerInterruptFlags[MAXTF2PLAYERS]; + + float traceMins[3] = { -16.0, ... }; + traceMins[2] = 0.0; + float traceMaxs[3] = { 16.0, ... }; + traceMaxs[2] = 0.0; + float buffer[3], myEyeAng[3]; + chaser.GetAbsAngles(myEyeAng); + float traceStartPos[3], traceEndPos[3]; + controller.GetEyePosition(traceStartPos); + + if (originalData.EyeData.Type == 1) + { + if (chaser.EyeBoneIndex <= 0) + { + chaser.EyeBoneIndex = LookupBone(chaser.index, originalData.EyeData.Bone); + } + GetBonePosition(chaser.index, chaser.EyeBoneIndex, traceStartPos, myEyeAng); + float offset[3]; + controller.GetEyePositionOffset(offset); + AddVectors(originalData.EyeData.OffsetAng, myEyeAng, myEyeAng); + VectorTransform(offset, traceStartPos, myEyeAng, traceStartPos); + } + + int oldTarget = chaser.OldTarget.index; + if (!IsTargetValidForSlender(SF2_BasePlayer(oldTarget), attackEliminated)) + { + chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(SF2_BasePlayer(oldTarget))) + { + chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + int bestNewTarget = oldTarget; + float searchRange = originalData.SearchRange[difficulty]; + float bestNewTargetDist = Pow(searchRange, 2.0); + + if (chaser.SmellPlayerList != null) + { + chaser.SmellPlayerList.Clear(); + } + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + + if (client.IsValid && g_PlayerDebugFlags[client.index] & DEBUG_BOSS_EYES) + { + float end[3]; + end[0] = 1000.0; + VectorTransform(end, traceStartPos, myEyeAng, end); + int color[4] = { 0, 255, 0, 255 }; + TE_SetupBeamPoints(traceStartPos, end, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); + TE_SendToClient(client.index); + } + + if (!IsTargetValidForSlender(client, attackEliminated) && !originalData.IsPvEBoss) + { + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(client)) + { + continue; + } + + TFClassType classType = client.Class; + int classToInt = view_as(classType); + + chaser.SetIsVisible(client, false); + chaser.SetInFOV(client, false); + chaser.SetIsNear(client, false); + + client.GetEyePosition(traceEndPos); + + float dist = 99999999999.9; + + bool isVisible, isTraceVisible; + int traceHitEntity; + if (data.UnnerfedVisibility) + { + Handle trace = TR_TraceRayFilterEx(traceStartPos, + traceEndPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, + RayType_EndPoint, + TraceRayBossVisibility, + chaser.index); + isVisible = !TR_DidHit(trace); + traceHitEntity = TR_GetEntityIndex(trace); + + delete trace; + } + else + { + TR_TraceHullFilter(traceStartPos, + traceEndPos, + traceMins, + traceMaxs, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, + TraceRayBossVisibility, + chaser.index); + + isVisible = !TR_DidHit(); + traceHitEntity = TR_GetEntityIndex(); + } + + if (!isVisible && traceHitEntity == client.index) + { + isVisible = true; + isTraceVisible = true; + } + + if (isVisible) + { + isVisible = NPCShouldSeeEntity(controller.Index, client.index); + } + + dist = GetVectorSquareMagnitude(traceStartPos, traceEndPos); + + if (g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) + { + int fogEntity = client.GetDataEnt(g_PlayerFogCtrlOffset); + if (IsValidEdict(fogEntity)) + { + if (GetEntData(fogEntity, g_FogCtrlEnableOffset) && + dist >= SquareFloat(GetEntDataFloat(fogEntity, g_FogCtrlEndOffset))) + { + isVisible = false; + isTraceVisible = false; + } + } + } + + if (dist > Pow(originalData.SearchRange[difficulty], 2.0)) + { + isVisible = false; + } + + float priorityValue; + + chaser.SetIsVisible(client, isVisible); + + // Near radius check. + if (chaser.GetIsVisible(client) && + dist <= SquareFloat(data.WakeRadius)) + { + chaser.SetIsNear(client, true); + playerInterruptFlags[client.index] |= COND_ENEMYNEAR; + } + if (chaser.GetIsVisible(client) && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) + { + TF2_StunPlayer(client.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); + } + + // FOV check. + SubtractVectors(traceEndPos, traceStartPos, buffer); + GetVectorAngles(buffer, buffer); + + if (FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (vision.GetFieldOfView() * 0.5)) + { + chaser.SetInFOV(client, true); + } + + if (chaser.GetIsVisible(client)) + { + playerInterruptFlags[client.index] |= COND_ENEMYVISIBLE; + if (chaser.GetInFOV(client)) + { + playerInterruptFlags[client.index] |= COND_SAWENEMY; + } + } + + playerDists[client.index] = dist; + + if (chaser.SmellPlayerList != null && data.SmellData.Enabled[difficulty]) + { + if (dist < Pow(data.SmellData.PlayerRange[difficulty], 2.0)) + { + chaser.SmellPlayerList.Push(client.index); + } + } + + if (client.IsTrapped && (chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) && dist <= SquareFloat(searchRange)) + { + client.SetForceChaseState(controller, true); + } + + if (client.ShouldBeForceChased(controller)) + { + bestNewTarget = client.index; + playerInterruptFlags[client.index] |= COND_ENEMYRECHASE; + } + + if (client.index != oldTarget) + { + playerInterruptFlags[client.index] |= COND_NEWENEMY; + } + + if (!SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !data.ChasesEndlessly) + { + priorityValue = g_PageMax > 0 ? ((float(client.PageCount) / float(g_PageMax)) / 4.0) : 0.0; + } + + if (!IsClassConfigsValid()) + { + if ((classType == TFClass_Medic || client.HasRegenItem) && !SF_IsBoxingMap()) + { + priorityValue += 0.2; + } + + if (classType == TFClass_Spy) + { + priorityValue += 0.1; + } + } + else + { + if (!SF_IsBoxingMap() && client.HasRegenItem) + { + priorityValue += 0.2; + } + priorityValue += g_ClassBossPriorityMultiplier[classToInt]; + } + + if (chaser.GetIsNear(client) || (chaser.GetIsVisible(client) && chaser.GetInFOV(client))) + { + float targetPos[3]; + client.GetAbsOrigin(targetPos); + if (dist <= SquareFloat(searchRange)) + { + // Subtract distance to increase priority. + dist -= ((dist * priorityValue)); + + if (dist < bestNewTargetDist) + { + bestNewTarget = client.index; + bestNewTargetDist = dist; + playerInterruptFlags[client.index] |= COND_SAWENEMY; + } + } + } + + if (data.ChaseOnLookData.Enabled[difficulty] && isTraceVisible) + { + bool shouldCalculate = false; + if (data.ChaseOnLookData.RequiredFOV[difficulty] <= 0.0) + { + shouldCalculate = chaser.GetInFOV(client); + } + else + { + shouldCalculate = FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (data.ChaseOnLookData.RequiredFOV[difficulty] * 0.5); + } + if (shouldCalculate) + { + float eyeAng[3], expectedAng[3], lookPos[3], myPos[3]; + client.GetEyeAngles(eyeAng); + chaser.GetAbsOrigin(myPos); + lookPos = data.ChaseOnLookData.RequiredLookPosition; + VectorTransform(lookPos, myPos, myEyeAng, lookPos); + SubtractVectors(lookPos, traceEndPos, expectedAng); + GetVectorAngles(expectedAng, expectedAng); + float minimumXAng = data.ChaseOnLookData.MinimumXAngle[difficulty] * 0.5; + float maximumXAng = data.ChaseOnLookData.MaximumXAngle[difficulty] * 0.5; + float minimumYAng = data.ChaseOnLookData.MinimumYAngle[difficulty] * 0.5; + float maximumYAng = data.ChaseOnLookData.MaximumYAngle[difficulty] * 0.5; + float xAng, yAng; + xAng = AngleDiff(eyeAng[0], expectedAng[0]); + yAng = FloatAbs(AngleDiff(eyeAng[1], expectedAng[1])); + if ((xAng >= minimumXAng && xAng < maximumXAng) && (yAng >= minimumYAng && yAng < maximumYAng) && + ((data.ChaseOnLookData.AddTargets[difficulty]) || (!data.ChaseOnLookData.AddTargets[difficulty] && controller.ChaseOnLookTargets.Length == 0))) + { + controller.ChaseOnLookTargets.Push(client.index); + SF2BossProfileSoundInfo soundInfo; + soundInfo = originalData.ScareSounds; + soundInfo.EmitSound(true, client.index); + client.ChangeCondition(TFCond_MarkedForDeathSilent); + } + } + } + } + + if (bestNewTarget != INVALID_ENT_REFERENCE) + { + interruptConditions = playerInterruptFlags[bestNewTarget]; + chaser.OldTarget = CBaseEntity(bestNewTarget); + } + + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly) + { + if (!IsTargetValidForSlender(SF2_BasePlayer(bestNewTarget), attackEliminated)) + { + if (chaser.State != STATE_CHASE && NPCAreAvailablePlayersAlive()) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || + !IsPlayerAlive(i) || + g_PlayerEliminated[i] || + IsClientInGhostMode(i) || + DidClientEscape(i)) + { + continue; + } + arrayRaidTargets.Push(i); + } + if (arrayRaidTargets.Length > 0) + { + int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); + if (IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) + { + bestNewTarget = raidTarget; + SF2_BasePlayer(bestNewTarget).SetForceChaseState(controller, true); + } + } + delete arrayRaidTargets; + } + } + chaser.CurrentChaseDuration = data.ChaseDuration[difficulty] + GetGameTime(); + } + + if (originalData.IsPvEBoss) + { + if (!IsPvETargetValid(SF2_BasePlayer(bestNewTarget))) + { + if (chaser.State != STATE_CHASE) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || + !IsPlayerAlive(i) || + !IsClientInPvE(i)) + { + continue; + } + arrayRaidTargets.Push(i); + } + if (arrayRaidTargets.Length > 0) + { + int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); + if (IsValidClient(raidTarget) && IsClientInPvE(raidTarget)) + { + bestNewTarget = raidTarget; + SF2_BasePlayer(bestNewTarget).SetForceChaseState(controller, true); + } + } + delete arrayRaidTargets; + } + } + chaser.CurrentChaseDuration = data.ChaseDuration[difficulty] + GetGameTime(); + } + + return CBaseEntity(bestNewTarget); +} + +static void ProcessSpeed(SF2_ChaserEntity chaser) +{ + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return; + } + SF2NPC_BaseNPC baseController = view_as(controller); + int difficulty = controller.Difficulty; + SF2NPCMoveTypes moveType = chaser.MovementType; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + float gameTime = GetGameTime(); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = baseController.GetProfileData(); + SF2ChaserBossProfilePostureInfo postureInfo; + char posture[64]; + chaser.GetPosture(posture, sizeof(posture)); + + float speed, acceleration; + + acceleration = originalData.Acceleration[difficulty]; + if (!IsNullString(posture) && data.GetPosture(posture, postureInfo)) + { + speed = postureInfo.Acceleration[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_ReducedAccelerationOnLook) && controller.CanBeSeen(_, true)) + { + acceleration *= controller.GetAttributeValue(SF2Attribute_ReducedAccelerationOnLook); + } + acceleration += controller.GetAddAcceleration(); + + Action action = Plugin_Continue; + float forwardSpeed; + switch (moveType) + { + case SF2NPCMoveType_Walk: + { + speed = data.WalkSpeed[difficulty]; + if (!IsNullString(posture) && data.GetPosture(posture, postureInfo)) + { + speed = postureInfo.WalkSpeed[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_ReducedWalkSpeedOnLook) && controller.CanBeSeen(_, true)) + { + speed *= controller.GetAttributeValue(SF2Attribute_ReducedWalkSpeedOnLook); + } + + forwardSpeed = speed; + Call_StartForward(g_OnBossGetWalkSpeedFwd); + Call_PushCell(controller.Index); + Call_PushFloatRef(forwardSpeed); + Call_Finish(action); + if (action == Plugin_Changed) + { + speed = forwardSpeed; + } + } + case SF2NPCMoveType_Run: + { + speed = originalData.RunSpeed[difficulty]; + if (!IsNullString(posture) && data.GetPosture(posture, postureInfo)) + { + speed = postureInfo.Speed[difficulty]; + } + + if (controller.HasAttribute(SF2Attribute_ReducedSpeedOnLook) && controller.CanBeSeen(_, true)) + { + speed *= controller.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); + } + + speed += baseController.GetAddSpeed(); + forwardSpeed = speed; + + Call_StartForward(g_OnBossGetSpeedFwd); + Call_PushCell(controller.Index); + Call_PushFloatRef(forwardSpeed); + Call_Finish(action); + if (action == Plugin_Changed) + { + speed = forwardSpeed; + } + } + case SF2NPCMoveType_Attack: + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(chaser.GetAttackName(), attackData); + float attackSpeed = attackData.RunSpeed[difficulty]; + if (attackData.RunDelay[difficulty] > 0.0 && chaser.AttackRunDelay > gameTime) + { + attackSpeed = 0.0; + } + if (attackData.RunDuration[difficulty] > 0.0 && chaser.AttackRunDuration < gameTime) + { + attackSpeed = 0.0; + } + speed = attackSpeed; + float groundSpeed = chaser.GetPropFloat(Prop_Data, "m_flGroundSpeed"); + if (groundSpeed != 0.0 && attackData.RunGroundSpeed[difficulty]) + { + speed += groundSpeed; + } + acceleration = attackData.RunAcceleration[difficulty]; + } + } + + if (g_InProxySurvivalRageMode) + { + speed *= 1.25; + } + + speed = (speed + (speed * g_RoundDifficultyModifier) / 15.0); + acceleration = (acceleration + (acceleration * g_RoundDifficultyModifier) / 15.0); + + if (moveType == SF2NPCMoveType_Run) + { + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S)) + { + if (speed < 520.0) + { + speed = 520.0; + } + acceleration += 9001.0; + } + if (SF_IsSlaughterRunMap()) + { + if (speed < 580.0) + { + speed = 580.0; + } + acceleration += 10000.0; + } + } + + if (IsBeatBoxBeating(2) || chaser.IsKillingSomeone) + { + speed = 0.0; + } + + npc.flWalkSpeed = speed * 0.9; + npc.flRunSpeed = speed; + npc.flAcceleration = acceleration; +} + +static void ProcessBody(SF2_ChaserEntity chaser) +{ + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return; + } + + if (chaser.CanUpdatePosture()) + { + char posture[64], resultPosture[64]; + chaser.GetDefaultPosture(posture, sizeof(posture)); + strcopy(resultPosture, sizeof(resultPosture), posture); + Action result = Plugin_Continue; + Call_StartForward(g_OnChaserUpdatePosturePFwd); + Call_PushCell(controller); + Call_PushStringEx(resultPosture, sizeof(resultPosture), 0, SM_PARAM_COPYBACK); + Call_PushCell(sizeof(resultPosture)); + Call_Finish(result); + + if (result == Plugin_Changed) + { + strcopy(posture, sizeof(posture), resultPosture); + } + + chaser.SetPosture(posture); + } + + INextBot bot = chaser.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + float speed = loco.GetGroundSpeed(); + + if (speed < 0.01) + { + if (chaser.MoveXParameter != -1) + { + chaser.SetPoseParameter(chaser.MoveXParameter, 0.0); + } + + if (chaser.MoveYParameter != -1) + { + chaser.SetPoseParameter(chaser.MoveYParameter, 0.0); + } + + if (chaser.MoveScaleParameter != -1) + { + chaser.SetPoseParameter(chaser.MoveScaleParameter, 0.0); + } + return; + } + + if (!chaser.IsAttemptingToMove) + { + return; + } + + switch (chaser.MoveParameterType) + { + case SF2NPCMoveParameter_XY: + { + float forwardVector[3], rightVector[3], upVector[3], motionVector[3]; + chaser.GetVectors(forwardVector, rightVector, upVector); + loco.GetGroundMotionVector(motionVector); + + chaser.SetPoseParameter(chaser.MoveXParameter, GetVectorDotProduct(motionVector, forwardVector)); + chaser.SetPoseParameter(chaser.MoveYParameter, GetVectorDotProduct(motionVector, rightVector)); + + float groundSpeed = chaser.GetPropFloat(Prop_Data, "m_flGroundSpeed"); + if (groundSpeed != 0.0 && loco.IsOnGround()) + { + float rate = (speed / groundSpeed); + if (rate < 0.0) + { + rate = 0.0; + } + if (rate > 12.0) + { + rate = 12.0; + } + + chaser.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + } + } + case SF2NPCMoveParameter_Yaw: + { + float fwd[3], right[3], up[3]; + chaser.GetVectors(fwd, right, up); + + float motion[3]; + loco.GetGroundMotionVector(motion); + + float deltaTime = GetGameFrameTime(); + if (deltaTime > 0.0) + { + float myAng[3], normalAngle, estimateYaw; + estimateYaw = chaser.EstimatedYaw; + chaser.GetLocalAngles(myAng); + + if (motion[0] == 0.0 && motion[1] == 0.0) + { + float yawDelta = myAng[1] - estimateYaw; + yawDelta = AngleNormalize(yawDelta); + + if (deltaTime < 0.25) + { + yawDelta *= (deltaTime * 4.0); + } + else + { + yawDelta *= deltaTime; + } + + estimateYaw += yawDelta; + estimateYaw = AngleNormalize(estimateYaw); + } + else + { + estimateYaw = (ArcTangent2(motion[1], motion[0]) * 180.0 / 3.14159); + estimateYaw = FloatClamp(estimateYaw, -180.0, 180.0); + } + normalAngle = AngleNormalize(myAng[1]); + chaser.EstimatedYaw = estimateYaw; + float actualYaw = normalAngle - estimateYaw; + actualYaw = AngleNormalize(-actualYaw); + + chaser.SetPoseParameter(chaser.MoveYawParameter, actualYaw); + } + } + } + + if (chaser.MoveScaleParameter != -1) + { + float scale = npc.flRunSpeed / speed; + chaser.SetPoseParameter(chaser.MoveScaleParameter, scale); + } + + if (data.OldAnimationAI) + { + float velocity, returnFloat, groundSpeed; + velocity = loco.GetGroundSpeed(); + groundSpeed = chaser.GetPropFloat(Prop_Data, "m_flGroundSpeed"); + if (groundSpeed != 0.0 && groundSpeed > 10.0) + { + returnFloat = (velocity / groundSpeed) * chaser.AnimationPlaybackRate; + + if (loco.IsOnGround() && chaser.IsAttemptingToMove && chaser.State != STATE_ATTACK) + { + if (returnFloat > 12.0) + { + returnFloat = 12.0; + } + if (returnFloat < -4.0) + { + returnFloat = -4.0; + } + chaser.SetPropFloat(Prop_Send, "m_flPlaybackRate", returnFloat); + } + } + else + { + velocity = (velocity + ((npc.flRunSpeed * g_RoundDifficultyModifier) / 15.0)) / npc.flRunSpeed; + + if (loco.IsOnGround() && chaser.IsAttemptingToMove && chaser.State != STATE_ATTACK) + { + float playbackSpeed = velocity * chaser.AnimationPlaybackRate; + if (playbackSpeed > 12.0) + { + playbackSpeed = 12.0; + } + if (playbackSpeed < -4.0) + { + playbackSpeed = -4.0; + } + chaser.SetPropFloat(Prop_Send, "m_flPlaybackRate", playbackSpeed); + } + } + } +} + +static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) +{ + if (IsValidEntity(other)) + { + SF2_BasePlayer player = SF2_BasePlayer(other); + if (player.IsValid) + { + if (!SF_IsBoxingMap() && !player.IsProxy && !player.IsInGhostMode && player.Team != TFTeam_Blue && !player.IsInDeathCam) + { + return true; + } + } + INextBot bot = loco.GetBot(); + SF2_ChaserEntity chaser = SF2_ChaserEntity(bot.GetEntity()); + if (chaser.IsValid() && chaser.Controller.IsValid()) + { + SF2BossProfileData data; + SF2NPC_BaseNPC controller = view_as(chaser.Controller); + data = controller.GetProfileData(); + if (data.IsPvEBoss && SF2_ChaserEntity(other).IsValid()) + { + return false; + } + } + } + return loco.CallBaseFunction(other); +} + +static Action Hook_ChaserSoundHook(int clients[64], int &numClients, char sample[PLATFORM_MAX_PATH], int &entity, int &channel, float &volume, int &level, int &pitch, int &flags) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + SF2_BasePlayer client = SF2_BasePlayer(entity); + if (!client.IsValid || !client.IsAlive || client.IsEliminated || client.HasEscaped) + { + return Plugin_Continue; + } + + switch (channel) + { + case SNDCHAN_VOICE: + { + if (StrContains(sample, "vo/halloween_scream") != -1) + { + return Plugin_Continue; + } + + OnPlayerEmitSound(client, SoundType_Voice); + } + case SNDCHAN_BODY: + { + if (!(client.GetFlags() & FL_ONGROUND)) + { + return Plugin_Continue; + } + + if (StrContains(sample, "player/footsteps", false) == -1 && StrContains(sample, "step", false) == -1) + { + return Plugin_Continue; + } + + bool isLoudStep = false, isCrouchStep = false; + if (client.IsSprinting && !(client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked"))) + { + isLoudStep = true; + } + else if (client.GetProp(Prop_Send, "m_bDucking") || client.GetProp(Prop_Send, "m_bDucked")) + { + isCrouchStep = true; + } + + SoundType soundType = SoundType_Footstep; + if (isLoudStep) + { + soundType = SoundType_LoudFootstep; + } + else if (isCrouchStep) + { + soundType = SoundType_QuietFootstep; + } + + OnPlayerEmitSound(client, soundType); + } + case SNDCHAN_ITEM, SNDCHAN_WEAPON: + { + if (StrContains(sample, "swing", false) == -1 && StrContains(sample, "impact", false) == -1 && + StrContains(sample, "hit", false) == -1 && StrContains(sample, "slice", false) == -1 && + StrContains(sample, "reload", false) == -1 && StrContains(sample, "woosh", false) == -1 && + StrContains(sample, "eviction", false) == -1 && StrContains(sample, "holy", false) == -1 && + StrContains(sample, "flashlight", false) == -1) + { + return Plugin_Continue; + } + + OnPlayerEmitSound(client, StrContains(sample, "flashlight", false) != -1 ? SoundType_Flashlight : SoundType_Weapon); + } + case SNDCHAN_STATIC: + { + if (StrContains(sample, "happy_birthday_tf", false) == -1 && StrContains(sample, "jingle_bells_nm", false) == -1) + { + return Plugin_Continue; + } + + OnPlayerEmitSound(client, SoundType_Voice); + } + } + + return Plugin_Continue; +} + +static void OnPlayerEmitSound(SF2_BasePlayer client, SoundType soundType) +{ + float gameTime = GetGameTime(); + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + if (NPCGetUniqueID(bossIndex) == -1) + { + continue; + } + + if (!NPCShouldHearEntity(bossIndex, client.index, soundType)) + { + continue; + } + + int entity = NPCGetEntIndex(bossIndex); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + continue; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + TFClassType class = client.Class; + int classToInt = view_as(class); + + float myPos[3], hisPos[3]; + chaser.GetAbsOrigin(myPos); + client.GetAbsOrigin(hisPos); + + int difficulty = controller.Difficulty; + + float hearRadius = view_as(controller).GetProfileData().SearchSoundRange[difficulty]; + if (hearRadius <= 0.0) + { + continue; + } + + float distance = GetVectorSquareMagnitude(hisPos, myPos); + + Handle trace = null; + bool hit = false; + + float myEyePos[3]; + controller.GetEyePosition(myEyePos); + + float cooldown = 0.0; + int addThreshold = 0; + + switch (soundType) + { + case SoundType_Footstep, SoundType_LoudFootstep, SoundType_QuietFootstep: + { + if (!(client.GetFlags() & FL_ONGROUND)) + { + continue; + } + + if (soundType == SoundType_QuietFootstep) + { + addThreshold = data.QuietFootstepSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.QuietFootstepSenses.Cooldown[difficulty]; + distance *= 1.85; + } + else if (soundType == SoundType_LoudFootstep) + { + addThreshold = data.LoudFootstepSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.LoudFootstepSenses.Cooldown[difficulty]; + distance *= 0.66; + } + else + { + addThreshold = data.FootstepSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.FootstepSenses.Cooldown[difficulty]; + } + + trace = TR_TraceRayFilterEx(myPos, hisPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_WINDOW, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, chaser.index); + hit = TR_DidHit(trace); + delete trace; + } + case SoundType_Voice, SoundType_Flashlight: + { + float hisEyePos[3]; + client.GetEyePosition(hisEyePos); + + trace = TR_TraceRayFilterEx(myEyePos, hisEyePos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_WINDOW, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, chaser.index); + hit = TR_DidHit(trace); + delete trace; + + if (!IsClassConfigsValid() || soundType == SoundType_Voice) + { + distance *= 0.5; + } + else if (IsClassConfigsValid() && soundType == SoundType_Flashlight) + { + distance *= g_ClassFlashlightSoundRadius[classToInt]; + } + + if (soundType == SoundType_Voice) + { + addThreshold = data.VoiceSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.VoiceSenses.Cooldown[difficulty]; + } + else if (soundType == SoundType_Flashlight) + { + addThreshold = data.FlashlightSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + cooldown = data.FlashlightSenses.Cooldown[difficulty]; + } + } + case SoundType_Weapon: + { + addThreshold = data.WeaponSenses.AddCount[difficulty]; + if (addThreshold <= 0) + { + continue; + } + + float hisMins[3], hisMaxs[3]; + client.GetPropVector(Prop_Send, "m_vecMins", hisMins); + client.GetPropVector(Prop_Send, "m_vecMaxs", hisMaxs); + + float middle[3]; + for (int i = 0; i < 2; i++) + { + middle[i] = (hisMins[i] + hisMaxs[i]) / 2.0; + } + + float endPos[3]; + endPos = hisPos; + AddVectors(hisPos, middle, endPos); + + trace = TR_TraceRayFilterEx(myEyePos, endPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_WINDOW, RayType_EndPoint, TraceRayDontHitCharactersOrEntity, chaser.index); + hit = TR_DidHit(trace); + delete trace; + + distance *= 0.66; + + cooldown = data.WeaponSenses.Cooldown[difficulty]; + } + } + + if (hit) + { + distance *= 1.66; + } + + if (!IsClassConfigsValid()) + { + if (class == TFClass_Spy) + { + distance *= 1.3; + } + + if (class == TFClass_Scout) + { + distance *= 0.8; + } + } + else + { + distance *= g_ClassBossHearingSensitivity[classToInt]; + } + + if (distance > SquareFloat(hearRadius)) + { + continue; + } + + if (chaser.GetAlertSoundTriggerCooldown(client) > gameTime && cooldown > 0.0) + { + continue; + } + + chaser.SetAlertSoundTriggerCooldown(client, gameTime + cooldown); + chaser.UpdateAlertTriggerCount(client, addThreshold); + + if (data.AutoChaseEnabled[difficulty] && chaser.GetAutoChaseAddCooldown(client) < gameTime) + { + int count = 0; + switch (soundType) + { + case SoundType_Footstep: + { + count = data.AutoChaseAddFootstep[difficulty]; + } + case SoundType_QuietFootstep: + { + count = data.AutoChaseAddQuietFootstep[difficulty]; + } + case SoundType_LoudFootstep: + { + count = data.AutoChaseAddLoudFootstep[difficulty]; + } + case SoundType_Voice: + { + count = data.AutoChaseAddVoice[difficulty]; + } + case SoundType_Flashlight, SoundType_Weapon: + { + count = data.AutoChaseAddWeapon[difficulty]; + } + } + if (count > 0) + { + chaser.SetAutoChaseAddCooldown(client, gameTime + 0.3); + chaser.UpdateAutoChaseCount(client, chaser.GetAutoChaseCount(client) + count); + } + } + } +} + +static any Native_GetIsValid(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + return bossEntity.IsValid(); +} + +static any Native_GetIsAttemptingToMove(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsAttemptingToMove; +} + +static any Native_GetIsAttacking(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsAttacking; +} + +static any Native_GetIsStunned(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsStunned; +} + +static any Native_GetStunHealth(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.StunHealth; +} + +static any Native_GetMaxStunHealth(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.MaxStunHealth; +} + +static any Native_GetCanBeStunned(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.CanBeStunned(); +} + +static any Native_GetCanTakeDamage(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.CanTakeDamage(); +} + +static any Native_GetIsRaging(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsRaging; +} + +static any Native_GetIsRunningAway(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsRunningAway; +} + +static any Native_GetIsSelfHealing(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.IsSelfHealing; +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + SF2ChaserBossProfileData data; + data = bossEntity.Controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_PerformVoice(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + char attackName[64]; + GetNativeString(3, attackName, sizeof(attackName)); + return bossEntity.PerformVoice(GetNativeCell(2), attackName); +} + +static any Native_GetDefaultPosture(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + int bufferSize = GetNativeCell(3); + char[] posture = new char[bufferSize]; + int result = bossEntity.GetDefaultPosture(posture, bufferSize); + SetNativeString(2, posture, bufferSize); + + return result; +} + +static any Native_SetDefaultPosture(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + int bufferSize; + GetNativeStringLength(2, bufferSize); + bufferSize++; + char[] buffer = new char[bufferSize]; + GetNativeString(2, buffer, bufferSize); + bossEntity.SetDefaultPosture(buffer); + + return 0; +} + +static any Native_GetAttackName(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + int bufferSize = GetNativeCell(3); + char[] attack = new char[bufferSize]; + int result = strcopy(attack, bufferSize, bossEntity.GetAttackName()); + SetNativeString(2, attack, bufferSize); + + return result; +} + +static any Native_GetNextAttackTime(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + int bufferSize; + GetNativeStringLength(2, bufferSize); + bufferSize++; + char[] buffer = new char[bufferSize]; + GetNativeString(2, buffer, bufferSize); + + return bossEntity.GetNextAttackTime(buffer); +} + +static any Native_SetNextAttackTime(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + int bufferSize; + GetNativeStringLength(2, bufferSize); + bufferSize++; + char[] buffer = new char[bufferSize]; + GetNativeString(2, buffer, bufferSize); + + float time = GetNativeCell(3); + + bossEntity.SetNextAttackTime(buffer, time); + return 0; +} + +static any Native_DropItem(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + bossEntity.DropItem(GetNativeCell(2)); + + return 0; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_alert.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_alert.sp new file mode 100644 index 00000000..8717a3cd --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_alert.sp @@ -0,0 +1,47 @@ +#pragma semicolon 1 + +void InitializePostureOnAlert() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionAlertInfo alertInfo; + alertInfo = postureInfo.AlertInfo; + if (!alertInfo.Enabled[difficulty]) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (chaser.IsValid() && chaser.State == STATE_ALERT) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_cloak.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_cloak.sp new file mode 100644 index 00000000..83e0c159 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_cloak.sp @@ -0,0 +1,47 @@ +#pragma semicolon 1 + +void InitializePostureOnCloak() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionCloakInfo cloakInfo; + cloakInfo = postureInfo.CloakInfo; + if (!cloakInfo.Enabled[difficulty]) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (chaser.IsValid() && chaser.HasCloaked) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_look.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_look.sp new file mode 100644 index 00000000..556d917a --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/on_look.sp @@ -0,0 +1,46 @@ +#pragma semicolon 1 + +void InitializePostureOnLook() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionLookAtInfo lookAtInfo; + lookAtInfo = postureInfo.LookAtInfo; + if (!lookAtInfo.Enabled[difficulty]) + { + continue; + } + + if (controller.CanBeSeen(_, true)) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/rage_phase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/rage_phase.sp new file mode 100644 index 00000000..46ead006 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/rage_phase.sp @@ -0,0 +1,54 @@ +#pragma semicolon 1 + +void InitializePostureRagePhase() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + SF2PostureConditionRagePhase ragePhaseInfo; + ragePhaseInfo = postureInfo.RagePhaseInfo; + if (!ragePhaseInfo.Enabled) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid() || chaser.RageIndex == -1) + { + continue; + } + + SF2ChaserRageInfo rageInfo; + data.Rages.GetArray(chaser.RageIndex, rageInfo, sizeof(rageInfo)); + int index = ragePhaseInfo.Names.FindString(rageInfo.Name); + + if (index != -1) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/running_away.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/running_away.sp new file mode 100644 index 00000000..c9532dd0 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/running_away.sp @@ -0,0 +1,44 @@ +#pragma semicolon 1 + +void InitializePostureRunningAway() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + SF2PostureConditionRunAwayInfo runAwayInfo; + runAwayInfo = postureInfo.RunAwayInfo; + if (!runAwayInfo.Enabled) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (chaser.IsValid() && chaser.IsRunningAway) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_bounds.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_bounds.sp new file mode 100644 index 00000000..1ae3d000 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_bounds.sp @@ -0,0 +1,51 @@ +#pragma semicolon 1 + +void InitializePostureWithinBounds() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + SF2PostureConditionWithinBoundsInfo boundsInfo; + boundsInfo = postureInfo.BoundsInfo; + if (!boundsInfo.Enabled) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + float myPos[3]; + chaser.GetAbsOrigin(myPos); + if (IsSpaceOccupiedIgnorePlayersAndEnts(myPos, boundsInfo.Mins, boundsInfo.Maxs, chaser.index)) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_range.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_range.sp new file mode 100644 index 00000000..1beadd3d --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/postures/within_range.sp @@ -0,0 +1,76 @@ +#pragma semicolon 1 + +void InitializePostureWithinRange() +{ + g_OnChaserUpdatePosturePFwd.AddFunction(null, OnChaserUpdatePosture); +} + +static Action OnChaserUpdatePosture(SF2NPC_Chaser controller, char[] buffer, int bufferSize) +{ + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + StringMap postures = data.Postures; + if (postures == null) + { + return Plugin_Continue; + } + + float gameTime = GetGameTime(); + + SF2ChaserBossProfilePostureInfo postureInfo; + StringMapSnapshot snapshot = postures.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + if (!data.GetPostureFromIndex(i, postureInfo)) + { + continue; + } + + int difficulty = controller.Difficulty; + + SF2PostureConditionWithinRangeInfo rangeInfo; + rangeInfo = postureInfo.RangeInfo; + if (!rangeInfo.Enabled[difficulty]) + { + continue; + } + + if (rangeInfo.CurrentCooldown > gameTime) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid() || chaser.State != STATE_CHASE) + { + continue; + } + + if (chaser.MyNextBotPointer().GetLocomotionInterface().GetGroundSpeed() < 0.01) + { + continue; + } + + CBaseEntity target = chaser.Target; + if (!target.IsValid()) + { + continue; + } + + float range = controller.Path.GetLength() - controller.Path.GetCursorPosition(); + + if (range > rangeInfo.MinRange[difficulty] && range < rangeInfo.MaxRange[difficulty]) + { + strcopy(buffer, bufferSize, postureInfo.Name); + delete snapshot; + return Plugin_Changed; + } + else + { + rangeInfo.CurrentCooldown = gameTime + rangeInfo.Cooldown[difficulty]; + } + } + + delete snapshot; + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp b/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp index d0169490..f2069a50 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/initialize.sp @@ -1,9 +1,12 @@ #pragma semicolon 1 +#include "base/entity.sp" +#include "chaser/entity.sp" #include "statue/entity.sp" -#include "statue/actions/main.sp" void SetupEntityActions() { + SF2_BaseBoss.Initialize(); + SF2_ChaserEntity.Initialize(); SF2_StatueEntity.Initialize(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp new file mode 100644 index 00000000..47b290b1 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp @@ -0,0 +1,158 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueChaseAction < NextBotAction +{ + public SF2_StatueChaseAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Chase"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_StatueChaseAction action, SF2_StatueEntity actor, NextBotAction priorAction) +{ + SF2NPC_Statue controller = actor.Controller; + int difficulty = controller.Difficulty; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + + actor.State = STATE_CHASE; + return action.Continue(); +} + +static int Update(SF2_StatueChaseAction action, SF2_StatueEntity actor) +{ + if (actor.CurrentChaseDuration <= 0.0) + { + actor.Target = CBaseEntity(-1); + actor.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + return action.ChangeTo(SF2_StatueIdleAction(), "Oh he got away..."); + } + + CBaseEntity target = actor.Target; + if (!target.IsValid()) + { + return action.ChangeTo(SF2_StatueIdleAction(), "No target? No problem, idle."); + } + + float gameTime = GetGameTime(); + + if (actor.LastKillTime > gameTime) + { + return action.Continue(); + } + + SF2NPC_Statue controller = actor.Controller; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + int difficulty = controller.Difficulty; + INextBot bot = actor.MyNextBotPointer(); + PathFollower path = controller.Path; + ILocomotion loco = bot.GetLocomotionInterface(); + float myPos[3]; + actor.GetAbsOrigin(myPos); + + actor.IsMoving = !controller.CanBeSeen(true, true, !attackEliminated) && actor.LastKillTime <= gameTime; + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && player.HasEscaped) + { + return action.ChangeTo(SF2_StatueIdleAction(), "Our target escaped, that is no good!"); + } + + if (actor.GetIsVisible(player)) + { + float maxRange = data.ChaseDurationAddMaxRange[difficulty]; + if (maxRange > 0.0 && player.IsValid && player.CanSeeSlender(controller.Index, false, _, !attackEliminated)) + { + float distanceRatio = bot.GetRangeTo(player.index) / maxRange; + if (distanceRatio < 1.0) + { + float durationTimeAddMin = data.ChaseDurationAddVisibilityMin[difficulty]; + float durationTimeAddMax = data.ChaseDurationAddVisibilityMax[difficulty]; + float durationAdd = durationTimeAddMin + ((durationTimeAddMax - durationTimeAddMin) * distanceRatio); + + actor.CurrentChaseDuration += durationAdd * GetGameFrameTime(); + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + } + else + { + actor.CurrentChaseDuration -= GetGameFrameTime(); + } + } + else + { + actor.CurrentChaseDuration -= GetGameFrameTime(); + } + + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + + bool tooClose = target.IsValid() && + actor.GetIsVisible(SF2_BasePlayer(target.index)) && + bot.IsRangeLessThan(target.index, 8.0); + + if ((tooClose || !actor.IsMoving) && path.IsValid()) + { + path.Invalidate(); + } + else + { + float pos[3]; + target.GetAbsOrigin(pos); + + if (!bot.IsRangeLessThanEx(pos, 8.0) && actor.IsMoving) + { + if (path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) + { + path.ComputeToPos(bot, pos); + } + } + } + + if (!path.IsValid()) + { + loco.Stop(); + } + else + { + path.Update(bot); + } + + if (actor.IsMoving) + { + g_SlenderStatueIdleLifeTime[controller.Index] = gameTime + data.IdleLifeTime[difficulty]; + + if (bot.GetRangeSquaredTo(target.index) <= Pow(originalData.InstantKillRadius, 2.0) && actor.GetIsVisible(player)) + { + if (controller.Flags & SFF_FAKE) + { + controller.MarkAsFake(); + return action.Done("Faker"); + } + else + { + actor.LastKillTime = gameTime + originalData.InstantKillCooldown[difficulty]; + player.StartDeathCam(controller.Index, myPos); + } + } + } + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/idle.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/idle.sp new file mode 100644 index 00000000..c21f6dff --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/idle.sp @@ -0,0 +1,39 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueIdleAction < NextBotAction +{ + public SF2_StatueIdleAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Idle"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_StatueIdleAction action, SF2_StatueEntity actor, NextBotAction priorAction) +{ + actor.State = STATE_IDLE; + return action.Continue(); +} + +static int Update(SF2_StatueIdleAction action, SF2_StatueEntity actor) +{ + CBaseEntity target = actor.Target; + if (actor.LastKillTime > GetGameTime()) + { + return action.Continue(); + } + + if (target.IsValid()) + { + return action.ChangeTo(SF2_StatueChaseAction(), "I sense someone, lock onto them!"); + } + actor.IsMoving = false; + return action.Continue(); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp new file mode 100644 index 00000000..ac7a5ea3 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp @@ -0,0 +1,252 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_StatueBaseAction < NextBotAction +{ + public SF2_StatueBaseAction() + { + return view_as(g_Factory.Create()); + } + + public static NextBotActionFactory GetFactory() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Statue_Main"); + g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.BeginDataMapDesc() + .DefineFloatField("m_LastStuckTime") + .DefineVectorField("m_LastPos") + .DefineVectorField("m_UnstuckPos") + .EndDataMapDesc(); + } + return g_Factory; + } + + property float LastStuckTime + { + public get() + { + return this.GetDataFloat("m_LastStuckTime"); + } + + public set(float value) + { + this.SetDataFloat("m_LastStuckTime", value); + } + } + + public void GetLastPos(float pos[3]) + { + this.GetDataVector("m_LastPos", pos); + } + + public void SetLastPos(const float pos[3]) + { + this.SetDataVector("m_LastPos", pos); + } + + public void GetUnstuckPos(float pos[3]) + { + this.GetDataVector("m_UnstuckPos", pos); + } + + public void SetUnstuckPos(const float pos[3]) + { + this.SetDataVector("m_UnstuckPos", pos); + } +} + +static NextBotAction InitialContainedAction(SF2_StatueBaseAction action, SF2_StatueEntity actor) +{ + return SF2_StatueIdleAction(); +} + +static int Update(SF2_StatueBaseAction action, SF2_StatueEntity actor, float interval) +{ + SF2NPC_Statue controller = actor.Controller; + if (controller.IsValid() && (controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return action.Done("I'm a faker"); + } + + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + SF2BossProfileSoundInfo soundInfo; + CBaseEntity target = actor.Target; + float myPos[3]; + actor.GetAbsOrigin(myPos); + int difficulty = controller.Difficulty; + + if (actor.LastKillTime > GetGameTime()) + { + actor.IsMoving = false; + } + + if (actor.IsMoving) + { + soundInfo = data.SingleMoveSounds; + soundInfo.EmitSound(_, actor.index); + soundInfo = data.MoveSounds; + soundInfo.EmitSound(_, actor.index); + + if (target.IsValid()) + { + float targetPos[3]; + target.GetAbsOrigin(targetPos); + + float distance = GetVectorSquareMagnitude(targetPos, myPos); + float maxRange = Pow(data.ModelChangeDistanceMax[difficulty], 2.0); + char model[PLATFORM_MAX_PATH]; + if (distance < maxRange * 0.33) + { + data.ModelsCloseDist.GetString(difficulty, model, sizeof(model)); + } + else if (distance < maxRange * 0.66) + { + data.ModelsAverageDist.GetString(difficulty, model, sizeof(model)); + } + else + { + originalData.Models.GetString(difficulty, model, sizeof(model)); + } + + if (model[0] == '\0' || strcmp(model, "models/") == 0) + { + originalData.Models.GetString(difficulty, model, sizeof(model)); + } + + actor.SetModel(model); + } + } + else + { + soundInfo = data.MoveSounds; + soundInfo.StopAllSounds(actor.index); + } + + UnstuckCheck(action, actor); + + return action.Continue(); +} + +static void UnstuckCheck(SF2_StatueBaseAction action, SF2_StatueEntity actor) +{ + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + SF2NPC_Statue controller = actor.Controller; + PathFollower path = controller.Path; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); + float gameTime = GetGameTime(); + + if (!path.IsValid() || !actor.IsMoving || loco.GetDesiredSpeed() <= 0.0) + { + action.LastStuckTime = gameTime; + return; + } + + float lastPos[3], myPos[3]; + action.GetLastPos(lastPos); + actor.GetAbsOrigin(myPos); + + if (bot.IsRangeLessThanEx(lastPos, 0.13) || loco.GetGroundSpeed() <= 0.1) + { + if (action.LastStuckTime > gameTime - 0.75) + { + return; + } + float destination[3]; + CNavArea area = TheNavMesh.GetNearestNavArea(lastPos, _, _, _, false); + area.GetCenter(destination); + float tempMaxs[3]; + npc.GetBodyMaxs(tempMaxs); + float traceMins[3]; + traceMins[0] = g_SlenderDetectMins[controller.Index][0] - 5.0; + traceMins[1] = g_SlenderDetectMins[controller.Index][1] - 5.0; + traceMins[2] = 0.0; + + float traceMaxs[3]; + traceMaxs[0] = g_SlenderDetectMaxs[controller.Index][0] + 5.0; + traceMaxs[1] = g_SlenderDetectMaxs[controller.Index][1] + 5.0; + traceMaxs[2] = tempMaxs[2]; + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (GetVectorSquareMagnitude(destination, lastPos) <= SquareFloat(16.0) || TR_DidHit()) + { + CursorData cursor = path.GetCursorData(); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + if (collector.Get(i).GetCostSoFar() < 16.0) + { + continue; + } + if (cursor.segmentPrior != NULL_PATH_SEGMENT) + { + CNavArea segmentArea = cursor.segmentPrior.area; + if (segmentArea == collector.Get(i)) + { + continue; + } + } + float navPos[3]; + collector.Get(i).GetCenter(navPos); + if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) + { + continue; + } + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + int randomArea = 0, randomCell = 0; + areaArray.Resize(validAreaCount); + area = NULL_AREA; + while (validAreaCount > 1) + { + randomCell = GetRandomInt(0, validAreaCount - 1); + randomArea = areaArray.Get(randomCell); + area = collector.Get(randomArea); + area.GetCenter(destination); + + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (TR_DidHit()) + { + area = NULL_AREA; + validAreaCount--; + int findValue = areaArray.FindValue(randomCell); + if (findValue != -1) + { + areaArray.Erase(findValue); + } + } + else + { + break; + } + } + + delete collector; + delete areaArray; + } + path.GetClosestPosition(destination, destination, path.FirstSegment(), 128.0); + action.LastStuckTime = gameTime + 0.75; + actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); + } + else + { + loco.GetFeet(lastPos); + action.SetLastPos(lastPos); + action.LastStuckTime += 0.03; + if (action.LastStuckTime > gameTime) + { + action.LastStuckTime = gameTime; + } + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index 9bec65a6..946f421d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -1,8 +1,12 @@ #pragma semicolon 1 +#include "actions/mainlayer.sp" +#include "actions/idle.sp" +#include "actions/chase.sp" + static CEntityFactory g_Factory; -methodmap SF2_StatueEntity < CBaseCombatCharacter +methodmap SF2_StatueEntity < SF2_BaseBoss { public SF2_StatueEntity(int entity) { @@ -21,14 +25,11 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter public static void Initialize() { - g_Factory = new CEntityFactory("sf2_statue_npc", OnCreate, OnRemove); - g_Factory.DeriveFromNPC(); + g_Factory = new CEntityFactory("sf2_npc_boss_statue", OnCreate); + g_Factory.DeriveFromFactory(SF2_BaseBoss.GetFactory()); g_Factory.SetInitialActionFactory(SF2_StatueBaseAction.GetFactory()); g_Factory.BeginDataMapDesc() - .DefineIntField("m_Controller") .DefineBoolField("m_IsMoving") - .DefineEntityField("m_Target") - .DefineFloatField("m_ChaseTime") .DefineFloatField("m_LastKillTime") .EndDataMapDesc(); g_Factory.Install(); @@ -38,12 +39,12 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter { public get() { - return view_as(SF2NPC_Statue.FromUniqueId(this.GetProp(Prop_Data, "m_Controller"))); + return view_as(view_as(this).Controller); } public set(SF2NPC_Statue controller) { - this.SetProp(Prop_Data, "m_Controller", controller.Index == -1 ? -1 : controller.UniqueID); + view_as(this).Controller = controller; } } @@ -59,53 +60,52 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter this.SetProp(Prop_Data, "m_IsMoving", value); } } - - property int Target + property float LastKillTime { public get() { - return this.GetPropEnt(Prop_Data, "m_Target"); + return this.GetPropFloat(Prop_Data, "m_LastKillTime"); } - public set(int entity) + + public set(float value) { - this.SetPropEnt(Prop_Data, "m_Target", entity); + this.SetPropFloat(Prop_Data, "m_LastKillTime", value); } } - property float ChaseTime + public void DoAlwaysLookAt(CBaseEntity target) { - public get() + if (!target.IsValid()) { - return this.GetPropFloat(Prop_Data, "m_ChaseTime"); + return; } - public set(float value) + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && !player.IsAlive) { - this.SetPropFloat(Prop_Data, "m_ChaseTime", value); + return; } - } - property float LastKillTime - { - public get() + INextBot bot = this.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); + if (!this.GetIsVisible(player)) { - return this.GetPropFloat(Prop_Data, "m_LastKillTime"); + return; } - public set(float value) - { - this.SetPropFloat(Prop_Data, "m_LastKillTime", value); - } + float pos[3]; + target.GetAbsOrigin(pos); + loco.FaceTowards(pos); } public static SF2_StatueEntity Create(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) { - SF2_StatueEntity statue = SF2_StatueEntity(CreateEntityByName("sf2_statue_npc")); + SF2_StatueEntity statue = SF2_StatueEntity(CreateEntityByName("sf2_npc_boss_statue")); if (!statue.IsValid()) { return SF2_StatueEntity(-1); } - if (controller == SF2_INVALID_NPC_STATUE) + if (controller == SF2_INVALID_NPC) { return SF2_StatueEntity(-1); } @@ -114,6 +114,9 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter controller.GetProfile(profile, sizeof(profile)); statue.Controller = view_as(controller); + SF2BossProfileData originalData; + originalData = view_as(statue.Controller).GetProfileData(); + char buffer[PLATFORM_MAX_PATH]; GetSlenderModel(controller.Index, _, buffer, sizeof(buffer)); @@ -141,9 +144,7 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter npc.flGravity = g_Gravity; npc.flDeathDropHeight = 99999.0; npc.flJumpHeight = 512.0; - loco.SetCallback(LocomotionCallback_IsAbleToJumpAcrossGaps, CanJumpAcrossGaps); - loco.SetCallback(LocomotionCallback_IsAbleToClimb, CanJumpAcrossGaps); - loco.SetCallback(LocomotionCallback_JumpAcrossGap, JumpAcrossGapsCBase); + npc.flMaxYawRate = originalData.TurnRate; loco.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); statue.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); @@ -152,12 +153,12 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter statue.SetPropVector(Prop_Send, "m_vecMinsPreScaled", HULL_HUMAN_MINS); statue.SetPropVector(Prop_Send, "m_vecMaxsPreScaled", HULL_HUMAN_MAXS); + npc.SetBodyMins(HULL_HUMAN_MINS); + npc.SetBodyMaxs(HULL_HUMAN_MAXS); + statue.SetProp(Prop_Data, "m_nSolidType", SOLID_BBOX); statue.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); - SDKHook(statue.iEnt, SDKHook_ThinkPost, SlenderStatueSetNextThink); - SDKHook(statue.iEnt, SDKHook_Think, SlenderStatueBossProcessMovement); - statue.Teleport(pos, ang, NULL_VECTOR); statue.Spawn(); @@ -165,26 +166,331 @@ methodmap SF2_StatueEntity < CBaseCombatCharacter return statue; } + + public static void SetupAPI() + { + CreateNative("SF2_StatueBossEntity.IsValid.get", Native_GetIsValid); + CreateNative("SF2_StatueBossEntity.IsMoving.get", Native_GetIsMoving); + CreateNative("SF2_StatueBossEntity.LastKillTime.get", Native_GetLastKillTime); + CreateNative("SF2_StatueBossEntity.ProfileData", Native_GetProfileData); + } } -static void OnCreate(int ent) +static void OnCreate(SF2_StatueEntity ent) { - // Do nothing + ent.LastKillTime = 0.0; + SDKHook(ent.index, SDKHook_Think, Think); + SDKHook(ent.index, SDKHook_ThinkPost, ThinkPost); } -static void OnRemove(int ent) +static Action Think(int entIndex) { - // Do nothing + SF2_StatueEntity statue = SF2_StatueEntity(entIndex); + statue.Target = ProcessVision(statue); + + if (statue.IsMoving) + { + statue.DoAlwaysLookAt(statue.Target); + } + + return Plugin_Continue; +} + +static void ThinkPost(int entIndex) +{ + SF2_StatueEntity statue = SF2_StatueEntity(entIndex); + + ProcessSpeed(statue); + + if (NPCGetCustomOutlinesState(statue.Controller.Index) && NPCGetRainbowOutlineState(statue.Controller.Index)) + { + statue.ProcessRainbowOutline(); + } + + if (statue.IsMoving) + { + statue.DoAlwaysLookAt(statue.Target); + } + + statue.SetNextThink(GetGameTime()); +} + +static CBaseEntity ProcessVision(SF2_StatueEntity statue) +{ + SF2NPC_Statue controller = statue.Controller; + bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + int difficulty = controller.Difficulty; + + float playerDists[MAXTF2PLAYERS]; + + float traceMins[3] = { -16.0, ... }; + traceMins[2] = 0.0; + float traceMaxs[3] = { 16.0, ... }; + traceMaxs[2] = 0.0; + + int oldTarget = statue.OldTarget.index; + if (!IsTargetValidForSlender(SF2_BasePlayer(oldTarget), attackEliminated)) + { + statue.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(SF2_BasePlayer(oldTarget))) + { + statue.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + int bestNewTarget = oldTarget; + float searchRange = originalData.SearchRange[difficulty]; + float bestNewTargetDist = Pow(searchRange, 2.0); + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!IsTargetValidForSlender(client, attackEliminated) && !originalData.IsPvEBoss) + { + continue; + } + + statue.SetIsVisible(client, false); + statue.SetInFOV(client, false); + statue.SetIsNear(client, false); + + float traceStartPos[3], traceEndPos[3]; + controller.GetEyePosition(traceStartPos); + client.GetEyePosition(traceEndPos); + + float dist = 99999999999.9; + + bool isVisible; + int traceHitEntity; + + Handle trace = TR_TraceRayFilterEx(traceStartPos, + traceEndPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, + RayType_EndPoint, + TraceRayBossVisibility, + statue.index); + isVisible = !TR_DidHit(trace); + traceHitEntity = TR_GetEntityIndex(trace); + + delete trace; + + if (!isVisible && traceHitEntity == client.index) + { + isVisible = true; + } + + if (isVisible) + { + isVisible = NPCShouldSeeEntity(controller.Index, client.index); + } + + dist = GetVectorSquareMagnitude(traceStartPos, traceEndPos); + + if (g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) + { + int fogEntity = client.GetDataEnt(g_PlayerFogCtrlOffset); + if (IsValidEdict(fogEntity)) + { + if (GetEntData(fogEntity, g_FogCtrlEnableOffset) && + dist >= SquareFloat(GetEntDataFloat(fogEntity, g_FogCtrlEndOffset))) + { + isVisible = false; + } + } + } + + statue.SetIsVisible(client, isVisible); + + if (statue.GetIsVisible(client) && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) + { + TF2_StunPlayer(client.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); + } + + if (client.ShouldBeForceChased(controller)) + { + bestNewTarget = client.index; + } + + playerDists[client.index] = dist; + + if (statue.GetIsVisible(client)) + { + float targetPos[3]; + client.GetAbsOrigin(targetPos); + if (dist <= SquareFloat(searchRange)) + { + if (dist < bestNewTargetDist) + { + bestNewTarget = client.index; + bestNewTargetDist = dist; + } + } + } + } + + if (bestNewTarget != INVALID_ENT_REFERENCE) + { + statue.OldTarget = CBaseEntity(bestNewTarget); + } + + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) + { + if (!IsTargetValidForSlender(SF2_BasePlayer(bestNewTarget), attackEliminated)) + { + if (statue.State != STATE_CHASE && NPCAreAvailablePlayersAlive()) + { + ArrayList arrayRaidTargets = new ArrayList(); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i) || + !IsPlayerAlive(i) || + g_PlayerEliminated[i] || + IsClientInGhostMode(i) || + DidClientEscape(i)) + { + continue; + } + arrayRaidTargets.Push(i); + } + if (arrayRaidTargets.Length > 0) + { + int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); + if (IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) + { + bestNewTarget = raidTarget; + SF2_BasePlayer(bestNewTarget).SetForceChaseState(controller, true); + } + } + delete arrayRaidTargets; + } + } + statue.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + + return CBaseEntity(bestNewTarget); +} + +static void ProcessSpeed(SF2_StatueEntity statue) +{ + SF2NPC_Statue controller = statue.Controller; + SF2NPC_BaseNPC baseController = view_as(controller); + int difficulty = controller.Difficulty; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(statue.index); + SF2BossProfileData originalData; + originalData = baseController.GetProfileData(); + + float speed, acceleration; + + acceleration = originalData.Acceleration[difficulty]; + if (controller.HasAttribute(SF2Attribute_ReducedAccelerationOnLook) && controller.CanBeSeen(_, true)) + { + acceleration *= controller.GetAttributeValue(SF2Attribute_ReducedAccelerationOnLook); + } + acceleration += controller.GetAddAcceleration(); + + speed = originalData.RunSpeed[difficulty]; + if (controller.HasAttribute(SF2Attribute_ReducedSpeedOnLook) && controller.CanBeSeen(_, true)) + { + speed *= controller.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); + } + + speed += baseController.GetAddSpeed(); + + float forwardSpeed = speed; + Action action = Plugin_Continue; + Call_StartForward(g_OnBossGetSpeedFwd); + Call_PushCell(controller.Index); + Call_PushFloatRef(forwardSpeed); + Call_Finish(action); + if (action == Plugin_Changed) + { + speed = forwardSpeed; + } + + if (g_InProxySurvivalRageMode) + { + speed *= 1.25; + } + + speed = (speed + (speed * g_RoundDifficultyModifier) / 15.0); + acceleration = (acceleration + (acceleration * g_RoundDifficultyModifier) / 15.0); + + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S)) + { + if (speed < 520.0) + { + speed = 520.0; + } + acceleration += 9001.0; + } + if (SF_IsSlaughterRunMap()) + { + if (speed < 580.0) + { + speed = 580.0; + } + acceleration += 10000.0; + } + + if (IsBeatBoxBeating(2) || statue.IsKillingSomeone || !statue.IsMoving) + { + speed = 0.0; + } + + npc.flWalkSpeed = speed * 0.9; + npc.flRunSpeed = speed; + npc.flAcceleration = acceleration; +} + +static any Native_GetIsValid(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + SF2_StatueEntity bossEntity = SF2_StatueEntity(entity); + return bossEntity.IsValid(); +} + +static any Native_GetIsMoving(Handle plugin, int numParams) +{ + int ent = GetNativeCell(1); + if (!IsValidEntity(ent)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", ent); + } + + SF2_StatueEntity boss = SF2_StatueEntity(ent); + return boss.IsMoving; +} + +static any Native_GetLastKillTime(Handle plugin, int numParams) +{ + int ent = GetNativeCell(1); + if (!IsValidEntity(ent)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", ent); + } + + SF2_StatueEntity boss = SF2_StatueEntity(ent); + return boss.LastKillTime; } -static void SlenderStatueSetNextThink(int bossEnt) +static any Native_GetProfileData(Handle plugin, int numParams) { - if (!g_Enabled) + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) { - return; + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); } - CBaseCombatCharacter(bossEnt).SetNextThink(GetGameTime()); + SF2_StatueEntity bossEntity = SF2_StatueEntity(entity); - return; + SF2StatueBossProfileData data; + data = bossEntity.Controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; } diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index efb7deef..52076dab 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -5,326 +5,43 @@ #pragma semicolon 1 -bool g_NpcOriginalVisibility[MAX_BOSSES]; - -static float g_NpcWalkSpeed[MAX_BOSSES][Difficulty_Max]; -static float g_NpcMaxWalkSpeed[MAX_BOSSES][Difficulty_Max]; - -static float g_NpcWakeRadius[MAX_BOSSES]; - -static bool g_NpcHasStunEnabled[MAX_BOSSES]; -static float g_NpcStunCooldown[MAX_BOSSES]; -static bool g_NpcStunFlashlightEnabled[MAX_BOSSES]; -static bool g_NpcHasKeyDrop[MAX_BOSSES]; -static float g_NpcStunFlashlightDamage[MAX_BOSSES]; -static float g_NpcStunInitialHealth[MAX_BOSSES]; static float g_NpcStunAddHealth[MAX_BOSSES]; -static float g_NpcStunHealth[MAX_BOSSES]; -static bool g_NpcChaseInitialOnStun[MAX_BOSSES]; + +static float g_NpcDeathInitialHealth[MAX_BOSSES][Difficulty_Max]; +static float g_NpcDeathHealth[MAX_BOSSES][Difficulty_Max]; static float g_NpcAlertGracetime[MAX_BOSSES][Difficulty_Max]; static float g_NpcAlertDuration[MAX_BOSSES][Difficulty_Max]; static float g_NpcChaseDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcCloakEnabled[MAX_BOSSES]; -static float g_NpcCloakCooldown[MAX_BOSSES][Difficulty_Max]; -static float g_NpcCloakRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcDecloakRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcCloakDuration[MAX_BOSSES][Difficulty_Max]; -float g_NpcNextDecloakTime[MAX_BOSSES]; -static float g_NpcCloakSpeedMultiplier[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcChaseOnLook[MAX_BOSSES]; ArrayList g_NpcChaseOnLookTarget[MAX_BOSSES] = { null, ... }; -static bool g_NpcHasProjectileEnabled[MAX_BOSSES]; -static float g_IceballSlowdownDuration[MAX_BOSSES][Difficulty_Max]; -static float g_IceballSlowdownPercent[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileCooldownMin[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileCooldownMax[MAX_BOSSES][Difficulty_Max]; -float g_NpcProjectileCooldown[MAX_BOSSES]; -static float g_NpcProjectileSpeed[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileDamage[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileRadius[MAX_BOSSES][Difficulty_Max]; -static float g_NpcProjectileDeviation[MAX_BOSSES][Difficulty_Max]; -static int g_NpcProjectileCount[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasCriticalRockets[MAX_BOSSES]; -static bool g_NpcHasUseShootGesture[MAX_BOSSES]; -static bool g_NpcHasUseProjectileAmmo[MAX_BOSSES]; -static int g_NpcProjectileLoadedAmmo[MAX_BOSSES][Difficulty_Max]; -int g_NpcProjectileAmmo[MAX_BOSSES]; -static float g_NpcProjectileReloadTime[MAX_BOSSES][Difficulty_Max]; -float g_NpcProjectileTimeToReload[MAX_BOSSES]; -static bool g_NpcHasUseChargeUpProjectiles[MAX_BOSSES]; -static float g_NpcProjectileChargeUpTime[MAX_BOSSES][Difficulty_Max]; -static int g_NpcProjectileType[MAX_BOSSES]; -bool g_NpcReloadingProjectiles[MAX_BOSSES]; - -bool g_NpcHasAlwaysLookAtTarget[MAX_BOSSES]; -bool g_NpcHasAlwaysLookAtTargetWhileAttacking[MAX_BOSSES]; -bool g_NpcHasAlwaysLookAtTargetWhileChasing[MAX_BOSSES]; - static float g_NpcSearchWanderRangeMin[MAX_BOSSES][Difficulty_Max]; static float g_NpcSearchWanderRangeMax[MAX_BOSSES][Difficulty_Max]; -//The advanced damage effects -static bool g_NpcHasUseAdvancedDamageEffects[MAX_BOSSES]; -static bool g_NpcHasAdvancedDamageEffectsRandom[MAX_BOSSES]; -static bool g_NpcHasAttachDamageParticle[MAX_BOSSES]; - -static int g_NpcRandomAttackIndexes[MAX_BOSSES]; -static float g_NpcRandomDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcRandomSlowdown[MAX_BOSSES][Difficulty_Max]; -static int g_NpcRandomStunType[MAX_BOSSES]; - -static bool g_NpcHasJaratePlayerEnabled[MAX_BOSSES]; -static int g_NpcJarateAttackIndexes[MAX_BOSSES]; -static float g_NpcJarateDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasJaratePlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasMilkPlayerEnabled[MAX_BOSSES]; -static int g_NpcMilkAttackIndexes[MAX_BOSSES]; -static float g_NpcMilkDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasMilkPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasGasPlayerEnabled[MAX_BOSSES]; -static int g_NpcGasAttackIndexes[MAX_BOSSES]; -static float g_NpcGasDuration[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasGasPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasMarkPlayerEnabled[MAX_BOSSES]; -static int g_NpcMarkAttackIndexes[MAX_BOSSES]; -static float g_NpcMarkDuration[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasSilentMarkPlayerEnabled[MAX_BOSSES]; -static int g_NpcSilentMarkAttackIndexes[MAX_BOSSES]; -static float g_NpcSilentMarkDuration[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasIgnitePlayerEnabled[MAX_BOSSES]; -static int g_NpcIgniteAttackIndexes[MAX_BOSSES]; -static float g_NpcIgniteDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcIgniteDelay[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasStunPlayerEnabled[MAX_BOSSES]; -static int g_NpcStunAttackIndexes[MAX_BOSSES]; -static float g_NpcStunAttackDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStunAttackSlowdown[MAX_BOSSES][Difficulty_Max]; -static int g_NpcStunAttackType[MAX_BOSSES]; -static bool g_NpcHasStunPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasBleedPlayerEnabled[MAX_BOSSES]; -static int g_NpcBleedAttackIndexes[MAX_BOSSES]; -static float g_NpcBleedDuration[MAX_BOSSES][Difficulty_Max]; - -static bool g_NpcHasElectricPlayerEnabled[MAX_BOSSES]; -static int g_NpcElectricAttackIndexes[MAX_BOSSES]; -static float g_NpcElectricDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcElectricSlowdown[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasElectricPlayerBeamParticle[MAX_BOSSES]; - -static bool g_NpcHasSmitePlayerEnabled[MAX_BOSSES]; -static int g_NpcSmiteAttackIndexes[MAX_BOSSES]; -static float g_NpcSmiteDamage[MAX_BOSSES]; -static int g_NpcSmiteDamageType[MAX_BOSSES]; -static int g_NpcSmiteColorR[MAX_BOSSES]; -static int g_NpcSmiteColorG[MAX_BOSSES]; -static int g_NpcSmiteColorB[MAX_BOSSES]; -static int g_NpcSmiteTransparency[MAX_BOSSES]; -static bool g_NpcHasSmiteMessage[MAX_BOSSES]; - -static bool g_NpcHasShockwaveEnabled[MAX_BOSSES]; -static float g_NpcShockwaveHeight[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveRange[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveDrain[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveForce[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasShockwaveStunEnabled[MAX_BOSSES]; -static float g_NpcShockwaveStunDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcShockwaveStunSlowdown[MAX_BOSSES][Difficulty_Max]; -static int g_NpcShockwaveAttackIndexes[MAX_BOSSES]; -static float g_NpcShockwaveWidth[MAX_BOSSES][2]; -static float g_NpcShockwaveAmplitude[MAX_BOSSES]; - -static int g_NpcState[MAX_BOSSES] = { -1, ... }; -static int g_NpcCurrentAnimationSequence[MAX_BOSSES] = { -1, ... }; -static bool g_NpcHasCurrentAnimationSequenceIsLooped[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesChaseInitialAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesCloakStartAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesCloakEndAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUseFireAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUseStartFleeAnimation[MAX_BOSSES] = { false, ... }; -bool g_NpcUsesHealAnimation[MAX_BOSSES] = { false, ... }; -static float g_NpcTimeUntilChaseAfterInitial[MAX_BOSSES]; -float g_NpcCurrentAnimationSequencePlaybackRate[MAX_BOSSES] = { 1.0, ... }; -static char g_NpcCurrentAnimationSequenceName[MAX_BOSSES][256]; -bool g_NpcAlreadyAttacked[MAX_BOSSES]; - bool g_NpcCopyAlerted[MAX_BOSSES]; -static bool g_NpcHasEarthquakeFootstepsEnabled[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsAmplitude[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsFrequency[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsDuration[MAX_BOSSES]; -static float g_NpcEarthquakeFootstepsRadius[MAX_BOSSES]; -static bool g_NpcHasEarthquakeFootstepsAirShake[MAX_BOSSES]; - -static int g_NpcSoundCountToAlert[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasDisappearOnStun[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasDropItemOnStun[MAX_BOSSES]; -static int g_NpcDropItemType[MAX_BOSSES]; static bool g_NpcHasIsBoxingBoss[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasNormalSoundHookEnabled[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasCloakToHealEnabled[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasCanUseChaseInitialAnimation[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasOldAnimationAIState[MAX_BOSSES] = { false, ... }; -static bool g_NpcHasCanUseAlertWalkingAnimation[MAX_BOSSES] = { false, ... }; - -static bool g_NpcHasXenobladeBreakComboSystem[MAX_BOSSES]; -static float g_NpcXenobladeBreakDuration[MAX_BOSSES]; -static float g_NpcXenobladeToppleDuration[MAX_BOSSES]; -static float g_NpcXenobladeToppleSlowdown[MAX_BOSSES]; -static float g_NpcXenobladeDazeDuration[MAX_BOSSES]; - -static char jaratePlayerParticle[PLATFORM_MAX_PATH]; -static char milkPlayerParticle[PLATFORM_MAX_PATH]; -static char gasPlayerParticle[PLATFORM_MAX_PATH]; -static char stunPlayerParticle[PLATFORM_MAX_PATH]; -static char electricPlayerParticleRed[PLATFORM_MAX_PATH]; -static char electricPlayerParticleBlue[PLATFORM_MAX_PATH]; -static char keyModel[PLATFORM_MAX_PATH]; bool g_NpcStealingLife[MAX_BOSSES]; Handle g_NpcLifeStealTimer[MAX_BOSSES]; static Handle g_NpcInstantKillThink[MAX_BOSSES]; -static bool g_NpcHasTrapsEnabled[MAX_BOSSES]; -static int g_NpcTrapType[MAX_BOSSES]; -static float g_NpcNextTrapSpawn[MAX_BOSSES][Difficulty_Max]; - -//Special thanks to The Gaben -bool g_SlenderHasDamageParticleEffect[MAX_BOSSES]; -char damageEffectParticle[PLATFORM_MAX_PATH]; -char damageEffectSound[PLATFORM_MAX_PATH]; - -static int g_NpcAutoChaseThreshold[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddGeneral[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddFootstep[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddLoudFootstep[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddVoice[MAX_BOSSES][Difficulty_Max]; -static int g_NpcAutoChaseAddWeapon[MAX_BOSSES][Difficulty_Max]; -static bool g_NpcHasAutoChaseSprinters[MAX_BOSSES] = { false, ... }; -float g_NpcAutoChaseSprinterCooldown[MAX_BOSSES]; - -bool g_NpcInAutoChase[MAX_BOSSES]; - -float g_NpcLaserTimer[MAX_BOSSES]; - -//KF2 Patriarch's Heal Logic -static bool g_NpcHasCanSelfHeal[MAX_BOSSES] = { false, ... }; -bool g_NpcIsRunningToHeal[MAX_BOSSES] = { false, ... }; -float g_NpcFleeHealTimer[MAX_BOSSES]; -bool g_NpcIsHealing[MAX_BOSSES] = { false, ... }; -bool g_NpcSetHealDestination[MAX_BOSSES] = { false, ... }; -static float g_NpcStartSelfHealPercentage[MAX_BOSSES]; -static float g_NpcSelfHealPercentageOne[MAX_BOSSES]; -static float g_NpcSelfHealPercentageTwo[MAX_BOSSES]; -static float g_NpcSelfHealPercentageThree[MAX_BOSSES]; -int g_NpcSelfHealStage[MAX_BOSSES]; -int g_NpcHealCount[MAX_BOSSES]; - //Boxing stuff static int g_NpcBoxingCurrentDifficulty[MAX_BOSSES]; static int g_NpcBoxingRagePhase[MAX_BOSSES]; -static bool g_NpcHasHasCrawling[MAX_BOSSES] = { false, ... }; -bool g_NpcIsCrawling[MAX_BOSSES] = { false, ... }; -bool g_NpcChangeToCrawl[MAX_BOSSES] = { false, ... }; -static float g_NpcCrawlSpeedMultiplier[MAX_BOSSES][Difficulty_Max]; -float g_NpcCrawlDetectMins[MAX_BOSSES][3]; -float g_NpcCrawlDetectMaxs[MAX_BOSSES][3]; - -enum struct BaseAttackStructure -{ - int baseAttackType; - float baseAttackDamage; - float baseAttackDamageVsProps; - float baseAttackDamageForce; - int baseAttackDamageType; - float baseAttackDamageDelay; - float baseAttackRange; - float baseAttackDuration; - float baseAttackSpread; - float baseAttackBeginRange; - float baseAttackBeginFOV; - float baseAttackCooldown; - int baseAttackDisappear; - int baseAttackRepeat; - int baseAttackMaxRepeats; - int currentAttackRepeat; - int weaponAttackIndex; - - bool baseAttackLifeSteal; - float baseAttackLifeStealDuration; - - float baseAttackProjectileDamage; - float baseAttackProjectileSpeed; - float baseAttackProjectileRadius; - float baseAttackProjectileDeviation; - int baseAttackProjectileCount; - int baseAttackProjectileType; - bool baseAttackProjectileCrits; - float baseAttackProjectileIceSlowdownPercent; - float baseAttackProjectileIceSlowdownDuration; - - int baseAttackBulletCount; - float baseAttackBulletDamage; - float baseAttackBulletSpread; - - float baseAttackNextAttackTime; - - float baseAttackLaserDamage; - float baseAttackLaserSize; - int baseAttackLaserColor[3]; - bool baseAttackLaserAttachment; - float baseAttackLaserDuration; - float baseAttackLaserNoise; - - bool baseAttackPullIn; - - bool baseAttackWhileRunning; - float baseAttackRunSpeed; - float baseAttackRunDuration; - float baseAttackRunDelay; - - int baseAttackUseOnDifficulty; - int baseAttackBlockOnDifficulty; +static bool g_ClientShouldBeForceChased[MAX_BOSSES][MAXTF2PLAYERS]; - int baseAttackExplosiveDanceRadius; +GlobalForward g_OnChaserBossStartAttackFwd; +GlobalForward g_OnChaserBossEndAttackFwd; - int baseAttackWeaponTypeInt; - bool baseAttackIgnoreAlwaysLooking; - bool baseAttackGestures; - bool baseAttackDeathCamOnLowHealth; - float baseAttackUseOnHealth; - float baseAttackBlockOnHealth; - bool baseAttackDontInterruptChaseInitial; -} - -int g_SlenderOldState[MAX_BOSSES]; -float g_LastPos[MAX_BOSSES][3]; - -static int g_NpcBaseAttacksCount[MAX_BOSSES]; -BaseAttackStructure g_NpcBaseAttacks[MAX_BOSSES][SF2_CHASER_BOSS_MAX_ATTACKS][Difficulty_Max]; -static int g_NpcCurrentAttackIndex[MAX_BOSSES]; -float g_NpcBaseAttackRunDurationTime[MAX_BOSSES][SF2_CHASER_BOSS_MAX_ATTACKS]; -float g_NpcBaseAttackRunDelayTime[MAX_BOSSES][SF2_CHASER_BOSS_MAX_ATTACKS]; - -#include "sf2/npc/npc_chaser_mind.sp" +/*#include "sf2/npc/npc_chaser_mind.sp" #include "sf2/npc/npc_chaser_attacks.sp" #include "sf2/npc/npc_chaser_pathing.sp" -#include "sf2/npc/npc_chaser_projectiles.sp" +#include "sf2/npc/npc_chaser_projectiles.sp"*/ #include "sf2/npc/npc_creeper.sp" -#include "sf2/methodmaps.sp" void NPCChaserInitialize() { @@ -332,370 +49,53 @@ void NPCChaserInitialize() { NPCChaserResetValues(npcIndex); } -} - -void NPCChaserResetAnimationInfo(int npcIndex, int sequence = 0) -{ - g_NpcCurrentAnimationSequence[npcIndex] = sequence; - g_NpcHasCurrentAnimationSequenceIsLooped[npcIndex] = false; - g_NpcCurrentAnimationSequencePlaybackRate[npcIndex] = 1.0; - g_NpcUsesChaseInitialAnimation[npcIndex] = false; - g_NpcUsesCloakStartAnimation[npcIndex] = false; - g_NpcUsesCloakEndAnimation[npcIndex] = false; - g_NpcUseFireAnimation[npcIndex] = false; - g_NpcUsesHealAnimation[npcIndex] = false; - g_NpcUseStartFleeAnimation[npcIndex] = false; - for (int i = 0; i < 3; i++) - { - g_NpcUsesRageAnimation[npcIndex][i] = false; - } -} - -float NPCChaserGetWalkSpeed(int npcIndex, int difficulty) -{ - return g_NpcWalkSpeed[npcIndex][difficulty]; -} - -float NPCChaserGetMaxWalkSpeed(int npcIndex, int difficulty) -{ - return g_NpcMaxWalkSpeed[npcIndex][difficulty]; -} - -float NPCChaserGetWakeRadius(int npcIndex) -{ - return g_NpcWakeRadius[npcIndex]; -} - -int NPCChaserGetAttackCount(int npcIndex) -{ - return g_NpcBaseAttacksCount[npcIndex]; -} - -int NPCChaserGetAttackType(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackType; -} - -float NPCChaserGetAttackDamage(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamage; -} - -float NPCChaserGetAttackDamageVsProps(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackDamageVsProps; -} - -float NPCChaserGetAttackDamageForce(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamageForce; -} -/* -float NPCChaserGetAttackLifeStealDuration(int npcIndex,int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLifeStealDuration; -} -*/ -bool NPCChaserGetAttackLifeStealState(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackLifeSteal; -} - -bool NPCChaserGetAttackWhileRunningState(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackWhileRunning; -} - -float NPCChaserGetAttackRunSpeed(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRunSpeed; -} - -float NPCChaserGetAttackRunDuration(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRunDuration; -} - -float NPCChaserGetAttackRunDelay(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRunDelay; -} - -int NPCChaserGetAttackUseOnDifficulty(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackUseOnDifficulty; -} - -int NPCChaserGetAttackBlockOnDifficulty(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackBlockOnDifficulty; -} - -int NPCChaserGetAttackExplosiveDanceRadius(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackExplosiveDanceRadius; -} - -int NPCChaserGetAttackWeaponTypeInt(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackWeaponTypeInt; -} - -int NPCChaserGetAttackProjectileType(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackProjectileType; -} - -float NPCChaserGetAttackProjectileDamage(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileDamage; -} - -float NPCChaserGetAttackProjectileSpeed(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileSpeed; -} - -float NPCChaserGetAttackProjectileRadius(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileRadius; -} - -float NPCChaserGetAttackProjectileDeviation(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileDeviation; -} - -int NPCChaserGetAttackProjectileCount(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileCount; -} - -bool NPCChaserGetAttackProjectileCrits(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileCrits; -} - -float NPCChaserGetAttackProjectileIceSlowdownPercent(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileIceSlowdownPercent; -} - -float NPCChaserGetAttackProjectileIceSlowdownDuration(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackProjectileIceSlowdownDuration; -} - -int NPCChaserGetAttackBulletCount(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBulletCount; -} - -float NPCChaserGetAttackBulletDamage(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBulletDamage; -} - -float NPCChaserGetAttackBulletSpread(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBulletSpread; -} - -float NPCChaserGetAttackLaserDamage(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackLaserDamage; -} - -float NPCChaserGetAttackLaserSize(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserSize; -} - -void NPCChaserGetAttackLaserColor(int npcIndex, int attackIndex, int color[3]) -{ - color[0] = g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserColor[0]; - color[1] = g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserColor[1]; - color[2] = g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserColor[2]; -} - -bool NPCChaserGetAttackLaserAttachmentState(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserAttachment; -} - -float NPCChaserGetAttackLaserDuration(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackLaserDuration; -} - -float NPCChaserGetAttackLaserNoise(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackLaserNoise; -} - -bool NPCChaserGetAttackPullIn(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackPullIn; -} - -int NPCChaserGetAttackDamageType(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamageType; -} - -int NPCChaserGetAttackDisappear(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDisappear; -} - -int NPCChaserGetAttackRepeat(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackRepeat; -} - -int NPCChaserGetMaxAttackRepeats(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackMaxRepeats; -} - -bool NPCChaserGetAttackIgnoreAlwaysLooking(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackIgnoreAlwaysLooking; -} - -float NPCChaserGetAttackDamageDelay(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDamageDelay; -} - -float NPCChaserGetAttackRange(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackRange; -} - -float NPCChaserGetAttackDuration(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDuration; -} - -float NPCChaserGetAttackSpread(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackSpread; -} - -float NPCChaserGetAttackBeginRange(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBeginRange; -} - -float NPCChaserGetAttackBeginFOV(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackBeginFOV; -} -bool NPCChaserGetAttackGestureState(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackGestures; -} - -bool NPCChaserGetAttackDeathCamOnLowHealth(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackDeathCamOnLowHealth; -} - -float NPCChaserGetAttackUseOnHealth(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackUseOnHealth; -} - -float NPCChaserGetAttackBlockOnHealth(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackBlockOnHealth; -} - -bool NPCChaserGetAttackInterruptChaseInitialState(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackDontInterruptChaseInitial; -} - -void NPCSetCurrentAttackIndex(int npcIndex, int attackIndex) -{ - g_NpcCurrentAttackIndex[npcIndex] = attackIndex; -} - -int NPCGetCurrentAttackIndex(int npcIndex) -{ - return g_NpcCurrentAttackIndex[npcIndex]; -} - -void NpcSetCurrentAttackRepeat(int npcIndex, int attackIndex, int value) -{ - g_NpcBaseAttacks[npcIndex][attackIndex][1].currentAttackRepeat = value; -} - -int NpcGetCurrentAttackRepeat(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].currentAttackRepeat; -} - -float NPCChaserGetAttackCooldown(int npcIndex, int attackIndex, int difficulty) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][difficulty].baseAttackCooldown; -} - -float NPCChaserGetNextAttackTime(int npcIndex, int attackIndex) -{ - return g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackNextAttackTime; -} - -void NPCChaserSetNextAttackTime(int npcIndex, int attackIndex, float value) -{ - g_NpcBaseAttacks[npcIndex][attackIndex][1].baseAttackNextAttackTime = value; -} - -bool NPCChaserIsStunEnabled(int npcIndex) -{ - return g_NpcHasStunEnabled[npcIndex]; -} - -bool NPCChaserIsStunByFlashlightEnabled(int npcIndex) -{ - return g_NpcStunFlashlightEnabled[npcIndex]; + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); } -bool NPCChaserCanChaseInitialOnStun(int npcIndex) +static void OnPutInServer(SF2_BasePlayer client) { - return g_NpcChaseInitialOnStun[npcIndex]; + ResetClientNPCStates(client); } -bool NPCChaseHasKeyDrop(int npcIndex) +static void OnPlayerSpawn(SF2_BasePlayer client) { - return g_NpcHasKeyDrop[npcIndex]; + ResetClientNPCStates(client); } -float NPCChaserGetStunFlashlightDamage(int npcIndex) +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { - return g_NpcStunFlashlightDamage[npcIndex]; + if (!fake) + { + ResetClientNPCStates(client); + } } -float NPCChaserGetStunCooldown(int npcIndex) +static void OnPlayerEscape(SF2_BasePlayer client) { - return g_NpcStunCooldown[npcIndex]; + ResetClientNPCStates(client); } -float NPCChaserGetStunHealth(int npcIndex) +static void OnBossRemoved(SF2NPC_BaseNPC npc) { - return g_NpcStunHealth[npcIndex]; + if (npc.Type == SF2BossType_Chaser) + { + NPCChaserOnRemoveProfile(npc.Index); + } } -void NPCChaserSetStunHealth(int npcIndex, float amount) +SF2ChaserBossProfileData NPCChaserGetProfileData(int npcIndex) { - g_NpcStunHealth[npcIndex] = amount; -} + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(npcIndex, profile, sizeof(profile)); -void NPCChaserSetStunInitialHealth(int npcIndex, float amount) -{ - g_NpcStunInitialHealth[npcIndex] = amount; + SF2ChaserBossProfileData profileData; + g_ChaserBossProfileData.GetArray(profile, profileData, sizeof(profileData)); + return profileData; } float NPCChaserGetAddStunHealth(int npcIndex) @@ -708,3672 +108,328 @@ void NPCChaserSetAddStunHealth(int npcIndex, float amount) g_NpcStunAddHealth[npcIndex] += amount; } -void NPCChaserAddStunHealth(int npcIndex, float amount) +float NPCChaserGetDeathInitialHealth(int npcIndex, int difficulty) { - if (GetGameTime() >= g_SlenderNextStunTime[npcIndex]) - { - NPCChaserSetStunHealth(npcIndex, NPCChaserGetStunHealth(npcIndex) + amount); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_STUN, 0, "Boss %i, new amount: %0.0f", npcIndex, NPCChaserGetStunHealth(npcIndex)); - #endif - } -} - -float NPCChaserGetStunInitialHealth(int npcIndex) -{ - return g_NpcStunInitialHealth[npcIndex]; -} - -float NPCChaserGetAlertGracetime(int npcIndex, int difficulty) -{ - return g_NpcAlertGracetime[npcIndex][difficulty]; + return g_NpcDeathInitialHealth[npcIndex][difficulty]; } -float NPCChaserGetAlertDuration(int npcIndex, int difficulty) +float NPCChaserGetDeathHealth(int npcIndex, int difficulty) { - return g_NpcAlertDuration[npcIndex][difficulty]; + return g_NpcDeathHealth[npcIndex][difficulty]; } -float NPCChaserGetChaseDuration(int npcIndex, int difficulty) +void NPCChaserSetDeathHealth(int npcIndex, int difficulty, float amount) { - return g_NpcChaseDuration[npcIndex][difficulty]; + g_NpcDeathHealth[npcIndex][difficulty] = amount; } -float NPCChaserGetWanderRangeMin(int npcIndex, int difficulty) +bool NPCChaserIsBoxingBoss(int npcIndex) { - return g_NpcSearchWanderRangeMin[npcIndex][difficulty]; + return g_NpcHasIsBoxingBoss[npcIndex]; } -float NPCChaserGetWanderRangeMax(int npcIndex, int difficulty) +int NPCChaserGetBoxingDifficulty(int npcIndex) { - return g_NpcSearchWanderRangeMax[npcIndex][difficulty]; + return g_NpcBoxingCurrentDifficulty[npcIndex]; } -bool NPCChaserIsCloakEnabled(int npcIndex) +void NPCChaserSetBoxingDifficulty(int npcIndex, int value) { - return g_NpcCloakEnabled[npcIndex]; + g_NpcBoxingCurrentDifficulty[npcIndex] = value; } -bool NPCChaserIsCloaked(int npcIndex) +bool ShouldClientBeForceChased(SF2NPC_BaseNPC controller, SF2_BasePlayer client) { - return g_NpcHasCloaked[npcIndex]; + return g_ClientShouldBeForceChased[controller.Index][client.index]; } -float NPCChaserGetCloakCooldown(int npcIndex, int difficulty) -{ - return g_NpcCloakCooldown[npcIndex][difficulty]; -} - -float NPCChaserGetCloakRange(int npcIndex, int difficulty) -{ - return g_NpcCloakRange[npcIndex][difficulty]; -} - -float NPCChaserGetDecloakRange(int npcIndex, int difficulty) -{ - return g_NpcDecloakRange[npcIndex][difficulty]; -} - -float NPCChaserGetCloakDuration(int npcIndex, int difficulty) -{ - return g_NpcCloakDuration[npcIndex][difficulty]; -} - -float NPCChaserGetCloakSpeedMultiplier(int npcIndex, int difficulty) -{ - return g_NpcCloakSpeedMultiplier[npcIndex][difficulty]; -} - -bool NPCChaserIsProjectileEnabled(int npcIndex) -{ - return g_NpcHasProjectileEnabled[npcIndex]; -} - -bool NPCChaserHasCriticalRockets(int npcIndex) -{ - return g_NpcHasCriticalRockets[npcIndex]; -} - -bool NPCChaserUseShootGesture(int npcIndex) -{ - return g_NpcHasUseShootGesture[npcIndex]; -} - -bool NPCChaserUseProjectileAmmo(int npcIndex) -{ - return g_NpcHasUseProjectileAmmo[npcIndex]; -} - -int NPCChaserGetProjectileType(int npcIndex) -{ - return g_NpcProjectileType[npcIndex]; -} - -float NPCChaserGetProjectileCooldownMin(int npcIndex, int difficulty) -{ - return g_NpcProjectileCooldownMin[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileCooldownMax(int npcIndex, int difficulty) -{ - return g_NpcProjectileCooldownMax[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileSpeed(int npcIndex, int difficulty) -{ - return g_NpcProjectileSpeed[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileDamage(int npcIndex, int difficulty) -{ - return g_NpcProjectileDamage[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileRadius(int npcIndex, int difficulty) -{ - return g_NpcProjectileRadius[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileDeviation(int npcIndex, int difficulty) -{ - return g_NpcProjectileDeviation[npcIndex][difficulty]; -} - -int NPCChaserGetProjectileCount(int npcIndex, int difficulty) -{ - return g_NpcProjectileCount[npcIndex][difficulty]; -} - -float NPCChaserGetIceballSlowdownDuration(int npcIndex, int difficulty) -{ - return g_IceballSlowdownDuration[npcIndex][difficulty]; -} - -float NPCChaserGetIceballSlowdownPercent(int npcIndex, int difficulty) -{ - return g_IceballSlowdownPercent[npcIndex][difficulty]; -} - -int NPCChaserGetLoadedProjectiles(int npcIndex, int difficulty) -{ - return g_NpcProjectileLoadedAmmo[npcIndex][difficulty]; -} - -float NPCChaserGetProjectileReloadTime(int npcIndex, int difficulty) -{ - return g_NpcProjectileReloadTime[npcIndex][difficulty]; -} -/* -float NPCChaserGetProjectileChargeUpDuration(int npcIndex,int difficulty) -{ - return g_NpcProjectileChargeUpTime[npcIndex][difficulty]; -} -*/ - -bool NPCChaserUseAdvancedDamageEffects(int npcIndex) -{ - return g_NpcHasUseAdvancedDamageEffects[npcIndex]; -} - -bool NPCChaserUseRandomAdvancedDamageEffects(int npcIndex) -{ - return g_NpcHasAdvancedDamageEffectsRandom[npcIndex]; -} - -bool NPCChaserAttachDamageParticle(int npcIndex) -{ - return g_NpcHasAttachDamageParticle[npcIndex]; -} - -int NPCChaserRandomEffectIndexes(int npcIndex) -{ - return g_NpcRandomAttackIndexes[npcIndex]; -} - -float NPCChaserRandomEffectDuration(int npcIndex, int difficulty) -{ - return g_NpcRandomDuration[npcIndex][difficulty]; -} - -float NPCChaserRandomEffectSlowdown(int npcIndex, int difficulty) -{ - return g_NpcRandomSlowdown[npcIndex][difficulty]; -} - -int NPCChaserRandomEffectStunType(int npcIndex) -{ - return g_NpcRandomStunType[npcIndex]; -} - -bool NPCChaserJaratePlayerOnHit(int npcIndex) -{ - return g_NpcHasJaratePlayerEnabled[npcIndex]; -} - -int NPCChaserGetJarateAttackIndexes(int npcIndex) -{ - return g_NpcJarateAttackIndexes[npcIndex]; -} - -float NPCChaserGetJarateDuration(int npcIndex, int difficulty) -{ - return g_NpcJarateDuration[npcIndex][difficulty]; -} - -bool NPCChaserGetJarateBeamParticle(int npcIndex) -{ - return g_NpcHasJaratePlayerBeamParticle[npcIndex]; -} - -bool NPCChaserMilkPlayerOnHit(int npcIndex) -{ - return g_NpcHasMilkPlayerEnabled[npcIndex]; -} - -int NPCChaserGetMilkAttackIndexes(int npcIndex) -{ - return g_NpcMilkAttackIndexes[npcIndex]; -} - -float NPCChaserGetMilkDuration(int npcIndex, int difficulty) -{ - return g_NpcMilkDuration[npcIndex][difficulty]; -} - -bool NPCChaserGetMilkBeamParticle(int npcIndex) -{ - return g_NpcHasMilkPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserGasPlayerOnHit(int npcIndex) -{ - return g_NpcHasGasPlayerEnabled[npcIndex]; -} - -int NPCChaserGetGasAttackIndexes(int npcIndex) -{ - return g_NpcGasAttackIndexes[npcIndex]; -} - -float NPCChaserGetGasDuration(int npcIndex, int difficulty) -{ - return g_NpcGasDuration[npcIndex][difficulty]; -} - -bool NPCChaserGetGasBeamParticle(int npcIndex) -{ - return g_NpcHasGasPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserMarkPlayerOnHit(int npcIndex) -{ - return g_NpcHasMarkPlayerEnabled[npcIndex]; -} - -int NPCChaserGetMarkAttackIndexes(int npcIndex) -{ - return g_NpcMarkAttackIndexes[npcIndex]; -} - -float NPCChaserGetMarkDuration(int npcIndex, int difficulty) -{ - return g_NpcMarkDuration[npcIndex][difficulty]; -} - -bool NPCChaserSilentMarkPlayerOnHit(int npcIndex) -{ - return g_NpcHasSilentMarkPlayerEnabled[npcIndex]; -} - -int NPCChaserGetSilentMarkAttackIndexes(int npcIndex) -{ - return g_NpcSilentMarkAttackIndexes[npcIndex]; -} - -float NPCChaserGetSilentMarkDuration(int npcIndex, int difficulty) -{ - return g_NpcSilentMarkDuration[npcIndex][difficulty]; -} - -bool NPCChaserIgnitePlayerOnHit(int npcIndex) -{ - return g_NpcHasIgnitePlayerEnabled[npcIndex]; -} - -int NPCChaserGetIgniteAttackIndexes(int npcIndex) -{ - return g_NpcIgniteAttackIndexes[npcIndex]; -} - -float NPCChaserGetIgniteDuration(int npcIndex, int difficulty) -{ - return g_NpcIgniteDuration[npcIndex][difficulty]; -} - -float NPCChaserGetIgniteDelay(int npcIndex, int difficulty) -{ - return g_NpcIgniteDelay[npcIndex][difficulty]; -} - -bool NPCChaserStunPlayerOnHit(int npcIndex) -{ - return g_NpcHasStunPlayerEnabled[npcIndex]; -} - -int NPCChaserGetStunAttackIndexes(int npcIndex) -{ - return g_NpcStunAttackIndexes[npcIndex]; -} - -float NPCChaserGetStunAttackDuration(int npcIndex, int difficulty) -{ - return g_NpcStunAttackDuration[npcIndex][difficulty]; -} - -float NPCChaserGetStunAttackSlowdown(int npcIndex, int difficulty) -{ - return g_NpcStunAttackSlowdown[npcIndex][difficulty]; -} - -int NPCChaserGetStunAttackType(int npcIndex) -{ - return g_NpcStunAttackType[npcIndex]; -} - -bool NPCChaserGetStunBeamParticle(int npcIndex) -{ - return g_NpcHasStunPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserBleedPlayerOnHit(int npcIndex) -{ - return g_NpcHasBleedPlayerEnabled[npcIndex]; -} - -int NPCChaserGetBleedAttackIndexes(int npcIndex) -{ - return g_NpcBleedAttackIndexes[npcIndex]; -} - -float NPCChaserGetBleedDuration(int npcIndex, int difficulty) -{ - return g_NpcBleedDuration[npcIndex][difficulty]; -} - -bool NPCChaserElectricPlayerOnHit(int npcIndex) -{ - return g_NpcHasElectricPlayerEnabled[npcIndex]; -} - -int NPCChaserGetElectricAttackIndexes(int npcIndex) -{ - return g_NpcElectricAttackIndexes[npcIndex]; -} - -float NPCChaserGetElectricDuration(int npcIndex, int difficulty) -{ - return g_NpcElectricDuration[npcIndex][difficulty]; -} - -float NPCChaserGetElectricSlowdown(int npcIndex, int difficulty) -{ - return g_NpcElectricSlowdown[npcIndex][difficulty]; -} - -bool NPCChaserGetElectricBeamParticle(int npcIndex) -{ - return g_NpcHasElectricPlayerBeamParticle[npcIndex]; -} - -bool NPCChaserSmitePlayerOnHit(int npcIndex) -{ - return g_NpcHasSmitePlayerEnabled[npcIndex]; -} - -bool NPCChaserSmitePlayerMessage(int npcIndex) -{ - return g_NpcHasSmiteMessage[npcIndex]; -} - -int NPCChaserGetSmiteAttackIndexes(int npcIndex) -{ - return g_NpcSmiteAttackIndexes[npcIndex]; -} - -bool NPCChaserXenobladeSystemEnabled(int npcIndex) -{ - return g_NpcHasXenobladeBreakComboSystem[npcIndex]; -} - -float NPCChaserGetXenobladeBreakDuration(int npcIndex) -{ - return g_NpcXenobladeBreakDuration[npcIndex]; -} - -float NPCChaserGetXenobladeToppleDuration(int npcIndex) -{ - return g_NpcXenobladeToppleDuration[npcIndex]; -} - -float NPCChaserGetXenobladeToppleSlowdown(int npcIndex) -{ - return g_NpcXenobladeToppleSlowdown[npcIndex]; -} - -float NPCChaserGetXenobladeDazeDuration(int npcIndex) -{ - return g_NpcXenobladeDazeDuration[npcIndex]; -} - -float NPCChaserGetSmiteDamage(int npcIndex) -{ - return g_NpcSmiteDamage[npcIndex]; -} - -int NPCChaserGetSmiteDamageType(int npcIndex) -{ - return g_NpcSmiteDamageType[npcIndex]; -} - -int NPCChaserGetSmiteColorR(int npcIndex) -{ - return g_NpcSmiteColorR[npcIndex]; -} - -int NPCChaserGetSmiteColorG(int npcIndex) -{ - return g_NpcSmiteColorG[npcIndex]; -} - -int NPCChaserGetSmiteColorB(int npcIndex) -{ - return g_NpcSmiteColorB[npcIndex]; -} - -int NPCChaserGetSmiteColorTrans(int npcIndex) -{ - return g_NpcSmiteTransparency[npcIndex]; -} - -bool NPCChaserGetTrapState(int npcIndex) -{ - return g_NpcHasTrapsEnabled[npcIndex]; -} - -int NPCChaserGetTrapType(int npcIndex) -{ - return g_NpcTrapType[npcIndex]; -} - -float NPCChaserGetTrapSpawnTime(int npcIndex, int difficulty) -{ - return g_NpcNextTrapSpawn[npcIndex][difficulty]; -} - -bool NPCChaserDamageParticlesEnabled(int npcIndex) -{ - return g_SlenderHasDamageParticleEffect[npcIndex]; -} - -bool NPCChaserGetSelfHealState(int npcIndex) -{ - return g_NpcHasCanSelfHeal[npcIndex]; -} - -float NPCChaserGetStartSelfHealPercentage(int npcIndex) -{ - return g_NpcStartSelfHealPercentage[npcIndex]; -} - -float NPCChaserGetSelfHealPercentageOne(int npcIndex) -{ - return g_NpcSelfHealPercentageOne[npcIndex]; -} - -float NPCChaserGetSelfHealPercentageTwo(int npcIndex) -{ - return g_NpcSelfHealPercentageTwo[npcIndex]; -} - -float NPCChaserGetSelfHealPercentageThree(int npcIndex) -{ - return g_NpcSelfHealPercentageThree[npcIndex]; -} - -bool NPCChaserIsAutoChaseEnabled(int npcIndex) -{ - return g_SlenderHasAutoChaseEnabled[npcIndex]; -} - -int NPCChaserAutoChaseThreshold(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseThreshold[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddGeneral(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddGeneral[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddFootstep(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddFootstep[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddLoudFootstep(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddLoudFootstep[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddVoice(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddVoice[npcIndex][difficulty]; -} - -int NPCChaserAutoChaseAddWeapon(int npcIndex, int difficulty) -{ - return g_NpcAutoChaseAddWeapon[npcIndex][difficulty]; -} - -bool NPCChaserCanAutoChaseSprinters(int npcIndex) -{ - return g_NpcHasAutoChaseSprinters[npcIndex]; -} - -bool NPCChaserShockwaveOnAttack(int npcIndex) -{ - return g_NpcHasShockwaveEnabled[npcIndex]; -} - -bool NPCChaserShockwaveStunEnabled(int npcIndex) -{ - return g_NpcHasShockwaveStunEnabled[npcIndex]; -} - -int NPCChaserGetShockwaveAttackIndexes(int npcIndex) -{ - return g_NpcShockwaveAttackIndexes[npcIndex]; -} - -float NPCChaserGetShockwaveDrain(int npcIndex, int difficulty) -{ - return g_NpcShockwaveDrain[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveForce(int npcIndex, int difficulty) -{ - return g_NpcShockwaveForce[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveHeight(int npcIndex, int difficulty) -{ - return g_NpcShockwaveHeight[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveRange(int npcIndex, int difficulty) -{ - return g_NpcShockwaveRange[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveStunDuration(int npcIndex, int difficulty) -{ - return g_NpcShockwaveStunDuration[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveStunSlowdown(int npcIndex, int difficulty) -{ - return g_NpcShockwaveStunSlowdown[npcIndex][difficulty]; -} - -float NPCChaserGetShockwaveWidth(int npcIndex, int iType) -{ - return g_NpcShockwaveWidth[npcIndex][iType]; -} - -float NPCChaserGetShockwaveAmplitude(int npcIndex) -{ - return g_NpcShockwaveAmplitude[npcIndex]; -} - -bool NPCChaserGetEarthquakeFootstepsState(int npcIndex) -{ - return g_NpcHasEarthquakeFootstepsEnabled[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsAmplitude(int npcIndex) -{ - return g_NpcEarthquakeFootstepsAmplitude[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsFrequency(int npcIndex) -{ - return g_NpcEarthquakeFootstepsFrequency[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsDuration(int npcIndex) -{ - return g_NpcEarthquakeFootstepsDuration[npcIndex]; -} - -float NPCChaserGetEarthquakeFootstepsRadius(int npcIndex) -{ - return g_NpcEarthquakeFootstepsRadius[npcIndex]; -} - -bool NPCChaserGetEarthquakeFootstepsAirShakeState(int npcIndex) -{ - return g_NpcHasEarthquakeFootstepsAirShake[npcIndex]; -} - -int NPCChaserGetSoundCountToAlert(int npcIndex, int difficulty) -{ - return g_NpcSoundCountToAlert[npcIndex][difficulty]; -} - -bool NPCChaserCanDisappearOnStun(int npcIndex) -{ - return g_NpcHasDisappearOnStun[npcIndex]; -} - -bool NPCChaserCanDropItemOnStun(int npcIndex) -{ - return g_NpcHasDropItemOnStun[npcIndex]; -} - -int NPCChaserItemDropTypeStun(int npcIndex) -{ - return g_NpcDropItemType[npcIndex]; -} - -bool NPCChaserIsBoxingBoss(int npcIndex) -{ - return g_NpcHasIsBoxingBoss[npcIndex]; -} - -bool NPCChaserNormalSoundHookEnabled(int npcIndex) -{ - return g_NpcHasNormalSoundHookEnabled[npcIndex]; -} - -bool NPCChaserCanCloakToHeal(int npcIndex) -{ - return g_NpcHasCloakToHealEnabled[npcIndex]; -} - -bool NPCChaserCanUseChaseInitialAnimation(int npcIndex) -{ - return g_NpcHasCanUseChaseInitialAnimation[npcIndex]; -} - -bool NPCChaserOldAnimationAIEnabled(int npcIndex) -{ - return g_NpcHasOldAnimationAIState[npcIndex]; -} - -bool NPCChaserCanUseAlertWalkingAnimation(int npcIndex) -{ - return g_NpcHasCanUseAlertWalkingAnimation[npcIndex]; -} - -bool NPCChaserCanCrawl(int npcIndex) -{ - return g_NpcHasHasCrawling[npcIndex]; -} - -float NPCChaserGetCrawlSpeedMultiplier(int npcIndex, int difficulty) -{ - return g_NpcCrawlSpeedMultiplier[npcIndex][difficulty]; -} - -int NPCChaserGetState(int npcIndex) -{ - return g_NpcState[npcIndex]; -} - -void NPCChaserSetState(int npcIndex, int state) -{ - g_NpcState[npcIndex] = state; -} - -int NPCChaserGetBoxingDifficulty(int npcIndex) -{ - return g_NpcBoxingCurrentDifficulty[npcIndex]; -} - -void NPCChaserSetBoxingDifficulty(int npcIndex, int value) -{ - g_NpcBoxingCurrentDifficulty[npcIndex] = value; -} - -int NPCChaserGetBoxingRagePhase(int npcIndex) -{ - return g_NpcBoxingRagePhase[npcIndex]; -} - -void NPCChaserSetBoxingRagePhase(int npcIndex, int value) -{ - g_NpcBoxingRagePhase[npcIndex] = value; -} - -bool NPCChaserCanChaseOnLook(int npcIndex) -{ - return g_NpcChaseOnLook[npcIndex]; -} - -void NPCChaserOnSelectProfile(int npcIndex, bool invincible) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(npcIndex, profile, sizeof(profile)); - - g_NpcWakeRadius[npcIndex] = GetChaserProfileWakeRadius(profile); - - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - g_NpcWalkSpeed[npcIndex][difficulty] = GetChaserProfileWalkSpeed(profile, difficulty); - g_NpcMaxWalkSpeed[npcIndex][difficulty] = GetChaserProfileMaxWalkSpeed(profile, difficulty); - - g_NpcAlertGracetime[npcIndex][difficulty] = GetChaserProfileAlertGracetime(profile, difficulty); - g_NpcAlertDuration[npcIndex][difficulty] = GetChaserProfileAlertDuration(profile, difficulty); - g_NpcChaseDuration[npcIndex][difficulty] = GetChaserProfileChaseDuration(profile, difficulty); - - g_NpcSoundCountToAlert[npcIndex][difficulty] = GetChaserProfileSoundCountToAlert(profile, difficulty); - - g_NpcCloakCooldown[npcIndex][difficulty] = GetChaserProfileCloakCooldown(profile, difficulty); - g_NpcCloakRange[npcIndex][difficulty] = GetChaserProfileCloakRange(profile, difficulty); - g_NpcDecloakRange[npcIndex][difficulty] = GetChaserProfileDecloakRange(profile, difficulty); - g_NpcCloakDuration[npcIndex][difficulty] = GetChaserProfileCloakDuration(profile, difficulty); - g_NpcCloakSpeedMultiplier[npcIndex][difficulty] = GetChaserProfileCloakSpeedMultiplier(profile, difficulty); - - g_NpcProjectileCooldownMin[npcIndex][difficulty] = GetChaserProfileProjectileCooldownMin(profile, difficulty); - g_NpcProjectileCooldownMax[npcIndex][difficulty] = GetChaserProfileProjectileCooldownMax(profile, difficulty); - g_NpcProjectileSpeed[npcIndex][difficulty] = GetChaserProfileProjectileSpeed(profile, difficulty); - g_NpcProjectileDamage[npcIndex][difficulty] = GetChaserProfileProjectileDamage(profile, difficulty); - g_NpcProjectileRadius[npcIndex][difficulty] = GetChaserProfileProjectileRadius(profile, difficulty); - g_NpcProjectileDeviation[npcIndex][difficulty] = GetChaserProfileProjectileDeviation(profile, difficulty); - g_NpcProjectileCount[npcIndex][difficulty] = GetChaserProfileProjectileCount(profile, difficulty); - g_IceballSlowdownDuration[npcIndex][difficulty] = GetChaserProfileIceballSlowdownDuration(profile, difficulty); - g_IceballSlowdownPercent[npcIndex][difficulty] = GetChaserProfileIceballSlowdownPercent(profile, difficulty); - g_NpcProjectileLoadedAmmo[npcIndex][difficulty] = GetChaserProfileProjectileLoadedAmmo(profile, difficulty); - g_NpcProjectileReloadTime[npcIndex][difficulty] = GetChaserProfileProjectileAmmoReloadTime(profile, difficulty); - g_NpcProjectileChargeUpTime[npcIndex][difficulty] = GetChaserProfileProjectileChargeUpTime(profile, difficulty); - g_NpcProjectileAmmo[npcIndex] = g_NpcProjectileLoadedAmmo[npcIndex][difficulty]; - g_NpcProjectileTimeToReload[npcIndex] = g_NpcProjectileReloadTime[npcIndex][difficulty]; - - g_NpcRandomDuration[npcIndex][difficulty] = GetChaserProfileRandomEffectDuration(profile, difficulty); - g_NpcRandomSlowdown[npcIndex][difficulty] = GetChaserProfileRandomEffectSlowdown(profile, difficulty); - g_NpcJarateDuration[npcIndex][difficulty] = GetChaserProfileJaratePlayerDuration(profile, difficulty); - g_NpcMilkDuration[npcIndex][difficulty] = GetChaserProfileMilkPlayerDuration(profile, difficulty); - g_NpcGasDuration[npcIndex][difficulty] = GetChaserProfileGasPlayerDuration(profile, difficulty); - g_NpcMarkDuration[npcIndex][difficulty] = GetChaserProfileMarkPlayerDuration(profile, difficulty); - g_NpcSilentMarkDuration[npcIndex][difficulty] = GetChaserProfileSilentMarkPlayerDuration(profile, difficulty); - g_NpcIgniteDuration[npcIndex][difficulty] = GetChaserProfileIgnitePlayerDuration(profile, difficulty); - g_NpcIgniteDelay[npcIndex][difficulty] = GetChaserProfileIgnitePlayerDelay(profile, difficulty); - g_NpcStunAttackDuration[npcIndex][difficulty] = GetChaserProfileStunPlayerDuration(profile, difficulty); - g_NpcStunAttackSlowdown[npcIndex][difficulty] = GetChaserProfileStunPlayerSlowdown(profile, difficulty); - g_NpcBleedDuration[npcIndex][difficulty] = GetChaserProfileBleedPlayerDuration(profile, difficulty); - g_NpcElectricDuration[npcIndex][difficulty] = GetChaserProfileEletricPlayerDuration(profile, difficulty); - g_NpcElectricSlowdown[npcIndex][difficulty] = GetChaserProfileEletricPlayerSlowdown(profile, difficulty); - - g_NpcShockwaveDrain[npcIndex][difficulty] = GetChaserProfileShockwaveDrain(profile, difficulty); - g_NpcShockwaveForce[npcIndex][difficulty] = GetChaserProfileShockwaveForce(profile, difficulty); - g_NpcShockwaveHeight[npcIndex][difficulty] = GetChaserProfileShockwaveHeight(profile, difficulty); - g_NpcShockwaveRange[npcIndex][difficulty] = GetChaserProfileShockwaveRange(profile, difficulty); - g_NpcShockwaveStunDuration[npcIndex][difficulty] = GetChaserProfileShockwaveStunDuration(profile, difficulty); - g_NpcShockwaveStunSlowdown[npcIndex][difficulty] = GetChaserProfileShockwaveStunSlowdown(profile, difficulty); - - g_NpcNextTrapSpawn[npcIndex][difficulty] = GetChaserProfileTrapSpawnCooldown(profile, difficulty); - g_SlenderNextTrapPlacement[npcIndex] = GetGameTime() + g_NpcNextTrapSpawn[npcIndex][difficulty]; - - g_NpcSearchWanderRangeMin[npcIndex][difficulty] = GetChaserProfileWanderRangeMin(profile, difficulty); - g_NpcSearchWanderRangeMax[npcIndex][difficulty] = GetChaserProfileWanderRangeMax(profile, difficulty); - - g_NpcAutoChaseThreshold[npcIndex][difficulty] = GetChaserProfileAutoChaseCount(profile, difficulty); - g_NpcAutoChaseAddGeneral[npcIndex][difficulty] = GetChaserProfileAutoChaseAddGeneral(profile, difficulty); - g_NpcAutoChaseAddFootstep[npcIndex][difficulty] = GetChaserProfileAutoChaseAddFootstep(profile, difficulty); - g_NpcAutoChaseAddLoudFootstep[npcIndex][difficulty] = GetChaserProfileAutoChaseAddLoudFootstep(profile, difficulty); - g_NpcAutoChaseAddVoice[npcIndex][difficulty] = GetChaserProfileAutoChaseAddVoice(profile, difficulty); - g_NpcAutoChaseAddWeapon[npcIndex][difficulty] = GetChaserProfileAutoChaseAddWeapon(profile, difficulty); - g_NpcCrawlSpeedMultiplier[npcIndex][difficulty] = GetChaserProfileCrawlSpeedMultiplier(profile, difficulty); - } - - g_NpcBaseAttacksCount[npcIndex] = GetChaserProfileAttackCount(profile); - // Get attack data. - for (int i = 0; i < g_NpcBaseAttacksCount[npcIndex]; i++) - { - g_NpcBaseAttacks[npcIndex][i][1].baseAttackType = GetChaserProfileAttackType(profile, i); - for (int diffAtk = 0; diffAtk < Difficulty_Max; diffAtk++) - { - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamage = GetChaserProfileAttackDamage(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunSpeed = GetChaserProfileAttackRunSpeed(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackCooldown = GetChaserProfileAttackCooldown(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDamage = GetChaserProfileAttackProjectileDamage(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileSpeed = GetChaserProfileAttackProjectileSpeed(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileRadius = GetChaserProfileAttackProjectileRadius(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDeviation = GetChaserProfileAttackProjectileDeviation(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCount = GetChaserProfileAttackProjectileCount(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownPercent = GetChaserProfileAttackProjectileIceSlowPercent(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownDuration = GetChaserProfileAttackProjectileIceSlowDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletCount = GetChaserProfileAttackBulletCount(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletDamage = GetChaserProfileAttackBulletDamage(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletSpread = GetChaserProfileAttackBulletSpread(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDamage = GetChaserProfileAttackLaserDamage(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageForce = GetChaserProfileAttackDamageForce(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageType = GetChaserProfileAttackDamageType(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageDelay = GetChaserProfileAttackDamageDelay(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRange = GetChaserProfileAttackRange(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDuration = GetChaserProfileAttackDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackSpread = GetChaserProfileAttackSpread(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginRange = GetChaserProfileAttackBeginRange(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginFOV = GetChaserProfileAttackBeginFOV(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDisappear = GetChaserProfileAttackDisappear(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackIgnoreAlwaysLooking = GetChaserProfileAttackIgnoreAlwaysLooking(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeSteal = GetChaserProfileAttackLifeStealState(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeStealDuration = GetChaserProfileAttackLifeStealDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCrits = GetChaserProfileAttackCritProjectiles(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackWhileRunning = GetChaserProfileAttackRunWhileAttackingState(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDuration = GetChaserProfileAttackRunDuration(profile, i, diffAtk); - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDelay = GetChaserProfileAttackRunDelay(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackExplosiveDanceRadius = GetChaserProfileAttackExplosiveDanceRadius(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDuration = GetChaserProfileAttackLaserDuration(profile, i, diffAtk); - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDontInterruptChaseInitial = GetChaserProfileAttackChaseInitialInterruptState(profile, i, diffAtk); - } - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDamageVsProps = GetChaserProfileAttackDamageVsProps(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackRepeat = GetChaserProfileAttackRepeat(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackMaxRepeats = GetChaserProfileMaxAttackRepeats(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackNextAttackTime = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackProjectileType = GetChaserProfileAttackProjectileType(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserSize = GetChaserProfileAttackLaserSize(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnDifficulty = GetChaserProfileAttackUseOnDifficulty(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnDifficulty = GetChaserProfileAttackBlockOnDifficulty(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackWeaponTypeInt = GetChaserProfileAttackWeaponTypeInt(profile, i); - - int laserColor[3]; - GetChaserProfileAttackLaserColor(profile, i, laserColor); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor = laserColor; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserAttachment = GetChaserProfileEnableLaserAttachment(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserNoise = GetChaserProfileAttackLaserNoise(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackPullIn = GetChaserProfileAttackPullIn(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackGestures = GetChaserProfileAttackGesturesState(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDeathCamOnLowHealth = GetChaserProfileAttackDeathCamLowHealth(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnHealth = GetChaserProfileAttackUseOnHealth(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnHealth = GetChaserProfileAttackBlockOnHealth(profile, i); - g_NpcBaseAttacks[npcIndex][i][1].currentAttackRepeat = 0; - } - - // Get stun data. - if (!invincible) - { - g_NpcHasStunEnabled[npcIndex] = GetChaserProfileStunState(profile); - } - else - { - g_NpcHasStunEnabled[npcIndex] = false; - } - g_NpcStunCooldown[npcIndex] = GetChaserProfileStunCooldown(profile); - g_NpcStunFlashlightEnabled[npcIndex] = GetChaserProfileStunFlashlightState(profile); - g_NpcStunFlashlightDamage[npcIndex] = GetChaserProfileStunFlashlightDamage(profile); - g_NpcStunInitialHealth[npcIndex] = GetChaserProfileStunHealth(profile); - g_NpcChaseInitialOnStun[npcIndex] = GetChaserProfileStunOnChaseInitial(profile); - - g_NpcHasKeyDrop[npcIndex] = GetChaserProfileKeyDrop(profile); - - g_NpcCloakEnabled[npcIndex] = GetChaserProfileCloakState(profile); - g_NpcNextDecloakTime[npcIndex] = -1.0; - GetChaserProfileCloakOnSound(profile, g_SlenderCloakOnSound[npcIndex], sizeof(g_SlenderCloakOnSound[])); - GetChaserProfileCloakOffSound(profile, g_SlenderCloakOffSound[npcIndex], sizeof(g_SlenderCloakOffSound[])); - - float stunHealthPerPlayer = GetChaserProfileStunHealthPerPlayer(profile); - int count; - for (int client = 1; client <= MaxClients; client++) - { - SF2_BasePlayer player = SF2_BasePlayer(client); - if (player.IsValid && player.IsEliminated) - { - count++; - } - } - - stunHealthPerPlayer *= float(count); - g_NpcStunInitialHealth[npcIndex] += stunHealthPerPlayer; - - g_NpcHasAlwaysLookAtTarget[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_AlwaysLookAtTarget); - g_NpcHasAlwaysLookAtTargetWhileAttacking[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_AlwaysLookAtTargetWhileAttacking); - g_NpcHasAlwaysLookAtTargetWhileChasing[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_AlwaysLookAtTargetWhileChasing); - g_NpcIgnoreNonMarkedForChase[npcIndex] = NPCHasAttribute(npcIndex, SF2Attribute_IgnoreNonMarkedForChase); - - g_NpcChaseOnLook[npcIndex] = GetChaserProfileChaseOnLook(profile); - if (g_NpcChaseOnLookTarget[npcIndex] == null && g_NpcChaseOnLook[npcIndex]) - { - g_NpcChaseOnLookTarget[npcIndex] = new ArrayList(); - } - - NPCChaserSetStunHealth(npcIndex, NPCChaserGetStunInitialHealth(npcIndex)); - - NPCSetAddSpeed(npcIndex, -NPCGetAddSpeed(npcIndex)); - NPCSetAddMaxSpeed(npcIndex, -NPCGetAddMaxSpeed(npcIndex)); - NPCSetAddAcceleration(npcIndex, -NPCGetAddAcceleration(npcIndex)); - NPCChaserSetAddStunHealth(npcIndex, -NPCChaserGetAddStunHealth(npcIndex)); - - g_NpcHasEarthquakeFootstepsEnabled[npcIndex] = GetChaserProfileEarthquakeFootstepState(profile); - g_NpcEarthquakeFootstepsAmplitude[npcIndex] = GetChaserProfileEarthquakeFootstepAmplitude(profile); - g_NpcEarthquakeFootstepsFrequency[npcIndex] = GetChaserProfileEarthquakeFootstepFrequency(profile); - g_NpcEarthquakeFootstepsDuration[npcIndex] = GetChaserProfileEarthquakeFootstepDuration(profile); - g_NpcEarthquakeFootstepsRadius[npcIndex] = GetChaserProfileEarthquakeFootstepRadius(profile); - g_NpcHasEarthquakeFootstepsAirShake[npcIndex] = GetChaserProfileEarthquakeFootstepAirShake(profile); - - g_NpcHasDisappearOnStun[npcIndex] = GetChaserProfileDisappearOnStun(profile); - g_NpcHasDropItemOnStun[npcIndex] = GetChaserProfileStunItemDropState(profile); - g_NpcDropItemType[npcIndex] = GetChaserProfileStunItemDropType(profile); - g_NpcHasIsBoxingBoss[npcIndex] = GetChaserProfileBoxingState(profile); - g_NpcHasNormalSoundHookEnabled[npcIndex] = GetChaserProfileNormalSoundHook(profile); - g_NpcHasCloakToHealEnabled[npcIndex] = GetChaserProfileCloakToHeal(profile); - g_NpcHasCanUseChaseInitialAnimation[npcIndex] = GetChaserProfileChaseInitialAnimationState(profile); - g_NpcHasOldAnimationAIState[npcIndex] = GetChaserProfileOldAnimState(profile); - g_NpcHasCanUseAlertWalkingAnimation[npcIndex] = GetChaserProfileAlertWalkingState(profile); - g_NpcOriginalVisibility[npcIndex] = GetChaserProfileUnnerfedVisibility(profile); - - g_NpcHasHasCrawling[npcIndex] = GetChaserProfileCrawlState(profile); - GetChaserProfileCrawlMins(profile, g_NpcCrawlDetectMins[npcIndex]); - GetChaserProfileCrawlMaxs(profile, g_NpcCrawlDetectMaxs[npcIndex]); - - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - g_NpcCrawlDetectMins[npcIndex][0] /= 2.0; - g_NpcCrawlDetectMins[npcIndex][1] /= 2.0; - g_NpcCrawlDetectMins[npcIndex][2] /= 2.0; - g_NpcCrawlDetectMaxs[npcIndex][0] /= 2.0; - g_NpcCrawlDetectMaxs[npcIndex][1] /= 2.0; - g_NpcCrawlDetectMaxs[npcIndex][2] /= 2.0; - } - - g_NpcAlreadyAttacked[npcIndex] = false; - g_NpcVelocityCancel[npcIndex] = false; - - g_NpcStealingLife[npcIndex] = false; - g_NpcLifeStealTimer[npcIndex] = null; - - g_NpcHasCloaked[npcIndex] = false; - - //Get Projectile Data - g_NpcHasProjectileEnabled[npcIndex] = GetChaserProfileProjectileState(profile); - g_NpcProjectileType[npcIndex] = GetChaserProfileProjectileType(profile); - g_NpcHasCriticalRockets[npcIndex] = GetChaserProfileCriticalRockets(profile); - GetChaserProfileFireballShootSound(profile, g_SlenderFireballShootSound[npcIndex], sizeof(g_SlenderFireballShootSound[])); - GetChaserProfileFireballTrail(profile, g_SlenderFireballTrail[npcIndex], sizeof(g_SlenderFireballTrail[])); - GetChaserProfileFireballExplodeSound(profile, g_SlenderFireballExplodeSound[npcIndex], sizeof(g_SlenderFireballExplodeSound[])); - - GetChaserProfileIceballSlowSound(profile, g_SlenderIceballImpactSound[npcIndex], sizeof(g_SlenderIceballImpactSound[])); - GetChaserProfileIceballTrail(profile, g_SlenderIceballTrail[npcIndex], sizeof(g_SlenderIceballTrail)); - - GetChaserProfileRocketShootSound(profile, g_SlenderRocketShootSound[npcIndex], sizeof(g_SlenderRocketShootSound[])); - GetChaserProfileRocketModel(profile, g_SlenderRocketModel[npcIndex], sizeof(g_SlenderRocketModel[])); - GetChaserProfileRocketExplodeSound(profile, g_SlenderRocketExplodeSound[npcIndex], sizeof(g_SlenderRocketExplodeSound[])); - GetChaserProfileRocketTrail(profile, g_SlenderRocketTrailParticle[npcIndex], sizeof(g_SlenderRocketTrailParticle[])); - GetChaserProfileRocketExplodeParticle(profile, g_SlenderRocketExplodeParticle[npcIndex], sizeof(g_SlenderRocketExplodeParticle[])); - GetChaserProfileSentryRocketShootSound(profile, g_SlenderSentryRocketShootSound[npcIndex], sizeof(g_SlenderSentryRocketShootSound[])); - - GetChaserProfileGrenadeShootSound(profile, g_SlenderGrenadeShootSound[npcIndex], sizeof(g_SlenderGrenadeShootSound[])); - - GetChaserProfileArrowShootSound(profile, g_SlenderArrowShootSound[npcIndex], sizeof(g_SlenderArrowShootSound[])); - - GetChaserProfileManglerShootSound(profile, g_SlenderManglerShootSound[npcIndex], sizeof(g_SlenderManglerShootSound[])); - - GetChaserProfileBaseballShootSound(profile, g_SlenderBaseballShootSound[npcIndex], sizeof(g_SlenderBaseballShootSound[])); - - g_NpcHasUseShootGesture[npcIndex] = GetChaserProfileGestureShoot(profile); - g_NpcHasUseProjectileAmmo[npcIndex] = GetChaserProfileProjectileAmmoState(profile); - g_NpcHasUseChargeUpProjectiles[npcIndex] = GetChaserProfileChargeUpProjectilesState(profile); - g_NpcProjectileCooldown[npcIndex] = 0.0; - g_NpcReloadingProjectiles[npcIndex] = false; - - g_NpcHasUseAdvancedDamageEffects[npcIndex] = GetChaserProfileEnableAdvancedDamageEffects(profile); - g_NpcHasAdvancedDamageEffectsRandom[npcIndex] = GetChaserProfileEnableAdvancedDamageEffectsRandom(profile); - g_NpcHasAttachDamageParticle[npcIndex] = GetChaserProfileEnableAdvancedDamageParticles(profile); - g_NpcRandomAttackIndexes[npcIndex] = GetChaserProfileRandomAttackIndexes(profile); - g_NpcRandomStunType[npcIndex] = GetChaserProfileRandomStunType(profile); - - g_NpcHasJaratePlayerEnabled[npcIndex] = GetChaserProfileJarateState(profile); - g_NpcJarateAttackIndexes[npcIndex] = GetChaserProfileJarateAttackIndexes(profile); - g_NpcHasJaratePlayerBeamParticle[npcIndex] = GetChaserProfileJarateBeamParticle(profile); - GetChaserProfileJarateHitSound(profile, g_SlenderJarateHitSound[npcIndex], sizeof(g_SlenderJarateHitSound[])); - - g_NpcHasMilkPlayerEnabled[npcIndex] = GetChaserProfileMilkState(profile); - g_NpcMilkAttackIndexes[npcIndex] = GetChaserProfileMilkAttackIndexes(profile); - g_NpcHasMilkPlayerBeamParticle[npcIndex] = GetChaserProfileMilkBeamParticle(profile); - GetChaserProfileMilkHitSound(profile, g_SlenderMilkHitSound[npcIndex], sizeof(g_SlenderMilkHitSound[])); - - g_NpcHasGasPlayerEnabled[npcIndex] = GetChaserProfileGasState(profile); - g_NpcGasAttackIndexes[npcIndex] = GetChaserProfileGasAttackIndexes(profile); - g_NpcHasGasPlayerBeamParticle[npcIndex] = GetChaserProfileGasBeamParticle(profile); - GetChaserProfileGasHitSound(profile, g_SlenderGasHitSound[npcIndex], sizeof(g_SlenderGasHitSound[])); - - g_NpcHasMarkPlayerEnabled[npcIndex] = GetChaserProfileMarkState(profile); - g_NpcMarkAttackIndexes[npcIndex] = GetChaserProfileMarkAttackIndexes(profile); - - g_NpcHasSilentMarkPlayerEnabled[npcIndex] = GetChaserProfileSilentMarkState(profile); - g_NpcSilentMarkAttackIndexes[npcIndex] = GetChaserProfileSilentMarkAttackIndexes(profile); - - g_NpcHasIgnitePlayerEnabled[npcIndex] = GetChaserProfileIgniteState(profile); - g_NpcIgniteAttackIndexes[npcIndex] = GetChaserProfileIgniteAttackIndexes(profile); - - g_NpcHasStunPlayerEnabled[npcIndex] = GetChaserProfileStunAttackState(profile); - g_NpcStunAttackIndexes[npcIndex] = GetChaserProfileStunAttackIndexes(profile); - g_NpcStunAttackType[npcIndex] = GetChaserProfileStunDamageType(profile); - g_NpcHasStunPlayerBeamParticle[npcIndex] = GetChaserProfileStunAttackBeamParticle(profile); - GetChaserProfileStunHitSound(profile, g_SlenderStunHitSound[npcIndex], sizeof(g_SlenderStunHitSound[])); - - g_NpcHasBleedPlayerEnabled[npcIndex] = GetChaserProfileBleedState(profile); - g_NpcBleedAttackIndexes[npcIndex] = GetChaserProfileBleedAttackIndexes(profile); - - g_NpcHasElectricPlayerEnabled[npcIndex] = GetChaserProfileEletricAttackState(profile); - g_NpcElectricAttackIndexes[npcIndex] = GetChaserProfileEletricAttackIndexes(profile); - g_NpcHasElectricPlayerBeamParticle[npcIndex] = GetChaserProfileEletricBeamParticle(profile); - - g_NpcHasSmitePlayerEnabled[npcIndex] = GetChaserProfileSmiteState(profile); - g_NpcHasSmiteMessage[npcIndex] = GetChaserProfileSmiteMessage(profile); - g_NpcSmiteAttackIndexes[npcIndex] = GetChaserProfileSmiteAttackIndexes(profile); - g_NpcSmiteDamage[npcIndex] = GetChaserProfileSmiteDamage(profile); - g_NpcSmiteDamageType[npcIndex] = GetChaserProfileSmiteDamageType(profile); - GetChaserProfileSmiteHitSound(profile, g_SlenderSmiteSound[npcIndex], sizeof(g_SlenderSmiteSound[])); - - g_NpcHasXenobladeBreakComboSystem[npcIndex] = GetChaserProfileXenobladeCombo(profile); - g_NpcXenobladeBreakDuration[npcIndex] = GetChaserProfileXenobladeBreakDuration(profile); - g_NpcXenobladeToppleDuration[npcIndex] = GetChaserProfileXenobladeToppleDuration(profile); - g_NpcXenobladeToppleSlowdown[npcIndex] = GetChaserProfileXenobladeToppleSlowdown(profile); - g_NpcXenobladeDazeDuration[npcIndex] = GetChaserProfileXenobladeDazeDuration(profile); - - int smiteColor[4]; - GetChaserProfileSmiteColor(profile, smiteColor); - - g_NpcSmiteColorR[npcIndex] = smiteColor[0]; - g_NpcSmiteColorG[npcIndex] = smiteColor[1]; - g_NpcSmiteColorB[npcIndex] = smiteColor[2]; - g_NpcSmiteTransparency[npcIndex] = smiteColor[3]; - g_SlenderHasDamageParticleEffect[npcIndex] = GetChaserProfileDamageParticleState(profile); - - g_NpcHasShockwaveEnabled[npcIndex] = GetChaserProfileShockwaveState(profile); - g_NpcHasShockwaveStunEnabled[npcIndex] = GetChaserProfileShockwaveStunState(profile); - g_NpcShockwaveAttackIndexes[npcIndex] = GetChaserProfileShockwaveAttackIndexes(profile); - g_NpcShockwaveWidth[npcIndex][0] = GetChaserProfileShockwaveWidth1(profile); - g_NpcShockwaveWidth[npcIndex][1] = GetChaserProfileShockwaveWidth2(profile); - g_NpcShockwaveAmplitude[npcIndex] = GetChaserProfileShockwaveAmplitude(profile); - GetChaserProfileShockwaveBeamSprite(profile, g_SlenderShockwaveBeamSprite[npcIndex], sizeof(g_SlenderShockwaveBeamSprite[])); - GetChaserProfileShockwaveHaloSprite(profile, g_SlenderShockwaveHaloSprite[npcIndex], sizeof(g_SlenderShockwaveHaloSprite[])); - - g_NpcHasTrapsEnabled[npcIndex] = GetChaserProfileTrapState(profile); - g_NpcTrapType[npcIndex] = GetChaserProfileTrapType(profile); - GetChaserProfileTrapModel(profile, g_SlenderTrapModel[npcIndex], sizeof(g_SlenderTrapModel[])); - GetChaserProfileTrapDeploySound(profile, g_SlenderTrapDeploySound[npcIndex], sizeof(g_SlenderTrapDeploySound[])); - GetChaserProfileTrapMissSound(profile, g_SlenderTrapMissSound[npcIndex], sizeof(g_SlenderTrapMissSound[])); - GetChaserProfileTrapCatchSound(profile, g_SlenderTrapHitSound[npcIndex], sizeof(g_SlenderTrapHitSound[])); - GetChaserProfileTrapAnimIdle(profile, g_SlenderTrapAnimIdle[npcIndex], sizeof(g_SlenderTrapAnimIdle[])); - GetChaserProfileTrapAnimClose(profile, g_SlenderTrapAnimClose[npcIndex], sizeof(g_SlenderTrapAnimClose[])); - GetChaserProfileTrapAnimOpen(profile, g_SlenderTrapAnimOpen[npcIndex], sizeof(g_SlenderTrapAnimOpen[])); - - g_SlenderHasAutoChaseEnabled[npcIndex] = GetChaserProfileAutoChaseState(profile); - g_NpcHasAutoChaseSprinters[npcIndex] = GetChaserProfileAutoChaseSprinterState(profile); - g_NpcAutoChaseSprinterCooldown[npcIndex] = 0.0; - g_NpcInAutoChase[npcIndex] = false; - g_SlenderIsAutoChasingLoudPlayer[npcIndex] = false; - - g_SlenderChasesEndlessly[npcIndex] = GetChaserProfileEndlessChasingState(profile); - - g_NpcLaserTimer[npcIndex] = 0.0; - - g_NpcHasCanSelfHeal[npcIndex] = GetChaserProfileSelfHealState(profile); - g_NpcStartSelfHealPercentage[npcIndex] = GetChaserProfileSelfHealStartPercentage(profile); - g_NpcSelfHealPercentageOne[npcIndex] = GetChaserProfileSelfHealPercentageOne(profile); - g_NpcSelfHealPercentageTwo[npcIndex] = GetChaserProfileSelfHealPercentageTwo(profile); - g_NpcSelfHealPercentageThree[npcIndex] = GetChaserProfileSelfHealPercentageThree(profile); - g_NpcIsRunningToHeal[npcIndex] = false; - g_NpcIsHealing[npcIndex] = false; - g_NpcFleeHealTimer[npcIndex] = 0.0; - g_NpcSelfHealStage[npcIndex] = 0; - g_NpcHealCount[npcIndex] = 0; - g_NpcSetHealDestination[npcIndex] = false; - - g_NpcBoxingCurrentDifficulty[npcIndex] = 1; - g_NpcBoxingRagePhase[npcIndex] = 0; - - g_NpcCopyAlerted[npcIndex] = false; -} - -void NPCChaserOnRemoveProfile(int npcIndex) -{ - NPCChaserResetValues(npcIndex); -} - -/** - * Resets all global variables on a specified NPC. Usually this should be done last upon removing a boss from the game. - */ -static void NPCChaserResetValues(int npcIndex) -{ - g_NpcWakeRadius[npcIndex] = 0.0; - - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - g_NpcWalkSpeed[npcIndex][difficulty] = 0.0; - g_NpcMaxWalkSpeed[npcIndex][difficulty] = 0.0; - - g_NpcAlertGracetime[npcIndex][difficulty] = 0.0; - g_NpcAlertDuration[npcIndex][difficulty] = 0.0; - g_NpcChaseDuration[npcIndex][difficulty] = 0.0; - - g_NpcSoundCountToAlert[npcIndex][difficulty] = 0; - - g_NpcCloakCooldown[npcIndex][difficulty] = 0.0; - g_NpcCloakRange[npcIndex][difficulty] = 0.0; - g_NpcDecloakRange[npcIndex][difficulty] = 0.0; - g_NpcCloakDuration[npcIndex][difficulty] = 0.0; - g_NpcCloakSpeedMultiplier[npcIndex][difficulty] = 0.0; - - g_NpcProjectileCooldownMin[npcIndex][difficulty] = 0.0; - g_NpcProjectileCooldownMax[npcIndex][difficulty] = 0.0; - g_NpcProjectileSpeed[npcIndex][difficulty] = 0.0; - g_NpcProjectileDamage[npcIndex][difficulty] = 0.0; - g_NpcProjectileRadius[npcIndex][difficulty] = 0.0; - g_NpcProjectileDeviation[npcIndex][difficulty] = 0.0; - g_NpcProjectileCount[npcIndex][difficulty] = 0; - g_IceballSlowdownDuration[npcIndex][difficulty] = 0.0; - g_IceballSlowdownPercent[npcIndex][difficulty] = 0.0; - g_NpcProjectileLoadedAmmo[npcIndex][difficulty] = 0; - g_NpcProjectileReloadTime[npcIndex][difficulty] = 0.0; - g_NpcProjectileChargeUpTime[npcIndex][difficulty] = 0.0; - - g_NpcRandomDuration[npcIndex][difficulty] = 0.0; - g_NpcRandomSlowdown[npcIndex][difficulty] = 0.0; - g_NpcJarateDuration[npcIndex][difficulty] = 0.0; - g_NpcMilkDuration[npcIndex][difficulty] = 0.0; - g_NpcGasDuration[npcIndex][difficulty] = 0.0; - g_NpcMarkDuration[npcIndex][difficulty] = 0.0; - g_NpcSilentMarkDuration[npcIndex][difficulty] = 0.0; - g_NpcIgniteDuration[npcIndex][difficulty] = 0.0; - g_NpcIgniteDelay[npcIndex][difficulty] = 0.0; - g_NpcStunAttackDuration[npcIndex][difficulty] = 0.0; - g_NpcStunAttackSlowdown[npcIndex][difficulty] = 0.0; - g_NpcBleedDuration[npcIndex][difficulty] = 0.0; - g_NpcElectricDuration[npcIndex][difficulty] = 0.0; - g_NpcElectricSlowdown[npcIndex][difficulty] = 0.0; - - g_NpcShockwaveDrain[npcIndex][difficulty] = 0.0; - g_NpcShockwaveForce[npcIndex][difficulty] = 0.0; - g_NpcShockwaveHeight[npcIndex][difficulty] = 0.0; - g_NpcShockwaveRange[npcIndex][difficulty] = 0.0; - g_NpcShockwaveStunDuration[npcIndex][difficulty] = 0.0; - g_NpcShockwaveStunSlowdown[npcIndex][difficulty] = 0.0; - - g_NpcNextTrapSpawn[npcIndex][difficulty] = 0.0; - g_SlenderNextTrapPlacement[npcIndex] = 0.0; - - g_NpcSearchWanderRangeMin[npcIndex][difficulty] = 0.0; - g_NpcSearchWanderRangeMax[npcIndex][difficulty] = 0.0; - - g_NpcAutoChaseThreshold[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddGeneral[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddFootstep[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddLoudFootstep[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddVoice[npcIndex][difficulty] = 0; - g_NpcAutoChaseAddWeapon[npcIndex][difficulty] = 0; - - g_NpcCrawlSpeedMultiplier[npcIndex][difficulty] = 0.0; - } - - // Clear attack data. - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS - 1; i++) - { - // Base attack data. - g_NpcBaseAttacks[npcIndex][i][1].baseAttackType = SF2BossAttackType_Invalid; - for (int diffAtk = 0; diffAtk < Difficulty_Max; diffAtk++) - { - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamage = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunSpeed = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackCooldown = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDamage = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileSpeed = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileRadius = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileDeviation = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCount = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownPercent = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileIceSlowdownDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletCount = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletDamage = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBulletSpread = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDamage = 0.0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageForce = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageType = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDamageDelay = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRange = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackSpread = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginRange = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackBeginFOV = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDisappear = 0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackIgnoreAlwaysLooking = false; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeSteal = false; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLifeStealDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackProjectileCrits = false; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackWhileRunning = false; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDuration = 0.0; - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackRunDelay = 0.0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackExplosiveDanceRadius = 0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackLaserDuration = 0.0; - - g_NpcBaseAttacks[npcIndex][i][diffAtk].baseAttackDontInterruptChaseInitial = false; - } - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDamageVsProps = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackRepeat = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackMaxRepeats = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackNextAttackTime = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].weaponAttackIndex = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackProjectileType = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserSize = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor[0] = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor[1] = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserColor[2] = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserAttachment = false; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackLaserNoise = 0.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackPullIn = false; - g_NpcBaseAttacks[npcIndex][i][1].currentAttackRepeat = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnDifficulty = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnDifficulty = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackWeaponTypeInt = 0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackGestures = false; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackDeathCamOnLowHealth = false; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackUseOnHealth = -1.0; - g_NpcBaseAttacks[npcIndex][i][1].baseAttackBlockOnHealth = -1.0; - } - - g_NpcHasEarthquakeFootstepsEnabled[npcIndex] = false; - g_NpcEarthquakeFootstepsAmplitude[npcIndex] = 0.0; - g_NpcEarthquakeFootstepsFrequency[npcIndex] = 0.0; - g_NpcEarthquakeFootstepsDuration[npcIndex] = 0.0; - g_NpcEarthquakeFootstepsRadius[npcIndex] = 0.0; - g_NpcHasEarthquakeFootstepsAirShake[npcIndex] = false; - - g_NpcHasDisappearOnStun[npcIndex] = false; - g_NpcHasDropItemOnStun[npcIndex] = false; - g_NpcDropItemType[npcIndex] = 0; - g_NpcHasIsBoxingBoss[npcIndex] = false; - g_NpcHasNormalSoundHookEnabled[npcIndex] = false; - g_NpcHasCloakToHealEnabled[npcIndex] = false; - g_NpcHasCanUseChaseInitialAnimation[npcIndex] = false; - g_NpcHasOldAnimationAIState[npcIndex] = false; - g_NpcHasCanUseAlertWalkingAnimation[npcIndex] = false; - - g_NpcHasHasCrawling[npcIndex] = false; - g_NpcIsCrawling[npcIndex] = false; - - g_NpcHasStunEnabled[npcIndex] = false; - g_NpcAlreadyAttacked[npcIndex] = false; - g_NpcStunCooldown[npcIndex] = 0.0; - g_NpcStunFlashlightEnabled[npcIndex] = false; - g_NpcStunInitialHealth[npcIndex] = 0.0; - g_NpcStunAddHealth[npcIndex] = 0.0; - g_NpcChaseInitialOnStun[npcIndex] = false; - g_NpcOriginalVisibility[npcIndex] = false; - - g_NpcCloakEnabled[npcIndex] = false; - g_NpcNextDecloakTime[npcIndex] = -1.0; - g_NpcHasCloaked[npcIndex] = false; - g_NpcVelocityCancel[npcIndex] = false; - g_NpcStealingLife[npcIndex] = false; - g_NpcLifeStealTimer[npcIndex] = null; - g_NpcChaseOnLook[npcIndex] = false; - if (g_NpcChaseOnLookTarget[npcIndex] != null) - { - delete g_NpcChaseOnLookTarget[npcIndex]; - g_NpcChaseOnLookTarget[npcIndex] = null; - } - - g_NpcHasAlwaysLookAtTarget[npcIndex] = false; - g_NpcHasAlwaysLookAtTargetWhileAttacking[npcIndex] = false; - g_NpcHasAlwaysLookAtTargetWhileChasing[npcIndex] = false; - g_NpcIgnoreNonMarkedForChase[npcIndex] = false; - - g_NpcUsedRage1[npcIndex] = false; - g_NpcUsedRage2[npcIndex] = false; - g_NpcHasUsedRage3[npcIndex] = false; - - g_NpcHasProjectileEnabled[npcIndex] = false; - g_NpcProjectileType[npcIndex] = SF2BossProjectileType_Invalid; - g_NpcHasCriticalRockets[npcIndex] = false; - g_NpcHasUseShootGesture[npcIndex] = false; - g_NpcHasUseProjectileAmmo[npcIndex] = false; - g_NpcHasUseChargeUpProjectiles[npcIndex] = false; - g_NpcProjectileAmmo[npcIndex] = 0; - g_NpcProjectileCooldown[npcIndex] = 0.0; - g_NpcProjectileTimeToReload[npcIndex] = 0.0; - g_NpcReloadingProjectiles[npcIndex] = false; - - g_NpcHasUseAdvancedDamageEffects[npcIndex] = false; - g_NpcHasAdvancedDamageEffectsRandom[npcIndex] = false; - g_NpcHasAttachDamageParticle[npcIndex] = false; - g_NpcRandomAttackIndexes[npcIndex] = 0; - g_NpcRandomStunType[npcIndex] = 0; - g_NpcHasJaratePlayerEnabled[npcIndex] = false; - g_NpcJarateAttackIndexes[npcIndex] = 0; - g_NpcHasMilkPlayerEnabled[npcIndex] = false; - g_NpcMilkAttackIndexes[npcIndex] = 0; - g_NpcHasGasPlayerEnabled[npcIndex] = false; - g_NpcGasAttackIndexes[npcIndex] = 0; - g_NpcHasMarkPlayerEnabled[npcIndex] = false; - g_NpcMarkAttackIndexes[npcIndex] = 0; - g_NpcHasSilentMarkPlayerEnabled[npcIndex] = false; - g_NpcSilentMarkAttackIndexes[npcIndex] = 0; - g_NpcHasIgnitePlayerEnabled[npcIndex] = false; - g_NpcIgniteAttackIndexes[npcIndex] = 0; - g_NpcHasStunPlayerEnabled[npcIndex] = false; - g_NpcStunAttackIndexes[npcIndex] = 0; - g_NpcStunAttackType[npcIndex] = 0; - g_NpcHasBleedPlayerEnabled[npcIndex] = false; - g_NpcBleedAttackIndexes[npcIndex] = 0; - g_NpcHasElectricPlayerEnabled[npcIndex] = false; - g_NpcElectricAttackIndexes[npcIndex] = 0; - g_NpcHasSmitePlayerEnabled[npcIndex] = false; - g_NpcHasSmiteMessage[npcIndex] = false; - g_NpcSmiteAttackIndexes[npcIndex] = 0; - g_NpcHasXenobladeBreakComboSystem[npcIndex] = false; - g_NpcXenobladeBreakDuration[npcIndex] = 0.0; - g_NpcXenobladeToppleDuration[npcIndex] = 0.0; - g_NpcXenobladeToppleSlowdown[npcIndex] = 0.0; - g_NpcXenobladeDazeDuration[npcIndex] = 0.0; - g_NpcSmiteDamage[npcIndex] = 0.0; - g_NpcSmiteDamageType[npcIndex] = 0; - g_NpcSmiteColorR[npcIndex] = 0; - g_NpcSmiteColorG[npcIndex] = 0; - g_NpcSmiteColorB[npcIndex] = 0; - g_NpcSmiteTransparency[npcIndex] = 0; - g_NpcHasShockwaveEnabled[npcIndex] = false; - g_NpcHasShockwaveStunEnabled[npcIndex] = false; - g_NpcShockwaveAttackIndexes[npcIndex] = 0; - g_NpcShockwaveWidth[npcIndex][0] = 0.0; - g_NpcShockwaveWidth[npcIndex][1] = 0.0; - g_NpcShockwaveAmplitude[npcIndex] = 0.0; - g_NpcTimeUntilChaseAfterInitial[npcIndex] = 0.0; - g_SlenderHasDamageParticleEffect[npcIndex] = false; - - g_NpcHasTrapsEnabled[npcIndex] = false; - g_NpcTrapType[npcIndex] = 0; - - g_SlenderHasAutoChaseEnabled[npcIndex] = false; - g_NpcInAutoChase[npcIndex] = false; - g_NpcHasAutoChaseSprinters[npcIndex] = false; - g_NpcAutoChaseSprinterCooldown[npcIndex] = 0.0; - g_SlenderIsAutoChasingLoudPlayer[npcIndex] = false; - - g_SlenderChasesEndlessly[npcIndex] = false; - - NPCSetAddSpeed(npcIndex, -NPCGetAddSpeed(npcIndex)); - NPCSetAddMaxSpeed(npcIndex, -NPCGetAddMaxSpeed(npcIndex)); - NPCSetAddAcceleration(npcIndex, -NPCGetAddAcceleration(npcIndex)); - NPCChaserSetAddStunHealth(npcIndex, -NPCChaserGetAddStunHealth(npcIndex)); - - NPCChaserSetStunHealth(npcIndex, 0.0); - - g_NpcState[npcIndex] = -1; - - g_NpcLaserTimer[npcIndex] = 0.0; - - g_NpcHasCanSelfHeal[npcIndex] = false; - g_NpcStartSelfHealPercentage[npcIndex] = 0.0; - g_NpcSelfHealPercentageOne[npcIndex] = 0.0; - g_NpcSelfHealPercentageTwo[npcIndex] = 0.0; - g_NpcSelfHealPercentageThree[npcIndex] = 0.0; - g_NpcIsRunningToHeal[npcIndex] = false; - g_NpcIsHealing[npcIndex] = false; - g_NpcFleeHealTimer[npcIndex] = 0.0; - g_NpcSelfHealStage[npcIndex] = 0; - g_NpcHealCount[npcIndex] = 0; - g_NpcSetHealDestination[npcIndex] = false; - - g_NpcBoxingCurrentDifficulty[npcIndex] = 0; - g_NpcBoxingRagePhase[npcIndex] = 0; - - g_NpcCopyAlerted[npcIndex] = false; -} - -void Spawn_Chaser(int bossIndex) -{ - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - g_LastStuckTime[bossIndex] = 0.0; - g_SlenderOldState[bossIndex] = STATE_IDLE; - g_NpcCopyAlerted[bossIndex] = false; - g_NpcNextDecloakTime[bossIndex] = -1.0; - g_NpcIsCrawling[bossIndex] = false; - g_NpcChangeToCrawl[bossIndex] = false; - g_SlenderSoundPositionSetCooldown[bossIndex] = 0.0; - - NPCSetAddSpeed(bossIndex, -NPCGetAddSpeed(bossIndex)); - NPCSetAddMaxSpeed(bossIndex, -NPCGetAddMaxSpeed(bossIndex)); - NPCSetAddAcceleration(bossIndex, -NPCGetAddAcceleration(bossIndex)); - NPCChaserSetAddStunHealth(bossIndex, -NPCChaserGetAddStunHealth(bossIndex)); - - g_NpcInstantKillThink[bossIndex] = CreateTimer(0.0, Timer_InstantKillThink, bossIndex, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); - - for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) - { - g_SlenderNextWanderPos[bossIndex][difficulty] = GetGameTime() + - GetRandomFloat(GetChaserProfileWanderEnterTimeMin(profile, difficulty), GetChaserProfileWanderEnterTimeMax(profile, difficulty)); - } - -} - -void Despawn_Chaser(int bossIndex) -{ - g_NpcInstantKillThink[bossIndex] = null; -} - -static Action Timer_InstantKillThink(Handle timer, int bossIndex) -{ - if (NPCGetUniqueID(bossIndex) == -1) - { - return Plugin_Stop; - } - - if (timer != g_NpcInstantKillThink[bossIndex]) - { - return Plugin_Stop; - } - - CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); - if (!boss.IsValid()) - { - return Plugin_Stop; - } - - float radius = NPCGetInstantKillRadius(bossIndex); - - if (radius < 0.0) - { - return Plugin_Stop; - } - - if (g_SlenderInDeathcam[bossIndex]) - { - return Plugin_Continue; - } - - if ((NPCGetFlags(bossIndex) & SFF_MARKEDASFAKE)) - { - return Plugin_Continue; - } - - float slenderPos[3]; - boss.GetAbsOrigin(slenderPos); - - bool attackWaiters = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int ref = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - if (!ref || ref == INVALID_ENT_REFERENCE) - { - return Plugin_Continue; - } - SF2_BasePlayer target = SF2_BasePlayer(ref); - - if (!target.IsValid || target.IsInDeathCam || target.IsInGhostMode || !target.IsAlive || target.IsProxy) - { - return Plugin_Continue; - } - - if (!attackWaiters && target.IsEliminated) - { - return Plugin_Continue; - } - - float myPos[3]; - - target.GetAbsOrigin(myPos); - myPos[2] += 35.0; - slenderPos[2] += 35.0; - - if ((GetVectorSquareMagnitude(myPos, slenderPos) <= SquareFloat(radius) && - (GetGameTime() - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) - && !g_SlenderInDeathcam[bossIndex]) - { - if (target.CanSeeSlender(bossIndex, false, _, !attackWaiters)) - { - g_SlenderLastKill[bossIndex] = GetGameTime(); - slenderPos[2] -= 35.0; - target.StartDeathCam(bossIndex, slenderPos); - } - } - - return Plugin_Continue; -} - -// So this is how the thought process of the bosses should go. -// 1. Search for enemy; either by sight or by sound. -// - Any noticeable sounds should be investigated. -// - Too many sounds will put me in alert mode. -// 2. Alert of an enemy; I saw something or I heard something unusual -// - Go to the position where I last heard the sound. -// - Keep on searching until I give up. Then drop back to idle mode. -// 3. Found an enemy! Give chase! -// - Keep on chasing until enemy is killed or I give up. -// - Keep a path in memory as long as I still have him in my sights. -// - If I lose sight or I'm unable to traverse safely, find paths around obstacles and follow memorized path. -// - If I reach the end of my path and I still don't see him and I still want to pursue him, keep on going in the direction I'm going. - -bool IsTargetValidForSlender(SF2_BasePlayer target, bool includeEliminated = false) -{ - if (!target.IsValid) - { - return false; - } - - if (target.IsValid) - { - if (!target.IsAlive || - target.IsInDeathCam || - (!includeEliminated && target.IsEliminated) || - target.IsInGhostMode || - target.HasEscaped) - { - return false; - } - } - - return true; -} - -SF2_BasePlayer NPCChaserGetClosestPlayer(int slender) -{ - if (!g_Enabled) - { - return SF2_INVALID_PLAYER; - } - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return SF2_INVALID_PLAYER; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return SF2_INVALID_PLAYER; - } - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float position[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", position); - - SF2_BasePlayer closestTarget = SF2_INVALID_PLAYER; - float searchRadius = NPCGetSearchRadius(bossIndex, difficulty); - - for (int i = 1; i <= MaxClients; i++) - { - SF2_BasePlayer client = SF2_BasePlayer(i); - if (!client.IsValid || client.IsInGhostMode || client.IsProxy || !client.IsAlive || client.IsEliminated) - { - continue; - } - - float clientPos[3]; - client.GetAbsOrigin(clientPos); - - float distance = GetVectorSquareMagnitude(position, clientPos); - - if (distance < SquareFloat(searchRadius)) - { - closestTarget = client; - searchRadius = distance; - } - } - - if (closestTarget.IsValid) - { - return closestTarget; - } - - return SF2_INVALID_PLAYER; -} - -bool NPCGetWanderPosition(SF2NPC_Chaser boss) -{ - // We're allowed to move in wander mode. Get a new wandering position and create a path to follow. - // If the position can't be reached, then just get to the closest area that we can get. - int difficulty = GetLocalGlobalDifficulty(boss.Index); - float myPos[3]; - boss.GetAbsOrigin(myPos); - float wanderRangeMin = NPCChaserGetWanderRangeMin(boss.Index, difficulty); - float wanderRangeMax = NPCChaserGetWanderRangeMax(boss.Index, difficulty); - float wanderRange = GetRandomFloat(wanderRangeMin, wanderRangeMax); - CBaseCombatCharacter npc = CBaseCombatCharacter(boss.EntIndex); - CNavArea navArea = npc.GetLastKnownArea(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(navArea, wanderRange); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) - { - continue; - } - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - if (areaArray.Length <= 0) - { - return false; - } - float wanderPos[3]; - CNavArea wanderArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); - if (wanderArea == NULL_AREA) - { - return false; - } - wanderArea.GetCenter(wanderPos); - - boss.SetGoalPos(wanderPos); - - g_SlenderNextPathTime[boss.Index] = -1.0; // We're not going to wander around too much, so no need for a time constraint. - - delete collector; - delete areaArray; - return true; -} - -void NPCChaserUpdateBossAnimation(int bossIndex, int ent, int state, bool spawn = false) -{ - char animation[256]; - float playbackRate, tempFootsteps, cycle, footstepTime, tempDuration, duration; - bool animationFound = false; - int index; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - SF2BossProfileMasterAnimationsData animData; - GetBossProfileAnimationsData(profile, animData); - - bool clearLayers = GetChaserProfileClearLayersState(profile); - - switch (state) - { - case STATE_IDLE: - { - if (!spawn) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Idle], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Spawn], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - } - case STATE_WANDER: - { - if (NPCGetFlags(bossIndex) & SFF_WANDERMOVE) - { - if (!g_NpcIsCrawling[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Walk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_CrawlWalk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - } - case STATE_ALERT: - { - if (!g_NpcIsCrawling[bossIndex]) - { - if (NPCChaserCanUseAlertWalkingAnimation(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_WalkAlert], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Walk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - else - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_CrawlWalk], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - case STATE_CHASE: - { - if (!g_NpcUsesChaseInitialAnimation[bossIndex] && - !NPCIsRaging(bossIndex) && - !g_NpcUsesHealAnimation[bossIndex] && !g_NpcUseStartFleeAnimation[bossIndex] && - !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex] && !g_NpcIsCrawling[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Run], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - else if (g_NpcUsesChaseInitialAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (NPCIsRaging(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Rage], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (g_NpcIsCrawling[bossIndex] && !g_NpcUsesChaseInitialAnimation[bossIndex] && - !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex] && - !NPCIsRaging(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_CrawlRun], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index); - } - } - case STATE_ATTACK: - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index, NPCGetCurrentAttackIndex(bossIndex)); - } - case STATE_STUN: - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Stun], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, footstepTime, index); - } - case STATE_DEATHCAM: - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_DeathCam], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, tempFootsteps, index); - } - } - - if (g_NpcUseFireAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Shoot], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, footstepTime, index, NPCGetCurrentAttackIndex(bossIndex)); - } - - if (NPCIsRaging(bossIndex)) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Rage], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (g_NpcUseStartFleeAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_FleeInitial], difficulty, animation, sizeof(animation), playbackRate, duration, cycle, tempFootsteps, index); - } - else if (g_NpcUsesHealAnimation[bossIndex]) - { - animationFound = animData.GetAnimation(g_SlenderAnimationsList[SF2BossAnimation_Heal], difficulty, animation, sizeof(animation), playbackRate, tempDuration, cycle, tempFootsteps, index); - } - - if (playbackRate < -12.0) - { - playbackRate = -12.0; - } - if (playbackRate > 12.0) - { - playbackRate = 12.0; - } - - if (animationFound && animation[0] != '\0') - { - Action action = Plugin_Continue; - Call_StartForward(g_OnBossAnimationUpdateFwd); - Call_PushCell(bossIndex); - Call_Finish(action); - if (action != Plugin_Handled) - { - g_SlenderFootstepTime[bossIndex] = footstepTime; - g_SlenderAnimationDuration[bossIndex] = duration; - g_NpcCurrentAnimationSequencePlaybackRate[bossIndex] = playbackRate; - g_NpcCurrentAnimationSequence[bossIndex] = EntitySetAnimation(ent, animation, playbackRate, _, cycle); - g_NpcCurrentAnimationSequenceName[bossIndex] = animation; - EntitySetAnimation(ent, animation, playbackRate, _, cycle); //Fix an issue where an anim could start on the wrong frame. - if (g_NpcCurrentAnimationSequence[bossIndex] <= -1) - { - g_NpcCurrentAnimationSequence[bossIndex] = 0; - } - bool animationLoop = (state == STATE_IDLE || state == STATE_ALERT || - (state == STATE_CHASE && !NPCIsRaging(bossIndex) - && !g_NpcUseStartFleeAnimation[bossIndex] && !g_NpcUsesHealAnimation[bossIndex] && !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex]) - || state == STATE_WANDER); - if (state == STATE_ATTACK && NPCChaserGetAttackWhileRunningState(bossIndex, NPCGetCurrentAttackIndex(bossIndex), difficulty)) - { - animationLoop = !!GetEntProp(ent, Prop_Data, "m_bSequenceLoops"); - } - if (state == STATE_CHASE && g_NpcUsesChaseInitialAnimation[bossIndex]) - { - animationLoop = !!GetEntProp(ent, Prop_Data, "m_bSequenceLoops"); - } - SetEntProp(ent, Prop_Data, "m_bSequenceLoops", animationLoop); - } - } - if (state == STATE_ATTACK && NPCChaserGetAttackGestureState(bossIndex, NPCGetCurrentAttackIndex(bossIndex))) - { - float gestureCycle; - animationFound = animData.GetGesture(NPCGetCurrentAttackIndex(bossIndex), g_SlenderAnimationsList[SF2BossAnimation_Attack], difficulty, animation, sizeof(animation), playbackRate, cycle); - if (animationFound && animation[0] != '\0') - { - CBaseCombatCharacter overlay = CBaseCombatCharacter(ent); - int gesture = overlay.LookupSequence(animation); - if (gesture != -1) - { - float gestureDuration = overlay.SequenceDuration(gesture); - int layer = overlay.AddLayeredSequence(gesture, 1); - overlay.SetLayerDuration(layer, gestureDuration); - overlay.SetLayerPlaybackRate(layer, playbackRate); - overlay.SetLayerCycle(layer, gestureCycle); - overlay.SetLayerAutokill(layer, true); - } - } - } - if (!g_IsSlenderAttacking[bossIndex] && clearLayers) - { - CBaseNPC_RemoveAllLayers(ent); - } -} - -void SlenderAlertAllValidBosses(int bossIndex, int target = -1, int bestTarget = -1) -{ - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - float myPos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - int difficulty = GetLocalGlobalDifficulty(bossIndex); - if (NPCHasAttribute(bossIndex, SF2Attribute_AlertCopies)) - { - for (int bossCheck = 0; bossCheck < MAX_BOSSES; bossCheck++) - { - if (NPCGetUniqueID(bossCheck) != -1 && !g_SlenderSpawning[bossCheck] && - g_SlenderState[bossCheck] != STATE_ATTACK && g_SlenderState[bossCheck] != STATE_STUN && g_SlenderState[bossCheck] != STATE_CHASE && - (g_SlenderCopyMaster[bossCheck] == bossIndex || g_SlenderCopyMaster[bossIndex] == bossCheck || - (g_SlenderCopyMaster[bossIndex] == g_SlenderCopyMaster[bossCheck] && g_SlenderCopyMaster[bossIndex] != -1 && g_SlenderCopyMaster[bossCheck] != -1))) - { - int bossEnt = NPCGetEntIndex(bossCheck); - if (!bossEnt || bossEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - float copyPos[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", copyPos); - - float dist1 = GetVectorSquareMagnitude(copyPos, myPos); - - if ((dist1 <= SquareFloat(NPCGetSearchRadius(bossCheck, difficulty)) || dist1 <= SquareFloat(NPCGetHearingRadius(bossCheck, difficulty)))) - { - if (IsValidClient(target)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(target); - GetClientAbsOrigin(target, g_SlenderGoalPos[bossCheck]); - } - else if (IsValidClient(bestTarget)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(bestTarget); - GetClientAbsOrigin(bestTarget, g_SlenderGoalPos[bossCheck]); - } - g_SlenderState[bossCheck] = STATE_CHASE; - NPCChaserUpdateBossAnimation(bossCheck, bossEnt, STATE_CHASE); - g_NpcCopyAlerted[bossCheck] = true; - g_SlenderTimeUntilNoPersistence[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - g_SlenderTimeUntilAlert[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - SlenderPerformVoice(bossCheck, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(bossCheck) && !g_NpcUsesChaseInitialAnimation[bossCheck] && !SF_IsSlaughterRunMap()) - { - int copySlender = NPCGetEntIndex(bossCheck); - if (copySlender && copySlender != INVALID_ENT_REFERENCE && g_SlenderChaseInitialTimer[bossCheck] == null && (g_NpcChaseOnLookTarget[bossCheck] == null || g_NpcChaseOnLookTarget[bossCheck].Length <= 0)) - { - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(copySlender); - g_NpcUsesChaseInitialAnimation[bossCheck] = true; - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - NPCChaserUpdateBossAnimation(bossCheck, copySlender, g_SlenderState[bossCheck]); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossCheck, profile, sizeof(profile)); - g_SlenderChaseInitialTimer[bossCheck] = CreateTimer(g_SlenderAnimationDuration[bossCheck], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(copySlender), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } - if (NPCHasAttribute(bossIndex, SF2Attribute_AlertCompanions)) - { - for (int bossCheck = 0; bossCheck < MAX_BOSSES; bossCheck++) - { - if (NPCGetUniqueID(bossCheck) != -1 && !g_SlenderSpawning[bossCheck] && - g_SlenderState[bossCheck] != STATE_ATTACK && g_SlenderState[bossCheck] != STATE_STUN && g_SlenderState[bossCheck] != STATE_CHASE && - (g_SlenderCompanionMaster[bossCheck] == bossIndex || g_SlenderCompanionMaster[bossIndex] == bossCheck || - ((g_SlenderCompanionMaster[bossIndex] == g_SlenderCompanionMaster[bossCheck] && g_SlenderCompanionMaster[bossIndex] != -1 && g_SlenderCompanionMaster[bossCheck] != -1)))) - { - int bossEnt = NPCGetEntIndex(bossCheck); - if (!bossEnt || bossEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - float copyPos[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", copyPos); - - float dist1 = GetVectorSquareMagnitude(copyPos, myPos); - - if (dist1 <= SquareFloat(NPCGetSearchRadius(bossCheck, difficulty)) || dist1 <= SquareFloat(NPCGetHearingRadius(bossCheck, difficulty))) - { - if (IsValidClient(target)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(target); - GetClientAbsOrigin(target, g_SlenderGoalPos[bossCheck]); - } - else if (IsValidClient(bestTarget)) - { - g_SlenderTarget[bossCheck] = EntIndexToEntRef(bestTarget); - GetClientAbsOrigin(bestTarget, g_SlenderGoalPos[bossCheck]); - } - g_SlenderState[bossCheck] = STATE_CHASE; - NPCChaserUpdateBossAnimation(bossCheck, bossEnt, STATE_CHASE); - g_NpcCopyAlerted[bossCheck] = true; - g_SlenderTimeUntilNoPersistence[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - g_SlenderTimeUntilAlert[bossCheck] = GetGameTime() + NPCChaserGetChaseDuration(bossCheck, difficulty); - SlenderPerformVoice(bossCheck, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(bossCheck) && !g_NpcUsesChaseInitialAnimation[bossCheck] && !SF_IsSlaughterRunMap()) - { - int copySlender = NPCGetEntIndex(bossCheck); - if (copySlender && copySlender != INVALID_ENT_REFERENCE && g_SlenderChaseInitialTimer[bossCheck] == null && (g_NpcChaseOnLookTarget[bossCheck] == null || g_NpcChaseOnLookTarget[bossCheck].Length <= 0)) - { - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(copySlender); - g_NpcUsesChaseInitialAnimation[bossCheck] = true; - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - NPCChaserUpdateBossAnimation(bossCheck, copySlender, g_SlenderState[bossCheck]); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossCheck, profile, sizeof(profile)); - g_SlenderChaseInitialTimer[bossCheck] = CreateTimer(g_SlenderAnimationDuration[bossCheck], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(copySlender), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } -} - -Action Timer_SlenderChaseInitialTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderChaseInitialTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesChaseInitialAnimation[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesChaseInitialAnimation[bossIndex] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - g_SlenderChaseInitialTimer[bossIndex] = null; - return Plugin_Stop; -} - -Action Timer_SlenderRageOneTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderRage1Timer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesRageAnimation[bossIndex][0]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesRageAnimation[bossIndex][0] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - return Plugin_Stop; -} - -Action Timer_SlenderRageTwoTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderRage2Timer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesRageAnimation[bossIndex][1]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesRageAnimation[bossIndex][1] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - return Plugin_Stop; -} - -Action Timer_SlenderRageThreeTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderRage3Timer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUsesRageAnimation[bossIndex][2]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcUsesRageAnimation[bossIndex][2] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - if (state != STATE_ATTACK) - { - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - return Plugin_Stop; -} - -Action Timer_SlenderSpawnTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderSpawnTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_SlenderSpawning[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - SDKHook(slender, SDKHook_Think, SlenderChaseBossProcessMovement); - g_SlenderEntityThink[bossIndex] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, entref, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderSpawning[bossIndex] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - NPCChaserUpdateBossAnimation(bossIndex, slender, STATE_IDLE); - Call_StartForward(g_OnBossFinishSpawningFwd); - Call_PushCell(bossIndex); - Call_Finish(); - return Plugin_Stop; -} - -Action Timer_SlenderHealAnimationTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderHealTimer[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - - g_NpcSetHealDestination[bossIndex] = false; - g_NpcIsRunningToHeal[bossIndex] = false; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - g_NpcUseStartFleeAnimation[bossIndex] = false; - g_NpcUsesHealAnimation[bossIndex] = false; - g_NpcIsHealing[bossIndex] = false; - g_NpcFleeHealTimer[bossIndex] = GetGameTime(); - g_NpcHealCount[bossIndex] = 0; - g_NpcSelfHealStage[bossIndex]++; - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - g_SlenderHealDelayTimer[bossIndex] = null; - g_SlenderStartFleeTimer[bossIndex] = null; - g_SlenderHealTimer[bossIndex] = null; - g_BossPathFollower[bossIndex].Invalidate(); - return Plugin_Stop; -} - -Action Timer_SlenderHealDelayTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderHealDelayTimer[bossIndex]) - { - return Plugin_Stop; - } - - SlenderPerformVoice(bossIndex, _, SF2BossSound_SelfHeal); - - g_NpcHealCount[bossIndex] = 0; - - g_SlenderHealEventTimer[bossIndex] = CreateTimer(0.05, Timer_SlenderHealEventTimer, EntIndexToEntRef(slender), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Stop; -} - -Action Timer_SlenderHealEventTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderHealEventTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcIsHealing[bossIndex]) - { - return Plugin_Stop; - } - - if (g_SlenderState[bossIndex] == STATE_STUN) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - float checkPercentage; - switch (g_NpcSelfHealStage[bossIndex]) - { - case 0: - { - checkPercentage = NPCChaserGetSelfHealPercentageOne(bossIndex) * NPCChaserGetStunInitialHealth(bossIndex); - } - case 1: - { - checkPercentage = NPCChaserGetSelfHealPercentageTwo(bossIndex) * NPCChaserGetStunInitialHealth(bossIndex); - } - case 2, 3: - { - checkPercentage = NPCChaserGetSelfHealPercentageThree(bossIndex) * NPCChaserGetStunInitialHealth(bossIndex); - } - } - float regenHealth = checkPercentage / 10.0; - - if (NPCChaserGetStunHealth(bossIndex) + regenHealth < NPCChaserGetStunInitialHealth(bossIndex)) - { - NPCChaserAddStunHealth(bossIndex, regenHealth); - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - g_NpcHealCount[bossIndex]++; - } - else - { - float addRemainHealth = NPCChaserGetStunInitialHealth(bossIndex) - NPCChaserGetStunHealth(bossIndex); - NPCChaserAddStunHealth(bossIndex, addRemainHealth); - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - g_NpcIsHealing[bossIndex] = false; - g_NpcHealCount[bossIndex] = 0; - return Plugin_Stop; - } - - if (g_NpcHealCount[bossIndex] >= 10) - { - g_NpcIsHealing[bossIndex] = false; - g_NpcHealCount[bossIndex] = 0; - return Plugin_Stop; - } - - return Plugin_Continue; -} - -Action Timer_SlenderFleeAnimationTimer(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderStartFleeTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (!g_NpcUseStartFleeAnimation[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int oldState = g_SlenderState[bossIndex]; - int state = oldState; - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float originalSpeed; - - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !g_Renevant90sEffect) - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - } - else - { - originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - if ((originalSpeed < 520.0)) - { - originalSpeed = 520.0; - } - } - float speed = originalSpeed; - g_SlenderCalculatedSpeed[bossIndex] = speed; - g_SlenderTarget[bossIndex] = INVALID_ENT_REFERENCE; - - g_NpcUseStartFleeAnimation[bossIndex] = false; - g_NpcIsRunningToHeal[bossIndex] = true; - g_LastStuckTime[bossIndex] = 0.0; - loco.ClearStuckStatus(); - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - - return Plugin_Stop; -} - -void SlenderDoDamageEffects(int bossIndex, int attackIndex, SF2_BasePlayer client) -{ - if (!client.IsValid) - { - return; - } - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - float flMyEyePos[3]; - NPCGetEyePosition(bossIndex, flMyEyePos); - - if (NPCChaserUseRandomAdvancedDamageEffects(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserRandomEffectIndexes(bossIndex); - GetChaserProfileRandomAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserRandomEffectDuration(bossIndex, difficulty) > 0.0) - { - int randomEffect = GetRandomInt(0, 6); - switch (randomEffect) - { - case 0: - { - client.ChangeCondition(TFCond_Jarated, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 1: - { - client.ChangeCondition(TFCond_Milked, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 2: - { - client.ChangeCondition(TFCond_Gas, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 3: - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 4: - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - } - case 5: - { - client.Ignite(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 6: - { - switch (NPCChaserRandomEffectStunType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserRandomEffectDuration(bossIndex, difficulty) > 0.0) - { - int randomEffect = GetRandomInt(0, 6); - switch (randomEffect) - { - case 0: - { - client.ChangeCondition(TFCond_Jarated, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 1: - { - client.ChangeCondition(TFCond_Milked, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 2: - { - client.ChangeCondition(TFCond_Gas, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 3: - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 4: - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - } - case 5: - { - client.Ignite(true, _, NPCChaserRandomEffectDuration(bossIndex, difficulty)); - } - case 6: - { - switch (NPCChaserRandomEffectStunType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserRandomEffectDuration(bossIndex, difficulty), NPCChaserRandomEffectSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - } - } - } - } - } - } - if (NPCChaserJaratePlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetJarateAttackIndexes(bossIndex); - GetChaserProfileJarateAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetJarateDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileJarateParticle(profile, jaratePlayerParticle, sizeof(jaratePlayerParticle)); - if (jaratePlayerParticle[0] == '\0') - { - jaratePlayerParticle = JARATE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetJarateBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, jaratePlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, jaratePlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, jaratePlayerParticle, 35.0); - } - if (g_SlenderJarateHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderJarateHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Jarated, _, NPCChaserGetJarateDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetJarateDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileJarateParticle(profile, jaratePlayerParticle, sizeof(jaratePlayerParticle)); - if (jaratePlayerParticle[0] == '\0') - { - jaratePlayerParticle = JARATE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetJarateBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, jaratePlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, jaratePlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, jaratePlayerParticle, 35.0); - } - if (g_SlenderJarateHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderJarateHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Jarated, _, NPCChaserGetJarateDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserMilkPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetMilkAttackIndexes(bossIndex); - GetChaserProfileMilkAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetMilkDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileMilkParticle(profile, milkPlayerParticle, sizeof(milkPlayerParticle)); - if (milkPlayerParticle[0] == '\0') - { - milkPlayerParticle = MILK_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetMilkBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, milkPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, milkPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, milkPlayerParticle, 55.0); - } - if (g_SlenderMilkHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderMilkHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Milked, _, NPCChaserGetMilkDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetMilkDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileMilkParticle(profile, milkPlayerParticle, sizeof(milkPlayerParticle)); - if (milkPlayerParticle[0] == '\0') - { - milkPlayerParticle = MILK_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetMilkBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, milkPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, milkPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, milkPlayerParticle, 55.0); - } - if (g_SlenderMilkHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderMilkHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Milked, _, NPCChaserGetMilkDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserGasPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetGasAttackIndexes(bossIndex); - GetChaserProfileGasAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetGasDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileGasParticle(profile, gasPlayerParticle, sizeof(gasPlayerParticle)); - if (gasPlayerParticle[0] == '\0') - { - gasPlayerParticle = GAS_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetGasBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, gasPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, gasPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, gasPlayerParticle, 55.0); - } - if (g_SlenderGasHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderGasHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Gas, _, NPCChaserGetGasDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetGasDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileGasParticle(profile, gasPlayerParticle, sizeof(gasPlayerParticle)); - if (gasPlayerParticle[0] == '\0') - { - gasPlayerParticle = GAS_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetGasBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, gasPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, gasPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, gasPlayerParticle, 55.0); - } - if (g_SlenderGasHitSound[bossIndex][0] != '\0') //No sound, what? - { - EmitSoundToAll(g_SlenderGasHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - client.ChangeCondition(TFCond_Gas, _, NPCChaserGetGasDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserMarkPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetMarkAttackIndexes(bossIndex); - GetChaserProfileMarkAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetMarkDuration(bossIndex, difficulty) > 0.0) - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserGetMarkDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetMarkDuration(bossIndex, difficulty)) - { - client.ChangeCondition(TFCond_MarkedForDeath, _, NPCChaserGetMarkDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserSilentMarkPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetSilentMarkAttackIndexes(bossIndex); - GetChaserProfileSilentMarkAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); +void SetClientForceChaseState(SF2NPC_BaseNPC controller, SF2_BasePlayer client, bool value) +{ + g_ClientShouldBeForceChased[controller.Index][client.index] = value; +} - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetSilentMarkDuration(bossIndex, difficulty) > 0.0) - { - client.ChangeCondition(TFCond_MarkedForDeathSilent, _, NPCChaserGetSilentMarkDuration(bossIndex, difficulty)); - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetSilentMarkDuration(bossIndex, difficulty)) - { - client.ChangeCondition(TFCond_MarkedForDeathSilent, _, NPCChaserGetSilentMarkDuration(bossIndex, difficulty)); - } - } - } - } - if (NPCChaserIgnitePlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetIgniteAttackIndexes(bossIndex); - GetChaserProfileIgniteAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); +ArrayList NPCChaserGetAutoChaseTargets(int npcIndex) +{ + return g_NpcChaseOnLookTarget[npcIndex]; +} - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetIgniteDuration(bossIndex, difficulty)) - { - g_PlayerIgniteDurationEffect[client.index] = NPCChaserGetIgniteDuration(bossIndex, difficulty); - if (NPCChaserGetIgniteDelay(bossIndex, difficulty) > 0.0 && g_PlayerIgniteTimer[client.index] == null) - { - g_PlayerIgniteTimer[client.index] = CreateTimer(NPCChaserGetIgniteDelay(bossIndex, difficulty), Timer_SlenderChaseBossAttackIgniteHit, EntIndexToEntRef(client.index), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - client.Ignite(true, _, g_PlayerIgniteDurationEffect[client.index]); - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetIgniteDuration(bossIndex, difficulty)) - { - g_PlayerIgniteDurationEffect[client.index] = NPCChaserGetIgniteDuration(bossIndex, difficulty); - if (NPCChaserGetIgniteDelay(bossIndex, difficulty) > 0.0 && g_PlayerIgniteTimer[client.index] == null) - { - g_PlayerIgniteTimer[client.index] = CreateTimer(NPCChaserGetIgniteDelay(bossIndex, difficulty), Timer_SlenderChaseBossAttackIgniteHit, EntIndexToEntRef(client.index), TIMER_FLAG_NO_MAPCHANGE); - } - else - { - client.Ignite(true, _, g_PlayerIgniteDurationEffect[client.index]); - } - } - } - } - } - if (NPCChaserStunPlayerOnHit(bossIndex)) +void ResetClientNPCStates(SF2_BasePlayer client) +{ + for (int i = 0; i < MAX_BOSSES; i++) { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetStunAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileStunAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetStunAttackDuration(bossIndex, difficulty)) - { - GetChaserProfileStunParticle(profile, stunPlayerParticle, sizeof(stunPlayerParticle)); - if (stunPlayerParticle[0] == '\0') - { - stunPlayerParticle = STUN_PARTICLE; - } - if (NPCChaserGetStunAttackType(bossIndex) != 4) - { - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetStunBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, stunPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, stunPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, stunPlayerParticle, 55.0); - } - } - if (g_SlenderStunHitSound[bossIndex][0] != '\0' && NPCChaserGetStunAttackType(bossIndex) != 4) //No sound, what? - { - EmitSoundToAll(g_SlenderStunHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetStunAttackDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileStunParticle(profile, stunPlayerParticle, sizeof(stunPlayerParticle)); - if (stunPlayerParticle[0] == '\0') - { - stunPlayerParticle = STUN_PARTICLE; - } - if (NPCChaserGetStunAttackType(bossIndex) != 4) - { - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (NPCChaserGetStunBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, stunPlayerParticle, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, stunPlayerParticle, 35.0, client); - } - } - else - { - SlenderCreateParticle(bossIndex, stunPlayerParticle, 55.0); - } - } - if (g_SlenderStunHitSound[bossIndex][0] != '\0' && NPCChaserGetStunAttackType(bossIndex) != 4) //No sound, what? - { - EmitSoundToAll(g_SlenderStunHitSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - case 4: - { - client.Stun(NPCChaserGetStunAttackDuration(bossIndex, difficulty), NPCChaserGetStunAttackSlowdown(bossIndex, difficulty), TF_STUNFLAGS_GHOSTSCARE); - } - } - } - } - } + g_ClientShouldBeForceChased[i][client.index] = false; } - if (NPCChaserBleedPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetBleedAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileBleedAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); +} - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetBleedDuration(bossIndex, difficulty)) - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserGetBleedDuration(bossIndex, difficulty)); - break; - } - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetBleedDuration(bossIndex, difficulty) > 0.0) - { - if (!client.InCondition(TFCond_Bleeding)) - { - client.Bleed(true, _, NPCChaserGetBleedDuration(bossIndex, difficulty)); - } - } - } - } - } - if (NPCChaserElectricPlayerOnHit(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetElectricAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileEletricAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); +void NPCChaserOnSelectProfile(int npcIndex) +{ + SF2NPC_Chaser chaser = SF2NPC_Chaser(npcIndex); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(npcIndex, profile, sizeof(profile)); - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1 && NPCChaserGetElectricDuration(bossIndex, difficulty)) - { - GetChaserProfileElectricRedParticle(profile, electricPlayerParticleRed, sizeof(electricPlayerParticleRed)); - GetChaserProfileElectricBlueParticle(profile, electricPlayerParticleBlue, sizeof(electricPlayerParticleBlue)); - if (electricPlayerParticleRed[0] == '\0') - { - electricPlayerParticleRed = ELECTRIC_RED_PARTICLE; - } - if (electricPlayerParticleBlue[0] == '\0') - { - electricPlayerParticleBlue = ELECTRIC_BLUE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (client.Team == TFTeam_Red) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleRed, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleRed, 35.0, client); - } - } - else if (client.Team == TFTeam_Blue) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - } - } - else - { - if (client.Team == TFTeam_Red) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleRed, 55.0); - } - else if (client.Team == TFTeam_Blue) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleBlue, 55.0); - } - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3, 4: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex && NPCChaserGetElectricDuration(bossIndex, difficulty) > 0.0) - { - GetChaserProfileElectricRedParticle(profile, electricPlayerParticleRed, sizeof(electricPlayerParticleRed)); - GetChaserProfileElectricBlueParticle(profile, electricPlayerParticleBlue, sizeof(electricPlayerParticleBlue)); - if (electricPlayerParticleRed[0] == '\0') - { - electricPlayerParticleRed = ELECTRIC_RED_PARTICLE; - } - if (electricPlayerParticleBlue[0] == '\0') - { - electricPlayerParticleBlue = ELECTRIC_BLUE_PARTICLE; - } - if (NPCChaserAttachDamageParticle(bossIndex)) - { - if (client.Team == TFTeam_Red) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleRed, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleRed, 35.0, client); - } - } - else if (client.Team == TFTeam_Blue) - { - if (NPCChaserGetElectricBeamParticle(bossIndex)) - { - SlenderCreateParticleBeamClient(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - else - { - SlenderCreateParticleAttach(bossIndex, electricPlayerParticleBlue, 35.0, client); - } - } - } - else - { - if (client.Team == TFTeam_Red) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleRed, 55.0); - } - else if (client.Team == TFTeam_Blue) - { - SlenderCreateParticle(bossIndex, electricPlayerParticleBlue, 55.0); - } - } - switch (NPCChaserGetStunAttackType(bossIndex)) - { - case 1: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN); - } - case 2: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAGS_LOSERSTATE); - } - case 3, 4: - { - client.Stun(NPCChaserGetElectricDuration(bossIndex, difficulty), NPCChaserGetElectricSlowdown(bossIndex, difficulty), TF_STUNFLAG_BONKSTUCK|TF_STUNFLAG_NOSOUNDOREFFECT); - } - } - } - } - } + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + g_NpcDeathInitialHealth[npcIndex][difficulty] = GetChaserProfileDeathHealth(profile, difficulty); + g_NpcDeathHealth[npcIndex][difficulty] = g_NpcDeathInitialHealth[npcIndex][difficulty]; } - if (NPCChaserSmitePlayerOnHit(bossIndex)) + + if (g_NpcChaseOnLookTarget[npcIndex] == null) { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetSmiteAttackIndexes(bossIndex); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex + 1); - GetChaserProfileSmiteAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); + g_NpcChaseOnLookTarget[npcIndex] = new ArrayList(); + } - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int i = 0; i < count && i < NPCChaserGetAttackCount(bossIndex); i++) - { - int forIndex = StringToInt(allowedIndexesList[i]); - if (forIndex == attackIndex + 1) - { - PerformSmiteBoss(bossIndex, client, EntIndexToEntRef(slender)); - client.TakeDamage(_, slender, slender, NPCChaserGetSmiteDamage(bossIndex), NPCChaserGetSmiteDamageType(bossIndex), _, view_as( { 0.0, 0.0, 0.0 } ), flMyEyePos); - if (NPCChaserSmitePlayerMessage(bossIndex)) - { - char player[32]; - FormatEx(player, sizeof(player), "%N", client.index); + chaser.SetAddSpeed(-chaser.GetAddSpeed()); + chaser.SetAddAcceleration(-chaser.GetAddAcceleration()); + chaser.StunHealthAdd = -chaser.StunHealthAdd; - char name[SF2_MAX_NAME_LENGTH]; - NPCGetBossName(bossIndex, name, sizeof(name)); - if (name[0] == '\0') - { - strcopy(name, sizeof(name), profile); - } - if (client.Team == TFTeam_Red) - { - CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Smote target", name, player); - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex + 1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex) - { - PerformSmiteBoss(bossIndex, client, EntIndexToEntRef(slender)); - client.TakeDamage(_, slender, slender, NPCChaserGetSmiteDamage(bossIndex), NPCChaserGetSmiteDamageType(bossIndex), _, view_as( { 0.0, 0.0, 0.0 } ), flMyEyePos); - if (NPCChaserSmitePlayerMessage(bossIndex)) - { - char player[32]; - FormatEx(player, sizeof(player), "%N", client.index); + g_NpcHasIsBoxingBoss[npcIndex] = GetChaserProfileBoxingState(profile); - char name[SF2_MAX_NAME_LENGTH]; - NPCGetBossName(bossIndex, name, sizeof(name)); - if (name[0] == '\0') - { - strcopy(name, sizeof(name), profile); - } - if (client.Team == TFTeam_Red) - { - CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Smote target", name, player); - } - } - } - } - } + g_NpcStealingLife[npcIndex] = false; + g_NpcLifeStealTimer[npcIndex] = null; + + g_NpcBoxingCurrentDifficulty[npcIndex] = 1; + g_NpcBoxingRagePhase[npcIndex] = 0; + + g_NpcCopyAlerted[npcIndex] = false; + + if (chaser.HasAttribute(SF2Attribute_ReducedSpeedOnLook) || chaser.HasAttribute(SF2Attribute_ReducedWalkSpeedOnLook) || chaser.HasAttribute(SF2Attribute_ReducedAccelerationOnLook)) + { + chaser.SetAffectedBySight(true); } } -void NPCClearAttackStats(int bossIndex, bool killTimers = false) +void NPCChaserOnRemoveProfile(int npcIndex) { - if (!killTimers) - { - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - } - else - { - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - } - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; + NPCChaserResetValues(npcIndex); } -static bool NPCPropPhysicsAttack(int bossIndex, int prop) +/** + * Resets all global variables on a specified NPC. Usually this should be done last upon removing a boss from the game. + */ +static void NPCChaserResetValues(int npcIndex) { - char profile[SF2_MAX_PROFILE_NAME_LENGTH], model[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - ArrayList props = GetChaserProfileAttackPropModels(profile); - if (props == null) - { - return false; - } - if (!IsValidEntity(prop)) + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - return false; + g_NpcAlertGracetime[npcIndex][difficulty] = 0.0; + g_NpcAlertDuration[npcIndex][difficulty] = 0.0; + g_NpcChaseDuration[npcIndex][difficulty] = 0.0; + + g_NpcSearchWanderRangeMin[npcIndex][difficulty] = 0.0; + g_NpcSearchWanderRangeMax[npcIndex][difficulty] = 0.0; + + g_NpcDeathInitialHealth[npcIndex][difficulty] = 0.0; + NPCChaserSetDeathHealth(npcIndex, difficulty, 0.0); } - GetEntPropString(prop, Prop_Data, "m_ModelName", model, sizeof(model)); - int arrayIndex = props.FindString(model); - if (arrayIndex == -1) + + g_NpcHasIsBoxingBoss[npcIndex] = false; + + g_NpcStunAddHealth[npcIndex] = 0.0; + + g_NpcStealingLife[npcIndex] = false; + g_NpcLifeStealTimer[npcIndex] = null; + if (g_NpcChaseOnLookTarget[npcIndex] != null) { - return false; + delete g_NpcChaseOnLookTarget[npcIndex]; + g_NpcChaseOnLookTarget[npcIndex] = null; } - return true; + + NPCSetAddSpeed(npcIndex, -NPCGetAddSpeed(npcIndex)); + NPCSetAddAcceleration(npcIndex, -NPCGetAddAcceleration(npcIndex)); + NPCChaserSetAddStunHealth(npcIndex, -NPCChaserGetAddStunHealth(npcIndex)); + + g_NpcBoxingCurrentDifficulty[npcIndex] = 0; + g_NpcBoxingRagePhase[npcIndex] = 0; + + g_NpcCopyAlerted[npcIndex] = false; } -void NPC_DropKey(int bossIndex) +SF2_ChaserEntity Spawn_Chaser(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) { - char buffer[PLATFORM_MAX_PATH], profile[SF2_MAX_PROFILE_NAME_LENGTH]; + /*char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); - GetChaserProfileKeyTrigger(profile, buffer, sizeof(buffer)); - if (buffer[0] != '\0') - { - float myPos[3], vel[3]; - int boss = NPCGetEntIndex(bossIndex); - GetEntPropVector(boss, Prop_Data, "m_vecAbsOrigin", myPos); - Format(buffer, PLATFORM_MAX_PATH, "sf2_key_%s", buffer); - int touchBox = CreateEntityByName("tf_halloween_pickup"); - DispatchKeyValue(touchBox, "targetname", buffer); - // New key model - GetChaserProfileKeyModel(profile, keyModel, sizeof(keyModel)); - DispatchKeyValue(touchBox, "powerup_model", keyModel); - DispatchKeyValue(touchBox, "modelscale", "2.0"); - DispatchKeyValue(touchBox, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); - DispatchKeyValue(touchBox, "pickup_particle", "utaunt_firework_teamcolor_red"); - DispatchKeyValue(touchBox, "TeamNum", "0"); - TeleportEntity(touchBox, myPos, NULL_VECTOR, NULL_VECTOR); - if (keyModel[0] == '\0') - { - SetEntityModel(touchBox, SF_KEYMODEL); - } - else - { - SetEntityModel(touchBox, keyModel); - } - SetEntProp(touchBox, Prop_Data, "m_iEFlags", 12845056); - DispatchSpawn(touchBox); - ActivateEntity(touchBox); - if (keyModel[0] == '\0') - { - SetEntityModel(touchBox, SF_KEYMODEL); - } - else - { - SetEntityModel(touchBox, keyModel); - } + g_LastStuckTime[bossIndex] = 0.0; + g_SlenderOldState[bossIndex] = STATE_IDLE; + g_NpcCopyAlerted[bossIndex] = false; + g_NpcNextDecloakTime[bossIndex] = -1.0; + g_NpcIsCrawling[bossIndex] = false; + g_NpcChangeToCrawl[bossIndex] = false; + g_SlenderSoundPositionSetCooldown[bossIndex] = 0.0; - int key = CreateEntityByName("tf_halloween_pickup"); - DispatchKeyValue(key, "targetname", buffer); - DispatchKeyValue(key, "powerup_model", PAGE_MODEL); - DispatchKeyValue(key, "modelscale", "2.0"); - DispatchKeyValue(key, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); - DispatchKeyValue(key, "pickup_particle", "utaunt_firework_teamcolor_red"); - DispatchKeyValue(key, "TeamNum", "0"); - TeleportEntity(key, myPos, NULL_VECTOR, NULL_VECTOR); - SetEntityModel(key, PAGE_MODEL); - SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); - DispatchSpawn(key); - ActivateEntity(key); + NPCSetAddSpeed(bossIndex, -NPCGetAddSpeed(bossIndex)); + NPCSetAddAcceleration(bossIndex, -NPCGetAddAcceleration(bossIndex)); + NPCChaserSetAddStunHealth(bossIndex, -NPCChaserGetAddStunHealth(bossIndex)); - SetEntityRenderMode(key, RENDER_TRANSCOLOR); - SetEntityRenderColor(key, 0, 0, 0, 1); + //g_NpcInstantKillThink[bossIndex] = CreateTimer(0.0, Timer_InstantKillThink, bossIndex, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); - int glow = CreateEntityByName("tf_taunt_prop"); - DispatchKeyValue(glow, "targetname", buffer); - if (keyModel[0] == '\0') - { - DispatchKeyValue(glow, "powerup_model", SF_KEYMODEL); - } - else - { - DispatchKeyValue(glow, "powerup_model", keyModel); - } - TeleportEntity(glow, myPos, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(glow); - ActivateEntity(glow); - if (keyModel[0] == '\0') - { - SetEntityModel(glow, SF_KEYMODEL); - } - else + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + g_SlenderNextWanderPos[bossIndex][difficulty] = GetGameTime() + + GetRandomFloat(GetChaserProfileWanderEnterTimeMin(profile, difficulty), GetChaserProfileWanderEnterTimeMax(profile, difficulty)); + }*/ + + return SF2_ChaserEntity.Create(controller, pos, ang); +} + +void Despawn_Chaser(int bossIndex) +{ + g_NpcInstantKillThink[bossIndex] = null; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) { - SetEntityModel(glow, keyModel); + continue; } - SetEntProp(glow, Prop_Send, "m_bGlowEnabled", 1); - SetEntPropFloat(glow, Prop_Send, "m_flModelScale", 2.0); - SetEntityRenderMode(glow, RENDER_TRANSCOLOR); - SetEntityRenderColor(glow, 0, 0, 0, 1); - - SetVariantString("!activator"); - AcceptEntityInput(touchBox, "SetParent", key); + SF2_BasePlayer player = SF2_BasePlayer(i); + player.SetForceChaseState(SF2NPC_BaseNPC(bossIndex), false); + } +} - SetVariantString("!activator"); - AcceptEntityInput(glow, "SetParent", key); +// So this is how the thought process of the bosses should go. +// 1. Search for enemy; either by sight or by sound. +// - Any noticeable sounds should be investigated. +// - Too many sounds will put me in alert mode. +// 2. Alert of an enemy; I saw something or I heard something unusual +// - Go to the position where I last heard the sound. +// - Keep on searching until I give up. Then drop back to idle mode. +// 3. Found an enemy! Give chase! +// - Keep on chasing until enemy is killed or I give up. +// - Keep a path in memory as long as I still have him in my sights. +// - If I lose sight or I'm unable to traverse safely, find paths around obstacles and follow memorized path. +// - If I reach the end of my path and I still don't see him and I still want to pursue him, keep on going in the direction I'm going. - SetEntityModel(key, PAGE_MODEL); - SetEntityMoveType(key, MOVETYPE_FLYGRAVITY); +bool IsTargetValidForSlender(SF2_BasePlayer target, bool includeEliminated = false) +{ + if (!target.IsValid) + { + return false; + } - HookSingleEntityOutput(touchBox, "OnRedPickup", KeyTrigger); + if (!target.IsAlive || + target.IsInDeathCam || + (!includeEliminated && target.IsEliminated) || + target.IsInGhostMode || + target.HasEscaped) + { + return false; + } - vel[0] = GetRandomFloat(-300.0, 300.0); - vel[1] = GetRandomFloat(-300.0, 300.0); - vel[2] = GetRandomFloat(700.0, 900.0); + return true; +} - SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); +bool IsPvETargetValid(SF2_BasePlayer target) +{ + if (!target.IsValid) + { + return false; + } - TeleportEntity(key, myPos, NULL_VECTOR, vel); - SetEntPropFloat(key, Prop_Send, "m_flModelScale", 2.0); - SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); - SetEntProp(key, Prop_Data, "m_MoveCollide", 1); + if (!target.IsAlive || !target.IsInPvE || target.IsInGhostMode) + { + return false; + } - SDKHook(key, SDKHook_SetTransmit, Hook_KeySetTransmit); - SDKHook(glow, SDKHook_SetTransmit, Hook_KeySetTransmit); - SDKHook(touchBox, SDKHook_SetTransmit, Hook_KeySetTransmit); + return true; +} - //The key can be stuck somewhere to prevent that, make an auto collect. - float timeLeft = float(g_RoundTime); - if (timeLeft > 60.0) - { - timeLeft = 30.0; - } - else - { - timeLeft = timeLeft - 20.0; - } - CreateTimer(timeLeft, CollectKey, EntIndexToEntRef(touchBox), TIMER_FLAG_NO_MAPCHANGE); +void NPC_DropKey(int bossIndex, const char[] model, const char[] trigger) +{ + char buffer[PLATFORM_MAX_PATH]; + float myPos[3], vel[3]; + int boss = NPCGetEntIndex(bossIndex); + GetEntPropVector(boss, Prop_Data, "m_vecAbsOrigin", myPos); + FormatEx(buffer, sizeof(buffer), "sf2_key_%s", trigger); + + int touchBox = CreateEntityByName("tf_halloween_pickup"); + DispatchKeyValue(touchBox, "targetname", buffer); + DispatchKeyValue(touchBox, "powerup_model", model); + DispatchKeyValue(touchBox, "modelscale", "2.0"); + DispatchKeyValue(touchBox, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); + DispatchKeyValue(touchBox, "pickup_particle", "utaunt_firework_teamcolor_red"); + DispatchKeyValue(touchBox, "TeamNum", "0"); + TeleportEntity(touchBox, myPos, NULL_VECTOR, NULL_VECTOR); + SetEntityModel(touchBox, model); + SetEntProp(touchBox, Prop_Data, "m_iEFlags", 12845056); + DispatchSpawn(touchBox); + ActivateEntity(touchBox); + SetEntityModel(touchBox, model); + + int key = CreateEntityByName("tf_halloween_pickup"); + DispatchKeyValue(key, "targetname", buffer); + DispatchKeyValue(key, "powerup_model", PAGE_MODEL); + DispatchKeyValue(key, "modelscale", "2.0"); + DispatchKeyValue(key, "pickup_sound", "ui/itemcrate_smash_ultrarare_short.wav"); + DispatchKeyValue(key, "pickup_particle", "utaunt_firework_teamcolor_red"); + DispatchKeyValue(key, "TeamNum", "0"); + TeleportEntity(key, myPos, NULL_VECTOR, NULL_VECTOR); + SetEntityModel(key, PAGE_MODEL); + SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); + DispatchSpawn(key); + ActivateEntity(key); + + SetEntityRenderMode(key, RENDER_TRANSCOLOR); + SetEntityRenderColor(key, 0, 0, 0, 1); + + int glow = CreateEntityByName("tf_taunt_prop"); + DispatchKeyValue(glow, "targetname", buffer); + DispatchKeyValue(glow, "powerup_model", model); + TeleportEntity(glow, myPos, NULL_VECTOR, NULL_VECTOR); + DispatchSpawn(glow); + ActivateEntity(glow); + SetEntityModel(glow, model); + + SetEntProp(glow, Prop_Send, "m_bGlowEnabled", 1); + SetEntPropFloat(glow, Prop_Send, "m_flModelScale", 2.0); + SetEntityRenderMode(glow, RENDER_TRANSCOLOR); + SetEntityRenderColor(glow, 0, 0, 0, 1); + + SetVariantString("!activator"); + AcceptEntityInput(touchBox, "SetParent", key); + + SetVariantString("!activator"); + AcceptEntityInput(glow, "SetParent", key); + + SetEntityModel(key, PAGE_MODEL); + SetEntityMoveType(key, MOVETYPE_FLYGRAVITY); + + HookSingleEntityOutput(touchBox, "OnRedPickup", KeyTrigger); + + vel[0] = GetRandomFloat(-300.0, 300.0); + vel[1] = GetRandomFloat(-300.0, 300.0); + vel[2] = GetRandomFloat(700.0, 900.0); + + SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); + + TeleportEntity(key, myPos, NULL_VECTOR, vel); + SetEntPropFloat(key, Prop_Send, "m_flModelScale", 2.0); + SetEntProp(key, Prop_Data, "m_iEFlags", 12845056); + SetEntProp(key, Prop_Data, "m_MoveCollide", 1); + + SDKHook(key, SDKHook_SetTransmit, Hook_KeySetTransmit); + SDKHook(glow, SDKHook_SetTransmit, Hook_KeySetTransmit); + SDKHook(touchBox, SDKHook_SetTransmit, Hook_KeySetTransmit); + + //The key can be stuck somewhere to prevent that, make an auto collect. + float timeLeft = float(g_RoundTime); + if (timeLeft > 60.0) + { + timeLeft = 30.0; + } + else + { + timeLeft = timeLeft - 20.0; } + CreateTimer(timeLeft, CollectKey, EntIndexToEntRef(touchBox), TIMER_FLAG_NO_MAPCHANGE); } -void KeyTrigger(const char[] output, int caller, int activator, float delay) +static void KeyTrigger(const char[] output, int caller, int activator, float delay) { TriggerKey(caller); } -Action Hook_KeySetTransmit(int entity, int other) +static Action Hook_KeySetTransmit(int entity, int other) { if (!IsValidClient(other)) { @@ -4393,7 +449,7 @@ Action Hook_KeySetTransmit(int entity, int other) return Plugin_Handled; } -Action CollectKey(Handle timer, any entref) +static Action CollectKey(Handle timer, any entref) { int ent = EntRefToEntIndex(entref); if (ent == INVALID_ENT_REFERENCE) @@ -4411,7 +467,7 @@ Action CollectKey(Handle timer, any entref) return Plugin_Stop; } -void TriggerKey(int caller) +static void TriggerKey(int caller) { char targetName[PLATFORM_MAX_PATH]; GetEntPropString(caller, Prop_Data, "m_iName", targetName, sizeof(targetName)); @@ -4468,7 +524,7 @@ void TriggerKey(int caller) EmitSoundToAll("ui/itemcrate_smash_ultrarare_short.wav", caller, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); } -bool NPC_CanAttackProps(int bossIndex, float flAttackRange, float flAttackFOV) +/*bool NPC_CanAttackProps(int bossIndex, float flAttackRange, float flAttackFOV) { int prop = -1; while ((prop = FindEntityByClassname(prop, "prop_physics")) > MaxClients) @@ -4501,4 +557,43 @@ bool NPC_CanAttackProps(int bossIndex, float flAttackRange, float flAttackFOV) } } return false; -} \ No newline at end of file +}*/ + +void NPCChaser_InitializeAPI() +{ + CreateNative("SF2_GetChaserProfileFromBossIndex", Native_GetProfileData); + CreateNative("SF2_GetChaserProfileFromName", Native_GetProfileDataEx); + + g_OnChaserBossStartAttackFwd = new GlobalForward("SF2_OnChaserBossStartAttack", ET_Ignore, Param_Cell, Param_String); + g_OnChaserBossEndAttackFwd = new GlobalForward("SF2_OnChaserBossEndAttack", ET_Ignore, Param_Cell, Param_String); + + SF2_ChaserEntity.SetupAPI(); +} + +static any Native_GetProfileData(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} + +static any Native_GetProfileDataEx(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetNativeString(1, profile, sizeof(profile)); + SF2ChaserBossProfileData data; + if (!g_ChaserBossProfileData.GetArray(profile, data, sizeof(data))) + { + return false; + } + + SetNativeArray(2, data, sizeof(data)); + return true; +} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp deleted file mode 100644 index e2f03c2e..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_attacks.sp +++ /dev/null @@ -1,1815 +0,0 @@ -#if defined _sf2_npc_chaser_attacks_included - #endinput -#endif - -#define _sf2_npc_chaser_attacks_included - -#pragma semicolon 1 - -void PerformSmiteBoss(int client, SF2_BasePlayer target, any entref) -{ - if (!g_Enabled) - { - return; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - // define where the lightning strike ends - float clientPos[3]; - target.GetAbsOrigin(clientPos); - clientPos[2] -= 26; // increase y-axis by 26 to strike at player's chest instead of the ground - - // get random numbers for the x and y starting positions - int randomX = GetRandomInt(-500, 500); - int randomY = GetRandomInt(-500, 500); - - // define where the lightning strike starts - float startPos[3]; - startPos[0] = clientPos[0] + randomX; - startPos[1] = clientPos[1] + randomY; - startPos[2] = clientPos[2] + 800; - - // define the color of the strike - int color[4]; - color[0] = NPCChaserGetSmiteColorR(bossIndex); - color[1] = NPCChaserGetSmiteColorG(bossIndex); - color[2] = NPCChaserGetSmiteColorB(bossIndex); - color[3] = NPCChaserGetSmiteColorTrans(bossIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - - // define the direction of the sparks - float dir[3]; - - TE_SetupBeamPoints(startPos, clientPos, g_LightningSprite, 0, 0, 0, 0.2, 20.0, 10.0, 0, 1.0, color, 3); - TE_SendToAll(); - - TE_SetupSparks(clientPos, dir, 5000, 1000); - TE_SendToAll(); - - TE_SetupEnergySplash(clientPos, dir, false); - TE_SendToAll(); - - TE_SetupSmoke(clientPos, g_SmokeSprite, 5.0, 10); - TE_SendToAll(); - - EmitAmbientSound(g_SlenderSmiteSound[bossIndex], startPos, client, SNDLEVEL_SCREAMING); - -} - -Action Timer_SlenderStealLife(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (!g_IsSlenderAttacking[bossIndex]) - { - return Plugin_Stop; - } - - if (timer != g_NpcLifeStealTimer[bossIndex]) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - float damage = NPCChaserGetAttackDamage(bossIndex, attackIndex, difficulty); - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - damage *= 0.5; - } - - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - float targetDist; - Handle traceHandle = null; - - float myPos[3], myEyePos[3], myEyeAng[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - float attackRange = NPCChaserGetAttackRange(bossIndex, attackIndex, difficulty); - float attackFOV = NPCChaserGetAttackSpread(bossIndex, attackIndex, difficulty); - float attackDamageForce = NPCChaserGetAttackDamageForce(bossIndex, attackIndex, difficulty); - - int i = -1; - while ((i = FindEntityByClassname(i, "player")) != -1) - { - SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) - { - continue; - } - - if (!attackEliminated && player.IsEliminated) - { - continue; - } - - float targetPos[3], clientPos[3]; - player.GetEyePosition(targetPos); - player.GetAbsOrigin(clientPos); - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - bool traceDidHit = TR_DidHit(traceHandle); - int traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (traceDidHit && traceHitEntity != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPos); - for (int i2 = 0; i2 < 3; i2++) - { - targetPos[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - traceDidHit = TR_DidHit(traceHandle); - traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - } - - if (!traceDidHit || traceHitEntity == player.index) - { - targetDist = GetVectorSquareMagnitude(targetPos, myEyePos); - - if (targetDist <= SquareFloat(attackRange)) - { - float direction[3]; - SubtractVectors(targetPos, myEyePos, direction); - GetVectorAngles(direction, direction); - - if (FloatAbs(AngleDiff(direction[1], myEyeAng[1])) <= attackFOV) - { - GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(direction, direction); - ScaleVector(direction, attackDamageForce); - - float healthRecover = damage * 2.0; - NPCChaserAddStunHealth(bossIndex, healthRecover); - - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - player.TakeDamage(_, slender, slender, damage * 0.25, 3, _, direction, myEyePos); - } - } - } - } - delete traceHandle; - - return Plugin_Continue; -} - -Action Timer_SlenderChaseBossAttack(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - if (timer != g_SlenderAttackTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - float damage = NPCChaserGetAttackDamage(bossIndex, attackIndex, difficulty); - if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) - { - damage *= 0.5; - } - float damageVsProps = NPCChaserGetAttackDamageVsProps(bossIndex, attackIndex); - int damageType = NPCChaserGetAttackDamageType(bossIndex, attackIndex, difficulty); - - // Damage all players within range. - float myPos[3], myEyePos[3], myEyeAng[3], myRot[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myRot); - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - AddVectors(g_SlenderEyePosOffset[bossIndex], myEyeAng, myEyeAng); - - float viewPunch[3]; - GetChaserProfileAttackPunchVelocity(profile, attackIndex, viewPunch); - - float targetDist; - Handle traceHandle = null; - Handle traceShockwave = null; - - float attackRange = NPCChaserGetAttackRange(bossIndex, attackIndex, difficulty); - float attackFOV = NPCChaserGetAttackSpread(bossIndex, attackIndex, difficulty); - float attackDamageForce = NPCChaserGetAttackDamageForce(bossIndex, attackIndex, difficulty); - - bool hit = false; - - if (g_SlenderState[bossIndex] == STATE_CHASE) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - if (g_LastStuckTime[bossIndex] != 0.0) - { - g_LastStuckTime[bossIndex] = GetGameTime(); - } - loco.ClearStuckStatus(); - if (g_SlenderChaseInitialTimer[bossIndex] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[bossIndex]); - } - - if (NPCChaserGetAttackRepeat(bossIndex, attackIndex) == 1) - { - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttack, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - if (g_SlenderBackupAtkTimer[bossIndex] == null && g_NpcAlreadyAttacked[bossIndex]) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - } - else if (NPCChaserGetAttackRepeat(bossIndex, attackIndex) == 2) - { - if (NpcGetCurrentAttackRepeat(bossIndex, attackIndex) < NPCChaserGetMaxAttackRepeats(bossIndex, attackIndex)) - { - ArrayList repeatArray = GetChaserProfileAttackRepeatTimers(profile, attackIndex); - float delay = repeatArray.Get(NpcGetCurrentAttackRepeat(bossIndex, attackIndex)); - g_SlenderAttackTimer[bossIndex] = CreateTimer(delay, Timer_SlenderChaseBossAttack, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - NpcSetCurrentAttackRepeat(bossIndex, attackIndex, NpcGetCurrentAttackRepeat(bossIndex, attackIndex) + 1); - if (g_SlenderBackupAtkTimer[bossIndex] == null && g_NpcAlreadyAttacked[bossIndex]) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - } - } - switch (NPCChaserGetAttackType(bossIndex, attackIndex)) - { - case SF2BossAttackType_Melee: - { - int prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_physics")) > MaxClients) - { - if (NPCAttackValidateTarget(bossIndex, prop, attackRange, attackFOV)) - { - hit = true; - SDKHooks_TakeDamage(prop, slender, slender, damageVsProps, damageType, _, _, myEyePos); - float spreadVel = 1800.0; - float vertVel = 1300.0; - float vel[3]; - GetAngleVectors(myEyeAng, vel, NULL_VECTOR, NULL_VECTOR); - ScaleVector(vel,spreadVel); - vel[2] = ((GetURandomFloat() + 0.1) * vertVel) * ((GetURandomFloat() + 0.1) * 2); - TeleportEntity(prop, NULL_VECTOR, NULL_VECTOR, vel); - } - } - - prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_dynamic")) > MaxClients) - { - if (GetEntProp(prop, Prop_Data, "m_iHealth") > 0) - { - if (NPCAttackValidateTarget(bossIndex, prop, attackRange, attackFOV)) - { - hit = true; - SDKHooks_TakeDamage(prop, slender, slender, damageVsProps, damageType, _, _, myEyePos); - } - } - } - prop = -1; - while ((prop = FindEntityByClassname(prop, "obj_*")) > MaxClients) - { - if (GetEntProp(prop, Prop_Data, "m_iHealth") > 0) - { - if (NPCAttackValidateTarget(bossIndex, prop, attackRange, attackFOV)) - { - hit = true; - SDKHooks_TakeDamage(prop, slender, slender, damageVsProps, damageType, _, _, myEyePos); - } - } - } - if (NPCChaserShockwaveOnAttack(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetShockwaveAttackIndexes(bossIndex); - GetChaserProfileShockwaveAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex+1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int index = 0; index < count && index < NPCChaserGetAttackCount(bossIndex); index++) - { - int forIndex = StringToInt(allowedIndexesList[index]); - if (forIndex == attackIndex + 1) - { - int beamColor[3], haloColor[3]; - int color1[4], color2[4]; - float myShockPos[3]; - - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myShockPos); - myShockPos[2] += 10; - - GetChaserProfileShockwaveColor1(profile, beamColor); - GetChaserProfileShockwaveColor2(profile, haloColor); - - color1[0] = beamColor[0]; - color1[1] = beamColor[1]; - color1[2] = beamColor[2]; - color1[3] = GetChaserProfileShockwaveAlpha1(profile); - - color2[0] = haloColor[0]; - color2[1] = haloColor[1]; - color2[2] = haloColor[2]; - color2[3] = GetChaserProfileShockwaveAlpha2(profile); - - int modelBeam, modelHalo; - modelBeam = GetChaserProfileShockwaveBeamModel(profile); - modelHalo = GetChaserProfileShockwaveHaloModel(profile); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.2, NPCChaserGetShockwaveWidth(bossIndex,1), NPCChaserGetShockwaveAmplitude(bossIndex), color2, 15, 0); //Inner - TE_SendToAll(); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.3, NPCChaserGetShockwaveWidth(bossIndex,0), NPCChaserGetShockwaveAmplitude(bossIndex), color1, 15, 0); //Outer - TE_SendToAll(); - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex+1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex) - { - int beamColor[3], haloColor[3]; - int color1[4], color2[4]; - float myShockPos[3]; - - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myShockPos); - myShockPos[2] += 10; - - GetChaserProfileShockwaveColor1(profile, beamColor); - GetChaserProfileShockwaveColor2(profile, haloColor); - - color1[0] = beamColor[0]; - color1[1] = beamColor[1]; - color1[2] = beamColor[2]; - color1[3] = GetChaserProfileShockwaveAlpha1(profile); - - color2[0] = haloColor[0]; - color2[1] = haloColor[1]; - color2[2] = haloColor[2]; - color2[3] = GetChaserProfileShockwaveAlpha2(profile); - - int modelBeam, modelHalo; - modelBeam = PrecacheModel(g_SlenderShockwaveBeamSprite[bossIndex], true); - modelHalo = PrecacheModel(g_SlenderShockwaveHaloSprite[bossIndex], true); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.2, NPCChaserGetShockwaveWidth(bossIndex,1), NPCChaserGetShockwaveAmplitude(bossIndex), color2, 15, 0); //Inner - TE_SendToAll(); - - TE_SetupBeamRingPoint(myShockPos, 10.0, NPCChaserGetShockwaveRange(bossIndex,difficulty), modelBeam, modelHalo, 0, 30, 0.3, NPCChaserGetShockwaveWidth(bossIndex,0), NPCChaserGetShockwaveAmplitude(bossIndex), color1, 15, 0); //Outer - TE_SendToAll(); - } - } - } - } - int i = -1; - while ((i = FindEntityByClassname(i, "player")) != -1) - { - SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) - { - continue; - } - - if (!attackEliminated && player.IsEliminated) - { - continue; - } - - float targetPos[3], clientPos[3], targetPosShockwave[3]; - player.GetEyePosition(targetPos); - targetPosShockwave = targetPos; - player.GetAbsOrigin(clientPos); - - if (NPCChaserShockwaveOnAttack(bossIndex)) - { - char indexes[8], allowedIndexes[88], allowedIndexesList[33][3]; - char currentIndex[2]; - int damageIndexes = NPCChaserGetShockwaveAttackIndexes(bossIndex); - GetChaserProfileShockwaveAttackIndexesString(profile, allowedIndexes, sizeof(allowedIndexes)); - FormatEx(indexes, sizeof(indexes), "%d", damageIndexes); - FormatEx(currentIndex, sizeof(currentIndex), "%d", attackIndex+1); - - int count = ExplodeString(allowedIndexes, " ", allowedIndexesList, 33, 3); - if (count > 1) - { - for (int index = 0; index < count && index < NPCChaserGetAttackCount(bossIndex); index++) - { - int forIndex = StringToInt(allowedIndexesList[index]); - if (forIndex == attackIndex + 1) - { - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - bool traceDidHitShockwave = TR_DidHit(traceShockwave); - int traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - - if (traceDidHitShockwave && traceHitEntityShockwave != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPosShockwave); - for (int i2 = 0; i2 < 3; i2++) - { - targetPosShockwave[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - traceDidHitShockwave = TR_DidHit(traceShockwave); - traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - } - - if (!traceDidHitShockwave || traceHitEntityShockwave == player.index) - { - float targetDistShockwave = GetVectorSquareMagnitude(targetPos, myEyePos); - - if ((clientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) - { - float percentLife; - percentLife = player.FlashlightBatteryLife - NPCChaserGetShockwaveDrain(bossIndex, difficulty); - if (percentLife < 0.0) - { - percentLife = 0.0; - } - player.FlashlightBatteryLife = percentLife; - - float directionForce[3]; - float newClientPosit[3]; - player.GetAbsOrigin(newClientPosit); - newClientPosit[2] += 10.0; - - MakeVectorFromPoints(myPos, newClientPosit, directionForce); - - NormalizeVector(directionForce, directionForce); - - ScaleVector(directionForce, NPCChaserGetShockwaveForce(bossIndex, difficulty)); - - if (directionForce[2] < 0.0) - { - directionForce[2] *= -1.0; - } - - player.SetPropVector(Prop_Data, "m_vecBaseVelocity", directionForce); - - if (NPCChaserShockwaveStunEnabled(bossIndex)) - { - player.Stun(NPCChaserGetShockwaveStunDuration(bossIndex, difficulty), NPCChaserGetShockwaveStunSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN, player.index); - } - } - } - break; - } - } - } - else //Legacy support - { - char number = currentIndex[0]; - int attackNumber = 0; - if (FindCharInString(indexes, number) != -1) - { - attackNumber += attackIndex+1; - } - if (indexes[0] != '\0' && currentIndex[0] != '\0' && attackNumber != -1) - { - int currentAtkIndex = StringToInt(currentIndex); - if (attackNumber == currentAtkIndex) - { - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - bool traceDidHitShockwave = TR_DidHit(traceShockwave); - int traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - - if (traceDidHitShockwave && traceHitEntityShockwave != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPosShockwave); - for (int i2 = 0; i2 < 3; i2++) - { - targetPosShockwave[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceShockwave = TR_TraceRayFilterEx(myEyePos, - targetPosShockwave, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitCharactersOrEntity, - slender); - - traceDidHitShockwave = TR_DidHit(traceShockwave); - traceHitEntityShockwave = TR_GetEntityIndex(traceShockwave); - delete traceShockwave; - } - - if (!traceDidHitShockwave || traceHitEntityShockwave == player.index) - { - float targetDistShockwave = GetVectorSquareMagnitude(targetPos, myEyePos); - - if ((clientPos[2] <= myPos[2] + NPCChaserGetShockwaveHeight(bossIndex, difficulty)) && (targetDistShockwave <= SquareFloat(NPCChaserGetShockwaveRange(bossIndex, difficulty)))) - { - float percentLife; - percentLife = player.FlashlightBatteryLife - NPCChaserGetShockwaveDrain(bossIndex, difficulty); - if (percentLife < 0.0) - { - percentLife = 0.0; - } - player.FlashlightBatteryLife = percentLife; - - float directionForce[3]; - float newClientPosit[3]; - player.GetAbsOrigin(newClientPosit); - newClientPosit[2] += 10.0; - - MakeVectorFromPoints(myPos, newClientPosit, directionForce); - - NormalizeVector(directionForce, directionForce); - - ScaleVector(directionForce, NPCChaserGetShockwaveForce(bossIndex, difficulty)); - - if (directionForce[2] < 0.0) - { - directionForce[2] *= -1.0; - } - - player.SetPropVector(Prop_Data, "m_vecBaseVelocity", directionForce); - - if (NPCChaserShockwaveStunEnabled(bossIndex)) - { - player.Stun(NPCChaserGetShockwaveStunDuration(bossIndex, difficulty), NPCChaserGetShockwaveStunSlowdown(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN, player.index); - } - } - } - } - } - } - } - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - bool traceDidHit = TR_DidHit(traceHandle); - int traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (traceDidHit && traceHitEntity != player.index) - { - float targetMins[3], targetMaxs[3]; - player.GetPropVector(Prop_Send, "m_vecMins", targetMins); - player.GetPropVector(Prop_Send, "m_vecMaxs", targetMaxs); - player.GetAbsOrigin(targetPos); - for (int i2 = 0; i2 < 3; i2++) - { - targetPos[i2] += ((targetMins[i2] + targetMaxs[i2]) / 2.0); - } - - traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - slender); - - traceDidHit = TR_DidHit(traceHandle); - traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - } - - if (!traceDidHit || traceHitEntity == player.index) - { - targetDist = GetVectorSquareMagnitude(targetPos, myEyePos); - - if (targetDist <= SquareFloat(attackRange)) - { - float direction[3]; - SubtractVectors(targetPos, myEyePos, direction); - GetVectorAngles(direction, direction); - - if (FloatAbs(AngleDiff(direction[1], myEyeAng[1])) <= attackFOV) - { - GetAngleVectors(direction, direction, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(direction, direction); - ScaleVector(direction, attackDamageForce); - - if (SF_SpecialRound(SPECIALROUND_MULTIEFFECT) || g_RenevantMultiEffect) - { - int effect = GetRandomInt(0, 6); - switch (effect) - { - case 1: - { - if (!player.InCondition(TFCond_Bleeding)) - { - player.Bleed(true, _, 4.0); - } - } - case 2: - { - player.Ignite(true); - } - case 3: - { - player.ChangeCondition(TFCond_Jarated, _, 4.0); - } - case 4: - { - player.ChangeCondition(TFCond_CritMmmph, _, 3.0); - } - case 5: - { - player.ChangeCondition(TFCond_Gas, _, 3.0); - } - case 6: - { - int effectRare = GetRandomInt(1, 30); - switch (effectRare) - { - case 1, 14, 25, 30: - { - int newHealth = player.GetProp(Prop_Send, "m_iHealth")+view_as(damage); - if (newHealth > 450) - { - newHealth = 450; - } - player.ChangeCondition(TFCond_MegaHeal, _, 2.0); - player.SetProp(Prop_Send, "m_iHealth", newHealth); - damage = 0.0; - } - case 7, 27: - { - //It's over 9000! - damage = 9001.0; - } - case 5, 16, 18, 22, 23, 26: - { - ScaleVector(direction, 1200.0); - } - } - } - } - } - - if (player.InCondition(TFCond_UberchargedCanteen) && player.InCondition(TFCond_CritOnFirstBlood) && player.InCondition(TFCond_UberBulletResist) && player.InCondition(TFCond_UberBlastResist) && player.InCondition(TFCond_UberFireResist) && player.InCondition(TFCond_MegaHeal)) //Remove Powerplay - { - player.ChangeCondition(TFCond_UberchargedCanteen, true); - player.ChangeCondition(TFCond_CritOnFirstBlood, true); - player.ChangeCondition(TFCond_UberBulletResist, true); - player.ChangeCondition(TFCond_UberBlastResist, true); - player.ChangeCondition(TFCond_UberFireResist, true); - player.ChangeCondition(TFCond_MegaHeal, true); - TF2_SetPlayerPowerPlay(player.index, false); - } - Call_StartForward(g_OnClientDamagedByBossFwd); - Call_PushCell(player.index); - Call_PushCell(bossIndex); - Call_PushCell(slender); - Call_PushFloat(damage); - Call_PushCell(damageType); - Call_Finish(); - float checkHealth = float(player.GetProp(Prop_Send, "m_iHealth")); - if ((NPCHasAttribute(bossIndex, SF2Attribute_DeathCamOnLowHealth) || NPCChaserGetAttackDeathCamOnLowHealth(bossIndex, attackIndex))) - { - float checkDamage = damage; - - if ((damageType & DMG_ACID) && !player.InCondition(TFCond_DefenseBuffed)) - { - checkDamage *= 3; - } - else if (((damageType & DMG_POISON) || player.InCondition(TFCond_Jarated) || player.InCondition(TFCond_MarkedForDeath) || player.InCondition(TFCond_MarkedForDeathSilent)) && !player.InCondition(TFCond_DefenseBuffed)) - { - checkDamage *= 1.35; - } - else if (player.InCondition(TFCond_DefenseBuffed)) - { - checkDamage *= 0.65; - } - - if (checkDamage > checkHealth) - { - player.StartDeathCam(bossIndex, myPos); - } - } - if (NPCChaserGetAttackPullIn(bossIndex, attackIndex)) - { - float newClientPos[3]; - player.GetAbsOrigin(newClientPos); - newClientPos[2] += 10.0; - - float effectAng[3] = {0.0, 0.0, 0.0}; - - VectorTransform(myEyePos, myPos, myRot, myEyePos); - AddVectors(effectAng, myRot, effectAng); - - float pullDirection[3], pullAngle[3]; - SubtractVectors(newClientPos, myEyePos, pullDirection); - NormalizeVector(pullDirection, pullDirection); - GetVectorAngles(pullDirection, pullAngle); - ScaleVector(pullAngle, 1200.0); - pullAngle[2] = 10.0; - - player.Teleport(newClientPos, NULL_VECTOR, pullAngle); - - player.TakeDamage(_, slender, slender, damage, damageType|DMG_PREVENT_PHYSICS_FORCE, _, direction, myEyePos); - } - else - { - player.TakeDamage(_, slender, slender, damage, damageType, _, direction, myEyePos); - } - player.ViewPunch(viewPunch); - - if (player.InCondition(TFCond_Gas)) - { - player.Ignite(true); - player.ChangeCondition(TFCond_Gas, true); - } - if (player.InCondition(TFCond_Milked)) - { - NPCChaserAddStunHealth(bossIndex, damage * 0.6); - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - } - float duration = NPCGetAttributeValue(bossIndex, SF2Attribute_BleedPlayerOnHit); - if (duration > 0.0 && !player.InCondition(TFCond_Bleeding) && !NPCChaserBleedPlayerOnHit(bossIndex)) - { - player.Bleed(true, _, duration); - } - if (NPCHasAttribute(bossIndex, SF2Attribute_IgnitePlayerOnHit)) - { - player.Ignite(true); - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_StunPlayerOnHit); - if (duration > 0.0) - { - float slowdown = NPCGetAttributeValue(bossIndex, SF2Attribute_StunPlayerPercentage); - if (slowdown > 0.0) - { - player.Stun(duration, slowdown, TF_STUNFLAGS_SMALLBONK, player.index); - } - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_JaratePlayerOnHit); - if (duration > 0.0 && !NPCChaserJaratePlayerOnHit(bossIndex)) - { - player.ChangeCondition(TFCond_Jarated, _, duration); - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_MilkPlayerOnHit); - if (duration > 0.0 && !NPCChaserMilkPlayerOnHit(bossIndex)) - { - player.ChangeCondition(TFCond_Milked, _, duration); - } - duration = NPCGetAttributeValue(bossIndex, SF2Attribute_GasPlayerOnHit); - if (duration > 0.0 && !NPCChaserGasPlayerOnHit(bossIndex)) - { - player.ChangeCondition(TFCond_Gas, _, duration); - } - - if (NPCChaserUseAdvancedDamageEffects(bossIndex)) - { - SlenderDoDamageEffects(bossIndex, attackIndex, player); - } - if (NPCChaserDamageParticlesEnabled(bossIndex)) - { - GetChaserProfileDamageParticleName(profile, damageEffectParticle, sizeof(damageEffectParticle)); - if (damageEffectParticle[0] != '\0') - { - bool beamParticle = GetChaserProfileDamageParticleBeamState(profile); - if (beamParticle) - { - SlenderCreateParticleBeamClient(bossIndex, damageEffectParticle, 35.0, player); - } - else - { - SlenderCreateParticleAttach(bossIndex, damageEffectParticle, 35.0, player); - } - GetChaserProfileDamageParticleSound(profile, damageEffectSound, sizeof(damageEffectSound)); - if (damageEffectSound[0] != '\0') - { - EmitSoundToAll(damageEffectSound, slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - if (NPCChaserXenobladeSystemEnabled(bossIndex)) - { - switch (damageType) - { - case 134217728: - { - player.ChangeCondition(TFCond_TeleportedGlow, _, NPCChaserGetXenobladeBreakDuration(bossIndex), player.index); - } - case 131072: - { - if (player.InCondition(TFCond_TeleportedGlow)) - { - player.Stun(NPCChaserGetXenobladeToppleDuration(bossIndex), NPCChaserGetXenobladeToppleSlowdown(bossIndex), TF_STUNFLAGS_LOSERSTATE, player.index); - player.ChangeCondition(TFCond_TeleportedGlow, true); - player.ChangeCondition(TFCond_MedigunDebuff, _, NPCChaserGetXenobladeToppleDuration(bossIndex), player.index); - } - } - case 268435456: - { - if (player.InCondition(TFCond_MedigunDebuff)) - { - player.Stun(NPCChaserGetXenobladeDazeDuration(bossIndex), 1.0, 10, player.index); - player.ChangeCondition(TFCond_MedigunDebuff, true); - } - } - } - } - - // Add stress - float stressScalar = damage / 125.0; - if (stressScalar > 1.0) - { - stressScalar = 1.0; - } - ClientAddStress(player.index, 0.33 * stressScalar); - - hit = true; - } - } - } - } - - if (hit) - { - // Fling it. - int phys = CreateEntityByName("env_physexplosion"); - if (phys != -1) - { - TeleportEntity(phys, myEyePos, NULL_VECTOR, NULL_VECTOR); - DispatchKeyValue(phys, "spawnflags", "1"); - DispatchKeyValueFloat(phys, "radius", attackRange); - DispatchKeyValueFloat(phys, "magnitude", attackDamageForce); - DispatchSpawn(phys); - ActivateEntity(phys); - AcceptEntityInput(phys, "Explode"); - RemoveEntity(phys); - } - ArrayList hitSounds = GetChaserProfileHitSounds(profile); - if (hitSounds != null && hitSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - hitSounds.GetArray(hitSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - } - else - { - ArrayList missSounds = GetChaserProfileMissSounds(profile); - if (missSounds != null && missSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - missSounds.GetArray(missSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - } - } - case SF2BossAttackType_Ranged: - { - //BULLETS ANYONE? Thx Pelipoika - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - char particleName[PLATFORM_MAX_PATH]; - GetChaserProfileAttackBulletTrace(profile, attackIndex, particleName, sizeof(particleName)); - float spread = NPCChaserGetAttackBulletSpread(bossIndex, attackIndex, difficulty); - ArrayList bulletSounds = GetChaserProfileBulletShootSounds(profile); - if (bulletSounds != null && bulletSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - bulletSounds.GetArray(bulletSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - - float effectPos[3], clientPos[3], basePos[3], baseAng[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - if (target.IsValid) - { - target.GetEyePosition(clientPos); - clientPos[2] -= 20.0; - } - else - { - clientPos[2] = 50.0; - clientPos[0] = 10.0; - VectorTransform(clientPos, basePos, baseAng, clientPos); - } - GetChaserProfileAttackBulletOffset(profile, attackIndex, effectPos); - VectorTransform(effectPos, basePos, baseAng, effectPos); - AddVectors(effectAng, baseAng, effectAng); - - float shootDirection[3], shootAng[3]; - if (!target.IsValid) - { - shootAng[0] = myEyeAng[0]; - shootAng[1] = myEyeAng[1]; - shootAng[2] = myEyeAng[2]; - } - SubtractVectors(clientPos, effectPos, shootDirection); - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - for (int i = 0; i < NPCChaserGetAttackBulletCount(bossIndex, attackIndex, difficulty); i++) - { - float x, y; - x = GetRandomFloat( -0.5, 0.5 ) + GetRandomFloat( -0.5, 0.5 ); - y = GetRandomFloat( -0.5, 0.5 ) + GetRandomFloat( -0.5, 0.5 ); - - float dirShooting[3], right[3], up[3]; - GetAngleVectors(shootAng, dirShooting, right, up); - - float dir[3]; - dir[0] = dirShooting[0] + x * spread * right[0] + y * spread * up[0]; - dir[1] = dirShooting[1] + x * spread * right[1] + y * spread * up[1]; - dir[2] = dirShooting[2] + x * spread * right[2] + y * spread * up[2]; - NormalizeVector(dir, dir); - - float end[3]; - end[0] = effectPos[0] + dir[0] * 9001.0; - end[1] = effectPos[1] + dir[1] * 9001.0; - end[2] = effectPos[2] + dir[2] * 9001.0; - - // Fire a bullet (ignoring the shooter). - Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - if (TR_GetFraction(trace) < 1.0) - { - // Verify we have an entity at the point of impact. - if (TR_GetEntityIndex(trace) == -1) - { - delete trace; - return Plugin_Stop; - } - - float endPos[3]; - TR_GetEndPosition(endPos, trace); - - if (TR_GetEntityIndex(trace) <= 0 || TR_GetEntityIndex(trace) > MaxClients) - { - float normal[3]; - TR_GetPlaneNormal(trace, normal); - GetVectorAngles(normal, normal); - //CreateParticle("impact_concrete", endPos, normal); - } - - // Regular impact effects. - char effect[PLATFORM_MAX_PATH]; - FormatEx(effect, PLATFORM_MAX_PATH, "%s", particleName); - - if (particleName[0] != '\0') - { - int tblidx = FindStringTable("ParticleEffectNames"); - if (tblidx == INVALID_STRING_TABLE) - { - LogError("Could not find string table: ParticleEffectNames"); - return Plugin_Stop; - } - char tmp[256]; - int count = GetStringTableNumStrings(tblidx); - int stridx = INVALID_STRING_INDEX; - for (int i2 = 0; i2 < count; i2++) - { - ReadStringTable(tblidx, i2, tmp, sizeof(tmp)); - if (strcmp(tmp, effect, false) == 0) - { - stridx = i2; - break; - } - } - if (stridx == INVALID_STRING_INDEX) - { - LogError("Could not find particle: %s", effect); - return Plugin_Stop; - } - - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", effectPos[0]); - TE_WriteFloat("m_vecOrigin[1]", effectPos[1]); - TE_WriteFloat("m_vecOrigin[2]", effectPos[2]); - TE_WriteNum("m_iParticleSystemIndex", stridx); - TE_WriteNum("entindex", slender); - TE_WriteNum("m_iAttachType", 2); - TE_WriteNum("m_iAttachmentPointIndex", 0); - TE_WriteNum("m_bResetParticles", false); - TE_WriteNum("m_bControlPoint1", 1); - TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); - TE_SendToAll(); - } - - // TE_SetupBeamPoints(effectPos, endPos, g_iPathLaserModelIndex, g_iPathLaserModelIndex, 0, 30, 0.1, 0.1, 0.1, 5, 0.0, view_as({255, 0, 255, 255}), 30); - // TE_SendToAll(); - if (NPCChaserUseAdvancedDamageEffects(bossIndex)) - { - SlenderDoDamageEffects(bossIndex, attackIndex, SF2_BasePlayer(TR_GetEntityIndex(trace))); - } - SDKHooks_TakeDamage(TR_GetEntityIndex(trace), slender, slender, NPCChaserGetAttackBulletDamage(bossIndex, attackIndex, difficulty), DMG_BULLET, _, CalculateBulletDamageForce(dir, 1.0), endPos); - } - - delete trace; - } - } - case SF2BossAttackType_Projectile: - { - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - if (target.IsValid && target.IsAlive && !target.IsInGhostMode) - { - ArrayList projectileSounds = GetChaserProfileProjectileShootSounds(profile); - if (projectileSounds != null && projectileSounds.Length > 0) - { - SF2BossProfileSoundInfo soundInfo; - projectileSounds.GetArray(projectileSounds.Length == 1 ? 0 : attackIndex, soundInfo, sizeof(soundInfo)); - soundInfo.EmitSound(_, slender); - } - NPCChaserProjectileAttackShoot(bossIndex, slender, target.index, profile); - if (GetChaserProfileShootAnimationsState(profile)) - { - g_NpcUseFireAnimation[bossIndex] = true; - int state = g_SlenderState[bossIndex]; - NPCChaserUpdateBossAnimation(bossIndex, slender, state); - } - } - else //Our target is unavailable, abort. - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - return Plugin_Stop; - } - } - } - Call_StartForward(g_OnBossAttackedFwd); - Call_PushCell(bossIndex); - Call_PushCell(attackIndex); - Call_Finish(); - if (NPCChaserGetAttackDisappear(bossIndex, attackIndex, difficulty) != 1 && NPCChaserGetAttackRepeat(bossIndex, attackIndex) < 1) - { - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEnd, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - } - if (NPCChaserGetAttackRepeat(bossIndex, attackIndex) >= 1 && !g_NpcAlreadyAttacked[bossIndex]) - { - g_SlenderBackupAtkTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEndBackup, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - g_NpcAlreadyAttacked[bossIndex] = true; - } - if (NPCChaserGetAttackDisappear(bossIndex, attackIndex, difficulty) == 1) - { - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - if (g_SlenderAttackTimer[bossIndex] != null) - { - KillTimer(g_SlenderAttackTimer[bossIndex]); - } - if (g_NpcLifeStealTimer[bossIndex] != null) - { - KillTimer(g_NpcLifeStealTimer[bossIndex]); - } - if (g_SlenderBackupAtkTimer[bossIndex] != null) - { - KillTimer(g_SlenderBackupAtkTimer[bossIndex]); - } - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - if (g_SlenderLaserTimer[bossIndex] != null) - { - KillTimer(g_SlenderLaserTimer[bossIndex]); - } - RemoveSlender(bossIndex); - return Plugin_Stop; - } - delete traceHandle; - delete traceShockwave; - return Plugin_Stop; -} - -static float[] CalculateBulletDamageForce(const float bulletDir[3], float scale) -{ - float force[3]; force = bulletDir; - NormalizeVector(force, force); - ScaleVector(force, g_PhysicsPushScaleConVar.FloatValue); - ScaleVector(force, scale); - return force; -} - -Action Timer_SlenderChaseBossAttackIgniteHit(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int player = EntRefToEntIndex(entref); - if (!player || player == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - if (timer != g_PlayerIgniteTimer[player]) - { - return Plugin_Stop; - } - - TF2_IgnitePlayer(player, player, g_PlayerIgniteDurationEffect[player]); - g_PlayerIgniteTimer[player] = null; - return Plugin_Stop; -} - -Action Timer_SlenderPrepareExplosiveDance(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - CreateTimer(0.13, Timer_SlenderChaseBossExplosiveDance, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Stop; -} - -static Action Timer_SlenderChaseBossExplosiveDance(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - static int exploded = 0; - - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - exploded=0; - return Plugin_Stop; - } - - // Damage all players within range. - float myPos[3], myEyePos[3], myEyeAng[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", myPos); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int range = NPCChaserGetAttackExplosiveDanceRadius(bossIndex, attackIndex, difficulty); - - AddVectors(g_SlenderEyePosOffset[bossIndex], myEyeAng, myEyeAng); - - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - if (!g_IsSlenderAttacking[bossIndex]) - { - exploded=0; - return Plugin_Stop; - } - - exploded++; - if (exploded <= 35) - { - float slenderPosition[3], explosionPosition[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - explosionPosition[2] = slenderPosition[2] + 50.0; - for (int e = 0; e < 5; e++) - { - for (int i = 1; i < MaxClients; i++) - { - SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || !player.IsAlive || player.IsInGhostMode) - { - continue; - } - - if (!attackEliminated && player.IsEliminated) - { - continue; - } - - int explosivePower = CreateEntityByName("env_explosion"); - if (explosivePower != -1) - { - DispatchKeyValueFloat(explosivePower, "DamageForce", 180.0); - - SetEntProp(explosivePower, Prop_Data, "m_iMagnitude", 666, 4); - SetEntProp(explosivePower, Prop_Data, "m_iRadiusOverride", 200, 4); - SetEntPropEnt(explosivePower, Prop_Data, "m_hOwnerEntity", slender); - explosionPosition[0] = slenderPosition[0] + float(GetRandomInt(-range, range)); - explosionPosition[1] = slenderPosition[1] + float(GetRandomInt(-range, range)); - TeleportEntity(explosivePower, explosionPosition, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(explosivePower); - - AcceptEntityInput(explosivePower, "Explode"); - RemoveEntity(explosivePower); - CreateTimer(0.1, Timer_KillEntity, EntIndexToEntRef(explosivePower), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - else - { - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEnd, EntIndexToEntRef(slender)); - exploded = 0; - return Plugin_Stop; - } - return Plugin_Continue; -} - -Action Timer_SlenderChaseBossAttackBeginLaser(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderAttackTimer[bossIndex]) - { - return Plugin_Stop; - } - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - g_NpcLaserTimer[bossIndex] = GetGameTime() + NPCChaserGetAttackLaserDuration(bossIndex, attackIndex, difficulty); - - g_SlenderLaserTimer[bossIndex] = CreateTimer(0.1, Timer_SlenderChaseBossAttackLaser, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - - g_SlenderAttackTimer[bossIndex] = CreateTimer(NPCChaserGetAttackDuration(bossIndex, attackIndex, difficulty) - NPCChaserGetAttackDamageDelay(bossIndex, attackIndex, difficulty), Timer_SlenderChaseBossAttackEnd, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - - NPCChaserSetNextAttackTime(bossIndex, attackIndex, GetGameTime()+NPCChaserGetAttackCooldown(bossIndex, attackIndex, difficulty)); - - return Plugin_Stop; -} - -static Action Timer_SlenderChaseBossAttackLaser(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderLaserTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (GetGameTime() >= g_NpcLaserTimer[bossIndex]) - { - return Plugin_Stop; - } - - if (NPCGetFlags(bossIndex) & SFF_FAKE) - { - SlenderMarkAsFake(bossIndex); - return Plugin_Stop; - } - - CBaseCombatCharacter animationEntity = CBaseCombatCharacter(slender); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - if (!g_IsSlenderAttacking[bossIndex]) - { - return Plugin_Stop; - } - - SF2_BasePlayer target = SF2_BasePlayer(EntRefToEntIndex(g_SlenderTarget[bossIndex])); - if (target.IsValid && target.IsAlive && !target.IsInGhostMode) - { - float effectPos[3]; - float clientPos[3]; - - target.GetEyePosition(clientPos); - clientPos[2] -= 20.0; - - float basePos[3], baseAng[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - GetChaserProfileAttackLaserOffset(profile, attackIndex, effectPos); - VectorTransform(effectPos, basePos, baseAng, effectPos); - AddVectors(effectAng, baseAng, effectAng); - - float shootDirection[3], shootAng[3]; - SubtractVectors(clientPos, effectPos, shootDirection); - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - float dirShooting[3]; - GetAngleVectors(shootAng, dirShooting, NULL_VECTOR, NULL_VECTOR); - - float dir[3]; - dir[0] = dirShooting[0]; - dir[1] = dirShooting[1]; - dir[2] = dirShooting[2]; - NormalizeVector(dir, dir); - - float end[3]; - end[0] = effectPos[0] + dir[0] * 9001.0; - end[1] = effectPos[1] + dir[1] * 9001.0; - end[2] = effectPos[2] + dir[2] * 9001.0; - - Handle trace = TR_TraceRayFilterEx(effectPos, end, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, TraceRayDontHitAnyEntity, slender); - if (TR_GetFraction(trace) < 1.0) - { - // Verify we have an entity at the point of impact. - if (TR_GetEntityIndex(trace) == -1) - { - delete trace; - return Plugin_Stop; - } - - float endPos[3]; - TR_GetEndPosition(endPos, trace); - - if (TR_GetEntityIndex(trace) <= 0 || TR_GetEntityIndex(trace) > MaxClients) - { - float normal[3]; TR_GetPlaneNormal(trace, normal); - GetVectorAngles(normal, normal); - } - - int color[3]; - NPCChaserGetAttackLaserColor(bossIndex, attackIndex, color); - int colorReal[4]; - colorReal[0] = color[0]; - colorReal[1] = color[1]; - colorReal[2] = color[2]; - colorReal[3] = 255; - - if (NPCChaserGetAttackLaserAttachmentState(bossIndex, attackIndex)) - { - char attachment[PLATFORM_MAX_PATH]; - GetChaserProfileAttackLaserAttachmentName(profile, attackIndex, attachment, sizeof(attachment)); - int attachmentIndex = animationEntity.LookupAttachment(attachment); - float targetEntPos[3], tempAng[3]; - animationEntity.GetAttachment(attachmentIndex, targetEntPos, tempAng); - TE_SetupBeamPoints(targetEntPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, NPCChaserGetAttackLaserSize(bossIndex, attackIndex), NPCChaserGetAttackLaserSize(bossIndex, attackIndex), 5, NPCChaserGetAttackLaserNoise(bossIndex, attackIndex), colorReal, 1); - TE_SendToAll(); - } - else - { - TE_SetupBeamPoints(effectPos, endPos, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, NPCChaserGetAttackLaserSize(bossIndex, attackIndex), NPCChaserGetAttackLaserSize(bossIndex, attackIndex), 5, NPCChaserGetAttackLaserNoise(bossIndex, attackIndex), colorReal, 1); - TE_SendToAll(); - } - if (NPCChaserUseAdvancedDamageEffects(bossIndex)) - { - SlenderDoDamageEffects(bossIndex, attackIndex, SF2_BasePlayer(TR_GetEntityIndex(trace))); - } - SDKHooks_TakeDamage(TR_GetEntityIndex(trace), slender, slender, NPCChaserGetAttackLaserDamage(bossIndex, attackIndex, difficulty), DMG_SHOCK|DMG_ALWAYSGIB, _, _, endPos); - } - - delete trace; - - } - return Plugin_Continue; -} - -bool NPCAttackValidateTarget(int bossIndex,int target, float attackRange, float attackFOV, bool checkBlock = false) -{ - int bossEntity = NPCGetEntIndex(bossIndex); - - float myEyePos[3], myEyeAng[3]; - NPCGetEyePosition(bossIndex, myEyePos); - if (target > MaxClients) - { - //float flVecMaxs[3]; - myEyePos[2]+=30.0; - } - GetEntPropVector(bossEntity, Prop_Data, "m_angAbsRotation", myEyeAng); - AddVectors(g_SlenderEyeAngOffset[bossIndex], myEyeAng, myEyeAng); - - float targetPos[3], targetMins[3], targetMaxs[3]; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - GetEntPropVector(target, Prop_Send, "m_vecMins", targetMins); - GetEntPropVector(target, Prop_Send, "m_vecMaxs", targetMaxs); - - for (int i = 0; i < 3; i++) - { - targetPos[i] += (targetMins[i] + targetMaxs[i]) / 2.0; - } - - float targetDist = GetVectorSquareMagnitude(targetPos, myEyePos); - if (targetDist <= SquareFloat(attackRange)) - { - float direction[3]; - SubtractVectors(g_SlenderGoalPos[bossIndex], myEyePos, direction); - GetVectorAngles(direction, direction); - - if (FloatAbs(AngleDiff(direction[1], myEyeAng[1])) <= attackFOV / 2.0) - { - Handle traceHandle = TR_TraceRayFilterEx(myEyePos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayDontHitAnyEntity, - bossEntity); - - bool traceDidHit = TR_DidHit(traceHandle); - int traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (!traceDidHit || traceHitEntity == target) - { - if (checkBlock) - { - float pos[3], posForward[3]; - GetEntPropVector(bossEntity, Prop_Data, "m_vecAbsOrigin", pos); - GetPositionForward(pos, myEyeAng, posForward, SquareRoot(targetDist+SquareFloat(50.0))); - if (NPCGetRaidHitbox(bossIndex) == 1) - { - traceHandle = TR_TraceHullFilterEx(pos, - posForward, - g_SlenderDetectMins[bossIndex], - g_SlenderDetectMaxs[bossIndex], - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - TraceRayBossVisibility, - bossEntity); - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - traceHandle = TR_TraceHullFilterEx(pos, - posForward, - HULL_HUMAN_MINS, - HULL_HUMAN_MAXS, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - TraceRayBossVisibility, - bossEntity); - } - traceHitEntity = TR_GetEntityIndex(traceHandle); - delete traceHandle; - - if (traceHitEntity == target) - { - return true; - } - } - else - { - return true; - } - } - delete traceHandle; - } - } - - return false; -} - -static Action Timer_SlenderChaseBossAttackEnd(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderAttackTimer[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - loco.ClearStuckStatus(); - - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - g_SlenderLaserTimer[bossIndex] = null; - CBaseNPC_RemoveAllLayers(slender); - return Plugin_Stop; -} - -static Action Timer_SlenderChaseBossAttackEndBackup(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex == -1) - { - return Plugin_Stop; - } - - if (timer != g_SlenderBackupAtkTimer[bossIndex]) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - loco.ClearStuckStatus(); - - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - g_SlenderLaserTimer[bossIndex] = null; - CBaseNPC_RemoveAllLayers(slender); - return Plugin_Stop; -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp deleted file mode 100644 index b7eca358..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_mind.sp +++ /dev/null @@ -1,2687 +0,0 @@ -#if defined _sf2_npc_chaser_mind_included -#endinput -#endif - -#define _sf2_npc_chaser_mind_included - -#pragma semicolon 1 - -char cloakParticle[PLATFORM_MAX_PATH]; -static bool g_BossForceAnimationUpdate[MAX_BOSSES] = { false, ... }; - -Action Timer_SlenderChaseBossThink(Handle timer, any entref) //God damn you are so big, you get a file dedicated to only you -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_Chaser chaserBoss = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); - if (chaserBoss == SF2_INVALID_NPC_CHASER) - { - return Plugin_Stop; - } - - if (chaserBoss.Flags & SFF_MARKEDASFAKE) - { - return Plugin_Stop; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(slender); - if (npc == INVALID_NPC) - { - return Plugin_Stop; - } - - if (timer != g_SlenderEntityThink[chaserBoss.Index]) - { - return Plugin_Stop; - } - - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - INextBot bot = npc.GetBot(); - CBaseCombatCharacter npcEntity = CBaseCombatCharacter(npc.GetEntity()); - - g_BossForceAnimationUpdate[chaserBoss.Index] = false; - - float gameTime = GetGameTime(); - - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_IDLE, 1, "g_SlenderTimeUntilKill[%d]: %f", chaserBoss.Index, g_SlenderTimeUntilKill[chaserBoss.Index] - gameTime); - #endif - - float myPos[3], myEyeAng[3]; - float buffer[3]; - - char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - chaserBoss.GetProfile(slenderProfile, sizeof(slenderProfile)); - - npcEntity.GetAbsOrigin(myPos); - npcEntity.GetAbsAngles(myEyeAng); - - AddVectors(myEyeAng, g_SlenderEyeAngOffset[chaserBoss.Index], myEyeAng); - - int difficulty = GetLocalGlobalDifficulty(chaserBoss.Index); - - float originalSpeed, originalMaxSpeed, originalAcceleration; - if (!g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - if (!SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) && !SF_IsSlaughterRunMap() && !g_Renevant90sEffect) - { - originalSpeed = chaserBoss.GetSpeed(difficulty); - originalMaxSpeed = chaserBoss.GetMaxSpeed(difficulty); - originalAcceleration = chaserBoss.GetAcceleration(difficulty); - } - else if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || SF_IsSlaughterRunMap() || g_Renevant90sEffect) - { - originalSpeed = chaserBoss.GetSpeed(difficulty); - originalMaxSpeed = chaserBoss.GetMaxSpeed(difficulty); - originalAcceleration = chaserBoss.GetAcceleration(difficulty); - if (originalSpeed < (SF_IsSlaughterRunMap() ? 580.0 : 520.0)) - { - originalSpeed = SF_IsSlaughterRunMap() ? 580.0 : 520.0; - } - if (originalMaxSpeed < (SF_IsSlaughterRunMap() ? 580.0 : 520.0)) - { - originalMaxSpeed = SF_IsSlaughterRunMap() ? 580.0 : 520.0; - } - if (originalAcceleration < (SF_IsSlaughterRunMap() ? 10000.0 : 9001.0)) - { - originalAcceleration = SF_IsSlaughterRunMap() ? 10000.0 : 9001.0; - } - } - originalSpeed += chaserBoss.GetAddSpeed(); - originalMaxSpeed += chaserBoss.GetAddMaxSpeed(); - originalAcceleration += chaserBoss.GetAddAcceleration(); - } - else - { - originalSpeed = 0.0; - originalMaxSpeed = 0.0; - originalAcceleration = chaserBoss.GetAcceleration(difficulty); - } - float originalWalkSpeed = chaserBoss.GetWalkSpeed(difficulty); - float originalMaxWalkSpeed = chaserBoss.GetMaxWalkSpeed(difficulty); - - if (g_InProxySurvivalRageMode) - { - originalSpeed *= 1.25; - originalWalkSpeed *= 1.25; - originalMaxSpeed *= 1.25; - originalMaxWalkSpeed *= 1.25; - } - float speed, maxSpeed, acceleration; - if (!g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - speed = originalSpeed; - maxSpeed = originalMaxSpeed; - } - else - { - speed = 0.0; - maxSpeed = 0.0; - } - acceleration = originalAcceleration; - if (g_RoundDifficultyModifier > 1.0) - { - speed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - maxSpeed = originalMaxSpeed + ((originalMaxSpeed * g_RoundDifficultyModifier) / 20.0); - acceleration = originalAcceleration + ((originalAcceleration * g_RoundDifficultyModifier) / 15.0); - } - else - { - speed = originalSpeed; - maxSpeed = originalMaxSpeed; - acceleration = originalAcceleration; - } - if (speed < originalSpeed) - { - speed = originalSpeed; - } - if (speed > maxSpeed) - { - speed = maxSpeed; - } - if (acceleration < originalAcceleration) - { - acceleration = originalAcceleration; - } - - float walkSpeed = originalWalkSpeed; - float maxWalkSpeed = originalMaxWalkSpeed; - if (g_RoundDifficultyModifier > 1.0) - { - walkSpeed = (originalWalkSpeed + (originalWalkSpeed * g_RoundDifficultyModifier) / 15.0); - maxWalkSpeed = (originalMaxWalkSpeed + (originalMaxWalkSpeed * g_RoundDifficultyModifier) / 20.0); - } - if (walkSpeed < originalWalkSpeed) - { - walkSpeed = originalWalkSpeed; - } - if (walkSpeed > maxWalkSpeed) - { - walkSpeed = maxWalkSpeed; - } - - g_SlenderSpeedMultiplier[chaserBoss.Index] = 1.0; - if (chaserBoss.CanBeSeen(_, true)) - { - if (chaserBoss.HasAttribute(SF2Attribute_ReducedSpeedOnLook)) - { - g_SlenderSpeedMultiplier[chaserBoss.Index] = chaserBoss.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); - speed *= chaserBoss.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); - } - - if (chaserBoss.HasAttribute(SF2Attribute_ReducedWalkSpeedOnLook)) - { - walkSpeed *= chaserBoss.GetAttributeValue(SF2Attribute_ReducedWalkSpeedOnLook); - } - - if (chaserBoss.HasAttribute(SF2Attribute_ReducedAccelerationOnLook)) - { - walkSpeed *= chaserBoss.GetAttributeValue(SF2Attribute_ReducedAccelerationOnLook); - } - } - if (g_NpcHasCloaked[chaserBoss.Index]) - { - speed *= chaserBoss.GetCloakSpeedMultiplier(difficulty); - maxSpeed *= chaserBoss.GetCloakSpeedMultiplier(difficulty); - } - if (g_NpcIsCrawling[chaserBoss.Index]) - { - walkSpeed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - maxWalkSpeed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - speed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - maxSpeed *= chaserBoss.GetCrawlSpeedMultiplier(difficulty); - } - Action action = Plugin_Continue; - float forwardSpeed = walkSpeed; - Call_StartForward(g_OnBossGetWalkSpeedFwd); - Call_PushCell(chaserBoss.Index); - Call_PushFloatRef(forwardSpeed); - Call_Finish(action); - if (action == Plugin_Changed) - { - walkSpeed = forwardSpeed; - } - - action = Plugin_Continue; - forwardSpeed = speed; - Call_StartForward(g_OnBossGetSpeedFwd); - Call_PushCell(chaserBoss.Index); - Call_PushFloatRef(forwardSpeed); - Call_Finish(action); - if (action == Plugin_Changed) - { - speed = forwardSpeed; - } - - if (difficulty >= RoundToNearest(chaserBoss.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty))) - { - g_SlenderCalculatedWalkSpeed[chaserBoss.Index] = walkSpeed; - } - else - { - g_SlenderCalculatedWalkSpeed[chaserBoss.Index] = 0.0; - } - - if (!g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - g_SlenderCalculatedSpeed[chaserBoss.Index] = speed; - } - else - { - g_SlenderCalculatedSpeed[chaserBoss.Index] = 0.0; - } - g_SlenderCalculatedAcceleration[chaserBoss.Index] = acceleration; - g_SlenderCalculatedMaxSpeed[chaserBoss.Index] = maxSpeed; - g_SlenderCalculatedMaxWalkSpeed[chaserBoss.Index] = maxWalkSpeed; - - int oldState = g_SlenderState[chaserBoss.Index]; - int oldTarget = EntRefToEntIndex(g_SlenderTarget[chaserBoss.Index]); - - int bestNewTarget = INVALID_ENT_REFERENCE; - int soundTarget = INVALID_ENT_REFERENCE; - float searchRange = NPCGetSearchRadius(chaserBoss.Index, difficulty); - float searchSoundRange = NPCGetHearingRadius(chaserBoss.Index, difficulty); - float bestNewTargetDist = SquareFloat(searchRange); - int state = oldState; - - bool playerInFOV[MAXTF2PLAYERS]; - bool playerNear[MAXTF2PLAYERS]; - bool playerMadeNoise[MAXTF2PLAYERS]; - bool playerInTrap[MAXTF2PLAYERS]; - float playerDists[MAXTF2PLAYERS]; - bool playerVisible[MAXTF2PLAYERS]; - - bool attackEliminated = !!(chaserBoss.Flags & SFF_ATTACKWAITERS); - bool stunEnabled = chaserBoss.StunEnabled; - - float traceMins[3] = { -16.0, ... }; - traceMins[2] = 0.0; - float traceMaxs[3] = { 16.0, ... }; - traceMaxs[2] = 0.0; - - char class[32]; - - bool building = false; - if (oldTarget > MaxClients) - { - GetEdictClassname(oldTarget, class, sizeof(class)); - if (strcmp(class, "obj_sentrygun") == 0 && !GetEntProp(oldTarget, Prop_Send, "m_bCarried")) - { - building = true; - } - if (strcmp(class, "obj_dispenser") == 0 && !GetEntProp(oldTarget, Prop_Send, "m_bCarried")) - { - building = true; - } - if (strcmp(class, "obj_teleporter") == 0 && !GetEntProp(oldTarget, Prop_Send, "m_bCarried")) - { - building = true; - } - } - - if (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) - { - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderSoundTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - - if (g_SlenderSoundTarget[chaserBoss.Index] != INVALID_ENT_REFERENCE) - { - soundTarget = EntRefToEntIndex(g_SlenderSoundTarget[chaserBoss.Index]); - } - - bool inFlashlight = false; - bool doubleFlashlightDamage = false; - float customFlashlightDamage = 1.0; - - // Gather data about the players around me and get the best new target, in case my old target is invalidated. - int ent = -1; - while ((ent = FindEntityByClassname(ent, "player")) != -1) - { - SF2_BasePlayer player = SF2_BasePlayer(ent); - if (!IsTargetValidForSlender(player, attackEliminated)) - { - continue; - } - TFClassType classType = player.Class; - int classToInt = view_as(classType); - - float traceStartPos[3], traceEndPos[3]; - chaserBoss.GetEyePosition(traceStartPos); - player.GetEyePosition(traceEndPos); - - float dist = 99999999999.9; - - bool isVisible; - int traceHitEntity; - if (!g_NpcOriginalVisibility[chaserBoss.Index]) - { - Handle trace = TR_TraceRayFilterEx(traceStartPos, - traceEndPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayBossVisibility, - npcEntity.index); - isVisible = !TR_DidHit(trace); - traceHitEntity = TR_GetEntityIndex(trace); - - delete trace; - } - else - { - TR_TraceHullFilter(traceStartPos, - traceEndPos, - traceMins, - traceMaxs, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - TraceRayBossVisibility, - npcEntity.index); - - isVisible = !TR_DidHit(); - traceHitEntity = TR_GetEntityIndex(); - } - - if (!isVisible && traceHitEntity == player.index) - { - isVisible = true; - } - - if (isVisible) - { - isVisible = NPCShouldSeeEntity(chaserBoss.Index, player.index); - } - - if (g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) - { - int fogEntity = player.GetDataEnt(g_PlayerFogCtrlOffset); - if (IsValidEdict(fogEntity)) - { - if (GetEntData(fogEntity, g_FogCtrlEnableOffset) && - GetVectorSquareMagnitude(traceStartPos, traceEndPos) >= SquareFloat(GetEntDataFloat(fogEntity, g_FogCtrlEndOffset))) - { - isVisible = false; - } - } - } - - float priorityValue; - - playerVisible[player.index] = isVisible; - - // Near radius check. - if (!g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && playerVisible[player.index] && - GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(NPCChaserGetWakeRadius(chaserBoss.Index))) - { - playerNear[player.index] = true; - } - if (playerVisible[player.index] && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) - { - TF2_StunPlayer(player.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); - } - - // FOV check. - SubtractVectors(traceEndPos, traceStartPos, buffer); - GetVectorAngles(buffer, buffer); - - if (FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (NPCGetFOV(chaserBoss.Index) * 0.5)) - { - playerInFOV[player.index] = true; - } - - if (!SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !g_SlenderChasesEndlessly[chaserBoss.Index]) - { - priorityValue = g_PageMax > 0 ? ((float(player.PageCount) / float(g_PageMax))/4.0) : 0.0; - } - - if (!IsClassConfigsValid()) - { - if ((classType == TFClass_Medic || player.HasRegenItem) && !SF_IsBoxingMap()) - { - priorityValue += 0.2; - } - - if (classType == TFClass_Spy) - { - priorityValue += 0.1; - } - } - else - { - if (!SF_IsBoxingMap() && player.HasRegenItem) - { - priorityValue += 0.2; - } - priorityValue += g_ClassBossPriorityMultiplier[classToInt]; - } - - //Taunt alerts - if (!g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && state != STATE_ALERT && state != STATE_ATTACK && state != STATE_STUN && state != STATE_CHASE && - GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(NPCGetTauntAlertRange(chaserBoss.Index, difficulty)) && player.InCondition(TFCond_Taunting)) - { - if (g_SlenderTauntAlertCount[chaserBoss.Index] < 5) - { - float targetPos[3]; - if (player.IsValid) - { - player.GetAbsOrigin(targetPos); - } - bestNewTarget = player.index; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - chaserBoss.SetGoalPos(targetPos); - g_SlenderAutoChaseCount[chaserBoss.Index] += NPCChaserAutoChaseAddVoice(chaserBoss.Index, difficulty) * 2; - state = STATE_ALERT; - g_SlenderTauntAlertCount[chaserBoss.Index]++; - } - else - { - playerMadeNoise[player.index] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - g_SlenderTauntAlertCount[chaserBoss.Index] = 0; - } - } - - //Trap check - if (g_PlayerTrapped[player.index] && GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(searchRange)) - { - if (state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - playerInTrap[player.index] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - } - } - - if (NPCChaserCanAutoChaseSprinters(chaserBoss.Index) && IsClientReallySprinting(ent) && GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(searchSoundRange) && gameTime >= g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - playerMadeNoise[player.index] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - } - - if (NPCChaserIsAutoChaseEnabled(chaserBoss.Index)) - { - if (IsValidClient(soundTarget) && g_SlenderAutoChaseCount[chaserBoss.Index] >= NPCChaserAutoChaseThreshold(chaserBoss.Index, difficulty) && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - playerMadeNoise[soundTarget] = true; - g_NpcInAutoChase[chaserBoss.Index] = true; - } - } - - dist = GetVectorSquareMagnitude(traceStartPos, traceEndPos); - playerDists[player.index] = dist; - - if (!g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && playerVisible[ent] && (playerNear[ent] || playerInFOV[ent])) - { - float targetPos[3]; - player.GetAbsOrigin(targetPos); - if (!g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] && !g_NpcIgnoreNonMarkedForChase[chaserBoss.Index]) - { - if (dist <= SquareFloat(searchRange)) - { - // Subtract distance to increase priority. - dist -= ((dist * priorityValue)); - - if (dist < bestNewTargetDist) - { - bestNewTarget = player.index; - bestNewTargetDist = dist; - g_SlenderInterruptConditions[chaserBoss.Index] |= COND_SAWENEMY; - g_SlenderSeeTarget[chaserBoss.Index] = EntIndexToEntRef(player.index); - } - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][0] = targetPos[0]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][1] = targetPos[1]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][2] = targetPos[2]; - g_SlenderLastFoundPlayer[chaserBoss.Index][player.index] = gameTime; - } - } - else if ((g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] && g_NpcIgnoreNonMarkedForChase[chaserBoss.Index]) || NPCChaserCanChaseOnLook(chaserBoss.Index)) - { - if (g_NpcChaseOnLookTarget[chaserBoss.Index].Length > 0 && g_NpcChaseOnLookTarget[chaserBoss.Index].FindValue(ent) != -1 && - dist <= SquareFloat(searchRange)) - { - // Subtract distance to increase priority. - dist -= ((dist * priorityValue)); - - if (dist < bestNewTargetDist) - { - bestNewTarget = player.index; - bestNewTargetDist = dist; - g_SlenderInterruptConditions[chaserBoss.Index] |= COND_SAWENEMY; - g_SlenderSeeTarget[chaserBoss.Index] = EntIndexToEntRef(player.index); - } - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][0] = targetPos[0]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][1] = targetPos[1]; - g_SlenderLastFoundPlayerPos[chaserBoss.Index][player.index][2] = targetPos[2]; - g_SlenderLastFoundPlayer[chaserBoss.Index][player.index] = gameTime; - } - } - } - - if ((playerMadeNoise[player.index] || playerInTrap[player.index]) && state != STATE_CHASE && state != STATE_STUN && state != STATE_ATTACK) - { - if (g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && playerMadeNoise[player.index]) - { - bestNewTarget = player.index; - state = STATE_CHASE; - SlenderAlertAllValidBosses(chaserBoss.Index, player.index, bestNewTarget); - player.GetAbsOrigin(g_SlenderGoalPos[chaserBoss.Index]); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderGiveUp[chaserBoss.Index] = false; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = true; - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - } - else if (playerInTrap[player.index] || (!g_NpcIgnoreNonMarkedForChase[chaserBoss.Index] && playerMadeNoise[ent])) - { - bestNewTarget = player.index; - state = STATE_CHASE; - SlenderAlertAllValidBosses(chaserBoss.Index, player.index, bestNewTarget); - player.GetAbsOrigin(g_SlenderGoalPos[chaserBoss.Index]); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderGiveUp[chaserBoss.Index] = false; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - } - } - - if (chaserBoss.StunByFlashlightEnabled && IsClientUsingFlashlight(ent) && playerInFOV[ent]) // Check to see if someone is facing at us with flashlight on. Only if I'm facing them too. BLINDNESS! - { - if (GetVectorSquareMagnitude(traceStartPos, traceEndPos) <= SquareFloat(SF2_FLASHLIGHT_LENGTH)) - { - float eyeAng[3], requiredAng[3]; - player.GetEyeAngles(eyeAng); - SubtractVectors(traceEndPos, traceStartPos, requiredAng); - GetVectorAngles(requiredAng, requiredAng); - - if (FloatAbs(AngleDiff(eyeAng[0], requiredAng[0])) <= 45.0 && FloatAbs(AngleDiff(requiredAng[1], eyeAng[1])) >= 135.0) - { - if (playerVisible[player.index]) - { - inFlashlight = true; - if (!IsClassConfigsValid()) - { - if (classType == TFClass_Engineer) - { - doubleFlashlightDamage = true; - } - } - else - { - customFlashlightDamage = g_ClassFlashlightDamageMultiplier[classToInt]; - } - } - } - } - } - if (NPCChaserCanChaseOnLook(chaserBoss.Index) && playerInFOV[player.index] && playerVisible[player.index]) - { - float eyeAng[3], requiredAng[3]; - player.GetEyeAngles(eyeAng); - SubtractVectors(traceEndPos, traceStartPos, requiredAng); - GetVectorAngles(requiredAng, requiredAng); - if (FloatAbs(AngleDiff(eyeAng[0], requiredAng[0])) <= 45.0 && FloatAbs(AngleDiff(requiredAng[1], eyeAng[1])) >= 135.0) - { - if (g_NpcChaseOnLookTarget[chaserBoss.Index].FindValue(player.index) == -1) - { - g_NpcChaseOnLookTarget[chaserBoss.Index].Push(player.index); - SF2BossProfileSoundInfo soundInfo; - GetBossProfileScareSounds(slenderProfile, soundInfo); - soundInfo.EmitSound(true, player.index); - player.ChangeCondition(TFCond_MarkedForDeathSilent); - if (NPCChaserCanUseChaseInitialAnimation(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; - g_BossForceAnimationUpdate[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, STATE_CHASE); - g_SlenderChaseInitialTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } - - if (IsValidClient(EntRefToEntIndex(g_SlenderTarget[chaserBoss.Index])) && playerVisible[EntRefToEntIndex(g_SlenderTarget[chaserBoss.Index])]) - { - g_SlenderTargetIsVisible[chaserBoss.Index] = true; - } - else - { - g_SlenderTargetIsVisible[chaserBoss.Index] = false; - } - - if (g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index]) - { - if (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0) - { - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - g_SlenderSoundTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - soundTarget = INVALID_ENT_REFERENCE; - } - } - - // Damage us if we're in a flashlight. - if (inFlashlight) - { - if (stunEnabled) - { - if (chaserBoss.StunByFlashlightEnabled && !(SF_SpecialRound(SPECIALROUND_NIGHTVISION)) && !(g_NightvisionEnabledConVar.BoolValue)) - { - if (chaserBoss.StunHealth > 0) - { - if (!IsClassConfigsValid()) - { - if (!doubleFlashlightDamage) - { - chaserBoss.AddStunHealth(-chaserBoss.StunFlashlightDamage); - } - else - { - chaserBoss.AddStunHealth(-chaserBoss.StunFlashlightDamage * 1.5); - } - } - else - { - chaserBoss.AddStunHealth(-chaserBoss.StunFlashlightDamage * customFlashlightDamage); - } - if (chaserBoss.StunHealth <= 0.0 && state != STATE_STUN) - { - chaserBoss.TriggerStun(npcEntity.index, slenderProfile, myPos); - Call_StartForward(g_OnBossStunnedFwd); - Call_PushCell(chaserBoss.Index); - Call_PushCell(-1); - Call_Finish(); - state = STATE_STUN; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - } - } - } - } - - // Process the target that we should have. - int target = oldTarget; - - if (target && target != INVALID_ENT_REFERENCE) - { - if (!IsTargetValidForSlender(SF2_BasePlayer(target), attackEliminated)) - { - // Clear our target; he's not valid anymore. - oldTarget = target; - target = INVALID_ENT_REFERENCE; - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - } - else - { - // Clear our target; he's not valid anymore. - oldTarget = target; - target = INVALID_ENT_REFERENCE; - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - - if (building) - { - target = bestNewTarget; - } - - //We should never give up, but sometimes it happens. - if (g_SlenderGiveUp[chaserBoss.Index]) - { - //Damit our target is unreachable for some unexplained reasons, haaaaaaaaaaaa! - if (!SF_IsRaidMap() || !SF_BossesChaseEndlessly() || !SF_IsProxyMap() || !g_SlenderChasesEndlessly[chaserBoss.Index] || !SF_IsBoxingMap() || !SF_IsSlaughterRunMap()) - { - state = STATE_IDLE; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - g_SlenderGiveUp[chaserBoss.Index] = false; - } - - if ((SF_BossesChaseEndlessly() || g_SlenderChasesEndlessly[chaserBoss.Index] || SF_IsSlaughterRunMap() || SF_IsRaidMap() || SF_IsProxyMap() || SF_IsBoxingMap()) && !(chaserBoss.Flags & SFF_NOTELEPORT)) - { - //RemoveSlender(chaserBoss.Index); - } - //Do not, ok? - g_SlenderGiveUp[chaserBoss.Index] = false; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - - int interruptConditions = g_SlenderInterruptConditions[chaserBoss.Index]; - - if ((SF_IsRaidMap() || (SF_BossesChaseEndlessly() || g_SlenderChasesEndlessly[chaserBoss.Index]) || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) && !g_SlenderGiveUp[chaserBoss.Index] && !building && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (!IsValidClient(target) || (IsValidClient(target) && g_PlayerEliminated[target])) - { - if (state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN && NPCAreAvailablePlayersAlive()) - { - ArrayList arrayRaidTargets = new ArrayList(); - - for (int i = 1; i < MaxClients; i++) - { - if (!IsValidClient(i) || - !IsPlayerAlive(i) || - g_PlayerEliminated[i] || - IsClientInGhostMode(i) || - DidClientEscape(i)) - { - continue; - } - arrayRaidTargets.Push(i); - } - if(arrayRaidTargets.Length > 0) - { - int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); - if(IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) - { - bestNewTarget = raidTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - target = bestNewTarget; - } - } - delete arrayRaidTargets; - } - } - } - - if (NPCChaserCanChaseOnLook(chaserBoss.Index)) - { - if (state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN && NPCAreAvailablePlayersAlive() && g_NpcChaseOnLookTarget[chaserBoss.Index].Length != 0) - { - for (int i = 0; i < g_NpcChaseOnLookTarget[chaserBoss.Index].Length; i++) - { - int lookClient = g_NpcChaseOnLookTarget[chaserBoss.Index].Get(i); - if (IsValidClient(lookClient) && !g_PlayerEliminated[lookClient] && IsPlayerAlive(lookClient) && - !IsClientInGhostMode(lookClient) && !DidClientEscape(lookClient)) - { - bestNewTarget = lookClient; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(lookClient); - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - target = bestNewTarget; - SlenderAlertAllValidBosses(chaserBoss.Index, target, bestNewTarget); - } - else - { - i--; - } - } - } - } - - // Process which state we should be in. - switch (state) - { - case STATE_IDLE, STATE_WANDER: - { - for (int i = 0; i < MAX_NPCTELEPORTER; i++) - { - if (chaserBoss.GetTeleporter(i) != INVALID_ENT_REFERENCE) - { - chaserBoss.SetTeleporter(i, INVALID_ENT_REFERENCE); - } - } - if (state == STATE_WANDER) - { - if ((!g_BossPathFollower[chaserBoss.Index].IsValid() && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) || g_SlenderNextWanderPos[chaserBoss.Index][difficulty] < gameTime) - { - float min = GetChaserProfileWanderEnterTimeMin(slenderProfile, difficulty); - float max = GetChaserProfileWanderEnterTimeMax(slenderProfile, difficulty); - g_SlenderNextWanderPos[chaserBoss.Index][difficulty] = gameTime + GetRandomFloat(min, max); - state = STATE_IDLE; - } - } - else - { - if ((chaserBoss.Flags & SFF_WANDERMOVE) && gameTime >= g_SlenderNextWanderPos[chaserBoss.Index][difficulty] && GetRandomFloat(0.0, 1.0) <= 0.25 && - difficulty >= RoundToNearest(chaserBoss.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)) && NPCGetWanderPosition(chaserBoss)) - { - float min = GetChaserProfileWanderTimeMin(slenderProfile, difficulty); - float max = GetChaserProfileWanderTimeMax(slenderProfile, difficulty); - g_SlenderNextWanderPos[chaserBoss.Index][difficulty] = gameTime + GetRandomFloat(min, max); - state = STATE_WANDER; - } - } - if (SF_SpecialRound(SPECIALROUND_BEACON) || g_RenevantBeaconEffect) - { - if (!g_SlenderInBacon[chaserBoss.Index]) - { - SF2_BasePlayer clientToPos = NPCChaserGetClosestPlayer(npcEntity.index); - float clientPosition[3]; - if (clientToPos.IsValid) - { - clientToPos.GetAbsOrigin(clientPosition); - } - bestNewTarget = clientToPos.index; - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - chaserBoss.SetGoalPos(clientPosition); - g_SlenderInBacon[chaserBoss.Index] = true; - } - } - if (interruptConditions & COND_SAWENEMY) - { - if (NPCChaserIsAutoChaseEnabled(chaserBoss.Index)) - { - g_SlenderAutoChaseCount[chaserBoss.Index] += NPCChaserAutoChaseAddGeneral(chaserBoss.Index, difficulty); - } - // I saw someone over here. Automatically put me into alert mode. - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - int clientToPos = EntRefToEntIndex(g_SlenderSeeTarget[chaserBoss.Index]); - float clientPosition[3]; - if (IsValidClient(clientToPos)) - { - GetClientAbsOrigin(clientToPos, clientPosition); - bestNewTarget = clientToPos; - chaserBoss.SetGoalPos(clientPosition); - } - } - else if (interruptConditions & COND_HEARDSUSPICIOUSSOUND) - { - // Sound counts will be reset after the boss hears a sound after a certain amount of time. - // The purpose of sound counts is to induce boss focusing on sounds suspicious entities are making. - - int count = 0; - if (interruptConditions & COND_HEARDFOOTSTEP) - { - count += GetChaserProfileFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPLOUD) - { - count += GetChaserProfileLoudFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPQUIET) - { - count += GetChaserProfileQuietFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDWEAPON) - { - count += GetChaserProfileWeaponAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFLASHLIGHT) - { - count += GetChaserProfileFlashlightAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDVOICE) - { - count += GetChaserProfileVoiceAddThreshold(slenderProfile, difficulty); - } - - bool discardMasterPos = gameTime >= g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index]; - - if (discardMasterPos) - { - g_SlenderTargetSoundCount[chaserBoss.Index] = 0; - } - - if ((GetVectorSquareMagnitude(g_SlenderTargetSoundTempPos[chaserBoss.Index], g_SlenderTargetSoundMasterPos[chaserBoss.Index]) <= SquareFloat(GetChaserProfileSoundPosDistanceTolerance(slenderProfile, difficulty)) || - discardMasterPos) && count > 0) - { - g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index] = gameTime + GetChaserProfileSoundPosDiscardTime(slenderProfile, difficulty); - g_SlenderTargetSoundMasterPos[chaserBoss.Index][0] = g_SlenderTargetSoundTempPos[chaserBoss.Index][0]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][1] = g_SlenderTargetSoundTempPos[chaserBoss.Index][1]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][2] = g_SlenderTargetSoundTempPos[chaserBoss.Index][2]; - g_SlenderTargetSoundCount[chaserBoss.Index] += count; - } - if (g_SlenderTargetSoundCount[chaserBoss.Index] >= NPCChaserGetSoundCountToAlert(chaserBoss.Index, difficulty)) - { - // Someone's making some noise over there! Time to investigate. - g_SlenderInvestigatingSound[chaserBoss.Index] = true; // This is just so that our sound position would be the goal position. - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - } - } - if (NPCChaserGetTrapState(chaserBoss.Index)) - { - if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount < 32) - { - Trap_SpawnTrap(myPos, myEyeAng, chaserBoss.Index); - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - else if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount >= 32) - { - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - } - } - case STATE_ALERT: - { - if (state == STATE_ALERT) - { - if (!g_BossPathFollower[chaserBoss.Index].IsValid() && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) - { - if (!(interruptConditions & COND_HEARDSUSPICIOUSSOUND)) - { - state = STATE_IDLE; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - else if ((interruptConditions & COND_HEARDSUSPICIOUSSOUND) && GetVectorSquareMagnitude(g_SlenderGoalPos[chaserBoss.Index], myPos) <= SquareFloat(20.0)) - { - state = STATE_IDLE; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - } - } - if (gameTime >= g_SlenderTimeUntilIdle[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && !g_NpcIsRunningToHeal[chaserBoss.Index]) - { - state = STATE_IDLE; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - else if (IsValidClient(bestNewTarget)) - { - if (gameTime >= g_SlenderTimeUntilChase[chaserBoss.Index] || playerNear[bestNewTarget]) - { - float traceStartPos[3], traceEndPos[3]; - NPCGetEyePosition(chaserBoss.Index, traceStartPos); - - if (IsValidClient(bestNewTarget)) - { - GetClientEyePosition(bestNewTarget, traceEndPos); - } - else - { - float targetMins[3], targetMaxs[3]; - GetEntPropVector(bestNewTarget, Prop_Send, "m_vecMins", targetMins); - GetEntPropVector(bestNewTarget, Prop_Send, "m_vecMaxs", targetMaxs); - GetEntPropVector(bestNewTarget, Prop_Data, "m_vecAbsOrigin", traceEndPos); - for (int i = 0; i < 3; i++) - { - traceEndPos[i] += ((targetMins[i] + targetMaxs[i]) / 2.0); - } - } - - Handle trace = TR_TraceRayFilterEx(traceStartPos, - traceEndPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayBossVisibility, - npcEntity.index); - - bool isVisible = !TR_DidHit(trace); - int traceHitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (!isVisible && traceHitEntity == bestNewTarget) - { - isVisible = true; - } - - if (building || ((playerNear[bestNewTarget] || playerInFOV[bestNewTarget]) && playerVisible[bestNewTarget])) - { - // AHAHAHAH! I GOT YOU NOW! - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - SlenderAlertAllValidBosses(chaserBoss.Index, target, bestNewTarget); - } - } - if (playerInTrap[bestNewTarget] || playerMadeNoise[bestNewTarget]) - { - // AHAHAHAH! I GOT YOU NOW! - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - SlenderAlertAllValidBosses(chaserBoss.Index, target, bestNewTarget); - } - } - if ((building)) - { - state = STATE_CHASE; - } - else - { - if (interruptConditions & COND_SAWENEMY) - { - if (IsValidClient(bestNewTarget)) - { - chaserBoss.SetGoalPos(g_SlenderLastFoundPlayerPos[chaserBoss.Index][bestNewTarget]); - } - } - else if (interruptConditions & COND_HEARDSUSPICIOUSSOUND) - { - int count = 0; - if (interruptConditions & COND_HEARDFOOTSTEP) - { - count += GetChaserProfileFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPLOUD) - { - count += GetChaserProfileLoudFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFOOTSTEPQUIET) - { - count += GetChaserProfileQuietFootstepAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDWEAPON) - { - count += GetChaserProfileWeaponAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDFLASHLIGHT) - { - count += GetChaserProfileFlashlightAddThreshold(slenderProfile, difficulty); - } - if (interruptConditions & COND_HEARDVOICE) - { - count += GetChaserProfileVoiceAddThreshold(slenderProfile, difficulty); - } - bool discardMasterPos = gameTime >= g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index]; - - if ((GetVectorSquareMagnitude(g_SlenderTargetSoundTempPos[chaserBoss.Index], g_SlenderTargetSoundMasterPos[chaserBoss.Index]) <= SquareFloat(GetChaserProfileSoundPosDistanceTolerance(slenderProfile, difficulty)) || - discardMasterPos) && count > 0) - { - g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index] = gameTime + GetChaserProfileSoundPosDiscardTime(slenderProfile, difficulty); - g_SlenderTargetSoundMasterPos[chaserBoss.Index][0] = g_SlenderTargetSoundTempPos[chaserBoss.Index][0]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][1] = g_SlenderTargetSoundTempPos[chaserBoss.Index][1]; - g_SlenderTargetSoundMasterPos[chaserBoss.Index][2] = g_SlenderTargetSoundTempPos[chaserBoss.Index][2]; - - // We have to manually set the goal position here because the goal position will not be changed due to no change in state. - chaserBoss.SetGoalPos(g_SlenderTargetSoundMasterPos[chaserBoss.Index]); - - g_SlenderInvestigatingSound[chaserBoss.Index] = true; - } - } - - for (int attackIndex = 0; attackIndex < NPCChaserGetAttackCount(chaserBoss.Index); attackIndex++) - { - if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_LaserBeam) - { - continue; - } - - bool blockingProp = false; - - if (chaserBoss.Flags & SFF_ATTACKPROPS) - { - blockingProp = NPC_CanAttackProps(chaserBoss.Index, NPCChaserGetAttackRange(chaserBoss.Index, attackIndex, difficulty), NPCChaserGetAttackSpread(chaserBoss.Index, attackIndex, difficulty)); - } - - if (blockingProp) - { - state = STATE_ATTACK; - NPCSetCurrentAttackIndex(chaserBoss.Index, attackIndex); - break; - } - } - } - if (NPCChaserGetTrapState(chaserBoss.Index)) - { - if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount < 32) - { - Trap_SpawnTrap(myPos, myEyeAng, chaserBoss.Index); - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - else if (g_SlenderNextTrapPlacement[chaserBoss.Index] <= gameTime && g_TrapEntityCount >= 32) - { - g_SlenderNextTrapPlacement[chaserBoss.Index] = gameTime + NPCChaserGetTrapSpawnTime(chaserBoss.Index, difficulty); - } - } - } - case STATE_CHASE, STATE_ATTACK, STATE_STUN: - { - if (state == STATE_CHASE) - { - if (IsValidEntity(target)) - { - float traceStartPos[3], traceEndPos[3]; - NPCGetEyePosition(chaserBoss.Index, traceStartPos); - if (chaserBoss.CloakEnabled) - { - float cloakDist = GetVectorSquareMagnitude(g_SlenderGoalPos[chaserBoss.Index], myPos); - float cloakRange = chaserBoss.GetCloakRange(difficulty); - float decloakRange = chaserBoss.GetDecloakRange(difficulty); - if (cloakDist <= SquareFloat(cloakRange) && !g_NpcHasCloaked[chaserBoss.Index] && g_SlenderNextCloakTime[chaserBoss.Index] <= gameTime && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index]) - { - // Time for a more cloaking aproach! - SetEntityRenderMode(npcEntity.index, view_as(GetChaserProfileCloakRenderMode(slenderProfile))); - - int cloakColor[4]; - GetChaserProfileCloakRenderColor(slenderProfile, cloakColor); - - SetEntityRenderColor(npcEntity.index, cloakColor[0], cloakColor[1], cloakColor[2], cloakColor[3]); - g_NpcNextDecloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakDuration(chaserBoss.Index, difficulty); - SlenderToggleParticleEffects(npcEntity.index); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOnSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_NpcHasCloaked[chaserBoss.Index] = true; - Call_StartForward(g_OnBossCloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - if ((cloakDist <= SquareFloat(decloakRange) || gameTime >= g_NpcNextDecloakTime[chaserBoss.Index]) && g_NpcHasCloaked[chaserBoss.Index]) - { - //Come back now! - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], g_SlenderRenderColor[chaserBoss.Index][3]); - - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - g_NpcHasCloaked[chaserBoss.Index] = false; - } - } - - if (chaserBoss.ProjectileEnabled) - { - if (chaserBoss.Flags & SFF_FAKE) - { - SlenderMarkAsFake(chaserBoss.Index); - } - if (g_NpcProjectileCooldown[chaserBoss.Index] <= gameTime && !NPCChaserUseProjectileAmmo(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && (building || (playerInFOV[target] && CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target)))) && !g_NpcHasCloaked[chaserBoss.Index]) - { - NPCChaserProjectileShoot(chaserBoss.Index, npcEntity.index, target, slenderProfile, myPos); - } - else if (chaserBoss.ProjectileUsesAmmo) - { - if (g_NpcProjectileCooldown[chaserBoss.Index] <= gameTime && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && (building || (playerInFOV[target] && CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target)))) && !g_NpcHasCloaked[chaserBoss.Index] && (g_NpcProjectileAmmo[chaserBoss.Index] != 0)) - { - NPCChaserProjectileShoot(chaserBoss.Index, npcEntity.index, target, slenderProfile, myPos); - g_NpcProjectileAmmo[chaserBoss.Index] = g_NpcProjectileAmmo[chaserBoss.Index] - 1; - } - if (g_NpcProjectileAmmo[chaserBoss.Index] == 0 && !g_NpcReloadingProjectiles[chaserBoss.Index]) - { - g_NpcProjectileTimeToReload[chaserBoss.Index] = gameTime + NPCChaserGetProjectileReloadTime(chaserBoss.Index, difficulty); - g_NpcReloadingProjectiles[chaserBoss.Index] = true; - } - if (g_NpcProjectileTimeToReload[chaserBoss.Index] <= gameTime && g_NpcProjectileAmmo[chaserBoss.Index] == 0) - { - g_NpcProjectileAmmo[chaserBoss.Index] = NPCChaserGetLoadedProjectiles(chaserBoss.Index, difficulty); - g_NpcReloadingProjectiles[chaserBoss.Index] = false; - } - } - } - - if (IsValidClient(target)) - { - GetClientEyePosition(target, traceEndPos); - } - else - { - float targetMins[3], targetMaxs[3]; - GetEntPropVector(target, Prop_Send, "m_vecMins", targetMins); - GetEntPropVector(target, Prop_Send, "m_vecMaxs", targetMaxs); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", traceEndPos); - for (int i = 0; i < 3; i++) - { - traceEndPos[i] += ((targetMins[i] + targetMaxs[i]) / 2.0); - } - } - - bool isDeathPosVisible = false; - - if (g_SlenderChaseDeathPositionBool[chaserBoss.Index]) - { - Handle trace = TR_TraceRayFilterEx(traceStartPos, - g_SlenderChaseDeathPosition[chaserBoss.Index], - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayBossVisibility, - npcEntity.index); - isDeathPosVisible = !TR_DidHit(trace); - delete trace; - } - - if (!CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target))) - { - - } - - if (!building && !playerVisible[target] && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (gameTime >= g_SlenderTimeUntilAlert[chaserBoss.Index] || (!attackEliminated && g_PlayerEliminated[target])) - { - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - g_NpcInAutoChase[chaserBoss.Index] = false; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - else if (isDeathPosVisible || (!attackEliminated && g_PlayerEliminated[target])) - { - state = STATE_IDLE; - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = false; - g_NpcInAutoChase[chaserBoss.Index] = false; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else if ((building || CanNPCSeePlayerNonTransparent(chaserBoss.Index, SF2_BasePlayer(target))) && !NPCIsRaging(chaserBoss.Index) && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; // We're not chasing a dead player after all! Reset. - - float attackDirection[3], attackEyePos[3], clientPosAttack[3], attackDist; - NPCGetEyePosition(chaserBoss.Index, attackEyePos); - if (IsValidClient(target)) - { - GetClientEyePosition(target, clientPosAttack); - } - else - { - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", clientPosAttack); - clientPosAttack[2] += 20.0; - } - attackDist = GetVectorSquareMagnitude(myPos, g_SlenderGoalPos[chaserBoss.Index]); - SubtractVectors(clientPosAttack, attackEyePos, attackDirection); - GetVectorAngles(attackDirection, attackDirection); - attackDirection[2] = 180.0; - - float attackBeginRangeEx, attackBeginFOVEx; - - float fov = FloatAbs(AngleDiff(attackDirection[1], myEyeAng[1])); - float clientHealth = float(GetEntProp(target, Prop_Send, "m_iHealth")); - ArrayList arrayAttacks = new ArrayList(); - for (int attackIndex2 = 0; attackIndex2 < NPCChaserGetAttackCount(chaserBoss.Index); attackIndex2++) - { - if ((difficulty >= NPCChaserGetAttackUseOnDifficulty(chaserBoss.Index, attackIndex2) && difficulty < NPCChaserGetAttackBlockOnDifficulty(chaserBoss.Index, attackIndex2)) && - NPCChaserGetNextAttackTime(chaserBoss.Index, attackIndex2) <= gameTime) - { - arrayAttacks.Push(attackIndex2); - } - if (IsValidClient(target) && (NPCChaserGetAttackUseOnHealth(chaserBoss.Index, attackIndex2) != -1.0 && clientHealth < NPCChaserGetAttackUseOnHealth(chaserBoss.Index, attackIndex2)) || - (NPCChaserGetAttackBlockOnHealth(chaserBoss.Index, attackIndex2) != -1.0 && clientHealth >= NPCChaserGetAttackBlockOnHealth(chaserBoss.Index, attackIndex2))) - { - int eraseAttack = arrayAttacks.FindValue(attackIndex2); - if (eraseAttack != -1) - { - arrayAttacks.Erase(eraseAttack); - } - } - if (g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && - NPCChaserGetAttackInterruptChaseInitialState(chaserBoss.Index, attackIndex2, difficulty)) - { - int eraseAttack = arrayAttacks.FindValue(attackIndex2); - if (eraseAttack != -1) - { - arrayAttacks.Erase(eraseAttack); - } - } - } - Call_StartForward(g_OnBossPreAttackFwd); - Call_PushCell(chaserBoss.Index); - Call_PushCell(arrayAttacks); - Call_Finish(); - if (arrayAttacks != null && arrayAttacks.Length > 0) - { - int randomAttackIndex = arrayAttacks.Get(GetRandomInt(0, arrayAttacks.Length - 1)); - attackBeginRangeEx = NPCChaserGetAttackBeginRange(chaserBoss.Index, randomAttackIndex, difficulty); - attackBeginFOVEx = NPCChaserGetAttackBeginFOV(chaserBoss.Index, randomAttackIndex, difficulty); - if (attackDist <= SquareFloat(attackBeginRangeEx) && fov <= (attackBeginFOVEx / 2.0) && - !NPCIsRaging(chaserBoss.Index) && - !g_NpcUseStartFleeAnimation[chaserBoss.Index] && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - state = STATE_ATTACK; - NPCSetCurrentAttackIndex(chaserBoss.Index, randomAttackIndex); - } - } - if (arrayAttacks != null) - { - delete arrayAttacks; - } - } - if (state == STATE_CHASE) - { - for (int attackIndex = 0; attackIndex < NPCChaserGetAttackCount(chaserBoss.Index); attackIndex++) - { - if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_LaserBeam) - { - continue; - } - - bool blockingProp = false; - - if (chaserBoss.Flags & SFF_ATTACKPROPS) - { - blockingProp = NPC_CanAttackProps(chaserBoss.Index, NPCChaserGetAttackRange(chaserBoss.Index, attackIndex, difficulty), NPCChaserGetAttackSpread(chaserBoss.Index, attackIndex, difficulty)); - } - - if (blockingProp) - { - state = STATE_ATTACK; - NPCSetCurrentAttackIndex(chaserBoss.Index, attackIndex); - break; - } - } - } - } - if ((!IsValidEntity(target) && !IsValidEntity(bestNewTarget)) || (!building && !IsTargetValidForSlender(SF2_BasePlayer(target), attackEliminated)) && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - // Even if the target isn't valid anymore, see if I still have some ways to go on my current path, - // because I shouldn't actually know that the target has died until I see it. - if (!g_BossPathFollower[chaserBoss.Index].IsValid()) - { - state = STATE_IDLE; - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - } - } - else if (state == STATE_ATTACK) - { - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - int attackIndex = NPCGetCurrentAttackIndex(chaserBoss.Index); - bool cancelLos = GetChaserProfileAttackCancelLOSState(slenderProfile, attackIndex, difficulty); - float cancelDistance = GetChaserProfileAttackCancelDistance(slenderProfile, attackIndex, difficulty); - if (cancelLos) - { - if (!IsValidClient(target) || !playerVisible[target]) - { - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - loco.ClearStuckStatus(); - g_SlenderTimeUntilAttackEnd[chaserBoss.Index] = -1.0; - NPCClearAttackStats(chaserBoss.Index, true); - if (IsValidClient(target)) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - g_SlenderGiveUp[chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - if (cancelDistance > 0.0) - { - float distance = GetVectorSquareMagnitude(myPos, g_SlenderGoalPos[chaserBoss.Index]); - if (distance > SquareFloat(cancelDistance)) - { - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - loco.ClearStuckStatus(); - NPCClearAttackStats(chaserBoss.Index, true); - if (IsValidClient(target)) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - g_SlenderGiveUp[chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - } - if (!g_IsSlenderAttacking[chaserBoss.Index] || (g_SlenderTimeUntilAttackEnd[chaserBoss.Index] != -1.0 && g_SlenderTimeUntilAttackEnd[chaserBoss.Index] <= gameTime)) - { - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - loco.ClearStuckStatus(); - g_SlenderTimeUntilAttackEnd[chaserBoss.Index] = -1.0; - NPCClearAttackStats(chaserBoss.Index); - - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - if (IsValidClient(target)) - { - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - // Chase him again! - g_SlenderGiveUp[chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - // Target isn't valid anymore. We killed him, Mac! - state = STATE_ALERT; - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - else if (state == STATE_STUN) - { - if (gameTime >= g_SlenderTimeUntilRecover[chaserBoss.Index]) - { - g_LastStuckTime[chaserBoss.Index] = 0.0; - if (chaserBoss.CanDisappearOnStun()) - { - RemoveSlender(chaserBoss.Index); - } - loco.ClearStuckStatus(); - float value = chaserBoss.GetAttributeValue(SF2Attribute_AddStunHealhOnStun); - if (value > 0.0) - { - chaserBoss.StunHealth = chaserBoss.InitialStunHealth; - chaserBoss.StunHealthAdd = value; - chaserBoss.AddStunHealth(chaserBoss.StunHealthAdd); - } - else - { - chaserBoss.StunHealth = chaserBoss.InitialStunHealth; - } - value = chaserBoss.GetAttributeValue(SF2Attribute_AddSpeedOnStun); - if (value > 0.0) - { - chaserBoss.SetAddSpeed(value); - } - value = chaserBoss.GetAttributeValue(SF2Attribute_AddMaxSpeedOnStun); - if (value > 0.0) - { - chaserBoss.SetAddMaxSpeed(value); - } - value = chaserBoss.GetAttributeValue(SF2Attribute_AddAccelerationOnStun); - if (value > 0.0) - { - chaserBoss.SetAddAcceleration(value); - } - g_SlenderNextStunTime[chaserBoss.Index] = gameTime + chaserBoss.StunCooldown; - if (IsValidClient(target)) - { - // Chase him again! - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - else - { - // WHAT DA FUUUUUUUUUUUQ. TARGET ISN'T VALID. AUSDHASUIHD - state = STATE_ALERT; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_NpcAutoChaseSprinterCooldown[chaserBoss.Index] = gameTime + 5.0; - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare)) - { - g_NpcLostChasingScareVictim[chaserBoss.Index] = true; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - } - if (chaserBoss.HasAttribute(SF2Attribute_AlertCopies) || chaserBoss.HasAttribute(SF2Attribute_AlertCompanions)) - { - g_NpcCopyAlerted[chaserBoss.Index] = false; - } - } - } - } - } - } - - if (state != STATE_STUN) - { - if (stunEnabled) - { - if (chaserBoss.StunHealth <= 0 && g_SlenderNextStunTime[chaserBoss.Index] <= gameTime) - { - state = STATE_STUN; - if (g_SlenderChaseInitialTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[chaserBoss.Index]); - } - } - if (SF_IsBoxingMap() && NPCChaserIsBoxingBoss(chaserBoss.Index)) - { - if (!NPCChaserGetSelfHealState(chaserBoss.Index)) - { - float percent = chaserBoss.InitialStunHealth > 0 ? (chaserBoss.StunHealth / chaserBoss.InitialStunHealth) : 0.0; - if (percent < 0.75 && percent >= 0.5 && !g_NpcUsesRageAnimation[chaserBoss.Index][0] && !g_NpcUsedRage1[chaserBoss.Index]) - { - g_NpcUsesRageAnimation[chaserBoss.Index][0] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 2 && difficulty < 2) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client < MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - - g_NpcUsedRage1[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderRage1Timer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderRageOneTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - else if (percent < 0.5 && percent >= 0.25 && !g_NpcUsesRageAnimation[chaserBoss.Index][1] && !g_NpcUsedRage2[chaserBoss.Index]) - { - g_NpcUsesRageAnimation[chaserBoss.Index][1] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 3 && difficulty < 3) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client < MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 3); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageTwoSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageTwo); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - - g_NpcUsedRage2[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderRage2Timer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderRageTwoTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - else if (percent < 0.25 && !g_NpcUsesRageAnimation[chaserBoss.Index][2] && !g_NpcHasUsedRage3[chaserBoss.Index]) - { - g_NpcUsesRageAnimation[chaserBoss.Index][2] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 4 && difficulty < 4) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client < MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 4); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritOnFirstBlood, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, CRIT_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 8.0 + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 4: - { - TF2_RegeneratePlayer(client); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageThreeSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageThree); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - - g_NpcHasUsedRage3[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderRage3Timer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderRageThreeTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - } - else - { - float percent = chaserBoss.InitialStunHealth > 0 ? (chaserBoss.StunHealth / chaserBoss.InitialStunHealth) : 0.0; - if (percent < NPCChaserGetStartSelfHealPercentage(chaserBoss.Index) && g_NpcSelfHealStage[chaserBoss.Index] < 3) - { - if (g_NpcIsRunningToHeal[chaserBoss.Index] || g_NpcIsHealing[chaserBoss.Index]) - { - oldTarget = target; - target = INVALID_ENT_REFERENCE; - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - } - if (g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcSetHealDestination[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - float min = GetChaserProfileHealTimeMin(slenderProfile); - float max = GetChaserProfileHealTimeMax(slenderProfile); - g_NpcFleeHealTimer[chaserBoss.Index] = gameTime + GetRandomFloat(min, max); - - float wanderRangeMin = GetChaserProfileHealRangeMin(slenderProfile); - float wanderRangeMax = GetChaserProfileHealRangeMax(slenderProfile); - float wanderRange = GetRandomFloat(wanderRangeMin, wanderRangeMax); - - CNavArea navArea = npcEntity.GetLastKnownArea(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(navArea, wanderRange); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - if (collector.Get(i).HasAttributes(NAV_MESH_CROUCH)) - { - continue; - } - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - if (areaArray.Length > 0) - { - float fleePos[3]; - CNavArea fleeArea = collector.Get(areaArray.Get(GetRandomInt(0, validAreaCount - 1))); - if (fleeArea != NULL_AREA) - { - fleeArea.GetCenter(fleePos); - - chaserBoss.SetGoalPos(fleePos); - } - } - - if (!g_NpcHasCloaked[chaserBoss.Index] && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && chaserBoss.CloakEnabled && NPCChaserCanCloakToHeal(chaserBoss.Index)) - { - // Time for a more cloaking aproach! - SetEntityRenderMode(npcEntity.index, view_as(GetChaserProfileCloakRenderMode(slenderProfile))); - - int cloakColor[4]; - GetChaserProfileCloakRenderColor(slenderProfile, cloakColor); - - SetEntityRenderColor(npcEntity.index, cloakColor[0], cloakColor[1], cloakColor[2], cloakColor[3]); - g_NpcHasCloaked[chaserBoss.Index] = true; - g_NpcNextDecloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakDuration(chaserBoss.Index, difficulty); - SlenderToggleParticleEffects(npcEntity.index); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOnSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - Call_StartForward(g_OnBossCloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - - g_NpcSetHealDestination[chaserBoss.Index] = true; - - delete collector; - delete areaArray; - } - if (!g_BossPathFollower[chaserBoss.Index].IsValid()) - { - g_NpcSetHealDestination[chaserBoss.Index] = false; - } - if ((GetVectorSquareMagnitude(g_SlenderGoalPos[chaserBoss.Index], myPos) < SquareFloat(125.0) || g_NpcFleeHealTimer[chaserBoss.Index] < gameTime) && g_NpcSetHealDestination[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], g_SlenderRenderColor[chaserBoss.Index][3]); - - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - float timerFloat = GetChaserProfileHealAnimTimer(slenderProfile); - g_NpcHealCount[chaserBoss.Index] = 0; - g_NpcUsesHealAnimation[chaserBoss.Index] = true; - g_NpcIsRunningToHeal[chaserBoss.Index] = false; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderHealTimer[chaserBoss.Index] = CreateTimer(timerFloat, Timer_SlenderHealAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_SlenderHealDelayTimer[chaserBoss.Index] = CreateTimer(GetChaserProfileHealFunctionTimer(slenderProfile), Timer_SlenderHealDelayTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcIsHealing[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - } - if (!g_NpcUseStartFleeAnimation[chaserBoss.Index] && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - switch (g_NpcSelfHealStage[chaserBoss.Index]) - { - case 0: - { - g_NpcUseStartFleeAnimation[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 2 && difficulty < 2) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client < MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderStartFleeTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderFleeAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcHealCount[chaserBoss.Index] = 0; - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - } - case 1: - { - g_NpcUseStartFleeAnimation[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 3 && difficulty < 3) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client < MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 3); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritCola, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderStartFleeTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderFleeAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcHealCount[chaserBoss.Index] = 0; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageTwoSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageTwo); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - } - case 2: - { - g_NpcUseStartFleeAnimation[chaserBoss.Index] = true; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - if (difficulty != 4 && difficulty < 4) - { - NPCChaserSetBoxingDifficulty(chaserBoss.Index, NPCChaserGetBoxingDifficulty(chaserBoss.Index) + 1); - for (int client = 1; client < MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - int buff = GetRandomInt(1, 4); - switch (buff) - { - case 1: - { - TF2_AddCondition(client, TFCond_CritOnFirstBlood, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, CRIT_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - TF2_AddCondition(client, TFCond_HalloweenQuickHeal, 5.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, UBER_ROLL, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 3: - { - TF2_AddCondition(client, TFCond_DefenseBuffed, 8.0 + GetChaserProfileHealTimeMax(slenderProfile) + g_SlenderAnimationDuration[chaserBoss.Index]); - EmitSoundToClient(client, MINICRIT_BUFF, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 4: - { - TF2_RegeneratePlayer(client); - } - } - } - } - } - NPCChaserSetBoxingRagePhase(chaserBoss.Index, NPCChaserGetBoxingRagePhase(chaserBoss.Index) + 1); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - g_SlenderStartFleeTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderFleeAnimationTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcHealCount[chaserBoss.Index] = 0; - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileRageThreeSounds(slenderProfile, soundInfo); - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageThree); - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_RageAll); - } - chaserBoss.SetAddSpeed(12.5); - chaserBoss.SetAddMaxSpeed(25.0); - chaserBoss.SetAddAcceleration(100.0); - } - } - } - if (g_IsSlenderAttacking[chaserBoss.Index]) - { - // Cancel attacking. - NPCClearAttackStats(chaserBoss.Index); - } - } - } - } - } - } - - if (!IsValidClient(target) && g_SlenderTeleportTargetIsCamping[chaserBoss.Index] && g_SlenderTeleportTarget[chaserBoss.Index] != INVALID_ENT_REFERENCE) //We spawned, and our target is a camper kill him! - { - int campingTarget = EntRefToEntIndex(g_SlenderTeleportTarget[chaserBoss.Index]); - if (MaxClients >= campingTarget > 0 && IsTargetValidForSlender(SF2_BasePlayer(campingTarget))) - { - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(campingTarget); - state = STATE_CHASE; - GetClientAbsOrigin(campingTarget, g_SlenderGoalPos[chaserBoss.Index]); - } - g_SlenderTeleportTargetIsCamping[chaserBoss.Index] = false; - } - - if (NPCChaserCanChaseOnLook(chaserBoss.Index) && g_NpcChaseOnLookTarget[chaserBoss.Index].Length > 0 && IsValidClient(target) && state != STATE_ATTACK && state != STATE_STUN) - { - g_SlenderIsAutoChasingLoudPlayer[chaserBoss.Index] = true; - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - if ((SF_IsRaidMap() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_BossesChaseEndlessly() || g_SlenderChasesEndlessly[chaserBoss.Index] || SF_IsSlaughterRunMap()) && state != STATE_ATTACK && state != STATE_STUN && IsValidClient(target) && !g_SlenderGiveUp[chaserBoss.Index]) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - if ((SF_IsRaidMap() || SF_IsBoxingMap()) && (g_NpcIsRunningToHeal[chaserBoss.Index] || g_NpcIsHealing[chaserBoss.Index])) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - state = STATE_CHASE; - } - if (chaserBoss.HasAttribute(SF2Attribute_ChaseTargetOnScare) && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN && IsValidClient(target) && g_PlayerScaredByBoss[target][chaserBoss.Index] && !g_NpcChasingScareVictin[chaserBoss.Index] && !g_NpcLostChasingScareVictim[chaserBoss.Index]) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_NpcChasingScareVictin[chaserBoss.Index] = true; - g_PlayerScaredByBoss[target][chaserBoss.Index] = false; - state = STATE_CHASE; - GetClientAbsOrigin(target, g_SlenderGoalPos[chaserBoss.Index]); - } - if (IsValidClient(bestNewTarget) && (playerInTrap[bestNewTarget] || playerMadeNoise[bestNewTarget]) && state != STATE_CHASE && state != STATE_ATTACK && state != STATE_STUN) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(target); - state = STATE_CHASE; - GetClientAbsOrigin(bestNewTarget, g_SlenderGoalPos[chaserBoss.Index]); - } - // Finally, set our new state. - g_SlenderState[chaserBoss.Index] = state; - - if (oldState != state) - { - g_BossPathFollower[chaserBoss.Index].Invalidate(); - - switch (state) - { - case STATE_IDLE, STATE_WANDER: - { - g_SlenderTarget[chaserBoss.Index] = INVALID_ENT_REFERENCE; - g_SlenderTimeUntilIdle[chaserBoss.Index] = 0.0; - g_SlenderTimeUntilAlert[chaserBoss.Index] = 0.0; - g_SlenderTimeUntilChase[chaserBoss.Index] = 0.0; - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - g_NpcLostChasingScareVictim[chaserBoss.Index] = false; - g_NpcChasingScareVictin[chaserBoss.Index] = false; - - if (oldState != STATE_IDLE && oldState != STATE_WANDER) - { - g_SlenderTargetSoundCount[chaserBoss.Index] = 0; - g_SlenderInvestigatingSound[chaserBoss.Index] = false; - g_SlenderTargetSoundDiscardMasterPosTime[chaserBoss.Index] = -1.0; - - g_SlenderTimeUntilKill[chaserBoss.Index] = gameTime + NPCGetIdleLifetime(chaserBoss.Index, difficulty); - } - - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], 0); - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - } - - case STATE_ALERT: - { - g_SlenderGiveUp[chaserBoss.Index] = false; - - g_SlenderChaseDeathPositionBool[chaserBoss.Index] = false; - - // Set our goal position. - if (g_SlenderInvestigatingSound[chaserBoss.Index]) - { - chaserBoss.SetGoalPos(g_SlenderTargetSoundMasterPos[chaserBoss.Index]); - } - - g_SlenderTimeUntilIdle[chaserBoss.Index] = gameTime + NPCChaserGetAlertDuration(chaserBoss.Index, difficulty); - g_SlenderTimeUntilAlert[chaserBoss.Index] = -1.0; - g_NpcLostChasingScareVictim[chaserBoss.Index] = false; - g_SlenderTimeUntilChase[chaserBoss.Index] = gameTime + NPCChaserGetAlertGracetime(chaserBoss.Index, difficulty); - - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], 0); - - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - } - case STATE_CHASE, STATE_ATTACK: - { - g_SlenderInvestigatingSound[chaserBoss.Index] = false; - g_SlenderTargetSoundCount[chaserBoss.Index] = 0; - - if (oldState != STATE_ATTACK && oldState != STATE_CHASE && oldState != STATE_STUN) - { - g_SlenderTimeUntilIdle[chaserBoss.Index] = -1.0; - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - g_SlenderAutoChaseCount[chaserBoss.Index] = 0; - g_SlenderTimeUntilChase[chaserBoss.Index] = -1.0; - - float persistencyTime = GetChaserProfileChasePersistencyTimeInit(slenderProfile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + persistencyTime; - } - } - - if (state == STATE_ATTACK) - { - if (NPCIsRaging(chaserBoss.Index)) - { - state = oldState; - } - else - { - g_IsSlenderAttacking[chaserBoss.Index] = true; - npcEntity.RemoveAllGestures(); - CBaseNPC_RemoveAllLayers(npcEntity.index); - loco.ClearStuckStatus(); - int attackIndex = NPCGetCurrentAttackIndex(chaserBoss.Index); - if (!NPCChaserGetAttackWhileRunningState(chaserBoss.Index, attackIndex, difficulty)) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) != SF2BossAttackType_ExplosiveDance && NPCChaserGetAttackType(chaserBoss.Index, attackIndex) != SF2BossAttackType_LaserBeam) - { - g_SlenderAttackTimer[chaserBoss.Index] = CreateTimer(NPCChaserGetAttackDamageDelay(chaserBoss.Index, attackIndex, difficulty), Timer_SlenderChaseBossAttack, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - g_NpcAlreadyAttacked[chaserBoss.Index] = false; - NpcSetCurrentAttackRepeat(chaserBoss.Index, attackIndex, 0); - NPCChaserSetNextAttackTime(chaserBoss.Index, attackIndex, gameTime + NPCChaserGetAttackCooldown(chaserBoss.Index, attackIndex, difficulty)); - } - else if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_ExplosiveDance) - { - g_SlenderAttackTimer[chaserBoss.Index] = CreateTimer(NPCChaserGetAttackDamageDelay(chaserBoss.Index, attackIndex, difficulty), Timer_SlenderPrepareExplosiveDance, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - NPCChaserSetNextAttackTime(chaserBoss.Index, attackIndex, gameTime + NPCChaserGetAttackCooldown(chaserBoss.Index, attackIndex, difficulty)); - } - else if (NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_LaserBeam) - { - g_SlenderAttackTimer[chaserBoss.Index] = CreateTimer(NPCChaserGetAttackDamageDelay(chaserBoss.Index, attackIndex, difficulty), Timer_SlenderChaseBossAttackBeginLaser, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - if (NPCChaserGetAttackLifeStealState(chaserBoss.Index, attackIndex, difficulty) && NPCChaserGetAttackType(chaserBoss.Index, attackIndex) == SF2BossAttackType_Melee) - { - if (!g_NpcStealingLife[chaserBoss.Index]) - { - g_NpcLifeStealTimer[chaserBoss.Index] = CreateTimer(0.5, Timer_SlenderStealLife, EntIndexToEntRef(npcEntity.index), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - g_NpcStealingLife[chaserBoss.Index] = true; - } - } - - g_SlenderTimeUntilAttackEnd[chaserBoss.Index] = gameTime + NPCChaserGetAttackDuration(chaserBoss.Index, attackIndex, difficulty) + 0.01; - - g_NpcBaseAttackRunDurationTime[chaserBoss.Index][attackIndex] = gameTime + NPCChaserGetAttackRunDuration(chaserBoss.Index, attackIndex, difficulty); - - g_NpcBaseAttackRunDelayTime[chaserBoss.Index][attackIndex] = gameTime + NPCChaserGetAttackRunDelay(chaserBoss.Index, attackIndex, difficulty); - - float persistencyTime = GetChaserProfileChaseAttackPersistencyTimeInit(slenderProfile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + persistencyTime; - } - - persistencyTime = GetChaserProfileChaseAttackPersistencyTimeAdd(slenderProfile); - if (persistencyTime >= 0.0) - { - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] < gameTime) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime; - } - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] += persistencyTime; - } - - if (g_NpcHasCloaked[chaserBoss.Index] && chaserBoss.CloakEnabled) - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], 0); - g_NpcHasCloaked[chaserBoss.Index] = false; - SlenderToggleParticleEffects(npcEntity.index, true); - GetChaserProfileCloakParticle(slenderProfile, cloakParticle, sizeof(cloakParticle)); - SlenderCreateParticle(chaserBoss.Index, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[chaserBoss.Index], npcEntity.index, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[chaserBoss.Index] = gameTime + NPCChaserGetCloakCooldown(chaserBoss.Index, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(chaserBoss.Index); - Call_Finish(); - } - SlenderPerformVoice(chaserBoss.Index, attackIndex, SF2BossSound_Attack); - if (g_LastStuckTime[chaserBoss.Index] != 0.0) - { - g_LastStuckTime[chaserBoss.Index] = gameTime; - } - loco.ClearStuckStatus(); - } - } - else - { - if (oldState != STATE_ATTACK && !building) - { - // Sound handling. - if (!NPCChaserCanChaseInitialOnStun(chaserBoss.Index)) - { - if (oldState != STATE_STUN) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0)) - { - g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; - g_BossForceAnimationUpdate[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - g_SlenderChaseInitialTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - else - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_ChaseInitial); - if (NPCChaserCanUseChaseInitialAnimation(chaserBoss.Index) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !SF_IsSlaughterRunMap()) - { - if (g_SlenderChaseInitialTimer[chaserBoss.Index] == null && (g_NpcChaseOnLookTarget[chaserBoss.Index] == null || g_NpcChaseOnLookTarget[chaserBoss.Index].Length <= 0)) - { - g_NpcUsesChaseInitialAnimation[chaserBoss.Index] = true; - g_BossForceAnimationUpdate[chaserBoss.Index] = true; - npc.flWalkSpeed = 0.0; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - g_SlenderChaseInitialTimer[chaserBoss.Index] = CreateTimer(g_SlenderAnimationDuration[chaserBoss.Index], Timer_SlenderChaseInitialTimer, EntIndexToEntRef(npcEntity.index), TIMER_FLAG_NO_MAPCHANGE); - } - } - } - } - } - } - } - NPCChaserSetState(chaserBoss.Index, state); - // Call our forward. - Call_StartForward(g_OnBossChangeStateFwd); - Call_PushCell(chaserBoss.Index); - Call_PushCell(oldState); - Call_PushCell(state); - Call_Finish(); - if (NPCChaserNormalSoundHookEnabled(chaserBoss.Index) && state != STATE_WANDER && - !NPCIsRaging(chaserBoss.Index) && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - if (state == STATE_CHASE) - { - SF2BossProfileSoundInfo soundInfo; - GetChaserProfileChaseInitialSounds(slenderProfile, soundInfo); - if (soundInfo.Paths == null || soundInfo.Paths.Length <= 0) - { - g_SlenderNextVoiceSound[chaserBoss.Index] = 0.0; - } - } - else - { - g_SlenderNextVoiceSound[chaserBoss.Index] = 0.0; - } - } - } - - if (oldState != state && !g_SlenderSpawning[chaserBoss.Index]) - { - if (g_BossForceAnimationUpdate[chaserBoss.Index]) - { - g_BossForceAnimationUpdate[chaserBoss.Index] = false; - } - else - { - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - } - } - - if (g_NpcChangeToCrawl[chaserBoss.Index] && (state == STATE_CHASE || state == STATE_WANDER || state == STATE_ALERT)) - { - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, state); - g_NpcChangeToCrawl[chaserBoss.Index] = false; - } - - if (NPCChaserOldAnimationAIEnabled(chaserBoss.Index)) - { - float slenderVelocity, returnFloat; - slenderVelocity = loco.GetGroundSpeed(); - returnFloat = (slenderVelocity / GetEntPropFloat(npcEntity.index, Prop_Data, "m_flGroundSpeed")) * g_NpcCurrentAnimationSequencePlaybackRate[chaserBoss.Index]; - if (GetEntPropFloat(npcEntity.index, Prop_Data, "m_flGroundSpeed") != 0.0 && GetEntPropFloat(npcEntity.index, Prop_Data, "m_flGroundSpeed") > 10.0) - { - if (loco.IsOnGround()) - { - if (state == STATE_WANDER && (chaserBoss.Flags & SFF_WANDERMOVE) || state == STATE_ALERT) - { - float playbackSpeed = returnFloat; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - if (state == STATE_CHASE && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - float playbackSpeed = returnFloat; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - } - } - else - { - float slenderVelocityOld; - float velocity, walkVelocity, generalVel; - slenderVelocityOld = loco.GetGroundSpeed(); - generalVel = slenderVelocityOld; - if (g_SlenderCalculatedSpeed[chaserBoss.Index] <= 0.0) - { - velocity = 0.0; - } - else - { - velocity = (generalVel + ((g_SlenderCalculatedSpeed[chaserBoss.Index] * g_RoundDifficultyModifier)/15))/g_SlenderCalculatedSpeed[chaserBoss.Index]; - } - - if (g_SlenderCalculatedWalkSpeed[chaserBoss.Index] <= 0.0) - { - walkVelocity = 0.0; - } - else - { - walkVelocity = (generalVel + ((g_SlenderCalculatedWalkSpeed[chaserBoss.Index] * g_RoundDifficultyModifier)/15))/g_SlenderCalculatedWalkSpeed[chaserBoss.Index]; - } - if (loco.IsOnGround()) - { - if (state == STATE_WANDER && (chaserBoss.Flags & SFF_WANDERMOVE) || state == STATE_ALERT) - { - float playbackSpeed = walkVelocity * g_NpcCurrentAnimationSequencePlaybackRate[chaserBoss.Index]; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - if (state == STATE_CHASE && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index] && !NPCIsRaging(chaserBoss.Index) && !g_NpcUsesHealAnimation[chaserBoss.Index] && !g_NpcUseStartFleeAnimation[chaserBoss.Index]) - { - float playbackSpeed = velocity * g_NpcCurrentAnimationSequencePlaybackRate[chaserBoss.Index]; - if (playbackSpeed > 12.0) - { - playbackSpeed = 12.0; - } - if (playbackSpeed < -4.0) - { - playbackSpeed = -4.0; - } - SetEntPropFloat(npcEntity.index, Prop_Send, "m_flPlaybackRate", playbackSpeed); - } - } - } - } - - switch (state) - { - case STATE_WANDER, STATE_ALERT, STATE_CHASE, STATE_ATTACK: - { - // These deal with movement, therefore we need to set our - // destination first. That is, if we don't have one. (nav mesh only) - - if (state == STATE_WANDER) - { - if (GetVectorSquareMagnitude(myPos, g_SlenderGoalPos[chaserBoss.Index]) <= SquareFloat(64.0)) - { - if (chaserBoss.Flags & SFF_WANDERMOVE) - { - if (!NPCGetWanderPosition(chaserBoss)) - { - g_BossPathFollower[chaserBoss.Index].Invalidate(); - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, STATE_IDLE); - } - } - } - } - else if (state == STATE_ALERT) - { - if (interruptConditions & COND_SAWENEMY) - { - if (IsValidEntity(bestNewTarget)) - { - if ((building) || (((playerInFOV[bestNewTarget] || playerNear[bestNewTarget]) && playerVisible[bestNewTarget]) || (playerMadeNoise[bestNewTarget] || playerInTrap[bestNewTarget]))) - { - // Constantly update my path if I see him. - if (gameTime >= g_SlenderNextPathTime[chaserBoss.Index]) - { - GetEntPropVector(bestNewTarget, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - g_SlenderNextPathTime[chaserBoss.Index] = gameTime + 0.4; - } - } - } - } - } - else if ((state == STATE_CHASE || state == STATE_ATTACK) && !g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index]) - { - if (IsValidEntity(bestNewTarget)) - { - oldTarget = target; - target = bestNewTarget; - g_SlenderTarget[chaserBoss.Index] = EntIndexToEntRef(bestNewTarget); - } - - if (target != INVALID_ENT_REFERENCE) - { - if (oldTarget != target) - { - // Brand new target! We need a path, and we need to reset our persistency, if needed. - float persistencyTime = GetChaserProfileChaseNewTargetPersistencyTimeInit(slenderProfile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + persistencyTime; - } - - persistencyTime = GetChaserProfileChaseNewTargetPersistencyTimeAdd(slenderProfile); - if (persistencyTime >= 0.0) - { - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] < gameTime) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime; - } - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] += persistencyTime; - } - - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - } - else if ((building) || ((playerInFOV[target] && playerVisible[target]) || gameTime <= g_SlenderTimeUntilNoPersistence[chaserBoss.Index])) - { - // Constantly update my path if I see him or if I'm still being persistent. - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - } - } - if (chaserBoss.GetTeleporter(0) != INVALID_ENT_REFERENCE) - { - int teleporter = EntRefToEntIndex(chaserBoss.GetTeleporter(0)); - if (IsValidEntity(teleporter) && teleporter > MaxClients) - { - GetEntPropVector(teleporter, Prop_Data, "m_vecAbsOrigin", g_SlenderGoalPos[chaserBoss.Index]); - } - } - } - if ((!g_SlenderInDeathcam[chaserBoss.Index] && (state == STATE_WANDER && (chaserBoss.Flags & SFF_WANDERMOVE) && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) - || (state == STATE_ALERT && g_SlenderCalculatedWalkSpeed[chaserBoss.Index] > 0.0) - || (state == STATE_CHASE && g_SlenderCalculatedSpeed[chaserBoss.Index] > 0.0) - || (state == STATE_ATTACK))) - { - g_BossPathFollower[chaserBoss.Index].ComputeToPos(bot, g_SlenderGoalPos[chaserBoss.Index]); - } - if (state == STATE_CHASE || state == STATE_ATTACK) - { - if (IsValidClient(target)) - { - #if defined DEBUG - SendDebugMessageToPlayer(target, DEBUG_BOSS_CHASE, 1, "g_SlenderTimeUntilAlert[%d]: %f\ng_SlenderTimeUntilNoPersistence[%d]: %f", chaserBoss.Index, g_SlenderTimeUntilAlert[chaserBoss.Index] - gameTime, chaserBoss.Index, g_SlenderTimeUntilNoPersistence[chaserBoss.Index] - gameTime); - #endif - - if ((building || (playerInFOV[target] && playerVisible[target])) && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index]) - { - float distRatio = (playerDists[target] / SquareFloat(NPCGetSearchRadius(chaserBoss.Index, difficulty))); - - float chaseDurationTimeAddMin = GetChaserProfileChaseDurationAddVisibleMin(slenderProfile); - float chaseDurationTimeAddMax = GetChaserProfileChaseDurationAddVisibleMax(slenderProfile); - - float chaseDurationAdd = chaseDurationTimeAddMax - ((chaseDurationTimeAddMax - chaseDurationTimeAddMin) * distRatio); - - if (chaseDurationAdd > 0.0) - { - g_SlenderTimeUntilAlert[chaserBoss.Index] += chaseDurationAdd; - if (g_SlenderTimeUntilAlert[chaserBoss.Index] > (gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty))) - { - g_SlenderTimeUntilAlert[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - } - } - - float persistencyTimeAddMin = GetChaserProfileChasePersistencyAddVisibleMin(slenderProfile); - float persistencyTimeAddMax = GetChaserProfileChasePersistencyAddVisibleMax(slenderProfile); - - float persistencyTimeAdd = persistencyTimeAddMax - ((persistencyTimeAddMax - persistencyTimeAddMin) * distRatio); - - if (persistencyTimeAdd > 0.0) - { - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] < gameTime) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime; - } - - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] += persistencyTimeAdd; - if (g_SlenderTimeUntilNoPersistence[chaserBoss.Index] > (gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty))) - { - g_SlenderTimeUntilNoPersistence[chaserBoss.Index] = gameTime + NPCChaserGetChaseDuration(chaserBoss.Index, difficulty); - } - } - } - } - } - } - } - - // Sound handling. - if (gameTime >= g_SlenderNextVoiceSound[chaserBoss.Index]) - { - switch (state) - { - case STATE_IDLE, STATE_WANDER: - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_Idle); - } - case STATE_ALERT: - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_Alert); - } - case STATE_CHASE: - { - if (!g_NpcIsRunningToHeal[chaserBoss.Index] && !g_NpcIsHealing[chaserBoss.Index] && !g_NpcUsesChaseInitialAnimation[chaserBoss.Index]) - { - SlenderPerformVoice(chaserBoss.Index, _, SF2BossSound_Chasing); - } - } - } - } - - if (gameTime >= g_SlenderNextFootstepSound[chaserBoss.Index]) - { - SlenderCastFootstep(chaserBoss.Index); - } - - // Reset our interrupt conditions. - g_SlenderInterruptConditions[chaserBoss.Index] = 0; - - return Plugin_Continue; -} - -Action Timer_SlenderPublicDeathCamThink(Handle timer, any entref) -{ - if (!g_Enabled) - { - return Plugin_Stop; - } - - int slender = EntRefToEntIndex(entref); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return Plugin_Stop; - } - - SF2NPC_Chaser chaserBoss = SF2NPC_Chaser(NPCGetFromEntIndex(slender)); - if (chaserBoss == SF2_INVALID_NPC_CHASER) - { - return Plugin_Stop; - } - - if (timer != g_SlenderEntityThink[chaserBoss.Index]) - { - return Plugin_Stop; - } - - if (!g_SlenderInDeathcam[chaserBoss.Index]) - { - return Plugin_Stop; - } - CBaseCombatCharacter npcEntity = CBaseCombatCharacter(slender); - int client = EntRefToEntIndex(g_SlenderDeathCamTarget[chaserBoss.Index]); - - bool attackWaiters = !!(chaserBoss.Flags & SFF_ATTACKWAITERS); - - if (!IsValidClient(client) || (IsValidClient(client) && (!IsPlayerAlive(client) || (GetClientTeam(client) == TFTeam_Blue && !attackWaiters)))) - { - if (g_SlenderDeathCamTimer[chaserBoss.Index] != null) - { - TriggerTimer(g_SlenderDeathCamTimer[chaserBoss.Index]); - } - else - { - if ((chaserBoss.Flags & SFF_FAKE)) - { - if (g_SlenderInDeathcam[chaserBoss.Index]) - { - g_SlenderInDeathcam[chaserBoss.Index] = false; - } - SlenderMarkAsFake(chaserBoss.Index); - } - else - { - SetEntityRenderMode(npcEntity.index, view_as(g_SlenderRenderMode[chaserBoss.Index])); - if (!NPCChaserIsCloaked(chaserBoss.Index)) - { - SetEntityRenderColor(npcEntity.index, g_SlenderRenderColor[chaserBoss.Index][0], g_SlenderRenderColor[chaserBoss.Index][1], g_SlenderRenderColor[chaserBoss.Index][2], g_SlenderRenderColor[chaserBoss.Index][3]); - } - if (g_SlenderEntityThink[chaserBoss.Index] != null) - { - KillTimer(g_SlenderEntityThink[chaserBoss.Index]); - } - g_SlenderEntityThink[chaserBoss.Index] = CreateTimer(BOSS_THINKRATE, Timer_SlenderChaseBossThink, EntIndexToEntRef(npcEntity.index), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderInDeathcam[chaserBoss.Index] = false; - NPCChaserUpdateBossAnimation(chaserBoss.Index, npcEntity.index, g_SlenderState[chaserBoss.Index]); - } - } - } - - return Plugin_Continue; -} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp deleted file mode 100644 index 26e8b3e6..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_pathing.sp +++ /dev/null @@ -1,895 +0,0 @@ -#if defined _sf2_npc_chaser_pathing_included - #endinput -#endif -#define _sf2_npc_chaser_pathing_included - -#pragma semicolon 1 - -static float g_NpcEstimatedYaw[MAX_BOSSES] = { 0.0, ... }; - -void SlenderChaseBossProcessMovement(int bossEnt) -{ - if (!g_Enabled) - { - return; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(bossEnt); - if (npc == INVALID_NPC) - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderChaseBossProcessMovement); //What no boss? - return; - } - - int bossIndex = NPCGetFromEntIndex(bossEnt); - if (bossIndex == -1) - { - //Boss is invalid somehow, and the hook wasn't killed. - SDKUnhook(bossEnt, SDKHook_Think, SlenderChaseBossProcessMovement); - return; - } - - INextBot bot = npc.GetBot(); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - CBaseCombatCharacter combatChar = CBaseCombatCharacter(bossEnt); - - char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, slenderProfile, sizeof(slenderProfile)); - - float myPos[3], myEyeAng[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", myPos); - GetEntPropVector(bossEnt, Prop_Data, "m_angAbsRotation", myEyeAng); - - int state = g_SlenderState[bossIndex]; - int oldState = g_SlenderOldState[bossIndex]; - g_SlenderOldState[bossIndex] = state; - - float gameTime = GetGameTime(); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - if (!g_SlenderInDeathcam[bossIndex]) - { - switch (state) - { - case STATE_WANDER, STATE_ALERT: - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; - npc.flRunSpeed = g_SlenderCalculatedWalkSpeed[bossIndex]; - } - loco.SetSpeedLimit(g_SlenderCalculatedMaxWalkSpeed[bossIndex]); - } - case STATE_CHASE: - { - if (!g_NpcUsesChaseInitialAnimation[bossIndex] && !NPCIsRaging(bossIndex) - && !g_NpcUsesHealAnimation[bossIndex] && !g_NpcUseStartFleeAnimation[bossIndex]) - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = g_SlenderCalculatedSpeed[bossIndex]; - npc.flRunSpeed = g_SlenderCalculatedSpeed[bossIndex]; - } - } - else - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - loco.SetSpeedLimit(g_SlenderCalculatedMaxSpeed[bossIndex]); - } - case STATE_ATTACK: - { - int currentAttackIndex = NPCGetCurrentAttackIndex(bossIndex); - if (NPCChaserGetAttackWhileRunningState(bossIndex, currentAttackIndex, difficulty) && - !NPCIsRaging(bossIndex) && gameTime >= g_NpcBaseAttackRunDelayTime[bossIndex][currentAttackIndex]) - { - if (NPCChaserGetAttackRunDuration(bossIndex, currentAttackIndex, difficulty) > 0.0) - { - if (gameTime < g_NpcBaseAttackRunDurationTime[bossIndex][currentAttackIndex]) - { - float attackSpeed, originalSpeed; - originalSpeed = NPCChaserGetAttackRunSpeed(bossIndex, currentAttackIndex, difficulty); - if (g_RoundDifficultyModifier > 1.0) - { - attackSpeed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - } - else - { - attackSpeed = originalSpeed; - } - if (npc != INVALID_NPC) - { - attackSpeed *= g_SlenderSpeedMultiplier[bossIndex]; - float groundSpeed = GetEntPropFloat(bossEnt, Prop_Data, "m_flGroundSpeed") * g_SlenderSpeedMultiplier[bossIndex]; - if (groundSpeed > 0.0 && attackSpeed < groundSpeed) - { - npc.flWalkSpeed = groundSpeed; - npc.flRunSpeed = groundSpeed; - } - else - { - npc.flWalkSpeed = attackSpeed; - npc.flRunSpeed = attackSpeed; - } - } - } - else - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - } - else - { - float attackSpeed, originalSpeed; - originalSpeed = NPCChaserGetAttackRunSpeed(bossIndex, currentAttackIndex, difficulty); - - if (g_RoundDifficultyModifier > 1.0) - { - attackSpeed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - } - else - { - attackSpeed = originalSpeed; - } - - if (npc != INVALID_NPC) - { - attackSpeed *= g_SlenderSpeedMultiplier[bossIndex]; - float groundSpeed = GetEntPropFloat(bossEnt, Prop_Data, "m_flGroundSpeed") * g_SlenderSpeedMultiplier[bossIndex]; - if (groundSpeed > 0.0 && attackSpeed < groundSpeed) - { - npc.flWalkSpeed = groundSpeed; - npc.flRunSpeed = groundSpeed; - } - else - { - npc.flWalkSpeed = attackSpeed; - npc.flRunSpeed = attackSpeed; - } - } - } - } - else - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - loco.SetSpeedLimit(999999.9); - } - case STATE_STUN: - { - if (npc != INVALID_NPC) - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - } - } - } - else - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - if (state == STATE_ATTACK) - { - int currentAttackIndex = NPCGetCurrentAttackIndex(bossIndex); - if (NPCChaserGetAttackWhileRunningState(bossIndex, currentAttackIndex, difficulty)) - { - npc.flAcceleration = 99999.9; - } - else - { - npc.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; - } - } - else - { - npc.flAcceleration = g_SlenderCalculatedAcceleration[bossIndex]; - } - - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - - // Process angles. - bool changeAngle = false; - float posToAt[3]; - if (state != STATE_STUN && !g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) - { - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - - if (g_NpcHasAlwaysLookAtTarget[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - else - { - switch (state) - { - case STATE_CHASE: - { - bool canSeeTarget = false; - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(100.0)) - { - TR_TraceRayFilter(myEyePos, targetPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharacters, bossEnt); - canSeeTarget = !TR_DidHit(); - } - } - - if (canSeeTarget) - { - canSeeTarget = NPCShouldSeeEntity(bossIndex, target); - } - - if (g_NpcHasAlwaysLookAtTargetWhileChasing[bossIndex] || canSeeTarget) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - - } - } - - if (g_NpcUsesChaseInitialAnimation[bossIndex] || NPCIsRaging(bossIndex) || g_NpcUseStartFleeAnimation[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!loco.IsOnGround() || loco.IsClimbingOrJumping()) - { - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(400.0)) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - case STATE_ATTACK: - { - if (g_NpcHasAlwaysLookAtTargetWhileAttacking[bossIndex] && !NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && ((NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_LaserBeam))) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && (NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_LaserBeam)) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - } - } - - if (changeAngle) - { - loco.FaceTowards(posToAt); - } - - if (!g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) - { - // Process our desired speed. - bool pfUpdate = false; - switch (state) - { - case STATE_WANDER: - { - if ((NPCGetFlags(bossIndex) & SFF_WANDERMOVE)) - { - pfUpdate = true; - loco.Run(); - } - } - case STATE_ALERT: - { - pfUpdate = true; - loco.Run(); - } - case STATE_CHASE: - { - pfUpdate = true; - loco.Run(); - } - case STATE_ATTACK: - { - if (NPCChaserGetAttackWhileRunningState(bossIndex, attackIndex, difficulty)) - { - pfUpdate = true; - loco.Run(); - } - } - } - if (pfUpdate) - { - g_BossPathFollower[bossIndex].Update(bot); - } - else - { - loco.Stop(); - } - if (state != oldState) - { - bot.Update(); - } - if (!SF_IsBoxingMap() && loco.IsOnGround() && !loco.IsClimbingOrJumping() && (state == STATE_ALERT || state == STATE_CHASE || state == STATE_WANDER || state == STATE_ATTACK) && - !g_NpcUsesChaseInitialAnimation[bossIndex] && !NPCIsRaging(bossIndex) && - !g_NpcUsesCloakStartAnimation[bossIndex] && !g_NpcUsesCloakEndAnimation[bossIndex] && !g_NpcUseStartFleeAnimation[bossIndex]) - { - float pathNodePos[3], pathEndPos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetForward(pathNodePos); - segment.GetPos(pathEndPos); - ScaleVector(pathNodePos, segment.length); - AddVectors(pathEndPos, pathNodePos, pathEndPos); - - if (g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, CLIMB_UP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, JUMP_OVER_GAP, 120.0)|| g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, LADDER_UP, 120.0)) - { - CBaseNPC_Jump(bossEnt, loco, myPos, pathEndPos); - } - } - } - } - - if (!loco.IsClimbingOrJumping() && !g_SlenderSpawning[bossIndex]) - { - float hullCheckMins[3], hullCheckMaxs[3]; - if (NPCGetRaidHitbox(bossIndex) == 1) - { - hullCheckMins = g_SlenderDetectMins[bossIndex]; - hullCheckMaxs = g_SlenderDetectMaxs[bossIndex]; - } - else if (NPCGetRaidHitbox(bossIndex) == 0) - { - hullCheckMins = HULL_HUMAN_MINS; - hullCheckMaxs = HULL_HUMAN_MAXS; - } - hullCheckMins[0] -= 20.0; - hullCheckMins[1] -= 20.0; - - hullCheckMaxs[0] += 20.0; - hullCheckMaxs[1] += 20.0; - - hullCheckMins[2] += loco.GetStepHeight(); - hullCheckMaxs[2] += 5.0; - - if (!g_NpcVelocityCancel[bossIndex] && IsSpaceOccupiedIgnorePlayersAndEnts(myPos, hullCheckMins, hullCheckMaxs, bossEnt))//The boss will start to merge with shits, cancel out velocity. - { - float origin[3]; - loco.SetVelocity(origin); - g_NpcVelocityCancel[bossIndex] = true; - } - } - else - g_NpcVelocityCancel[bossIndex] = false; - - if (NPCChaserCanCrawl(bossIndex) && !g_SlenderSpawning[bossIndex] && - state != STATE_ATTACK && state != STATE_STUN && state != STATE_IDLE && loco.IsOnGround() && !loco.IsClimbingOrJumping()) - { - float crawlDetectMins[3], crawlDetectMaxs[3]; - crawlDetectMins = g_NpcCrawlDetectMins[bossIndex]; - crawlDetectMaxs = g_NpcCrawlDetectMaxs[bossIndex]; - - if (IsSpaceOccupiedIgnorePlayersAndEnts(myPos, crawlDetectMins, crawlDetectMaxs, bossEnt) && !g_NpcIsCrawling[bossIndex]) - { - NPCChaserUpdateBossAnimation(bossIndex, bossEnt, g_SlenderState[bossIndex]); - g_NpcIsCrawling[bossIndex] = true; - g_NpcChangeToCrawl[bossIndex] = true; - } - if (!IsSpaceOccupiedIgnorePlayersAndEnts(myPos, crawlDetectMins, crawlDetectMaxs, bossEnt) && g_NpcIsCrawling[bossIndex]) - { - NPCChaserUpdateBossAnimation(bossIndex, bossEnt, g_SlenderState[bossIndex]); - g_NpcIsCrawling[bossIndex] = false; - g_NpcChangeToCrawl[bossIndex] = true; - } - } - - if ((state == STATE_CHASE || state == STATE_ALERT || state == STATE_WANDER) && !g_SlenderInDeathcam[bossIndex]) - { - int pitch = combatChar.LookupPoseParameter("body_pitch"); - int yaw = combatChar.LookupPoseParameter("body_yaw"); - float dir[3], ang[3], npcCenter[3], lookPos[3]; - combatChar.WorldSpaceCenter(npcCenter); - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - if (target && target != INVALID_ENT_REFERENCE) - { - GetClientEyePosition(target, lookPos); - lookPos[2] -= 20.0; - } - else - { - CopyVector(g_SlenderGoalPos[bossIndex], lookPos); - } - SubtractVectors(npcCenter, lookPos, dir); - NormalizeVector(dir, dir); - GetVectorAngles(dir, ang); - - float pitchPose = combatChar.GetPoseParameter(pitch); - float yawPose = combatChar.GetPoseParameter(yaw); - - ang[0] = UTIL_Clamp(UTIL_AngleNormalize(ang[0]), -44.0, 89.0); - combatChar.SetPoseParameter(pitch, UTIL_ApproachAngle(state == STATE_CHASE && g_SlenderTargetIsVisible[bossIndex] ? ang[0] : 0.0, pitchPose, (NPCGetTurnRate(bossIndex)/1000.0) * 16.0)); - ang[1] = UTIL_Clamp(-UTIL_AngleNormalize(UTIL_AngleDiff(UTIL_AngleNormalize(ang[1]), UTIL_AngleNormalize(myEyeAng[1]+180.0))), -44.0, 44.0); - combatChar.SetPoseParameter(yaw, UTIL_ApproachAngle(state == STATE_CHASE && g_SlenderTargetIsVisible[bossIndex] ? ang[1] : 0.0, yawPose, (NPCGetTurnRate(bossIndex)/1000.0) * 16.0)); - - int moveX = combatChar.LookupPoseParameter("move_x"); - int moveY = combatChar.LookupPoseParameter("move_y"); - - float groundSpeed = loco.GetGroundSpeed(); - if (moveX > 0 || moveY > 0) - { - if (groundSpeed != 0.0) - { - float forwardVector[3], rightVector[3], upVector[3], motionVector[3]; - combatChar.GetVectors(forwardVector, rightVector, upVector); - loco.GetGroundMotionVector(motionVector); - - if (moveX >= 0) - { - combatChar.SetPoseParameter(moveX, GetVectorDotProduct(motionVector,forwardVector)); - } - if (moveY >= 0) - { - combatChar.SetPoseParameter(moveY, GetVectorDotProduct(motionVector,rightVector)); - } - } - else - { - if (moveX >= 0) - { - combatChar.SetPoseParameter(moveX, 0.0); - } - if (moveY >= 0) - { - combatChar.SetPoseParameter(moveY, 0.0); - } - } - float _groundSpeed = GetEntPropFloat(bossEnt, Prop_Data, "m_flGroundSpeed"); - if (_groundSpeed != 0.0 && loco.IsOnGround()) - { - float returnValue = (groundSpeed / _groundSpeed) * g_NpcCurrentAnimationSequencePlaybackRate[bossIndex]; - if (returnValue < -4.0) - { - returnValue = -4.0; - } - if (returnValue > 12.0) - { - returnValue = 12.0; - } - - SetEntPropFloat(bossEnt, Prop_Send, "m_flPlaybackRate", returnValue); - } - } - - int moveScale = combatChar.LookupPoseParameter("move_scale"); - int moveYaw = combatChar.LookupPoseParameter("move_yaw"); - - float fwd[3], right[3], up[3]; - combatChar.GetVectors(fwd, right, up); - - float motionVector[3]; - loco.GetGroundMotionVector(motionVector); - - if (moveScale > 0) - { - float moveSpeed = loco.GetGroundSpeed(); - float scale = npc.flRunSpeed / moveSpeed; - combatChar.SetPoseParameter(moveScale, scale); - } - - if (moveYaw > 0) - { - float deltaTime = GetGameFrameTime(); - if (deltaTime > 0.0) - { - float myAngle[3], normalAngle, estimateYaw; - estimateYaw = g_NpcEstimatedYaw[bossIndex]; - combatChar.GetLocalAngles(myAngle); - - if (motionVector[0] == 0.0 && motionVector[1] == 0.0) - { - float yawDelta = myAngle[1] - estimateYaw; - yawDelta = AngleNormalize(yawDelta); - - if (deltaTime < 0.25) - { - yawDelta *= (deltaTime * 4.0); - } - else - { - yawDelta *= deltaTime; - } - - estimateYaw += yawDelta; - estimateYaw = AngleNormalize(estimateYaw); - } - else - { - estimateYaw = (ArcTangent2(motionVector[1], motionVector[0]) * 180.0 / 3.14159); - estimateYaw = FloatClamp(estimateYaw, -180.0, 180.0); - } - normalAngle = AngleNormalize(myAngle[1]); - g_NpcEstimatedYaw[bossIndex] = estimateYaw; - float actualYaw = normalAngle - estimateYaw; - actualYaw = AngleNormalize(-actualYaw); - - combatChar.SetPoseParameter(moveYaw, actualYaw); - } - } - } - - if (state != STATE_IDLE && state != STATE_STUN && state != STATE_ATTACK && !g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) - { - bool runUnstuck = (state == STATE_CHASE && g_SlenderCalculatedSpeed[bossIndex] > 0.0); - if (!runUnstuck) - { - runUnstuck = (state == STATE_ALERT && g_SlenderCalculatedWalkSpeed[bossIndex] > 0.0); - } - if (!runUnstuck) - { - runUnstuck = (state == STATE_WANDER && (NPCGetFlags(bossIndex) & SFF_WANDERMOVE) && g_SlenderCalculatedWalkSpeed[bossIndex] > 0.0 && - difficulty >= RoundToNearest(NPCGetAttributeValue(bossIndex, SF2Attribute_BlockWalkSpeedUnderDifficulty))); - } - if (runUnstuck) - { - if (loco.GetGroundSpeed() <= 0.1 || GetVectorSquareMagnitude(myPos, g_LastPos[bossIndex]) <= 0.13 || loco.IsStuck()) - { - bool blockingProp = false; - - if (NPCGetFlags(bossIndex) & SFF_ATTACKPROPS) - { - for (int attackIndex2 = 0; attackIndex2 < NPCChaserGetAttackCount(bossIndex); attackIndex2++) - { - if (NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_LaserBeam) - { - continue; - } - blockingProp = NPC_CanAttackProps(bossIndex,NPCChaserGetAttackRange(bossIndex, attackIndex2, difficulty), NPCChaserGetAttackSpread(bossIndex, attackIndex2, difficulty)); - if (blockingProp) - { - break; - } - } - } - if (!blockingProp) - { - if (g_LastStuckTime[bossIndex] == 0.0) - { - g_LastStuckTime[bossIndex] = gameTime; - } - - if ((g_LastStuckTime[bossIndex] <= gameTime - 1.0 || loco.GetStuckDuration() >= 1.0) && - !g_NpcIsRunningToHeal[bossIndex] && !g_NpcIsHealing[bossIndex]) - { - float destination[3]; - CNavArea area = TheNavMesh.GetNearestNavArea(g_LastPos[bossIndex], _, _, _, false); - area.GetCenter(destination); - float tempMaxs[3]; - npc.GetBodyMaxs(tempMaxs); - float traceMins[3]; - traceMins[0] = g_SlenderDetectMins[bossIndex][0] - 5.0; - traceMins[1] = g_SlenderDetectMins[bossIndex][1] - 5.0; - traceMins[2] = 0.0; - - float traceMaxs[3]; - traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0] + 5.0; - traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1] + 5.0; - traceMaxs[2] = tempMaxs[2]; - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (GetVectorSquareMagnitude(destination, g_LastPos[bossIndex]) <= SquareFloat(16.0) || TR_DidHit()) - { - CursorData cursor = g_BossPathFollower[bossIndex].GetCursorData(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - if (collector.Get(i).GetCostSoFar() < 16.0) - { - continue; - } - if (cursor.segmentPrior != NULL_PATH_SEGMENT) - { - CNavArea segmentArea = cursor.segmentPrior.area; - if (segmentArea == collector.Get(i)) - { - continue; - } - } - float navPos[3]; - collector.Get(i).GetCenter(navPos); - if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) - { - continue; - } - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - int randomArea = 0, randomCell = 0; - areaArray.Resize(validAreaCount); - area = NULL_AREA; - while (validAreaCount > 1) - { - randomCell = GetRandomInt(0, validAreaCount - 1); - randomArea = areaArray.Get(randomCell); - area = collector.Get(randomArea); - area.GetCenter(destination); - - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (TR_DidHit()) - { - area = NULL_AREA; - validAreaCount--; - int findValue = areaArray.FindValue(randomCell); - if (findValue != -1) - { - areaArray.Erase(findValue); - } - } - else - { - break; - } - } - - delete collector; - delete areaArray; - } - g_BossPathFollower[bossIndex].GetClosestPosition(destination, destination, g_BossPathFollower[bossIndex].FirstSegment(), 128.0); - TeleportEntity(bossEnt, destination, NULL_VECTOR, NULL_VECTOR); - - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = gameTime; - } - } - } - else - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] += 0.03; - if (g_LastStuckTime[bossIndex] > gameTime) - { - g_LastStuckTime[bossIndex] = gameTime; - } - g_LastPos[bossIndex] = myPos; - } - } - } - else - { - g_LastStuckTime[bossIndex] = gameTime; - } - - if (gameTime >= g_SlenderNextFootstepSound[bossIndex]) - { - SlenderCastFootstep(bossIndex); - } - - return; -} - -void SlenderSetNextThink(int bossEnt) -{ - if (!g_Enabled) - { - return; - } - - CBaseCombatCharacter(bossEnt).SetNextThink(GetGameTime()); - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(bossEnt); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - int bossIndex = NPCGetFromEntIndex(bossEnt); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - //From Pelipoika's rainbow outline plugin - if (NPCGetCustomOutlinesState(bossIndex) && NPCGetRainbowOutlineState(bossIndex)) - { - int glow = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - int color[4]; - color[0] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 0) * 127.5 + 127.5); - color[1] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 2) * 127.5 + 127.5); - color[2] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 4) * 127.5 + 127.5); - color[3] = 255; - if (IsValidEntity(glow)) - { - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - } - } - - int state = g_SlenderState[bossIndex]; - bool changeAngle = false; - float posToAt[3]; - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - if (state != STATE_STUN && !g_SlenderSpawning[bossIndex]) - { - int target = EntRefToEntIndex(g_SlenderTarget[bossIndex]); - - if (g_NpcHasAlwaysLookAtTarget[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - else - { - switch (state) - { - case STATE_CHASE: - { - bool canSeeTarget = false; - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(100.0)) - { - TR_TraceRayFilter(myEyePos, targetPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitCharacters, bossEnt); - canSeeTarget = !TR_DidHit(); - } - } - - if (canSeeTarget) - { - canSeeTarget = NPCShouldSeeEntity(bossIndex, target); - } - - if (g_NpcHasAlwaysLookAtTargetWhileChasing[bossIndex] || canSeeTarget) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - - } - } - - if (g_NpcUsesChaseInitialAnimation[bossIndex] || NPCIsRaging(bossIndex) || g_NpcUseStartFleeAnimation[bossIndex]) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!loco.IsOnGround() || loco.IsClimbingOrJumping()) - { - if (target && target != INVALID_ENT_REFERENCE) - { - float targetPos[3], myEyePos[3]; - NPCGetEyePosition(bossIndex, myEyePos); - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", targetPos); - targetPos[2] += 18.0; - if (GetVectorSquareMagnitude(targetPos, myEyePos) <= SquareFloat(400.0)) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - case STATE_ATTACK: - { - if (g_NpcHasAlwaysLookAtTargetWhileAttacking[bossIndex] && !NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && ((NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) != SF2BossAttackType_LaserBeam))) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - if (!NPCChaserGetAttackIgnoreAlwaysLooking(bossIndex, attackIndex, difficulty) && (NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Ranged || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_Projectile || NPCChaserGetAttackType(bossIndex, attackIndex) == SF2BossAttackType_LaserBeam)) - { - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } - } - } - } - } - - if (changeAngle) - { - loco.FaceTowards(posToAt); - } - - } - - return; -} - -void CBaseNPC_Jump(int entity, NextBotGroundLocomotion nextbotLocomotion, float startPos[3], float endPos[3]) -{ - float jumpVel[3]; - startPos[2] += nextbotLocomotion.GetStepHeight(); - float actualHeight = endPos[2] - startPos[2]; - float height = actualHeight; - if (height < 16.0) - { - height = 16.0; - } - - float additionalHeight = 20.0; - if (height < 32.0) - { - additionalHeight += 16.0; - } - float gravity = nextbotLocomotion.GetGravity(); - - height += additionalHeight; - - float speed = SquareRoot(2.0 * gravity * height); - float time = (speed / gravity); - - time += SquareRoot((2.0 * additionalHeight) / gravity); - - SubtractVectors(endPos, startPos, jumpVel); - jumpVel[0] /= time; - jumpVel[1] /= time; - jumpVel[2] /= time; - - jumpVel[2] = speed; - - float jumpSpeed = GetVectorLength(jumpVel, true); - float maxSpeed = SquareFloat(650.0); - if (jumpSpeed > maxSpeed) - { - jumpVel[0] *= (maxSpeed / jumpSpeed); - jumpVel[1] *= (maxSpeed / jumpSpeed); - jumpVel[2] *= (maxSpeed / jumpSpeed); - } - - CBaseEntity(entity).SetLocalOrigin(startPos); - nextbotLocomotion.Jump(); - nextbotLocomotion.SetVelocity(jumpVel); -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp deleted file mode 100644 index a5ace8a2..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_projectiles.sp +++ /dev/null @@ -1,1107 +0,0 @@ -#if defined _sf2_npc_chaser_projectiles_included - #endinput -#endif -#define _sf2_npc_chaser_projectiles_included - -#pragma semicolon 1 - -static char gestureShootAnim[PLATFORM_MAX_PATH]; -static char baseballModel[PLATFORM_MAX_PATH]; - -//static float g_projectileSpeed[2049]; -//static float g_projectileDamage[2049]; -//static float g_projectileRadius[2049]; -//static float g_projectileIceSlowPercent[2049]; -//static float g_projectileIceSlowDuration[2049]; -//static float g_flOriginalProjectilePos[2049][3]; -//static int g_projectileType[2049]; -//static int g_projectileRotateState[2049]; // 0 = 0 degrees, 1 = 1-180 degrees, 2 = 181-360 degrees -//static Handle g_hProjectileTimer[2049]; - -/*public Action Timer_ProjectileThink(Handle timer, any entref) -{ - int projectile = EntRefToEntIndex(entref); - if (!projectile || projectile == INVALID_ENT_REFERENCE) return Plugin_Stop; - - if (timer != g_hProjectileTimer[projectile]) return Plugin_Stop; - - float flPos[3], flAng[3], flNewPos[3]; - GetEntPropVector(projectile, Prop_Data, "m_vecAbsOrigin", flPos); - GetEntPropVector(projectile, Prop_Data, "m_angAbsRotation", flAng); - - flNewPos[0] = flPos[0]; - flNewPos[1] = flPos[1]; - flNewPos[2] = flPos[2]; - - float flSpeed = (g_projectileSpeed[projectile] - 64.0) / 54.0; - AddVectors(g_flOriginalProjectilePos[projectile], flNewPos, flNewPos); - flNewPos[0] += g_flOriginalProjectilePos[projectile][0]; - flNewPos[1] += g_flOriginalProjectilePos[projectile][1]; - switch (g_projectileRotateState[projectile]) - { - case 0: flNewPos[2] = g_flOriginalProjectilePos[projectile][2]; - case 1: flNewPos[2] += g_flOriginalProjectilePos[projectile][2]/flAng[0]; - case 2: flNewPos[2] -= g_flOriginalProjectilePos[projectile][2]/flAng[0]; - } - - TeleportEntity(projectile, flNewPos, flAng, NULL_VECTOR); - - return Plugin_Continue; -}*/ - -int NPCChaserProjectileShoot(int bossIndex, int slender, int target, const char[] slenderProfile, float myPos[3]) -{ - if (g_RestartSessionEnabled) - { - return -1; - } - - CBaseCombatCharacter combatChar = CBaseCombatCharacter(slender); - - int projectileType = NPCChaserGetProjectileType(bossIndex); - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int projectileEnt; - float shootDist = GetVectorSquareMagnitude(g_SlenderGoalPos[bossIndex], myPos); - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char projectileName[45]; - float clientPos[3]; - float slenderPosition[3]; - float nonRocketScaleMins[3], nonRocketScaleMaxs[3]; - nonRocketScaleMins[0] = -10.0; - nonRocketScaleMins[1] = -10.0; - nonRocketScaleMins[2] = -10.0; - nonRocketScaleMaxs[0] = 10.0; - nonRocketScaleMaxs[1] = 10.0; - nonRocketScaleMaxs[2] = 10.0; - NPCGetEyePosition(bossIndex, slenderPosition); - GetClientEyePosition(target, clientPos); - switch (projectileType) - { - case SF2BossProjectileType_Grenade: - { - if (shootDist < SquareFloat(600.0)) - { - clientPos[2] += 0.0; - } - else if (shootDist > SquareFloat(600.0) && shootDist < SquareFloat(1000.0)) - { - clientPos[2] += 60.0; - } - else if (shootDist > SquareFloat(1000.0)) - { - clientPos[2] += 120.0; - } - } - case SF2BossProjectileType_Arrow, SF2BossProjectileType_Baseball: - { - clientPos[2] -= 0.0; - } - default: - { - clientPos[2] -= 25.0; - } - } - - float basePos[3], baseAng[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - - float effectPos[3], tempEffectPos[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - - int randomPosMin = GetChaserProfileRandomProjectilePosMin(profile); - int randomPosMax = GetChaserProfileRandomProjectilePosMax(profile); - ArrayList posArray = GetChaserProfileProjectilePositionsArray(profile); - - if (randomPosMin == randomPosMax) - { - posArray.GetArray(0, tempEffectPos); - } - else - { - int randomProjectilePos = GetRandomInt(randomPosMin, randomPosMax); - posArray.GetArray(randomProjectilePos, tempEffectPos); - } - - posArray = null; - - VectorTransform(tempEffectPos, basePos, baseAng, tempEffectPos); - AddVectors(effectAng, baseAng, effectAng); - - float spread = NPCChaserGetProjectileDeviation(bossIndex, difficulty); - - int team = 3; - int teamNon = 5; - - float min = NPCChaserGetProjectileCooldownMin(bossIndex, difficulty); - float max = NPCChaserGetProjectileCooldownMax(bossIndex, difficulty); - - for (int i = 0; i < NPCChaserGetProjectileCount(bossIndex, difficulty); i++) - { - if (NPCChaserGetProjectileCount(bossIndex, difficulty) != 1) - { - effectPos[0] = tempEffectPos[0] + GetRandomFloat(-10.0, 10.0); - effectPos[1] = tempEffectPos[1] + GetRandomFloat(-10.0, 10.0); - effectPos[2] = tempEffectPos[2] + GetRandomFloat(-10.0, 10.0); - } - else - { - effectPos[0] = tempEffectPos[0]; - effectPos[1] = tempEffectPos[1]; - effectPos[2] = tempEffectPos[2]; - } - float shootDirection[3], shootAng[3]; - SubtractVectors(clientPos, effectPos, shootDirection); - if (spread != 0.0) - { - shootDirection[0] += GetRandomFloat(-spread, spread); - shootDirection[1] += GetRandomFloat(-spread, spread); - shootDirection[2] += GetRandomFloat(-spread, spread); - } - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - switch (projectileType) - { - case SF2BossProjectileType_Fireball: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - AttachParticle(projectileEnt, g_SlenderFireballTrail[bossIndex]); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flModelScale", 1.0); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage to nothing - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_FIREBALL); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderFireballShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Iceball: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - AttachParticle(projectileEnt, g_SlenderIceballTrail[bossIndex]); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flModelScale", 1.0); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage to nothing - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_ICEBALL); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderFireballShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Rocket: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, NPCChaserGetProjectileDamage(bossIndex, difficulty), true); // set damage - ProjectileSetFlags(projectileEnt, PROJ_ROCKET); - SetEntityModel(projectileEnt, g_SlenderRocketModel[bossIndex]); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderRocketShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - /*case SF2BossProjectileType_Rocket: - { - projectileName = "prop_dynamic_override"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - g_projectileSpeed[projectileEnt] = NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - //if (NPCChaserHasCriticalRockets(bossIndex)) SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - //SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - g_hProjectileTimer[projectileEnt] = CreateTimer(BOSS_THINKRATE, Timer_ProjectileThink, EntIndexToEntRef(projectileEnt), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - ProjectileSetFlags(projectileEnt, PROJ_ROCKET); - SetEntityModel(projectileEnt, ROCKET_MODEL); - - //SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - GetEntPropVector(projectileEnt, Prop_Data, "m_vecOrigin", g_flOriginalProjectilePos[projectileEnt]); - g_flOriginalProjectilePos[projectileEnt][0] /= (g_projectileSpeed[projectileEnt] - 64.0) / 54.0; - g_flOriginalProjectilePos[projectileEnt][1] /= (g_projectileSpeed[projectileEnt] - 64.0) / 54.0; - g_flOriginalProjectilePos[projectileEnt][2] /= (g_projectileSpeed[projectileEnt] - 64.0) / 54.0; - PrintToChatAll("%f", shootAng[0]); - if (shootAng[0] == 0.0 || shootAng[0] == 180.0) - g_projectileRotateState[projectileEnt] = 0; - else if (shootAng[0] > 0.0 && shootAng[0] < 180.0) - g_projectileRotateState[projectileEnt] = 1; - else if (shootAng[0] > 180.0) - g_projectileRotateState[projectileEnt] = 2; - - CreateTimer(1.0, Timer_KillEntity, EntIndexToEntRef(projectileEnt), TIMER_FLAG_NO_MAPCHANGE); - - if (NPCChaserUseShootGesture(bossIndex)) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderRocketShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - }*/ - case SF2BossProjectileType_Grenade: - { - projectileName = "tf_projectile_pipe"; - if (shootDist <= SquareFloat(1800.0)) - { - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - TeleportEntity(projectileEnt, effectPos, shootAng, NULL_VECTOR); - DispatchSpawn(projectileEnt); - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", 3, 1); - SetEntProp(projectileEnt, Prop_Send, "m_nSkin", 1, 1); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Send, "m_DmgRadius", NPCChaserGetProjectileRadius(bossIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Send, "m_flDamage", NPCChaserGetProjectileDamage(bossIndex, difficulty)); - TeleportEntity(projectileEnt, NULL_VECTOR, NULL_VECTOR, velocity); - SetEntDataFloat(projectileEnt, g_FullDamageData, NPCChaserGetProjectileDamage(bossIndex, difficulty)); - ProjectileSetFlags(projectileEnt, PROJ_GRENADE); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderGrenadeShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - } - case SF2BossProjectileType_SentryRocket: - { - projectileName = "tf_projectile_sentryrocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_SentryRocket", "m_iDeflected") + 4, NPCChaserGetProjectileDamage(bossIndex, difficulty), true); // set damage - ProjectileSetFlags(projectileEnt, PROJ_SENTRYROCKET); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderSentryRocketShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Arrow: - { - projectileName = "tf_point_weapon_mimic"; - if (shootDist <= SquareFloat(1250.0)) - { - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - if (NPCChaserHasCriticalRockets(bossIndex)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCrits", 1, 1); - } - SetEntPropFloat(projectileEnt, Prop_Data, "m_flDamage", NPCChaserGetProjectileDamage(bossIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flSpeedMin", NPCChaserGetProjectileSpeed(bossIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Data, "m_flSpeedMax", NPCChaserGetProjectileSpeed(bossIndex, difficulty)); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - ProjectileSetFlags(projectileEnt, PROJ_ARROW); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - DispatchKeyValueVector(projectileEnt, "origin", effectPos); - DispatchKeyValueVector(projectileEnt, "angles", shootAng); - DispatchKeyValue(projectileEnt, "WeaponType", "2"); - - AcceptEntityInput(projectileEnt, "FireOnce"); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - CreateTimer(5.0, Timer_KillEntity, EntIndexToEntRef(projectileEnt), TIMER_FLAG_NO_MAPCHANGE); - if (i == 0) - { - EmitSoundToAll(g_SlenderArrowShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - } - case SF2BossProjectileType_Mangler: - { - projectileName = "tf_projectile_energy_ball"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntProp(projectileEnt, Prop_Send, "m_CollisionGroup", 4); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", 5, 1); - SetEntProp(projectileEnt, Prop_Data, "m_takedamage", 0); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", view_as( { 3.0, 3.0, 3.0 } )); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", view_as( { 3.0, 3.0, 3.0 } )); - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - ProjectileSetFlags(projectileEnt, PROJ_MANGLER); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderManglerShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - case SF2BossProjectileType_Baseball: - { - projectileName = "tf_projectile_stun_ball"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetProjectileSpeed(bossIndex, difficulty); - GetChaserProfileBaseballModel(slenderProfile, baseballModel, sizeof(baseballModel)); - if (baseballModel[0] == '\0') - { - baseballModel = BASEBALL_MODEL; - } - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hThrower", slender); - SetEntityModel(projectileEnt, baseballModel); - ProjectileSetFlags(projectileEnt, PROJ_BASEBALL); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - - if (NPCChaserUseShootGesture(bossIndex) && i == 0) - { - GetChaserProfileShootGestureName(slenderProfile, gestureShootAnim, sizeof(gestureShootAnim)); - - int sequence = combatChar.LookupSequence(gestureShootAnim); - if (sequence != -1) - { - combatChar.AddGestureSequence(sequence); - } - } - - if (i == 0) - { - EmitSoundToAll(g_SlenderBaseballShootSound[bossIndex], slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - g_NpcProjectileCooldown[bossIndex] = GetGameTime() + GetRandomFloat(min, max); - } - } - } - } - - return projectileEnt; -} - -int NPCChaserProjectileAttackShoot(int bossIndex, int slender, int target, const char[] slenderProfile) -{ - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - int projectileType = NPCChaserGetAttackProjectileType(bossIndex, attackIndex); - int projectileEnt; - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - char projectileName[45]; - float clientPos[3]; - float slenderPosition[3]; - float nonRocketScaleMins[3], nonRocketScaleMaxs[3]; - nonRocketScaleMins[0] = -10.0; - nonRocketScaleMins[1] = -10.0; - nonRocketScaleMins[2] = -10.0; - nonRocketScaleMaxs[0] = 10.0; - nonRocketScaleMaxs[1] = 10.0; - nonRocketScaleMaxs[2] = 10.0; - NPCGetEyePosition(bossIndex, slenderPosition); - GetClientEyePosition(target, clientPos); - clientPos[2] -= 25.0; - - float basePos[3], baseAng[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", basePos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", baseAng); - - float effectPos[3], tempEffectPos[3]; - float effectAng[3] = {0.0, 0.0, 0.0}; - - GetChaserProfileAttackProjectileOffset(slenderProfile, attackIndex, tempEffectPos); - VectorTransform(tempEffectPos, basePos, baseAng, tempEffectPos); - AddVectors(effectAng, baseAng, effectAng); - - float spread = NPCChaserGetAttackProjectileDeviation(bossIndex, attackIndex, difficulty); - - int team = 3; - int teamNon = 5; - for (int i = 0; i < NPCChaserGetAttackProjectileCount(bossIndex, attackIndex, difficulty); i++) - { - if (NPCChaserGetAttackProjectileCount(bossIndex, attackIndex, difficulty) != 1) - { - effectPos[0] = tempEffectPos[0] + GetRandomFloat(-10.0, 10.0); - effectPos[1] = tempEffectPos[1] + GetRandomFloat(-10.0, 10.0); - effectPos[2] = tempEffectPos[2] + GetRandomFloat(-10.0, 10.0); - } - else - { - effectPos[0] = tempEffectPos[0]; - effectPos[1] = tempEffectPos[1]; - effectPos[2] = tempEffectPos[2]; - } - - float shootDirection[3], shootAng[3]; - SubtractVectors(clientPos, effectPos, shootDirection); - if (spread != 0.0) - { - shootDirection[0] += GetRandomFloat(-spread, spread); - shootDirection[1] += GetRandomFloat(-spread, spread); - shootDirection[2] += GetRandomFloat(-spread, spread); - } - NormalizeVector(shootDirection, shootDirection); - GetVectorAngles(shootDirection, shootAng); - - switch (projectileType) - { - case 0: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - char fireballTrail[PLATFORM_MAX_PATH]; - GetChaserProfileAttackFireballTrail(slenderProfile, attackIndex, fireballTrail, sizeof(fireballTrail)); - AttachParticle(projectileEnt, fireballTrail); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_FIREBALL_ATTACK); - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileAttackTouch); - } - } - case 1: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - char rocketModel[PLATFORM_MAX_PATH]; - GetChaserProfileAttackRocketModel(slenderProfile, attackIndex, rocketModel, sizeof(rocketModel)); - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - if (NPCChaserGetAttackProjectileCrits(bossIndex, attackIndex, difficulty)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty), true); // set damage - ProjectileSetFlags(projectileEnt, PROJ_ROCKET); - if (strcmp(rocketModel, ROCKET_MODEL, true) != 0) - { - int model; - model = PrecacheModel(rocketModel, true); - SetEntProp(projectileEnt, Prop_Send, "m_nModelIndexOverrides", model); - } - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", team, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - } - } - case 2: - { - projectileName = "tf_projectile_rocket"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - char fireballTrail[PLATFORM_MAX_PATH]; - GetChaserProfileAttackIceballTrail(slenderProfile, attackIndex, fireballTrail, sizeof(fireballTrail)); - AttachParticle(projectileEnt, fireballTrail); - - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMins", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxs", nonRocketScaleMaxs); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMinsPreScaled", nonRocketScaleMins); - SetEntPropVector(projectileEnt, Prop_Send, "m_vecMaxsPreScaled", nonRocketScaleMaxs); - SetEntityRenderMode(projectileEnt, RENDER_TRANSCOLOR); - SetEntityRenderColor(projectileEnt, 0, 0, 0, 0); - SetEntDataFloat(projectileEnt, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.1, true); // set damage to nothing - SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileAttackTouch); - SDKHook(projectileEnt, SDKHook_SetTransmit, Hook_ProjectileTransmit); - - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", teamNon, 1); - TeleportEntity(projectileEnt, effectPos, shootAng, velocity); - DispatchSpawn(projectileEnt); - ProjectileSetFlags(projectileEnt, PROJ_ICEBALL_ATTACK); - } - } - case 3: - { - projectileName = "tf_projectile_pipe"; - projectileEnt = CreateEntityByName(projectileName); - if (projectileEnt != -1) - { - float velocity[3], bufferProj[3]; - - GetAngleVectors(shootAng, bufferProj, NULL_VECTOR, NULL_VECTOR); - - velocity[0] = bufferProj[0]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[1] = bufferProj[1]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - velocity[2] = bufferProj[2]*NPCChaserGetAttackProjectileSpeed(bossIndex, attackIndex, difficulty); - - TeleportEntity(projectileEnt, effectPos, shootAng, NULL_VECTOR); - DispatchSpawn(projectileEnt); - if (NPCChaserGetAttackProjectileCrits(bossIndex, attackIndex, difficulty)) - { - SetEntProp(projectileEnt, Prop_Send, "m_bCritical", 1, 1); - } - SetEntProp(projectileEnt, Prop_Send, "m_iTeamNum", 3, 1); - SetEntProp(projectileEnt, Prop_Send, "m_nSkin", 1, 1); - SetEntPropEnt(projectileEnt, Prop_Send, "m_hOwnerEntity", slender); - SetEntPropFloat(projectileEnt, Prop_Send, "m_DmgRadius", NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty)); - SetEntPropFloat(projectileEnt, Prop_Send, "m_flDamage", NPCChaserGetAttackProjectileRadius(bossIndex, attackIndex, difficulty)); - SetEntDataFloat(projectileEnt, g_FullDamageData, NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty)); - TeleportEntity(projectileEnt, NULL_VECTOR, NULL_VECTOR, velocity); - ProjectileSetFlags(projectileEnt, PROJ_GRENADE); - - //SDKHook(projectileEnt, SDKHook_StartTouch, Hook_ProjectileTouch); - } - } - } - } - - return projectileEnt; -} - -static Action Hook_ProjectileAttackTouch(int entity, int other) -{ - switch (ProjectileGetFlags(entity)) - { - case PROJ_ICEBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - EmitSoundToAll(FIREBALL_IMPACT, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - CreateGeneralParticle(entity, "spell_batball_impact_blue"); - } - } - case PROJ_FIREBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - EmitSoundToAll(FIREBALL_IMPACT, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - CreateGeneralParticle(entity, "bombinomicon_burningdebris"); - } - } - } - switch (ProjectileGetFlags(entity)) - { - case PROJ_FIREBALL_ATTACK, PROJ_ICEBALL_ATTACK: - { - float entPos[3]; - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - entPos[2] += 10.0; - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - int attackIndex = NPCGetCurrentAttackIndex(bossIndex); - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - float radius = NPCChaserGetAttackProjectileRadius(bossIndex, attackIndex, difficulty); - for (int client = 1; client < MaxClients; client++) - { - if (!IsValidClient(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) - { - continue; - } - - if (!attackEliminated && g_PlayerEliminated[client]) - { - continue; - } - - float targetPos[3]; - GetClientEyePosition(client, targetPos); - - Handle trace = TR_TraceRayFilterEx(entPos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, - RayType_EndPoint, - TraceRayBossVisibility, - entity); - - bool isVisible = !TR_DidHit(trace); - int traceHitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (!isVisible && traceHitEntity == client) - { - isVisible = true; - } - - if (isVisible) - { - float distance = GetVectorSquareMagnitude(entPos, targetPos); - if (distance <= SquareFloat(radius)) - { - SDKHooks_TakeDamage(client, entity, entity, NPCChaserGetAttackProjectileDamage(bossIndex, attackIndex, difficulty), DMG_BLAST); - if (TF2_IsPlayerInCondition(client, TFCond_Gas)) - { - TF2_IgnitePlayer(client, client); - TF2_RemoveCondition(client, TFCond_Gas); - } - if ((ProjectileGetFlags(entity) & PROJ_FIREBALL_ATTACK)) - { - TF2_IgnitePlayer(client, client); - } - else if ((ProjectileGetFlags(entity) & PROJ_ICEBALL_ATTACK)) - { - EmitSoundToClient(client, ICEBALL_IMPACT, _, MUSIC_CHAN); - TF2_StunPlayer(client, NPCChaserGetAttackProjectileIceSlowdownDuration(bossIndex, attackIndex, difficulty), NPCChaserGetAttackProjectileIceSlowdownPercent(bossIndex, attackIndex, difficulty), TF_STUNFLAG_SLOWDOWN, client); - } - } - } - } - RemoveEntity(entity); - } - } - } - } - - return Plugin_Handled; -} - -static Action Hook_ProjectileTouch(int entity, int other) -{ - switch (ProjectileGetFlags(entity)) - { - case PROJ_ICEBALL, PROJ_ICEBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - EmitSoundToAll(g_SlenderFireballExplodeSound[bossIndex], entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - CreateGeneralParticle(entity, "spell_batball_impact_blue"); - } - } - case PROJ_FIREBALL, PROJ_FIREBALL_ATTACK: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - EmitSoundToAll(g_SlenderFireballExplodeSound[bossIndex], entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - } - CreateGeneralParticle(entity, "bombinomicon_burningdebris"); - } - } - } - switch (ProjectileGetFlags(entity)) - { - case PROJ_BASEBALL: - { - float entPos[3], otherPos[3]; - GetEntPropVector(other, Prop_Data, "m_vecAbsOrigin", otherPos); - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - if (IsValidClient(other)) - { - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - int difficulty = GetLocalGlobalDifficulty(bossIndex); - SDKHooks_TakeDamage(other, slender, slender, NPCChaserGetProjectileDamage(bossIndex, difficulty), 1048576); - if (TF2_IsPlayerInCondition(other, TFCond_Gas)) - { - TF2_IgnitePlayer(other, other); - TF2_RemoveCondition(other, TFCond_Gas); - } - RemoveEntity(other); - } - } - } - case PROJ_MANGLER, PROJ_FIREBALL, PROJ_ICEBALL: - { - float entPos[3]; - GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", entPos); - entPos[2] += 10.0; - int slender = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); - if (slender != INVALID_ENT_REFERENCE) - { - int bossIndex = NPCGetFromEntIndex(slender); - if (bossIndex != -1) - { - int difficulty = GetLocalGlobalDifficulty(bossIndex); - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - float radius = NPCChaserGetProjectileRadius(bossIndex, difficulty); - float fallOff = NPCChaserGetProjectileRadius(bossIndex, difficulty)/2.0; - for (int client = 1; client < MaxClients; client++) - { - if (!IsValidClient(client) || !IsPlayerAlive(client) || IsClientInGhostMode(client)) - { - continue; - } - - if (!attackEliminated && g_PlayerEliminated[client]) - { - continue; - } - - float targetPos[3]; - GetClientEyePosition(client, targetPos); - - Handle trace = TR_TraceRayFilterEx(entPos, - targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_GRATE, - RayType_EndPoint, - TraceRayBossVisibility, - entity); - - bool isVisible = !TR_DidHit(trace); - int traceHitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (!isVisible && traceHitEntity == client) isVisible = true; - - if (isVisible) - { - float distance = GetVectorSquareMagnitude(entPos, targetPos); - if (distance <= SquareFloat(radius)) - { - float finalDamage; - if (distance <= SquareFloat(fallOff)) - { - finalDamage = NPCChaserGetProjectileDamage(bossIndex, difficulty); - } - else - { - float multiplier = (1.0 - ((distance - SquareFloat(fallOff)) / (SquareFloat(radius) - SquareFloat(fallOff)))); - finalDamage = multiplier * NPCChaserGetProjectileDamage(bossIndex, difficulty); - } - if ((ProjectileGetFlags(entity) & PROJ_MANGLER)) - { - SDKHooks_TakeDamage(client, entity, entity, finalDamage, DMG_BLAST); - } - else - { - SDKHooks_TakeDamage(client, entity, entity, NPCChaserGetProjectileDamage(bossIndex, difficulty), DMG_BLAST); - } - if (TF2_IsPlayerInCondition(client, TFCond_Gas)) - { - TF2_IgnitePlayer(client, client); - TF2_RemoveCondition(client, TFCond_Gas); - } - if ((ProjectileGetFlags(entity) & PROJ_FIREBALL)) - { - TF2_IgnitePlayer(client, client); - } - else if ((ProjectileGetFlags(entity) & PROJ_ICEBALL)) - { - EmitSoundToClient(client, g_SlenderIceballImpactSound[bossIndex], _, MUSIC_CHAN); - TF2_StunPlayer(client, NPCChaserGetIceballSlowdownDuration(bossIndex, difficulty), NPCChaserGetIceballSlowdownPercent(bossIndex, difficulty), TF_STUNFLAG_SLOWDOWN, client); - } - } - } - } - if ((ProjectileGetFlags(entity) & PROJ_MANGLER)) - { - int randomSound = GetRandomInt(0, 2); - switch (randomSound) - { - case 0: - { - EmitSoundToAll(MANGLER_EXPLODE1, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 1: - { - EmitSoundToAll(MANGLER_EXPLODE2, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - case 2: - { - EmitSoundToAll(MANGLER_EXPLODE3, entity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - RemoveEntity(entity); - } - } - } - } - - return Plugin_Handled; -} - -static Action Hook_ProjectileTransmit(int ent, int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - return Plugin_Handled; -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp deleted file mode 100644 index 05d670c0..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser_takedamage.sp +++ /dev/null @@ -1,694 +0,0 @@ -#if defined _sf2_npc_chaser_takedamage_included - #endinput -#endif - -#define _sf2_npc_chaser_takedamage_included - -#pragma semicolon 1 - -//death_enable -//death_health -//death_duration -//death_cooldown -//death_disappear -//death_remove -//death_health_per_ -//"add death health on death" -/* - "death" - { - "1" - { - "animation_death" "diebackward" - "animation_death_playbackrate" "1.0" - } - } -*/ - -Action Hook_SlenderOnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) -{ - if (!IsValidEntity(victim)) - { - //Theres no boss, what? - SDKUnhook(victim, SDKHook_OnTakeDamageAlive, Hook_SlenderOnTakeDamage); - return Plugin_Stop; - } - - float myPos[3], myEyeAng[3], clientPos[3], buffer[3], traceStartPos[3], traceEndPos[3]; - GetEntPropVector(victim, Prop_Data, "m_vecAbsOrigin", myPos); - - int maxHealth = GetEntProp(victim, Prop_Data, "m_iMaxHealth"); - if (GetEntProp(victim, Prop_Data, "m_iHealth") != maxHealth) - { - SetEntProp(victim, Prop_Data, "m_iHealth", maxHealth); - } - - int bossIndex = NPCGetFromEntIndex(victim); - if (bossIndex == -1) - { - //Theres still no boss, how did this happen? - SDKUnhook(victim, SDKHook_OnTakeDamageAlive, Hook_SlenderOnTakeDamage); - return Plugin_Stop; - } - int slender = victim; - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - NPCGetEyePosition(bossIndex, traceStartPos); - - bool miniCrit = false; - if (IsValidClient(attacker)) - { - GetClientAbsOrigin(attacker, clientPos); - GetClientEyePosition(attacker, traceEndPos); - } - float shootDist = GetVectorSquareMagnitude(clientPos, myPos); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", myEyeAng); - - AddVectors(myEyeAng, g_SlenderEyeAngOffset[bossIndex], myEyeAng); - - if (IsValidClient(attacker) && SF_IsBoxingMap() && (TF2_IsPlayerInCondition(attacker, TFCond_RegenBuffed)) && !NPCIsRaging(bossIndex)) - { - int health = GetClientHealth(attacker); - float multipliedDamage = damage; - multipliedDamage *= 0.475; - int newHealth = health + RoundToCeil(multipliedDamage); - if (newHealth <= GetEntProp(attacker, Prop_Data, "m_iMaxHealth")) - { - SetEntityHealth(attacker, newHealth); - } - else - { - int clientMaxHealth = GetEntProp(attacker, Prop_Data, "m_iMaxHealth"); - SetEntityHealth(attacker, clientMaxHealth); - } - } - - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Scout) - { - int stick = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(stick) && GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 349 && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && g_SlenderIsBurning[bossIndex] && !NPCIsRaging(bossIndex)) - { - damagetype = DMG_CRIT; - damage *= 3.0; - } - if (IsValidEntity(stick) && (GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 325 || GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 452) && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 5.0; - } - if (IsValidEntity(stick) && GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 355 && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderMarkedTimer[bossIndex] = CreateTimer(15.0, Timer_BossMarked, EntIndexToEntRef(slender), TIMER_FLAG_NO_MAPCHANGE); - g_SlenderIsMarked[bossIndex] = true; - } - if (IsValidEntity(stick) && GetEntProp(stick, Prop_Send, "m_iItemDefinitionIndex") == 648 && stick == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && shootDist > SquareFloat(72.0) && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 5.0; - } - } - if (IsValidClient(attacker) && TF2_GetPlayerClass(attacker) == TFClass_Pyro) - { - //Probably the only time where buffing the phlog is a good thing. - int phlog = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); - int fragment = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(phlog) && GetEntProp(phlog, Prop_Send, "m_iItemDefinitionIndex") == TF_WEAPON_PHLOGISTINATOR && GetEntPropFloat(attacker, Prop_Send, "m_flNextRageEarnTime") <= GetGameTime() && !(GetEntProp(attacker, Prop_Send, "m_bRageDraining"))) - { - float rage = GetEntPropFloat(attacker, Prop_Send, "m_flRageMeter"); - rage += (damage / 30.00); - if (rage > 100.0) - { - rage = 100.0; - } - SetEntPropFloat(attacker, Prop_Send, "m_flRageMeter", rage); - } - if (IsValidEntity(fragment) && SF_IsBoxingMap() && GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 348 && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBurnTimer[bossIndex] = CreateTimer(0.5, Timer_BossBurn, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBurningTimer[bossIndex] = GetGameTime() + 15.0; - g_SlenderIsBurning[bossIndex] = true; - } - if (IsValidEntity(fragment) && SF_IsBoxingMap() && - (GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 38 || GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 457 || GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 1000) - && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && g_SlenderIsBurning[bossIndex] && !NPCIsRaging(bossIndex)) - { - g_SlenderBurnTimer[bossIndex] = null; - g_SlenderStopBurningTimer[bossIndex] = GetGameTime(); - g_SlenderIsBurning[bossIndex] = false; - miniCrit = true; - damage *= 1.35; - } - if (IsValidEntity(fragment) && GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 1181 && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - if (IsValidEntity(fragment) && SF_IsBoxingMap() && - (GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 813 || GetEntProp(fragment, Prop_Send, "m_iItemDefinitionIndex") == 834) - && fragment == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex) && (g_SlenderIsMarked[bossIndex])) - { - damagetype = DMG_CRIT; - damage *= 3.0; - } - } - if (IsValidClient(attacker) && TF2_GetPlayerClass(attacker) == TFClass_Soldier) - { - int weapon = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(weapon) && GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex") == 447 && weapon == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) // Whip - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - else if (IsValidEntity(weapon) && GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex") == 416 && weapon == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) - { - float zVelocity[3]; - GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); - if (zVelocity[2] < 0.0) // A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. - { - damagetype |= DMG_CRIT; - damage *= 2.0; - } - } - float requiredRage = 6.0; - if (IsValidEntity(weapon) && GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex") == 354) // Concheror - { - requiredRage = 4.8; - } - bool draining = !!GetEntProp(attacker, Prop_Send, "m_bRageDraining"); - if (!draining) - { - float rage = GetEntPropFloat(attacker, Prop_Send, "m_flRageMeter"); - rage += (damage / requiredRage); - if (rage > 100.0) - { - rage = 100.0; - } - SetEntPropFloat(attacker, Prop_Send, "m_flRageMeter", rage); - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Heavy) - { - int gloves = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(gloves) && GetEntProp(gloves, Prop_Send, "m_iItemDefinitionIndex") == 426 && gloves == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - if (IsValidEntity(gloves) && GetEntProp(gloves, Prop_Send, "m_iItemDefinitionIndex") == 43 && gloves == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && !IsClientCritBoosted(attacker) && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_PlayerHitsToCrits[attacker]++; - if (g_PlayerHitsToCrits[attacker] == 5) - { - TF2_AddCondition(attacker, TFCond_CritOnFlagCapture, 5.0); - g_PlayerHitsToCrits[attacker] = 0; - } - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Engineer) - { - int wrench = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(wrench) && GetEntProp(wrench, Prop_Send, "m_iItemDefinitionIndex") == 155 && wrench == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 5.0; - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Sniper) - { - int sharpy = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(sharpy) && GetEntProp(sharpy, Prop_Send, "m_iItemDefinitionIndex") == 171 && sharpy == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_SlenderBleedTimer[bossIndex] = CreateTimer(0.5, Timer_BossBleed, EntIndexToEntRef(slender), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - g_SlenderStopBleedingTimer[bossIndex] = GetGameTime() + 6.0; - } - if (IsValidEntity(sharpy) && GetEntProp(sharpy, Prop_Send, "m_iItemDefinitionIndex") == 232 && sharpy == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && g_SlenderIsMarked[bossIndex] && !NPCIsRaging(bossIndex)) - { - damagetype = DMG_CRIT; - damage *= 3.0; - miniCrit = false; - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_DemoMan) - { - int sword = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(sword) && - (GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 132 || GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 266 || GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 482 || GetEntProp(sword, Prop_Send, "m_iItemDefinitionIndex") == 1082) - && sword == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && SF_IsBoxingMap() && !NPCIsRaging(bossIndex)) - { - g_PlayerHitsToHeads[attacker]++; - if (g_PlayerHitsToHeads[attacker] == 5) - { - if (!TF2_IsPlayerInCondition(attacker, TFCond_DemoBuff)) - { - TF2_AddCondition(attacker, TFCond_DemoBuff, -1.0); - } - int decapitations = GetEntProp(attacker, Prop_Send, "m_iDecapitations"); - int health = GetClientHealth(attacker); - SetEntProp(attacker, Prop_Send, "m_iDecapitations", decapitations+1); - SetEntityHealth(attacker, health+15); - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 0.01); - g_PlayerHitsToHeads[attacker] = 0; - } - } - } - if (IsValidClient(attacker) && SF_IsBoxingMap() && TF2_GetPlayerClass(attacker) == TFClass_Spy) - { - int stabbingTime = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - char weaponClass[64]; - GetEdictClassname(stabbingTime, weaponClass, sizeof(weaponClass)); - if (IsValidEntity(stabbingTime) && stabbingTime == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon") && (strcmp(weaponClass, "tf_weapon_knife", false) == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy", false) == 0)) && SF_IsBoxingMap()) - { - SubtractVectors(traceEndPos, traceStartPos, buffer); - GetVectorAngles(buffer, buffer); - - if (FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) >= (NPCGetBackstabFOV(bossIndex) * 0.5) && !NPCIsRaging(bossIndex) && GetChaserProfileBackstabDamageScale(profile) > 0.0) - { - damagetype = DMG_CRIT; - EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - SetEntPropFloat(stabbingTime, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); - int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); - if (vm > MaxClients) - { - int meleeIndex = GetEntProp(stabbingTime, Prop_Send, "m_iItemDefinitionIndex"); - int anim = 41; - switch (meleeIndex) - { - case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: - { - anim = 15; - } - case 638: - { - anim = 31; - } - } - SetEntProp(vm, Prop_Send, "m_nSequence", anim); - damage = NPCChaserGetStunInitialHealth(bossIndex) * GetChaserProfileBackstabDamageScale(profile); - NPCSetAddSpeed(bossIndex, 12.5); - NPCSetAddMaxSpeed(bossIndex, 25.0); - NPCSetAddAcceleration(bossIndex, 100.0); - - if (meleeIndex == 356) //Kunai - { - int health = GetClientHealth(attacker) + 100; - if (health > 210) - { - health = 210; - } - SetEntityHealth(attacker, health); - } - switch (meleeIndex) - { - case 356: //Kunai - { - int health = GetClientHealth(attacker) + 100; - if (health > 210) - { - health = 210; - } - SetEntityHealth(attacker, health); - } - case 461: //Big Earner - { - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - } - } - } - } - } - } - - if (IsValidClient(attacker) && TF2_IsMiniCritBuffed(attacker))//Mini crit boosted - { - miniCrit = true; - } - - if ((g_SlenderIsMarked[bossIndex]) && !NPCIsRaging(bossIndex)) - { - miniCrit = true; - damage *= 1.35; - } - if (IsValidClient(attacker) && ((SF_IsBoxingMap() && GetClientTeam(attacker) == TFTeam_Blue) || (g_PlayerProxy[attacker]))) - { - damage = 0.0; - } - if (!IsValidClient(attacker)) - { - damage = 0.0; - } - bool attackEliminated = !!(NPCGetFlags(bossIndex) & SFF_ATTACKWAITERS); - - if (SF_IsBoxingMap() && IsValidClient(attacker) && !attackEliminated && (GetClientTeam(attacker) == TFTeam_Blue)) - { - damage = 0.0; - } - - ArrayList resistances = GetChaserProfileDamageResistances(profile); - if (resistances != null) - { - for (int i = 0; i < resistances.Length; i++) - { - int type = resistances.Get(i); - if (damagetype & type || damagetype == type) - { - damage = 0.0; - return Plugin_Changed; - } - } - } - - if (NPCGetType(bossIndex) == SF2BossType_Chaser && damage > 0.0) - { - int state = g_SlenderState[bossIndex]; - - if (NPCChaserIsStunEnabled(bossIndex) && !g_SlenderSpawning[bossIndex]) - { - if (g_SlenderNextStunTime[bossIndex] <= GetGameTime() && !NPCIsRaging(bossIndex) && !g_SlenderInDeathcam[bossIndex] && !g_RestartSessionEnabled) - { - NPCChaserAddStunHealth(bossIndex, -damage); - if (NPCChaserGetStunHealth(bossIndex) <= 0.0 && state != STATE_STUN) - { - NPCBossTriggerStun(bossIndex, slender, profile, myPos); - Call_StartForward(g_OnBossStunnedFwd); - Call_PushCell(bossIndex); - Call_PushCell(attacker); - Call_Finish(); - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) - { - int clientMaxHealth = SDKCall(g_SDKGetMaxHealth, attacker); - float healthToRecover = float(clientMaxHealth)/7.5; - int _healthToRecover = RoundToNearest(healthToRecover) + GetEntProp(attacker, Prop_Send, "m_iHealth"); - SetEntityHealth(attacker, _healthToRecover); - } - } - if (NPCGetHealthbarState(bossIndex)) - { - UpdateHealthBar(bossIndex); - } - - Event event = CreateEvent("npc_hurt"); - if (event) - { - event.SetInt("entindex", slender); - event.SetInt("health", 2147483646); - event.SetInt("damageamount", RoundToFloor(damage)); - event.SetBool("crit", (damagetype & DMG_CRIT) ? true : false); - - if (IsValidClient(attacker)) - { - event.SetInt("attacker_player", GetClientUserId(attacker)); - event.SetInt("weaponid", 0); - } - else - { - event.SetInt("attacker_player", 0); - event.SetInt("weaponid", 0); - } - - event.Fire(); - } - if (IsValidClient(attacker)) - { - if ((damagetype & DMG_CRIT) && !miniCrit) - { - float myEyePos[3]; - SlenderGetAbsOrigin(bossIndex, myEyePos); - float myEyePosEx[3]; - GetEntPropVector(slender, Prop_Send, "m_vecMaxs", myEyePosEx); - myEyePos[2] += myEyePosEx[2]; - - TE_Particle(g_Particles[CriticalHit], myEyePos, myEyePos); - TE_SendToClient(attacker); - - EmitSoundToClient(attacker, CRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - else if (miniCrit) - { - float myEyePos[3]; - SlenderGetAbsOrigin(bossIndex, myEyePos); - float myEyePosEx[3]; - GetEntPropVector(slender, Prop_Send, "m_vecMaxs", myEyePosEx); - myEyePos[2]+=myEyePosEx[2]; - - TE_Particle(g_Particles[MiniCritHit], myEyePos, myEyePos); - TE_SendToClient(attacker); - - EmitSoundToClient(attacker, MINICRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - } - } - } - } - } - - return Plugin_Changed; -} - -void UpdateHealthBar(int bossIndex, int optionalSetPercent = -1) -{ - float maxHealth = NPCChaserGetStunInitialHealth(bossIndex); - float health = NPCChaserGetStunHealth(bossIndex); - if (g_HealthBar == -1) - { - return; - } - int healthPercent; - SetEntProp(g_HealthBar, Prop_Send, "m_iBossState", 0); - healthPercent = RoundToCeil((health/maxHealth)*float(255)); - if (healthPercent>255) - { - healthPercent=255; - } - else if (healthPercent<=0) - { - healthPercent=0; - } - if (optionalSetPercent > -1) - { - healthPercent = optionalSetPercent; - } - SetEntProp(g_HealthBar, Prop_Send, "m_iBossHealthPercentageByte", healthPercent); -} - -void NPCBossTriggerStun(int bossIndex, int victim, char profile[SF2_MAX_PROFILE_NAME_LENGTH], float position[3]) -{ - if (bossIndex == -1) - { - return; - } - if (!victim || victim == INVALID_ENT_REFERENCE) - { - return; - } - int difficulty = GetLocalGlobalDifficulty(bossIndex); - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(victim); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - CBaseCombatCharacter overlay = CBaseCombatCharacter(victim); - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - int state = g_SlenderState[bossIndex]; - bool doChasePersistencyInit = false; - overlay.RemoveAllGestures(); - CBaseNPC_RemoveAllLayers(victim); - if (g_LastStuckTime[bossIndex] != 0.0) - { - g_LastStuckTime[bossIndex] = GetGameTime(); - } - loco.ClearStuckStatus(); - g_SlenderState[bossIndex] = STATE_STUN; - if (g_SlenderChaseInitialTimer[bossIndex] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[bossIndex]); - } - if (NPCChaseHasKeyDrop(bossIndex)) - { - if (SF_IsBoxingMap()) - { - g_SlenderBoxingBossKilled += 1; - if ((g_SlenderBoxingBossKilled == g_SlenderBoxingBossCount) && !g_SlenderBoxingBossIsKilled[bossIndex]) - { - NPC_DropKey(bossIndex); - } - g_SlenderBoxingBossIsKilled[bossIndex] = true; - } - else - { - NPC_DropKey(bossIndex); - } - } - - if (NPCChaserCanDropItemOnStun(bossIndex)) - { - switch (NPCChaserItemDropTypeStun(bossIndex)) - { - case 1: - { - int smallammo = CreateEntityByName("item_ammopack_small"); - DispatchKeyValue(smallammo, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(smallammo)) - { - SetEntProp(smallammo, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(smallammo, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 2: - { - int medammo = CreateEntityByName("item_ammopack_medium"); - DispatchKeyValue(medammo, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(medammo)) - { - SetEntProp(medammo, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(medammo, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 3: - { - int fullammo = CreateEntityByName("item_ammopack_full"); - DispatchKeyValue(fullammo, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(fullammo)) - { - SetEntProp(fullammo, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(fullammo, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 4: - { - int smallhp = CreateEntityByName("item_healthkit_small"); - DispatchKeyValue(smallhp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(smallhp)) - { - SetEntProp(smallhp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(smallhp, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 5: - { - int medhp = CreateEntityByName("item_healthkit_medium"); - DispatchKeyValue(medhp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(medhp)) - { - SetEntProp(medhp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(medhp, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 6: - { - int fullhp = CreateEntityByName("item_healthkit_full"); - DispatchKeyValue(fullhp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(fullhp)) - { - SetEntProp(fullhp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(fullhp, position, NULL_VECTOR, NULL_VECTOR); - } - } - case 7: - { - int smallammohp = CreateEntityByName("item_healthammokit"); - DispatchKeyValue(smallammohp, "OnPlayerTouch", "!self,Kill,,0,-1"); - if (DispatchSpawn(smallammohp)) - { - SetEntProp(smallammohp, Prop_Send, "m_iTeamNum", 0); - TeleportEntity(smallammohp, position, NULL_VECTOR, NULL_VECTOR); - } - } - } - } - if (g_SlenderChaseInitialTimer[bossIndex] != null) - { - TriggerTimer(g_SlenderChaseInitialTimer[bossIndex]); - } - g_NpcUsesHealAnimation[bossIndex] = false; - g_NpcUseStartFleeAnimation[bossIndex] = false; - g_NpcIsHealing[bossIndex] = false; - g_NpcIsRunningToHeal[bossIndex] = false; - g_SlenderHealDelayTimer[bossIndex] = null; - g_SlenderHealTimer[bossIndex] = null; - g_SlenderStartFleeTimer[bossIndex] = null; - g_NpcSelfHealStage[bossIndex] = 12; - if (SF_IsBoxingMap() && NPCChaserIsBoxingBoss(bossIndex)) - { - for (int client = 1; client < MaxClients; client++) - { - if (IsValidClient(client) && !g_PlayerEliminated[client] && GetClientTeam(client) == TFTeam_Red) - { - char player[32]; - FormatEx(player, sizeof(player), "%N", client); - char bossName[SF2_MAX_NAME_LENGTH]; - NPCGetBossName(bossIndex, bossName, sizeof(bossName)); - if (bossName[0] == '\0') - { - strcopy(bossName, sizeof(bossName), profile); - } - CPrintToChatAll("{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Boxing Win Message", player, bossName); - } - } - } - if (state != STATE_CHASE && state != STATE_ATTACK) - { - doChasePersistencyInit = true; - } - if (g_IsSlenderAttacking[bossIndex]) - { - // Cancel attacking. - g_IsSlenderAttacking[bossIndex] = false; - g_NpcStealingLife[bossIndex] = false; - g_SlenderAttackTimer[bossIndex] = null; - g_NpcLifeStealTimer[bossIndex] = null; - g_SlenderBackupAtkTimer[bossIndex] = null; - g_NpcAlreadyAttacked[bossIndex] = false; - g_NpcUseFireAnimation[bossIndex] = false; - } - - if (g_NpcHasCloaked[bossIndex] && NPCChaserIsCloakEnabled(bossIndex)) - { - SetEntityRenderMode(victim, RENDER_NORMAL); - SetEntityRenderColor(victim, g_SlenderRenderColor[bossIndex][0], g_SlenderRenderColor[bossIndex][1], g_SlenderRenderColor[bossIndex][2], 0); - - g_NpcHasCloaked[bossIndex] = false; - SlenderToggleParticleEffects(victim, true); - GetChaserProfileCloakParticle(profile, cloakParticle, sizeof(cloakParticle)); - if (cloakParticle[0] == '\0') - { - cloakParticle = "drg_cow_explosioncore_charged_blue"; - } - SlenderCreateParticle(bossIndex, cloakParticle, 35.0); - EmitSoundToAll(g_SlenderCloakOffSound[bossIndex], victim, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - g_SlenderNextCloakTime[bossIndex] = GetGameTime() + NPCChaserGetCloakCooldown(bossIndex, difficulty); - Call_StartForward(g_OnBossDecloakedFwd); - Call_PushCell(bossIndex); - Call_Finish(); - } - - if (!doChasePersistencyInit) - { - float persistencyTime = GetChaserProfileChaseStunPersistencyTimeInit(profile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[bossIndex] = GetGameTime() + persistencyTime; - } - - persistencyTime = GetChaserProfileChaseStunPersistencyTimeAdd(profile); - if (persistencyTime >= 0.0) - { - if (g_SlenderTimeUntilNoPersistence[bossIndex] < GetGameTime()) - { - g_SlenderTimeUntilNoPersistence[bossIndex] = GetGameTime(); - } - g_SlenderTimeUntilNoPersistence[bossIndex] += persistencyTime; - } - } - else - { - float persistencyTime = GetChaserProfileChaseAttackPersistencyTimeInit(profile); - if (persistencyTime >= 0.0) - { - g_SlenderTimeUntilNoPersistence[bossIndex] = GetGameTime() + persistencyTime; - } - } - - NPCChaserUpdateBossAnimation(bossIndex, victim, g_SlenderState[bossIndex]); - - g_SlenderTimeUntilRecover[bossIndex] = GetGameTime() + g_SlenderAnimationDuration[bossIndex]; - - // Sound handling. Ignore time check. - SlenderPerformVoice(bossIndex, _, SF2BossSound_Stun); -} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp index c537d1e2..d17cc662 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp @@ -5,28 +5,16 @@ #pragma semicolon 1 -static float g_NpcStatueChaseDuration[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStatueChaseDurationAddMin[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStatueChaseDurationAddMax[MAX_BOSSES][Difficulty_Max]; - -static float g_NpcTimeUntilAbandon[MAX_BOSSES] = { -1.0, ... }; - static float g_NpcStatueIdleLifetime[MAX_BOSSES][Difficulty_Max]; -static float g_NpcStatueModelChangeDistMax[MAX_BOSSES][Difficulty_Max]; - -float NPCStatueChaseDuration(int npcIndex, int difficulty) -{ - return g_NpcStatueChaseDuration[npcIndex][difficulty]; -} -float NPCStatueGetChaseDurationAddMin(int npcIndex, int difficulty) +SF2StatueBossProfileData NPCStatueGetProfileData(int npcIndex) { - return g_NpcStatueChaseDurationAddMin[npcIndex][difficulty]; -} + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(npcIndex, profile, sizeof(profile)); -float NPCStatueGetChaseDurationAddMax(int npcIndex, int difficulty) -{ - return g_NpcStatueChaseDurationAddMax[npcIndex][difficulty]; + SF2StatueBossProfileData profileData; + g_StatueBossProfileData.GetArray(profile, profileData, sizeof(profileData)); + return profileData; } float NPCStatueGetIdleLifetime(int npcIndex, int difficulty) @@ -34,298 +22,67 @@ float NPCStatueGetIdleLifetime(int npcIndex, int difficulty) return g_NpcStatueIdleLifetime[npcIndex][difficulty]; } -float NPCStatueGetModelChangeDistance(int npcIndex, int difficulty) -{ - return g_NpcStatueModelChangeDistMax[npcIndex][difficulty]; -} - -void NPCStatueOnSelectProfile(const char[] profile, int npcIndex) +void NPCStatueOnSelectProfile(int npcIndex) { + SF2NPC_Statue statue = SF2NPC_Statue(npcIndex); + SF2StatueBossProfileData data; + data = statue.GetProfileData(); for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - g_NpcStatueChaseDuration[npcIndex][difficulty] = GetStatueProfileChaseDuration(profile, difficulty); - g_NpcStatueChaseDurationAddMin[npcIndex][difficulty] = GetStatueProfileChaseDurationAddVisibleMin(profile, difficulty); - g_NpcStatueChaseDurationAddMax[npcIndex][difficulty] = GetStatueProfileChaseDurationAddVisibleMax(profile, difficulty); - - g_NpcStatueIdleLifetime[npcIndex][difficulty] = GetStatueProfileIdleLifeTime(profile, difficulty); - g_NpcStatueModelChangeDistMax[npcIndex][difficulty] = GetStatueProfileModelChangeDistMax(profile, difficulty); + g_NpcStatueIdleLifetime[npcIndex][difficulty] = data.IdleLifeTime[difficulty]; } + + statue.SetAffectedBySight(true); } -CBaseCombatCharacter Spawn_Statue(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) +SF2_StatueEntity Spawn_Statue(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) { - g_SlenderTarget[controller.Index] = INVALID_ENT_REFERENCE; - g_SlenderStatueIdleLifeTime[controller.Index] = GetGameTime() + g_NpcStatueIdleLifetime[controller.Index][GetLocalGlobalDifficulty()]; + g_SlenderStatueIdleLifeTime[controller.Index] = GetGameTime() + g_NpcStatueIdleLifetime[controller.Index][controller.Difficulty]; return SF2_StatueEntity.Create(controller, pos, ang); } -void SlenderStatueBossProcessMovement(int bossEnt) +void Despawn_Statue(SF2NPC_Statue controller, CBaseEntity bossEnt) { - if (!g_Enabled) - { - return; - } - - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(bossEnt); - if (npc == INVALID_NPC) - { - SDKUnhook(bossEnt, SDKHook_Think, SlenderStatueBossProcessMovement); //What no boss? - return; - } - - int bossIndex = NPCGetFromEntIndex(bossEnt); - if (bossIndex == -1) - { - //Boss is invalid somehow, and the hook wasn't killed. - SDKUnhook(bossEnt, SDKHook_Think, SlenderStatueBossProcessMovement); - return; - } - - //From Pelipoika's rainbow outline plugin - if (NPCGetCustomOutlinesState(bossIndex) && NPCGetRainbowOutlineState(bossIndex)) - { - int glow = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - int color[4]; - color[0] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 0) * 127.5 + 127.5); - color[1] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 2) * 127.5 + 127.5); - color[2] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(bossIndex)) + bossIndex + 4) * 127.5 + 127.5); - color[3] = 255; - if (IsValidEntity(glow)) - { - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - } - } - - INextBot bot = npc.GetBot(); - CBaseNPC_Locomotion loco = npc.GetLocomotion(); - SF2_StatueEntity statue = SF2_StatueEntity(bossEnt); - - char slenderProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, slenderProfile, sizeof(slenderProfile)); - - int difficulty = GetLocalGlobalDifficulty(bossIndex); - - float myPos[3], myEyeAng[3]; - GetEntPropVector(bossEnt, Prop_Data, "m_vecAbsOrigin", myPos); - GetEntPropVector(bossEnt, Prop_Data, "m_angAbsRotation", myEyeAng); - - int state = g_SlenderState[bossIndex]; - g_SlenderOldState[bossIndex] = state; - - float originalSpeed = NPCGetSpeed(bossIndex, difficulty) + NPCGetAddSpeed(bossIndex); - float speed = originalSpeed; - speed = originalSpeed + ((originalSpeed * g_RoundDifficultyModifier) / 15.0); - - if (!g_SlenderInDeathcam[bossIndex] && statue.IsMoving) - { - npc.flWalkSpeed = speed * 10.0; - npc.flRunSpeed = speed * 10.0; - } - else - { - npc.flWalkSpeed = 0.0; - npc.flRunSpeed = 0.0; - } - npc.flAcceleration = 50000.0; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + // Stop all possible looping sounds. + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.MoveSounds; + soundInfo.StopAllSounds(bossEnt.index); +} - // Process angles. - bool changeAngle = false; - float posToAt[3]; - g_SlenderTarget[bossIndex] = EnsureEntRef(statue.Target); - if (statue.IsMoving) - { - int target = statue.Target; - if (target && target != INVALID_ENT_REFERENCE) - { - changeAngle = true; - GetEntPropVector(target, Prop_Data, "m_vecAbsOrigin", posToAt); - } - } +void NPCStatue_InitializeAPI() +{ + CreateNative("SF2_GetStatueProfileFromBossIndex", Native_GetProfileData); + CreateNative("SF2_GetStatueProfileFromName", Native_GetProfileDataEx); - if (changeAngle) - { - float ang[3]; - SubtractVectors(posToAt, myPos, ang); - GetVectorAngles(ang, ang); - AddVectors(ang, g_SlenderEyeAngOffset[bossIndex], ang); - for (int cell2 = 0; cell2 < 3; cell2++) - { - ang[cell2] = AngleNormalize(ang[cell2]); - } - ang[0] = 0.0; - TeleportEntity(bossEnt, NULL_VECTOR, ang, NULL_VECTOR); - } + SF2_StatueEntity.SetupAPI(); +} - if (!g_SlenderSpawning[bossIndex] && !g_SlenderInDeathcam[bossIndex]) +static any Native_GetProfileData(Handle plugin, int numParams) +{ + SF2NPC_Statue controller = SF2NPC_Statue(GetNativeCell(1)); + if (!controller.IsValid()) { - if (statue.IsMoving) - { - loco.Run(); - g_BossPathFollower[bossIndex].Update(bot); - bot.Update(); - } - else - { - loco.Stop(); - } - if (loco.IsOnGround() && !loco.IsClimbingOrJumping() && statue.IsMoving) - { - float pathNodePos[3], pathEndPos[3]; - Segment segment; - if (g_BossPathFollower[bossIndex].FirstSegment() != NULL_PATH_SEGMENT && g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()) != NULL_PATH_SEGMENT) - { - segment = g_BossPathFollower[bossIndex].NextSegment(g_BossPathFollower[bossIndex].FirstSegment()); - segment.GetForward(pathNodePos); - segment.GetPos(pathEndPos); - ScaleVector(pathNodePos, segment.length); - AddVectors(pathEndPos, pathNodePos, pathEndPos); - - if (g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, JUMP_OVER_GAP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, CLIMB_UP, 120.0) || g_BossPathFollower[bossIndex].IsDiscontinuityAhead(bot, LADDER_UP, 120.0)) - { - CBaseNPC_Jump(bossEnt, loco, myPos, pathEndPos); - } - } - } + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); } - if (!loco.IsClimbingOrJumping() && !g_SlenderSpawning[bossIndex]) - { - float hullcheckmins[3], hullcheckmaxs[3]; - hullcheckmins = HULL_HUMAN_MINS; - hullcheckmaxs = HULL_HUMAN_MAXS; - - hullcheckmins[0] -= 20.0; - hullcheckmins[1] -= 20.0; - - hullcheckmaxs[0] += 20.0; - hullcheckmaxs[1] += 20.0; - - hullcheckmins[2] += loco.GetStepHeight(); - hullcheckmaxs[2] += 5.0; + SF2StatueBossProfileData data; + data = controller.GetProfileData(); + SetNativeArray(2, data, sizeof(data)); + return 0; +} - if (!g_NpcVelocityCancel[bossIndex] && IsSpaceOccupiedIgnorePlayersAndEnts(myPos, hullcheckmins, hullcheckmaxs, bossEnt))//The boss will start to merge with shits, cancel out velocity. - { - float origin[3]; - loco.SetVelocity(origin); - g_NpcVelocityCancel[bossIndex] = true; - } - } - else +static any Native_GetProfileDataEx(Handle plugin, int numParams) +{ + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetNativeString(1, profile, sizeof(profile)); + SF2StatueBossProfileData data; + if (!g_StatueBossProfileData.GetArray(profile, data, sizeof(data))) { - g_NpcVelocityCancel[bossIndex] = false; + return false; } - if (statue.IsMoving && !g_SlenderInDeathcam[bossIndex] && GetGameTime() < g_NpcTimeUntilAbandon[bossIndex] && (GetGameTime() - g_SlenderLastKill[bossIndex]) >= NPCGetInstantKillCooldown(bossIndex, difficulty)) - { - bool runUnstuck = true; - if (runUnstuck) - { - if (GetVectorSquareMagnitude(myPos, g_LastPos[bossIndex]) < 0.1 || loco.IsStuck()) - { - bool blockingProp = false; - - if (!blockingProp) - { - if (g_LastStuckTime[bossIndex] == 0.0) - { - g_LastStuckTime[bossIndex] = GetGameTime(); - } - - if ((g_LastStuckTime[bossIndex] <= GetGameTime() - 1.0 || loco.GetStuckDuration() >= 1.0)) - { - float destination[3]; - CNavArea area = TheNavMesh.GetNearestNavArea(g_LastPos[bossIndex], _, _, _, false); - area.GetCenter(destination); - float tempMaxs[3]; - npc.GetBodyMaxs(tempMaxs); - float traceMins[3]; - traceMins[0] = g_SlenderDetectMins[bossIndex][0]; - traceMins[1] = g_SlenderDetectMins[bossIndex][1]; - traceMins[2] = 0.0; - - float traceMaxs[3]; - traceMaxs[0] = g_SlenderDetectMaxs[bossIndex][0]; - traceMaxs[1] = g_SlenderDetectMaxs[bossIndex][1]; - traceMaxs[2] = tempMaxs[2]; - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (GetVectorSquareMagnitude(destination, g_LastPos[bossIndex]) <= SquareFloat(16.0) || TR_DidHit()) - { - CursorData cursor = g_BossPathFollower[bossIndex].GetCursorData(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - if (collector.Get(i).GetCostSoFar() < 16.0) - { - continue; - } - if (cursor.segmentPrior != NULL_PATH_SEGMENT) - { - CNavArea segmentArea = cursor.segmentPrior.area; - if (segmentArea == collector.Get(i)) - { - continue; - } - } - float navPos[3]; - collector.Get(i).GetCenter(navPos); - if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) - { - continue; - } - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - int randomArea = 0, randomCell = 0; - areaArray.Resize(validAreaCount); - area = NULL_AREA; - while (validAreaCount > 1) - { - randomCell = GetRandomInt(0, validAreaCount - 1); - randomArea = areaArray.Get(randomCell); - area = collector.Get(randomArea); - area.GetCenter(destination); - - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (TR_DidHit()) - { - area = NULL_AREA; - validAreaCount--; - int findValue = areaArray.FindValue(randomCell); - if (findValue != -1) - { - areaArray.Erase(findValue); - } - } - else - { - break; - } - } - - delete collector; - delete areaArray; - } - g_BossPathFollower[bossIndex].GetClosestPosition(destination, destination, g_BossPathFollower[bossIndex].FirstSegment(), 128.0); - TeleportEntity(bossEnt, destination, NULL_VECTOR, NULL_VECTOR); - - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = GetGameTime(); - } - } - } - else - { - loco.ClearStuckStatus(); - g_LastStuckTime[bossIndex] = 0.0; - g_LastPos[bossIndex] = myPos; - } - } - } - - return; -} \ No newline at end of file + SetNativeArray(2, data, sizeof(data)); + return true; +} diff --git a/addons/sourcemod/scripting/sf2/npc/npc_death_msg.sp b/addons/sourcemod/scripting/sf2/npc/npc_death_msg.sp deleted file mode 100644 index a60225eb..00000000 --- a/addons/sourcemod/scripting/sf2/npc/npc_death_msg.sp +++ /dev/null @@ -1 +0,0 @@ -//Work in progress \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/playergroups/menus.sp b/addons/sourcemod/scripting/sf2/playergroups/menus.sp index 7d148115..cf8705ed 100644 --- a/addons/sourcemod/scripting/sf2/playergroups/menus.sp +++ b/addons/sourcemod/scripting/sf2/playergroups/menus.sp @@ -45,7 +45,7 @@ static int Menu_GroupMain(Menu menu, MenuAction action, int param1, int param2) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuMain, param1, 30); + g_MenuMain.Display(param1, 30); } } else if (action == MenuAction_Select) diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 195bab5f..2adae88c 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -53,9 +53,6 @@ void SetupBossProfileNatives() CreateNative("SF2_GetBossProfileFloat", Native_GetBossProfileFloat); CreateNative("SF2_GetBossProfileString", Native_GetBossProfileString); CreateNative("SF2_GetBossProfileVector", Native_GetBossProfileVector); - CreateNative("SF2_GetBossProfileDifficultyNumValues", Native_GetBossProfileDifficultyNumValues); - CreateNative("SF2_GetBossProfileDifficultyBoolValues", Native_GetBossProfileDifficultyBoolValues); - CreateNative("SF2_GetBossProfileDifficultyFloatValues", Native_GetBossProfileDifficultyFloatValues); CreateNative("SF2_GetBossAttackProfileNum", Native_GetBossAttackProfileNum); CreateNative("SF2_GetBossAttackProfileFloat", Native_GetBossAttackProfileFloat); CreateNative("SF2_GetBossAttackProfileString", Native_GetBossAttackProfileString); @@ -67,6 +64,8 @@ void SetupBossProfileNatives() CreateNative("SF2_GetBossProfileData", Native_GetBossProfileData); CreateNative("SF2_GetChaserBossProfileData", Native_GetChaserBossProfileData); CreateNative("SF2_GetStatueBossProfileData", Native_GetStatueBossProfileData); + CreateNative("SF2_TranslateProfileActivityFromName", Native_TranslateProfileActivityFromName); + CreateNative("SF2_LookupProfileAnimation", Native_LookupProfileAnimation); } void InitializeBossProfiles() @@ -188,6 +187,22 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_DIE_GUTSHOT", ACT_DIE_GUTSHOT); g_Activities.SetValue("ACT_DIE_BACKSHOT", ACT_DIE_BACKSHOT); + // Rage activites + g_Activities.SetValue("ACT_BUSY_QUEUE", ACT_BUSY_QUEUE); + + // Flee starting activities + g_Activities.SetValue("ACT_SIGNAL1", ACT_SIGNAL1); + g_Activities.SetValue("ACT_SIGNAL2", ACT_SIGNAL2); + g_Activities.SetValue("ACT_SIGNAL3", ACT_SIGNAL3); + + // Heal activities + g_Activities.SetValue("ACT_USE", ACT_USE); + g_Activities.SetValue("ACT_BUSY_QUEUE", ACT_BUSY_QUEUE); + g_Activities.SetValue("ACT_SHIELD_UP", ACT_SHIELD_UP); + g_Activities.SetValue("ACT_SHIELD_UP_IDLE", ACT_SHIELD_UP_IDLE); + g_Activities.SetValue("ACT_CROUCHING_SHIELD_UP", ACT_CROUCHING_SHIELD_UP); + g_Activities.SetValue("ACT_CROUCHING_SHIELD_UP_IDLE", ACT_CROUCHING_SHIELD_UP_IDLE); + // Misc activities g_Activities.SetValue("ACT_TRANSITION", ACT_TRANSITION); // Spawn animation g_Activities.SetValue("ACT_DISARM", ACT_DISARM); // Spawn animation @@ -212,6 +227,24 @@ Activity TranslateProfileActivityFromName(const char[] activityName) return ACT_INVALID; } +int LookupProfileAnimation(int entity, const char[] animName) +{ + CBaseAnimating animator = CBaseAnimating(entity); + + int sequence = -1; + Activity activity = TranslateProfileActivityFromName(animName); + if (activity != ACT_INVALID) + { + sequence = animator.SelectWeightedSequence(activity); + } + else + { + sequence = animator.LookupSequence(animName); + } + + return sequence; +} + /* Command */ @@ -352,6 +385,15 @@ void UnloadBossProfile(const char[] profile) g_SelectableRenevantBossAdminProfileList.Erase(index); } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + strcopy(setProfile, sizeof(setProfile), profile); + UnregisterPvESlenderBoss(setProfile); + } + g_BossProfileData.Remove(profile); g_Config.Rewind(); @@ -376,6 +418,10 @@ void ClearBossProfiles() continue; } + Call_StartForward(g_OnBossProfileUnloadedFwd); + Call_PushString(profile); + Call_Finish(); + PreUnloadBossProfile(profile); } @@ -505,7 +551,7 @@ void ReloadBossProfiles() char bossPackName[128]; g_BossPackConfig.GetSectionName(bossPackName, sizeof(bossPackName)); - bool autoLoad = !!g_BossPackConfig.GetNum("autoload"); + bool autoLoad = g_BossPackConfig.GetNum("autoload") != 0; if (autoLoad || (mapBossPack[0] != '\0' && strcmp(mapBossPack, bossPackName) == 0)) { @@ -800,7 +846,7 @@ void InitiateBossPackVote(int initiator) do { - if (!g_BossPackConfig.GetNum("autoload") && !!g_BossPackConfig.GetNum("show_in_vote", 1)) + if (!g_BossPackConfig.GetNum("autoload") && g_BossPackConfig.GetNum("show_in_vote", 1) != 0) { char bossPack[128]; g_BossPackConfig.GetSectionName(bossPack, sizeof(bossPack)); @@ -1263,6 +1309,20 @@ static any Native_GetStatueBossProfileData(Handle plugin,int numParams) return g_StatueBossProfileData; } +static any Native_TranslateProfileActivityFromName(Handle plugin, int numParams) +{ + char activityName[64]; + GetNativeString(1, activityName, sizeof(activityName)); + return TranslateProfileActivityFromName(activityName); +} + +static any Native_LookupProfileAnimation(Handle plugin, int numParams) +{ + char animationName[64]; + GetNativeString(2, animationName, sizeof(animationName)); + return LookupProfileAnimation(GetNativeCell(1), animationName); +} + static any Native_IsBossProfileValid(Handle plugin,int numParams) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -1331,42 +1391,6 @@ static any Native_GetBossProfileVector(Handle plugin,int numParams) return success; } -static any Native_GetBossProfileDifficultyNumValues(Handle plugin,int numParams) -{ - char keyValue[PLATFORM_MAX_PATH]; - GetNativeString(2, keyValue, sizeof(keyValue)); - int result[Difficulty_Max]; - int defaultValue[Difficulty_Max]; - GetNativeArray(3, result, Difficulty_Max); - GetNativeArray(4, defaultValue, Difficulty_Max); - GetProfileDifficultyNumValues(GetNativeCell(1), keyValue, result, defaultValue); - return 0; -} - -static any Native_GetBossProfileDifficultyBoolValues(Handle plugin,int numParams) -{ - char keyValue[PLATFORM_MAX_PATH]; - GetNativeString(2, keyValue, sizeof(keyValue)); - bool result[Difficulty_Max]; - bool defaultValue[Difficulty_Max]; - GetNativeArray(3, result, Difficulty_Max); - GetNativeArray(4, defaultValue, Difficulty_Max); - GetProfileDifficultyBoolValues(GetNativeCell(1), keyValue, result, defaultValue); - return 0; -} - -static any Native_GetBossProfileDifficultyFloatValues(Handle plugin,int numParams) -{ - char keyValue[PLATFORM_MAX_PATH]; - GetNativeString(2, keyValue, sizeof(keyValue)); - float result[Difficulty_Max]; - float defaultValue[Difficulty_Max]; - GetNativeArray(3, result, Difficulty_Max); - GetNativeArray(4, defaultValue, Difficulty_Max); - GetProfileDifficultyFloatValues(GetNativeCell(1), keyValue, result, defaultValue); - return 0; -} - static any Native_GetBossAttackProfileNum(Handle plugin,int numParams) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -1438,9 +1462,9 @@ static any Native_GetRandomStringFromBossProfile(Handle plugin,int numParams) int bufferLen = GetNativeCell(4); char[] buffer = new char[bufferLen]; - int iIndex = GetNativeCell(5); + int index = GetNativeCell(5); - bool success = GetRandomStringFromProfile(profile, keyValue, buffer, bufferLen, iIndex); + bool success = GetRandomStringFromProfile(profile, keyValue, buffer, bufferLen, index); SetNativeString(3, buffer, bufferLen); return success; } diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp index 73df0d24..1efbc9eb 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp @@ -17,15 +17,12 @@ void InitializeChaserProfiles() void UnloadChaserBossProfile(const char[] profile) { - char tempProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - if (!g_ChaserBossProfileData.GetString(profile, tempProfile, sizeof(tempProfile)) || tempProfile[0] == '\0') + SF2ChaserBossProfileData chaserProfileData; + if (!g_ChaserBossProfileData.GetArray(profile, chaserProfileData, sizeof(chaserProfileData))) { return; } - SF2ChaserBossProfileData chaserProfileData; - g_ChaserBossProfileData.GetArray(profile, chaserProfileData, sizeof(chaserProfileData)); - chaserProfileData.Destroy(); g_ChaserBossProfileData.Remove(profile); @@ -33,2384 +30,16 @@ void UnloadChaserBossProfile(const char[] profile) static SF2ChaserBossProfileData g_CachedProfileData; -bool GetChaserProfileEndlessChasingState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasesEndlessly; -} - -bool GetChaserProfileAutoChaseState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseEnabled; -} - -bool GetChaserProfileAutoChaseSprinterState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseSprinters; -} - -int GetChaserProfileAutoChaseCount(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseCount[difficulty]; -} - -int GetChaserProfileAutoChaseAddGeneral(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAdd[difficulty]; -} - -int GetChaserProfileAutoChaseAddFootstep(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddFootstep[difficulty]; -} - -int GetChaserProfileAutoChaseAddLoudFootstep(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddLoudFootstep[difficulty]; -} - -int GetChaserProfileAutoChaseAddVoice(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddVoice[difficulty]; -} - -int GetChaserProfileAutoChaseAddWeapon(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AutoChaseAddWeapon[difficulty]; -} - -float GetChaserProfileWalkSpeed(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WalkSpeed[difficulty]; -} - -float GetChaserProfileMaxWalkSpeed(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MaxWalkSpeed[difficulty]; -} - -float GetChaserProfileAlertGracetime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AlertGracetime[difficulty]; -} - -float GetChaserProfileAlertDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AlertDuration[difficulty]; -} - -float GetChaserProfileChaseDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDuration[difficulty]; -} - -float GetChaserProfileChaseDurationAddVisibleMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibleMin; -} - -float GetChaserProfileChaseDurationAddVisibleMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibleMax; -} - -float GetChaserProfileSoundPosDiscardTime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SoundPosDiscardTime[difficulty]; -} - -float GetChaserProfileSoundPosDistanceTolerance(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SoundPosDistanceTolerance[difficulty]; -} - -float GetChaserProfileChasePersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasePersistencyTimeInit; -} - -float GetChaserProfileChaseAttackPersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseAttackPersistencyTimeInit; -} - -float GetChaserProfileChaseAttackPersistencyTimeAdd(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseAttackPersistencyTimeAdd; -} - -float GetChaserProfileChaseNewTargetPersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseNewTargetPersistencyTimeInit; -} - -float GetChaserProfileChaseNewTargetPersistencyTimeAdd(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseNewTargetPersistencyTimeAdd; -} - -float GetChaserProfileChasePersistencyAddVisibleMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasePersistencyAddVisibleMin; -} - -float GetChaserProfileChasePersistencyAddVisibleMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChasePersistencyAddVisibleMax; -} - -float GetChaserProfileChaseStunPersistencyTimeInit(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseStunPersistencyTimeInit; -} - -float GetChaserProfileChaseStunPersistencyTimeAdd(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseStunPersistencyTimeAdd; -} - -float GetChaserProfileHearFootstepCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FootstepSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearLoudFootstepCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.LoudFootstepSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearQuietFootstepCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.QuietFootstepSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearVoiceCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.VoiceSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearWeaponCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WeaponSenses.Cooldown[difficulty]; -} - -float GetChaserProfileHearFlashlightCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightSenses.Cooldown[difficulty]; -} - -int GetChaserProfileFootstepAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FootstepSenses.AddCount[difficulty]; -} - -int GetChaserProfileLoudFootstepAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.LoudFootstepSenses.AddCount[difficulty]; -} - -int GetChaserProfileQuietFootstepAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.QuietFootstepSenses.AddCount[difficulty]; -} - -int GetChaserProfileVoiceAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.VoiceSenses.AddCount[difficulty]; -} - -int GetChaserProfileWeaponAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WeaponSenses.AddCount[difficulty]; -} - -int GetChaserProfileFlashlightAddThreshold(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightSenses.AddCount[difficulty]; -} - -float GetChaserProfileWanderRangeMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderRangeMin[difficulty]; -} - -float GetChaserProfileWanderRangeMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderRangeMax[difficulty]; -} - -float GetChaserProfileWanderTimeMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderTimeMin[difficulty]; -} - -float GetChaserProfileWanderTimeMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderTimeMax[difficulty]; -} - -float GetChaserProfileWanderEnterTimeMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderEnterTimeMin[difficulty]; -} - -float GetChaserProfileWanderEnterTimeMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WanderEnterTimeMax[difficulty]; -} - -float GetChaserProfileProjectileCooldownMin(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileCooldownMin[difficulty]; -} - -float GetChaserProfileProjectileCooldownMax(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileCooldownMax[difficulty]; -} - -float GetChaserProfileIceballSlowdownDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IceballSlowDuration[difficulty]; -} - -float GetChaserProfileIceballSlowdownPercent(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IceballSlowPercent[difficulty]; -} - -float GetChaserProfileProjectileSpeed(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileSpeed[difficulty]; -} - -float GetChaserProfileProjectileDamage(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileDamage[difficulty]; -} - -float GetChaserProfileProjectileRadius(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileRadius[difficulty]; -} - -float GetChaserProfileProjectileDeviation(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileDeviation[difficulty]; -} - -int GetChaserProfileProjectileCount(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileCount[difficulty]; -} - -float GetChaserProfileRandomEffectDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectDuration[difficulty]; -} - -float GetChaserProfileRandomEffectSlowdown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectSlowdown[difficulty]; -} - -float GetChaserProfileJaratePlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateDuration[difficulty]; -} - -float GetChaserProfileMilkPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkDuration[difficulty]; -} - -float GetChaserProfileGasPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasDuration[difficulty]; -} - -float GetChaserProfileMarkPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MarkDuration[difficulty]; -} - -float GetChaserProfileSilentMarkPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentMarkDuration[difficulty]; -} - -float GetChaserProfileIgnitePlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteDuration[difficulty]; -} - -float GetChaserProfileIgnitePlayerDelay(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteDelay[difficulty]; -} - -float GetChaserProfileStunPlayerDuration(const char[] profile, int difficulty) +float GetChaserProfileDeathHealth(const char[] profile, int difficulty) { g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectDuration[difficulty]; + return g_CachedProfileData.DeathData.Health[difficulty]; } -float GetChaserProfileStunPlayerSlowdown(const char[] profile, int difficulty) +bool GetChaserProfileBoxingState(const char[] profile) { g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectSlowdown[difficulty]; -} - -float GetChaserProfileBleedPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BleedDuration[difficulty]; -} - -float GetChaserProfileEletricPlayerDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricDuration[difficulty]; -} - -float GetChaserProfileEletricPlayerSlowdown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricSlowdown[difficulty]; -} - -float GetChaserProfileWakeRadius(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.WakeRadius; -} - -int GetChaserProfileAttackCount(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Attacks.Length; -} - -ArrayList GetChaserProfileAttackPropModels(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AttackPropModels; -} - -int GetChaserProfileRandomAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectIndexes; -} - -int GetChaserProfileRandomAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RandomEffectIndexesString); -} - -int GetChaserProfileRandomStunType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomEffectStunType; -} - -int GetChaserProfileJarateAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateIndexes; -} - -int GetChaserProfileJarateAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.JarateIndexesString); -} - -int GetChaserProfileMilkAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkIndexes; -} - -int GetChaserProfileMilkAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MilkIndexesString); -} - -int GetChaserProfileGasAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasIndexes; -} - -int GetChaserProfileGasAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GasIndexesString); -} - -int GetChaserProfileMarkAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MarkIndexes; -} - -int GetChaserProfileMarkAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MarkIndexesString); -} - -int GetChaserProfileSilentMarkAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentMarkIndexes; -} - -int GetChaserProfileSilentMarkAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SilentMarkIndexesString); -} - -int GetChaserProfileIgniteAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteIndexes; -} - -int GetChaserProfileIgniteAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.IgniteIndexesString); -} - -int GetChaserProfileStunAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectIndexes; -} - -int GetChaserProfileStunAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.StunEffectIndexesString); -} - -int GetChaserProfileStunDamageType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectType; -} - -int GetChaserProfileBleedAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BleedIndexes; -} - -int GetChaserProfileBleedAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.BleedIndexesString); -} - -int GetChaserProfileEletricAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricIndexes; -} - -int GetChaserProfileEletricAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ElectricIndexesString); -} - -int GetChaserProfileSmiteAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteIndexes; -} - -int GetChaserProfileSmiteAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SmiteIndexesString); -} - -int GetChaserProfileSmiteDamageType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteDamageType; -} - -void GetChaserProfileSmiteColor(const char[] profile, int buffer[4]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.SmiteColor; -} - -float GetChaserProfileSmiteDamage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteDamage; -} - -int GetChaserProfileSmiteHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SmiteHitSound); -} - -int GetChaserProfileAttackType(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::Type); -} - -void GetChaserProfileAttackPunchVelocity(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.PunchVelocity; -} - -bool GetChaserProfileAttackDisappear(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Disappear[difficulty]; -} - -int GetChaserProfileAttackRepeat(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::Repeat); -} - -int GetChaserProfileMaxAttackRepeats(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::MaxRepeats); -} - -ArrayList GetChaserProfileAttackRepeatTimers(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return null; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RepeatTimers; -} - -bool GetChaserProfileAttackIgnoreAlwaysLooking(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.IgnoreAlwaysLooking[difficulty]; -} - -int GetChaserProfileAttackWeaponTypeInt(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::WeaponInt); -} - -int GetChaserProfileAttackWeaponTypeString(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.WeaponString); -} - -float GetChaserProfileAttackDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Damage[difficulty]; -} - -float GetChaserProfileAttackLifeStealDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LifeStealDuration[difficulty]; -} - -float GetChaserProfileAttackProjectileDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileDamage[difficulty]; -} - -float GetChaserProfileAttackProjectileSpeed(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileSpeed[difficulty]; -} - -float GetChaserProfileAttackProjectileRadius(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileRadius[difficulty]; -} - -float GetChaserProfileAttackProjectileDeviation(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileDeviation[difficulty]; -} - -int GetChaserProfileAttackProjectileCount(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ProjectileCount[difficulty]; -} - -bool GetChaserProfileAttackCritProjectiles(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.CritProjectiles[difficulty]; -} - -int GetChaserProfileAttackProjectileType(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::ProjectileType); -} - -void GetChaserProfileAttackProjectileOffset(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.ProjectileOffset; -} - -float GetChaserProfileAttackProjectileIceSlowPercent(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.IceballSlowdownPercent[difficulty]; -} - -float GetChaserProfileAttackProjectileIceSlowDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.IceballSlowdownDuration[difficulty]; -} - -int GetChaserProfileAttackFireballTrail(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.FireballTrail); -} - -int GetChaserProfileAttackIceballTrail(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.IceballTrail); -} - -int GetChaserProfileAttackRocketModel(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.RocketModel); -} - -int GetChaserProfileAttackBulletCount(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BulletCount[difficulty]; -} - -float GetChaserProfileAttackBulletDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BulletDamage[difficulty]; -} - -float GetChaserProfileAttackBulletSpread(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BulletSpread[difficulty]; -} - -int GetChaserProfileAttackBulletTrace(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.BulletTrace); -} - -void GetChaserProfileAttackBulletOffset(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.BulletOffset; -} - -float GetChaserProfileAttackLaserDamage(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LaserDamage[difficulty]; -} - -float GetChaserProfileAttackLaserSize(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::LaserSize); -} - -void GetChaserProfileAttackLaserColor(const char[] profile, int attackIndex, int buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.LaserColor; -} - -bool GetChaserProfileEnableLaserAttachment(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::LaserAttachment); -} - -float GetChaserProfileAttackLaserDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LaserDuration[difficulty]; -} - -float GetChaserProfileAttackLaserNoise(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::LaserNoise); -} - -int GetChaserProfileAttackLaserAttachmentName(const char[] profile, int attackIndex, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return strcopy(buffer, bufferlen, attackData.LaserAttachmentName); -} - -void GetChaserProfileAttackLaserOffset(const char[] profile, int attackIndex, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - buffer = attackData.LaserOffset; -} - -bool GetChaserProfileAttackPullIn(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::PullIn); -} - -float GetChaserProfileAttackDamageVsProps(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::DamageVsProps); -} - -float GetChaserProfileAttackDamageForce(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DamageForce[difficulty]; -} - -int GetChaserProfileAttackDamageType(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DamageType[difficulty]; -} - -float GetChaserProfileAttackDamageDelay(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DamageDelay[difficulty]; -} - -float GetChaserProfileAttackRange(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Range[difficulty]; -} - -float GetChaserProfileAttackDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Duration[difficulty]; -} - -float GetChaserProfileAttackSpread(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Spread[difficulty]; -} - -float GetChaserProfileAttackBeginRange(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BeginRange[difficulty]; -} - -float GetChaserProfileAttackBeginFOV(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.BeginFOV[difficulty]; -} - -float GetChaserProfileAttackCooldown(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.Cooldown[difficulty]; -} - -bool GetChaserProfileAttackLifeStealState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.LifeSteal[difficulty]; -} - -bool GetChaserProfileAttackRunWhileAttackingState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.AttackWhileRunning[difficulty]; -} - -float GetChaserProfileAttackRunSpeed(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RunSpeed[difficulty]; -} - -float GetChaserProfileAttackRunDuration(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RunDuration[difficulty]; -} - -float GetChaserProfileAttackRunDelay(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.RunDelay[difficulty]; -} - -int GetChaserProfileAttackUseOnDifficulty(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::UseOnDifficulty); -} - -int GetChaserProfileAttackBlockOnDifficulty(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::BlockOnDifficulty); -} - -int GetChaserProfileAttackExplosiveDanceRadius(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.ExplosiveDanceRadius[difficulty]; -} - -bool GetChaserProfileAttackGesturesState(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::Gestures); -} - -bool GetChaserProfileAttackDeathCamLowHealth(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::DeathCamLowHealth); -} - -bool GetChaserProfileAttackChaseInitialInterruptState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.DontInterruptChaseInitial[difficulty]; -} - -float GetChaserProfileAttackUseOnHealth(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::UseOnHealth); -} - -float GetChaserProfileAttackBlockOnHealth(const char[] profile, int attackIndex) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - return g_CachedProfileData.Attacks.Get(attackIndex, SF2ChaserBossProfileAttackData::BlockOnHealth); -} - -bool GetChaserProfileAttackCancelLOSState(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return false; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.CancelLos[difficulty]; -} - -float GetChaserProfileAttackCancelDistance(const char[] profile, int attackIndex, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - if (g_CachedProfileData.Attacks.Length <= attackIndex) - { - return 0.0; - } - SF2ChaserBossProfileAttackData attackData; - g_CachedProfileData.Attacks.GetArray(attackIndex, attackData, sizeof(attackData)); - return attackData.CancelDistance[difficulty]; -} - -bool GetChaserProfileEnableAdvancedDamageEffects(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AdvancedDamageEffects; -} - -bool GetChaserProfileEnableAdvancedDamageEffectsRandom(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RandomAdvancedDamageEffects; -} - -bool GetChaserProfileEnableAdvancedDamageParticles(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AdvancedDamageEffectParticles; -} - -bool GetChaserProfileJarateState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateEffects; -} - -bool GetChaserProfileJarateBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.JarateBeamParticle; -} - -int GetChaserProfileJarateParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.JarateParticle); -} - -int GetChaserProfileJarateHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.JarateHitSound); -} - -bool GetChaserProfileMilkState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkEffects; -} - -bool GetChaserProfileMilkBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MilkBeamParaticle; -} - -int GetChaserProfileMilkParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MilkParticle); -} - -int GetChaserProfileMilkHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.MilkHitSound); -} - -bool GetChaserProfileGasState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasEffects; -} - -bool GetChaserProfileGasBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GasBeamParticle; -} - -int GetChaserProfileGasParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GasParticle); -} - -int GetChaserProfileGasHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GasHitSound); -} - -bool GetChaserProfileMarkState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MarkEffects; -} - -bool GetChaserProfileSilentMarkState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentMarkEffects; -} - -bool GetChaserProfileIgniteState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IgniteEffects; -} - -bool GetChaserProfileStunAttackState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffects; -} - -bool GetChaserProfileStunAttackBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEffectBeamParticle; -} - -int GetChaserProfileStunParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.StunParticle); -} - -int GetChaserProfileStunHitSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.StunHitSound); -} - -bool GetChaserProfileBleedState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BleedEffects; -} - -bool GetChaserProfileEletricAttackState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricEffects; -} - -bool GetChaserProfileEletricBeamParticle(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectricBeamParticle; -} - -int GetChaserProfileElectricRedParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ElectricParticleRed); -} - -int GetChaserProfileElectricBlueParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ElectricParticleBlue); -} - -bool GetChaserProfileSmiteState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteEffects; -} - -bool GetChaserProfileSmiteMessage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SmiteMessage; -} - -bool GetChaserProfileXenobladeCombo(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeCombo; -} - -float GetChaserProfileXenobladeBreakDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeDuration; -} - -float GetChaserProfileXenobladeToppleDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeToppleDuration; -} - -float GetChaserProfileXenobladeToppleSlowdown(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeToppleSlowdown; -} - -float GetChaserProfileXenobladeDazeDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.XenobladeDazeDuration; -} - -bool GetChaserProfileCloakState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CanCloak; -} - -bool GetChaserProfileProjectileState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectilesEnabled; -} - -bool GetChaserProfileCriticalRockets(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CriticalProjectiles; -} - -int GetChaserProfileFireballShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.FireballShootSound); -} - -int GetChaserProfileFireballTrail(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.FireballTrail); -} - -int GetChaserProfileFireballExplodeSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.FireballExplodeSound); -} - -int GetChaserProfileIceballSlowSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.IceballSlowSound); -} - -int GetChaserProfileIceballTrail(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.IceballTrail); -} - -int GetChaserProfileRocketShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketShootSound); -} - -int GetChaserProfileRocketModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketModel); -} - -int GetChaserProfileRocketTrail(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketTrail); -} - -int GetChaserProfileRocketExplodeParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketExplodeParticle); -} - -int GetChaserProfileRocketExplodeSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.RocketExplodeSound); -} - -int GetChaserProfileSentryRocketShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SentryRocketShootSound); -} - -int GetChaserProfileGrenadeShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.GrenadeShootSound); -} - -int GetChaserProfileArrowShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ArrowShootSound); -} - -int GetChaserProfileManglerShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ManglerShootSound); -} - -int GetChaserProfileBaseballShootSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.BaseballShootSound); -} - -bool GetChaserProfileGestureShoot(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShootGestures; -} - -int GetChaserProfileShootGestureName(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShootGestureName); -} - -bool GetChaserProfileShootAnimationsState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShootAnimations; -} - -bool GetChaserProfileProjectileAmmoState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileClips; -} - -bool GetChaserProfileChargeUpProjectilesState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChargeUpProjectiles; -} - -int GetChaserProfileRandomProjectilePosMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileRandomPosMin; -} - -int GetChaserProfileRandomProjectilePosMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileRandomPosMax; -} - -int GetChaserProfileBaseballModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.BaseballModel); -} - -ArrayList GetChaserProfileProjectilePositionsArray(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectilePosOffsets; -} - -int GetChaserProfileProjectileType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileType; -} - -int GetChaserProfileProjectileLoadedAmmo(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileClipSize[difficulty]; -} - -float GetChaserProfileProjectileAmmoReloadTime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileReloadTime[difficulty]; -} - -float GetChaserProfileProjectileChargeUpTime(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileChargeUp[difficulty]; -} - -bool GetChaserProfileDamageParticleState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DamageParticles; -} - -int GetChaserProfileDamageParticleName(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DamageParticleName); -} - -bool GetChaserProfileDamageParticleBeamState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DamageParticleBeam; -} - -int GetChaserProfileDamageParticleSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DamageParticleSound); -} - -bool GetChaserProfileStunState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunEnabled; -} - -float GetChaserProfileStunCooldown(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunCooldown; -} - -bool GetChaserProfileStunFlashlightState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightStun; -} - -float GetChaserProfileStunFlashlightDamage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FlashlightDamage; -} - -bool GetChaserProfileStunOnChaseInitial(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseInitialOnStun; -} - -ArrayList GetChaserProfileDamageResistances(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DamageResistances; -} - -float GetChaserProfileStunHealth(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunHealth; -} - -float GetChaserProfileStunHealthPerPlayer(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunHealthPerPlayer; -} - -float GetChaserProfileStunHealthPerClass(const char[] profile, int index) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunHealthPerClass[index]; -} - -bool GetChaserProfileKeyDrop(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.KeyDrop; -} - -int GetChaserProfileKeyModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.KeyModel); -} - -int GetChaserProfileKeyTrigger(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.KeyTrigger); -} - -bool GetChaserProfileDisappearOnStun(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DisappearOnStun; -} - -bool GetChaserProfileStunItemDropState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ItemDropOnStun; -} - -int GetChaserProfileStunItemDropType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.StunItemDropType; -} - -bool GetChaserProfileBoxingState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BoxingBoss; -} - -bool GetChaserProfileNormalSoundHook(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.NormalSoundHook; -} - -bool GetChaserProfileChaseOnLook(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseOnLook; -} - -bool GetChaserProfileEarthquakeFootstepState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootsteps; -} - -float GetChaserProfileEarthquakeFootstepAmplitude(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepAmplitude; -} - -float GetChaserProfileEarthquakeFootstepFrequency(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepFrequency; -} - -float GetChaserProfileEarthquakeFootstepDuration(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepDuration; -} - -float GetChaserProfileEarthquakeFootstepRadius(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepRadius; -} - -bool GetChaserProfileEarthquakeFootstepAirShake(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EarthquakeFootstepAirShake; -} - -int GetChaserProfileSoundCountToAlert(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SoundCountToAlert[difficulty]; -} - -float GetChaserProfileCloakCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakCooldown[difficulty]; -} - -float GetChaserProfileCloakRange(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakRange[difficulty]; -} - -float GetChaserProfileDecloakRange(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DecloakRange[difficulty]; -} - -float GetChaserProfileCloakDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakDuration[difficulty]; -} - -float GetChaserProfileCloakSpeedMultiplier(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakSpeedMultiplier[difficulty]; -} - -bool GetChaserProfileCloakToHeal(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakToHeal; -} - -int GetChaserProfileCloakParticle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.CloakParticle); -} - -void GetChaserProfileCloakRenderColor(const char[] profile, int buffer[4]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.CloakRenderColor; -} - -int GetChaserProfileCloakRenderMode(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakRenderMode; -} - -int GetChaserProfileCloakOnSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.CloakOnSound); -} - -int GetChaserProfileCloakOffSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.CloakOffSound); -} - -bool GetChaserProfileChaseInitialAnimationState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseInitialAnimations; -} - -bool GetChaserProfileSpawnAnimationState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SpawnAnimationsEnabled; -} - -bool GetChaserProfileOldAnimState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OldAnimationAI; -} - -bool GetChaserProfileAlertWalkingState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AlertWalkingAnimation; -} - -bool GetChaserProfileUnnerfedVisibility(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.UnnerfedVisibility; -} - -bool GetChaserProfileClearLayersState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ClearLayersOnAnimUpdate; -} - -float GetChaserProfileHealAnimTimer(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealAnimationTimer; -} - -float GetChaserProfileHealFunctionTimer(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealFunctionTimer; -} - -float GetChaserProfileHealRangeMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealRangeMin; -} - -float GetChaserProfileHealRangeMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealRangeMax; -} - -float GetChaserProfileHealTimeMin(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealTimeMin; -} - -float GetChaserProfileHealTimeMax(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HealTimeMax; -} - -float GetChaserProfileAfterburnMultiplier(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AfterburnMultiplier; -} - -float GetChaserProfileBackstabDamageScale(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BackstabDamageScale; -} - -bool GetChaserProfileShockwaveState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Shockwaves; -} - -float GetChaserProfileShockwaveHeight(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveHeight[difficulty]; -} - -float GetChaserProfileShockwaveRange(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveRange[difficulty]; -} - -float GetChaserProfileShockwaveDrain(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveDrain[difficulty]; -} - -float GetChaserProfileShockwaveForce(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveForce[difficulty]; -} - -bool GetChaserProfileShockwaveStunState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveStun; -} - -float GetChaserProfileShockwaveStunDuration(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveStunDuration[difficulty]; -} - -float GetChaserProfileShockwaveStunSlowdown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveStunSlowdown[difficulty]; -} - -int GetChaserProfileShockwaveAttackIndexes(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveIndexes; -} - -int GetChaserProfileShockwaveAttackIndexesString(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShockwaveIndexesString); -} - -float GetChaserProfileShockwaveWidth1(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveWidths[0]; -} - -float GetChaserProfileShockwaveWidth2(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveWidths[1]; -} - -float GetChaserProfileShockwaveAmplitude(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveAmplitude; -} - -void GetChaserProfileShockwaveColor1(const char[] profile, int buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.ShockwaveColor1; -} - -void GetChaserProfileShockwaveColor2(const char[] profile, int buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.ShockwaveColor2; -} - -int GetChaserProfileShockwaveAlpha1(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveAlpha1; -} - -int GetChaserProfileShockwaveAlpha2(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveAlpha2; -} - -int GetChaserProfileShockwaveBeamSprite(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShockwaveBeamSprite); -} - -int GetChaserProfileShockwaveHaloSprite(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.ShockwaveHaloSprite); -} - -int GetChaserProfileShockwaveBeamModel(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveBeamModel; -} - -int GetChaserProfileShockwaveHaloModel(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ShockwaveHaloModel; -} - -bool GetChaserProfileTrapState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Traps; -} - -int GetChaserProfileTrapType(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TrapType; -} - -float GetChaserProfileTrapSpawnCooldown(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TrapCooldown[difficulty]; -} - -int GetChaserProfileTrapModel(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapModel); -} - -int GetChaserProfileTrapDeploySound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapDeploySound); -} - -int GetChaserProfileTrapMissSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapMissSound); -} - -int GetChaserProfileTrapCatchSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapCatchSound); -} - -int GetChaserProfileTrapAnimIdle(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapAnimIdle); -} - -int GetChaserProfileTrapAnimClose(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapAnimClose); -} - -int GetChaserProfileTrapAnimOpen(const char[] profile, char[] buffer, int bufferlen) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.TrapAnimOpen); -} - -bool GetChaserProfileCrawlState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Crawling; -} - -float GetChaserProfileCrawlSpeedMultiplier(const char[] profile, int difficulty) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CrawlSpeedMultiplier[difficulty]; -} - -void GetChaserProfileCrawlMins(const char[] profile, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.CrawlDetectMins; -} - -void GetChaserProfileCrawlMaxs(const char[] profile, float buffer[3]) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.CrawlDetectMaxs; -} - -bool GetChaserProfileSelfHealState(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHeal; -} - -float GetChaserProfileSelfHealStartPercentage(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealPercentageStart; -} - -float GetChaserProfileSelfHealPercentageOne(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealRecover[0]; -} - -float GetChaserProfileSelfHealPercentageTwo(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealRecover[1]; -} - -float GetChaserProfileSelfHealPercentageThree(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SelfHealRecover[2]; -} - -void GetChaserProfileIdleSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.IdleSounds; -} - -void GetChaserProfileAlertSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AlertSounds; -} - -void GetChaserProfileChasingSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.ChasingSounds; -} - -void GetChaserProfileChaseInitialSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.ChaseInitialSounds; -} - -void GetChaserProfileStunSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.StunnedSounds; -} - -void GetChaserProfileAttackKilledSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AttackKilledSounds; -} - -void GetChaserProfileAttackKilledAllSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AttackKilledAllSounds; -} - -void GetChaserProfileAttackKilledClientSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.AttackKilledClientSounds; -} - -void GetChaserProfileRageAllSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.RageAllSounds; -} - -void GetChaserProfileRageTwoSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.RageTwoSounds; -} - -void GetChaserProfileRageThreeSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.RageThreeSounds; -} - -void GetChaserProfileSelfHealSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.SelfHealSounds; -} - -void GetChaserProfileFootstepSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.FootstepSounds; -} - -ArrayList GetChaserProfileAttackSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AttackSounds; -} - -ArrayList GetChaserProfileHitSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.HitSounds; -} - -ArrayList GetChaserProfileMissSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MissSounds; -} - -ArrayList GetChaserProfileBulletShootSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BulletShootSounds; -} - -ArrayList GetChaserProfileProjectileShootSounds(const char[] profile) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ProjectileShootSounds; + return g_CachedProfileData.BoxingBoss; } void GetChaserProfileChaseMusics(const char[] profile, SF2BossProfileSoundInfo params) diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index bd307e14..3ead2c18 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -10,22 +10,18 @@ * Parses and stores the unique values of a chaser profile from the current position in the profiles config. * Returns true if loading was successful, false if not. */ -bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen) +bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen, SF2BossProfileData baseData) { strcopy(loadFailReasonBuffer, loadFailReasonBufferLen, ""); SF2ChaserBossProfileData profileData; profileData.Init(); - SF2BossProfileData g_CachedProfileData; - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); + profileData.UnnerfedVisibility = kv.GetNum("old_boss_visibility", profileData.UnnerfedVisibility) != 0; - profileData.UnnerfedVisibility = !!kv.GetNum("old_boss_visibility", profileData.UnnerfedVisibility); - - profileData.ClearLayersOnAnimUpdate = !!kv.GetNum("animation_clear_layers_on_update", profileData.ClearLayersOnAnimUpdate); + profileData.ClearLayersOnAnimUpdate = kv.GetNum("animation_clear_layers_on_update", profileData.ClearLayersOnAnimUpdate) != 0; GetProfileDifficultyFloatValues(kv, "walkspeed", profileData.WalkSpeed, profileData.WalkSpeed); - GetProfileDifficultyFloatValues(kv, "walkspeed_max", profileData.MaxWalkSpeed, profileData.MaxWalkSpeed); profileData.WakeRadius = kv.GetFloat("wake_radius", profileData.WakeRadius); if (profileData.WakeRadius < 0.0) @@ -33,21 +29,50 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.WakeRadius = 0.0; } + if (kv.JumpToKey("idle")) + { + profileData.IdleData.Load(kv); + kv.GoBack(); + } + GetProfileDifficultyFloatValues(kv, "alert_gracetime", profileData.AlertGracetime, profileData.AlertGracetime); GetProfileDifficultyFloatValues(kv, "alert_duration", profileData.AlertDuration, profileData.AlertDuration); + + if (kv.JumpToKey("alert")) + { + GetProfileDifficultyFloatValues(kv, "gracetime", profileData.AlertGracetime, profileData.AlertGracetime); + GetProfileDifficultyFloatValues(kv, "duration", profileData.AlertDuration, profileData.AlertDuration); + GetProfileDifficultyBoolValues(kv, "run_on_wander", profileData.AlertRunOnWander, profileData.AlertRunOnWander); + GetProfileDifficultyBoolValues(kv, "run_on_suspect", profileData.AlertRunOnHearSound, profileData.AlertRunOnHearSound); + + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.AlertOnAlertInfo.Radius[i] = baseData.SearchRange[i]; + } + profileData.AlertOnAlertInfo.Load(kv); + + kv.GoBack(); + } + + if (kv.JumpToKey("chase")) + { + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.AlertOnChaseInfo.Radius[i] = baseData.SearchRange[i]; + } + profileData.AlertOnChaseInfo.Load(kv); + + kv.GoBack(); + } + GetProfileDifficultyFloatValues(kv, "chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); - profileData.ChaseDurationAddVisibleMin = kv.GetFloat("chase_duration_add_visible_min", profileData.ChaseDurationAddVisibleMin); - profileData.ChaseDurationAddVisibleMax = kv.GetFloat("chase_duration_add_visible_max", profileData.ChaseDurationAddVisibleMax); - - profileData.ChasePersistencyTimeInit = kv.GetFloat("chase_persistency_time_init", profileData.ChasePersistencyTimeInit); - profileData.ChaseAttackPersistencyTimeInit = kv.GetFloat("chase_persistency_time_init_attack", profileData.ChaseAttackPersistencyTimeInit); - profileData.ChaseAttackPersistencyTimeAdd = kv.GetFloat("chase_persistency_time_add_attack", profileData.ChaseAttackPersistencyTimeAdd); - profileData.ChaseNewTargetPersistencyTimeInit = kv.GetFloat("chase_persistency_time_init_newtarget", profileData.ChaseNewTargetPersistencyTimeInit); - profileData.ChaseNewTargetPersistencyTimeAdd = kv.GetFloat("chase_persistency_time_add_newtarget", profileData.ChaseNewTargetPersistencyTimeAdd); - profileData.ChasePersistencyAddVisibleMin = kv.GetFloat("chase_persistency_time_add_visible_min", profileData.ChasePersistencyAddVisibleMin); - profileData.ChasePersistencyAddVisibleMax = kv.GetFloat("chase_persistency_time_add_visible_max", profileData.ChasePersistencyAddVisibleMax); - profileData.ChaseStunPersistencyTimeInit = kv.GetFloat("chase_persistency_time_init_stun", profileData.ChaseStunPersistencyTimeInit); - profileData.ChaseStunPersistencyTimeAdd = kv.GetFloat("chase_persistency_time_add_stun", profileData.ChaseStunPersistencyTimeAdd); + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.ChaseDurationAddMaxRange[i] = baseData.SearchRange[i]; + } + GetProfileDifficultyFloatValues(kv, "chase_duration_add_max_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); + GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_min", profileData.ChaseDurationAddVisibleMin, profileData.ChaseDurationAddVisibleMin); + GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_max", profileData.ChaseDurationAddVisibleMax, profileData.ChaseDurationAddVisibleMax); if (kv.JumpToKey("senses")) { @@ -81,9 +106,16 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } kv.GoBack(); } + if (kv.JumpToKey("smell")) + { + profileData.SmellData.Load(kv); + + kv.GoBack(); + } kv.GoBack(); } + GetProfileDifficultyBoolValues(kv, "wander_move", profileData.CanWander, profileData.CanWander); GetProfileDifficultyFloatValues(kv, "wander_range_min", profileData.WanderRangeMin, profileData.WanderRangeMin); GetProfileDifficultyFloatValues(kv, "wander_range_max", profileData.WanderRangeMax, profileData.WanderRangeMax); GetProfileDifficultyFloatValues(kv, "wander_time_min", profileData.WanderTimeMin, profileData.WanderTimeMin); @@ -91,19 +123,11 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe GetProfileDifficultyFloatValues(kv, "wander_enter_time_min", profileData.WanderEnterTimeMin, profileData.WanderEnterTimeMin); GetProfileDifficultyFloatValues(kv, "wander_enter_time_max", profileData.WanderEnterTimeMax, profileData.WanderEnterTimeMax); - profileData.StunEnabled = !!kv.GetNum("stun_enabled", profileData.StunEnabled); + profileData.StunEnabled = kv.GetNum("stun_enabled", profileData.StunEnabled) != 0; if (profileData.StunEnabled) { - profileData.StunCooldown = kv.GetFloat("stun_cooldown", profileData.StunCooldown); - if (profileData.StunCooldown < 0.0) - { - profileData.StunCooldown = 0.0; - } - profileData.StunHealth = kv.GetFloat("stun_health", profileData.StunHealth); - if (profileData.StunHealth < 0.0) - { - profileData.StunHealth = 0.0; - } + GetProfileDifficultyFloatValues(kv, "stun_cooldown", profileData.StunCooldown, profileData.StunCooldown); + GetProfileDifficultyFloatValues(kv, "stun_health", profileData.StunHealth, profileData.StunHealth); profileData.StunHealthPerPlayer = kv.GetFloat("stun_health_per_player", profileData.StunHealthPerPlayer); if (profileData.StunHealthPerPlayer < 0.0) { @@ -118,25 +142,14 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.StunHealthPerClass[7] = kv.GetFloat("stun_health_per_pyro", profileData.StunHealthPerClass[7]); profileData.StunHealthPerClass[8] = kv.GetFloat("stun_health_per_spy", profileData.StunHealthPerClass[8]); profileData.StunHealthPerClass[9] = kv.GetFloat("stun_health_per_engineer", profileData.StunHealthPerClass[9]); - profileData.FlashlightStun = !!kv.GetNum("stun_damage_flashlight_enabled", profileData.FlashlightStun); - profileData.FlashlightDamage = kv.GetFloat("stun_damage_flashlight", profileData.FlashlightDamage); - profileData.ChaseInitialOnStun = !!kv.GetNum("chase_initial_on_stun", profileData.ChaseInitialOnStun); + GetProfileDifficultyBoolValues(kv, "stun_damage_flashlight_enabled", profileData.FlashlightStun, profileData.FlashlightStun); + GetProfileDifficultyFloatValues(kv, "stun_damage_flashlight", profileData.FlashlightDamage, profileData.FlashlightDamage); + profileData.ChaseInitialOnStun = kv.GetNum("chase_initial_on_stun", profileData.ChaseInitialOnStun) != 0; - profileData.ItemDropOnStun = !!kv.GetNum("drop_item_on_stun", profileData.ItemDropOnStun); - if (profileData.ItemDropOnStun) - { - profileData.StunItemDropType = kv.GetNum("drop_item_type", profileData.StunItemDropType); - if (profileData.StunItemDropType < 1) - { - profileData.StunItemDropType = 1; - } - if (profileData.StunItemDropType > 7) - { - profileData.StunItemDropType = 7; - } - } + GetProfileDifficultyBoolValues(kv, "drop_item_on_stun", profileData.ItemDropOnStun); + GetProfileDifficultyNumValues(kv, "drop_item_type", profileData.StunItemDropType, profileData.StunItemDropType); - profileData.DisappearOnStun = !!kv.GetNum("disappear_on_stun", profileData.DisappearOnStun); + profileData.DisappearOnStun = kv.GetNum("disappear_on_stun", profileData.DisappearOnStun) != 0; if (kv.JumpToKey("resistances")) { @@ -156,7 +169,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } - profileData.KeyDrop = !!kv.GetNum("keydrop_enabled", profileData.KeyDrop); + profileData.KeyDrop = kv.GetNum("keydrop_enabled", profileData.KeyDrop) != 0; if (profileData.KeyDrop) { kv.GetString("key_model", profileData.KeyModel, sizeof(profileData.KeyModel), profileData.KeyModel); @@ -165,25 +178,18 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } } - profileData.CanCloak = !!kv.GetNum("cloak_enable", profileData.CanCloak); - if (profileData.CanCloak) + if (kv.JumpToKey("death")) + { + profileData.DeathData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + + if (kv.JumpToKey("cloaking")) { - GetProfileDifficultyFloatValues(kv, "cloak_cooldown", profileData.CloakCooldown, profileData.CloakCooldown); - GetProfileDifficultyFloatValues(kv, "cloak_range", profileData.CloakRange, profileData.CloakRange); - GetProfileDifficultyFloatValues(kv, "cloak_decloak_range", profileData.DecloakRange, profileData.DecloakRange); - GetProfileDifficultyFloatValues(kv, "cloak_duration", profileData.CloakDuration, profileData.CloakDuration); - GetProfileDifficultyFloatValues(kv, "cloak_speed_multiplier", profileData.CloakSpeedMultiplier, profileData.CloakSpeedMultiplier); - kv.GetString("cloak_particle", profileData.CloakParticle, sizeof(profileData.CloakParticle), profileData.CloakParticle); - - GetProfileColorNoBacks(kv, "cloak_rendercolor", profileData.CloakRenderColor[0], profileData.CloakRenderColor[1], profileData.CloakRenderColor[2], profileData.CloakRenderColor[3], - g_CachedProfileData.RenderColor[0], g_CachedProfileData.RenderColor[1], g_CachedProfileData.RenderColor[2], profileData.CloakRenderColor[3]); - profileData.CloakRenderMode = kv.GetNum("cloak_rendermode", profileData.CloakRenderMode); - - kv.GetString("cloak_on_sound", profileData.CloakOnSound, sizeof(profileData.CloakOnSound), profileData.CloakOnSound); - kv.GetString("cloak_off_sound", profileData.CloakOffSound, sizeof(profileData.CloakOffSound), profileData.CloakOffSound); - TryPrecacheBossProfileSoundPath(profileData.CloakOnSound, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.CloakOffSound, g_FileCheckConVar.BoolValue); + profileData.CloakData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); } + profileData.ProjectilesEnabled = !!kv.GetNum("projectile_enable", profileData.ProjectilesEnabled); if (profileData.ProjectilesEnabled) { @@ -198,8 +204,8 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe GetProfileDifficultyFloatValues(kv, "projectile_damageradius", profileData.ProjectileRadius, profileData.ProjectileRadius); GetProfileDifficultyFloatValues(kv, "projectile_deviation", profileData.ProjectileDeviation, profileData.ProjectileDeviation); GetProfileDifficultyNumValues(kv, "projectile_count", profileData.ProjectileCount, profileData.ProjectileCount); - profileData.CriticalProjectiles = !!kv.GetNum("enable_crit_rockets", profileData.CriticalProjectiles); - profileData.ShootGestures = !!kv.GetNum("use_gesture_shoot", profileData.ShootGestures); + profileData.CriticalProjectiles = kv.GetNum("enable_crit_rockets", profileData.CriticalProjectiles) != 0; + profileData.ShootGestures = kv.GetNum("use_gesture_shoot", profileData.ShootGestures) != 0; if (profileData.ShootGestures) { kv.GetString("gesture_shootprojectile", profileData.ShootGestureName, sizeof(profileData.ShootGestureName), profileData.ShootGestureName); @@ -321,7 +327,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe float position[3]; for (int i = 0; i <= profileData.ProjectileRandomPosMax; i++) { - position = view_as( { 0.0, 0.0, 0.0 }); + position = { 0.0, 0.0, 0.0 }; FormatEx(keyName, sizeof(keyName), "projectile_pos_offset_%i", i); kv.GetVector(keyName, position, position); profileData.ProjectilePosOffsets.PushArray(position); @@ -331,197 +337,6 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.ShootAnimations = !!kv.GetNum("use_shoot_animations", profileData.ShootAnimations); - profileData.AdvancedDamageEffects = !!kv.GetNum("player_damage_effects", profileData.AdvancedDamageEffects); - if (profileData.AdvancedDamageEffects) - { - profileData.AdvancedDamageEffectParticles = !!kv.GetNum("player_attach_particle", profileData.AdvancedDamageEffectParticles); - - profileData.RandomAdvancedDamageEffects = !!kv.GetNum("player_damage_random_effects", profileData.RandomAdvancedDamageEffects); - if (profileData.RandomAdvancedDamageEffects) - { - profileData.RandomEffectIndexes = kv.GetNum("player_random_attack_indexes", profileData.RandomEffectIndexes); - if (profileData.RandomEffectIndexes < 0) - { - profileData.RandomEffectIndexes = 1; - } - kv.GetString("player_random_attack_indexes", profileData.RandomEffectIndexesString, sizeof(profileData.RandomEffectIndexesString), profileData.RandomEffectIndexesString); - GetProfileDifficultyFloatValues(kv, "player_random_duration", profileData.RandomEffectDuration, profileData.RandomEffectDuration); - GetProfileDifficultyFloatValues(kv, "player_random_slowdown", profileData.RandomEffectSlowdown, profileData.RandomEffectSlowdown); - profileData.RandomEffectStunType = kv.GetNum("player_random_stun_type", profileData.RandomEffectStunType); - if (profileData.RandomEffectStunType < 0) - { - profileData.RandomEffectStunType = 0; - } - if (profileData.RandomEffectStunType > 3) - { - profileData.RandomEffectStunType = 3; - } - } - - profileData.JarateEffects = !!kv.GetNum("player_jarate_on_hit", profileData.JarateEffects); - if (profileData.JarateEffects) - { - profileData.JarateIndexes = kv.GetNum("player_jarate_attack_indexs", profileData.JarateIndexes); - if (profileData.JarateIndexes < 0) - { - profileData.JarateIndexes = 1; - } - kv.GetString("player_jarate_attack_indexs", profileData.JarateIndexesString, sizeof(profileData.JarateIndexesString), profileData.JarateIndexesString); - GetProfileDifficultyFloatValues(kv, "player_jarate_duration", profileData.JarateDuration, profileData.JarateDuration); - profileData.JarateBeamParticle = !!kv.GetNum("player_jarate_beam_particle", profileData.JarateBeamParticle); - kv.GetString("player_jarate_particle", profileData.JarateParticle, sizeof(profileData.JarateParticle), profileData.JarateParticle); - - kv.GetString("player_jarate_sound", profileData.JarateHitSound, sizeof(profileData.JarateHitSound), profileData.JarateHitSound); - TryPrecacheBossProfileSoundPath(profileData.JarateHitSound, g_FileCheckConVar.BoolValue); - } - - profileData.MilkEffects = !!kv.GetNum("player_milk_on_hit", profileData.MilkEffects); - if (profileData.MilkEffects) - { - profileData.MilkIndexes = kv.GetNum("player_milk_attack_indexs", profileData.MilkIndexes); - if (profileData.MilkIndexes < 0) - { - profileData.MilkIndexes = 1; - } - kv.GetString("player_milk_attack_indexs", profileData.MilkIndexesString, sizeof(profileData.MilkIndexesString), profileData.MilkIndexesString); - GetProfileDifficultyFloatValues(kv, "player_milk_duration", profileData.MilkDuration, profileData.MilkDuration); - profileData.MilkBeamParaticle = !!kv.GetNum("player_milk_beam_particle", profileData.MilkBeamParaticle); - kv.GetString("player_milk_particle", profileData.MilkParticle, sizeof(profileData.MilkParticle), profileData.MilkParticle); - - kv.GetString("player_milk_sound", profileData.MilkHitSound, sizeof(profileData.MilkHitSound), profileData.MilkHitSound); - TryPrecacheBossProfileSoundPath(profileData.MilkHitSound, g_FileCheckConVar.BoolValue); - } - - profileData.GasEffects = !!kv.GetNum("player_gas_on_hit", profileData.GasEffects); - if (profileData.GasEffects) - { - profileData.GasIndexes = kv.GetNum("player_gas_attack_indexs", profileData.GasIndexes); - if (profileData.GasIndexes < 0) - { - profileData.GasIndexes = 1; - } - kv.GetString("player_gas_attack_indexs", profileData.GasIndexesString, sizeof(profileData.GasIndexesString), profileData.GasIndexesString); - GetProfileDifficultyFloatValues(kv, "player_gas_duration", profileData.GasDuration, profileData.GasDuration); - profileData.GasBeamParticle = !!kv.GetNum("player_gas_beam_particle", profileData.GasBeamParticle); - kv.GetString("player_gas_particle", profileData.GasParticle, sizeof(profileData.GasParticle), profileData.GasParticle); - - kv.GetString("player_gas_sound", profileData.GasHitSound, sizeof(profileData.GasHitSound), profileData.GasHitSound); - TryPrecacheBossProfileSoundPath(profileData.GasHitSound, g_FileCheckConVar.BoolValue); - } - - profileData.MarkEffects = !!kv.GetNum("player_mark_on_hit", profileData.MarkEffects); - if (profileData.MarkEffects) - { - profileData.MarkIndexes = kv.GetNum("player_mark_attack_indexs", profileData.MarkIndexes); - if (profileData.MarkIndexes < 0) - { - profileData.MarkIndexes = 1; - } - kv.GetString("player_mark_attack_indexs", profileData.MarkIndexesString, sizeof(profileData.MarkIndexesString), profileData.MarkIndexesString); - GetProfileDifficultyFloatValues(kv, "player_mark_duration", profileData.MarkDuration, profileData.MarkDuration); - } - - profileData.SilentMarkEffects = !!kv.GetNum("player_silent_mark_on_hit", profileData.SilentMarkEffects); - if (profileData.SilentMarkEffects) - { - profileData.SilentMarkIndexes = kv.GetNum("player_silent_mark_attack_indexs", profileData.SilentMarkIndexes); - if (profileData.SilentMarkIndexes < 0) - { - profileData.SilentMarkIndexes = 1; - } - kv.GetString("player_silent_mark_attack_indexs", profileData.SilentMarkIndexesString, sizeof(profileData.SilentMarkIndexesString), profileData.SilentMarkIndexesString); - GetProfileDifficultyFloatValues(kv, "player_silent_mark_duration", profileData.SilentMarkDuration, profileData.SilentMarkDuration); - } - - profileData.IgniteEffects = !!kv.GetNum("player_ignite_on_hit", profileData.IgniteEffects); - if (profileData.IgniteEffects) - { - profileData.IgniteIndexes = kv.GetNum("player_ignite_attack_indexs", profileData.IgniteIndexes); - if (profileData.IgniteIndexes < 0) - { - profileData.IgniteIndexes = 1; - } - kv.GetString("player_ignite_attack_indexs", profileData.IgniteIndexesString, sizeof(profileData.IgniteIndexesString), profileData.IgniteIndexesString); - GetProfileDifficultyFloatValues(kv, "player_ignite_duration", profileData.IgniteDuration, profileData.IgniteDuration); - GetProfileDifficultyFloatValues(kv, "player_ignite_delay", profileData.IgniteDelay, profileData.IgniteDelay); - } - - profileData.StunEffects = !!kv.GetNum("player_stun_on_hit", profileData.StunEffects); - if (profileData.StunEffects) - { - profileData.StunEffectType = kv.GetNum("player_stun_type", profileData.StunEffectType); - if (profileData.StunEffectType < 0) - { - profileData.StunEffectType = 0; - } - if (profileData.StunEffectType > 3) - { - profileData.StunEffectType = 3; - } - profileData.StunEffectIndexes = kv.GetNum("player_stun_attack_indexs", profileData.StunEffectIndexes); - if (profileData.StunEffectIndexes < 0) - { - profileData.StunEffectIndexes = 1; - } - kv.GetString("player_stun_attack_indexs", profileData.StunEffectIndexesString, sizeof(profileData.StunEffectIndexesString), profileData.StunEffectIndexesString); - GetProfileDifficultyFloatValues(kv, "player_stun_duration", profileData.StunEffectDuration, profileData.StunEffectDuration); - GetProfileDifficultyFloatValues(kv, "player_stun_slowdown", profileData.StunEffectSlowdown, profileData.StunEffectSlowdown); - profileData.StunEffectBeamParticle = !!kv.GetNum("player_stun_beam_particle", profileData.StunEffectBeamParticle); - kv.GetString("player_stun_particle", profileData.StunParticle, sizeof(profileData.StunParticle), profileData.StunParticle); - - kv.GetString("player_stun_sound", profileData.StunHitSound, sizeof(profileData.StunHitSound), profileData.StunHitSound); - TryPrecacheBossProfileSoundPath(profileData.StunHitSound, g_FileCheckConVar.BoolValue); - } - - profileData.BleedEffects = !!kv.GetNum("player_bleed_on_hit", profileData.BleedEffects); - if (profileData.BleedEffects) - { - profileData.BleedIndexes = kv.GetNum("player_bleed_attack_indexs", profileData.BleedIndexes); - if (profileData.BleedIndexes < 0) - { - profileData.BleedIndexes = 1; - } - kv.GetString("player_bleed_attack_indexs", profileData.BleedIndexesString, sizeof(profileData.BleedIndexesString), profileData.BleedIndexesString); - GetProfileDifficultyFloatValues(kv, "player_bleed_duration", profileData.BleedDuration, profileData.BleedDuration); - } - - profileData.ElectricEffects = !!kv.GetNum("player_electric_slow_on_hit", profileData.ElectricEffects); - if (profileData.ElectricEffects) - { - profileData.ElectricIndexes = kv.GetNum("player_electrocute_attack_indexs", profileData.ElectricIndexes); - if (profileData.ElectricIndexes < 0) - { - profileData.ElectricIndexes = 1; - } - kv.GetString("player_electrocute_attack_indexs", profileData.ElectricIndexesString, sizeof(profileData.ElectricIndexesString), profileData.ElectricIndexesString); - GetProfileDifficultyFloatValues(kv, "player_electric_slow_duration", profileData.ElectricDuration, profileData.ElectricDuration); - GetProfileDifficultyFloatValues(kv, "player_electric_slow_slowdown", profileData.ElectricSlowdown, profileData.ElectricSlowdown); - profileData.ElectricBeamParticle = !!kv.GetNum("player_electric_beam_particle", profileData.ElectricBeamParticle); - kv.GetString("player_electric_red_particle", profileData.ElectricParticleRed, sizeof(profileData.ElectricParticleRed), profileData.ElectricParticleRed); - kv.GetString("player_electric_blue_particle", profileData.ElectricParticleBlue, sizeof(profileData.ElectricParticleBlue), profileData.ElectricParticleBlue); - } - - profileData.SmiteEffects = !!kv.GetNum("player_smite_on_hit", profileData.SmiteEffects); - if (profileData.SmiteEffects) - { - profileData.SmiteIndexes = kv.GetNum("player_smite_attack_indexs", profileData.SmiteIndexes); - if (profileData.SmiteIndexes < 0) - { - profileData.SmiteIndexes = 1; - } - kv.GetString("player_smite_attack_indexs", profileData.SmiteIndexesString, sizeof(profileData.SmiteIndexesString), profileData.SmiteIndexesString); - profileData.SmiteDamage = kv.GetFloat("player_smite_damage", profileData.SmiteDamage); - profileData.SmiteDamageType = kv.GetNum("player_smite_damage_type", profileData.SmiteDamageType); - profileData.SmiteColor[0] = kv.GetNum("player_smite_color_r", profileData.SmiteColor[0]); - profileData.SmiteColor[1] = kv.GetNum("player_smite_color_g", profileData.SmiteColor[1]); - profileData.SmiteColor[2] = kv.GetNum("player_smite_color_b", profileData.SmiteColor[2]); - profileData.SmiteColor[3] = kv.GetNum("player_smite_transparency", profileData.SmiteColor[3]); - profileData.SmiteMessage = !!kv.GetNum("player_smite_message", profileData.SmiteMessage); - - kv.GetString("player_smite_sound", profileData.SmiteHitSound, sizeof(profileData.SmiteHitSound), profileData.SmiteHitSound); - } - } - profileData.XenobladeCombo = !!kv.GetNum("xenoblade_chain_art_combo", profileData.XenobladeCombo); if (profileData.XenobladeCombo) { @@ -531,63 +346,6 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.XenobladeDazeDuration = kv.GetFloat("xenoblade_daze_duration", profileData.XenobladeDazeDuration); } - profileData.DamageParticles = !!kv.GetNum("damage_particle_effect_enabled", profileData.DamageParticles); - if (profileData.DamageParticles) - { - kv.GetString("damage_effect_particle", profileData.DamageParticleName, sizeof(profileData.DamageParticleName), profileData.DamageParticleName); - profileData.DamageParticleBeam = !!kv.GetNum("damage_effect_beam_particle", profileData.DamageParticleBeam); - kv.GetString("sound_damage_effect", profileData.DamageParticleSound, sizeof(profileData.DamageParticleSound), profileData.DamageParticleSound); - TryPrecacheBossProfileSoundPath(profileData.DamageParticleSound, g_FileCheckConVar.BoolValue); - } - - profileData.Shockwaves = !!kv.GetNum("shockwave", profileData.Shockwaves); - if (profileData.Shockwaves) - { - GetProfileDifficultyFloatValues(kv, "shockwave_height", profileData.ShockwaveHeight, profileData.ShockwaveHeight); - GetProfileDifficultyFloatValues(kv, "shockwave_range", profileData.ShockwaveRange, profileData.ShockwaveRange); - GetProfileDifficultyFloatValues(kv, "shockwave_drain", profileData.ShockwaveDrain, profileData.ShockwaveDrain); - GetProfileDifficultyFloatValues(kv, "shockwave_force", profileData.ShockwaveForce, profileData.ShockwaveForce); - - profileData.ShockwaveStun = !!kv.GetNum("shockwave_stun", profileData.ShockwaveStun); - GetProfileDifficultyFloatValues(kv, "shockwave_stun_duration", profileData.ShockwaveStunDuration, profileData.ShockwaveStunDuration); - GetProfileDifficultyFloatValues(kv, "shockwave_stun_slowdown", profileData.ShockwaveStunSlowdown, profileData.ShockwaveStunSlowdown); - - profileData.ShockwaveIndexes = kv.GetNum("shockwave_attack_index", profileData.ShockwaveIndexes); - if (profileData.ShockwaveIndexes < 1) - { - profileData.ShockwaveIndexes = 1; - } - kv.GetString("shockwave_attack_index", profileData.ShockwaveIndexesString, sizeof(profileData.ShockwaveIndexesString), profileData.ShockwaveIndexesString); - profileData.ShockwaveWidths[0] = kv.GetFloat("shockwave_width_1", profileData.ShockwaveWidths[0]); - profileData.ShockwaveWidths[1] = kv.GetFloat("shockwave_width_2", profileData.ShockwaveWidths[1]); - profileData.ShockwaveAmplitude = kv.GetFloat("shockwave_amplitude", profileData.ShockwaveAmplitude); - - float tempColor[3]; - tempColor[0] = float(profileData.ShockwaveColor1[0]); - tempColor[1] = float(profileData.ShockwaveColor1[1]); - tempColor[2] = float(profileData.ShockwaveColor1[2]); - kv.GetVector("shockwave_color_1", tempColor, tempColor); - profileData.ShockwaveColor1[0] = RoundToNearest(tempColor[0]); - profileData.ShockwaveColor1[1] = RoundToNearest(tempColor[1]); - profileData.ShockwaveColor1[2] = RoundToNearest(tempColor[2]); - - tempColor[0] = float(profileData.ShockwaveColor2[0]); - tempColor[1] = float(profileData.ShockwaveColor2[1]); - tempColor[2] = float(profileData.ShockwaveColor2[2]); - kv.GetVector("shockwave_color_2", tempColor, tempColor); - profileData.ShockwaveColor2[0] = RoundToNearest(tempColor[0]); - profileData.ShockwaveColor2[1] = RoundToNearest(tempColor[1]); - profileData.ShockwaveColor2[2] = RoundToNearest(tempColor[2]); - - profileData.ShockwaveAlpha1 = kv.GetNum("shockwave_alpha_1", profileData.ShockwaveAlpha1); - profileData.ShockwaveAlpha2 = kv.GetNum("shockwave_alpha_2", profileData.ShockwaveAlpha2); - - kv.GetString("shockwave_beam_sprite", profileData.ShockwaveBeamSprite, sizeof(profileData.ShockwaveBeamSprite), profileData.ShockwaveBeamSprite); - kv.GetString("shockwave_halo_sprite", profileData.ShockwaveHaloSprite, sizeof(profileData.ShockwaveHaloSprite), profileData.ShockwaveHaloSprite); - profileData.ShockwaveBeamModel = PrecacheModel(profileData.ShockwaveBeamSprite, true); - profileData.ShockwaveHaloModel = PrecacheModel(profileData.ShockwaveHaloSprite, true); - } - profileData.EarthquakeFootsteps = !!kv.GetNum("earthquake_footsteps", profileData.EarthquakeFootsteps); if (profileData.EarthquakeFootsteps) { @@ -598,134 +356,102 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.EarthquakeFootstepAirShake = !!kv.GetNum("earthquake_footsteps_airshake", profileData.EarthquakeFootstepAirShake); } - profileData.Traps = !!kv.GetNum("traps_enabled", profileData.Traps); - if (profileData.Traps) + GetProfileDifficultyBoolValues(kv, "traps_enabled", profileData.Traps, profileData.Traps); + GetProfileDifficultyNumValues(kv, "trap_type", profileData.TrapType, profileData.TrapType); + GetProfileDifficultyFloatValues(kv, "trap_spawn_cooldown", profileData.TrapCooldown, profileData.TrapCooldown); + kv.GetString("trap_model", profileData.TrapModel, sizeof(profileData.TrapModel), profileData.TrapModel); + kv.GetString("trap_deploy_sound", profileData.TrapDeploySound, sizeof(profileData.TrapDeploySound), profileData.TrapDeploySound); + kv.GetString("trap_miss_sound", profileData.TrapMissSound, sizeof(profileData.TrapMissSound), profileData.TrapMissSound); + kv.GetString("trap_catch_sound", profileData.TrapCatchSound, sizeof(profileData.TrapCatchSound), profileData.TrapCatchSound); + kv.GetString("trap_animation_idle", profileData.TrapAnimIdle, sizeof(profileData.TrapAnimIdle), profileData.TrapAnimIdle); + kv.GetString("trap_animation_closed", profileData.TrapAnimClose, sizeof(profileData.TrapAnimClose), profileData.TrapAnimClose); + kv.GetString("trap_animation_open", profileData.TrapAnimOpen, sizeof(profileData.TrapAnimOpen), profileData.TrapAnimOpen); + TryPrecacheBossProfileSoundPath(profileData.TrapDeploySound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.TrapMissSound, g_FileCheckConVar.BoolValue); + TryPrecacheBossProfileSoundPath(profileData.TrapCatchSound, g_FileCheckConVar.BoolValue); + + if (strcmp(profileData.TrapModel, TRAP_MODEL, true) != 0) { - profileData.TrapType = kv.GetNum("trap_type", profileData.TrapType); - GetProfileDifficultyFloatValues(kv, "trap_spawn_cooldown", profileData.TrapCooldown, profileData.TrapCooldown); - kv.GetString("trap_model", profileData.TrapModel, sizeof(profileData.TrapModel), profileData.TrapModel); - kv.GetString("trap_deploy_sound", profileData.TrapDeploySound, sizeof(profileData.TrapDeploySound), profileData.TrapDeploySound); - kv.GetString("trap_miss_sound", profileData.TrapMissSound, sizeof(profileData.TrapMissSound), profileData.TrapMissSound); - kv.GetString("trap_catch_sound", profileData.TrapCatchSound, sizeof(profileData.TrapCatchSound), profileData.TrapCatchSound); - kv.GetString("trap_animation_idle", profileData.TrapAnimIdle, sizeof(profileData.TrapAnimIdle), profileData.TrapAnimIdle); - kv.GetString("trap_animation_closed", profileData.TrapAnimClose, sizeof(profileData.TrapAnimClose), profileData.TrapAnimClose); - kv.GetString("trap_animation_open", profileData.TrapAnimOpen, sizeof(profileData.TrapAnimOpen), profileData.TrapAnimOpen); - TryPrecacheBossProfileSoundPath(profileData.TrapDeploySound, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.TrapMissSound, g_FileCheckConVar.BoolValue); - TryPrecacheBossProfileSoundPath(profileData.TrapCatchSound, g_FileCheckConVar.BoolValue); - - if (strcmp(profileData.TrapModel, TRAP_MODEL, true) != 0) + if (!PrecacheModel(profileData.TrapModel, true)) { - if (!PrecacheModel(profileData.TrapModel, true)) - { - LogSF2Message("Trap model file %s failed to be loaded, likely does not exist. This will crash the server if not fixed.", profileData.TrapModel); - } - else - { - PrecacheModel2(profileData.TrapModel, _, _, g_FileCheckConVar.BoolValue); - } + LogSF2Message("Trap model file %s failed to be loaded, likely does not exist. This will crash the server if not fixed.", profileData.TrapModel); + } + else + { + PrecacheModel2(profileData.TrapModel, _, _, g_FileCheckConVar.BoolValue); } } - profileData.AutoChaseEnabled = !!kv.GetNum("auto_chase_enabled", profileData.AutoChaseEnabled); - if (profileData.AutoChaseEnabled) + if (kv.JumpToKey("autochase")) { + GetProfileDifficultyBoolValues(kv, "enabled", profileData.AutoChaseEnabled, profileData.AutoChaseEnabled); + GetProfileDifficultyNumValues(kv, "threshold", profileData.AutoChaseCount, profileData.AutoChaseCount); + GetProfileDifficultyFloatValues(kv, "cooldown_after_chase", profileData.AutoChaseAfterChaseCooldown, profileData.AutoChaseAfterChaseCooldown); + GetProfileDifficultyBoolValues(kv, "sprinters", profileData.AutoChaseSprinters, profileData.AutoChaseSprinters); + if (kv.JumpToKey("add")) + { + GetProfileDifficultyNumValues(kv, "on_state_change", profileData.AutoChaseAdd, profileData.AutoChaseAdd); + GetProfileDifficultyNumValues(kv, "footsteps", profileData.AutoChaseAddFootstep, profileData.AutoChaseAddFootstep); + GetProfileDifficultyNumValues(kv, "footsteps_loud", profileData.AutoChaseAddLoudFootstep, profileData.AutoChaseAddLoudFootstep); + GetProfileDifficultyNumValues(kv, "footsteps_quiet", profileData.AutoChaseAddQuietFootstep, profileData.AutoChaseAddQuietFootstep); + GetProfileDifficultyNumValues(kv, "voice", profileData.AutoChaseAddVoice, profileData.AutoChaseAddVoice); + GetProfileDifficultyNumValues(kv, "weapon", profileData.AutoChaseAddWeapon, profileData.AutoChaseAddWeapon); + kv.GoBack(); + } + kv.GoBack(); + } + else + { + GetProfileDifficultyBoolValues(kv, "auto_chase_enabled", profileData.AutoChaseEnabled, profileData.AutoChaseEnabled); GetProfileDifficultyNumValues(kv, "auto_chase_sound_threshold", profileData.AutoChaseCount, profileData.AutoChaseCount); GetProfileDifficultyNumValues(kv, "auto_chase_sound_add", profileData.AutoChaseAdd, profileData.AutoChaseAdd); GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps", profileData.AutoChaseAddFootstep, profileData.AutoChaseAddFootstep); - GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps_loud", profileData.AutoChaseAddLoudFootstep, profileData.AutoChaseAddFootstep); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps_loud", profileData.AutoChaseAddLoudFootstep, profileData.AutoChaseAddLoudFootstep); + GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_footsteps_quiet", profileData.AutoChaseAddQuietFootstep, profileData.AutoChaseAddQuietFootstep); GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_voice", profileData.AutoChaseAddVoice, profileData.AutoChaseAddVoice); GetProfileDifficultyNumValues(kv, "auto_chase_sound_add_weapon", profileData.AutoChaseAddWeapon, profileData.AutoChaseAddWeapon); + GetProfileDifficultyBoolValues(kv, "auto_chase_sprinters", profileData.AutoChaseSprinters, profileData.AutoChaseSprinters); + GetProfileDifficultyFloatValues(kv, "auto_chase_cooldown_after_chase", profileData.AutoChaseAfterChaseCooldown, profileData.AutoChaseAfterChaseCooldown); } - profileData.AutoChaseSprinters = !!kv.GetNum("auto_chase_sprinters", profileData.AutoChaseSprinters); profileData.ChasesEndlessly = !!KvGetNum(kv, "boss_chases_endlessly", profileData.ChasesEndlessly); - profileData.SelfHeal = !!kv.GetNum("self_heal_enabled", profileData.SelfHeal); - if (profileData.SelfHeal) - { - profileData.SelfHealPercentageStart = kv.GetFloat("health_percentage_to_heal", profileData.SelfHealPercentageStart); - if (profileData.SelfHealPercentageStart < 0.0) - { - profileData.SelfHealPercentageStart = 0.0; - } - if (profileData.SelfHealPercentageStart > 0.999) - { - profileData.SelfHealPercentageStart = 0.999; - } - profileData.SelfHealRecover[0] = kv.GetFloat("heal_percentage_one", profileData.SelfHealRecover[0]); - if (profileData.SelfHealRecover[0] < 0.0) - { - profileData.SelfHealRecover[0] = 0.0; - } - if (profileData.SelfHealRecover[0] > 1.0) - { - profileData.SelfHealRecover[0] = 1.0; - } - profileData.SelfHealRecover[1] = kv.GetFloat("heal_percentage_two", profileData.SelfHealRecover[1]); - if (profileData.SelfHealRecover[1] < 0.0) - { - profileData.SelfHealRecover[1] = 0.0; - } - if (profileData.SelfHealRecover[1] > 1.0) - { - profileData.SelfHealRecover[1] = 1.0; - } - profileData.SelfHealRecover[2] = kv.GetFloat("heal_percentage_three", profileData.SelfHealRecover[2]); - if (profileData.SelfHealRecover[2] < 0.0) - { - profileData.SelfHealRecover[2] = 0.0; - } - if (profileData.SelfHealRecover[2] > 1.0) - { - profileData.SelfHealRecover[2] = 1.0; - } - profileData.CloakToHeal = !!kv.GetNum("cloak_to_heal", profileData.CloakToHeal); - } - - profileData.Crawling = !!kv.GetNum("crawling_enabled", profileData.Crawling); - if (profileData.Crawling) + if (kv.JumpToKey("chase_on_look")) { - GetProfileDifficultyFloatValues(kv, "crawl_multiplier", profileData.CrawlSpeedMultiplier, profileData.CrawlSpeedMultiplier); - kv.GetVector("crawl_detect_mins", profileData.CrawlDetectMins, profileData.CrawlDetectMins); - kv.GetVector("crawl_detect_maxs", profileData.CrawlDetectMaxs, profileData.CrawlDetectMaxs); + profileData.ChaseOnLookData.Load(kv); + kv.GoBack(); } - profileData.ChaseOnLook = !!kv.GetNum("auto_chase_upon_look", profileData.ChaseOnLook); - - GetProfileDifficultyFloatValues(kv, "awareness_rate_increase", profileData.AwarenessIncreaseRate, profileData.AwarenessIncreaseRate); - GetProfileDifficultyFloatValues(kv, "awareness_rate_decrease", profileData.AwarenessDecreaseRate, profileData.AwarenessDecreaseRate); + profileData.BoxingBoss = kv.GetNum("boxing_boss", profileData.BoxingBoss) != 0; - profileData.BoxingBoss = !!kv.GetNum("boxing_boss", profileData.BoxingBoss); - - profileData.NormalSoundHook = !!kv.GetNum("normal_sound_hook", profileData.NormalSoundHook); - - profileData.ChaseInitialAnimations = !!kv.GetNum("use_chase_initial_animation", profileData.ChaseInitialAnimations); - - profileData.SpawnAnimationsEnabled = !!kv.GetNum("spawn_animation", profileData.SpawnAnimationsEnabled); + profileData.NormalSoundHook = kv.GetNum("normal_sound_hook", profileData.NormalSoundHook) != 0; profileData.OldAnimationAI = !!kv.GetNum("old_animation_ai", profileData.OldAnimationAI); - profileData.AlertWalkingAnimation = !!kv.GetNum("use_alert_walking_animation", profileData.AlertWalkingAnimation); - - if (profileData.BoxingBoss) + if (kv.JumpToKey("rages")) { - profileData.HealAnimationTimer = kv.GetFloat("heal_timer_animation", profileData.HealAnimationTimer); - profileData.HealFunctionTimer = kv.GetFloat("heal_timer", profileData.HealFunctionTimer); - profileData.HealRangeMin = kv.GetFloat("heal_range_min", profileData.HealRangeMin); - profileData.HealRangeMax = kv.GetFloat("heal_range_max", profileData.HealRangeMax); - profileData.HealTimeMin = kv.GetFloat("heal_time_min", profileData.HealTimeMin); - profileData.HealTimeMax = kv.GetFloat("heal_range_max", profileData.HealTimeMax); - - profileData.AfterburnMultiplier = kv.GetFloat("fire_damage_multiplier", profileData.AfterburnMultiplier); - profileData.BackstabDamageScale = kv.GetFloat("backstab_damage_scale", profileData.BackstabDamageScale); + profileData.Rages = new ArrayList(sizeof(SF2ChaserRageInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2ChaserRageInfo rage; + rage.Init(); + rage.Load(kv, g_FileCheckConVar.BoolValue); + profileData.Rages.PushArray(rage); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); } profileData.Attacks = new ArrayList(sizeof(SF2ChaserBossProfileAttackData)); - int attackNums = ParseChaserProfileAttacks(kv, profileData); + int attackNums = ParseChaserProfileAttacks(kv, profileData, baseData); - if (g_CachedProfileData.Flags & SFF_ATTACKPROPS) + if (baseData.Flags & SFF_ATTACKPROPS) { profileData.AttackPropModels = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); if (kv.JumpToKey("attack_props_physics_models")) @@ -756,10 +482,9 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.ChasingSounds.Channel = SNDCHAN_VOICE; profileData.ChaseInitialSounds.Channel = SNDCHAN_VOICE; profileData.StunnedSounds.Channel = SNDCHAN_VOICE; - profileData.RageAllSounds.Channel = SNDCHAN_VOICE; - profileData.RageTwoSounds.Channel = SNDCHAN_VOICE; - profileData.RageThreeSounds.Channel = SNDCHAN_VOICE; - profileData.SelfHealSounds.Channel = SNDCHAN_VOICE; + profileData.DeathSounds.Channel = SNDCHAN_VOICE; + profileData.TauntKillSounds.Channel = SNDCHAN_VOICE; + profileData.SmellSounds.Channel = SNDCHAN_VOICE; } // We have to copy and paste here, the previous kv.GotoFirstSubKey() in profile_boss_functions.sp will somehow reset all values of these sound sections @@ -785,249 +510,61 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_Attack])) { - profileData.AttackSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) - { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) - { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - if (profileData.NormalSoundHook) - { - soundInfo.Channel = SNDCHAN_VOICE; - } - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.AttackSounds.PushArray(soundInfo, sizeof(soundInfo)); - } - } - else - { - profileData.AttackSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - SF2BossProfileSoundInfo tempInfo; - profileData.AttackSounds.SetArray(i, tempInfo, sizeof(tempInfo)); - } - do - { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - if (profileData.NormalSoundHook) - { - soundInfo.Channel = SNDCHAN_VOICE; - } - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.AttackSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } - } - while (kv.GotoNextKey()); - kv.GoBack(); - } - } - kv.GoBack(); + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackSounds, profileData.NormalSoundHook); } if (kv.JumpToKey("sound_hitenemy")) { - profileData.HitSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) - { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) - { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.HitSounds.PushArray(soundInfo, sizeof(soundInfo)); - } - } - else - { - profileData.HitSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - SF2BossProfileSoundInfo tempInfo; - profileData.HitSounds.SetArray(i, tempInfo, sizeof(tempInfo)); - } - do - { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.HitSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } - } - while (kv.GotoNextKey()); - kv.GoBack(); - } - } - kv.GoBack(); + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.HitSounds, false); } if (kv.JumpToKey("sound_missenemy")) { - profileData.MissSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) - { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) - { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.MissSounds.PushArray(soundInfo, sizeof(soundInfo)); - } - } - else - { - profileData.MissSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - SF2BossProfileSoundInfo tempInfo; - profileData.MissSounds.SetArray(i, tempInfo, sizeof(tempInfo)); - } - do - { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.MissSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } - } - while (kv.GotoNextKey()); - kv.GoBack(); - } - } - kv.GoBack(); + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.MissSounds, false); } if (kv.JumpToKey("sound_bulletshoot")) { - profileData.BulletShootSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) - { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) - { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.BulletShootSounds.PushArray(soundInfo, sizeof(soundInfo)); - } - } - else - { - profileData.BulletShootSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - SF2BossProfileSoundInfo tempInfo; - profileData.BulletShootSounds.SetArray(i, tempInfo, sizeof(tempInfo)); - } - do - { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.BulletShootSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } - } - while (kv.GotoNextKey()); - kv.GoBack(); - } - } - kv.GoBack(); + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.BulletShootSounds, false); } if (kv.JumpToKey("sound_attackshootprojectile")) { - profileData.ProjectileShootSounds = new ArrayList(sizeof(SF2BossProfileSoundInfo)); - char number[3]; - if (kv.GotoFirstSubKey()) + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.ProjectileShootSounds, false); + } + if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_AttackBegin])) + { + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackBeginSounds, profileData.NormalSoundHook); + } + if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_AttackEnd])) + { + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackEndSounds, profileData.NormalSoundHook); + } + if (kv.JumpToKey("sound_attack_loop")) + { + profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackLoopSounds, false); + } + } + + if (kv.JumpToKey("postures")) + { + profileData.Postures = new StringMap(); + if (kv.GotoFirstSubKey()) + { + do { - char sectionName[64]; - kv.GetSectionName(sectionName, sizeof(sectionName)); - if (strcmp(sectionName, "paths") == 0) + SF2ChaserBossProfilePostureInfo posture; + posture.Init(); + for (int i = 0; i < Difficulty_Max; i++) { - kv.GoBack(); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.ProjectileShootSounds.PushArray(soundInfo, sizeof(soundInfo)); - } - } - else - { - profileData.ProjectileShootSounds.Resize(SF2_CHASER_BOSS_MAX_ATTACKS); - for (int i = 0; i < SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - SF2BossProfileSoundInfo tempInfo; - profileData.ProjectileShootSounds.SetArray(i, tempInfo, sizeof(tempInfo)); - } - do - { - kv.GetSectionName(number, sizeof(number)); - int index = StringToInt(number); - SF2BossProfileSoundInfo soundInfo; - soundInfo.Init(); - soundInfo.Load(kv, g_FileCheckConVar.BoolValue); - soundInfo.PostLoad(); - if (soundInfo.Paths != null) - { - profileData.ProjectileShootSounds.SetArray(index - 1, soundInfo, sizeof(soundInfo)); - } - } - while (kv.GotoNextKey()); - kv.GoBack(); + posture.Speed[i] = baseData.RunSpeed[i]; + posture.WalkSpeed[i] = profileData.WalkSpeed[i]; + posture.Acceleration[i] = baseData.Acceleration[i]; } + posture.Load(kv); + profileData.Postures.SetArray(posture.Name, posture, sizeof(posture)); } + while (kv.GotoNextKey()); + kv.GoBack(); } - char sectionName[128]; - kv.GetSectionName(sectionName, sizeof(sectionName)); + kv.GoBack(); } profileData.PostLoad(); @@ -1037,145 +574,118 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe return true; } -static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chaserProfileData) +static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chaserProfileData, SF2BossProfileData baseData) { // Create the array. ArrayList attacks = chaserProfileData.Attacks; - int maxAttacks = -1; - if (kv.JumpToKey("attacks")) - { - maxAttacks = 0; - char num[3]; - for (int i = 1; i <= SF2_CHASER_BOSS_MAX_ATTACKS; i++) - { - FormatEx(num, sizeof(num), "%d", i); - if (kv.JumpToKey(num)) - { - maxAttacks++; - kv.GoBack(); - } - } - if (maxAttacks == 0) - { - LogSF2Message("[SF2 PROFILES PARSER] Critical error, found \"attacks\" section with no attacks inside of it!"); - } - } - if (maxAttacks <= -1) + if (!kv.JumpToKey("attacks")) { return -1; } - else + + if (!kv.GotoFirstSubKey()) { - attacks.Resize(maxAttacks); - for (int i = 0; i < maxAttacks; i++) - { - attacks.Set(i, SF2BossAttackType_Invalid, SF2ChaserBossProfileAttackData::Type); - } + LogSF2Message("[SF2 PROFILES PARSER] Critical error, found \"attacks\" section with no attacks inside of it!"); + kv.GoBack(); + return -1; } - char num[3]; - for (int attackNum = -1; attackNum <=maxAttacks; attackNum++) + + char attackName[64]; + int index = 0; + do { - if (attackNum < 1) - { - attackNum = 1; - } - FormatEx(num, sizeof(num), "%d", attackNum); - kv.JumpToKey(num); + kv.GetSectionName(attackName, sizeof(attackName)); + SF2ChaserBossProfileAttackData attackData; attackData.Init(); + attackData.Index = index; + attackData.Name = attackName; + attackData.Type = kv.GetNum("type", attackData.Type); + if (attackData.Type == SF2BossAttackType_Invalid) + { + LogSF2Message("[SF2 PROFILES PARSER] Attack %s is not supposed to be an invalid attack!", attackName); + continue; + } + GetProfileDifficultyFloatValues(kv, "range", attackData.Range, attackData.Range); - switch (attackData.Type) - { - case SF2BossAttackType_Melee, SF2BossAttackType_Custom: - { - GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); + GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); - attackData.DamageVsProps = kv.GetFloat("damage_vs_props", attackData.DamageVsProps); - GetProfileDifficultyFloatValues(kv, "damageforce", attackData.DamageForce, attackData.DamageForce); + attackData.DamageVsProps = kv.GetFloat("damage_vs_props", attackData.DamageVsProps); + GetProfileDifficultyFloatValues(kv, "damageforce", attackData.DamageForce, attackData.DamageForce); - GetProfileDifficultyNumValues(kv, "damagetype", attackData.DamageType, attackData.DamageType); + GetProfileDifficultyNumValues(kv, "damagetype", attackData.DamageType, attackData.DamageType); - attackData.CanUseAgainstProps = !!kv.GetNum("props", attackData.CanUseAgainstProps); + attackData.CanUseAgainstProps = !!kv.GetNum("props", attackData.CanUseAgainstProps); - kv.GetVector("punchvel", attackData.PunchVelocity, attackData.PunchVelocity); + kv.GetVector("punchvel", attackData.PunchVelocity, attackData.PunchVelocity); - GetProfileDifficultyBoolValues(kv, "lifesteal", attackData.LifeSteal, attackData.LifeSteal); - GetProfileDifficultyFloatValues(kv, "lifesteal_duration", attackData.LifeStealDuration, attackData.LifeStealDuration); + GetProfileDifficultyBoolValues(kv, "lifesteal", attackData.LifeSteal, attackData.LifeSteal); + GetProfileDifficultyFloatValues(kv, "lifesteal_duration", attackData.LifeStealDuration, attackData.LifeStealDuration); - attackData.PullIn = !!kv.GetNum("pull_player_in", attackData.PullIn); + attackData.PullIn = !!kv.GetNum("pull_player_in", attackData.PullIn); - attackData.DeathCamLowHealth = !!kv.GetNum("deathcam_on_low_health", attackData.DeathCamLowHealth); - } - case SF2BossAttackType_Ranged: - { - GetProfileDifficultyNumValues(kv, "bullet_count", attackData.BulletCount, attackData.BulletCount); - GetProfileDifficultyFloatValues(kv, "bullet_damage", attackData.BulletDamage, attackData.BulletDamage); - GetProfileDifficultyFloatValues(kv, "bullet_spread", attackData.BulletSpread, attackData.BulletSpread); - kv.GetVector("bullet_offset", attackData.BulletOffset, attackData.BulletOffset); - kv.GetString("bullet_tracer", attackData.BulletTrace, sizeof(attackData.BulletTrace), attackData.BulletTrace); - } - case SF2BossAttackType_Projectile: - { - GetProfileDifficultyFloatValues(kv, "projectile_damage", attackData.ProjectileDamage, attackData.ProjectileDamage); - GetProfileDifficultyFloatValues(kv, "projectile_speed", attackData.ProjectileSpeed, attackData.ProjectileSpeed); - GetProfileDifficultyFloatValues(kv, "projectile_radius", attackData.ProjectileRadius, attackData.ProjectileRadius); - GetProfileDifficultyFloatValues(kv, "projectile_deviation", attackData.ProjectileDeviation, attackData.ProjectileDeviation); - GetProfileDifficultyBoolValues(kv, "projectile_crits", attackData.CritProjectiles, attackData.CritProjectiles); - GetProfileDifficultyFloatValues(kv, "projectile_iceslow_percent", attackData.IceballSlowdownPercent, attackData.IceballSlowdownPercent); - GetProfileDifficultyFloatValues(kv, "projectile_iceslow_duration", attackData.IceballSlowdownDuration, attackData.IceballSlowdownDuration); - GetProfileDifficultyNumValues(kv, "projectile_count", attackData.ProjectileCount, attackData.ProjectileCount); - attackData.ProjectileType = kv.GetNum("projectiletype", attackData.ProjectileType); - kv.GetVector("projectile_offset", attackData.ProjectileOffset, attackData.ProjectileOffset); - kv.GetString("fire_trail", attackData.FireballTrail, sizeof(attackData.FireballTrail), attackData.FireballTrail); - kv.GetString("fire_iceball_trail", attackData.IceballTrail, sizeof(attackData.IceballTrail), attackData.IceballTrail); - kv.GetString("rocket_model", attackData.RocketModel, sizeof(attackData.RocketModel), attackData.RocketModel); - - if (strcmp(attackData.RocketModel, ROCKET_MODEL, true) != 0) - { - if (!PrecacheModel(attackData.RocketModel, true)) - { - LogSF2Message("Attack rocket model file %s on attack index %i failed to be loaded, likely does not exist. This will crash the server if not fixed.", attackData.RocketModel, attackNum); - } - else - { - PrecacheModel2(attackData.RocketModel, _, _, g_FileCheckConVar.BoolValue); - } - } - } - case SF2BossAttackType_ExplosiveDance: + attackData.DeathCamLowHealth = !!kv.GetNum("deathcam_on_low_health", attackData.DeathCamLowHealth); + + GetProfileDifficultyNumValues(kv, "bullet_count", attackData.BulletCount, attackData.BulletCount); + GetProfileDifficultyFloatValues(kv, "bullet_damage", attackData.BulletDamage, attackData.BulletDamage); + GetProfileDifficultyFloatValues(kv, "bullet_spread", attackData.BulletSpread, attackData.BulletSpread); + kv.GetVector("bullet_offset", attackData.BulletOffset, attackData.BulletOffset); + kv.GetString("bullet_tracer", attackData.BulletTrace, sizeof(attackData.BulletTrace), attackData.BulletTrace); + + GetProfileDifficultyFloatValues(kv, "projectile_damage", attackData.ProjectileDamage, attackData.ProjectileDamage); + GetProfileDifficultyFloatValues(kv, "projectile_speed", attackData.ProjectileSpeed, attackData.ProjectileSpeed); + GetProfileDifficultyFloatValues(kv, "projectile_radius", attackData.ProjectileRadius, attackData.ProjectileRadius); + GetProfileDifficultyFloatValues(kv, "projectile_deviation", attackData.ProjectileDeviation, attackData.ProjectileDeviation); + GetProfileDifficultyBoolValues(kv, "projectile_crits", attackData.CritProjectiles, attackData.CritProjectiles); + GetProfileDifficultyFloatValues(kv, "projectile_iceslow_percent", attackData.IceballSlowdownPercent, attackData.IceballSlowdownPercent); + GetProfileDifficultyFloatValues(kv, "projectile_iceslow_duration", attackData.IceballSlowdownDuration, attackData.IceballSlowdownDuration); + GetProfileDifficultyNumValues(kv, "projectile_count", attackData.ProjectileCount, attackData.ProjectileCount); + attackData.ProjectileType = kv.GetNum("projectiletype", attackData.ProjectileType); + kv.GetVector("projectile_offset", attackData.ProjectileOffset, attackData.ProjectileOffset); + kv.GetString("fire_trail", attackData.FireballTrail, sizeof(attackData.FireballTrail), attackData.FireballTrail); + kv.GetString("fire_iceball_trail", attackData.IceballTrail, sizeof(attackData.IceballTrail), attackData.IceballTrail); + kv.GetString("rocket_model", attackData.RocketModel, sizeof(attackData.RocketModel), attackData.RocketModel); + + if (strcmp(attackData.RocketModel, ROCKET_MODEL, true) != 0) + { + if (!PrecacheModel(attackData.RocketModel, true)) { - GetProfileDifficultyNumValues(kv, "explosivedance_radius", attackData.ExplosiveDanceRadius, attackData.ExplosiveDanceRadius); + LogSF2Message("Attack rocket model file %s on attack name %s failed to be loaded, likely does not exist. This will crash the server if not fixed.", attackData.RocketModel, attackName); } - case SF2BossAttackType_LaserBeam: + else { - GetProfileDifficultyFloatValues(kv, "laser_damage", attackData.LaserDamage, attackData.LaserDamage); - attackData.LaserSize = kv.GetFloat("laser_size", attackData.LaserSize); - if (attackData.LaserSize < 0.0) - { - attackData.LaserSize = 0.0; - } - attackData.LaserColor[0] = kv.GetNum("laser_color_r", attackData.LaserColor[0]); - attackData.LaserColor[1] = kv.GetNum("laser_color_g", attackData.LaserColor[1]); - attackData.LaserColor[2] = kv.GetNum("laser_color_b", attackData.LaserColor[2]); - attackData.LaserAttachment = !!kv.GetNum("laser_attachment", attackData.LaserAttachment); - if (attackData.LaserAttachment) - { - kv.GetString("laser_attachment_name", attackData.LaserAttachmentName, sizeof(attackData.LaserAttachmentName), attackData.LaserAttachmentName); - } - else - { - kv.GetVector("laser_offset", attackData.LaserOffset, attackData.LaserOffset); - } - GetProfileDifficultyFloatValues(kv, "laser_duration", attackData.LaserDuration, attackData.LaserDuration); - attackData.LaserNoise = kv.GetFloat("laser_noise", attackData.LaserNoise); + PrecacheModel2(attackData.RocketModel, _, _, g_FileCheckConVar.BoolValue); } } + GetProfileDifficultyFloatValues(kv, "explosivedance_radius", attackData.ExplosiveDanceRadius, attackData.ExplosiveDanceRadius); + + GetProfileDifficultyFloatValues(kv, "laser_damage", attackData.LaserDamage, attackData.LaserDamage); + attackData.LaserSize = kv.GetFloat("laser_size", attackData.LaserSize); + if (attackData.LaserSize < 0.0) + { + attackData.LaserSize = 0.0; + } + attackData.LaserColor[0] = kv.GetNum("laser_color_r", attackData.LaserColor[0]); + attackData.LaserColor[1] = kv.GetNum("laser_color_g", attackData.LaserColor[1]); + attackData.LaserColor[2] = kv.GetNum("laser_color_b", attackData.LaserColor[2]); + attackData.LaserAttachment = !!kv.GetNum("laser_attachment", attackData.LaserAttachment); + if (attackData.LaserAttachment) + { + kv.GetString("laser_attachment_name", attackData.LaserAttachmentName, sizeof(attackData.LaserAttachmentName), attackData.LaserAttachmentName); + } + else + { + kv.GetVector("laser_offset", attackData.LaserOffset, attackData.LaserOffset); + } + GetProfileDifficultyFloatValues(kv, "laser_duration", attackData.LaserDuration, attackData.LaserDuration); + attackData.LaserNoise = kv.GetFloat("laser_noise", attackData.LaserNoise); + GetProfileDifficultyBoolValues(kv, "dont_interrupt_chaseinitial", attackData.DontInterruptChaseInitial, attackData.DontInterruptChaseInitial); GetProfileDifficultyFloatValues(kv, "delay", attackData.DamageDelay, attackData.DamageDelay); @@ -1249,31 +759,184 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas GetProfileDifficultyFloatValues(kv, "run_speed", attackData.RunSpeed, attackData.RunSpeed); GetProfileDifficultyFloatValues(kv, "run_duration", attackData.RunDuration, attackData.RunDuration); GetProfileDifficultyFloatValues(kv, "run_delay", attackData.RunDelay, attackData.RunDelay); + for (int i = 0; i < Difficulty_Max; i++) + { + attackData.RunAcceleration[i] = baseData.Acceleration[i]; + } + GetProfileDifficultyFloatValues(kv, "run_acceleration", attackData.RunAcceleration, attackData.RunAcceleration); + GetProfileDifficultyBoolValues(kv, "run_ground_speed", attackData.RunGroundSpeed, attackData.RunGroundSpeed); attackData.UseOnDifficulty = kv.GetNum("use_on_difficulty", attackData.UseOnDifficulty); attackData.BlockOnDifficulty = kv.GetNum("block_on_difficulty", attackData.BlockOnDifficulty); attackData.UseOnHealth = kv.GetFloat("use_on_health", attackData.UseOnHealth); attackData.BlockOnHealth = kv.GetFloat("block_on_health", attackData.BlockOnHealth); - attackData.Gestures = !!kv.GetNum("gestures", attackData.Gestures); + attackData.Gestures = kv.GetNum("gestures", attackData.Gestures) != 0; GetProfileDifficultyBoolValues(kv, "cancel_los", attackData.CancelLos, attackData.CancelLos); - GetProfileDifficultyFloatValues(kv, "cancel_distance", attackData.CancelDistance, attackData.CancelDistance); + GetProfileDifficultyFloatValues(kv, "cancel_distance_max", attackData.CancelDistance, attackData.CancelDistance); + GetProfileDifficultyFloatValues(kv, "cancel_distance_min", attackData.MinCancelDistance, attackData.MinCancelDistance); - attacks.SetArray(attackNum - 1, attackData, sizeof(attackData)); + attackData.EventNumber = kv.GetNum("event", attackData.EventNumber); - if (maxAttacks > 0) // Backward compatibility + kv.GetString("subtype", attackData.SubType, sizeof(attackData.SubType), attackData.SubType); + + if (kv.JumpToKey("shockwave")) { + attackData.Shockwave.Load(kv, g_FileCheckConVar.BoolValue); kv.GoBack(); } - else + + if (kv.JumpToKey("effects")) { - break; + if (kv.JumpToKey("start")) + { + attackData.StartEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.StartEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("hit")) + { + attackData.HitEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.HitEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("miss")) + { + attackData.MissEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.MissEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + kv.GoBack(); } + + if (kv.JumpToKey("apply_conditions")) + { + attackData.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + SF2ChaserBossProfileDamageEffectData damageData; + damageData.Init(); + kv.GetSectionName(section, sizeof(section)); + if (!damageData.SetType(section)) + { + continue; + } + damageData.Load(kv, g_FileCheckConVar.BoolValue); + attackData.DamageEffects.PushArray(damageData, sizeof(damageData)); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + + GetProfileDifficultyBoolValues(kv, "invulnerable", attackData.ImmuneToDamage, attackData.ImmuneToDamage); + + if (kv.JumpToKey("use_with_posture")) + { + attackData.PostureWhitelist = new ArrayList(ByteCountToCells(64)); + char key[64], posture[64]; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + kv.GetString(key, posture, sizeof(posture)); + if (posture[0] == '\0') + { + break; + } + attackData.PostureWhitelist.PushString(posture); + } + kv.GoBack(); + } + + if (kv.JumpToKey("tongue")) + { + GetProfileDifficultyFloatValues(kv, "speed", attackData.TongueSpeed, attackData.TongueSpeed); + GetProfileDifficultyFloatValues(kv, "pull_scale", attackData.TonguePullScale, attackData.TonguePullScale); + GetProfileDifficultyBoolValues(kv, "can_escape", attackData.TongueCanEscape, attackData.TongueCanEscape); + kv.GetString("material", attackData.TongueMaterial, sizeof(attackData.TongueMaterial), attackData.TongueMaterial); + PrecacheMaterial2(attackData.TongueMaterial, g_FileCheckConVar.BoolValue); + kv.GetString("attachment", attackData.TongueAttachment, sizeof(attackData.TongueAttachment), attackData.TongueAttachment); + kv.GetVector("offset", attackData.TongueOffset, attackData.TongueOffset); + + if (kv.JumpToKey("sounds")) + { + if (kv.JumpToKey("launch")) + { + attackData.TongueLaunchSound.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + + if (kv.JumpToKey("hit")) + { + attackData.TongueHitSound.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + + if (kv.JumpToKey("tied")) + { + attackData.TongueTiedSound.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + kv.GoBack(); + } + + attackData.Animations.Init(); + attackData.Animations.Load(kv); + + kv.GoBack(); + } + + attacks.PushArray(attackData, sizeof(attackData)); + index++; } - if (maxAttacks > 0) - { - kv.GoBack(); - } - return maxAttacks; + while (kv.GotoNextKey()); + + kv.GoBack(); + kv.GoBack(); + + return index; } diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp b/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp index 40ae88cf..83e5b2e4 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp @@ -15,21 +15,18 @@ void InitializeStatueProfiles() void UnloadStatueBossProfile(const char[] profile) { - char tempProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - if (!g_StatueBossProfileData.GetString(profile, tempProfile, sizeof(tempProfile)) || tempProfile[0] == '\0') + SF2StatueBossProfileData statueProfileData; + if (!g_StatueBossProfileData.GetArray(profile, statueProfileData, sizeof(statueProfileData))) { return; } - SF2StatueBossProfileData statueProfileData; - g_StatueBossProfileData.GetArray(profile, statueProfileData, sizeof(statueProfileData)); - statueProfileData.Destroy(); g_StatueBossProfileData.Remove(profile); } -bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen) +bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen, SF2BossProfileData baseData) { SF2StatueBossProfileData profileData; profileData.Init(); @@ -59,6 +56,11 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } GetProfileDifficultyFloatValues(kv, "chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.ChaseDurationAddMaxRange[i] = baseData.SearchRange[i]; + } + GetProfileDifficultyFloatValues(kv, "chase_duration_add_max_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_min", profileData.ChaseDurationAddVisibilityMin, profileData.ChaseDurationAddVisibilityMin); GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_max", profileData.ChaseDurationAddVisibilityMax, profileData.ChaseDurationAddVisibilityMax); GetProfileDifficultyFloatValues(kv, "model_change_dist_max", profileData.ModelChangeDistanceMax, profileData.ModelChangeDistanceMax); @@ -89,59 +91,3 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe return true; } - -static SF2StatueBossProfileData g_CachedProfileData; - -ArrayList GetStatueProfileModelsAverageDist(const char[] profile) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ModelsAverageDist; -} - -ArrayList GetStatueProfileModelsCloseDist(const char[] profile) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ModelsCloseDist; -} - -float GetStatueProfileChaseDuration(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDuration[difficulty]; -} - -float GetStatueProfileChaseDurationAddVisibleMin(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibilityMin[difficulty]; -} - -float GetStatueProfileChaseDurationAddVisibleMax(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ChaseDurationAddVisibilityMax[difficulty]; -} - -float GetStatueProfileIdleLifeTime(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IdleLifeTime[difficulty]; -} - -float GetStatueProfileModelChangeDistMax(const char[] profile, int difficulty) -{ - g_StatueBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ModelChangeDistanceMax[difficulty]; -} - -void GetStatueProfileMoveSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.MoveSounds; -} - -void GetStatueProfileSingleMoveSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_ChaserBossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.SingleMoveSounds; -} diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 8f66cd07..2fef0ea2 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -79,7 +79,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu return false; } - profileData.SkinDifficultiesOn = !!kv.GetNum("skin_difficulty", profileData.SkinDifficultiesOn); + profileData.SkinDifficultiesOn = kv.GetNum("skin_difficulty", profileData.SkinDifficultiesOn) != 0; profileData.Body[1] = kv.GetNum("body", profileData.Body[1]); if (profileData.Body[1] < 0) @@ -96,9 +96,9 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu return false; } - profileData.BodyDifficultiesOn = !!kv.GetNum("body_difficulty", profileData.BodyDifficultiesOn); + profileData.BodyDifficultiesOn = kv.GetNum("body_difficulty", profileData.BodyDifficultiesOn) != 0; - profileData.RaidHitbox = !!kv.GetNum("use_raid_hitbox", profileData.RaidHitbox); + profileData.RaidHitbox = kv.GetNum("use_raid_hitbox", profileData.RaidHitbox) != 0; profileData.InstantKillRadius = kv.GetFloat("kill_radius", profileData.InstantKillRadius); @@ -152,7 +152,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("kill_weapontype", profileData.WeaponString, sizeof(profileData.WeaponString), profileData.WeaponString); profileData.WeaponInt = kv.GetNum("kill_weapontype", profileData.WeaponInt); - profileData.DiscoMode = !!kv.GetNum("disco_mode", profileData.DiscoMode); + profileData.DiscoMode = kv.GetNum("disco_mode", profileData.DiscoMode) != 0; if (profileData.DiscoMode) { profileData.DiscoDistanceMin = kv.GetFloat("disco_mode_rng_distance_min", profileData.DiscoDistanceMin); @@ -160,7 +160,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetVector("disco_mode_pos", profileData.DiscoPos, profileData.DiscoPos); } - profileData.FestiveLights = !!kv.GetNum("festive_lights", profileData.FestiveLights); + profileData.FestiveLights = kv.GetNum("festive_lights", profileData.FestiveLights) != 0; if (profileData.FestiveLights) { profileData.FestiveLightBrightness = kv.GetNum("festive_light_brightness", profileData.FestiveLightBrightness); @@ -170,7 +170,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetVector("festive_lights_ang", profileData.FestiveLightAng, profileData.FestiveLightAng); } - profileData.EnableSpawnParticles = !!kv.GetNum("tp_effect_spawn", profileData.EnableSpawnParticles); + profileData.EnableSpawnParticles = kv.GetNum("tp_effect_spawn", profileData.EnableSpawnParticles) != 0; if (profileData.EnableSpawnParticles) { kv.GetString("tp_effect_spawn_particle", profileData.SpawnParticle, sizeof(profileData.SpawnParticle), profileData.SpawnParticle); @@ -180,7 +180,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.SpawnParticleSoundPitch = kv.GetNum("tp_effect_spawn_sound_pitch", profileData.SpawnParticleSoundPitch); } - profileData.EnableDespawnParticles = !!kv.GetNum("tp_effect_despawn", profileData.EnableDespawnParticles); + profileData.EnableDespawnParticles = kv.GetNum("tp_effect_despawn", profileData.EnableDespawnParticles) != 0; if (profileData.EnableDespawnParticles) { kv.GetString("tp_effect_despawn_particle", profileData.DespawnParticle, sizeof(profileData.DespawnParticle), profileData.DespawnParticle); @@ -198,11 +198,11 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.BlinkLookRate = kv.GetFloat("blink_look_rate_multiply", profileData.BlinkLookRate); profileData.BlinkStaticRate = kv.GetFloat("blink_static_rate_multiply", profileData.BlinkStaticRate); - profileData.DeathCam = !!kv.GetNum("death_cam", profileData.DeathCam); + profileData.DeathCam = kv.GetNum("death_cam", profileData.DeathCam) != 0; if (profileData.DeathCam) { - profileData.DeathCamScareSound = !!kv.GetNum("death_cam_play_scare_sound", profileData.DeathCamScareSound); - profileData.PublicDeathCam = !!kv.GetNum("death_cam_public", profileData.PublicDeathCam); + profileData.DeathCamScareSound = kv.GetNum("death_cam_play_scare_sound", profileData.DeathCamScareSound) != 0; + profileData.PublicDeathCam = kv.GetNum("death_cam_public", profileData.PublicDeathCam) != 0; if (profileData.PublicDeathCam) { profileData.PublicDeathCamSpeed = kv.GetFloat("death_cam_speed", profileData.PublicDeathCamSpeed); @@ -211,7 +211,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.PublicDeathCamBackwardOffset = kv.GetFloat("deathcam_death_backward_offset", profileData.PublicDeathCamBackwardOffset); profileData.PublicDeathCamDownwardOffset = kv.GetFloat("deathcam_death_downward_offset", profileData.PublicDeathCamDownwardOffset); } - profileData.DeathCamOverlay = !!kv.GetNum("death_cam_overlay", profileData.DeathCamOverlay); + profileData.DeathCamOverlay = kv.GetNum("death_cam_overlay", profileData.DeathCamOverlay) != 0; profileData.DeathCamOverlayStartTime = kv.GetFloat("death_cam_time_overlay_start", profileData.DeathCamOverlayStartTime); if (profileData.DeathCamOverlayStartTime < 0.0) { @@ -227,6 +227,12 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("death_cam_attachtment_point", profileData.PublicDeathCamAttachment, sizeof(profileData.PublicDeathCamAttachment), profileData.PublicDeathCamAttachment); } + if (kv.JumpToKey("public_death_cam")) + { + profileData.DeathCamData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + GetProfileDifficultyFloatValues(kv, "sound_music_loop", profileData.SoundMusicLoop, profileData.SoundMusicLoop); GetProfileDifficultyFloatValues(kv, "kill_cooldown", profileData.InstantKillCooldown, profileData.InstantKillCooldown); @@ -253,7 +259,6 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.JumpscareNoSight = !!kv.GetNum("jumpscare_no_sight", profileData.JumpscareNoSight); GetProfileDifficultyFloatValues(kv, "speed", profileData.RunSpeed, profileData.RunSpeed); - GetProfileDifficultyFloatValues(kv, "speed_max", profileData.MaxRunSpeed, profileData.MaxRunSpeed); GetProfileDifficultyFloatValues(kv, "acceleration", profileData.Acceleration, profileData.Acceleration); GetProfileDifficultyFloatValues(kv, "idle_lifetime", profileData.IdleLifeTime, profileData.IdleLifeTime); @@ -382,6 +387,14 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetVector("eye_pos", profileData.EyePosOffset, profileData.EyePosOffset); kv.GetVector("eye_ang_offset", profileData.EyeAngOffset, profileData.EyeAngOffset); + if (kv.JumpToKey("eyes")) + { + profileData.EyeData.Load(kv); + kv.GoBack(); + profileData.EyePosOffset = profileData.EyeData.OffsetPos; + profileData.EyeAngOffset = profileData.EyeData.OffsetAng; + } + // Parse through flags. int bossFlags = 0; if (kv.GetNum("static_on_look")) @@ -400,10 +413,6 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { bossFlags |= SFF_HASJUMPSCARE; } - if (kv.GetNum("sound_sight_enabled")) - { - bossFlags |= SFF_HASSIGHTSOUNDS; - } if (kv.GetNum("sound_static_loop_local_enabled")) { bossFlags |= SFF_HASSTATICLOOPLOCALSOUND; @@ -412,10 +421,6 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { bossFlags |= SFF_HASVIEWSHAKE; } - if (kv.GetNum("copy")) - { - bossFlags |= SFF_COPIES; - } if (kv.GetNum("wander_move", 1)) { bossFlags |= SFF_WANDERMOVE; @@ -434,13 +439,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } profileData.Flags = bossFlags; - if (profileData.Flags & SFF_COPIES) - { - GetProfileDifficultyNumValues(kv, "copy_max", profileData.Copies, profileData.Copies); - profileData.TeleportCopyDistance = kv.GetFloat("copy_teleport_dist_from_others", profileData.TeleportCopyDistance); - profileData.TeleportCopyDistance = kv.GetFloat("teleport_distance_between_copies", profileData.TeleportCopyDistance); - profileData.FakeCopies = !!kv.GetNum("fake_copies", profileData.FakeCopies); - } + profileData.CopiesInfo.Load(kv); if (profileData.Flags & SFF_PROXIES) { @@ -512,14 +511,15 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.ProxyWeaponSlots[i] = kv.GetNum(keyValue, profileData.ProxyWeaponSlots[i]); } } - profileData.ProxySpawnEffect = !!kv.GetNum("proxies_spawn_effect_enabled", profileData.ProxySpawnEffect); + profileData.ProxySpawnEffect = kv.GetNum("proxies_spawn_effect_enabled", profileData.ProxySpawnEffect) != 0; if (profileData.ProxySpawnEffect) { kv.GetString("proxies_spawn_effect", profileData.ProxySpawnEffectName, sizeof(profileData.ProxySpawnEffectName), profileData.ProxySpawnEffectName); profileData.ProxySpawnEffectZOffset = kv.GetFloat("proxies_spawn_effect_z_offset", profileData.ProxySpawnEffectZOffset); } - profileData.ProxyZombies = !!kv.GetNum("proxies_zombie", profileData.ProxyZombies); - profileData.ProxyDifficultyModels = !!kv.GetNum("proxy_difficulty_models", profileData.ProxyDifficultyModels); + profileData.ProxyZombies = kv.GetNum("proxies_zombie", profileData.ProxyZombies) != 0; + profileData.ProxyRobots = kv.GetNum("proxies_robot", profileData.ProxyRobots) != 0; + profileData.ProxyDifficultyModels = kv.GetNum("proxy_difficulty_models", profileData.ProxyDifficultyModels) != 0; char index[64], modelDirectory[PLATFORM_MAX_PATH]; if (profileData.ProxyDifficultyModels) @@ -704,14 +704,14 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { case SF2BossType_Statue: { - if (!LoadStatueBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen)) + if (!LoadStatueBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen, profileData)) { return false; } } case SF2BossType_Chaser: { - if (!LoadChaserBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen)) + if (!LoadChaserBossProfile(kv, profile, loadFailReasonBuffer, loadFailReasonBufferLen, profileData)) { return false; } @@ -818,6 +818,61 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } + profileData.IsPvEBoss = kv.GetNum("is_pve", profileData.IsPvEBoss) != 0; + if (profileData.IsPvEBoss) + { + profileData.Flags = profileData.Flags & ~SFF_PROXIES; + if (kv.JumpToKey("pve_spawn_messages")) + { + profileData.PvESpawnMessagesArray = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char message[256], section[64]; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + kv.GetString(section, message, sizeof(message)); + if (message[0] == '\0') + { + break; + } + + profileData.PvESpawnMessagesArray.PushString(message); + } + kv.GoBack(); + } + kv.GetString("pve_spawn_message_prefix", profileData.PvESpawnMessagePrefix, sizeof(profileData.PvESpawnMessagePrefix), profileData.PvESpawnMessagePrefix); + char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + strcopy(setProfile, sizeof(setProfile), profile); + if (kv.GetNum("pve_selectable", 1) != 0) + { + RegisterPvESlenderBoss(setProfile); + } + index = GetSelectableBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableBossProfileList().Erase(index); + } + index = GetSelectableAdminBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableAdminBossProfileList().Erase(index); + } + index = GetSelectableBoxingBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableBoxingBossProfileList().Erase(index); + } + index = GetSelectableRenevantBossProfileList().FindString(profile); + if (index != -1) + { + GetSelectableRenevantBossProfileList().Erase(index); + } + index = GetSelectableRenevantBossAdminProfileList().FindString(profile); + if (index != -1) + { + GetSelectableRenevantBossAdminProfileList().Erase(index); + } + } + if (kv.GotoFirstSubKey()) //Special thanks to Fire for modifying the code for download errors. { char s2[64], s3[64], s4[PLATFORM_MAX_PATH], s5[PLATFORM_MAX_PATH]; @@ -826,7 +881,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { kv.GetSectionName(s2, sizeof(s2)); - if (!StrContains(s2, "sound_")) + if (StrContains(s2, "sound_") != -1) { bool doBack = false; if (kv.JumpToKey("paths")) @@ -987,7 +1042,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("1", s4, sizeof(s4)); profileData.OverlayJumpscare = s4; } - if (!StrContains(s2, "sound_footsteps_event_")) + if (StrContains(s2, "sound_footsteps_event_") != -1) { if (profileData.FootstepEventSounds == null) { @@ -1012,7 +1067,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.FootstepEventSounds.PushArray(soundInfo); } } - else if (!StrContains(s2, "sound_event_")) + else if (StrContains(s2, "sound_event_") != -1) { if (profileData.EventSounds == null) { @@ -1081,7 +1136,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu SF2BossProfileBaseEffectInfo effects; effects.Init(); effects.ModelScale = profileData.ModelScale; - effects.Load(kv); + effects.Load(kv, g_FileCheckConVar.BoolValue); profileData.EffectsArray.PushArray(effects); } while (kv.GotoNextKey()); @@ -1152,7 +1207,7 @@ int GetBossProfileBodyGroupsMax(const char[] profile) return g_CachedProfileData.BodyMax; } -int GetBossProfileRaidHitbox(const char[] profile) +bool GetBossProfileRaidHitbox(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.RaidHitbox; @@ -1173,7 +1228,7 @@ float GetBossProfileSoundMusicLoop(const char[] profile, int difficulty) int GetBossProfileMaxCopies(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Copies[difficulty]; + return g_CachedProfileData.CopiesInfo.MaxCopies[difficulty]; } float GetBossProfileModelScale(const char[] profile) @@ -1446,18 +1501,6 @@ float GetBossProfilePublicDeathCamDownwardOffset(const char[] profile) return g_CachedProfileData.PublicDeathCamDownwardOffset; } -int GetBossProfilePublicDeathCamTargetAttachment(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.PublicDeathCamAttachmentTarget); -} - -int GetBossProfilePublicDeathCamAttachment(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.PublicDeathCamAttachment); -} - bool GetBossProfileDeathCamOverlayState(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1476,30 +1519,6 @@ float GetBossProfileDeathCamTime(const char[] profile) return g_CachedProfileData.DeathCamTime; } -void GetBossProfileDeathCamPosition(const char[] profile, float buffer[3]) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.DeathCamPos; -} - -float GetBossProfileSpeed(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RunSpeed[difficulty]; -} - -float GetBossProfileMaxSpeed(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MaxRunSpeed[difficulty]; -} - -float GetBossProfileAcceleration(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Acceleration[difficulty]; -} - float GetBossProfileIdleLifetime(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1554,30 +1573,12 @@ float GetBossProfileStaticShakeLocalVolumeMax(const char[] profile) return g_CachedProfileData.StaticShakeVolumeMax; } -float GetBossProfileSearchRadius(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SearchRange[difficulty]; -} - -float GetBossProfileHearRadius(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SearchSoundRange[difficulty]; -} - bool GetBossProfileTeleportAllowed(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.TeleportAllowed[difficulty]; } -float GetBossProfileTauntAlertRange(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TauntAlertRange[difficulty]; -} - float GetBossProfileTeleportTimeMin(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1638,10 +1639,10 @@ bool GetBossProfileTeleportIgnoreVis(const char[] profile) return g_CachedProfileData.TeleportIgnoreVis; } -float GetBossProfileTeleportCopyDistance(const char[] profile) +float GetBossProfileTeleportCopyDistance(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TeleportCopyDistance; + return g_CachedProfileData.CopiesInfo.TeleportDistanceSpacing[difficulty]; } float GetBossProfileJumpscareDistance(const char[] profile, int difficulty) @@ -1764,10 +1765,10 @@ int GetBossProfileMaxProxies(const char[] profile, int difficulty) return g_CachedProfileData.MaxProxies[difficulty]; } -bool GetBossProfileFakeCopies(const char[] profile) +bool GetBossProfileFakeCopies(const char[] profile, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FakeCopies; + return g_CachedProfileData.CopiesInfo.Fakes[difficulty]; } bool GetBossProfileDrainCreditState(const char[] profile) @@ -1861,18 +1862,6 @@ float GetBossProfileProxyMaxSpeed(const char[] profile, int difficulty) return g_CachedProfileData.ProxyMaxSpeed[difficulty]; } -float GetBossProfileFOV(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.FOV; -} - -float GetBossProfileTurnRate(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.TurnRate; -} - void GetBossProfileEyePositionOffset(const char[] profile, float buffer[3]) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -1885,18 +1874,6 @@ void GetBossProfileEyeAngleOffset(const char[] profile, float buffer[3]) buffer = g_CachedProfileData.EyeAngOffset; } -float GetBossProfileInstantKillRadius(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.InstantKillRadius; -} - -float GetBossProfileInstantKillCooldown(const char[] profile, int difficulty) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.InstantKillCooldown[difficulty]; -} - float GetBossProfileScareRadius(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -2065,192 +2042,6 @@ int GetBossProfileChatDeathMessagePrefix(const char[] profile, char[] buffer, in return strcopy(buffer, bufferlen, g_CachedProfileData.DeathMessagePrefix); } -bool GetBossProfileHealthbarState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.Healthbar; -} - -bool GetBossProfileBurnRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.BurnRagdoll; -} - -bool GetBossProfileCloakRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CloakRagdoll; -} - -bool GetBossProfileDecapRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DecapRagdoll; -} - -bool GetBossProfileGibRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GibRagdoll; -} - -bool GetBossProfileGoldRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.GoldRagdoll; -} - -bool GetBossProfileIceRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.IceRagdoll; -} - -bool GetBossProfileElectrocuteRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ElectrocuteRagdoll; -} - -bool GetBossProfileAshRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.AshRagdoll; -} - -bool GetBossProfileDeleteRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DeleteRagdoll; -} - -bool GetBossProfilePushRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.PushRagdoll; -} - -void GetBossProfilePushRagdollForce(const char[] profile, float buffer[3]) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.PushRagdollForce; -} - -bool GetBossProfileDissolveRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DissolveRagdoll; -} - -int GetBossProfileDissolveRagdollType(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DissolveKillType; -} - -bool GetBossProfilePlasmaRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.PlasmaRagdoll; -} - -bool GetBossProfileResizeRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdoll; -} - -float GetBossProfileResizeRagdollHead(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdollHead; -} - -float GetBossProfileResizeRagdollHands(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdollHands; -} - -float GetBossProfileResizeRagdollTorso(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.ResizeRagdollTorso; -} - -bool GetBossProfileDecapOrGibRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DecapOrGibRagdoll; -} - -bool GetBossProfileSilentKill(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SilentKill; -} - -bool GetBossProfileMultieffectRagdoll(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.MultiEffectRagdoll; -} - -bool GetBossProfileCustomDeathFlag(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CustomDeathFlag; -} - -int GetBossProfileCustomDeathFlagType(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.CustomDeathFlagType; -} - -bool GetBossProfileOutroMusicState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutroMusic; -} - -int GetBossProfileEngineSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.EngineSound); -} - -int GetBossProfileEngineSoundLevel(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EngineSoundLevel; -} - -float GetBossProfileEngineSoundVolume(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EngineSoundVolume; -} - -void GetBossProfileLocalDeathCamSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.LocalDeathCamSounds; -} - -void GetBossProfileClientDeathCamSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.ClientDeathCamSounds; -} - -void GetBossProfileGlobalDeathCamSounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.GlobalDeathCamSounds; -} - void GetBossProfileMusicSounds(const char[] profile, SF2BossProfileSoundInfo params, int difficulty) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -2303,12 +2094,6 @@ void GetBossProfileSpawnLocalSounds(const char[] profile, SF2BossProfileSoundInf params = g_CachedProfileData.SpawnLocalSounds; } -void GetBossProfilePlayerDeathcamOverlaySounds(const char[] profile, SF2BossProfileSoundInfo params) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - params = g_CachedProfileData.PlayerDeathCamOverlaySounds; -} - void GetBossProfileProxySpawnSounds(const char[] profile, SF2BossProfileSoundInfo params) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); @@ -2421,10 +2206,4 @@ void GetBossProfileAnimationsData(const char[] profile, SF2BossProfileMasterAnim { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); data = g_CachedProfileData.AnimationData; -} - -ArrayList GetBossProfileEffectsArray(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EffectsArray; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/pve.sp b/addons/sourcemod/scripting/sf2/pve.sp new file mode 100644 index 00000000..51913651 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/pve.sp @@ -0,0 +1,1251 @@ +#pragma semicolon 1 + +static bool g_PlayerInPvE[MAXTF2PLAYERS]; +static bool g_PlayerIsLeavingPvE[MAXTF2PLAYERS]; +static Handle g_PlayerPvETimer[MAXTF2PLAYERS]; +static Handle g_PlayerPvERespawnTimer[MAXTF2PLAYERS]; +static int g_PlayerPvETimerCount[MAXTF2PLAYERS]; +static ArrayList g_PlayerEnteredPvETriggers[MAXTF2PLAYERS] = { null, ... }; +static ArrayList g_SlenderBosses; +static ArrayList g_CustomBosses; +static ArrayList g_ActiveBosses; +static bool g_IsPvEActive; +static bool g_DoesPvEExist; +static char g_PlayerCurrentPvEMusic[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static bool g_PvEBoxingMode; + +static float g_TimeUntilBossSpawns; +static bool g_HasBossSpawned; + +#define MAX_MUSICS 14 +static Database g_MusicDataBase; +static bool g_IsMusicCached[MAXTF2PLAYERS]; +static char g_MusicFile[MAXTF2PLAYERS][MAX_MUSICS][PLATFORM_MAX_PATH]; +static char g_MenuMusic[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; +static float g_MusicVolume[MAXTF2PLAYERS][MAX_MUSICS]; +static float g_MusicLoopTime[MAXTF2PLAYERS][MAX_MUSICS]; +static char g_CustomMusicOverride[PLATFORM_MAX_PATH]; + +static GlobalForward g_OnSelectedPvEBoss; + +static ArrayList g_PvETriggers; + +static Menu g_MenuSettingsPvE; + +void PvE_Initialize() +{ + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + g_OnGameFramePFwd.AddFunction(null, GameFrame); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnRoundStartPFwd.AddFunction(null, RoundStart); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + + g_SlenderBosses = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); + g_CustomBosses = new ArrayList(ByteCountToCells(64)); + g_ActiveBosses = new ArrayList(); + + g_PvETriggers = new ArrayList(); + + RegConsoleCmd("sm_sf2_add_pve_music", Command_AddPvEMusic); + RegAdminCmd("sm_sf2_do_pve", Command_DoPvE, ADMFLAG_SLAY); +} + +void PvE_SetupMenus() +{ + g_MenuSettingsPvE = new Menu(Menu_SettingsPvE); + g_MenuSettingsPvE.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Settings PvE Menu Title"); + char formatter[256]; + FormatEx(formatter, sizeof(formatter), "%t", "SF2 PvE Music Settings Title"); + g_MenuSettingsPvE.AddItem("0", formatter); + g_MenuSettingsPvE.ExitBackButton = true; +} + +Menu GetPvEMenu() +{ + return g_MenuSettingsPvE; +} + +static void OnGamemodeStart() +{ + Database base; + char error[512]; + if (SQL_CheckConfig("sf2_pve_music")) + { + base = SQL_Connect("sf2_pve_music", true, error, sizeof(error)); + if (base == null) + { + PrintToServer("%s", error); + return; + } + } + else + { + base = SQLite_UseDatabase("sf2_pve_music", error, sizeof(error)); + if (base == null) + { + PrintToServer("%s", error); + return; + } + } + + Transaction action = new Transaction(); + for (int i = 0; i < MAXTF2PLAYERS; i++) + { + for (int i2 = 0; i2 < MAX_MUSICS; i2++) + { + g_MusicFile[i][i2][0] = '\0'; + g_MusicVolume[i][i2] = 0.75; + g_MusicLoopTime[i][i2] = 0.0; + } + } + + char formatter[1024]; + for (int i = 0; i < MAX_MUSICS; i++) + { + FormatEx(formatter, sizeof(formatter), "CREATE TABLE IF NOT EXISTS music_selection_%i (" + ... "steamid INTEGER PRIMARY KEY, " + ... "music_file VARCHAR(256), " + ... "music_volume FLOAT NOT NULL DEFAULT 1.0, " + ... "music_loop_time FLOAT NOT NULL DEFAULT 0.0);", + i); + + action.AddQuery(formatter); + } + + base.Execute(action, Database_Success, Database_FailHandle, base); +} + +static void GameFrame() +{ + if (g_TimeUntilBossSpawns < GetGameTime() && !g_HasBossSpawned && GetRoundState() != SF2RoundState_Waiting) + { + SpawnPvEBoss(); + } +} + +static void MapStart() +{ + g_PvETriggers.Clear(); + g_DoesPvEExist = false; + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + CBaseEntity trigger = CBaseEntity(ent); + if (trigger.IsValid()) + { + char name[50]; + trigger.GetPropString(Prop_Data, "m_iName", name, sizeof(name)); + if (StrContains(name, "sf2_pve_trigger", false) == 0) + { + float mins[3], maxs[3]; + trigger.GetPropVector(Prop_Send, "m_vecMins", mins); + trigger.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + float range = ((maxs[0] + maxs[1]) / 2.0) + (FloatAbs(mins[0] + mins[1]) / 2.0); + if (range > 2000.0) + { + // Temporary fix for maps like Cliffroad and Mountain Complex + g_PvETriggers.Push(ent); + g_DoesPvEExist = true; + } + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "sf2_trigger_pve")) != -1) + { + SF2TriggerPvEEntity trigger = SF2TriggerPvEEntity(ent); + if (trigger.IsValid()) + { + if (trigger.IsBossPvE) + { + g_PvETriggers.Push(ent); + g_DoesPvEExist = true; + } + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } +} + +static void Database_Success(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData) +{ + g_MusicDataBase = data; + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientAuthorized(i)) + { + OnClientAuthorized(i, NULL_STRING); + } + } +} + +public void OnClientAuthorized(int client, const char[] auth) +{ + if (g_MusicDataBase == null || IsFakeClient(client)) + { + return; + } + + int id = GetSteamAccountID(client); + if (id == 0) + { + return; + } + + Transaction action = new Transaction(); + + char formatter[256]; + for (int i = 0; i < MAX_MUSICS; i++) + { + FormatEx(formatter, sizeof(formatter), "SELECT * FROM music_selection_%i WHERE steamid = %d;", i, id); + action.AddQuery(formatter); + } + + g_MusicDataBase.Execute(action, Database_SetupMusics, Database_RetryClient, GetClientUserId(client)); +} + +static void Database_SetupMusics(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData) +{ + int client = GetClientOfUserId(data); + if (!IsValidClient(client)) + { + return; + } + char formatter[256]; + Transaction action; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (results[i].FetchRow()) + { + results[i].FetchString(1, g_MusicFile[client][i], sizeof(g_MusicFile[][])); + g_MusicVolume[client][i] = results[i].FetchFloat(2); + g_MusicLoopTime[client][i] = results[i].FetchFloat(3); + if (g_MusicFile[client][i][0] != '\0') + { + PrecacheSound(g_MusicFile[client][i]); + } + } + else if (!results[i].MoreRows) + { + if (action == null) + { + action = new Transaction(); + } + + FormatEx(formatter, sizeof(formatter), "INSERT INTO music_selection_%i (steamid, music_file) VALUES ('%d', '%s')", i, GetSteamAccountID(client), ""); + action.AddQuery(formatter); + } + } + + if (action != null) + { + g_MusicDataBase.Execute(action, _, Database_Fail); + } + + g_IsMusicCached[client] = true; +} + +static void Database_Fail(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData) +{ + LogError(error); +} + +static void Database_FailHandle(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData) +{ + LogError(error); + CloseHandle(data); +} + +static void Database_RetryClient(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData) +{ + int client = GetClientOfUserId(data); + if (IsValidClient(client)) + { + OnClientAuthorized(client, error); + } +} + +bool IsPvEBoxing() +{ + return g_PvEBoxingMode; +} + +static void RoundStart() +{ + g_IsPvEActive = false; + g_HasBossSpawned = false; + g_ActiveBosses.Clear(); + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + CBaseEntity trigger = CBaseEntity(ent); + if (trigger.IsValid()) + { + char name[50]; + trigger.GetPropString(Prop_Data, "m_iName", name, sizeof(name)); + if (StrContains(name, "sf2_pve_trigger", false) == 0) + { + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "sf2_trigger_pve")) != -1) + { + SF2TriggerPvEEntity trigger = SF2TriggerPvEEntity(ent); + if (trigger.IsValid()) + { + SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); + } + } + if (!g_DoesPvEExist) + { + return; + } + + g_TimeUntilBossSpawns = GetRandomFloat(10.0, 20.0) + GetGameTime(); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + g_PlayerEnteredPvETriggers[client.index] = new ArrayList(); + + PvE_ForceResetPlayerPvEData(client.index); + + if (IsClientAuthorized(client.index)) + { + OnClientAuthorized(client.index, NULL_STRING); + } +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + PvE_SetPlayerPvEState(client.index, false, false); + + if (g_PlayerEnteredPvETriggers[client.index] != null) + { + delete g_PlayerEnteredPvETriggers[client.index]; + g_PlayerEnteredPvETriggers[client.index] = null; + } + + if (g_MusicDataBase != null && !IsFakeClient(client.index) && g_IsMusicCached[client.index]) + { + int id = GetSteamAccountID(client.index); + if (id != 0) + { + Transaction action = new Transaction(); + + char formatter[512]; + for (int i = 0; i < MAX_MUSICS; i++) + { + FormatEx(formatter, sizeof(formatter), "UPDATE music_selection_%i SET " + ... "music_file = '%s', " + ... "music_volume = %f, " + ... "music_loop_time = %f " + ... "WHERE steamid = %d;", + i, + g_MusicFile[client.index][i], + g_MusicVolume[client.index][i], + g_MusicLoopTime[client.index][i], + id); + + action.AddQuery(formatter); + } + + g_MusicDataBase.Execute(action, _, Database_Fail, _, DBPrio_High); + } + } + + g_IsMusicCached[client.index] = false; +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_DoesPvEExist) + { + return; + } + + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + PvE_SetPlayerPvEState(client.index, false, false); + + g_PlayerIsLeavingPvE[client.index] = false; +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + if (client.IsInPvE) + { + g_PlayerIsLeavingPvE[client.index] = false; + } + + PvE_SetPlayerPvEState(client.index, false, false); + } +} + +static Action Timer_TeleportPlayerToPvE(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (g_PlayerProxy[client]) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvERespawnTimer[client]) + { + return Plugin_Stop; + } + g_PlayerPvERespawnTimer[client] = null; + + ArrayList spawnPointList = new ArrayList(); + + int ent = -1; + + { + float myPos[3]; + float mins[3], maxs[3]; + GetEntPropVector(client, Prop_Send, "m_vecMins", mins); + GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); + + char name[32]; + + ent = -1; + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); + + if (!StrContains(name, "spawn_loot", false)) + { + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", myPos); + + spawnPointList.Push(ent); + } + } + } + + int num; + if ((num = spawnPointList.Length) > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, num - 1)); + } + + delete spawnPointList; + + if (num > 0) + { + float pos[3], ang[3]; + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); + TeleportEntity(client, pos, ang, { 0.0, 0.0, 0.0 }); + + EmitAmbientSound(SF2_PVP_SPAWN_SOUND, pos, _, SNDLEVEL_NORMAL, _, 1.0); + } + return Plugin_Stop; +} + +void PvE_ForceResetPlayerPvEData(int client) +{ + g_PlayerInPvE[client] = false; + g_PlayerPvETimer[client] = null; + g_PlayerPvETimerCount[client] = 0; + g_PlayerPvERespawnTimer[client] = null; + g_PlayerIsLeavingPvE[client] = false; + + if (g_PlayerEnteredPvETriggers[client] != null) + { + g_PlayerEnteredPvETriggers[client].Clear(); + } +} + +void PvE_SetPlayerPvEState(int client, bool status, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool oldInPvE = g_PlayerInPvE[player.index]; + if (status == oldInPvE) + { + return; // no change + } + + g_PlayerInPvE[player.index] = status; + if (!status && g_PlayerCurrentPvEMusic[client][0] != '\0') + { + StopSound(client, SNDCHAN_AUTO, g_PlayerCurrentPvEMusic[client]); + g_PlayerCurrentPvEMusic[client][0] = '\0'; + } + + if (status && player.IsInPvP) + { + PvP_ForceResetPlayerPvPData(player.index); + PvP_SetPlayerPvPState(player.index, false, false, false); + } + g_PlayerPvETimer[player.index] = null; + g_PlayerPvERespawnTimer[player.index] = null; + g_PlayerPvETimerCount[player.index] = 0; + + if (status) + { + player.ChangeCondition(TFCond_UberchargedCanteen, true); + player.ChangeCondition(TFCond_UberchargedHidden, true); + player.ChangeCondition(TFCond_Ubercharged, true); + player.ChangeCondition(TFCond_UberchargedOnTakeDamage, true); + player.ChangeCondition(TFCond_Taunting, true); + + player.ChangeCondition(TFCond_UberchargedCanteen, _, 1.0); + } + + if (regenerate) + { + // Regenerate player but keep health the same. + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +void PvE_OnTriggerStartTouch(int trigger, int other) +{ + char name[64]; + GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); + + // The reason I'm not searching for the PvE triggers array is because of maps like Cliffroad and Mountain Complex + // where the PvE arenas are too small but still serve as shooting ranges. + if (StrContains(name, "sf2_pve_trigger", false) == 0 || SF2TriggerPvEEntity(trigger).IsValid()) + { + if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + { + if (!g_PlayerEliminated[other] && !DidClientEscape(other)) + { + return; + } + //Use valve's kill code if the player is stuck. + if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) + { + TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); + } + //Resize the player. + SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); + + int entRef = EnsureEntRef(trigger); + if (g_PlayerEnteredPvETriggers[other].FindValue(entRef) == -1) + { + g_PlayerEnteredPvETriggers[other].Push(entRef); + } + + if (IsClientInPvE(other)) + { + if (g_PlayerIsLeavingPvE[other]) + { + // Player left and came back again, but is still in PvP mode. + g_PlayerPvETimerCount[other] = 0; + g_PlayerPvETimer[other] = null; + g_PlayerIsLeavingPvE[other] = false; + } + } + else + { + PvE_SetPlayerPvEState(other, true); + if (g_IsPvEActive && g_PvETriggers.FindValue(trigger) != -1) + { + if (g_CustomMusicOverride[0] != '\0') + { + EmitSoundToClient(other, g_CustomMusicOverride, _, _, _, _, 0.75); + strcopy(g_PlayerCurrentPvEMusic[other], sizeof(g_PlayerCurrentPvEMusic[]), g_CustomMusicOverride); + } + else + { + ArrayList musics = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + ArrayList volumes = new ArrayList(); + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[other][i][0] != '\0') + { + musics.PushString(g_MusicFile[other][i]); + volumes.Push(g_MusicVolume[other][i]); + } + } + if (musics.Length > 0) + { + char preferredMusic[PLATFORM_MAX_PATH]; + int random = GetRandomInt(0, musics.Length - 1); + musics.GetString(random, preferredMusic, sizeof(preferredMusic)); + EmitSoundToClient(other, preferredMusic, _, _, _, _, volumes.Get(random)); + strcopy(g_PlayerCurrentPvEMusic[other], sizeof(g_PlayerCurrentPvEMusic[]), preferredMusic); + } + delete volumes; + delete musics; + } + } + } + } + } +} + +void PvE_OnTriggerEndTouch(int trigger, int other) +{ + if (IsValidClient(other)) + { + if (g_PlayerEnteredPvETriggers[other] != null) + { + int triggerEntRef = EnsureEntRef(trigger); + for (int i = g_PlayerEnteredPvETriggers[other].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredPvETriggers[other].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredPvETriggers[other].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredPvETriggers[other].Erase(i); + } + } + } + + if (IsClientInPvE(other)) + { + if (g_PlayerEnteredPvETriggers[other].Length == 0) + { + g_PlayerPvETimerCount[other] = 4; + if (g_PlayerPvETimerCount[other] != 0) + { + g_PlayerPvETimer[other] = CreateTimer(1.0, Timer_PlayerPvELeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerIsLeavingPvE[other] = true; + } + else + { + g_PlayerPvETimer[other] = CreateTimer(0.1, Timer_PlayerPvELeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static Action Timer_PlayerPvELeaveCountdown(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvETimer[client]) + { + return Plugin_Stop; + } + + if (!IsClientInPvE(client)) + { + return Plugin_Stop; + } + + if (g_PlayerPvETimerCount[client] <= 0) + { + PvE_SetPlayerPvEState(client, false); + g_PlayerIsLeavingPvE[client] = false; + + // Force them to their melee weapon and stop taunting, to prevent tposing and what not. + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + TF2_RemoveCondition(client, TFCond_Taunting); + return Plugin_Stop; + } + + g_PlayerPvETimerCount[client]--; + + //if (!g_PlayerProxyAvailableInForce[client]) + { + SetHudTextParams(-1.0, 0.75, + 1.0, + 255, 255, 255, 255, + _, + _, + 0.25, 1.25); + + ShowSyncHudText(client, g_HudSync, "%T", "SF2 Exiting PvE Arena", client, g_PlayerPvETimerCount[client]); + } + + return Plugin_Continue; +} + +bool IsClientInPvE(int client) +{ + return g_PlayerInPvE[client]; +} + +static void SpawnPvEBoss(const char[] override = "") +{ + if (g_CustomBosses.Length == 0 && g_SlenderBosses.Length == 0) + { + return; + } + + int ent = -1; + char targetName[64]; + while ((ent = FindEntityByClassname(ent, "logic_relay")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (targetName[0] != '\0' && strcmp(targetName, "boss_start", false) == 0) + { + AcceptEntityInput(ent, "Trigger"); + break; + } + } + + ArrayList spawnPointList = new ArrayList(); + + ent = -1; + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (!StrContains(targetName, "minigames_boss_spawnpoint", false)) + { + spawnPointList.Push(ent); + } + } + + if (spawnPointList.Length > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, spawnPointList.Length - 1)); + } + + delete spawnPointList; + + if (!IsValidEntity(ent)) + { + return; + } + + g_IsPvEActive = true; + bool shouldDoCustom = false; + shouldDoCustom = g_CustomBosses.Length > 0 && GetRandomInt(0, 1) == 1; + if (override[0] != '\0') + { + shouldDoCustom = g_CustomBosses.FindString(override) != -1; + } + int bossChosen; + if (shouldDoCustom || g_SlenderBosses.Length == 0) + { + char boss[64]; + float spawnPos[3], spawnAng[3]; + CBaseEntity(ent).GetAbsOrigin(spawnPos); + CBaseEntity(ent).GetAbsAngles(spawnAng); + bossChosen = GetRandomInt(0, g_CustomBosses.Length - 1); + if (override[0] != '\0') + { + bossChosen = g_CustomBosses.FindString(override); + if (bossChosen == -1) + { + bossChosen = GetRandomInt(0, g_CustomBosses.Length - 1); + } + } + g_CustomBosses.GetString(bossChosen, boss, sizeof(boss)); + Call_StartForward(g_OnSelectedPvEBoss); + Call_PushString(boss); + Call_PushArray(spawnPos, sizeof(spawnPos)); + Call_PushArray(spawnAng, sizeof(spawnAng)); + Call_Finish(); + } + else + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + float spawnPos[3]; + CBaseEntity(ent).GetAbsOrigin(spawnPos); + bossChosen = GetRandomInt(0, g_SlenderBosses.Length - 1); + if (override[0] != '\0') + { + bossChosen = g_SlenderBosses.FindString(override); + if (bossChosen == -1) + { + bossChosen = GetRandomInt(0, g_SlenderBosses.Length - 1); + } + } + g_SlenderBosses.GetString(bossChosen, profile, sizeof(profile)); + SF2NPC_BaseNPC npc = AddProfile(profile); + if (npc.IsValid()) + { + npc.Spawn(spawnPos); + SF2BossProfileData data; + data = npc.GetProfileData(); + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.MusicSoundsNormal; + SF2ChaserBossProfileData chaserData; + chaserData = view_as(npc).GetProfileData(); + g_PvEBoxingMode = chaserData.BoxingBoss; + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), g_CustomMusicOverride, sizeof(g_CustomMusicOverride)); + } + g_ActiveBosses.Push(npc.EntIndex); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC testNPC = SF2NPC_BaseNPC(i); + if (!testNPC.IsValid()) + { + continue; + } + + if (testNPC.CompanionMaster == npc) + { + testNPC.Spawn(spawnPos); + g_ActiveBosses.Push(testNPC.EntIndex); + } + } + + if (data.CopiesInfo.Enabled[1]) + { + for (int i = 0; i < data.CopiesInfo.MaxCopies[1]; i++) + { + SF2NPC_BaseNPC copy = AddProfile(profile, _, npc); + if (!copy.IsValid()) + { + continue; + } + copy.Spawn(spawnPos); + g_ActiveBosses.Push(copy.EntIndex); + } + } + } + } + + g_HasBossSpawned = true; +} + +static Action Command_DoPvE(int client, int args) +{ + if (!g_DoesPvEExist) + { + CPrintToChat(client, "{royalblue}%t {default}A PvE arena does not exist.", "SF2 Prefix"); + return Plugin_Handled; + } + + if (g_IsPvEActive) + { + CPrintToChat(client, "{royalblue}%t {default}You may not reactivate the PvE arena.", "SF2 Prefix"); + return Plugin_Handled; + } + + if (g_CustomBosses.Length == 0 && g_SlenderBosses.Length == 0) + { + return Plugin_Handled; + } + + char override[64]; + GetCmdArg(1, override, sizeof(override)); + SpawnPvEBoss(override); + + return Plugin_Handled; +} + +static Action Command_AddPvEMusic(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (args < 1) + { + ReplyToCommand(client, "Usage: sf2_add_pve_music "); + return Plugin_Handled; + } + + char soundFile[PLATFORM_MAX_PATH]; + GetCmdArg(1, soundFile, sizeof(soundFile)); + + if (soundFile[0] == '\0') + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Invalid File", client); + return Plugin_Handled; + } + + bool isAvailable = false; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[client][i][0] == '\0') + { + isAvailable = true; + strcopy(g_MusicFile[client][i], sizeof(g_MusicFile[][]), soundFile); + break; + } + + if (strcmp(g_MusicFile[client][i], soundFile, false) == 0) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Add Unique Track", client); + return Plugin_Handled; + } + } + + if (!isAvailable) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Tracks Unavailable", client); + return Plugin_Handled; + } + + PrecacheSound(soundFile); + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Added Track", client, soundFile); + + ClientSaveCookies(client); + + return Plugin_Handled; +} + +static void DisplayMusicSelectionPvE(int client) +{ + Menu newMenu = new Menu(PvEMenu_MusicSelection); + ArrayList musics = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[client][i][0] != '\0') + { + musics.PushString(g_MusicFile[client][i]); + } + } + for (int i = 0; i < musics.Length; i++) + { + char music[PLATFORM_MAX_PATH]; + musics.GetString(i, music, sizeof(music)); + newMenu.AddItem(music, music); + } + delete musics; + newMenu.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 PvE Music Settings Title", client); + newMenu.ExitBackButton = true; + newMenu.Display(client, MENU_TIME_FOREVER); + g_MenuMusic[client][0] = '\0'; +} + +static int Menu_SettingsPvE(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack) + { + g_MenuSettings.Display(param1, 30); + } + } + case MenuAction_Select: + { + bool hasMusic = false; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (g_MusicFile[param1][i][0] != '\0') + { + hasMusic = true; + break; + } + } + if (!hasMusic) + { + g_MenuSettingsPvE.Display(param1, 30); + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE No Tracks 1-3", param1); + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE No Tracks 2-3", param1); + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE No Tracks 3-3", param1); + } + else + { + DisplayMusicSelectionPvE(param1); + } + } + } + return 0; +} + +static int PvEMenu_MusicSelection(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack) + { + g_MenuSettings.Display(param1, 30); + } + } + case MenuAction_Select: + { + char music[PLATFORM_MAX_PATH]; + float volume = 0.75; + menu.GetItem(param2, music, sizeof(music)); + Menu newMenu = new Menu(PvEMenu_SelectedMusic); + newMenu.SetTitle("%t %s\n \n", "SF2 Prefix", music); + newMenu.ExitBackButton = true; + + char formatter[256]; + for (int i = 0; i < MAX_MUSICS; i++) + { + if (strcmp(g_MusicFile[param1][i], music, false) == 0) + { + volume = g_MusicVolume[param1][i]; + break; + } + } + FormatEx(formatter, sizeof(formatter), "%T", "SF2 PvE Set Volume", param1, RoundToNearest(volume * 100.0)); + newMenu.AddItem("0", formatter); + FormatEx(formatter, sizeof(formatter), "%T", "SF2 PvE Remove Music", param1); + newMenu.AddItem("1", formatter); + newMenu.Display(param1, MENU_TIME_FOREVER); + strcopy(g_MenuMusic[param1], sizeof(g_MenuMusic[]), music); + } + } + return 0; +} + +static int PvEMenu_SelectedMusic(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select) + { + switch (param2) + { + case 0: + { + char buffer[512]; + Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings Music Volume Title", param1); + + Panel panel = new Panel(); + panel.SetTitle(buffer); + + panel.DrawItem("0%"); + panel.DrawItem("25%"); + panel.DrawItem("50%"); + panel.DrawItem("75% (Default)"); + panel.DrawItem("100%"); + + panel.Send(param1, Panel_SettingsMusicVolume, 30); + delete panel; + } + + case 1: + { + for (int i = 0; i < MAX_MUSICS; i++) + { + if (strcmp(g_MusicFile[param1][i], g_MenuMusic[param1], false) == 0) + { + g_MusicFile[param1][i][0] = '\0'; + g_MusicVolume[param1][i] = 0.75; + g_MusicLoopTime[param1][i] = 0.0; + break; + } + } + CPrintToChat(param1, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 PvE Removed Music", param1, g_MenuMusic[param1]); + DisplayMusicSelectionPvE(param1); + } + } + } + else if (action == MenuAction_Cancel) + { + if (param2 == MenuCancel_ExitBack) + { + DisplayMusicSelectionPvE(param1); + } + } + return 0; +} + +static int Panel_SettingsMusicVolume(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Select) + { + float volume; + for (int i = 1; i < param2; i++) + { + volume += 0.25; + } + for (int i = 0; i < MAX_MUSICS; i++) + { + if (strcmp(g_MusicFile[param1][i], g_MenuMusic[param1], false) == 0) + { + g_MusicVolume[param1][i] = volume; + break; + } + } + CPrintToChat(param1, "%t", "SF2 Music Volum Changed", RoundToNearest(volume * 100.0)); + ClientSaveCookies(param1); + + DisplayMusicSelectionPvE(param1); + } + return 0; +} + +// These register boss entity names to choose from +void RegisterPvEBoss(char[] bossName) +{ + g_CustomBosses.PushString(bossName); +} + +void UnregisterPveBoss(char[] bossName) +{ + int index = g_CustomBosses.FindString(bossName); + if (index != -1) + { + g_CustomBosses.Erase(index); + } +} + +void RegisterPvESlenderBoss(char profile[SF2_MAX_PROFILE_NAME_LENGTH]) +{ + g_SlenderBosses.PushString(profile); +} + +void UnregisterPvESlenderBoss(char profile[SF2_MAX_PROFILE_NAME_LENGTH]) +{ + int index = g_SlenderBosses.FindString(profile); + if (index != -1) + { + g_SlenderBosses.Erase(index); + } +} + +void KillPvEBoss(int boss) +{ + int index = g_ActiveBosses.FindValue(boss); + if (index != -1) + { + g_ActiveBosses.Erase(index); + } + else + { + return; + } + if (g_ActiveBosses.Length == 0) + { + g_CustomMusicOverride[0] = '\0'; + g_IsPvEActive = false; + char targetName[64]; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || !client.IsInPvE) + { + continue; + } + + if (g_PlayerCurrentPvEMusic[client.index][0] != '\0') + { + StopSound(client.index, SNDCHAN_AUTO, g_PlayerCurrentPvEMusic[client.index]); + } + + g_PlayerPvERespawnTimer[client.index] = CreateTimer(5.0, Timer_TeleportPlayerToPvE, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "logic_relay")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (targetName[0] != '\0' && strcmp(targetName, "boss_end", false) == 0) + { + AcceptEntityInput(ent, "Trigger"); + break; + } + } + + CreateTimer(5.0, Timer_RemoveAllPvEBosses, _, TIMER_FLAG_NO_MAPCHANGE); + } +} + +static Action Timer_RemoveAllPvEBosses(Handle timer) +{ + if (!g_Enabled) + { + return Plugin_Stop; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + SF2BossProfileData data; + data = npc.GetProfileData(); + if (!data.IsPvEBoss) + { + continue; + } + + npc.Remove(); + } + + return Plugin_Stop; +} + +void PvE_InitializeAPI() +{ + CreateNative("SF2_IsClientInPvE", Native_IsClientInPvE); + CreateNative("SF2_RegisterPvEBoss", Native_RegisterPvEBoss); + CreateNative("SF2_SetOverridePvEMusic", Native_SetOverridePvEMusic); + CreateNative("SF2_UnregisterPvEBoss", Native_UnregisterPvEBoss); + g_OnSelectedPvEBoss = new GlobalForward("SF2_OnSelectedPvEBoss", ET_Ignore, Param_String, Param_Array, Param_Array); + CreateNative("SF2_AddPvEBoss", Native_AddPvEBoss); + CreateNative("SF2_KillPvEBoss", Native_KillPvEBoss); +} + +static any Native_IsClientInPvE(Handle plugin, int numParams) +{ + return IsClientInPvE(GetNativeCell(1)); +} + +static any Native_RegisterPvEBoss(Handle plugin, int numParams) +{ + char boss[64]; + GetNativeString(1, boss, sizeof(boss)); + RegisterPvEBoss(boss); + return 0; +} + +static any Native_SetOverridePvEMusic(Handle plugin, int numParams) +{ + char music[PLATFORM_MAX_PATH]; + GetNativeString(1, music, sizeof(music)); + strcopy(g_CustomMusicOverride, sizeof(g_CustomMusicOverride), music); + return 0; +} + +static any Native_UnregisterPvEBoss(Handle plugin, int numParams) +{ + char boss[64]; + GetNativeString(1, boss, sizeof(boss)); + UnregisterPveBoss(boss); + return 0; +} + +static any Native_AddPvEBoss(Handle plugin, int numParams) +{ + g_ActiveBosses.Push(GetNativeCell(1)); + return 0; +} + +static any Native_KillPvEBoss(Handle plugin, int numParams) +{ + KillPvEBoss(GetNativeCell(1)); + return 0; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index 286ec7b0..4569b74c 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -8,8 +8,8 @@ #define SF2_PVP_SPAWN_SOUND "items/pumpkin_drop.wav" #define FLAME_HIT_DELAY 0.05 -ConVar g_PvPArenaLeaveTimeConVar = null; -ConVar g_PvPArenaProjectileZapConVar = null; +static ConVar g_PvPArenaLeaveTimeConVar = null; +static ConVar g_PvPArenaProjectileZapConVar = null; static const char g_PvPProjectileClasses[][] = { @@ -35,6 +35,7 @@ static const char g_PvPProjectileClasses[][] = "tf_projectile_energy_ring", "tf_projectile_syringe" }; + static const char g_PvPProjectileClassesNoTouch[][] = { "tf_projectile_flare" @@ -134,11 +135,11 @@ void PvP_Initialize() void PvP_SetupMenus() { - g_MenuSettingsPvP = CreateMenu(Menu_SettingsPvP); - SetMenuTitle(g_MenuSettingsPvP, "%t%t\n \n", "SF2 Prefix", "SF2 Settings PvP Menu Title"); - AddMenuItem(g_MenuSettingsPvP, "0", "Toggle automatic spawning"); - AddMenuItem(g_MenuSettingsPvP, "1", "Toggle spawn protection"); - SetMenuExitBackButton(g_MenuSettingsPvP, true); + g_MenuSettingsPvP = new Menu(Menu_SettingsPvP); + g_MenuSettingsPvP.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Settings PvP Menu Title"); + g_MenuSettingsPvP.AddItem("0", "Toggle automatic spawning"); + g_MenuSettingsPvP.AddItem("1", "Toggle spawn protection"); + g_MenuSettingsPvP.ExitBackButton = true; } static void MapStart() @@ -188,7 +189,7 @@ static void RoundStart() //flags |= TRIGGER_PHYSICS_OBJECTS; flags |= TRIGGER_PHYSICS_DEBRIS; SetEntProp(ent, Prop_Data, "m_spawnflags", flags); - SDKHook( ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch ); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch); } } } @@ -741,7 +742,7 @@ static Action EntityStillAlive(Handle timer, int ref) /** * Enables/Disables PvP mode on the player. */ -void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles=true, bool regenerate=true) +void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles = true, bool regenerate = true) { SF2_BasePlayer player = SF2_BasePlayer(client); if (!player.IsValid) @@ -755,6 +756,12 @@ void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles=true, return; // no change } + if (status && player.IsInPvE) + { + PvE_ForceResetPlayerPvEData(player.index); + PvE_SetPlayerPvEState(player.index, false, false); + } + g_PlayerInPvP[player.index] = status; g_PlayerPvPTimer[player.index] = null; g_PlayerPvPRespawnTimer[player.index] = null; @@ -949,10 +956,13 @@ static Action Timer_TeleportPlayerToPvP(Handle timer, any userid) float pos[3], ang[3]; GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); - TeleportEntity(client, pos, ang, view_as({ 0.0, 0.0, 0.0 })); + TeleportEntity(client, pos, ang, { 0.0, 0.0, 0.0 }); EmitAmbientSound(SF2_PVP_SPAWN_SOUND, pos, _, SNDLEVEL_NORMAL, _, 1.0); - if (g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection) TF2_AddCondition(client, TFCond_UberchargedCanteen, 1.5); + if (g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection) + { + TF2_AddCondition(client, TFCond_UberchargedCanteen, 1.5); + } SF2PlayerPvPSpawnEntity spawnPoint = SF2PlayerPvPSpawnEntity(ent); if (spawnPoint.IsValid()) @@ -1012,6 +1022,11 @@ bool IsClientInPvP(int client) { return g_PlayerInPvP[client]; } + +bool IsClientLeavingPvP(int client) +{ + return g_PlayerIsLeavingPvP[client]; +} //////////////// // Blood effects //////////////// @@ -1130,6 +1145,11 @@ static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHoo return MRES_Ignored; } + if (!IsValidEntity(projectile)) + { + return MRES_Ignored; + } + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); if (IsValidClient(owner) && (IsRoundInWarmup() || IsClientInPvP(owner))) { diff --git a/addons/sourcemod/scripting/sf2/pvp/menus.sp b/addons/sourcemod/scripting/sf2/pvp/menus.sp index 48c21a14..1994ccdb 100644 --- a/addons/sourcemod/scripting/sf2/pvp/menus.sp +++ b/addons/sourcemod/scripting/sf2/pvp/menus.sp @@ -6,9 +6,9 @@ #pragma semicolon 1 -Handle g_MenuSettingsPvP; +Menu g_MenuSettingsPvP; -int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) +int Menu_SettingsPvP(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -19,15 +19,15 @@ int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings PvP Spawn Menu Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); Format(buffer, sizeof(buffer), "%T", "Yes", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); Format(buffer, sizeof(buffer), "%T", "No", param1); - DrawPanelItem(panel, buffer); + panel.DrawItem(buffer); - SendPanelToClient(panel, param1, Panel_SettingsPvPSpawn, 30); + panel.Send(param1, Panel_SettingsPvPSpawn, 30); delete panel; } case 1: @@ -35,13 +35,13 @@ int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) char buffer[512]; Format(buffer, sizeof(buffer), "%T\n \n", "SF2 Settings PvP Spawn Protection Title", param1); - Handle panel = CreatePanel(); - SetPanelTitle(panel, buffer); + Panel panel = new Panel(); + panel.SetTitle(buffer); - DrawPanelItem(panel, "On"); - DrawPanelItem(panel, "Off"); + panel.DrawItem("On"); + panel.DrawItem("Off"); - SendPanelToClient(panel, param1, Panel_SettingsPvPProtection, 30); + panel.Send(param1, Panel_SettingsPvPProtection, 30); delete panel; } } @@ -50,13 +50,13 @@ int Menu_SettingsPvP(Handle menu, MenuAction action,int param1,int param2) { if (param2 == MenuCancel_ExitBack) { - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } } return 0; } -static int Panel_SettingsPvPSpawn(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsPvPSpawn(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -74,12 +74,12 @@ static int Panel_SettingsPvPSpawn(Handle menu, MenuAction action,int param1,int } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } -static int Panel_SettingsPvPProtection(Handle menu, MenuAction action,int param1,int param2) +static int Panel_SettingsPvPProtection(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { @@ -97,7 +97,7 @@ static int Panel_SettingsPvPProtection(Handle menu, MenuAction action,int param } } - DisplayMenu(g_MenuSettings, param1, 30); + g_MenuSettings.Display(param1, 30); } return 0; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index 84d4946c..d9c86b80 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -11,6 +11,9 @@ #define SR_SOUND_SELECT "slender/specialroundselect.mp3" #define SR_SOUND_SELECT_BR "ambient/rottenburg/rottenburg_belltower.wav" #define SR_DUCK_MODEL "models/workshop/player/items/pyro/eotl_ducky/eotl_bonus_duck.mdl" +#define SR_BEATBOX_MUSIC "sf2m/specialround/beatbox/theme.wav" +#define SR_BEATBOX_BEEP "sf2m/specialround/beatbox/beep.wav" +#define SR_BEATBOX_FLICK "sf2m/specialround/beatbox/flick.mp3" #define FILE_SPECIALROUNDS "configs/sf2/specialrounds.cfg" #define FILE_SPECIALROUNDS_DATA "data/sf2/specialrounds.cfg" @@ -25,21 +28,236 @@ static bool g_Started = false; static int doublerouletteCount = 0; static int g_SpecialRoundType = 0; +static Handle g_BeatBoxMasterTime = null; +static Handle g_BeatBoxMusicTimer = null; +static int g_BeatBoxCueIndex = 0; + void SetupSpecialRounds() { + g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); g_OnRoundEndPFwd.AddFunction(null, OnRoundEnd); + g_OnAdminMenuCreateOptionsPFwd.AddFunction(null, OnAdminMenuCreateOptions); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerTeamPFwd.AddFunction(null, OnPlayerTeam); g_OnPlayerClassPFwd.AddFunction(null, OnPlayerClass); } +static void OnGamemodeStart() +{ + PrecacheSound2(SR_BEATBOX_MUSIC, true); + PrecacheSound2(SR_BEATBOX_BEEP, true); + PrecacheSound2(SR_BEATBOX_FLICK, true); +} + +static Action Timer_BeatBoxMasterTimer(Handle timer) +{ + if (timer != g_BeatBoxMasterTime) + { + return Plugin_Stop; + } + + if (!g_Enabled) + { + return Plugin_Stop; + } + + if (!SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + return Plugin_Stop; + } + + g_BeatBoxCueIndex++; + switch (g_BeatBoxCueIndex) + { + case 1: + { + DoBeatBoxBeat("Stop moving in: 3..."); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 2: + { + DoBeatBoxBeat("Stop moving in: 2..."); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 3: + { + DoBeatBoxBeat("Stop moving in: 1..."); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 4: + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || !client.IsAlive) + { + continue; + } + if (client.IsEliminated && !client.IsProxy) + { + continue; + } + if (client.HasEscaped) + { + continue; + } + if (!client.IsMoving()) + { + continue; + } + switch (g_DifficultyConVar.IntValue) + { + case Difficulty_Normal: + { + client.Stun(2.0, 0.25, TF_STUNFLAGS_SMALLBONK, client.index); + } + case Difficulty_Hard: + { + client.Stun(3.0, 0.45, TF_STUNFLAGS_SMALLBONK, client.index); + } + case Difficulty_Insane: + { + client.Stun(3.5, 0.6, TF_STUNFLAGS_SMALLBONK, client.index); + client.TakeDamage(true, _, _, 20.0, DMG_CLUB); + } + case Difficulty_Nightmare: + { + client.Stun(5.5, 0.7, TF_STUNFLAGS_SMALLBONK, client.index); + client.TakeDamage(true, _, _, 50.0, DMG_CLUB); + } + case Difficulty_Apollyon: + { + client.Stun(7.5, 0.8, TF_STUNFLAGS_SMALLBONK, client.index); + client.TakeDamage(true, _, _, 50.0, DMG_CLUB); + } + } + } + DoBeatBoxBeat("Stop moving!", true); + g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + case 5: + { + DoBeatBoxBeat("Start moving!", true, true); + g_BeatBoxMasterTime = CreateTimer(1.95, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + } + if (g_BeatBoxCueIndex >= 5) + { + g_BeatBoxCueIndex = 0; + } + return Plugin_Stop; +} + +static Action Timer_BeatBoxMusic(Handle timer) +{ + if (timer != g_BeatBoxMusicTimer) + { + StopBeatBoxMusicForAll(); + return Plugin_Stop; + } + + if (!g_Enabled) + { + StopBeatBoxMusicForAll(); + return Plugin_Stop; + } + + if (!SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + StopBeatBoxMusicForAll(); + return Plugin_Stop; + } + + EmitBeatBoxMusic(); + + return Plugin_Continue; +} + +static void DoBeatBoxBeat(const char[] message, bool end = false, bool messageOnly = false) +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.HasEscaped || client.IsEliminated || client.IsInDeathCam || !client.IsAlive) + { + continue; + } + if (!messageOnly) + { + if (!end) + { + EmitSoundToClient(client.index, SR_BEATBOX_BEEP, _, _, _, _, 0.35); + } + else + { + EmitSoundToClient(client.index, SR_BEATBOX_FLICK, _, _, _, _, 0.6); + } + } + PrintCenterText(client.index, message); + } +} + +static void EmitBeatBoxMusic() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + float volume = 1.0; + if (!client.IsValid) + { + continue; + } + if (client.HasEscaped) + { + volume = 0.0; + } + else if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + volume = 0.0; + } + else if (!client.IsAlive) + { + volume = 0.0; + } + EmitSoundToClient(client.index, SR_BEATBOX_MUSIC, _, MUSIC_CHAN, _, (1 << 0) | (1 << 8), volume, _, _, _, _, false); + } +} + +static void StopBeatBoxMusic(SF2_BasePlayer client) +{ + StopSound(client.index, SNDCHAN_AUTO, SR_BEATBOX_MUSIC); +} + +void StopBeatBoxMusicForAll() +{ + for (int i = 1; i < MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + StopBeatBoxMusic(client); + } +} + +bool IsBeatBoxBeating(int index = 0) +{ + return g_BeatBoxCueIndex > index && SF_SpecialRound(SPECIALROUND_BEATBOX); +} + static void OnRoundEnd() { g_Started = false; SF_RemoveAllSpecialRound(); } +static void OnAdminMenuCreateOptions(TopMenu topMenu, TopMenuObject commands) +{ + //topMenu.AddItem("sf2_specialrounds_main", AdminTopMenu_SpecialsMain, commands, "sm_sf2_force_special_round", ADMFLAG_CHEATS); +} + static void OnPlayerSpawn(SF2_BasePlayer client) { if (client.IsEliminated) @@ -47,6 +265,8 @@ static void OnPlayerSpawn(SF2_BasePlayer client) return; } + //StopBeatBoxMusic(client); + if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) { TF2_StripContrackerOnly(client.index); @@ -77,20 +297,22 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo return; } + //StopBeatBoxMusic(client); + if (SF_SpecialRound(SPECIALROUND_MULTIEFFECT)) { CreateTimer(0.1, Timer_ReplacePlayerRagdoll, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } - if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !client.HasEscaped && !client.IsEliminated) + if (SF_SpecialRound(SPECIALROUND_THANATOPHOBIA) && IsRoundPlaying() && !client.HasEscaped && client.Team == TFTeam_Red) { for (int reds = 1; reds <= MaxClients; reds++) { - if (!IsValidClient(reds) || - g_PlayerEliminated[reds] || - DidClientEscape(reds) || - GetClientTeam(reds) != TFTeam_Red || - !IsPlayerAlive(reds)) + SF2_BasePlayer player = SF2_BasePlayer(reds); + if (!player.IsValid || + player.IsEliminated || + player.HasEscaped || + !player.IsAlive) { continue; } @@ -99,28 +321,28 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo { case 1: { - TF2_MakeBleed(reds, reds, 4.0); + player.Bleed(true, _, 4.0); EmitSoundToClient(reds, BLEED_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); } case 2: { - TF2_AddCondition(reds, TFCond_Jarated, 5.0); + player.ChangeCondition(TFCond_Jarated, _, 5.0); EmitSoundToClient(reds, JARATE_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); } case 3: { - TF2_AddCondition(reds, TFCond_Gas, 5.0); + player.ChangeCondition(TFCond_Gas, _, 5.0); EmitSoundToClient(reds, GAS_ROLL, reds, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); } case 4: { int maxHealth = SDKCall(g_SDKGetMaxHealth, reds); float damageToTake = float(maxHealth) / 10.0; - SDKHooks_TakeDamage(reds, reds, reds, damageToTake, 128, _, { 0.0, 0.0, 0.0 } ); + player.TakeDamage(true, _, _, damageToTake, 128); } case 5: { - TF2_AddCondition(reds, TFCond_MarkedForDeath, 5.0); + player.ChangeCondition(TFCond_MarkedForDeath, _, 5.0); } } } @@ -409,7 +631,7 @@ static bool SpecialRoundCanBeSelected(int specialRound) return false; } - return !!g_SpecialRoundsConfig.GetNum("enabled", 1); + return g_SpecialRoundsConfig.GetNum("enabled", 1) != 0; } static bool IsSpecialRoundEnabled(int specialRound) @@ -507,7 +729,7 @@ static Action Timer_SpecialRoundFakeBosses(Handle timer) } for (int i = 0; i < MAX_BOSSES; i++) { - SF2NPC_BaseNPC Npc = view_as(i); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (!Npc.IsValid()) { continue; @@ -609,7 +831,7 @@ static ArrayList SpecialEnabledList() if (GetSelectableBossProfileList().Length > 0) { AddSpecialRoundToList(SPECIALROUND_DOUBLETROUBLE, enabledRounds); - AddSpecialRoundToList(SPECIALROUND_DOOMBOX, enabledRounds); + AddSpecialRoundToList(SPECIALROUND_SILENTSLENDER, enabledRounds); } } else @@ -617,7 +839,7 @@ static ArrayList SpecialEnabledList() if (GetSelectableBoxingBossProfileList().Length > 0) { AddSpecialRoundToList(SPECIALROUND_DOUBLETROUBLE, enabledRounds); - AddSpecialRoundToList(SPECIALROUND_DOOMBOX, enabledRounds); + AddSpecialRoundToList(SPECIALROUND_SILENTSLENDER, enabledRounds); } } @@ -633,10 +855,6 @@ static ArrayList SpecialEnabledList() { AddSpecialRoundToList(SPECIALROUND_2DOUBLE, enabledRounds); } - if (g_DifficultyConVar.IntValue < 2) - { - AddSpecialRoundToList(SPECIALROUND_2DOOM, enabledRounds); - } } } else @@ -646,7 +864,7 @@ static ArrayList SpecialEnabledList() AddSpecialRoundToList(SPECIALROUND_2DOUBLE, enabledRounds); } } - if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && !SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) && !SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_2DOOM) && g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) + if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && !SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) && !SF_SpecialRound(SPECIALROUND_DOUBLETROUBLE) && !SF_SpecialRound(SPECIALROUND_2DOUBLE) && g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_INSANEDIFFICULTY, enabledRounds); } @@ -775,7 +993,10 @@ static ArrayList SpecialEnabledList() { AddSpecialRoundToList(SPECIALROUND_SINGLEPLAYER, enabledRounds); } - //Always keep this special round push at the bottom, we need the array length. + if (!SF_SpecialRound(SPECIALROUND_BEATBOX) && !SF_IsRaidMap() && !SF_IsBoxingMap() && !SF_IsProxyMap() && !SF_SpecialRound(SPECIALROUND_DOUBLEROULETTE) && !SF_SpecialRound(SPECIALROUND_REVOLUTION)) + { + AddSpecialRoundToList(SPECIALROUND_BEATBOX, enabledRounds); + } if (!SF_SpecialRound(SPECIALROUND_VOTE) && !SF_SpecialRound(SPECIALROUND_DOUBLEROULETTE) && !SF_SpecialRound(SPECIALROUND_REVOLUTION) && !SF_SpecialRound(SPECIALROUND_SUPRISE) && enabledRounds.Length > 5 && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_VOTE, enabledRounds); @@ -830,35 +1051,35 @@ void SpecialRoundStart() delete selectableBoxingBosses; SF_AddSpecialRound(SPECIALROUND_DOUBLETROUBLE); } - case SPECIALROUND_DOOMBOX: + case SPECIALROUND_SILENTSLENDER: { + ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); + if (g_DifficultyConVar.IntValue < 2) + { + g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. + } + char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList().Clone(); - if (!SF_IsBoxingMap()) - { - if (selectableBosses.Length > 0) - { - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - } - } - else + if (selectableBosses.Length > 0) { - if (selectableBoxingBosses.Length > 0) - { - selectableBoxingBosses.GetString(GetRandomInt(0, selectableBoxingBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBoxingBosses.GetString(GetRandomInt(0, selectableBoxingBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - } + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); + + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); + + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); + + selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); + AddProfile(buffer, _, _, _, false); } delete selectableBosses; delete selectableBoxingBosses; - SF_AddSpecialRound(SPECIALROUND_DOOMBOX); + SF_AddSpecialRound(SPECIALROUND_SILENTSLENDER); } case SPECIALROUND_THANATOPHOBIA: { @@ -1059,7 +1280,7 @@ void SpecialRoundStart() } case SPECIALROUND_MODBOSSES: { - char buffer[SF2_MAX_PROFILE_NAME_LENGTH], sNightmareDisplay[256]; + char buffer[SF2_MAX_PROFILE_NAME_LENGTH], nightmareDisplay[256]; if (!SF_SpecialRound(SPECIALROUND_DOUBLEROULETTE) && !SF_SpecialRound(SPECIALROUND_REVOLUTION)) { NPCStopMusic(); @@ -1093,16 +1314,16 @@ void SpecialRoundStart() case 4: { PlayNightmareSound(); - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Nightmare); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {valve}%t!", "SF2 Prefix", "SF2 Nightmare Difficulty"); } case 5: { PlayNightmareSound(); - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Apollyon); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {darkgray}%t!", "SF2 Prefix", "SF2 Apollyon Difficulty"); int randomQuote = GetRandomInt(1, 8); @@ -1176,16 +1397,16 @@ void SpecialRoundStart() case 4: { PlayNightmareSound(); - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Nightmare); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {valve}%t!", "SF2 Prefix", "SF2 Nightmare Difficulty"); } case 5: { PlayNightmareSound(); - FormatEx(sNightmareDisplay, sizeof(sNightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); - SpecialRoundGameText(sNightmareDisplay, "leaderboard_streak"); + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); + SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); g_DifficultyConVar.SetInt(Difficulty_Apollyon); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {darkgray}%t!", "SF2 Prefix", "SF2 Apollyon Difficulty"); int randomQuote = GetRandomInt(1, 8); @@ -1247,7 +1468,7 @@ void SpecialRoundStart() for (int i = 0; i < MAX_BOSSES; i++) { NPCStopMusic(); - SF2NPC_BaseNPC Npc = view_as(i); + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); if (!Npc.IsValid()) { continue; @@ -1263,7 +1484,7 @@ void SpecialRoundStart() } SF_AddSpecialRound(SPECIALROUND_TRIPLEBOSSES); } - case SPECIALROUND_LIGHTSOUT,SPECIALROUND_NIGHTVISION: + case SPECIALROUND_LIGHTSOUT, SPECIALROUND_NIGHTVISION: { if (g_SpecialRoundType == SPECIALROUND_LIGHTSOUT) { @@ -1304,6 +1525,12 @@ void SpecialRoundStart() { continue; } + SF2BossProfileData data; + data = NPCGetProfileData(npcIndex); + if (data.IsPvEBoss) + { + continue; + } SlenderRemoveGlow(npcIndex); if (NPCGetCustomOutlinesState(npcIndex)) { @@ -1350,7 +1577,7 @@ void SpecialRoundStart() } else { - SlenderAddGlow(npcIndex, view_as({0, 0, 0, 0})); + SlenderAddGlow(npcIndex, {0, 0, 0, 0}); } } else @@ -1499,27 +1726,6 @@ void SpecialRoundStart() } SF_AddSpecialRound(SPECIALROUND_PAGEDETECTOR); } - case SPECIALROUND_2DOOM: - { - ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); - if (g_DifficultyConVar.IntValue < 2) - { - g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. - } - char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - if (selectableBosses.Length > 0) - { - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); - AddProfile(buffer,_,_,_,false); - } - delete selectableBosses; - SF_AddSpecialRound(SPECIALROUND_2DOOM); - } case SPECIALROUND_SINGLEPLAYER: { for (int client = 1; client <= MaxClients; client++) @@ -1531,6 +1737,14 @@ void SpecialRoundStart() } SF_AddSpecialRound(SPECIALROUND_SINGLEPLAYER); } + case SPECIALROUND_BEATBOX: + { + g_BeatBoxCueIndex = 0; + g_BeatBoxMasterTime = CreateTimer(2.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); + g_BeatBoxMusicTimer = CreateTimer(0.1, Timer_BeatBoxMusic, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); + EmitBeatBoxMusic(); + SF_AddSpecialRound(SPECIALROUND_BEATBOX); + } default: { SF_AddSpecialRound(g_SpecialRoundType); @@ -1655,9 +1869,9 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Bacon Spray"); } - case SPECIALROUND_DOOMBOX: + case SPECIALROUND_SILENTSLENDER: { - FormatEx(item, sizeof(item), "Doom Box"); + FormatEx(item, sizeof(item), "Silent Slender"); } case SPECIALROUND_NOGRACE: { @@ -1743,10 +1957,6 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Class Scramble"); } - case SPECIALROUND_2DOOM: - { - FormatEx(item, sizeof(item), "Silent Slender"); - } case SPECIALROUND_PAGEREWARDS: { FormatEx(item, sizeof(item), "Page Rewards"); diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 6fdeeaa5..a334d26a 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -25,11 +25,11 @@ #define HIDEHUD_INVEHICLE ( 1<<10 ) #define HIDEHUD_BONUS_PROGRESS ( 1<<11 ) // Hide bonus progress display (for bonus map challenges) -#define FFADE_IN 0x0001 // Just here so we don't pass 0 into the function -#define FFADE_OUT 0x0002 // Fade out (not in) -#define FFADE_MODULATE 0x0004 // Modulate (don't blend) -#define FFADE_STAYOUT 0x0008 // ignores the duration, stays faded out until new ScreenFade message received -#define FFADE_PURGE 0x0010 // Purges all other fades, replacing them with this one +#define FFADE_IN 0x0001 // Just here so we don't pass 0 into the function +#define FFADE_OUT 0x0002 // Fade out (not in) +#define FFADE_MODULATE 0x0004 // Modulate (don't blend) +#define FFADE_STAYOUT 0x0008 // ignores the duration, stays faded out until new ScreenFade message received +#define FFADE_PURGE 0x0010 // Purges all other fades, replacing them with this one #define SF_FADE_IN 0x0001 // Fade in, not out #define SF_FADE_MODULATE 0x0002 // Modulate, don't blend @@ -115,7 +115,7 @@ float HULL_HUMAN_MINS[3] = { -13.0, -13.0, 0.0 }; float HULL_HUMAN_MAXS[3] = { 13.0, 13.0, 72.0 }; float HULL_TF2PLAYER_MINS[3] = { -24.5, -24.5, 0.0 }; -float HULL_TF2PLAYER_MAXS[3] = { 24.5, 24.5, 83.0 }; +float HULL_TF2PLAYER_MAXS[3] = { 24.5, 24.5, 83.0 }; // ========================================================== // Overrides @@ -132,32 +132,32 @@ bool TF2_IsPlayerInConditionEx(int client, TFCond condition) bool SF_IsSurvivalMap() { - return !!(g_IsSurvivalMap || (g_SurvivalMapConVar.IntValue == 1)); + return (g_IsSurvivalMap || (g_SurvivalMapConVar.IntValue == 1)); } bool SF_IsRaidMap() { - return !!(g_IsRaidMap || (g_RaidMapConVar.IntValue == 1)); + return (g_IsRaidMap || (g_RaidMapConVar.IntValue == 1)); } bool SF_IsProxyMap() { - return !!(g_IsProxyMap || (g_ProxyMapConVar.IntValue == 1)); + return (g_IsProxyMap || (g_ProxyMapConVar.IntValue == 1)); } bool SF_BossesChaseEndlessly() { - return !!(g_BossesChaseEndlessly || (g_BossChaseEndlesslyConVar.IntValue == 1)); + return (g_BossesChaseEndlessly || (g_BossChaseEndlesslyConVar.IntValue == 1)); } bool SF_IsBoxingMap() { - return !!(g_IsBoxingMap || (g_BoxingMapConVar.IntValue == 1)); + return (g_IsBoxingMap || (g_BoxingMapConVar.IntValue == 1)); } bool SF_IsSlaughterRunMap() { - return !!(g_IsSlaughterRunMap || (g_SlaughterRunMapConVar.IntValue == 1)); + return (g_IsSlaughterRunMap || (g_SlaughterRunMapConVar.IntValue == 1)); } /* int SDK_StartTouch(int entity, int iOther) @@ -182,7 +182,7 @@ bool SDK_PointIsWithin(int func, float pos[3]) { if (g_SDKPointIsWithin != null) { - return !!(SDKCall(g_SDKPointIsWithin, func, pos)); + return (SDKCall(g_SDKPointIsWithin, func, pos)) != 0; } return false; @@ -203,17 +203,17 @@ int EnsureEntRef(int entIndex) int SetEntityTransmitState(int entity, int newFlags) { - if (!IsValidEdict(entity)) - { + if (!IsValidEdict(entity)) + { return 0; } - int flags = GetEdictFlags(entity); - flags &= ~(FL_EDICT_ALWAYS | FL_EDICT_PVSCHECK | FL_EDICT_DONTSEND); - flags |= newFlags; - SetEdictFlags(entity, flags); + int flags = GetEdictFlags(entity); + flags &= ~(FL_EDICT_ALWAYS | FL_EDICT_PVSCHECK | FL_EDICT_DONTSEND); + flags |= newFlags; + SetEdictFlags(entity, flags); - return flags; + return flags; } bool IsEntityClassname(int entIndex, const char[] classname, bool caseSensitive=true) @@ -252,11 +252,11 @@ float GetVectorSquareMagnitude(const float vec1[3], const float vec2[3]) /*float GetVectorAnglesTwoPoints(const float startPos[3], const float endPos[3], float angles[3]) { - static float tmpVec[3]; - tmpVec[0] = endPos[0] - startPos[0]; - tmpVec[1] = endPos[1] - startPos[1]; - tmpVec[2] = endPos[2] - startPos[2]; - GetVectorAngles(tmpVec, angles); + static float tmpVec[3]; + tmpVec[0] = endPos[0] - startPos[0]; + tmpVec[1] = endPos[1] - startPos[1]; + tmpVec[2] = endPos[2] - startPos[2]; + GetVectorAngles(tmpVec, angles); }*/ float SquareFloat(const float value) @@ -276,16 +276,7 @@ float EntityDistanceFromEntity(int ent1, int ent2) GetEntPropVector(ent2, Prop_Data, "m_vecAbsOrigin", hisPos); return GetVectorSquareMagnitude(myPos, hisPos); } -/* -bool IsSpaceOccupied(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) -{ - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; - return hit; -} -*/ + bool IsSpaceOccupiedIgnorePlayers(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitPlayersOrEntity, entity); @@ -322,41 +313,6 @@ bool IsSpaceOccupiedNPC(const float pos[3], const float mins[3], const float max return hit; } -int EntitySetAnimation(int entity, const char[] name, float playbackRate = 1.0, int forceSequence = -1, float cycle = 0.0) -{ - CBaseCombatCharacter animationEntity = CBaseCombatCharacter(entity); - int sequence = forceSequence; - Activity activity = TranslateProfileActivityFromName(name); - if (activity != ACT_INVALID) - { - sequence = animationEntity.SelectWeightedSequence(activity); - } - else if (activity == ACT_INVALID && forceSequence == -1) - { - sequence = animationEntity.LookupSequence(name); - } - - if (sequence != -1) - { - animationEntity.ResetSequence(sequence); - if (cycle > 0.0) - { - animationEntity.SetPropFloat(Prop_Data, "m_flCycle", cycle); - } - } - - if (playbackRate < -12.0) - { - playbackRate = -12.0; - } - if (playbackRate > 12.0) - { - playbackRate = 12.0; - } - animationEntity.SetPropFloat(Prop_Send, "m_flPlaybackRate", playbackRate); - return sequence; -} - void CBaseNPC_RemoveAllLayers(int entity) { if (!IsValidEntity(entity)) @@ -408,6 +364,18 @@ bool NavHasFuncPrefer(CNavArea area) return false; } +int LookupBone(int entity, const char[] name) +{ + int bone = -1; + bone = SDKCall(g_SDKLookupBone, entity, name); + return bone; +} + +void GetBonePosition(int entity, int bone, float origin[3], float angles[3]) +{ + SDKCall(g_SDKGetBonePosition, entity, bone, origin, angles); +} + // ========================================================= // GLOW FUNCTIONS // ========================================================= @@ -469,26 +437,26 @@ void KillClient(int client) { if (client != -1) { - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); ForcePlayerSuicide(client); SetVariantInt(9001); AcceptEntityInput(client, "RemoveHealth"); } } -bool IsEntityAProjectile(int entity) +void Explode(float pos[3], float damage, float radius, int attacker, const char[] explosionParticle = "ExplosionCore_MidAir") { - char classname[64]; - if (IsValidEntity(entity) && GetEntityClassname(entity, classname, sizeof(classname)) && - (strcmp(classname, "env_explosion") == 0 || - strcmp(classname, "tf_projectile_sentryrocket") == 0 || - strcmp(classname, "tf_projectile_rocket") == 0 || - strcmp(classname, "tf_projectile_pipe") == 0 || - strcmp(classname, "tf_projectile_arrow") == 0)) - { - return true; - } - return false; + int bomb = CreateEntityByName("tf_generic_bomb"); + DispatchKeyValueVector(bomb, "origin", pos); + DispatchKeyValueFloat(bomb, "damage", damage); + DispatchKeyValueFloat(bomb, "radius", radius); + DispatchKeyValue(bomb, "health", "1"); + DispatchKeyValue(bomb, "friendlyfire", "1"); + DispatchKeyValue(bomb, "explode_particle", explosionParticle); + SetEntityOwner(bomb, attacker); + DispatchSpawn(bomb); + + SDKHooks_TakeDamage(bomb, attacker, attacker, 9001.0, DMG_BLAST); } void DestroyAllActiveWeapons(int client) @@ -504,9 +472,6 @@ void DestroyAllActiveWeapons(int client) } } -#define SF_IGNORE_LOS 0x0004 -#define SF_NO_DISGUISED_SPY_HEALING 0x0008 - int SDK_SwitchWeapon(int client, int weapon) { if (g_SDKWeaponSwitch != null) @@ -591,7 +556,7 @@ bool IsClientCritBoosted(int client) void TF2_StripWearables(int client) { int entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_wearable")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_wearable")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -600,7 +565,7 @@ void TF2_StripWearables(int client) } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_wearable_vm")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_wearable_vm")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -609,7 +574,7 @@ void TF2_StripWearables(int client) } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_powerup_bottle")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_powerup_bottle")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -618,7 +583,7 @@ void TF2_StripWearables(int client) } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_wearable_razorback")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_wearable_razorback")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) { @@ -630,7 +595,7 @@ void TF2_StripWearables(int client) void TF2_DestroySpyWeapons() { int entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_weapon_revolver")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_weapon_revolver")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -639,7 +604,7 @@ void TF2_DestroySpyWeapons() } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_weapon_builder")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_weapon_builder")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -648,7 +613,7 @@ void TF2_DestroySpyWeapons() } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_weapon_knife")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_weapon_knife")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -657,7 +622,7 @@ void TF2_DestroySpyWeapons() } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "saxxy")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "saxxy")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -666,7 +631,7 @@ void TF2_DestroySpyWeapons() } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_weapon_pda_spy")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_weapon_pda_spy")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -675,7 +640,7 @@ void TF2_DestroySpyWeapons() } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_weapon_invis")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_weapon_invis")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -684,7 +649,7 @@ void TF2_DestroySpyWeapons() } entity = MaxClients + 1; - while((entity = FindEntityByClassname(entity, "tf_weapon_sapper")) > MaxClients) + while ((entity = FindEntityByClassname(entity, "tf_weapon_sapper")) > MaxClients) { if (GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") < 1) { @@ -706,7 +671,7 @@ void ClientSwitchToWeaponSlot(int client,int slot) void ChangeClientTeamNoSuicide(int client,int team, bool respawn=true) { - if (!IsClientInGameEx(client)) + if (!IsValidClient(client)) { return; } @@ -737,7 +702,7 @@ void UTIL_ClientScreenShake(int client, float amplitude, float duration, float f } } -void UTIL_ScreenFade(int client,int duration,int time,int flags,int r,int g,int b,int a) +void UTIL_ScreenFade(int client, int duration, int time, int flags, int r, int g, int b, int a) { int clients[1]; Handle bf; @@ -768,7 +733,7 @@ bool IsValidClient(int client) void PrintToSourceTV(const char[] message) { int client = GetClientOfUserId(g_SourceTVUserID); - if (MaxClients >= client > 0 && IsClientInGameEx(client) && IsClientSourceTV(client)) + if (MaxClients >= client > 0 && IsValidClient(client) && IsClientSourceTV(client)) { CPrintToChat(client, message); } @@ -779,9 +744,9 @@ void PrintToSourceTV(const char[] message) bool TF2_IsMiniCritBuffed(int client) { return (TF2_IsPlayerInCondition(client, TFCond_CritCola) - || TF2_IsPlayerInCondition(client, TFCond_CritHype) - || TF2_IsPlayerInCondition(client, TFCond_Buffed) - ); + || TF2_IsPlayerInCondition(client, TFCond_CritHype) + || TF2_IsPlayerInCondition(client, TFCond_Buffed) + ); } bool IsTauntWep(int weaponEnt) @@ -862,11 +827,11 @@ void SpeakResponseConcept(int client, const char[] concept) //Thanks The Gaben void SpecialRoundGameText(const char[] message, const char[] icon = "") { int entity = CreateEntityByName("game_text_tf"); - DispatchKeyValue(entity,"message", message); - DispatchKeyValue(entity,"display_to_team", "0"); - DispatchKeyValue(entity,"icon", icon); - DispatchKeyValue(entity,"targetname", "game_text1"); - DispatchKeyValue(entity,"background", "0"); + DispatchKeyValue(entity, "message", message); + DispatchKeyValue(entity, "display_to_team", "0"); + DispatchKeyValue(entity, "icon", icon); + DispatchKeyValue(entity, "targetname", "game_text1"); + DispatchKeyValue(entity, "background", "0"); DispatchSpawn(entity); AcceptEntityInput(entity, "Display", entity, entity); //The only time I keep this. CreateTimer(2.0, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); @@ -926,43 +891,91 @@ void TF2_StripContrackerOnly(int client) } } -void TE_Particle(int particleIndex, float origin[3] = NULL_VECTOR, float start[3] = NULL_VECTOR, float angles[3] = NULL_VECTOR, int entindex = -1, int attachtype = -1, int attachpoint = -1, bool resetParticles = true) +void TE_Particle(int particleIndex, + float origin[3] = NULL_VECTOR, + float start[3] = NULL_VECTOR, + float angles[3] = NULL_VECTOR, + int entindex = -1, + int attachtype = -1, + int attachpoint = -1, + bool resetParticles = true, + bool controlPoint = false, + int controlPointAttachType = -1, + const float controlPointOffset[3] = NULL_VECTOR) { - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", origin[0]); - TE_WriteFloat("m_vecOrigin[1]", origin[1]); - TE_WriteFloat("m_vecOrigin[2]", origin[2]); - TE_WriteFloat("m_vecStart[0]", start[0]); - TE_WriteFloat("m_vecStart[1]", start[1]); - TE_WriteFloat("m_vecStart[2]", start[2]); - TE_WriteVector("m_vecAngles", angles); - TE_WriteNum("m_iParticleSystemIndex", particleIndex); - TE_WriteNum("entindex", entindex); + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", origin[0]); + TE_WriteFloat("m_vecOrigin[1]", origin[1]); + TE_WriteFloat("m_vecOrigin[2]", origin[2]); + TE_WriteFloat("m_vecStart[0]", start[0]); + TE_WriteFloat("m_vecStart[1]", start[1]); + TE_WriteFloat("m_vecStart[2]", start[2]); + TE_WriteVector("m_vecAngles", angles); + TE_WriteNum("m_iParticleSystemIndex", particleIndex); + TE_WriteNum("entindex", entindex); + + if (attachtype != -1) + { + TE_WriteNum("m_iAttachType", attachtype); + } + if (attachpoint != -1) + { + TE_WriteNum("m_iAttachmentPointIndex", attachpoint); + } + TE_WriteNum("m_bResetParticles", resetParticles ? 1 : 0); - if (attachtype != -1) - { - TE_WriteNum("m_iAttachType", attachtype); - } - if (attachpoint != -1) - { - TE_WriteNum("m_iAttachmentPointIndex", attachpoint); - } - TE_WriteNum("m_bResetParticles", resetParticles ? 1 : 0); + TE_WriteNum("m_bControlPoint1", controlPoint); + if (controlPoint) + { + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", controlPointAttachType); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", controlPointOffset[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", controlPointOffset[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", controlPointOffset[2]); + } +} + +void CreateParticle(char[] particle, float pos[3], float ang[3]) +{ + int tblidx = FindStringTable("ParticleEffectNames"); + char tmp[256]; + int count = GetStringTableNumStrings(tblidx); + int stridx = INVALID_STRING_INDEX; + + for (int i = 0; i < count; i++) + { + ReadStringTable(tblidx, i, tmp, sizeof(tmp)); + if (StrEqual(tmp, particle, false)) + { + stridx = i; + break; + } + } + + TE_Start("TFParticleEffect"); + TE_WriteFloat("m_vecOrigin[0]", pos[0]); + TE_WriteFloat("m_vecOrigin[1]", pos[1]); + TE_WriteFloat("m_vecOrigin[2]", pos[2]); + TE_WriteVector("m_vecAngles", ang); + TE_WriteNum("m_iParticleSystemIndex", stridx); + TE_WriteNum("entindex", -1); + TE_WriteNum("m_iAttachType", 5); + TE_SendToAll(); } void UTIL_ScreenShake(float center[3], float amplitude, float frequency, float duration, float radius, int command, bool airShake) { - for(int i=1; i<=MaxClients; i++) + for(int i = 1; i <= MaxClients; i++) { - if (IsClientInGameEx(i) && !IsFakeClient(i) && !IsClientInGhostMode(i)) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (player.IsValid && !player.IsBot && !player.IsInGhostMode) { - if (!airShake && command == 0 && !(GetEntityFlags(i) && FL_ONGROUND)) + if (!airShake && command == 0 && !(player.GetFlags() && FL_ONGROUND)) { continue; } float playerPos[3]; - GetClientAbsOrigin(i, playerPos); + player.GetAbsOrigin(playerPos); float localAmplitude = ComputeShakeAmplitude(center, playerPos, amplitude, radius); @@ -973,7 +986,7 @@ void UTIL_ScreenShake(float center[3], float amplitude, float frequency, float d if (localAmplitude > 0 || command == 1) { - Handle msg = StartMessageOne("Shake", i, USERMSG_RELIABLE); + Handle msg = StartMessageOne("Shake", player.index, USERMSG_RELIABLE); if (msg != null) { BfWriteByte(msg, command); @@ -1060,19 +1073,19 @@ float FloatClamp(float a, float min, float max) /** * Linearly interpolates between a and b by t. */ -/*float LerpFloats(const float a, const float b, float t) +float LerpFloats(const float a, const float b, float t) { - if (t < 0.0) + if (t < 0.0) { t = 0.0; } - if (t > 1.0) + if (t > 1.0) { t = 1.0; } - return a + (b - a) * t; -}*/ + return a + (b - a) * t; +} // ========================================================== // VECTOR FUNCTIONS @@ -1090,18 +1103,18 @@ void CopyVector(const float copy[3], float dest[3]) /*void LerpVectors(const float a[3] , const float b[3], float c[3], float t) { - if (t < 0.0) + if (t < 0.0) { t = 0.0; } - if (t > 1.0) + if (t > 1.0) { t = 1.0; } - c[0] = a[0] + (b[0] - a[0]) * t; - c[1] = a[1] + (b[1] - a[1]) * t; - c[2] = a[2] + (b[2] - a[2]) * t; + c[0] = a[0] + (b[0] - a[0]) * t; + c[1] = a[1] + (b[1] - a[1]) * t; + c[2] = a[2] + (b[2] - a[2]) * t; }*/ /** @@ -1165,66 +1178,48 @@ void GetPositionForward(float pos[3], float ang[3], float returnValue[3], float return AngleNormalize(value); }*/ -float AngleNormalize(float angle) -{ - while (angle > 180.0) - { - angle -= 360.0; - } - while (angle < -180.0) - { - angle += 360.0; - } - return angle; -} - -float AngleDiff(float firstAngle, float secondAngle) -{ - float diff = secondAngle - firstAngle; - return AngleNormalize(diff); -} //Credits to Boikinov for figuring out these calculations I don't understand the thought process of float GetAngleBetweenVectors(const float vector1[3], const float vector2[3], const float direction[3]) { - float vector1_n[3], vector2_n[3], direction_n[3], cross[3]; - NormalizeVector(direction, direction_n); - NormalizeVector(vector1, vector1_n); - NormalizeVector(vector2, vector2_n); - float degree = ArcCosine(GetVectorDotProduct(vector1_n, vector2_n)) * 57.29577951; - GetVectorCrossProduct(vector1_n, vector2_n, cross); + float vector1_n[3], vector2_n[3], direction_n[3], cross[3]; + NormalizeVector(direction, direction_n); + NormalizeVector(vector1, vector1_n); + NormalizeVector(vector2, vector2_n); + float degree = ArcCosine(GetVectorDotProduct(vector1_n, vector2_n)) * 57.29577951; + GetVectorCrossProduct(vector1_n, vector2_n, cross); - if (GetVectorDotProduct(cross, direction_n) < 0.0) - { - degree *= -1.0; - } + if (GetVectorDotProduct(cross, direction_n) < 0.0) + { + degree *= -1.0; + } - return degree; + return degree; } void RotateYaw(float angles[3], float degree) { - float direction[3], normal[3]; - GetAngleVectors(angles, direction, NULL_VECTOR, normal); + float direction[3], normal[3]; + GetAngleVectors(angles, direction, NULL_VECTOR, normal); - float sin = Sine(degree * 0.01745328); - float cos = Cosine(degree * 0.01745328); - float a = normal[0] * sin; - float b = normal[1] * sin; - float c = normal[2] * sin; - float x = direction[2] * b + direction[0] * cos - direction[1] * c; - float y = direction[0] * c + direction[1] * cos - direction[2] * a; - float z = direction[1] * a + direction[2] * cos - direction[0] * b; - direction[0] = x; - direction[1] = y; - direction[2] = z; + float sin = Sine(degree * 0.01745328); + float cos = Cosine(degree * 0.01745328); + float a = normal[0] * sin; + float b = normal[1] * sin; + float c = normal[2] * sin; + float x = direction[2] * b + direction[0] * cos - direction[1] * c; + float y = direction[0] * c + direction[1] * cos - direction[2] * a; + float z = direction[1] * a + direction[2] * cos - direction[0] * b; + direction[0] = x; + direction[1] = y; + direction[2] = z; - GetVectorAngles(direction, angles); + GetVectorAngles(direction, angles); - float up[3]; - GetVectorVectors(direction, NULL_VECTOR, up); + float up[3]; + GetVectorVectors(direction, NULL_VECTOR, up); - float roll = GetAngleBetweenVectors(up, normal, direction); - angles[2] += roll; + float roll = GetAngleBetweenVectors(up, normal, direction); + angles[2] += roll; } // ========================================================== @@ -1289,6 +1284,44 @@ bool TraceRayDontHitPlayersOrEntityEx(int entity, int mask, any data) return true; } +bool EnumerateLivingPlayers(int entIndex, ArrayList players) +{ + if (IsValidClient(entIndex) && IsPlayerAlive(entIndex) && !IsClientInGhostMode(entIndex)) + { + players.Push(entIndex); + } + + return true; +} + +bool EnumerateBreakableEntities(int entIndex, ArrayList array) +{ + if (!IsValidEntity(entIndex)) + { + return true; + } + + char className[64]; + GetEntityClassname(entIndex, className, sizeof(className)); + + if (strcmp(className, "prop_physics") == 0 || strcmp(className, "prop_dynamic") == 0) + { + if (GetEntProp(entIndex, Prop_Data, "m_iHealth") > 0) + { + array.Push(entIndex); + } + } + else if (strncmp(className, "obj_", 4) == 0) + { + if (GetEntProp(entIndex, Prop_Data, "m_iHealth") > 0) + { + array.Push(entIndex); + } + } + + return true; +} + // ========================================================== // TIMER/CALLBACK FUNCTIONS // ========================================================== @@ -1323,10 +1356,10 @@ Action Timer_KillEdict(Handle timer, any entref) // ========================================================== bool IsInfiniteFlashlightEnabled() { - return !!(g_RoundInfiniteFlashlight || (g_PlayerInfiniteFlashlightOverrideConVar.IntValue == 1) || SF_SpecialRound(SPECIALROUND_INFINITEFLASHLIGHT) || ((g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)) && g_NightvisionType == 1)); + return (g_RoundInfiniteFlashlight || (g_PlayerInfiniteFlashlightOverrideConVar.IntValue == 1) || SF_SpecialRound(SPECIALROUND_INFINITEFLASHLIGHT) || ((g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)) && g_NightvisionType == 1)); } -int g_ArraySpecialRoundType[SPECIALROUND_MAXROUNDS]; +static int g_ArraySpecialRoundType[SPECIALROUND_MAXROUNDS]; bool SF_SpecialRound(int specialRound) { @@ -1370,6 +1403,10 @@ void SF_RemoveSpecialRound(int specialRound) void SF_RemoveAllSpecialRound() { + if (SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + StopBeatBoxMusicForAll(); + } for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { g_ArraySpecialRoundType[array] = 0; @@ -1381,7 +1418,19 @@ void SF_RemoveAllSpecialRound() // ========================================================== int GetLocalGlobalDifficulty(int npcIndex = -1) { - if (SF_IsBoxingMap()) + if (npcIndex == -1) + { + return g_DifficultyConVar.IntValue; + } + SF2BossProfileData data; + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(npcIndex); + data = controller.GetProfileData(); + SF2ChaserBossProfileData chaserData; + if (controller.Type == SF2BossType_Chaser) + { + chaserData = view_as(controller).GetProfileData(); + } + if (data.IsPvEBoss || chaserData.BoxingBoss) { if (NPCGetUniqueID(npcIndex) != -1) { @@ -1395,118 +1444,6 @@ int GetLocalGlobalDifficulty(int npcIndex = -1) return g_DifficultyConVar.IntValue; } -bool DispatchParticleEffect(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], - int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) -{ - char particleReal[PLATFORM_MAX_PATH]; - FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); - if (particle[0] != '\0') - { - int tblidx = FindStringTable("ParticleEffectNames"); - if (tblidx == INVALID_STRING_TABLE) - { - LogError("Could not find string table: ParticleEffectNames"); - return false; - } - char tmp[256]; - int count = GetStringTableNumStrings(tblidx); - int stridx = INVALID_STRING_INDEX; - for (int i = 0; i < count; i++) - { - ReadStringTable(tblidx, i, tmp, sizeof(tmp)); - if (strcmp(tmp, particleReal, false) == 0) - { - stridx = i; - break; - } - } - if (stridx == INVALID_STRING_INDEX) - { - LogError("Could not find particle: %s", particleReal); - return false; - } - - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", startPos[0]); - TE_WriteFloat("m_vecOrigin[1]", startPos[1]); - TE_WriteFloat("m_vecOrigin[2]", startPos[2]); - TE_WriteVector("m_vecAngles", angles); - TE_WriteNum("m_iParticleSystemIndex", stridx); - TE_WriteNum("entindex", entity); - TE_WriteNum("m_iAttachType", view_as(attachType)); - TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); - TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); - TE_WriteNum("m_bControlPoint1", 0); - TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 0); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); - TE_SendToAll(); - } - else - { - //LogError("There is no valid particle to use for effects."); - return false; - } - return true; -} - -bool DispatchParticleEffectBeam(int entity, const char[] particle, float startPos[3], float angles[3], float endPos[3], - int attachmentPointIndex = 0, ParticleAttachment attachType = PATTACH_CUSTOMORIGIN, bool resetAllParticlesOnEntity = false) -{ - char particleReal[PLATFORM_MAX_PATH]; - FormatEx(particleReal, PLATFORM_MAX_PATH, "%s", particle); - if (particle[0] != '\0') - { - int tblidx = FindStringTable("ParticleEffectNames"); - if (tblidx == INVALID_STRING_TABLE) - { - LogError("Could not find string table: ParticleEffectNames"); - return false; - } - char tmp[256]; - int count = GetStringTableNumStrings(tblidx); - int stridx = INVALID_STRING_INDEX; - for (int i = 0; i < count; i++) - { - ReadStringTable(tblidx, i, tmp, sizeof(tmp)); - if (strcmp(tmp, particleReal, false) == 0) - { - stridx = i; - break; - } - } - if (stridx == INVALID_STRING_INDEX) - { - LogError("Could not find particle: %s", particleReal); - return false; - } - - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", startPos[0]); - TE_WriteFloat("m_vecOrigin[1]", startPos[1]); - TE_WriteFloat("m_vecOrigin[2]", startPos[2]); - TE_WriteVector("m_vecAngles", angles); - TE_WriteNum("m_iParticleSystemIndex", stridx); - TE_WriteNum("entindex", entity); - TE_WriteNum("m_iAttachType", view_as(attachType)); - TE_WriteNum("m_iAttachmentPointIndex", attachmentPointIndex); - TE_WriteNum("m_bResetParticles", resetAllParticlesOnEntity); - TE_WriteNum("m_bControlPoint1", 1); - TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); - TE_SendToAll(); - } - else - { - //LogError("There is no valid particle to use for effects."); - return false; - } - return true; -} - MRESReturn Hook_GlowUpdateTransmitState(int glow, DHookReturn returnHook) { returnHook.Value = SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index 0a565f57..66c10371 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -8,7 +8,7 @@ static float g_TrapDespawnTimer[2049]; static bool g_TrapClosed[2049]; static int g_TrapState[2049]; -static int g_TrapMaster[2049]; +static SF2NPC_Chaser g_TrapMaster[2049]; static bool g_TrapStartedOpenAnim[2049]; static bool g_TrapDoIdleAnim[2049]; static bool g_TrapAnimChange[2049]; @@ -38,9 +38,9 @@ static void OnJump(SF2_BasePlayer client) } } -void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) +void Trap_SpawnTrap(float position[3], float direction[3], SF2NPC_Chaser controller) { - int slender = NPCGetEntIndex(bossIndex); + int slender = controller.EntIndex; if (!slender || slender == INVALID_ENT_REFERENCE) { return; @@ -64,7 +64,11 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) float yaw = GetAngleBetweenVectors(product2, cross, tempAngles); RotateYaw(newAngles, yaw - 90.0); - switch (NPCChaserGetTrapType(bossIndex)) + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + int difficulty = controller.Difficulty; + + switch (data.TrapType[difficulty]) { case SF2BossTrapType_BearTrap: { @@ -72,7 +76,7 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) if (trapEntity != -1) { TeleportEntity(trapEntity, newPos, newAngles, NULL_VECTOR); - SetEntityModel(trapEntity, g_SlenderTrapModel[bossIndex]); + SetEntityModel(trapEntity, data.TrapModel); DispatchSpawn(trapEntity); ActivateEntity(trapEntity); @@ -95,21 +99,21 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) AcceptEntityInput(trapEntity, "EnableCollision"); - g_TrapMaster[trapEntity] = bossIndex; + g_TrapMaster[trapEntity] = controller; g_TrapClosed[trapEntity] = false; g_TrapAnimChange[trapEntity] = true; SetEntProp(trapEntity, Prop_Data, "m_bSequenceLoops", false); - if (g_SlenderTrapAnimOpen[bossIndex][0] != '\0') + if (data.TrapAnimOpen[0] != '\0') { - SetVariantString(g_SlenderTrapAnimOpen[bossIndex]); + SetVariantString(data.TrapAnimOpen); AcceptEntityInput(trapEntity, "SetAnimation"); HookSingleEntityOutput(trapEntity, "OnAnimationDone", OnTrapOpenComplete, false); g_TrapStartedOpenAnim[trapEntity] = true; } else { - SetVariantString(g_SlenderTrapAnimIdle[bossIndex]); + SetVariantString(data.TrapAnimIdle); AcceptEntityInput(trapEntity, "SetAnimation"); } @@ -118,7 +122,7 @@ void Trap_SpawnTrap(float position[3], float direction[3], int bossIndex) g_TrapDespawnTimer[trapEntity] = GetGameTime() + GetRandomFloat(20.0, 40.0); g_TrapState[trapEntity] = 0; - EmitSoundToAll(g_SlenderTrapDeploySound[bossIndex], trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); + EmitSoundToAll(data.TrapDeploySound, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); SDKHook(trapEntity, SDKHook_OnTakeDamage, Hook_TrapOnTakeDamage); g_TrapTimer[trapEntity] = CreateTimer(0.1, Timer_TrapThink, EntIndexToEntRef(trapEntity), TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); @@ -145,7 +149,8 @@ static Action Timer_TrapThink(Handle timer, any entref) return Plugin_Stop; } - if (GetGameTime() >= g_TrapDespawnTimer[trapEntity]) + SF2NPC_Chaser controller = g_TrapMaster[trapEntity]; + if (GetGameTime() >= g_TrapDespawnTimer[trapEntity] || !controller.IsValid()) { Trap_Despawn(trapEntity); } @@ -190,16 +195,17 @@ static Action Timer_TrapThink(Handle timer, any entref) player.TrapCount = GetRandomInt(2, 4); } } - if (!g_PlayerHints[player.index][PlayerHint_Trap]) + if (!player.HasHint(PlayerHint_Trap)) { player.ShowHint(PlayerHint_Trap); } player.TakeDamage(true, _, _, 10.0, 128); g_TrapState[trapEntity] = 1; g_TrapAnimChange[trapEntity] = true; - int bossIndex = g_TrapMaster[trapEntity]; - EmitSoundToAll(g_SlenderTrapHitSound[bossIndex], trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - SetVariantString(g_SlenderTrapAnimClose[bossIndex]); + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + EmitSoundToAll(data.TrapCatchSound, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); + SetVariantString(data.TrapAnimClose); AcceptEntityInput(trapEntity, "SetAnimation"); AcceptEntityInput(trapEntity, "DisableCollision"); g_TrapClosed[trapEntity] = true; @@ -218,27 +224,33 @@ static void TrapUpdateAnimation(int trapEntity) return; } int state = g_TrapState[trapEntity]; - int bossIndex = g_TrapMaster[trapEntity]; + SF2NPC_Chaser controller = g_TrapMaster[trapEntity]; + if (!controller.IsValid()) + { + return; + } + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); switch (state) { case 0: { - if (!g_TrapStartedOpenAnim[trapEntity] && g_SlenderTrapAnimOpen[bossIndex][0] != '\0') + if (!g_TrapStartedOpenAnim[trapEntity] && data.TrapAnimOpen[0] != '\0') { - SetVariantString(g_SlenderTrapAnimOpen[bossIndex]); + SetVariantString(data.TrapAnimOpen); AcceptEntityInput(trapEntity, "SetAnimation"); HookSingleEntityOutput(trapEntity, "OnAnimationDone", OnTrapOpenComplete, true); g_TrapStartedOpenAnim[trapEntity] = true; } else if (g_TrapDoIdleAnim[trapEntity]) { - SetVariantString(g_SlenderTrapAnimIdle[bossIndex]); + SetVariantString(data.TrapAnimIdle); AcceptEntityInput(trapEntity, "SetAnimation"); } } case 1: { - SetVariantString(g_SlenderTrapAnimClose[bossIndex]); + SetVariantString(data.TrapAnimClose); AcceptEntityInput(trapEntity, "SetAnimation"); AcceptEntityInput(trapEntity, "DisableCollision"); } @@ -272,9 +284,11 @@ static Action Hook_TrapOnTakeDamage(int trapEntity,int &attacker,int &inflictor, g_TrapClosed[trapEntity] = true; g_TrapState[trapEntity] = 1; g_TrapAnimChange[trapEntity] = true; - int bossIndex = g_TrapMaster[trapEntity]; - EmitSoundToAll(g_SlenderTrapMissSound[bossIndex], trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); - SetVariantString(g_SlenderTrapAnimClose[bossIndex]); + SF2NPC_Chaser controller = g_TrapMaster[trapEntity]; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + EmitSoundToAll(data.TrapMissSound, trapEntity, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, 1.0); + SetVariantString(data.TrapAnimClose); AcceptEntityInput(trapEntity, "SetAnimation"); AcceptEntityInput(trapEntity, "DisableCollision"); if (g_TrapDespawnTimer[trapEntity] > 5.0) diff --git a/addons/sourcemod/translations/sf2.phrases.txt b/addons/sourcemod/translations/sf2.phrases.txt index a4813aff..1dca3361 100644 --- a/addons/sourcemod/translations/sf2.phrases.txt +++ b/addons/sourcemod/translations/sf2.phrases.txt @@ -20,6 +20,18 @@ "ko" "투표 성공! 선택된 난이도:" } + "SF2 Difficulty Vote Finished Unsuccessful" + { + "#format" "{1:d}" + "en" "Vote for ({lightblue}Apollyon{default}/{lightblue}Nightmare{default}) failed, ({lightblue}{1}{default}%%) of the vote is required to pass. The difficulty has been changed to:" + } + + "SF2 Difficulty Vote Finished Unsuccessful Runoff" + { + "#format" "{1:d}" + "en" "Vote for ({lightblue}Apollyon{default}/{lightblue}Nightmare{default}) failed, ({lightblue}{1}{default}%%) of the vote is required to pass. Initiating a runoff vote..." + } + "SF2 Easy Difficulty" { "en" "Newbie" @@ -81,7 +93,7 @@ "pt" "Calamidade" "ko" "대재앙" } - + "SF2 Random Difficulty" { "en" "Random" @@ -149,8 +161,9 @@ "chi" "宽限期结束。死亡将会导致出局。" "pt" "O tempo de preparação acabou. Mortes resultarão em eliminação." "ko" "유예 기간이 끝났습니다. 이제 죽으면 탈락입니다." + } - + "SF2 Grace Period Page" { "en" "Wait for Grace period to end to collect a page." @@ -183,12 +196,7 @@ "SF2 Camping System Warning" { "#format" "{1:d}" - "en" "WARNING: If you do not move from your spot in {1} seconds, you will be caught by the boss." - "ru" "ВНИМАНИЕ: Если в течение {1} сек. вы не начнёте двигаться, то вы будете схвачены!" - "fi" "VAROITUS: Jos et liiku paikaltasi {1} sekunttiin, vihollinen nappaa sinut." - "chi" "警告:如果你在 {1} 秒内不从当前位置移动的话,你会被Boss抓住。" - "pt" "AVISO: caso não se mova do seu lugar em {1} segundos, você será pego pelo chefão." - "ko" "경고: {1} 초 안에 해당 장소를 벗어나지 않으면, 죽게 됩니다." + "en" "ALERT: If you do not move from your spot in {1} seconds, you will be eliminated from the game." } "SF2 Main Menu Title" @@ -296,8 +304,6 @@ "SF2 Prefix" { "en" "[SF2]" - "pt" "[SF2]" - "ko" "[SF2]" } "SF2 Help Menu Title" @@ -442,182 +448,137 @@ "SF2 Help Scout Class Info Menu Title" { - "en" "[1] The Scout" - "ru" "[1] Разведчик" - "fi" "[1] Scout" - "chi" "[1] 侦察兵" - "pt" "[1] Scout" - "ko" "[1] 스카웃" + "en" "The Scout" + "ru" "Разведчик" + "fi" "Scout" + "chi" "侦察兵" + "pt" "Scout" + "ko" "스카웃" } "SF2 Help Scout Class Info Description" { - "en" "[+] 15%% increase in sprint duration\n[+] Fastest base sprint speed\n[-] Low health, most affected by screen wobbling" - "ru" "[+] На 15%% увеличена продолжительность бега\n[-] Мало здоровья, наиболее подвержен внушению" - "fi" "[+] 15%% lisäys juoksun kestoon\n[-] Heikko terveys, ottaa eniten vaikustusta näytön keikkumisesta" - "chi" "[+] 增加 15%% 冲刺持续时间\n[-] 低生命值,更容易受到屏幕震动效果" - "pt" "[+] Aumento de 15%% na duração da corrida\n[-] Menos vida (afetado principalmente pelo balanço da tela)" - "ko" "[+] 8% 더 많은 스테미너 양\n[+] 기본 달리기 속도가 제일 빠름\n[-] 소음이 보스에게 20%% 더 크게 들림\n[-] 체력 낮음\n[-] 스태틱 15%% 취약\n[-] 스태틱 회복 속도 15%% 감소" + "en" "[+] 10%% increase in sprint duration\n[-] Low health, most affected by screen wobbling" } "SF2 Help Soldier Class Info Menu Title" { - "en" "[2] The Soldier" - "ru" "[2] Солдат" - "fi" "[2] Soldier" - "chi" "[2] 士兵" - "pt" "[2] Soldier" - "ko" "[2] 솔저" + "en" "The Soldier" + "ru" "Солдат" + "fi" "Soldier" + "chi" "士兵" + "pt" "Soldier" + "ko" "솔저" } "SF2 Help Soldier Class Info Description" { - "en" "[+] The Disciplinary Action increases both walking\nand sprinting speed\n[+] 5%% higher static resistance and recovery rate\n[+] Market Gardener crits in midair\n[+] Jumping while walking doesn't cost stamina\n[+] The Escape Plan increases speed by your health loss\n[-] The Half-Zatoichi only restores 5 HP on kill\n[-] Slowest sprint speed along with Heavy and Demoman" - "ru" "[+] Дисциплинарное взыскание увеличивает скорость бега и ходьбы\n[-] Полудзатоити восстанавливает только 5 ед. за убийство\n[-] План эвакуации влияет только на скорость бега" - "fi" "[+] Kuritoimenpide lisää kävelyn ja juoksemisen nopeutta\nPuolisokea Samurai antaa vain 5 terveyttä taposta\n[-] Pakosuunnitelma vaikuttaa vain ja ainostaan juoksu nopeuteen" - "chi" "[+] 指挥官的军鞭同时增加走路和冲刺速度\n[-] 座头市之刀击杀后只恢复5HP\n[-] 逃生计划镐只影响冲刺速度" - "pt" "[+] A Ação Disciplinar aumenta a velocidade de andar e de correr\n[-] A Quase-Zatoichi restaura apenas 5 de vida ao matar\n[-] O Plano de Fuga afeta somente a velocidade de correr" - "ko" "[+] 징계 조치로 속도 증진 부여 가능\n[+] 스태틱 5%% 저항\n[+] 스태틱 회복 속도 5%% 증가\n[+] 마켓 가든 모종삽으로 공중에 있을 때 2배의 피해를 가할 수 있음\n[+] 걸을 때 점프는 스테미너에 영향을 주지 않음\n[+] 탈출 계획을 들고 있을 때 체력 손실에 따라 속도 증가\n[-] 반맹인 검객을 통해서 얻는 회복량이 5로 고정\n[-] 헤비와 데모맨과 더불어 가장 느린 달리기 속도" + "en" "[+] The Disciplinary Action increases both walking\nand sprinting speed\n[+] Cannot lose sprint points while non-sprint jumping\n[-] The Half-Zatoichi only restores 5 HP on kill\n[-] The Escape Plan only affects sprinting speed" } "SF2 Help Sniper Class Info Menu Title" { - "en" "[8] The Sniper" - "ru" "[8] Спайпер" - "fi" "[8] Sniper" - "chi" "[8] 狙击手" - "pt" "[8] Sniper" - "ko" "[8] 스나이퍼" + "en" "The Sniper" + "ru" "Спайпер" + "fi" "Sniper" + "chi" "狙击手" + "pt" "Sniper" + "ko" "스나이퍼" } "SF2 Help Sniper Class Info Description" { "en" "[+] Blink rate increased by 100%%\n[-] Low health, most affected by screen wobbling" - "ru" "[+] Моргает на 45%% реже\n[-] Мало здоровья, наиболее подвержен внушению" - "fi" "[+] Räpäyttelya lisätty 40%%\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" - "chi" "[+] 眨眼频率增益 45%%\n[-] 低生命值,更容易受到屏幕震动效果" - "pt" "[+] Aumento da taxa de piscada em 45%%\n[-] Menos vida (afetado principalmente pelo balanço da tela)" - "ko" "[+] 눈 깜빡임 지속 시간 100%% 증가\n[+] 울트라비전의 범위가 2배로 증가함\n[+] 스태틱 5%% 저항\n[+] 스태틱 회복 속도 10%% 증가\n[-] 울트라비전의 밝기가 25%% 어두어짐" } "SF2 Help Demoman Class Info Menu Title" { - "en" "[4] The Demoman" - "ru" "[4] Подрывник" - "fi" "[4] Demomies" - "chi" "[4] 爆破手" - "pt" "[4] Demoman" - "ko" "[4] 데모맨" + "en" "The Demoman" + "ru" "Подрывник" + "fi" "Demomies" + "chi" "爆破手" + "pt" "Demoman" + "ko" "데모맨" } "SF2 Help Demoman Class Info Description" { - "en" "[+] 66%% increase in scare sprint duration\n[-] Slowest base sprint speed along with Soldier and Heavy\n[-] The Half-Zatoichi only restores 5 HP on kill" - "ru" "[+] На 66%% увеличена продолжительность бега при испуге\n[-] Полудзатоити восстанавливает только 20 ед. за убийство" - "fi" "[+] 66%% lisäys pelottamisen juoksunopeuteen\n[-] Puolisokea Samurai palauttaa vain 20 terveyttä taposta" - "chi" "[+] 惊吓状态下增加 66%% 冲刺持续时间\n[-] 座头市之刀击杀后只恢复20HP" - "pt" "[+] Aumento de 66%% na duração da corrida quando assustado\n[-] A Quase-Zatoichi restaura apenas 20 de vida ao matar" - "ko" "[+] 공포 상태일 때, 스테미너 양 66%% 증가\n[-] 솔저와 헤비와 더불어 가장 느린 달리기 속도\n[-] 반맹인 검객을 통해서 얻는 회복량이 5로 고정" + "en" "[+] 10%% increase in sprint duration\n[-] The Half-Zatoichi only restores 5 HP on kill" } "SF2 Help Heavy Class Info Menu Title" { - "en" "[5] The Heavy" - "ru" "[5] Пулемётчик" - "fi" "[5] Heavy" - "chi" "[5] 机枪手" - "pt" "[5] Heavy" - "ko" "[5] 헤비" + "en" "The Heavy" + "ru" "Пулемётчик" + "fi" "Heavy" + "chi" "机枪手" + "pt" "Heavy" + "ko" "헤비" } "SF2 Help Heavy Class Info Description" { - "en" "[+] Large health, least affected by screen wobbling\n [+] Unable to be trapped by bear traps\n [-] Slowest sprint speed along with Soldier and Demoman\n[-] The Gloves of Running Urgently only affects sprinting speed" - "ru" "[+] Много здоровья, мало подвержен внушению\n[-] ГРУ влияют только на скорость бега" - "fi" "[+] Suuri terveys, vähiten vaikutusta näytön keikkumisesta\n Pikajuoksuhanskat vaikuttaa vain juoksunopeuteen" - "chi" "[+] 很多生命值,不容易受到屏幕震动效果\n[-] 紧急逃跑手套只影响冲刺速度" - "pt" "[+] Mais vida e o balanço da tela afeta menos\n[-] As Geradoras de Rapidez Urgente afetam somente a velocidade de correr" - "ko" "[+] 체력 많음\n[+] 스태틱 15%% 저항\n[+] 스태틱 회복 속도 15%% 증가\n[+] 덫에 걸리지 않음\n[-] G.R.U는 옛날 성능으로 돌아감" + "en" "[+] Large health, least affected by screen wobbling\n[+] Bear traps only deal minimum damage\n[-] The Gloves of Running Urgently only affects sprinting speed" } "SF2 Help Medic Class Info Menu Title" { - "en" "[7] The Medic" - "ru" "[7] Медик" - "fi" "[7] Medic" - "chi" "[7] 医生" - "pt" "[7] Medic" - "ko" "[7] 메딕" + "en" "The Medic" + "ru" "Медик" + "fi" "Medic" + "chi" "医生" + "pt" "Medic" + "ko" "메딕" } "SF2 Help Medic Class Info Description" { - "en" "[+] Regenerates health over time\n[-] Highest priority to bosses\n[-] Unable to pick up health packs\n[-] Proxies deal 15%% more damage" - "ru" "[+] Со временем восстанавливает здоровье\n[-] Приоритетная цель" - "fi" "[+] Terveys palautuu ajan myötä\n[-] Ensisijainen kohde bosseille" - "chi" "[+] 随时间恢复生命值\n[-] Boss们的优先目标" - "pt" "[+] Regenera vida ao longo do tempo\n[-] Torna-se o alvo prioritário de chefões" - "ko" "[+] 매 초마다 체력이 회복됨\n[-] 보스 타겟 우선순위 20%% 증가\n[-] 치료제를 먹을 수 없음\n[-] 프록시에게 받는 피해 15%% 취약\n[-] Thanatophobia 스페셜 라운드 때 사용 불가" + "en" "[+] Regenerates health over time\n[-] Prioritized target to bosses\n[-] Blocked on the special round Thanatophobia" } "SF2 Help Pyro Class Info Menu Title" { - "en" "[3] The Pyro" - "ru" "[3] Поджигатель" - "fi" "[3] Pyro" - "chi" "[3] 火焰兵" - "pt" "[3] Pyro" - "ko" "[3] 파이로" + "en" "The Pyro" + "ru" "Поджигатель" + "fi" "Pyro" + "chi" "火焰兵" + "pt" "Pyro" + "ko" "파이로" } "SF2 Help Pyro Class Info Description" { "en" "[+] Fireproof, duh!\n[-] The Powerjack only restores 20 HP on kill, and\nmelee damage taken increased to 33%%" - "ru" "[+] Огнеупорный, хех!\n[-] Разъединитель восстанавливает только 20 ед. за убийство, и\nполучаемый при ближнем бое урон увеличен на 33%%" - "fi" "[+] Tulenkestävä, duh!\n[-] Tunkki palauttaa vain 20 terveyttä taposta\nlähitaistelu vahingon ottoa lisätty 33%%" - "chi" "[+] 防火!哇!\n[-] 强力千斤顶击杀只恢复20HP,受到的近战伤害增加33%%" - "pt" "[+] Imune a fogo (ah, vá?)\n[-] A Ligação Direta restaura apenas 20 de vida e\no dano sofrido de armas corpo a corpo é 33%% maior" - "ko" "[+] 화염 면역!\n[-] 전원 잭을 통해 얻을 수 있는 회복량이 20으로 고정,\n착용 시 받는 피해량이 33% 증가함" } "SF2 Help Spy Class Info Menu Title" { - "en" "[9] The Spy" - "ru" "[9] Шпион" - "fi" "[9] Spy" - "chi" "[9] 间谍" - "pt" "[9] Spy" - "ko" "[9] 스파이" + "en" "The Spy" + "ru" "Шпион" + "fi" "Spy" + "chi" "间谍" + "pt" "Spy" + "ko" "스파이" } "SF2 Help Spy Class Info Description" { - "en" "[+] All sounds you make are 30%% quieter to bosses\n[+] 5%% more sprint duration\n[-] Low health\n[-] Higher boss priority\n[-] Least amount of scare sprint speed boost\n[-] Big Earner and Kunai only work against proxies" - "ru" "[+] Все издаваемые звуки на 30%% тише\n[-] Мало здоровья, наиболее подвержен внушению" - "fi" "[+] Kaikki äänet jota teet ovat 30%% hiljaisempia bosseille\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" - "chi" "[+] 你发出的声音对Boss来说有 30%% 安静加成\n[-] 低生命值,最容易受到屏幕震动效果" - "pt" "[+] Todos os sons que você faz são 30%% mais silenciosos\npara os chefões\n[-] Menos vida (afetado principalmente pelo balanço da tela)" - "ko" "[+] 소음이 30%% 더 조용히 들림\n[+] 5%% 더 많은 스테미나 양\n[-] 낮은 체력\n[-] 보스 타겟 우선순위 10%% 증가\n[-] 공포 상태에서 증가하는 속도값 감소\n[-] 재력가와 묵인자의 쿠나이는 프록시한테만 적용됨" + "en" "[+] All sounds you make are 30%% quieter to bosses\n[-] Low health, most affected by screen wobbling\n[-] Slightly prioritized target to bosses" } "SF2 Help Engineer Class Info Menu Title" { - "en" "[6] The Engineer" - "ru" "[6] Инженер" - "fi" "[6] Engineer" - "chi" "[6] 工程师" - "pt" "[6] Engineer" - "ko" "[6] 엔지니어" + "en" "The Engineer" + "ru" "Инженер" + "fi" "Engineer" + "chi" "工程师" + "pt" "Engineer" + "ko" "엔지니어" } "SF2 Help Engineer Class Info Description" { - "en" "[+] 75%% increase in Flashlight battery life\n[+] 60%% decrease in Flashlight recharge rate\n[-] Low health, most affected by screen wobbling" - "ru" "[+] Увеличение батареи фонарика на 75%%\n[+] На 60%% снижена скорость перезарядки фонарика\n[-] Низкое здоровье, наиболее подвержен внушению" - "fi" "[+] 75%% lisäys taskulampun patterin elämään\n[-] Vähemmän terveyttä ja eniten vaikutusta näytön keikkumisesta" - "chi" "[+] 增加 75%% 手电筒电池容量\n[+] 增益 60%% 手电筒充能速率\n[-] 低生命值,最容易受到屏幕震动效果" - "pt" "[+] Aumento de 75%% da duração bateria da lanterna\n[+] Aumento de 60%% da taxa de recarga da lanterna\n[-] Menos vida (afetado principalmente pelo balanço da tela)" - "ko" "[+] 손전등 배터리 용량 50%% 증가\n[+] 손전등 범위 100%% 증가\n[+] 손전등 거리 200%% 증가\n[+] 손전등 배터리 충전 속도 20%% 증가\n[+] 손전등 스턴 피해량 50%% 증가\n[+] 25%% 작은 손전등 소음\n[-] 낮은 체력, 스태틱 10%% 취약\n[-] 울트라비전이 생기기까지 걸리는 시간 증가" + "en" "[+] 50%% increase in Flashlight battery life\n[+] 20%% decrease in Flashlight recharge rate\n[+] Flashlight is overall brighter\n[-] Low health, most affected by screen wobbling" } "SF2 Settings Menu Title" @@ -706,14 +667,6 @@ "ko" "프록시 참가 요청 메시지 활성화/비활성화" } - "SF2 Settings Hud Version Title" - { - "en" "Switch HUD version between legacy and new" - "fi" "Vaihda HUD versio vanhan ja uuden välillä" - "pt" "Alternar versão da interface entre antiga e nova" - "ko" "구 버전과 신 버전으로 HUD 전환 설정" - } - "SF2 Settings Ghost Overlay Menu Title" { "en" "Toggle Ghost Mode overlay" @@ -723,7 +676,7 @@ "pt" "Alternar sobreposição do modo fantasma" "ko" "유령 모드 오버레이 토글" } - + "SF2 Settings Ghost Mode Teleport Menu Title" { "en" "Set ghost mode teleport state" @@ -731,7 +684,7 @@ "pt" "Definir estado do teletransporte do modo fantasma" "ko" "유령 모드 텔레포트 상태 설정" } - + "SF2 Settings Ghost Mode Toggle State Menu Title" { "en" "Set ghost mode toggle state" @@ -739,7 +692,12 @@ "pt" "Definir estado da alternação do modo fantasma" "ko" "유령 모드 돌입 토글 설정" } - + + "SF2 Settings View Bobbing Title" + { + "en" "Toggle camera view bobbing" + } + "SF2 Settings Hud Version Title" { "en" "Set HUD version" @@ -758,6 +716,11 @@ "ko" "음악 볼륨 설정" } + "SF2 Settings PvE Menu Title" + { + "en" "PvE Arena Settings" + } + "SF2 Music Volum Changed" { "#format" "{1:d}" @@ -766,7 +729,7 @@ "fi" "{lightblue}Musiikin äänenvoimakkuus vaihdettu {1}%%." "chi" "{lightblue}音乐音量已设为 {1}%% 。" "pt" "{lightblue}Volume da música definido para {1}%%." - "ko" "{lightblue}음악 볼륨이 {1}% 으로 설정 되었습니다." + "ko" "{lightblue}음악 볼륨이 {1}%% 으로 설정 되었습니다." } "SF2 Settings Flashlight Temperature Title" @@ -782,12 +745,7 @@ "SF2 Flashlight Temperature Changed" { "#format" "{1:d}" - "en" "{lightblue}Flashlight temperature set to {1}%%." - "ru" "{lightblue}Температура фонарика установлена на {1}%%." - "fi" "{lightblue}Taskulampun lämpötila vaihdettu {1}%%." - "chi" "{lightblue}手电筒温度设为 {1}%% 。" - "pt" "{lightblue}Temperatura da lanterna definida para {1}%%." - "ko" "{lightblue}손전등 색감이 {1}% 으로 설정되었습니다." + "en" "{lightblue}Flashlight temperature set to {1}k kelvin." } "SF2 Ad Message 1" @@ -875,7 +833,7 @@ "pt" "Você está saindo da arena JxJ! Você perderá as suas armas em {1} segundo(s) caso não retorne!" "ko" "PvP 구역을 벗어났습니다!\n무기를 잃기 싫다면 {1} 초 안에 돌아오세요!" } - + "SF2 AFK On" { "en" "{royalblue}Disabled going to RED naturally." @@ -995,23 +953,13 @@ "SF2 Give Queue Points" { "#format" "{1:d}" - "en" "{dodgerblue}You have been given {1} queue points for playing." - "ru" "{dodgerblue}Вами были получены {1} очков очереди." - "fi" "{dodgerblue}Sinulle on annettu {1} jonopistettä pelaamisesta." - "chi" "{dodgerblue}你因为游玩获得了 {1} 队列点数。" - "pt" "{dodgerblue}Você recebeu {1} ponto(s) de espera por jogar." - "ko" "{dodgerblue}플레이를 위해 {1} 만큼의 대기열 포인트를 받았습니다." + "en" "{dodgerblue}You have been given {lightblue}{1} {dodgerblue}queue points for playing. Type {lightblue}!slnext {dodgerblue}to see when you will get to play on {red}RED{dodgerblue}." } "SF2 Give Group Queue Points" { "#format" "{1:d}" - "en" "{dodgerblue}Your group has been given {1} queue points for playing." - "ru" "{dodgerblue}Вашей группой были получены {1} очков очереди." - "fi" "{dodgerblue}Sinun ryhmällesi on annettu {1} jonopistettä pelaamisesta." - "chi" "{dodgerblue}你的组因为游玩获得了 {1} 队列点数。" - "pt" "{dodgerblue}O seu grupo recebeu {1} ponto(s) de esepra por jogar." - "ko" "{dodgerblue}귀하의 그룹은 {1} 만큼의 대기열 포인트를 받았습니다." + "en" "{dodgerblue}Your group has been given {1} queue points for playing. Type {lightblue}!slnext {dodgerblue}to see when you will get to play on {red}RED{dodgerblue}." } "SF2 Force Play" @@ -1074,7 +1022,7 @@ "pt" "Menu principal — digite !sf2 ou !slender na janela de conversa" "ko" "메인 메뉴 - 채팅창에 !sf2 또는 !slender 입력" } - + "SF2 Hint Trap" { "en" "Get Out of Trap - Press the jump key a few times." @@ -1085,6 +1033,11 @@ "ko" "함정 벗어나기 - 점프 키를 연타하세요." } + "SF2 Admin Menu Title" + { + "en" "SF2 Commands" + } + "SF2 Admin Menu Boss Main" { "en" "Boss Administration" @@ -1381,7 +1334,7 @@ "pt" "{red}{1}{default} foi forçado a sair da rodada atual." "ko" "{red}{1}{default}을(를) 게임에 강제 퇴장시켰습니다." } - + "SF2 Set Queue Points" { "#format" "{1:s}" @@ -1756,7 +1709,7 @@ "chi" "因为玩家已在你的组,所以你不可以使用此命令!" "pt" "Não é possível usar este comando porque o jogador já está no seu grupo." } - + "SF2 Player In Game" { "en" "You cannot perform this command because the player is currently playing on RED." @@ -1976,39 +1929,39 @@ "SF2 Boss Pack Menu Title" { - "en" "Show the current pack, if there's one:" - "ru" "Показать текущий набор, если он есть:" - "fi" "Näytä nykyinen paketti, jos sellainen on:" + "en" "Show the current pack, if there's one:" + "ru" "Показать текущий набор, если он есть:" + "fi" "Näytä nykyinen paketti, jos sellainen on:" "chi" "如果有的话,显示当前包:" "pt" "Exibir o pacote atual (caso haja um):" "ko" "현재 보스팩이 있는 경우, 해당 보스팩을 표시:" } - + "SF2 Boss Next Pack Menu Title" { - "en" "Show the next boss pack, if there is one:" - "ru" "Показать следующий набор боссов, если он есть:" - "fi" "Näytä seuraava boss paketti, jos sellainen on:" + "en" "Show the next boss pack, if there is one:" + "ru" "Показать следующий набор боссов, если он есть:" + "fi" "Näytä seuraava boss paketti, jos sellainen on:" "chi" "如果有的话,显示下一个boss包:" "pt" "Exibir o próximo pacote de chefões (caso haja um):" "ko" "다음 보스팩이 있는 경우, 해당 보스팩을 표시:" } - + "SF2 Boss View On List Title" { - "en" "Show the bosses on the current pack:" - "ru" "Показать боссов в текущем наборе:" - "fi" "Näytä nykyisen paketin bossit:" + "en" "Show the bosses on the current pack:" + "ru" "Показать боссов в текущем наборе:" + "fi" "Näytä nykyisen paketin bossit:" "chi" "显示当前包的Boss:" "pt" "Exibir os chefões do pacote atual:" "ko" "현재 보스팩에 있는 보스 표시:" } - + "SF2 Boss List Menu Title" { - "en" "Current Bosses" - "ru" "Текущие боссы" - "fi" "Nykyiset Bossit" + "en" "Current Bosses" + "ru" "Текущие боссы" + "fi" "Nykyiset Bossit" "chi" "当前Boss" "pt" "Chefões atuais" "ko" "현재 보스 목록" @@ -2053,7 +2006,7 @@ "chi" "选择特殊回合!" "pt" "Selecione a rodada especial!" "ko" "스페셜 라운드를 고르세요!" - } + } "SF2 Special Round Vote Successful" { @@ -2170,7 +2123,7 @@ "pt" "{lightblue}Agora, o modo fantasma será ativado após morrer estando na RED." "ko" "{lightblue}이제 RED 팀으로 플레이 도중 사망 시 유령 모드가 활성화됩니다." } - + "SF2 Settings View Bobbing Toggle Title" { "en" "Set view bobbing toggle" @@ -2201,13 +2154,6 @@ "pt" "Alterações recentes:" "ko" "최근 변경 사항:" } - - "SF2 Change Log" - { - "en" "-Precached literally every boss key value\n-Removed certain backwards compatibility" - "pt" "- Pré-carregamento de todos os valores-chave\nde chefões (literalmente)\n- Remoção de certas retrocompatibilidades" - "ko" "-모든 보스의 키밸류 값을 프리캐쉬하고\n사후 지원용으로 남겨뒀던 불필요한 커맨드를 지웠음!" - } "SF2 Smite player" { @@ -2250,4 +2196,69 @@ "pt" "{red}{1}{default} derrotou {valve}{2}{default}." "ko" "{red}{1}{default}이(가) {default}{2}을(를) 쓰러뜨렸습니다!" } + + "SF2 Exiting PvE Arena" + { + "#format" "{1:d}" + "en" "You are exiting the PvE Arena!\nYou will lose your weapons in {1} second(s) if you don't return!" + } + + "SF2 PvE Invalid File" + { + "en" "That is an invalid sound file." + } + + "SF2 PvE Add Unique Track" + { + "en" "You already added that soundtrack, add a unique soundtrack." + } + + "SF2 PvE Tracks Unavailable" + { + "en" "You don't have any more music sounds available, please remove at least one music file." + } + + "SF2 PvE Added Track" + { + "#format" "{1:s}" + "en" "Successfully added sound file {1}." + } + + "SF2 PvE Music Settings Title" + { + "en" "Adjust music settings" + } + + "SF2 PvE No Tracks 1-3" + { + "en" "It looks like you don't have any music themes added, use !sf2_add_pve_music or sm_sf2_add_pve_music (for console users) to add a sound file." + } + + "SF2 PvE No Tracks 2-3" + { + "en" "Example: !sf2_add_pve_music \"slender_boxing_ring/music/metal_gear_rising_revengeance_it_has_to_be_this_way.wav\"" + } + + "SF2 PvE No Tracks 3-3" + { + "en" "Make sure the sound file you want to add is found in your TF2 game files otherwise it will not play for you and print out a missing sound error." + } + + "SF2 PvE Set Volume" + { + "#format" "{1:d}" + "en" "Set volume ({1}%%)" + } + + "SF2 PvE Remove Music" + { + "en" "Remove" + } + + "SF2 PvE Removed Music" + { + "#format" "{1:s}" + "en" "Removed music {1}." + } } + diff --git a/sf2.fgd b/sf2.fgd index 304045dd..1eaeeb04 100644 --- a/sf2.fgd +++ b/sf2.fgd @@ -398,6 +398,11 @@ input DisableStaminaJumpRestriction(void) : "Disable the stamina jump restriction." ] -@SolidClass base(trigger_multiple) = sf2_trigger_pve : "(Private only) A trigger brush that when touched by a player will enter PvE mode." +@SolidClass base(trigger_multiple) = sf2_trigger_pve : "A trigger brush that when touched by a player will enter PvE mode." [ -] + bosspve(choices) : "Is Boss Arena" : 1 : "(Modified only) Determines if the PvE arena can spawn bosses and play soundtracks to players. Useful if you want areas where players can wield weapons but shouldn't damage each other." = + [ + 0 : "Disabled" + 1 : "Enabled" + ] +] \ No newline at end of file From 08ad8669105ca4ba427416e9ef2d5ad3048a0e55 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Wed, 15 Nov 2023 11:37:16 -0700 Subject: [PATCH 15/86] Patch 1 --- addons/sourcemod/scripting/include/sf2.inc | 27 ++- .../include/sf2/profiles/profiles_chaser.inc | 7 - addons/sourcemod/scripting/sf2.sp | 2 +- .../scripting/sf2/extras/commands.sp | 2 - .../sourcemod/scripting/sf2/extras/natives.sp | 1 + addons/sourcemod/scripting/sf2/npc.sp | 6 - .../scripting/sf2/npc/entities/base/entity.sp | 8 +- .../sf2/npc/entities/chaser/actions/attack.sp | 37 +++- .../chaser/actions/attacks/forward_based.sp | 190 ++++++++++++++++++ .../entities/chaser/actions/attacks/melee.sp | 4 +- .../npc/entities/chaser/actions/mainlayer.sp | 4 +- .../sf2/npc/entities/chaser/entity.sp | 70 ++++--- .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 18 ++ .../sf2/profiles/profile_chaser_precache.sp | 8 - .../sf2/profiles/profiles_boss_functions.sp | 6 - .../sourcemod/scripting/sf2/specialround.sp | 12 +- addons/sourcemod/scripting/sf2/stocks.sp | 4 - 17 files changed, 323 insertions(+), 83 deletions(-) create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/forward_based.sp diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 507a7f64..8e376e45 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -449,6 +449,9 @@ forward void SF2_OnBossDecloaked(int bossIndex); */ forward void SF2_OnBossFinishSpawning(int bossIndex); +#pragma deprecated Use custom boss attacks. +forward void SF2_OnBossAttacked(int bossIndex, int attackIndex); + /** * Called when the boss finishes validating all attack indexes. * @@ -1482,21 +1485,15 @@ native bool SF2_GetBossAttackProfileVector(const char[] profile, const char[] ke #pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetRandomStringFromBossProfile(const char[] profile, const char[] key, char[] buffer, int bufferLen, int index=-1); -/** - * Returns an attribute name if one exists - * @param bossIndex Boss index. - * @param attribute Name of attribute in enum. -*/ +#pragma deprecated Use SF2BossProfileData instead. native bool SF2_GetBossAttributeName(int bossIndex, int attribute); -/** - * Returns the value of an attribute. - * @param bossIndex Boss index. - * @param attribute Name of attribute in enum. - * @param defaultValue Default value of attribute. -*/ +#pragma deprecated Use SF2BossProfileData instead. native float SF2_GetBossAttributeValue(int bossIndex, int attribute); +#pragma deprecated Use SF2_ChaserBossEntity.GetAttackName instead. +native int SF2_GetBossCurrentAttackIndex(int bossIndex); + /** * Returns the teleport think timer in Handle form * @@ -1915,6 +1912,14 @@ methodmap SF2_ChaserBossEntity < SF2_BaseBossEntity */ public native int GetAttackName(char[] buffer, int bufferSize); + /** + * Gets the current attack index of the boss. + */ + property int AttackIndex + { + public native get(); + } + /** * Gets the time which the attack can be used again * diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 3bc3f22b..0bbed505 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -1,4 +1,3 @@ -ConVar g_DefaultBossVisibilityStateConVar; #include enum @@ -2046,8 +2045,6 @@ enum struct SF2ChaserBossProfileData SF2ChaserBossProfileChaseOnLookData ChaseOnLookData; - bool UnnerfedVisibility; - bool ClearLayersOnAnimUpdate; float HealAnimationTimer; @@ -2261,10 +2258,6 @@ enum struct SF2ChaserBossProfileData this.NormalSoundHook = false; this.OldAnimationAI = false; - this.ChaseOnLookData.Init(); - - this.UnnerfedVisibility = g_DefaultBossVisibilityStateConVar.BoolValue; - this.ClearLayersOnAnimUpdate = true; this.HealAnimationTimer = 0.0; diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index dc7deb17..7a64969c 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -192,7 +192,6 @@ int g_SlenderOutlineColorG[MAX_BOSSES]; int g_SlenderOutlineColorB[MAX_BOSSES]; int g_SlenderOutlineTransparency[MAX_BOSSES]; bool g_SlenderUseRainbowOutline[MAX_BOSSES]; -float g_SlenderRainbowCycleRate[MAX_BOSSES]; int g_TrapEntityCount; float g_RoundTimeMessage = 0.0; @@ -620,6 +619,7 @@ GlobalForward g_OnBossCloakedFwd; GlobalForward g_OnBossDecloakedFwd; GlobalForward g_OnBossFinishSpawningFwd; GlobalForward g_OnBossPreAttackFwd; +GlobalForward g_OnBossAttackedFwd; GlobalForward g_OnBossPreTakeDamageFwd; GlobalForward g_OnBossPreFlashlightDamageFwd; GlobalForward g_OnChaserBossGetSuspendActionFwd; diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 6b2e3d11..a03c927a 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -163,8 +163,6 @@ public void OnPluginStart() g_IgnoreRedPlayerDeathSwapConVar = CreateConVar("sf2_ignore_red_player_death_team_switch", "0", "If set to 1, RED players will not switch back to the BLU team.", FCVAR_NOTIFY, true, 0.0, true, 1.0); g_IgnoreRedPlayerDeathSwapConVar.AddChangeHook(OnConVarChanged); - g_DefaultBossVisibilityStateConVar = CreateConVar("sf2_default_boss_visibility", "0", "Sets the default visibility state for bosses before the CBaseNPC update to allow for easier/harder peeking, this does not affect bosses seeing through transparent surfaces like glass however. Keep in mind creators can override this value for specific bosses only.", _, true, 0.0, true, 1.0); - g_EnableWallHaxConVar = CreateConVar("sf2_enable_wall_hax", "0", "Enables/disables the Wall Hax special round without needing to turn on Wall Hax. This will not force the difficulty to Insane and will show player + boss outlines.", _, true, 0.0, true, 1.0); g_TimeLimitConVar = CreateConVar("sf2_timelimit_default", "300", "The time limit of the round. Maps can change the time limit.", _, true, 0.0); diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 89f7bf68..3db11dc4 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -23,6 +23,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnBossCloakedFwd = new GlobalForward("SF2_OnBossCloaked", ET_Ignore, Param_Cell); g_OnBossDecloakedFwd = new GlobalForward("SF2_OnBossDecloaked", ET_Ignore, Param_Cell); g_OnBossPreAttackFwd = new GlobalForward("SF2_OnBossPreAttack", ET_Ignore, Param_Cell, Param_Cell); + g_OnBossAttackedFwd = new GlobalForward("SF2_OnBossAttacked", ET_Ignore, Param_Cell, Param_Cell); g_OnBossPreTakeDamageFwd = new GlobalForward("SF2_OnBossPreTakeDamage", ET_Hook, Param_Cell, Param_Cell, Param_Cell, Param_Cell); g_OnBossPreFlashlightDamageFwd = new GlobalForward("SF2_OnBossPreFlashlightDamage", ET_Hook, Param_Cell, Param_Cell); g_OnChaserBossGetSuspendActionFwd = new GlobalForward("SF2_OnChaserBossGetSuspendAction", ET_Hook, Param_Cell, Param_CellByRef); diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 4244a9e3..7e730cd7 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -1234,11 +1234,6 @@ bool NPCGetRainbowOutlineState(int npcIndex) return g_SlenderUseRainbowOutline[npcIndex]; } -float NPCGetRainbowOutlineCycleRate(int npcIndex) -{ - return g_SlenderRainbowCycleRate[npcIndex]; -} - void NPCSetAddSpeed(int npcIndex, float amount) { g_NpcAddSpeed[npcIndex] += amount; @@ -1740,7 +1735,6 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF g_SlenderOutlineColorB[npc.Index] = GetBossProfileOutlineColorB(profile); g_SlenderOutlineTransparency[npc.Index] = GetBossProfileOutlineTransparency(profile); g_SlenderUseRainbowOutline[npc.Index] = GetBossProfileRainbowOutlineState(profile); - g_SlenderRainbowCycleRate[npc.Index] = GetBossProfileRainbowCycleRate(profile); g_NpcHasProxyWeaponsEnabled[npc.Index] = GetBossProfileProxyWeapons(profile); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index 072bd92b..525b53a4 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -635,11 +635,13 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter public void ProcessRainbowOutline() { SF2NPC_BaseNPC controller = this.Controller; + SF2BossProfileData data; + data = controller.GetProfileData(); int glow = EntRefToEntIndex(g_NpcGlowEntity[controller.Index]); int color[4]; - color[0] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(controller.Index)) + controller.Index + 0) * 127.5 + 127.5); - color[1] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(controller.Index)) + controller.Index + 2) * 127.5 + 127.5); - color[2] = RoundToNearest(Cosine((GetGameTime() * NPCGetRainbowOutlineCycleRate(controller.Index)) + controller.Index + 4) * 127.5 + 127.5); + color[0] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 0) * 127.5 + 127.5); + color[1] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 2) * 127.5 + 127.5); + color[2] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 4) * 127.5 + 127.5); color[3] = 255; if (glow && glow != INVALID_ENT_REFERENCE) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp index 33f6a7a2..73ebdefa 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -8,12 +8,13 @@ #include "attacks/explosive_dance.sp" #include "attacks/custom.sp" #include "attacks/tongue.sp" +#include "attacks/forward_based.sp" static NextBotActionFactory g_Factory; methodmap SF2_ChaserAttackAction < NextBotAction { - public SF2_ChaserAttackAction(const char[] attackName, float endTime) + public SF2_ChaserAttackAction(const char[] attackName, int attackIndex, float endTime) { if (g_Factory == null) { @@ -27,7 +28,9 @@ methodmap SF2_ChaserAttackAction < NextBotAction g_Factory.SetEventCallback(EventResponderType_OnOtherKilled, OnOtherKilled); g_Factory.BeginDataMapDesc() .DefineIntField("m_OldState") + .DefineIntField("m_OldMovementType") .DefineStringField("m_AttackName") + .DefineIntField("m_AttackIndex") .DefineFloatField("m_EndTime") .DefineBoolField("m_DidEndAnimation") .DefineBoolField("m_PlayedBeginVoice") @@ -52,6 +55,7 @@ methodmap SF2_ChaserAttackAction < NextBotAction SF2_ChaserAttackAction_Laser.Initialize(); SF2_ChaserAttackAction_Custom.Initialize(); SF2_ChaserAttackAction_Tongue.Initialize(); + SF2_ChaserAttackAction_ForwardBased.Initialize(); } property int OldState @@ -67,6 +71,19 @@ methodmap SF2_ChaserAttackAction < NextBotAction } } + property SF2NPCMoveTypes OldMovementType + { + public get() + { + return this.GetData("m_OldMovementType"); + } + + public set(SF2NPCMoveTypes value) + { + this.SetData("m_OldMovementType", value); + } + } + public char[] GetAttackName() { char name[128]; @@ -79,6 +96,19 @@ methodmap SF2_ChaserAttackAction < NextBotAction this.SetDataString("m_AttackName", name); } + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + property float EndTime { public get() @@ -181,9 +211,11 @@ static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBo } action.OldState = actor.State; + action.OldMovementType = actor.MovementType; actor.State = STATE_ATTACK; actor.SetAttackName(action.GetAttackName()); + actor.AttackIndex = action.AttackIndex; actor.IsAttacking = true; actor.RemoveAllGestures(); @@ -375,7 +407,7 @@ static void OnEnd(SF2_ChaserAttackAction action, SF2_ChaserEntity actor) actor.IsAttacking = false; actor.IsAttemptingToMove = false; actor.CancelAttack = false; - actor.MovementType = SF2NPCMoveType_Run; + actor.MovementType = action.OldMovementType; char value[PLATFORM_MAX_PATH]; value = action.GetLoopSound(); @@ -407,6 +439,7 @@ static void OnEnd(SF2_ChaserAttackAction action, SF2_ChaserEntity actor) actor.State = action.OldState; actor.SetAttackName(""); + actor.AttackIndex = -1; } static void OnOtherKilled(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseCombatCharacter victim, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damagetype) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/forward_based.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/forward_based.sp new file mode 100644 index 00000000..cc78288e --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/forward_based.sp @@ -0,0 +1,190 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserAttackAction_ForwardBased < NextBotAction +{ + public SF2_ChaserAttackAction_ForwardBased(int attackIndex, const char[] attackName, float damageDelay) + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_AttackForward"); + g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetEventCallback(EventResponderType_OnAnimationEvent, OnAnimationEvent); + g_Factory.BeginDataMapDesc() + .DefineIntField("m_AttackIndex") + .DefineStringField("m_AttackName") + .DefineFloatField("m_NextDamageTime") + .DefineIntField("m_RepeatIndex") + .EndDataMapDesc(); + } + SF2_ChaserAttackAction_ForwardBased action = view_as(g_Factory.Create()); + + action.NextDamageTime = damageDelay; + action.AttackIndex = attackIndex; + action.SetAttackName(attackName); + + return action; + } + + property int AttackIndex + { + public get() + { + return this.GetData("m_AttackIndex"); + } + + public set(int value) + { + this.SetData("m_AttackIndex", value); + } + } + + public static void Initialize() + { + g_OnChaserGetAttackActionPFwd.AddFunction(null, OnChaserGetAttackAction); + } + + public char[] GetAttackName() + { + char name[128]; + this.GetDataString("m_AttackName", name, sizeof(name)); + return name; + } + + public void SetAttackName(const char[] name) + { + this.SetDataString("m_AttackName", name); + } + + property float NextDamageTime + { + public get() + { + return this.GetDataFloat("m_NextDamageTime"); + } + + public set(float value) + { + this.SetDataFloat("m_NextDamageTime", value); + } + } + + property int RepeatIndex + { + public get() + { + return this.GetData("m_RepeatIndex"); + } + + public set(int value) + { + this.SetData("m_RepeatIndex", value); + } + } +} + +static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] attackName, NextBotAction &result) +{ + if (result != NULL_ACTION) + { + return Plugin_Continue; + } + + SF2ChaserBossProfileData data; + data = chaser.Controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attackName, attackData); + int difficulty = chaser.Controller.Difficulty; + + if (attackData.Type >= SF2BossAttackType_Melee && attackData.Type <= SF2BossAttackType_Tongue) + { + return Plugin_Continue; + } + + result = SF2_ChaserAttackAction_ForwardBased(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + return Plugin_Changed; +} + +static int Update(SF2_ChaserAttackAction_ForwardBased action, SF2_ChaserEntity actor, float interval) +{ + if (action.Parent == NULL_ACTION) + { + return action.Done("No longer forward attacking"); + } + + if (actor.CancelAttack) + { + return action.Done(); + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; + + float gameTime = GetGameTime(); + if (action.NextDamageTime >= 0.0 && gameTime > action.NextDamageTime && attackData.EventNumber == -1) + { + RegisterForward(action, actor); + + int repeatState = attackData.Repeat; + if (repeatState > 0) + { + switch (repeatState) + { + case 1: + { + action.NextDamageTime = gameTime + attackData.DamageDelay[difficulty]; + } + case 2: + { + if (action.RepeatIndex >= attackData.RepeatTimers.Length) + { + action.NextDamageTime = -1.0; + } + else + { + float next = attackData.RepeatTimers.Get(action.RepeatIndex); + action.NextDamageTime = next + gameTime; + action.RepeatIndex++; + } + } + } + } + else + { + action.NextDamageTime = -1.0; + } + } + return action.Continue(); +} + +static void RegisterForward(SF2_ChaserAttackAction_ForwardBased action, SF2_ChaserEntity actor) +{ + Call_StartForward(g_OnBossAttackedFwd); + Call_PushCell(actor.Controller.Index); + Call_PushCell(action.AttackIndex); + Call_Finish(); +} + +static void OnAnimationEvent(SF2_ChaserAttackAction_ForwardBased action, SF2_ChaserEntity actor, int event) +{ + if (event == 0) + { + return; + } + + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(action.GetAttackName(), attackData); + + if (event == attackData.EventNumber) + { + RegisterForward(action, actor); + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp index 948ce571..52ec04f5 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -321,6 +321,8 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity info.EmitSound(_, actor.index); } + actor.DoShockwave(action.GetAttackName()); + if (hit) { CBaseEntity phys = CBaseEntity(CreateEntityByName("env_physexplosion")); @@ -352,8 +354,6 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity } delete targets; - - actor.DoShockwave(action.GetAttackName()); } static void OnAnimationEvent(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, int event) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp index b567d89e..7adb0114 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -540,7 +540,7 @@ static void OnLeaveGround(SF2_ChaserMainAction action, SF2_ChaserEntity actor, C actor.IsJumping = false; } -static int OnCommandString(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, const char[] command) +static int OnCommandString(SF2_ChaserMainAction action, SF2_ChaserEntity actor, const char[] command) { if (StrContains(command, "debug attack ") == 0 && !actor.IsAttacking) { @@ -553,7 +553,7 @@ static int OnCommandString(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntit data = controller.GetProfileData(); SF2ChaserBossProfileAttackData attackData; data.GetAttack(attack, attackData); - return action.TrySuspendFor(SF2_ChaserAttackAction(attack, attackData.Duration[difficulty] + GetGameTime()), RESULT_IMPORTANT); + return action.TrySuspendFor(SF2_ChaserAttackAction(attack, attackData.Index, attackData.Duration[difficulty] + GetGameTime()), RESULT_IMPORTANT); } if (strcmp(command, "suspend for action") == 0) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 19731094..03c4701c 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -66,6 +66,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss .DefineBoolField("m_IsAttacking") .DefineBoolField("m_CancelAttack") .DefineStringField("m_AttackName") + .DefineIntField("m_AttackIndex") .DefineIntField("m_NextAttackTime") .DefineFloatField("m_AttackRunDuration") .DefineFloatField("m_AttackRunDelay") @@ -434,6 +435,19 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss this.SetPropString(Prop_Data, "m_AttackName", name); } + property int AttackIndex + { + public get() + { + return this.GetProp(Prop_Data, "m_AttackIndex"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_AttackIndex", value); + } + } + property StringMap NextAttackTime { public get() @@ -1428,7 +1442,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss attackName = attackData.Name; - return SF2_ChaserAttackAction(attackName, attackData.Duration[difficulty] + gameTime); + return SF2_ChaserAttackAction(attackName, attackData.Index, attackData.Duration[difficulty] + gameTime); } delete arrayAttacks; @@ -2939,6 +2953,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss CreateNative("SF2_ChaserBossEntity.GetDefaultPosture", Native_GetDefaultPosture); CreateNative("SF2_ChaserBossEntity.SetDefaultPosture", Native_SetDefaultPosture); CreateNative("SF2_ChaserBossEntity.GetAttackName", Native_GetAttackName); + CreateNative("SF2_ChaserBossEntity.GetAttackIndex", Native_GetAttackIndex); CreateNative("SF2_ChaserBossEntity.GetNextAttackTime", Native_GetNextAttackTime); CreateNative("SF2_ChaserBossEntity.SetNextAttackTime", Native_SetNextAttackTime); CreateNative("SF2_ChaserBossEntity.DropItem", Native_DropItem); @@ -3080,6 +3095,7 @@ static void OnDisconnected(SF2_BasePlayer client) static void OnCreate(SF2_ChaserEntity ent) { + ent.AttackIndex = -1; ent.Target = CBaseEntity(INVALID_ENT_REFERENCE); ent.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); ent.IsAllowedToDespawn = true; @@ -3131,12 +3147,15 @@ static Action Think(int entIndex) static void ThinkPost(int entIndex) { SF2_ChaserEntity chaser = SF2_ChaserEntity(entIndex); + SF2NPC_Chaser controller = chaser.Controller; + SF2BossProfileData data; + data = view_as(controller).GetProfileData(); ProcessSpeed(chaser); ProcessBody(chaser); - if (NPCGetCustomOutlinesState(chaser.Controller.Index) && NPCGetRainbowOutlineState(chaser.Controller.Index)) + if (data.CustomOutlines && data.RainbowOutline) { chaser.ProcessRainbowOutline(); } @@ -3582,32 +3601,16 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio bool isVisible, isTraceVisible; int traceHitEntity; - if (data.UnnerfedVisibility) - { - Handle trace = TR_TraceRayFilterEx(traceStartPos, - traceEndPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, - RayType_EndPoint, - TraceRayBossVisibility, - chaser.index); - isVisible = !TR_DidHit(trace); - traceHitEntity = TR_GetEntityIndex(trace); + TR_TraceHullFilter(traceStartPos, + traceEndPos, + traceMins, + traceMaxs, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, + TraceRayBossVisibility, + chaser.index); - delete trace; - } - else - { - TR_TraceHullFilter(traceStartPos, - traceEndPos, - traceMins, - traceMaxs, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, - TraceRayBossVisibility, - chaser.index); - - isVisible = !TR_DidHit(); - traceHitEntity = TR_GetEntityIndex(); - } + isVisible = !TR_DidHit(); + traceHitEntity = TR_GetEntityIndex(); if (!isVisible && traceHitEntity == client.index) { @@ -4764,6 +4767,19 @@ static any Native_GetAttackName(Handle plugin, int numParams) return result; } +static any Native_GetAttackIndex(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + return bossEntity.AttackIndex; +} + static any Native_GetNextAttackTime(Handle plugin, int numParams) { int entity = GetNativeCell(1); diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index 52076dab..8d28fd4f 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -561,6 +561,7 @@ static void TriggerKey(int caller) void NPCChaser_InitializeAPI() { + CreateNative("SF2_GetBossCurrentAttackIndex", Native_GetBossCurrentAttackIndex); CreateNative("SF2_GetChaserProfileFromBossIndex", Native_GetProfileData); CreateNative("SF2_GetChaserProfileFromName", Native_GetProfileDataEx); @@ -570,6 +571,23 @@ void NPCChaser_InitializeAPI() SF2_ChaserEntity.SetupAPI(); } +static any Native_GetBossCurrentAttackIndex(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + } + + return chaser.AttackIndex; +} + static any Native_GetProfileData(Handle plugin, int numParams) { SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index 3ead2c18..82deeb7e 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -17,8 +17,6 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe SF2ChaserBossProfileData profileData; profileData.Init(); - profileData.UnnerfedVisibility = kv.GetNum("old_boss_visibility", profileData.UnnerfedVisibility) != 0; - profileData.ClearLayersOnAnimUpdate = kv.GetNum("animation_clear_layers_on_update", profileData.ClearLayersOnAnimUpdate) != 0; GetProfileDifficultyFloatValues(kv, "walkspeed", profileData.WalkSpeed, profileData.WalkSpeed); @@ -605,12 +603,6 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas attackData.Type = kv.GetNum("type", attackData.Type); - if (attackData.Type == SF2BossAttackType_Invalid) - { - LogSF2Message("[SF2 PROFILES PARSER] Attack %s is not supposed to be an invalid attack!", attackName); - continue; - } - GetProfileDifficultyFloatValues(kv, "range", attackData.Range, attackData.Range); GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 2fef0ea2..7d8e5bfe 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -1982,12 +1982,6 @@ bool GetBossProfileRainbowOutlineState(const char[] profile) return g_CachedProfileData.RainbowOutline; } -float GetBossProfileRainbowCycleRate(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.RainbowOutlineCycle; -} - bool GetBossProfileProxyWeapons(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index d9c86b80..de5b6c48 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -41,6 +41,7 @@ void SetupSpecialRounds() g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerTeamPFwd.AddFunction(null, OnPlayerTeam); g_OnPlayerClassPFwd.AddFunction(null, OnPlayerClass); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); } static void OnGamemodeStart() @@ -249,6 +250,7 @@ bool IsBeatBoxBeating(int index = 0) static void OnRoundEnd() { + StopBeatBoxMusicForAll(); g_Started = false; SF_RemoveAllSpecialRound(); } @@ -265,8 +267,6 @@ static void OnPlayerSpawn(SF2_BasePlayer client) return; } - //StopBeatBoxMusic(client); - if (SF_SpecialRound(SPECIALROUND_SINGLEPLAYER)) { TF2_StripContrackerOnly(client.index); @@ -367,6 +367,14 @@ static void OnPlayerClass(SF2_BasePlayer client) } } +static void OnPlayerEscape(SF2_BasePlayer client) +{ + if (SF_SpecialRound(SPECIALROUND_BEATBOX)) + { + StopBeatBoxMusic(client); + } +} + static void ChangeThanatophobiaClass(SF2_BasePlayer client) { TFClassType class = client.Class; diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index a334d26a..6fb60810 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -1403,10 +1403,6 @@ void SF_RemoveSpecialRound(int specialRound) void SF_RemoveAllSpecialRound() { - if (SF_SpecialRound(SPECIALROUND_BEATBOX)) - { - StopBeatBoxMusicForAll(); - } for (int array = 0; array < SPECIALROUND_MAXROUNDS; array++) { g_ArraySpecialRoundType[array] = 0; From b0cc737df59aa241f5e7f6715ebd33dfc81f5f75 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Wed, 15 Nov 2023 11:39:32 -0700 Subject: [PATCH 16/86] Wait let me try that again --- .../scripting/include/sf2/profiles/profiles_chaser.inc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 0bbed505..fc3ffe81 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -2258,6 +2258,8 @@ enum struct SF2ChaserBossProfileData this.NormalSoundHook = false; this.OldAnimationAI = false; + this.ChaseOnLookData.Init(); + this.ClearLayersOnAnimUpdate = true; this.HealAnimationTimer = 0.0; From 8afa5bc31586a38d89fdb08effe5e09bbf3d01b0 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Tue, 21 Nov 2023 16:50:26 -0700 Subject: [PATCH 17/86] Alpha 2.1 --- .../include/sf2/profiles/profiles_chaser.inc | 45 +++++++++++++++- addons/sourcemod/scripting/sf2.sp | 2 - .../sourcemod/scripting/sf2/client/sprint.sp | 12 ++--- .../sf2/npc/entities/chaser/actions/attack.sp | 27 +++++++++- .../entities/chaser/actions/attacklayer.sp | 11 ++-- .../chaser/actions/attacks/laser_beam.sp | 12 +++-- .../entities/chaser/actions/attacks/melee.sp | 23 ++++---- .../sf2/npc/entities/chaser/actions/chase.sp | 6 +-- .../sf2/npc/entities/chaser/actions/death.sp | 52 +++++++++++++++++++ .../npc/entities/chaser/actions/mainlayer.sp | 2 +- .../sf2/npc/entities/chaser/entity.sp | 16 +++--- addons/sourcemod/scripting/sf2/profiles.sp | 28 ++++++++++ .../sf2/profiles/profile_chaser_precache.sp | 5 +- .../sourcemod/scripting/sf2/specialround.sp | 15 ++++-- 14 files changed, 207 insertions(+), 49 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index fc3ffe81..a7e0860d 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -654,6 +654,9 @@ enum struct SF2ChaserBossProfileAttackData bool IgnoreAlwaysLooking[Difficulty_Max]; + bool StartThroughWalls[Difficulty_Max]; + bool HitThroughWalls[Difficulty_Max]; + int WeaponInt; char WeaponString[PLATFORM_MAX_PATH]; @@ -689,10 +692,10 @@ enum struct SF2ChaserBossProfileAttackData float LaserNoise; float LaserOffset[3]; char LaserAttachmentName[PLATFORM_MAX_PATH]; + float LaserTicks[Difficulty_Max]; bool PullIn; - bool AttackWhileRunning[Difficulty_Max]; float RunSpeed[Difficulty_Max]; float RunDuration[Difficulty_Max]; float RunDelay[Difficulty_Max]; @@ -764,6 +767,9 @@ enum struct SF2ChaserBossProfileAttackData this.IgnoreAlwaysLooking[difficulty] = false; + this.StartThroughWalls[difficulty] = false; + this.HitThroughWalls[difficulty] = false; + this.LifeSteal[difficulty] = false; this.LifeStealDuration[difficulty] = 0.0; @@ -783,8 +789,8 @@ enum struct SF2ChaserBossProfileAttackData this.LaserDamage[difficulty] = 25.0; this.LaserDuration[difficulty] = this.Duration[difficulty]; + this.LaserTicks[difficulty] = 0.1; - this.AttackWhileRunning[difficulty] = false; this.RunDuration[difficulty] = 0.0; this.RunDelay[difficulty] = 0.0; this.RunSpeed[difficulty] = 0.0; @@ -1132,6 +1138,9 @@ enum struct SF2ChaserBossProfileDeathData float AddHealthPerSpy[Difficulty_Max]; float AddHealthPerDeath[Difficulty_Max]; + ArrayList Gibs; + int GibSkin; + void Init() { this.RemoveOnDeath = false; @@ -1162,6 +1171,9 @@ enum struct SF2ChaserBossProfileDeathData this.AddHealthPerSpy[difficulty] = 0.0; this.AddHealthPerDeath[difficulty] = 0.0; } + + this.Gibs = null; + this.GibSkin = 0; } void Load(KeyValues kv, bool checkFile) @@ -1195,6 +1207,33 @@ enum struct SF2ChaserBossProfileDeathData GetProfileDifficultyFloatValues(kv, "on_death", this.AddHealthPerDeath, this.AddHealthPerDeath); kv.GoBack(); } + + if (kv.JumpToKey("gibs")) + { + this.GibSkin = kv.GetNum("skin", this.GibSkin); + this.Gibs = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char value[PLATFORM_MAX_PATH], key[64]; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + kv.GetString(key, value, sizeof(value)); + if (value[0] == '\0') + { + break; + } + PrecacheModel2(value, _, _, checkFile); + this.Gibs.PushString(value); + } + kv.GoBack(); + } + } + + void Destroy() + { + if (this.Gibs != null) + { + delete this.Gibs; + } } } @@ -2574,6 +2613,8 @@ enum struct SF2ChaserBossProfileData this.CloakData.Destroy(); + this.DeathData.Destroy(); + if (this.Attacks != null) { SF2ChaserBossProfileAttackData attackData; diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 7a64969c..b717649d 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -6616,7 +6616,6 @@ static Action Timer_RoundTime(Handle timer) float buffer[3]; GetClientAbsOrigin(i, buffer); - ClientStartDeathCam(i, 0, buffer, true); if (SF_SpecialRound(SPECIALROUND_1UP)) { g_PlayerDied1Up[i] = false; @@ -6697,7 +6696,6 @@ static Action Timer_RoundTimeEscape(Handle timer) float buffer[3]; GetClientAbsOrigin(i, buffer); - ClientStartDeathCam(i, 0, buffer, true); if (SF_SpecialRound(SPECIALROUND_1UP)) { g_PlayerDied1Up[i] = false; diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp index 79766a73..1a9526b8 100644 --- a/addons/sourcemod/scripting/sf2/client/sprint.sp +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -848,19 +848,19 @@ static void Hook_SprintThink(int client) { if (SF_IsBoxingMap() || SF_IsRaidMap()) { - player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed*2.5); + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed * 2.5); } else { - player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed/2.5); + player.SetPropFloat(Prop_Send, "m_flMaxspeed", sprintSpeed / 2.5); } } - player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed * 0.5); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 190.0); } else { player.SetPropFloat(Prop_Send, "m_flMaxspeed", 520.0); - player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", sprintSpeed * 0.5); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 190.0); } } else @@ -877,9 +877,9 @@ static void Hook_SprintThink(int client) } else { - player.SetPropFloat(Prop_Send, "m_flMaxspeed", walkSpeed / 2.5); + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 190.0); } } - player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", walkSpeed * 0.5); + player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 190.0); } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp index 73ebdefa..109e3e84 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -241,7 +241,6 @@ static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBo if (attackData.RunSpeed[difficulty] > 0.0) { - actor.IsAttemptingToMove = true; actor.MovementType = SF2NPCMoveType_Attack; } actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); @@ -289,9 +288,13 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i SF2ChaserBossProfileData data; SF2ChaserBossProfileAttackData attackData; + int interrputConditions = actor.InterruptConditions; data = controller.GetProfileData(); data.GetAttack(actor.GetAttackName(), attackData); bool end = false; + INextBot bot = actor.MyNextBotPointer(); + PathFollower path = controller.Path; + ILocomotion loco = bot.GetLocomotionInterface(); int difficulty = controller.Difficulty; @@ -316,6 +319,17 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i { end = true; } + + if (actor.MovementType == SF2NPCMoveType_Attack && attackData.Type != SF2BossAttackType_Custom) + { + if (!bot.IsRangeLessThanEx(targetPos, 8.0)) + { + if ((interrputConditions & COND_NEWENEMY) != 0 || path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) + { + path.ComputeToPos(bot, targetPos); + } + } + } } if (attackData.Type == SF2BossAttackType_Custom) @@ -359,6 +373,17 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i return action.Done("Attack finished"); } + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + return action.Continue(); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp index 8303a63e..d038b3d6 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacklayer.sp @@ -29,14 +29,11 @@ static int Update(SF2_ChaserAttackLayerAction action, SF2_ChaserEntity actor) CBaseEntity target = actor.Target; if (!actor.IsAttacking && target.IsValid()) { - if (actor.IsLOSClearFromTarget(target)) + NextBotAction attackAction = actor.GetAttackAction(target); + if (attackAction != NULL_ACTION) { - NextBotAction attackAction = actor.GetAttackAction(target); - if (attackAction != NULL_ACTION) - { - actor.EndCloak(); - return action.SuspendFor(attackAction, "Time to die!"); - } + actor.EndCloak(); + return action.SuspendFor(attackAction, "Time to die!"); } } return action.Continue(); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp index 2377b7a2..ea4fae7c 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp @@ -4,7 +4,7 @@ static NextBotActionFactory g_Factory; methodmap SF2_ChaserAttackAction_Laser < NextBotAction { - public SF2_ChaserAttackAction_Laser(int attackIndex, const char[] attackName, float fireDelay) + public SF2_ChaserAttackAction_Laser(int attackIndex, const char[] attackName, float fireDelay, float duration) { if (g_Factory == null) { @@ -24,6 +24,7 @@ methodmap SF2_ChaserAttackAction_Laser < NextBotAction action.NextFireTime = fireDelay; action.AttackIndex = attackIndex; + action.LaserDuration = duration; action.SetAttackName(attackName); return action; @@ -118,7 +119,7 @@ static Action OnChaserGetAttackAction(SF2_ChaserEntity chaser, const char[] atta return Plugin_Continue; } - result = SF2_ChaserAttackAction_Laser(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime()); + result = SF2_ChaserAttackAction_Laser(attackData.Index, attackData.Name, attackData.DamageDelay[difficulty] + GetGameTime(), attackData.LaserDuration[difficulty] + GetGameTime() + attackData.DamageDelay[difficulty]); return Plugin_Changed; } @@ -144,6 +145,7 @@ static int Update(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, f data = controller.GetProfileData(); SF2ChaserBossProfileAttackData attackData; data.GetAttack(action.GetAttackName(), attackData); + int difficulty = controller.Difficulty; float gameTime = GetGameTime(); @@ -151,8 +153,8 @@ static int Update(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, f { if (action.LaserCooldown < gameTime) { - action.LaserCooldown = gameTime + 0.1; - FireLaser(action, actor, 0.1); + action.LaserCooldown = gameTime + attackData.LaserTicks[difficulty]; + FireLaser(action, actor, attackData.LaserTicks[difficulty]); } if (action.LaserDuration < gameTime) @@ -243,7 +245,7 @@ static void FireLaser(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity acto TE_SendToAll(); } - SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.LaserDamage[difficulty], DMG_SHOCK|DMG_ALWAYSGIB, _, _, endPos); + SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.LaserDamage[difficulty], DMG_SHOCK | DMG_ALWAYSGIB, _, _, endPos); attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp index 52ec04f5..25d59147 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -218,7 +218,7 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity { CBaseEntity prop = CBaseEntity(targets.Get(i)); - if (!IsTargetInMeleeChecks(actor, prop, range, spread)) + if (!IsTargetInMeleeChecks(actor, attackData, prop, range, spread)) { continue; } @@ -239,7 +239,7 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity continue; } - if (!IsTargetInMeleeChecks(actor, player, range, spread)) + if (!IsTargetInMeleeChecks(actor, attackData, player, range, spread)) { continue; } @@ -254,7 +254,7 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity NormalizeVector(direction, direction); ScaleVector(direction, force); - if (controller.HasAttribute(SF2Attribute_DeathCamOnLowHealth)) + if (controller.HasAttribute(SF2Attribute_DeathCamOnLowHealth) || attackData.DeathCamLowHealth) { float checkDamage = damage; @@ -375,7 +375,7 @@ static void OnAnimationEvent(SF2_ChaserAttackAction_Melee action, SF2_ChaserEnti } } -static bool IsTargetInMeleeChecks(SF2_ChaserEntity actor, CBaseEntity target, float range, float fov) +static bool IsTargetInMeleeChecks(SF2_ChaserEntity actor, SF2ChaserBossProfileAttackData data, CBaseEntity target, float range, float fov) { if (!target.IsValid()) { @@ -407,13 +407,16 @@ static bool IsTargetInMeleeChecks(SF2_ChaserEntity actor, CBaseEntity target, fl return false; } - TR_TraceRayFilter(myEyePos, targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, - RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); - - if (TR_DidHit() && TR_GetEntityIndex() != target.index) + if (!data.HitThroughWalls[actor.Controller.Difficulty]) { - return false; + TR_TraceRayFilter(myEyePos, targetPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP | CONTENTS_GRATE | CONTENTS_WINDOW, + RayType_EndPoint, TraceRayDontHitAnyEntity, actor.index); + + if (TR_DidHit() && TR_GetEntityIndex() != target.index) + { + return false; + } } return true; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp index 508154ff..20264ba5 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -109,11 +109,11 @@ static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) return action.ChangeTo(SF2_ChaserIdleAction(), "Our target escaped, that is no good!"); } - float maxRange = data.ChaseDurationAddMaxRange[difficulty]; - if (maxRange > 0.0 && player.IsValid && player.CanSeeSlender(controller.Index, false, _, !attackEliminated)) + if (player.IsValid && player.CanSeeSlender(controller.Index, false, _, !attackEliminated)) { + float maxRange = data.ChaseDurationAddMaxRange[difficulty]; float distanceRatio = bot.GetRangeTo(player.index) / maxRange; - if (distanceRatio < 1.0) + if (maxRange > 0.0 && distanceRatio < 1.0) { float durationTimeAddMin = data.ChaseDurationAddVisibleMin[difficulty]; float durationTimeAddMax = data.ChaseDurationAddVisibleMax[difficulty]; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp index 18a901a3..89d7782b 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp @@ -136,10 +136,12 @@ static void OnEnd(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) if (data.DeathData.RemoveOnDeath) { + SpawnGibs(actor); controller.Remove(); } else if (data.DeathData.DisappearOnDeath) { + SpawnGibs(actor); controller.UnSpawn(); } else if (data.DeathData.RagdollOnDeath) @@ -158,3 +160,53 @@ static void OnAnimationEvent(SF2_ChaserDeathAction action, SF2_ChaserEntity acto actor.CastAnimEvent(event); actor.CastAnimEvent(event, true); } + +static void SpawnGibs(SF2_ChaserEntity actor) +{ + SF2NPC_Chaser controller = actor.Controller; + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.DeathData.Gibs == null) + { + return; + } + float pos[3], ang[3], vel[3]; + char model[PLATFORM_MAX_PATH]; + actor.WorldSpaceCenter(pos); + actor.GetAbsAngles(ang); + for (int i = 0; i < data.DeathData.Gibs.Length; i++) + { + ang[1] = GetRandomFloat(-180.0, 180.0); + + for(int i2 = 0; i2 < 2; i2++) + { + vel[i2] += GetRandomFloat(-300.0, 300.0); + } + vel[2] = GetRandomFloat(-300.0, 300.0); + + data.DeathData.Gibs.GetString(i, model, sizeof(model)); + + if (strlen(model) > 0) + { + CBaseEntity gib = CBaseEntity(CreateEntityByName("prop_physics_multiplayer")); + gib.KeyValue("model", model); + gib.KeyValue("physicsmode", "2"); + + gib.Teleport(pos, ang, vel); + gib.Spawn(); + gib.Teleport(NULL_VECTOR, NULL_VECTOR, vel); + + gib.SetProp(Prop_Send, "m_CollisionGroup", 1); + gib.SetProp(Prop_Send, "m_usSolidFlags", 0); + gib.SetProp(Prop_Send, "m_nSolidType", 2); + gib.SetProp(Prop_Send, "m_nSkin", data.DeathData.GibSkin); + + int effects = 16 | 64; + gib.SetProp(Prop_Send, "m_fEffects", effects); + + SetVariantString("OnUser1 !self:Kill::10.0:1"); + gib.AcceptInput("AddOutput"); + gib.AcceptInput("FireUser1"); + } + } +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp index 7adb0114..fd65a9fd 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -374,7 +374,7 @@ static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); float gameTime = GetGameTime(); - if (!path.IsValid() || !actor.IsAttemptingToMove || loco.GetDesiredSpeed() <= 0.0) + if (!path.IsValid() || !actor.IsAttemptingToMove || loco.GetDesiredSpeed() <= 0.0 || actor.MovementType == SF2NPCMoveType_Attack) { action.LastStuckTime = gameTime; return; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 03c4701c..91021816 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -1356,6 +1356,11 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss continue; } + if (!attackData.StartThroughWalls[difficulty] && !this.IsLOSClearFromTarget(target)) + { + continue; + } + if (attackData.Type == SF2BossAttackType_Custom) { Action result = Plugin_Continue; @@ -3034,16 +3039,13 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo info.EmitSound(true, client.index); info = data.AttackKilledAllSounds; - if (info.Paths != null && info.Paths.Length > 0) + for (int i = 1; i <= MaxClients; i++) { - for (int i = 1; i <= MaxClients; i++) + if (!IsValidClient(i)) { - if (!IsValidClient(i)) - { - continue; - } - info.EmitSound(true, i); + continue; } + info.EmitSound(true, i); } } diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 2adae88c..01813a97 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -109,6 +109,18 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_CROUCHIDLE_AIM_STIMULATED", ACT_CROUCHIDLE_AIM_STIMULATED); g_Activities.SetValue("ACT_CROUCHIDLE_AGITATED", ACT_CROUCHIDLE_AGITATED); g_Activities.SetValue("ACT_STAND", ACT_STAND); // standing up from crouch + g_Activities.SetValue("ACT_MP_STAND_PRIMARY", ACT_MP_STAND_PRIMARY); + g_Activities.SetValue("ACT_MP_STAND_SECONDARY", ACT_MP_STAND_SECONDARY); + g_Activities.SetValue("ACT_MP_STAND_MELEE", ACT_MP_STAND_MELEE); + g_Activities.SetValue("ACT_MP_STAND_SECONDARY2", ACT_MP_STAND_SECONDARY2); + g_Activities.SetValue("ACT_MP_STAND_ITEM1", ACT_MP_STAND_ITEM1); + g_Activities.SetValue("ACT_MP_STAND_ITEM2", ACT_MP_STAND_ITEM2); + g_Activities.SetValue("ACT_MP_CROUCH_PRIMARY", ACT_MP_CROUCH_PRIMARY); + g_Activities.SetValue("ACT_MP_CROUCH_SECONDARY", ACT_MP_CROUCH_SECONDARY); + g_Activities.SetValue("ACT_MP_CROUCH_MELEE", ACT_MP_CROUCH_MELEE); + g_Activities.SetValue("ACT_MP_CROUCH_SECONDARY2", ACT_MP_CROUCH_SECONDARY2); + g_Activities.SetValue("ACT_MP_CROUCH_ITEM1", ACT_MP_CROUCH_ITEM1); + g_Activities.SetValue("ACT_MP_CROUCH_ITEM2", ACT_MP_CROUCH_ITEM2); g_Activities.SetValue("ACT_WALK", ACT_WALK); g_Activities.SetValue("ACT_WALK_STIMULATED", ACT_WALK_STIMULATED); g_Activities.SetValue("ACT_WALK_AGITATED", ACT_WALK_AGITATED); @@ -118,6 +130,12 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_WALK_AIM_AGITATED", ACT_WALK_AIM_AGITATED); g_Activities.SetValue("ACT_WALK_CROUCH", ACT_WALK_CROUCH); g_Activities.SetValue("ACT_WALK_CROUCH_AIM", ACT_WALK_CROUCH_AIM); + g_Activities.SetValue("ACT_MP_WALK_PRIMARY", ACT_MP_WALK_PRIMARY); + g_Activities.SetValue("ACT_MP_WALK_SECONDARY", ACT_MP_WALK_SECONDARY); + g_Activities.SetValue("ACT_MP_WALK_MELEE", ACT_MP_WALK_MELEE); + g_Activities.SetValue("ACT_MP_WALK_SECONDARY2", ACT_MP_WALK_SECONDARY2); + g_Activities.SetValue("ACT_MP_WALK_ITEM1", ACT_MP_WALK_ITEM1); + g_Activities.SetValue("ACT_MP_WALK_ITEM2", ACT_MP_WALK_ITEM2); g_Activities.SetValue("ACT_RUN", ACT_RUN); g_Activities.SetValue("ACT_RUN_STIMULATED", ACT_RUN_STIMULATED); g_Activities.SetValue("ACT_RUN_AGITATED", ACT_RUN_AGITATED); @@ -127,6 +145,12 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_RUN_AIM_RELAXED", ACT_RUN_AIM_RELAXED); g_Activities.SetValue("ACT_RUN_CROUCH", ACT_RUN_CROUCH); g_Activities.SetValue("ACT_RUN_CROUCH_AIM", ACT_RUN_CROUCH_AIM); + g_Activities.SetValue("ACT_MP_RUN_PRIMARY", ACT_MP_RUN_PRIMARY); + g_Activities.SetValue("ACT_MP_RUN_SECONDARY", ACT_MP_RUN_SECONDARY); + g_Activities.SetValue("ACT_MP_RUN_MELEE", ACT_MP_RUN_MELEE); + g_Activities.SetValue("ACT_MP_RUN_SECONDARY2", ACT_MP_RUN_SECONDARY2); + g_Activities.SetValue("ACT_MP_RUN_ITEM1", ACT_MP_RUN_ITEM1); + g_Activities.SetValue("ACT_MP_RUN_ITEM2", ACT_MP_RUN_ITEM2); g_Activities.SetValue("ACT_JUMP", ACT_JUMP); g_Activities.SetValue("ACT_FLY", ACT_FLY); g_Activities.SetValue("ACT_LAND", ACT_LAND); @@ -158,6 +182,10 @@ static void AddProfileActivities() g_Activities.SetValue("ACT_RELOAD_START", ACT_RELOAD_START); g_Activities.SetValue("ACT_RELOAD_FINISH", ACT_RELOAD_FINISH); g_Activities.SetValue("ACT_GESTURE_RELOAD", ACT_GESTURE_RELOAD); + g_Activities.SetValue("ACT_MP_ATTACK_STAND_MELEE", ACT_MP_ATTACK_STAND_MELEE); + g_Activities.SetValue("ACT_MP_ATTACK_CROUCH_MELEE", ACT_MP_ATTACK_CROUCH_MELEE); + g_Activities.SetValue("ACT_MP_ATTACK_SWIM_MELEE", ACT_MP_ATTACK_SWIM_MELEE); + g_Activities.SetValue("ACT_MP_ATTACK_AIRWALK_MELEE", ACT_MP_ATTACK_AIRWALK_MELEE); // Flinch activities g_Activities.SetValue("ACT_SMALL_FLINCH", ACT_SMALL_FLINCH); diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index 82deeb7e..79d0cb1d 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -677,6 +677,7 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas } GetProfileDifficultyFloatValues(kv, "laser_duration", attackData.LaserDuration, attackData.LaserDuration); attackData.LaserNoise = kv.GetFloat("laser_noise", attackData.LaserNoise); + GetProfileDifficultyFloatValues(kv, "laser_tick_delay", attackData.LaserTicks, attackData.LaserTicks); GetProfileDifficultyBoolValues(kv, "dont_interrupt_chaseinitial", attackData.DontInterruptChaseInitial, attackData.DontInterruptChaseInitial); @@ -740,6 +741,9 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas GetProfileDifficultyBoolValues(kv, "ignore_always_looking", attackData.IgnoreAlwaysLooking, attackData.IgnoreAlwaysLooking); GetProfileDifficultyBoolValues(kv, "disappear_upon_damaging", attackData.Disappear, attackData.Disappear); + GetProfileDifficultyBoolValues(kv, "start_through_walls", attackData.StartThroughWalls, attackData.StartThroughWalls); + GetProfileDifficultyBoolValues(kv, "hit_through_walls", attackData.HitThroughWalls, attackData.HitThroughWalls); + attackData.WeaponInt = kv.GetNum("weapontypeint", attackData.WeaponInt); if (attackData.WeaponInt < 0) { @@ -747,7 +751,6 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas } kv.GetString("weapontype", attackData.WeaponString, sizeof(attackData.WeaponString), attackData.WeaponString); - GetProfileDifficultyBoolValues(kv, "run_enabled", attackData.AttackWhileRunning, attackData.AttackWhileRunning); GetProfileDifficultyFloatValues(kv, "run_speed", attackData.RunSpeed, attackData.RunSpeed); GetProfileDifficultyFloatValues(kv, "run_duration", attackData.RunDuration, attackData.RunDuration); GetProfileDifficultyFloatValues(kv, "run_delay", attackData.RunDelay, attackData.RunDelay); diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index de5b6c48..b0738348 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -1981,7 +1981,6 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Triple Bosses"); } - case SPECIALROUND_BOSSROULETTE: { FormatEx(item, sizeof(item), "Boss Roulette"); @@ -1990,14 +1989,22 @@ static void SpecialCreateVote() { FormatEx(item, sizeof(item), "Wall Hax"); } + case SPECIALROUND_SINGLEPLAYER: + { + FormatEx(item, sizeof(item), "Single Player"); + } + case SPECIALROUND_BEATBOX: + { + FormatEx(item, sizeof(item), "Beat Box"); + } } - for (int iBit = 0; iBit < 30; iBit++) + for (int bit = 0; bit < 30; bit++) { - if (strcmp(item[iBit],"-") == 0 ||strcmp(item[iBit],":") == 0) + if (strcmp(item[bit],"-") == 0 ||strcmp(item[bit],":") == 0) { break; } - itemOutPut[iBit] = item[iBit]; + itemOutPut[bit] = item[bit]; } FormatEx(item, sizeof(item), "%d", round); NativeVotes_AddItem(voteMenu, item, itemOutPut); From 3a9daa3a96db0fab4d975284eec27d0dbcba585b Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Wed, 22 Nov 2023 19:25:03 -0700 Subject: [PATCH 18/86] Alpha 2.2 --- .../include/sf2/profiles/profiles.inc | 2 + addons/sourcemod/scripting/sf2.sp | 33 +- .../sourcemod/scripting/sf2/client/think.sp | 49 ++ .../sf2/entities/sf2_base_projectile.sp | 29 ++ .../scripting/sf2/extras/commands.sp | 2 + addons/sourcemod/scripting/sf2/menus.sp | 6 + addons/sourcemod/scripting/sf2/npc.sp | 16 +- .../scripting/sf2/npc/entities/base/entity.sp | 75 ++- .../entities/chaser/actions/flee_to_heal.sp | 28 ++ .../npc/entities/chaser/actions/mainlayer.sp | 25 +- .../sf2/npc/entities/chaser/actions/rage.sp | 30 ++ .../sf2/npc/entities/chaser/actions/smell.sp | 2 +- .../sf2/npc/entities/chaser/entity.sp | 438 +++++++++++++----- .../sf2/npc/entities/statue/entity.sp | 2 + .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 31 +- .../sf2/profiles/profiles_boss_functions.sp | 1 + addons/sourcemod/scripting/sf2/pve.sp | 13 +- addons/sourcemod/scripting/sf2/stocks.sp | 3 +- 18 files changed, 602 insertions(+), 183 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index b7bf6d76..a5cb499c 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -2168,6 +2168,7 @@ enum struct SF2BossProfileData bool IsPvEBoss; ArrayList PvESpawnMessagesArray; char PvESpawnMessagePrefix[PLATFORM_MAX_PATH]; + bool DisplayPvEHealth; // Sounds SF2BossProfileSoundInfo LocalDeathCamSounds; @@ -2379,6 +2380,7 @@ enum struct SF2BossProfileData this.IsPvEBoss = false; this.PvESpawnMessagesArray = null; this.PvESpawnMessagePrefix[0] = '\0'; + this.DisplayPvEHealth = true; this.EngineSound[0] = '\0'; this.EngineSoundLevel = 83; diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index b717649d..02b40313 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -603,6 +603,8 @@ int g_ShockwaveBeam; int g_ShockwaveHalo; char g_DebugBeamSound[PLATFORM_MAX_PATH]; +ArrayList g_Buildings; + // Global forwards. GlobalForward g_OnBossAddedFwd; GlobalForward g_OnBossSpawnFwd; @@ -1000,6 +1002,24 @@ static void StartPlugin() OnClientPutInServer(i); } + int ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_sentrygun")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_teleporter")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_dispenser")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + Call_StartForward(g_OnGamemodeStartPFwd); Call_Finish(); } @@ -1606,7 +1626,7 @@ Action Hook_CommandBuild(int client, const char[] command, int argc) { return Plugin_Continue; } - if (!IsClientInPvP(client)) + if (!IsClientInPvP(client) && !IsClientInPvE(client)) { return Plugin_Handled; } @@ -2387,6 +2407,11 @@ public void OnEntityCreated(int ent, const char[] classname) { SetEntProp(ent, Prop_Send, "m_bIceRagdoll", true); } + else if (strcmp(classname, "obj_sentrygun", false) == 0 || strcmp(classname, "obj_dispenser", false) == 0 || + strcmp(classname, "obj_teleporter", false) == 0) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } Call_StartForward(g_OnEntityCreatedPFwd); Call_PushCell(CBaseEntity(ent)); @@ -2468,6 +2493,12 @@ public void OnEntityDestroyed(int ent) } } + int index = g_Buildings.FindValue(EntIndexToEntRef(ent)); + if (index != -1) + { + g_Buildings.Erase(index); + } + Call_StartForward(g_OnEntityDestroyedPFwd); Call_PushCell(CBaseEntity(ent)); Call_PushString(classname); diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index 8486d3f1..7218948a 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -823,6 +823,7 @@ Action Timer_ClientAverageUpdate(Handle timer) static int hudColorHealthy[3]; static int hudColorCritical[3] = { 255, 10, 10 }; + static int hudColorBossBar[3] = { 43, 103, 255 }; for (int i = 1; i <= MaxClients; i++) { @@ -1057,6 +1058,54 @@ Action Timer_ClientAverageUpdate(Handle timer) 0.5); ShowSyncHudText(player.index, g_HudSync2, buffer); } + + if (player.IsInPvE) + { + ArrayList bosses = GetActivePvEBosses(); + buffer[0] = '\0'; + char buffer2[64]; + char formatter[128], name[SF2_MAX_NAME_LENGTH]; + for (int i2 = 0; i2 < bosses.Length; i2++) + { + if (i2 >= 4) + { + continue; + } + name[0] = '\0'; + SF2_ChaserEntity chaser = SF2_ChaserEntity(EntRefToEntIndex(bosses.Get(i2))); + if (chaser.IsValid() && chaser.Controller.IsValid()) + { + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); + data.Names.GetString(1, name, sizeof(name)); + if (!data.DisplayPvEHealth) + { + continue; + } + } + + if (bosses.Length > 1 && i2 > 0) + { + FormatEx(buffer2, sizeof(buffer2), "\n"); + } + + CBaseCombatCharacter boss = CBaseCombatCharacter(EntRefToEntIndex(bosses.Get(i2))); + int health = boss.GetProp(Prop_Data, "m_iHealth"); + if (name[0] != '\0') + { + FormatEx(formatter, sizeof(formatter), "%s: %d", name, health); + } + else + { + FormatEx(formatter, sizeof(formatter), "%d", health); + } + + StrCat(buffer2, sizeof(buffer2), formatter); + StrCat(buffer, sizeof(buffer), buffer2); + SetHudTextParams(-1.0, 0.15, 0.25, hudColorBossBar[0], hudColorBossBar[1], hudColorBossBar[2], 225, 0, 1.0, 0.07, 0.3); + ShowSyncHudText(i, g_HudSync2, buffer); + } + } } } player.UpdateListeningFlags(); diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp index a59c3778..fbca82d6 100644 --- a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -343,6 +343,8 @@ methodmap SF2_ProjectileBase < CBaseAnimating int owner = this.GetPropEnt(Prop_Send, "m_hOwnerEntity"); float pos[3]; this.GetAbsOrigin(pos); + + // Go for players ArrayList hitList = new ArrayList(); TR_EnumerateEntitiesSphere(pos, this.BlastRadius, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, hitList); @@ -377,6 +379,33 @@ methodmap SF2_ProjectileBase < CBaseAnimating Call_Finish(); } } + hitList.Clear(); + + // Then do props + TR_EnumerateEntitiesSphere(pos, this.BlastRadius, PARTITION_SOLID_EDICTS, EnumerateBreakableEntities, hitList); + for (int i = 0; i < hitList.Length; i++) + { + CBaseEntity prop = CBaseEntity(hitList.Get(i)); + + float targetPos[3]; + prop.WorldSpaceCenter(targetPos); + TR_TraceRayFilter(pos, targetPos, + CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_GRATE, + RayType_EndPoint, TraceRayDontHitAnyEntity, this.index); + + if (!TR_DidHit() || TR_GetEntityIndex() == prop.index) + { + float damage = this.CalculateFallOff(prop); + int flags = DMG_BLAST; + if (this.IsCrits) + { + flags |= DMG_ACID; + } + float force[3]; + this.GetDamageForce(prop, force); + SDKHooks_TakeDamage(prop.index, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, damage, flags, _, force, pos, false); + } + } delete hitList; if (this.Type != SF2BossProjectileType_Mangler) diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index a03c927a..9ce7ba2f 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -235,6 +235,8 @@ public void OnPluginStart() g_RoundTimerSync = CreateHudSynchronizer(); g_Cookie = RegClientCookie("sf2_newcookies", "", CookieAccess_Private); + g_Buildings = new ArrayList(); + switch (g_DifficultyConVar.IntValue) { case Difficulty_Hard: diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 6b35906d..0cb4daff 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -1966,6 +1966,12 @@ void DisplayBossList(int client) { strcopy(displayName, sizeof(displayName), profile); } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + StrCat(displayName, sizeof(displayName), " (PvE Boss)"); + } menu.AddItem(profile, displayName); } } diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 7e730cd7..f1247559 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -2823,7 +2823,17 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) } } - entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + SF2BossProfileData data; + data = npc.GetProfileData(); + + if (!data.IsPvEBoss) + { + entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + } + else + { + entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Spectator); + } int difficulty = GetLocalGlobalDifficulty(bossIndex); @@ -2863,9 +2873,7 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) } entity.AddFlag(FL_NPC); - SF2BossProfileData data; - data = npc.GetProfileData(); - if (data.IsPvEBoss) + if (!data.IsPvEBoss) { entity.AddFlag(FL_NOTARGET); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index 525b53a4..881fb433 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -41,9 +41,9 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter .DefineEntityField("m_Target") .DefineEntityField("m_OldTarget") .DefineBoolField("m_IsJumping") - .DefineBoolField("m_IsPlayerVisible", MAXTF2PLAYERS) - .DefineBoolField("m_IsPlayerInFOV", MAXTF2PLAYERS) - .DefineBoolField("m_IsPlayerNear", MAXTF2PLAYERS) + .DefineBoolField("m_IsEntityVisible", 2049) + .DefineBoolField("m_IsEntityInFOV", 2049) + .DefineBoolField("m_IsEntityNear", 2049) .DefineFloatField("m_CurrentChaseDuration") .DefineFloatField("m_LegacyFootstepInterval") .DefineFloatField("m_LegacyFootstepTime") @@ -62,6 +62,7 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter .DefineEntityField("m_KillTarget") .DefineBoolField("m_IsAttemptingToMove") .DefineIntField("m_EyeBoneIndex") + .DefineBoolField("m_VelocityCancel") .EndDataMapDesc(); g_Factory.Install(); @@ -165,34 +166,34 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter } } - public bool GetIsVisible(SF2_BasePlayer player) + public bool GetIsVisible(CBaseEntity entity) { - return this.GetProp(Prop_Data, "m_IsPlayerVisible", player.index) != 0; + return this.GetProp(Prop_Data, "m_IsEntityVisible", entity.index) != 0; } - public void SetIsVisible(SF2_BasePlayer player, bool value) + public void SetIsVisible(CBaseEntity entity, bool value) { - this.SetProp(Prop_Data, "m_IsPlayerVisible", value, player.index); + this.SetProp(Prop_Data, "m_IsEntityVisible", value, entity.index); } - public bool GetInFOV(SF2_BasePlayer player) + public bool GetInFOV(CBaseEntity entity) { - return this.GetProp(Prop_Data, "m_IsPlayerInFOV", player.index) != 0; + return this.GetProp(Prop_Data, "m_IsEntityInFOV", entity.index) != 0; } - public void SetInFOV(SF2_BasePlayer player, bool value) + public void SetInFOV(CBaseEntity entity, bool value) { - this.SetProp(Prop_Data, "m_IsPlayerInFOV", value, player.index); + this.SetProp(Prop_Data, "m_IsEntityInFOV", value, entity.index); } - public bool GetIsNear(SF2_BasePlayer player) + public bool GetIsNear(CBaseEntity entity) { - return this.GetProp(Prop_Data, "m_IsPlayerNear", player.index) != 0; + return this.GetProp(Prop_Data, "m_IsEntityNear", entity.index) != 0; } - public void SetIsNear(SF2_BasePlayer player, bool value) + public void SetIsNear(CBaseEntity entity, bool value) { - this.SetProp(Prop_Data, "m_IsPlayerNear", value, player.index); + this.SetProp(Prop_Data, "m_IsEntityNear", value, entity.index); } property float CurrentChaseDuration @@ -426,6 +427,19 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter } } + property bool VelocityCancel + { + public get() + { + return this.GetProp(Prop_Data, "m_VelocityCancel") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_VelocityCancel", value); + } + } + public void EyePosition(float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) { this.Controller.GetEyePosition(buffer, defaultValue); @@ -649,6 +663,37 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter AcceptEntityInput(glow, "SetGlowColor"); } } + + public void CheckVelocityCancel() + { + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(this.index); + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + if (loco.IsClimbingOrJumping()) + { + this.VelocityCancel = false; + return; + } + float mins[3], maxs[3]; + npc.GetBodyMins(mins); + npc.GetBodyMaxs(maxs); + mins[0] -= 20.0; + mins[1] -= 20.0; + + maxs[0] += 20.0; + maxs[1] += 20.0; + + mins[2] += loco.GetStepHeight(); + maxs[2] += 5.0; + float myPos[3]; + this.GetAbsOrigin(myPos); + + if (!this.VelocityCancel && IsSpaceOccupiedIgnorePlayersAndEnts(myPos, mins, maxs, this.index)) + { + float origin[3]; + loco.SetVelocity(origin); + this.VelocityCancel = true; + } + } } static void OnCreate(SF2_BaseBoss boss) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp index 114f5047..8158eca5 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp @@ -14,6 +14,7 @@ methodmap SF2_ChaserFleeToHealAction < NextBotAction g_Factory.SetEventCallback(EventResponderType_OnMoveToSuccess, OnMoveToSuccess); g_Factory.SetEventCallback(EventResponderType_OnMoveToFailure, OnMoveToFailure); g_Factory.BeginDataMapDesc() + .DefineFloatField("m_FleeTime") .DefineFloatField("m_HealTime") .DefineFloatField("m_HealDuration") .DefineBoolField("m_HasPath") @@ -26,6 +27,19 @@ methodmap SF2_ChaserFleeToHealAction < NextBotAction return view_as(g_Factory.Create()); } + property float FleeTime + { + public get() + { + return this.GetDataFloat("m_FleeTime"); + } + + public set(float value) + { + this.SetDataFloat("m_FleeTime", value); + } + } + property float HealTime { public get() @@ -132,6 +146,7 @@ static NextBotAction InitialContainedAction(SF2_ChaserFleeToHealAction action, S int difficulty = controller.Difficulty; action.IsHealing = false; action.HasPath = false; + action.FleeTime = GetRandomFloat(10.0, 15.0); if (rageInfo.Animations.GetAnimation("start", difficulty, animName, sizeof(animName), rate, duration, cycle)) { int sequence = LookupProfileAnimation(actor.index, animName); @@ -265,6 +280,19 @@ static int Update(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, flo } } + if (!action.IsHealing) + { + action.FleeTime -= GetGameFrameTime(); + if (action.FleeTime <= 0.0) + { + path.Invalidate(); + actor.EndCloak(); + actor.IsRunningAway = false; + action.IsHealing = true; + actor.IsSelfHealing = true; + } + } + return action.Continue(); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp index fd65a9fd..944f547c 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -327,7 +327,7 @@ static int OnResume(SF2_ChaserMainAction action, SF2_ChaserEntity actor, NextBot static int OnInjured(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damageType) { - if (actor.CanBeStunned() && IsValidClient(attacker.index) && actor.CanTakeDamage(attacker, inflictor, damage)) + if (actor.CanBeStunned() && IsValidClient(attacker.index) && actor.CanTakeDamage(attacker, inflictor, damage) && !actor.IsRaging) { actor.StunHealth -= damage; if (actor.StunHealth <= 0.0) @@ -407,31 +407,12 @@ static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); if (GetVectorSquareMagnitude(destination, lastPos) <= SquareFloat(16.0) || TR_DidHit()) { - CursorData cursor = path.GetCursorData(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 400.0); int areaCount = collector.Count(); ArrayList areaArray = new ArrayList(1, areaCount); int validAreaCount = 0; for (int i = 0; i < areaCount; i++) { - if (collector.Get(i).GetCostSoFar() < 16.0) - { - continue; - } - if (cursor.segmentPrior != NULL_PATH_SEGMENT) - { - CNavArea segmentArea = cursor.segmentPrior.area; - if (segmentArea == collector.Get(i)) - { - continue; - } - } - float navPos[3]; - collector.Get(i).GetCenter(navPos); - if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) - { - continue; - } areaArray.Set(validAreaCount, i); validAreaCount++; } @@ -466,7 +447,7 @@ static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) delete collector; delete areaArray; } - path.GetClosestPosition(destination, destination, path.FirstSegment(), 128.0); + path.GetClosestPosition(destination, destination, path.FirstSegment(), 400.0); action.LastStuckTime = gameTime + 0.75; actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp index 7687cb7a..eea288ad 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp @@ -14,6 +14,7 @@ methodmap SF2_ChaserRageAction < NextBotAction g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); g_Factory.BeginDataMapDesc() .DefineBoolField("m_HasRaged") + .DefineBoolField("m_IsHealing") .EndDataMapDesc(); } return view_as(g_Factory.Create()); @@ -31,6 +32,19 @@ methodmap SF2_ChaserRageAction < NextBotAction this.SetData("m_HasRaged", value); } } + + property bool IsHealing + { + public get() + { + return this.GetData("m_IsHealing") != 0; + } + + public set(bool value) + { + this.SetData("m_IsHealing", value); + } + } } static NextBotAction InitialContainedAction(SF2_ChaserRageAction action, SF2_ChaserEntity actor) @@ -58,8 +72,14 @@ static NextBotAction InitialContainedAction(SF2_ChaserRageAction action, SF2_Cha actor.EndCloak(); + if (actor.State == STATE_ATTACK) + { + actor.CancelAttack = true; + } + if (rageInfo.IsHealing) { + action.IsHealing = true; return SF2_ChaserFleeToHealAction(); } @@ -79,6 +99,16 @@ static int Update(SF2_ChaserRageAction action, SF2_ChaserEntity actor, float int { if (action.ActiveChild != NULL_ACTION) { + if (!action.IsHealing) + { + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float pos[3]; + target.GetAbsOrigin(pos); + actor.MyNextBotPointer().GetLocomotionInterface().FaceTowards(pos); + } + } return action.Continue(); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp index 37571bab..ace6e722 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp @@ -171,7 +171,7 @@ static int OnSuspend(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextB return action.Done(); } -static void OnEnd(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) +static void OnEnd(SF2_ChaserSmellAction action, SF2_ChaserEntity actor) { actor.IsAllowedToDespawn = true; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 91021816..df135517 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -2809,56 +2809,67 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); - if (player.IsValid && !player.IsEliminated && !player.HasEscaped) + if (!player.IsValid) + { + continue; + } + + if (originalData.IsPvEBoss && !player.IsEliminated) + { + continue; + } + + if (!originalData.IsPvEBoss && (player.IsEliminated || player.HasEscaped)) { - count++; + continue; + } + count++; - switch (player.Class) + switch (player.Class) + { + case TFClass_Scout: { - case TFClass_Scout: - { - classAdd += data.StunHealthPerClass[1]; - } + classAdd += data.StunHealthPerClass[1]; + } - case TFClass_Soldier: - { - classAdd += data.StunHealthPerClass[3]; - } + case TFClass_Soldier: + { + classAdd += data.StunHealthPerClass[3]; + } - case TFClass_Pyro: - { - classAdd += data.StunHealthPerClass[7]; - } + case TFClass_Pyro: + { + classAdd += data.StunHealthPerClass[7]; + } - case TFClass_DemoMan: - { - classAdd += data.StunHealthPerClass[4]; - } + case TFClass_DemoMan: + { + classAdd += data.StunHealthPerClass[4]; + } - case TFClass_Heavy: - { - classAdd += data.StunHealthPerClass[6]; - } + case TFClass_Heavy: + { + classAdd += data.StunHealthPerClass[6]; + } - case TFClass_Engineer: - { - classAdd += data.StunHealthPerClass[9]; - } + case TFClass_Engineer: + { + classAdd += data.StunHealthPerClass[9]; + } - case TFClass_Medic: - { - classAdd += data.StunHealthPerClass[5]; - } + case TFClass_Medic: + { + classAdd += data.StunHealthPerClass[5]; + } - case TFClass_Sniper: - { - classAdd += data.StunHealthPerClass[2]; - } + case TFClass_Sniper: + { + classAdd += data.StunHealthPerClass[2]; + } - case TFClass_Spy: - { - classAdd += data.StunHealthPerClass[8]; - } + case TFClass_Spy: + { + classAdd += data.StunHealthPerClass[8]; } } } @@ -3132,7 +3143,7 @@ static Action Think(int entIndex) chaser.InterruptConditions |= interruptConditions; chaser.Target = target; - if (chaser.State == STATE_CHASE) + if (chaser.State == STATE_CHASE && !chaser.IsRaging) { chaser.DoAlwaysLookAt(chaser.Target); } @@ -3143,6 +3154,8 @@ static Action Think(int entIndex) chaser.CastFootstep(); } + chaser.CheckVelocityCancel(); + return Plugin_Continue; } @@ -3162,7 +3175,7 @@ static void ThinkPost(int entIndex) chaser.ProcessRainbowOutline(); } - if (chaser.State == STATE_CHASE) + if (chaser.State == STATE_CHASE && !chaser.IsRaging) { chaser.DoAlwaysLookAt(chaser.Target); } @@ -3205,10 +3218,10 @@ static void SpawnPost(int entIndex) } else { - chaser.SetProp(Prop_Data, "m_iHealth", RoundToFloor(controller.GetDeathHealth(difficulty))); - chaser.SetProp(Prop_Data, "m_takedamage", DAMAGE_YES); - + float addDeathHealth = data.DeathData.AddHealthPerPlayer[difficulty]; + int count; float add = 0.0; + float result = controller.GetDeathHealth(difficulty); for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); @@ -3230,6 +3243,7 @@ static void SpawnPost(int entIndex) continue; } } + count++; switch (player.Class) { @@ -3279,8 +3293,11 @@ static void SpawnPost(int entIndex) } } } + addDeathHealth *= float(count); + result += addDeathHealth + add; - chaser.SetProp(Prop_Data, "m_iHealth", chaser.GetProp(Prop_Data, "m_iHealth") + RoundToFloor(add)); + chaser.SetProp(Prop_Data, "m_iHealth", RoundToFloor(result)); + chaser.SetProp(Prop_Data, "m_takedamage", DAMAGE_YES); } chaser.MaxHealth = float(chaser.GetProp(Prop_Data, "m_iHealth")); if (SF2_ChaserSmellAction.IsPossible(chaser)) @@ -3315,6 +3332,85 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam return Plugin_Handled; } + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); + SF2ChaserBossProfileData chaserData; + chaserData = chaser.Controller.GetProfileData(); + int difficulty = chaser.Controller.Difficulty; + + if (player.IsValid) + { + CBaseEntity weaponEnt = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); + if (weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && player.Class == TFClass_Spy && (data.IsPvEBoss || SF_IsBoxingMap()) && chaser.State != STATE_DEATH) + { + float myEyePos[3], clientEyePos[3], buffer[3], myAng[3]; + player.GetEyePosition(clientEyePos); + chaser.Controller.GetEyePosition(myEyePos); + SubtractVectors(clientEyePos, myEyePos, buffer); + GetVectorAngles(buffer, buffer); + chaser.GetAbsAngles(myAng); + + if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 90.0 && chaserData.BackstabDamageScale > 0.0) + { + damageType = DMG_CRIT; + EmitSoundToClient(player.index, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + weaponEnt.SetPropFloat(Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); + player.SetPropFloat(Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + player.SetPropFloat(Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); + CBaseAnimating model = CBaseAnimating(player.GetPropEnt(Prop_Send, "m_hViewModel")); + if (model.IsValid() && model.index > MaxClients) + { + int sequence = 0; + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 4, 194, 665, 727, 794, 803, 883, 892, 901, 910, 959, 968: // Butterfly knives + { + sequence = 42; + } + case 225, 356, 461, 574, 649: // Non-butterfly knives + { + sequence = 16; + } + case 423, 1071, 30758: // Multi-class knives + { + sequence = 21; + } + case 638: // Sharp Dresser + { + sequence = 32; + } + } + model.SetProp(Prop_Send, "m_nSequence", sequence); + } + + damage = chaser.MaxHealth * chaserData.BackstabDamageScale; + if (!chaserData.DeathData.Enabled[difficulty]) + { + damage = chaser.MaxStunHealth * chaserData.BackstabDamageScale; + } + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 356: // Kunai + { + int health = player.Health + 100; + if (health > 210) + { + health = 210; + } + SetEntityHealth(player.index, health); + } + + case 461: // Big Earner + { + player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); + } + } + + return Plugin_Changed; + } + } + } + return Plugin_Continue; } @@ -3340,7 +3436,7 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float { switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) { - case 447, 426: // Whip + Eviction Notice + case 447, 426, 1181: // Whip + Eviction Notice + Hot Hand { player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); } @@ -3357,6 +3453,48 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float } } } + + weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); + if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) + { + switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 594: // Phlog + { + float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); + rage += (damage / 30.00); + if (rage > 100.0) + { + rage = 100.0; + } + player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); + } + } + } + + weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Secondary)); + switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 129, 1001, 226, 354: // Banners + { + float requiredRage = 6.0; + if (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex") == 354) + { + requiredRage = 4.8; + } + + if (player.GetProp(Prop_Send, "m_bRageDraining") == 0) + { + float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); + rage += (damage / requiredRage); + if (rage > 100.0) + { + rage = 100.0; + } + player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); + } + } + } } if (damage > 0.0) @@ -3378,6 +3516,21 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float } } + if (damage > 0.0 && player.IsValid && player.InCondition(TFCond_RegenBuffed) && (SF_IsBoxingMap() || data.IsPvEBoss)) + { + int health = player.Health; + float mult = damage * 0.475; + int newHealth = health + RoundToCeil(mult); + if (newHealth <= player.GetProp(Prop_Data, "m_iMaxHealth")) + { + SetEntityHealth(player.index, newHealth); + } + else + { + SetEntityHealth(player.index, player.GetProp(Prop_Data, "m_iMaxHealth")); + } + } + if (broadcastDamage) { bool miniCrit = false; @@ -3522,8 +3675,8 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio int difficulty = controller.Difficulty; - float playerDists[MAXTF2PLAYERS]; - int playerInterruptFlags[MAXTF2PLAYERS]; + float playerDists[2049]; + int playerInterruptFlags[2049]; float traceMins[3] = { -16.0, ... }; traceMins[2] = 0.0; @@ -3548,12 +3701,12 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } int oldTarget = chaser.OldTarget.index; - if (!IsTargetValidForSlender(SF2_BasePlayer(oldTarget), attackEliminated)) + if (!IsTargetValidForSlender(CBaseEntity(oldTarget), attackEliminated)) { chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); oldTarget = INVALID_ENT_REFERENCE; } - if (originalData.IsPvEBoss && !IsPvETargetValid(SF2_BasePlayer(oldTarget))) + if (originalData.IsPvEBoss && !IsPvETargetValid(CBaseEntity(oldTarget))) { chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); oldTarget = INVALID_ENT_REFERENCE; @@ -3567,37 +3720,65 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio chaser.SmellPlayerList.Clear(); } + ArrayList valids = new ArrayList(); for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer client = SF2_BasePlayer(i); - if (client.IsValid && g_PlayerDebugFlags[client.index] & DEBUG_BOSS_EYES) + if (!IsTargetValidForSlender(client, attackEliminated) && !originalData.IsPvEBoss) { - float end[3]; - end[0] = 1000.0; - VectorTransform(end, traceStartPos, myEyeAng, end); - int color[4] = { 0, 255, 0, 255 }; - TE_SetupBeamPoints(traceStartPos, end, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); - TE_SendToClient(client.index); + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(client)) + { + continue; } - if (!IsTargetValidForSlender(client, attackEliminated) && !originalData.IsPvEBoss) + valids.Push(client.index); + } + + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!IsTargetValidForSlender(entity, attackEliminated) && !originalData.IsPvEBoss) { continue; } - if (originalData.IsPvEBoss && !IsPvETargetValid(client)) + if (originalData.IsPvEBoss && !IsPvETargetValid(entity)) { continue; } - TFClassType classType = client.Class; - int classToInt = view_as(classType); + valids.Push(entity.index); + } - chaser.SetIsVisible(client, false); - chaser.SetInFOV(client, false); - chaser.SetIsNear(client, false); + for (int i = 0; i < valids.Length; i++) + { + CBaseEntity entity = CBaseEntity(valids.Get(i)); + SF2_BasePlayer player = SF2_BasePlayer(entity.index); - client.GetEyePosition(traceEndPos); + if (player.IsValid && g_PlayerDebugFlags[player.index] & DEBUG_BOSS_EYES) + { + float end[3]; + end[0] = 1000.0; + VectorTransform(end, traceStartPos, myEyeAng, end); + int color[4] = { 0, 255, 0, 255 }; + TE_SetupBeamPoints(traceStartPos, end, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); + TE_SendToClient(player.index); + } + + chaser.SetIsVisible(entity, false); + chaser.SetInFOV(entity, false); + chaser.SetIsNear(entity, false); + + if (player.IsValid) + { + player.GetEyePosition(traceEndPos); + } + else + { + entity.WorldSpaceCenter(traceEndPos); + } float dist = 99999999999.9; @@ -3614,7 +3795,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio isVisible = !TR_DidHit(); traceHitEntity = TR_GetEntityIndex(); - if (!isVisible && traceHitEntity == client.index) + if (!isVisible && traceHitEntity == entity.index) { isVisible = true; isTraceVisible = true; @@ -3622,14 +3803,14 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (isVisible) { - isVisible = NPCShouldSeeEntity(controller.Index, client.index); + isVisible = NPCShouldSeeEntity(controller.Index, entity.index); } dist = GetVectorSquareMagnitude(traceStartPos, traceEndPos); - if (g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) + if (player.IsValid && g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) { - int fogEntity = client.GetDataEnt(g_PlayerFogCtrlOffset); + int fogEntity = player.GetDataEnt(g_PlayerFogCtrlOffset); if (IsValidEdict(fogEntity)) { if (GetEntData(fogEntity, g_FogCtrlEnableOffset) && @@ -3648,18 +3829,18 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio float priorityValue; - chaser.SetIsVisible(client, isVisible); + chaser.SetIsVisible(entity, isVisible); // Near radius check. - if (chaser.GetIsVisible(client) && + if (chaser.GetIsVisible(entity) && dist <= SquareFloat(data.WakeRadius)) { - chaser.SetIsNear(client, true); - playerInterruptFlags[client.index] |= COND_ENEMYNEAR; + chaser.SetIsNear(entity, true); + playerInterruptFlags[entity.index] |= COND_ENEMYNEAR; } - if (chaser.GetIsVisible(client) && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) + if (player.IsValid && chaser.GetIsVisible(player) && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) { - TF2_StunPlayer(client.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); + TF2_StunPlayer(player.index, SPECIALROUND_BOO_DURATION, _, TF_STUNFLAGS_GHOSTSCARE); } // FOV check. @@ -3668,74 +3849,79 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (vision.GetFieldOfView() * 0.5)) { - chaser.SetInFOV(client, true); + chaser.SetInFOV(entity, true); } - if (chaser.GetIsVisible(client)) + if (chaser.GetIsVisible(entity)) { - playerInterruptFlags[client.index] |= COND_ENEMYVISIBLE; - if (chaser.GetInFOV(client)) + playerInterruptFlags[entity.index] |= COND_ENEMYVISIBLE; + if (chaser.GetInFOV(entity)) { - playerInterruptFlags[client.index] |= COND_SAWENEMY; + playerInterruptFlags[entity.index] |= COND_SAWENEMY; } } - playerDists[client.index] = dist; + playerDists[entity.index] = dist; if (chaser.SmellPlayerList != null && data.SmellData.Enabled[difficulty]) { - if (dist < Pow(data.SmellData.PlayerRange[difficulty], 2.0)) + if (player.IsValid && dist < Pow(data.SmellData.PlayerRange[difficulty], 2.0)) { - chaser.SmellPlayerList.Push(client.index); + chaser.SmellPlayerList.Push(player.index); } } - if (client.IsTrapped && (chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) && dist <= SquareFloat(searchRange)) + if (player.IsValid && player.IsTrapped && (chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) && dist <= SquareFloat(searchRange)) { - client.SetForceChaseState(controller, true); + player.SetForceChaseState(controller, true); } - if (client.ShouldBeForceChased(controller)) + if (ShouldClientBeForceChased(controller, entity)) { - bestNewTarget = client.index; - playerInterruptFlags[client.index] |= COND_ENEMYRECHASE; + bestNewTarget = entity.index; + playerInterruptFlags[entity.index] |= COND_ENEMYRECHASE; } - if (client.index != oldTarget) + if (entity.index != oldTarget) { - playerInterruptFlags[client.index] |= COND_NEWENEMY; + playerInterruptFlags[entity.index] |= COND_NEWENEMY; } - if (!SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !data.ChasesEndlessly) + if (player.IsValid && !SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !data.ChasesEndlessly) { - priorityValue = g_PageMax > 0 ? ((float(client.PageCount) / float(g_PageMax)) / 4.0) : 0.0; + priorityValue = g_PageMax > 0 ? ((float(player.PageCount) / float(g_PageMax)) / 4.0) : 0.0; } - if (!IsClassConfigsValid()) + if (player.IsValid) { - if ((classType == TFClass_Medic || client.HasRegenItem) && !SF_IsBoxingMap()) + TFClassType classType = player.Class; + int classToInt = view_as(classType); + if (!IsClassConfigsValid()) { - priorityValue += 0.2; - } + if ((classType == TFClass_Medic || player.HasRegenItem) && !SF_IsBoxingMap()) + { + priorityValue += 0.2; + } - if (classType == TFClass_Spy) - { - priorityValue += 0.1; + if (classType == TFClass_Spy) + { + priorityValue += 0.1; + } } - } - else - { - if (!SF_IsBoxingMap() && client.HasRegenItem) + else { - priorityValue += 0.2; + if (!SF_IsBoxingMap() && player.HasRegenItem) + { + priorityValue += 0.2; + } + priorityValue += g_ClassBossPriorityMultiplier[classToInt]; } - priorityValue += g_ClassBossPriorityMultiplier[classToInt]; } - if (chaser.GetIsNear(client) || (chaser.GetIsVisible(client) && chaser.GetInFOV(client))) + if (chaser.GetIsNear(entity) || (chaser.GetIsVisible(entity) && chaser.GetInFOV(entity))) { float targetPos[3]; - client.GetAbsOrigin(targetPos); + entity.GetAbsOrigin(targetPos); if (dist <= SquareFloat(searchRange)) { // Subtract distance to increase priority. @@ -3743,19 +3929,19 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (dist < bestNewTargetDist) { - bestNewTarget = client.index; + bestNewTarget = entity.index; bestNewTargetDist = dist; - playerInterruptFlags[client.index] |= COND_SAWENEMY; + playerInterruptFlags[entity.index] |= COND_SAWENEMY; } } } - if (data.ChaseOnLookData.Enabled[difficulty] && isTraceVisible) + if (data.ChaseOnLookData.Enabled[difficulty] && isTraceVisible && player.IsValid) { bool shouldCalculate = false; if (data.ChaseOnLookData.RequiredFOV[difficulty] <= 0.0) { - shouldCalculate = chaser.GetInFOV(client); + shouldCalculate = chaser.GetInFOV(player); } else { @@ -3764,7 +3950,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (shouldCalculate) { float eyeAng[3], expectedAng[3], lookPos[3], myPos[3]; - client.GetEyeAngles(eyeAng); + player.GetEyeAngles(eyeAng); chaser.GetAbsOrigin(myPos); lookPos = data.ChaseOnLookData.RequiredLookPosition; VectorTransform(lookPos, myPos, myEyeAng, lookPos); @@ -3780,11 +3966,11 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if ((xAng >= minimumXAng && xAng < maximumXAng) && (yAng >= minimumYAng && yAng < maximumYAng) && ((data.ChaseOnLookData.AddTargets[difficulty]) || (!data.ChaseOnLookData.AddTargets[difficulty] && controller.ChaseOnLookTargets.Length == 0))) { - controller.ChaseOnLookTargets.Push(client.index); + controller.ChaseOnLookTargets.Push(player.index); SF2BossProfileSoundInfo soundInfo; soundInfo = originalData.ScareSounds; - soundInfo.EmitSound(true, client.index); - client.ChangeCondition(TFCond_MarkedForDeathSilent); + soundInfo.EmitSound(true, player.index); + player.ChangeCondition(TFCond_MarkedForDeathSilent); } } } @@ -3798,7 +3984,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly) { - if (!IsTargetValidForSlender(SF2_BasePlayer(bestNewTarget), attackEliminated)) + if (!IsTargetValidForSlender(CBaseEntity(bestNewTarget), attackEliminated)) { if (chaser.State != STATE_CHASE && NPCAreAvailablePlayersAlive()) { @@ -3822,7 +4008,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) { bestNewTarget = raidTarget; - SF2_BasePlayer(bestNewTarget).SetForceChaseState(controller, true); + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); } } delete arrayRaidTargets; @@ -3855,7 +4041,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (IsValidClient(raidTarget) && IsClientInPvE(raidTarget)) { bestNewTarget = raidTarget; - SF2_BasePlayer(bestNewTarget).SetForceChaseState(controller, true); + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); } } delete arrayRaidTargets; @@ -4086,7 +4272,7 @@ static void ProcessBody(SF2_ChaserEntity chaser) chaser.SetPoseParameter(chaser.MoveYParameter, GetVectorDotProduct(motionVector, rightVector)); float groundSpeed = chaser.GetPropFloat(Prop_Data, "m_flGroundSpeed"); - if (groundSpeed != 0.0 && loco.IsOnGround()) + if (groundSpeed != 0.0 && loco.IsOnGround() && chaser.State != STATE_ATTACK) { float rate = (speed / groundSpeed); if (rate < 0.0) @@ -4202,15 +4388,25 @@ static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) if (IsValidEntity(other)) { SF2_BasePlayer player = SF2_BasePlayer(other); + INextBot bot = loco.GetBot(); + SF2_ChaserEntity chaser = SF2_ChaserEntity(bot.GetEntity()); if (player.IsValid) { + if (chaser.IsValid() && chaser.Controller.IsValid()) + { + SF2BossProfileData data; + SF2NPC_BaseNPC controller = view_as(chaser.Controller); + data = controller.GetProfileData(); + if (data.IsPvEBoss && player.IsInPvE) + { + return true; + } + } if (!SF_IsBoxingMap() && !player.IsProxy && !player.IsInGhostMode && player.Team != TFTeam_Blue && !player.IsInDeathCam) { return true; } } - INextBot bot = loco.GetBot(); - SF2_ChaserEntity chaser = SF2_ChaserEntity(bot.GetEntity()); if (chaser.IsValid() && chaser.Controller.IsValid()) { SF2BossProfileData data; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index 946f421d..25756035 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -193,6 +193,8 @@ static Action Think(int entIndex) statue.DoAlwaysLookAt(statue.Target); } + statue.CheckVelocityCancel(); + return Plugin_Continue; } diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index 8d28fd4f..b1e3f31b 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -32,7 +32,7 @@ static Handle g_NpcInstantKillThink[MAX_BOSSES]; static int g_NpcBoxingCurrentDifficulty[MAX_BOSSES]; static int g_NpcBoxingRagePhase[MAX_BOSSES]; -static bool g_ClientShouldBeForceChased[MAX_BOSSES][MAXTF2PLAYERS]; +static bool g_ClientShouldBeForceChased[MAX_BOSSES][2049]; GlobalForward g_OnChaserBossStartAttackFwd; GlobalForward g_OnChaserBossEndAttackFwd; @@ -138,12 +138,12 @@ void NPCChaserSetBoxingDifficulty(int npcIndex, int value) g_NpcBoxingCurrentDifficulty[npcIndex] = value; } -bool ShouldClientBeForceChased(SF2NPC_BaseNPC controller, SF2_BasePlayer client) +bool ShouldClientBeForceChased(SF2NPC_BaseNPC controller, CBaseEntity client) { return g_ClientShouldBeForceChased[controller.Index][client.index]; } -void SetClientForceChaseState(SF2NPC_BaseNPC controller, SF2_BasePlayer client, bool value) +void SetClientForceChaseState(SF2NPC_BaseNPC controller, CBaseEntity client, bool value) { g_ClientShouldBeForceChased[controller.Index][client.index] = value; } @@ -153,7 +153,7 @@ ArrayList NPCChaserGetAutoChaseTargets(int npcIndex) return g_NpcChaseOnLookTarget[npcIndex]; } -void ResetClientNPCStates(SF2_BasePlayer client) +void ResetClientNPCStates(CBaseEntity client) { for (int i = 0; i < MAX_BOSSES; i++) { @@ -300,18 +300,19 @@ void Despawn_Chaser(int bossIndex) // - If I lose sight or I'm unable to traverse safely, find paths around obstacles and follow memorized path. // - If I reach the end of my path and I still don't see him and I still want to pursue him, keep on going in the direction I'm going. -bool IsTargetValidForSlender(SF2_BasePlayer target, bool includeEliminated = false) +bool IsTargetValidForSlender(CBaseEntity target, bool includeEliminated = false) { - if (!target.IsValid) + if (!target.IsValid()) { return false; } - if (!target.IsAlive || - target.IsInDeathCam || - (!includeEliminated && target.IsEliminated) || - target.IsInGhostMode || - target.HasEscaped) + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && (!player.IsAlive || + player.IsInDeathCam || + (!includeEliminated && player.IsEliminated) || + player.IsInGhostMode || + player.HasEscaped)) { return false; } @@ -319,14 +320,16 @@ bool IsTargetValidForSlender(SF2_BasePlayer target, bool includeEliminated = fal return true; } -bool IsPvETargetValid(SF2_BasePlayer target) +bool IsPvETargetValid(CBaseEntity target) { - if (!target.IsValid) + if (!target.IsValid()) { return false; } - if (!target.IsAlive || !target.IsInPvE || target.IsInGhostMode) + SF2_BasePlayer player = SF2_BasePlayer(target.index); + + if (player.IsValid && (!player.IsAlive || !player.IsInPvE || player.IsInGhostMode)) { return false; } diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 7d8e5bfe..fe92960d 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -840,6 +840,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GoBack(); } kv.GetString("pve_spawn_message_prefix", profileData.PvESpawnMessagePrefix, sizeof(profileData.PvESpawnMessagePrefix), profileData.PvESpawnMessagePrefix); + profileData.DisplayPvEHealth = kv.GetNum("pve_health_bar", profileData.DisplayPvEHealth) != 0; char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; strcopy(setProfile, sizeof(setProfile), profile); if (kv.GetNum("pve_selectable", 1) != 0) diff --git a/addons/sourcemod/scripting/sf2/pve.sp b/addons/sourcemod/scripting/sf2/pve.sp index 51913651..b343515a 100644 --- a/addons/sourcemod/scripting/sf2/pve.sp +++ b/addons/sourcemod/scripting/sf2/pve.sp @@ -802,7 +802,7 @@ static void SpawnPvEBoss(const char[] override = "") { soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), g_CustomMusicOverride, sizeof(g_CustomMusicOverride)); } - g_ActiveBosses.Push(npc.EntIndex); + g_ActiveBosses.Push(EntIndexToEntRef(npc.EntIndex)); for (int i = 0; i < MAX_BOSSES; i++) { SF2NPC_BaseNPC testNPC = SF2NPC_BaseNPC(i); @@ -814,7 +814,7 @@ static void SpawnPvEBoss(const char[] override = "") if (testNPC.CompanionMaster == npc) { testNPC.Spawn(spawnPos); - g_ActiveBosses.Push(testNPC.EntIndex); + g_ActiveBosses.Push(EntIndexToEntRef(testNPC.EntIndex)); } } @@ -828,7 +828,7 @@ static void SpawnPvEBoss(const char[] override = "") continue; } copy.Spawn(spawnPos); - g_ActiveBosses.Push(copy.EntIndex); + g_ActiveBosses.Push(EntIndexToEntRef(copy.EntIndex)); } } } @@ -1125,7 +1125,7 @@ void UnregisterPvESlenderBoss(char profile[SF2_MAX_PROFILE_NAME_LENGTH]) void KillPvEBoss(int boss) { - int index = g_ActiveBosses.FindValue(boss); + int index = g_ActiveBosses.FindValue(EntIndexToEntRef(boss)); if (index != -1) { g_ActiveBosses.Erase(index); @@ -1170,6 +1170,11 @@ void KillPvEBoss(int boss) } } +ArrayList GetActivePvEBosses() +{ + return g_ActiveBosses; +} + static Action Timer_RemoveAllPvEBosses(Handle timer) { if (!g_Enabled) diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 6fb60810..3d7a4c64 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -1304,7 +1304,8 @@ bool EnumerateBreakableEntities(int entIndex, ArrayList array) char className[64]; GetEntityClassname(entIndex, className, sizeof(className)); - if (strcmp(className, "prop_physics") == 0 || strcmp(className, "prop_dynamic") == 0) + if (strcmp(className, "prop_physics") == 0 || strcmp(className, "prop_dynamic") == 0 || + strcmp(className, "func_breakable", false) == 0) { if (GetEntProp(entIndex, Prop_Data, "m_iHealth") > 0) { From f88b51d4f611274b1186f1146e13728c344e693b Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Wed, 22 Nov 2023 20:18:19 -0700 Subject: [PATCH 19/86] 2.21 --- .../scripting/sf2/npc/entities/base/entity.sp | 12 ++++++------ .../scripting/sf2/npc/entities/chaser/entity.sp | 10 ++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index 881fb433..b9b3283b 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -131,12 +131,12 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter { public get() { - return CBaseEntity(this.GetPropEnt(Prop_Data, "m_Target")); + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_Target"))); } public set(CBaseEntity entity) { - this.SetPropEnt(Prop_Data, "m_Target", entity.index); + this.SetPropEnt(Prop_Data, "m_Target", EnsureEntRef(entity.index)); } } @@ -144,12 +144,12 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter { public get() { - return CBaseEntity(this.GetPropEnt(Prop_Data, "m_OldTarget")); + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_OldTarget"))); } public set(CBaseEntity entity) { - this.SetPropEnt(Prop_Data, "m_OldTarget", entity.index); + this.SetPropEnt(Prop_Data, "m_OldTarget", EnsureEntRef(entity.index)); } } @@ -392,12 +392,12 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter { public get() { - return CBaseEntity(this.GetPropEnt(Prop_Data, "m_KillTarget")); + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_KillTarget"))); } public set(CBaseEntity entity) { - this.SetPropEnt(Prop_Data, "m_KillTarget", entity.index); + this.SetPropEnt(Prop_Data, "m_KillTarget", EnsureEntRef(entity.index)); } } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index df135517..07146cd4 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -810,12 +810,12 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss { public get() { - return CBaseEntity(this.GetPropEnt(Prop_Data, "m_KillTarget")); + return CBaseEntity(EntRefToEntIndex(this.GetPropEnt(Prop_Data, "m_KillTarget"))); } public set(CBaseEntity entity) { - this.SetPropEnt(Prop_Data, "m_KillTarget", entity.index); + this.SetPropEnt(Prop_Data, "m_KillTarget", EnsureEntRef(entity.index)); } } @@ -3109,8 +3109,8 @@ static void OnDisconnected(SF2_BasePlayer client) static void OnCreate(SF2_ChaserEntity ent) { ent.AttackIndex = -1; - ent.Target = CBaseEntity(INVALID_ENT_REFERENCE); - ent.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + ent.Target = CBaseEntity(-1); + ent.OldTarget = CBaseEntity(-1); ent.IsAllowedToDespawn = true; SDKHook(ent.index, SDKHook_Think, Think); SDKHook(ent.index, SDKHook_ThinkPost, ThinkPost); @@ -3976,6 +3976,8 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } } + delete valids; + if (bestNewTarget != INVALID_ENT_REFERENCE) { interruptConditions = playerInterruptFlags[bestNewTarget]; From ace2753954a8bfedc45762a8f25c874d5f02b50a Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Thu, 23 Nov 2023 02:08:51 -0700 Subject: [PATCH 20/86] Update entity.sp --- .../sf2/npc/entities/chaser/entity.sp | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 07146cd4..d3be3634 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -3432,7 +3432,7 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float if (player.IsValid) { CBaseEntity weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); - if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) + if (weapon.IsValid() && weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) { switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) { @@ -3455,7 +3455,7 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float } weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); - if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) + if (weapon.IsValid() && weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) { switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) { @@ -3473,25 +3473,28 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float } weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Secondary)); - switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + if (weapon.IsValid()) { - case 129, 1001, 226, 354: // Banners + switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) { - float requiredRage = 6.0; - if (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex") == 354) + case 129, 1001, 226, 354: // Banners { - requiredRage = 4.8; - } + float requiredRage = 6.0; + if (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex") == 354) + { + requiredRage = 4.8; + } - if (player.GetProp(Prop_Send, "m_bRageDraining") == 0) - { - float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); - rage += (damage / requiredRage); - if (rage > 100.0) + if (player.GetProp(Prop_Send, "m_bRageDraining") == 0) { - rage = 100.0; + float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); + rage += (damage / requiredRage); + if (rage > 100.0) + { + rage = 100.0; + } + player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); } - player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); } } } From fbb9338201eba858eeb633d765a53d9ebed540e4 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 2 Dec 2023 03:42:10 -0700 Subject: [PATCH 21/86] Patch --- addons/sourcemod/scripting/sf2.sp | 82 ++------ addons/sourcemod/scripting/sf2/client.sp | 4 +- .../scripting/sf2/client/deathcam.sp | 9 +- addons/sourcemod/scripting/sf2/effects.sp | 4 +- .../projectiles/sf2_grenade_projectile.sp | 15 +- .../scripting/sf2/extras/commands.sp | 1 + .../scripting/sf2/extras/game_events.sp | 35 ++-- addons/sourcemod/scripting/sf2/npc.sp | 177 ++++++++++++++++++ .../scripting/sf2/npc/entities/base/entity.sp | 25 ++- .../sf2/npc/entities/chaser/actions/attack.sp | 4 + .../sf2/npc/entities/chaser/actions/chase.sp | 20 ++ .../npc/entities/chaser/actions/chaselayer.sp | 15 ++ .../sf2/npc/entities/chaser/actions/death.sp | 68 ++++++- .../npc/entities/chaser/actions/mainlayer.sp | 84 +++------ .../sf2/npc/entities/chaser/actions/rage.sp | 2 +- .../sf2/npc/entities/chaser/actions/stun.sp | 1 + .../sf2/npc/entities/chaser/entity.sp | 36 +++- .../sf2/npc/entities/statue/actions/chase.sp | 13 ++ .../npc/entities/statue/actions/mainlayer.sp | 78 +------- .../sf2/npc/entities/statue/entity.sp | 4 +- .../sf2/profiles/profiles_boss_functions.sp | 36 ++-- addons/sourcemod/scripting/sf2/stocks.sp | 28 +++ 22 files changed, 479 insertions(+), 262 deletions(-) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 02b40313..3ab64173 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -667,6 +667,7 @@ PrivateForward g_OnRoundStartPFwd; PrivateForward g_OnRoundEndPFwd; PrivateForward g_OnEntityCreatedPFwd; PrivateForward g_OnEntityDestroyedPFwd; +PrivateForward g_OnEntityTeleportedPFwd; PrivateForward g_OnAdminMenuCreateOptionsPFwd; PrivateForward g_OnPlayerJumpPFwd; PrivateForward g_OnPlayerSpawnPFwd; @@ -2846,90 +2847,29 @@ void Hook_TriggerOnEndTouch(const char[] output, int caller, int activator, floa #endif } -void Hook_TriggerTeleportOnStartTouch(const char[] output, int caller, int activator, float delay) +void Hook_TriggerTeleportOnStartTouch(const char[] output, int teleporter, int activator, float delay) { if (!g_Enabled) { return; } - if (!IsValidEntity(caller)) + if (!IsValidEntity(teleporter)) { return; } - int flags = GetEntProp(caller, Prop_Data, "m_spawnflags"); - if (((flags & TRIGGER_CLIENTS) && (flags & TRIGGER_NPCS)) || (flags & TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS)) + if (SDKCall(g_SDKPassesTriggerFilters, teleporter, activator) == 0) { - if (IsValidClient(activator)) - { - bool chase = ClientHasMusicFlag(activator, MUSICF_CHASE); - if (chase) - { - // The player took a teleporter and is chased, and the boss can take it too, add the teleporter to the temp boss' goals. - for (int i = 0; i < MAX_BOSSES; i++) - { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); - if (Npc.UniqueID == -1) - { - continue; - } - /*if (EntRefToEntIndex(g_SlenderTarget[i]) == activator) - { - for (int ii = 0; ii < MAX_NPCTELEPORTER; ii++) - { - if (Npc.GetTeleporter(ii) == INVALID_ENT_REFERENCE) - { - Npc.SetTeleporter(ii, EntIndexToEntRef(caller)); - break; - } - } - }*/ - } - } - return; - } - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(activator)); - if (Npc.IsValid()) - { - //A boss took a teleporter - int teleporter = Npc.GetTeleporter(0); - if (teleporter == EntIndexToEntRef(caller)) //Remove our temp goal, and go back chase our target! GRAAAAAAAAAAAAh! Unless we have some other teleporters to take....fak. - { - Npc.SetTeleporter(0, INVALID_ENT_REFERENCE); - } - if (MAX_NPCTELEPORTER > 2 && Npc.GetTeleporter(1) != INVALID_ENT_REFERENCE) - { - for (int i = 0; i + 1 < MAX_NPCTELEPORTER; i++) - { - if (Npc.GetTeleporter(i + 1) != INVALID_ENT_REFERENCE) - { - Npc.SetTeleporter(i, Npc.GetTeleporter(i + 1)); - } - else - { - Npc.SetTeleporter(i, INVALID_ENT_REFERENCE); - } - } - } - } - } - if (IsValidClient(activator)) - { - bool chase = ClientHasMusicFlag(activator, MUSICF_CHASE); - if (chase) - { - // The player took a teleporter and is chased, but the boss can't follow. - for (int i = 0; i < MAX_BOSSES; i++) - { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - } - } + return; } + + Call_StartForward(g_OnEntityTeleportedPFwd); + Call_PushCell(CBaseEntity(teleporter)); + Call_PushCell(CBaseEntity(activator)); + Call_Finish(); } + static Action Hook_PageOnTakeDamage(int page, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { if (!g_Enabled) diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index ffe4b971..b090af4e 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -790,8 +790,8 @@ void ClientProcessVisibility(int client) TriggerTimer(g_PlayerLastStaticTimer[client], true); // Start up our own static timer. - float staticIncreaseRate = (g_SlenderStaticRate[bossNewStatic][difficulty] - (g_SlenderStaticRate[bossNewStatic][difficulty] * g_RoundDifficultyModifier)/10); - float staticDecreaseRate = (g_SlenderStaticRateDecay[bossNewStatic][difficulty] + (g_SlenderStaticRateDecay[bossNewStatic][difficulty] * g_RoundDifficultyModifier)/10); + float staticIncreaseRate = (g_SlenderStaticRate[bossNewStatic][difficulty] - (g_SlenderStaticRate[bossNewStatic][difficulty] * GetDifficultyModifier(difficulty)) / 10); + float staticDecreaseRate = (g_SlenderStaticRateDecay[bossNewStatic][difficulty] + (g_SlenderStaticRateDecay[bossNewStatic][difficulty] * GetDifficultyModifier(difficulty)) / 10); if (!IsClassConfigsValid()) { if (class == TFClass_Heavy) diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index c50dd2e9..623162f3 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -249,7 +249,9 @@ void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool TF2_IgnitePlayer(client, client); } - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); + + SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. KillClient(client); return; @@ -473,7 +475,9 @@ static void StopDeathCam(int client) Call_PushCell(deathCamBoss.Index); Call_Finish(); - SDKHooks_TakeDamage(client, 0, 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); + CBaseEntity boss = CBaseEntity(deathCamBoss.EntIndex); + + SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. KillClient(client); } @@ -547,7 +551,6 @@ static void Hook_DeathCamThink(int client) soundInfo = data.PlayerDeathCamOverlaySounds; soundInfo.EmitSound(true, player.index); duration = g_SlenderDeathCamTime[Npc.Index]; - duration -= g_SlenderDeathCamOverlayTimeStart[Npc.Index]; } g_PlayerDeathCamTimer[player.index] = duration; g_PlayerDeathCamMustDoOverlay[player.index] = false; diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index 780094cd..504b64a2 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -564,8 +564,8 @@ static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, if (effectsInfo.TEParticleAttachType == 2) { VectorTransform(start, basePos, baseAng, start); - pos = effectsInfo.Origin; - ang = effectsInfo.Angles; + pos = start; + ang = effectAng; } else { diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp index f09987e4..c65d0c72 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -281,12 +281,17 @@ methodmap SF2_ProjectileGrenade < SF2_ProjectileBase grenade.Timer = GetGameTime() + 2.0; - SDKHook(grenade.index, SDKHook_VPhysicsUpdate, Think); + CreateTimer(0.1, Timer_Think, EntIndexToEntRef(grenade.index), TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); } } -static void Think(int entity) +static Action Timer_Think(Handle timer, any ref) { + int entity = EntRefToEntIndex(ref); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + return Plugin_Stop; + } SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade(entity); if (projectile.Timer < GetGameTime()) @@ -300,7 +305,7 @@ static void Think(int entity) if (projectile.Touched) { - return; + return Plugin_Continue; } float pos[3], mins[3], maxs[3]; @@ -313,7 +318,7 @@ static void Think(int entity) if (hitIndex == 0) { projectile.Touched = true; - return; + return Plugin_Continue; } else { @@ -323,6 +328,8 @@ static void Think(int entity) projectile.DoExplosion(); } } + + return Plugin_Continue; } bool TraceRayGrenade(int entity, int mask, any data) diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 9ce7ba2f..b5a74e63 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -398,6 +398,7 @@ public void OnPluginStart() g_OnRoundEndPFwd = new PrivateForward(ET_Ignore); g_OnEntityCreatedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_String); g_OnEntityDestroyedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_String); + g_OnEntityTeleportedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_OnPlayerJumpPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerDeathPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 363a07a2..f8ac056f 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -750,16 +750,31 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) } } + bool modify = false; + + if (!modify && SF2_ProjectileIceball(inflictor).IsValid()) + { + modify = true; + CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + + if (!modify && SF2_ProjectileCowMangler(inflictor).IsValid()) + { + modify = true; + CreateTimer(0.01, Timer_ManglerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + if (npcIndex != -1) { SF2BossProfileData data; data = NPCGetProfileData(npcIndex); g_PlayerBossKillSubject[client] = npcIndex; - if (data.AshRagdoll || data.CloakRagdoll || data.DecapRagdoll || data.DeleteRagdoll || data.DissolveRagdoll || + if (!modify && (data.AshRagdoll || data.CloakRagdoll || data.DecapRagdoll || data.DeleteRagdoll || data.DissolveRagdoll || data.ElectrocuteRagdoll || data.GoldRagdoll || data.IceRagdoll || data.PlasmaRagdoll || data.PushRagdoll || - data.ResizeRagdoll || data.BurnRagdoll) + data.ResizeRagdoll || data.BurnRagdoll || data.GibRagdoll)) { + modify = true; CreateTimer(0.01, Timer_ModifyRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } @@ -768,27 +783,19 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) event.SetInt("death_flags", data.CustomDeathFlagType); } - if (data.DecapOrGibRagdoll) + if (!modify && data.DecapOrGibRagdoll) { + modify = true; CreateTimer(0.01, Timer_DeGibRagdoll, GetClientUserId(client)); } - if (data.MultiEffectRagdoll) + if (!modify && data.MultiEffectRagdoll) { + modify = true; CreateTimer(0.01, Timer_MultiRagdoll, GetClientUserId(client)); } } - if (SF2_ProjectileIceball(inflictor).IsValid()) - { - CreateTimer(0.01, Timer_IceRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - - if (SF2_ProjectileCowMangler(inflictor).IsValid()) - { - CreateTimer(0.01, Timer_ManglerRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - } - #if defined DEBUG if (g_DebugDetailConVar.IntValue > 1) { diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index f1247559..1db1f9b2 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -199,6 +199,7 @@ void NPCInitialize() g_OnClientCaughtByBossFwd = new GlobalForward("SF2_OnClientCaughtByBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + g_OnEntityTeleportedPFwd.AddFunction(null, EntityTeleported); CreateNative("SF2_GetMaxBossCount", Native_GetMaxBosses); CreateNative("SF2_EntIndexToBossIndex", Native_EntIndexToBossIndex); @@ -250,6 +251,87 @@ static void EntityDestroyed(CBaseEntity ent, const char[] classname) } } +static void EntityTeleported(CBaseEntity teleporter, CBaseEntity activator) +{ + int flags = teleporter.GetProp(Prop_Data, "m_spawnflags"); + if (((flags & TRIGGER_CLIENTS) != 0 && (flags & TRIGGER_NPCS)) || (flags & TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS) != 0) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(NPCGetFromEntIndex(activator.index)); + if (controller.IsValid()) + { + // A boss took a teleporter, remove it from our list if possible + SF2_BaseBoss boss = SF2_BaseBoss(controller.EntIndex); + ArrayList teleporters = boss.Teleporters; + int index = teleporters.FindValue(teleporter.index); + if (index != -1) + { + teleporters.Erase(index); + } + } + else + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + SF2_BaseBoss boss = SF2_BaseBoss(npc.EntIndex); + if (!SF2_ChaserEntity(npc.EntIndex).IsValid() && !SF2_StatueEntity(npc.EntIndex).IsValid()) + { + continue; + } + + if (boss.Target == activator) + { + // The boss target took a teleporter and the boss can follow, add the teleporter to the list of teleporters + boss.Teleporters.Push(teleporter.index); + } + } + } + } + else + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i); + if (!controller.IsValid()) + { + continue; + } + + SF2_BaseBoss boss = SF2_BaseBoss(controller.EntIndex); + if (!SF2_ChaserEntity(controller.EntIndex).IsValid() && !SF2_StatueEntity(controller.EntIndex).IsValid()) + { + continue; + } + + // Currently a boss is chasing this entity but the boss cannot follow, abort the chase state + if (boss.Target == activator) + { + boss.Target = CBaseEntity(-1); + boss.OldTarget = CBaseEntity(-1); + + // OR if we're supposed to endlessly chase, despawn us otherwise that'd lead to some bad moments + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) + { + controller.UnSpawn(); + } + + if (controller.Type == SF2BossType_Chaser) + { + if (view_as(controller).GetProfileData().ChasesEndlessly) + { + controller.UnSpawn(); + } + } + } + } + } +} + void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -2371,6 +2453,101 @@ bool GetSlenderModel(int bossIndex, int modelState = 0, char[] buffer, int buffe return true; } +bool NPCFindUnstuckPosition(SF2_BaseBoss boss, float lastPos[3], float destination[3]) +{ + SF2NPC_BaseNPC controller = boss.Controller; + PathFollower path = controller.Path; + CBaseNPC npc = TheNPCs.FindNPCByEntIndex(boss.index); + CNavArea area = TheNavMesh.GetNearestNavArea(lastPos, _, _, _, false); + area.GetCenter(destination); + float tempMaxs[3]; + npc.GetBodyMaxs(tempMaxs); + float traceMins[3]; + traceMins[0] = g_SlenderDetectMins[controller.Index][0] - 5.0; + traceMins[1] = g_SlenderDetectMins[controller.Index][1] - 5.0; + traceMins[2] = 0.0; + + float traceMaxs[3]; + traceMaxs[0] = g_SlenderDetectMaxs[controller.Index][0] + 5.0; + traceMaxs[1] = g_SlenderDetectMaxs[controller.Index][1] + 5.0; + traceMaxs[2] = tempMaxs[2]; + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (GetVectorSquareMagnitude(destination, lastPos) <= SquareFloat(16.0) || TR_DidHit()) + { + SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 400.0); + int areaCount = collector.Count(); + ArrayList areaArray = new ArrayList(1, areaCount); + int validAreaCount = 0; + for (int i = 0; i < areaCount; i++) + { + areaArray.Set(validAreaCount, i); + validAreaCount++; + } + + int randomArea = 0, randomCell = 0; + areaArray.Resize(validAreaCount); + area = NULL_AREA; + while (validAreaCount > 1) + { + randomCell = GetRandomInt(0, validAreaCount - 1); + randomArea = areaArray.Get(randomCell); + area = collector.Get(randomArea); + area.GetCenter(destination); + + TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); + if (TR_DidHit()) + { + area = NULL_AREA; + validAreaCount--; + int findValue = areaArray.FindValue(randomCell); + if (findValue != -1) + { + areaArray.Erase(findValue); + } + } + else + { + break; + } + } + + delete collector; + delete areaArray; + } + path.GetClosestPosition(destination, destination, path.FirstSegment(), 400.0); + if (GetVectorSquareMagnitude(destination, lastPos) > SquareFloat(8.0)) + { + return true; + } + + Segment first = path.FirstSegment(); + if (first != NULL_PATH_SEGMENT) + { + int attempts = 0; + Segment next = NULL_PATH_SEGMENT; + while (attempts <= 2) + { + next = path.NextSegment(first); + if (next == NULL_PATH_SEGMENT) + { + break; + } + float segmentPos[3], temp[3]; + next.GetPos(segmentPos); + path.GetClosestPosition(segmentPos, temp, next, 800.0); + if (GetVectorSquareMagnitude(temp, lastPos) > SquareFloat(64.0)) + { + destination = temp; + return true; + } + first = next; + attempts++; + } + } + + return false; +} + void ChangeAllSlenderModels() { char buffer[PLATFORM_MAX_PATH]; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index b9b3283b..72799907 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -31,7 +31,7 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter public static void Initialize() { - g_Factory = new CEntityFactory("sf2_npc_boss_base", OnCreate); + g_Factory = new CEntityFactory("sf2_npc_boss_base", OnCreate, OnRemove); g_Factory.IsAbstract = true; g_Factory.DeriveFromNPC(); g_Factory.BeginDataMapDesc() @@ -63,6 +63,7 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter .DefineBoolField("m_IsAttemptingToMove") .DefineIntField("m_EyeBoneIndex") .DefineBoolField("m_VelocityCancel") + .DefineIntField("m_Teleporters") .EndDataMapDesc(); g_Factory.Install(); @@ -440,6 +441,19 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter } } + property ArrayList Teleporters + { + public get() + { + return view_as(this.GetProp(Prop_Data, "m_Teleporters")); + } + + public set(ArrayList value) + { + this.SetProp(Prop_Data, "m_Teleporters", value); + } + } + public void EyePosition(float buffer[3], const float defaultValue[3] = { 0.0, 0.0, 0.0 }) { this.Controller.GetEyePosition(buffer, defaultValue); @@ -701,10 +715,19 @@ static void OnCreate(SF2_BaseBoss boss) boss.Controller = SF2_INVALID_NPC; boss.Target = CBaseEntity(-1); boss.LastKillTime = 0.0; + boss.Teleporters = new ArrayList(); SDKHook(boss.index, SDKHook_SpawnPost, SpawnPost); } +static void OnRemove(SF2_BaseBoss boss) +{ + if (boss.Teleporters != null) + { + delete boss.Teleporters; + } +} + static void SpawnPost(int entIndex) { SF2_BaseBoss boss = SF2_BaseBoss(entIndex); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp index 109e3e84..592b2f3d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -322,6 +322,10 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i if (actor.MovementType == SF2NPCMoveType_Attack && attackData.Type != SF2BossAttackType_Custom) { + if (actor.Teleporters.Length > 0) + { + CBaseEntity(actor.Teleporters.Get(0)).GetAbsOrigin(targetPos); + } if (!bot.IsRangeLessThanEx(targetPos, 8.0)) { if ((interrputConditions & COND_NEWENEMY) != 0 || path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp index 20264ba5..64b7e729 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -12,6 +12,7 @@ methodmap SF2_ChaserChaseAction < NextBotAction g_Factory.SetCallback(NextBotActionCallbackType_InitialContainedAction, InitialContainedAction); g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); } return view_as(g_Factory.Create()); @@ -163,6 +164,21 @@ static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) return action.Continue(); } +static void OnResume(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) +{ + if (actor.WasStunned) + { + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + if (controller.GetProfileData().ChaseInitialOnStun) + { + actor.PerformVoice(SF2BossSound_ChaseInitial); + } + } + } +} + static void OnEnd(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) { if (!actor.Controller.IsValid()) @@ -184,6 +200,10 @@ static void OnEnd(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) data = actor.Controller.GetProfileData(); actor.FollowCooldownChase = GetGameTime() + data.AlertOnChaseInfo.FollowCooldown[difficulty]; } + if (actor.Teleporters != null) + { + actor.Teleporters.Clear(); + } SF2ChaserBossProfileData chaserData; chaserData = actor.Controller.GetProfileData(); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp index 90f8b6e9..6f054f47 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp @@ -58,6 +58,10 @@ static int Update(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, flo { float pos[3]; target.GetAbsOrigin(pos); + if (actor.Teleporters.Length > 0) + { + CBaseEntity(actor.Teleporters.Get(0)).GetAbsOrigin(pos); + } if (!bot.IsRangeLessThanEx(pos, 8.0)) { @@ -92,5 +96,16 @@ static int OnResume(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, N { actor.MovementType = SF2NPCMoveType_Run; actor.UpdateMovementAnimation(); + if (actor.WasStunned) + { + SF2NPC_Chaser controller = actor.Controller; + if (controller.IsValid()) + { + if (controller.GetProfileData().ChaseInitialOnStun && SF2_ChaserChaseInitialAction.IsPossible(actor)) + { + return action.SuspendFor(SF2_ChaserChaseInitialAction()); + } + } + } return action.Continue(); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp index 89d7782b..30cf5499 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp @@ -137,7 +137,73 @@ static void OnEnd(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) if (data.DeathData.RemoveOnDeath) { SpawnGibs(actor); - controller.Remove(); + if (data.DeathData.RagdollOnDeath) + { + actor.AcceptInput("BecomeRagdoll"); + } + if (controller.IsCopy) + { + controller.Remove(); + } + else + { + SF2NPC_BaseNPC newMaster = SF2_INVALID_NPC; + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC other = SF2NPC_BaseNPC(i); + if (!other.IsValid()) + { + continue; + } + + if (other == controller) + { + continue; + } + + if (!other.IsCopy) + { + continue; + } + + if (other.CopyMaster == controller && CBaseEntity(other.EntIndex).IsValid()) + { + newMaster = other; + newMaster.CopyMaster = SF2_INVALID_NPC; + break; + } + } + + // Let's switch copy masters + if (newMaster.IsValid()) + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC other = SF2NPC_BaseNPC(i); + if (!other.IsValid()) + { + continue; + } + + if (other == controller) + { + continue; + } + + if (!other.IsCopy) + { + continue; + } + + if (other.CopyMaster == controller) + { + other.CopyMaster = newMaster; + } + } + } + + controller.Remove(); + } } else if (data.DeathData.DisappearOnDeath) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp index 944f547c..192d7687 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -239,8 +239,6 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) return action.SuspendFor(SF2_DeathCamAction()); } - UnstuckCheck(action, actor); - actor.ProcessTraps(); #if defined DEBUG @@ -315,13 +313,22 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) delete props; } + UnstuckCheck(action, actor); + return action.Continue(); } static int OnResume(SF2_ChaserMainAction action, SF2_ChaserEntity actor, NextBotAction priorAction) { action.LastStuckTime = GetGameTime() + 0.75; - actor.UpdateMovementAnimation(); + if (!actor.IsInChaseInitial) + { + actor.UpdateMovementAnimation(); + } + if (actor.WasStunned) + { + actor.WasStunned = false; + } return action.Continue(); } @@ -371,18 +378,23 @@ static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) ILocomotion loco = bot.GetLocomotionInterface(); SF2NPC_Chaser controller = actor.Controller; PathFollower path = controller.Path; - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); float gameTime = GetGameTime(); - if (!path.IsValid() || !actor.IsAttemptingToMove || loco.GetDesiredSpeed() <= 0.0 || actor.MovementType == SF2NPCMoveType_Attack) + float goalPos[3], myPos[3]; + if (path.IsValid()) + { + path.GetEndPosition(goalPos); + } + actor.GetAbsOrigin(myPos); + + if (!path.IsValid() || !actor.IsAttemptingToMove || loco.GetDesiredSpeed() <= 0.0 || actor.MovementType == SF2NPCMoveType_Attack || (path.IsValid() && GetVectorSquareMagnitude(myPos, goalPos) <= Pow(16.0, 2.0))) { action.LastStuckTime = gameTime; return; } - float lastPos[3], myPos[3]; + float lastPos[3]; action.GetLastPos(lastPos); - actor.GetAbsOrigin(myPos); if (bot.IsRangeLessThanEx(lastPos, 0.13) || loco.GetGroundSpeed() <= 0.1) { @@ -391,63 +403,11 @@ static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) return; } float destination[3]; - CNavArea area = TheNavMesh.GetNearestNavArea(lastPos, _, _, _, false); - area.GetCenter(destination); - float tempMaxs[3]; - npc.GetBodyMaxs(tempMaxs); - float traceMins[3]; - traceMins[0] = g_SlenderDetectMins[controller.Index][0] - 5.0; - traceMins[1] = g_SlenderDetectMins[controller.Index][1] - 5.0; - traceMins[2] = 0.0; - - float traceMaxs[3]; - traceMaxs[0] = g_SlenderDetectMaxs[controller.Index][0] + 5.0; - traceMaxs[1] = g_SlenderDetectMaxs[controller.Index][1] + 5.0; - traceMaxs[2] = tempMaxs[2]; - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (GetVectorSquareMagnitude(destination, lastPos) <= SquareFloat(16.0) || TR_DidHit()) + if (!NPCFindUnstuckPosition(actor, lastPos, destination)) { - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 400.0); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - int randomArea = 0, randomCell = 0; - areaArray.Resize(validAreaCount); - area = NULL_AREA; - while (validAreaCount > 1) - { - randomCell = GetRandomInt(0, validAreaCount - 1); - randomArea = areaArray.Get(randomCell); - area = collector.Get(randomArea); - area.GetCenter(destination); - - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (TR_DidHit()) - { - area = NULL_AREA; - validAreaCount--; - int findValue = areaArray.FindValue(randomCell); - if (findValue != -1) - { - areaArray.Erase(findValue); - } - } - else - { - break; - } - } - - delete collector; - delete areaArray; + controller.UnSpawn(); + return; } - path.GetClosestPosition(destination, destination, path.FirstSegment(), 400.0); action.LastStuckTime = gameTime + 0.75; actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp index eea288ad..3f01a119 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp @@ -127,7 +127,7 @@ static void OnEnd(SF2_ChaserRageAction action, SF2_ChaserEntity actor) data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); if (rageInfo.IncreaseDifficulty) { - actor.Controller.Difficulty++; + actor.Controller.Difficulty += 1; if (actor.Controller.Difficulty > Difficulty_Apollyon) { actor.Controller.Difficulty = Difficulty_Apollyon; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp index facdf8cb..71fd0e2e 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp @@ -171,5 +171,6 @@ static void OnEnd(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) actor.UpdateMovementAnimation(); } + actor.WasStunned = true; actor.State = action.OldState; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index d3be3634..2c3d5c75 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -56,6 +56,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss .DefineFloatField("m_OverrideSpawnAnimationDuration") .DefineFloatField("m_Override") .DefineBoolField("m_IsStunned") + .DefineBoolField("m_WasStunned") .DefineFloatField("m_StunHealth") .DefineFloatField("m_NextStunTime") .DefineFloatField("m_MaxStunHealth") @@ -252,6 +253,19 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } } + property bool WasStunned + { + public get() + { + return this.GetProp(Prop_Data, "m_WasStunned") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_WasStunned", value); + } + } + property float StunHealth { public get() @@ -1818,8 +1832,9 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } else { - if (this.ProjectileAmmo != 0) + if (this.ProjectileAmmo > 0) { + this.ProjectileAmmo--; this.ShootProjectile(); } else @@ -1829,7 +1844,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss this.ProjectileReloadTime = gameTime + data.ProjectileReloadTime[difficulty]; this.IsReloadingProjectiles = true; } - else if (this.ProjectileReloadTime <= gameTime && this.IsReloadingProjectiles) + if (this.ProjectileReloadTime <= gameTime && this.IsReloadingProjectiles) { this.ProjectileAmmo = data.ProjectileClipSize[difficulty]; this.IsReloadingProjectiles = false; @@ -3675,6 +3690,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio { attackEliminated = originalData.IsPvEBoss; } + float gameTime = GetGameTime(); int difficulty = controller.Difficulty; @@ -3874,7 +3890,8 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } } - if (player.IsValid && player.IsTrapped && (chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) && dist <= SquareFloat(searchRange)) + if (player.IsValid && (player.IsTrapped || (player.IsReallySprinting && data.AutoChaseSprinters[difficulty] && chaser.GetAutoChaseCooldown(player) < gameTime)) + && (chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) && dist <= SquareFloat(searchRange)) { player.SetForceChaseState(controller, true); } @@ -3984,6 +4001,10 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (bestNewTarget != INVALID_ENT_REFERENCE) { interruptConditions = playerInterruptFlags[bestNewTarget]; + if (bestNewTarget != oldTarget) + { + chaser.Teleporters.Clear(); + } chaser.OldTarget = CBaseEntity(bestNewTarget); } @@ -4172,8 +4193,11 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) speed *= 1.25; } - speed = (speed + (speed * g_RoundDifficultyModifier) / 15.0); - acceleration = (acceleration + (acceleration * g_RoundDifficultyModifier) / 15.0); + if (difficulty > Difficulty_Normal) + { + speed = (speed + (speed * GetDifficultyModifier(difficulty)) / 15.0); + acceleration = (acceleration + (acceleration * GetDifficultyModifier(difficulty)) / 15.0); + } if (moveType == SF2NPCMoveType_Run) { @@ -4369,7 +4393,7 @@ static void ProcessBody(SF2_ChaserEntity chaser) } else { - velocity = (velocity + ((npc.flRunSpeed * g_RoundDifficultyModifier) / 15.0)) / npc.flRunSpeed; + velocity = (velocity + ((npc.flRunSpeed * GetDifficultyModifier(controller.Difficulty)) / 15.0)) / npc.flRunSpeed; if (loco.IsOnGround() && chaser.IsAttemptingToMove && chaser.State != STATE_ATTACK) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp index 47b290b1..1fd33ce1 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp @@ -11,6 +11,7 @@ methodmap SF2_StatueChaseAction < NextBotAction g_Factory = new NextBotActionFactory("Statue_Chase"); g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); } return view_as(g_Factory.Create()); } @@ -117,6 +118,10 @@ static int Update(SF2_StatueChaseAction action, SF2_StatueEntity actor) { float pos[3]; target.GetAbsOrigin(pos); + if (actor.Teleporters.Length > 0) + { + CBaseEntity(actor.Teleporters.Get(0)).GetAbsOrigin(pos); + } if (!bot.IsRangeLessThanEx(pos, 8.0) && actor.IsMoving) { @@ -155,4 +160,12 @@ static int Update(SF2_StatueChaseAction action, SF2_StatueEntity actor) } } return action.Continue(); +} + +static void OnEnd(SF2_StatueChaseAction action, SF2_StatueEntity actor) +{ + if (actor.Teleporters != null) + { + actor.Teleporters.Clear(); + } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp index ac7a5ea3..592456f9 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp @@ -140,7 +140,6 @@ static void UnstuckCheck(SF2_StatueBaseAction action, SF2_StatueEntity actor) ILocomotion loco = bot.GetLocomotionInterface(); SF2NPC_Statue controller = actor.Controller; PathFollower path = controller.Path; - CBaseNPC npc = TheNPCs.FindNPCByEntIndex(actor.index); float gameTime = GetGameTime(); if (!path.IsValid() || !actor.IsMoving || loco.GetDesiredSpeed() <= 0.0) @@ -160,82 +159,11 @@ static void UnstuckCheck(SF2_StatueBaseAction action, SF2_StatueEntity actor) return; } float destination[3]; - CNavArea area = TheNavMesh.GetNearestNavArea(lastPos, _, _, _, false); - area.GetCenter(destination); - float tempMaxs[3]; - npc.GetBodyMaxs(tempMaxs); - float traceMins[3]; - traceMins[0] = g_SlenderDetectMins[controller.Index][0] - 5.0; - traceMins[1] = g_SlenderDetectMins[controller.Index][1] - 5.0; - traceMins[2] = 0.0; - - float traceMaxs[3]; - traceMaxs[0] = g_SlenderDetectMaxs[controller.Index][0] + 5.0; - traceMaxs[1] = g_SlenderDetectMaxs[controller.Index][1] + 5.0; - traceMaxs[2] = tempMaxs[2]; - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (GetVectorSquareMagnitude(destination, lastPos) <= SquareFloat(16.0) || TR_DidHit()) + if (!NPCFindUnstuckPosition(actor, lastPos, destination)) { - CursorData cursor = path.GetCursorData(); - SurroundingAreasCollector collector = TheNavMesh.CollectSurroundingAreas(area, 256.0); - int areaCount = collector.Count(); - ArrayList areaArray = new ArrayList(1, areaCount); - int validAreaCount = 0; - for (int i = 0; i < areaCount; i++) - { - if (collector.Get(i).GetCostSoFar() < 16.0) - { - continue; - } - if (cursor.segmentPrior != NULL_PATH_SEGMENT) - { - CNavArea segmentArea = cursor.segmentPrior.area; - if (segmentArea == collector.Get(i)) - { - continue; - } - } - float navPos[3]; - collector.Get(i).GetCenter(navPos); - if (GetVectorSquareMagnitude(myPos, navPos) <= SquareFloat(16.0)) - { - continue; - } - areaArray.Set(validAreaCount, i); - validAreaCount++; - } - - int randomArea = 0, randomCell = 0; - areaArray.Resize(validAreaCount); - area = NULL_AREA; - while (validAreaCount > 1) - { - randomCell = GetRandomInt(0, validAreaCount - 1); - randomArea = areaArray.Get(randomCell); - area = collector.Get(randomArea); - area.GetCenter(destination); - - TR_TraceHullFilter(destination, destination, traceMins, traceMaxs, MASK_NPCSOLID, TraceRayDontHitPlayersOrEntityEx); - if (TR_DidHit()) - { - area = NULL_AREA; - validAreaCount--; - int findValue = areaArray.FindValue(randomCell); - if (findValue != -1) - { - areaArray.Erase(findValue); - } - } - else - { - break; - } - } - - delete collector; - delete areaArray; + controller.UnSpawn(); + return; } - path.GetClosestPosition(destination, destination, path.FirstSegment(), 128.0); action.LastStuckTime = gameTime + 0.75; actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index 25756035..4bb41166 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -420,8 +420,8 @@ static void ProcessSpeed(SF2_StatueEntity statue) speed *= 1.25; } - speed = (speed + (speed * g_RoundDifficultyModifier) / 15.0); - acceleration = (acceleration + (acceleration * g_RoundDifficultyModifier) / 15.0); + speed = (speed + (speed * GetDifficultyModifier(difficulty)) / 15.0); + acceleration = (acceleration + (acceleration * GetDifficultyModifier(difficulty)) / 15.0); if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S)) { diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index fe92960d..22b3fcee 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -343,43 +343,43 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } kv.GetString("chat_message_upon_death_prefix", profileData.DeathMessagePrefix, sizeof(profileData.DeathMessagePrefix), profileData.DeathMessagePrefix); - profileData.BurnRagdoll = !!kv.GetNum("burn_ragdoll_on_kill", profileData.BurnRagdoll); - profileData.CloakRagdoll = !!kv.GetNum("cloak_ragdoll_on_kill", profileData.CloakRagdoll); - profileData.DecapRagdoll = !!kv.GetNum("decap_ragdoll_on_kill", profileData.DecapRagdoll); - profileData.GibRagdoll = !!kv.GetNum("gib_ragdoll_on_kill", profileData.GibRagdoll); - profileData.IceRagdoll = !!kv.GetNum("ice_ragdoll_on_kill", profileData.IceRagdoll); - profileData.GoldRagdoll = !!kv.GetNum("gold_ragdoll_on_kill", profileData.GoldRagdoll); - profileData.ElectrocuteRagdoll = !!kv.GetNum("electrocute_ragdoll_on_kill", profileData.ElectrocuteRagdoll); - profileData.AshRagdoll = !!kv.GetNum("disintegrate_ragdoll_on_kill", profileData.AshRagdoll); - profileData.DeleteRagdoll = !!kv.GetNum("delete_ragdoll_on_kill", profileData.DeleteRagdoll); - profileData.PushRagdoll = !!kv.GetNum("push_ragdoll_on_kill", profileData.PushRagdoll); + profileData.BurnRagdoll = kv.GetNum("burn_ragdoll_on_kill", profileData.BurnRagdoll) != 0; + profileData.CloakRagdoll = kv.GetNum("cloak_ragdoll_on_kill", profileData.CloakRagdoll) != 0; + profileData.DecapRagdoll = kv.GetNum("decap_ragdoll_on_kill", profileData.DecapRagdoll) != 0; + profileData.GibRagdoll = kv.GetNum("gib_ragdoll_on_kill", profileData.GibRagdoll) != 0; + profileData.IceRagdoll = kv.GetNum("ice_ragdoll_on_kill", profileData.IceRagdoll) != 0; + profileData.GoldRagdoll = kv.GetNum("gold_ragdoll_on_kill", profileData.GoldRagdoll) != 0; + profileData.ElectrocuteRagdoll = kv.GetNum("electrocute_ragdoll_on_kill", profileData.ElectrocuteRagdoll) != 0; + profileData.AshRagdoll = kv.GetNum("disintegrate_ragdoll_on_kill", profileData.AshRagdoll) != 0; + profileData.DeleteRagdoll = kv.GetNum("delete_ragdoll_on_kill", profileData.DeleteRagdoll) != 0; + profileData.PushRagdoll = kv.GetNum("push_ragdoll_on_kill", profileData.PushRagdoll) != 0; if (profileData.PushRagdoll) { kv.GetVector("push_ragdoll_force", profileData.PushRagdollForce, profileData.PushRagdollForce); } - profileData.DissolveRagdoll = !!kv.GetNum("dissolve_ragdoll_on_kill", profileData.DissolveRagdoll); + profileData.DissolveRagdoll = kv.GetNum("dissolve_ragdoll_on_kill", profileData.DissolveRagdoll) != 0; if (profileData.DissolveRagdoll) { profileData.DissolveKillType = kv.GetNum("dissolve_ragdoll_type", profileData.DissolveKillType); } - profileData.PlasmaRagdoll = !!kv.GetNum("plasma_ragdoll_on_kill", profileData.PlasmaRagdoll); - profileData.ResizeRagdoll = !!kv.GetNum("resize_ragdoll_on_kill", profileData.ResizeRagdoll); + profileData.PlasmaRagdoll = kv.GetNum("plasma_ragdoll_on_kill", profileData.PlasmaRagdoll) != 0; + profileData.ResizeRagdoll = kv.GetNum("resize_ragdoll_on_kill", profileData.ResizeRagdoll) != 0; if (profileData.ResizeRagdoll) { profileData.ResizeRagdollHead = kv.GetFloat("resize_ragdoll_head", profileData.ResizeRagdollHead); profileData.ResizeRagdollHands = kv.GetFloat("resize_ragdoll_hands", profileData.ResizeRagdollHands); profileData.ResizeRagdollTorso = kv.GetFloat("resize_ragdoll_torso", profileData.ResizeRagdollTorso); } - profileData.DecapOrGibRagdoll = !!kv.GetNum("decap_or_gib_ragdoll_on_kill", profileData.DecapOrGibRagdoll); - profileData.SilentKill = !!kv.GetNum("silent_kill", profileData.SilentKill); - profileData.MultiEffectRagdoll = !!kv.GetNum("multieffect_ragdoll_on_kill", profileData.MultiEffectRagdoll); - profileData.CustomDeathFlag = !!kv.GetNum("attack_custom_deathflag_enabled", profileData.CustomDeathFlag); + profileData.DecapOrGibRagdoll = kv.GetNum("decap_or_gib_ragdoll_on_kill", profileData.DecapOrGibRagdoll) != 0; + profileData.SilentKill = kv.GetNum("silent_kill", profileData.SilentKill) != 0; + profileData.MultiEffectRagdoll = kv.GetNum("multieffect_ragdoll_on_kill", profileData.MultiEffectRagdoll) != 0; + profileData.CustomDeathFlag = kv.GetNum("attack_custom_deathflag_enabled", profileData.CustomDeathFlag) != 0; if (profileData.CustomDeathFlag) { profileData.CustomDeathFlagType = kv.GetNum("attack_custom_deathflag", profileData.CustomDeathFlagType); } - profileData.OutroMusic = !!kv.GetNum("sound_music_outro_enabled", profileData.OutroMusic); + profileData.OutroMusic = kv.GetNum("sound_music_outro_enabled", profileData.OutroMusic) != 0; profileData.EngineSoundLevel = kv.GetNum("constant_sound_level", profileData.EngineSoundLevel); profileData.EngineSoundVolume = kv.GetFloat("constant_sound_volume", profileData.EngineSoundVolume); diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 3d7a4c64..532f1f4f 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -1441,6 +1441,34 @@ int GetLocalGlobalDifficulty(int npcIndex = -1) return g_DifficultyConVar.IntValue; } +float GetDifficultyModifier(int difficulty) +{ + switch (difficulty) + { + case Difficulty_Hard: + { + return DIFFICULTYMODIFIER_HARD; + } + + case Difficulty_Insane: + { + return DIFFICULTYMODIFIER_INSANE; + } + + case Difficulty_Nightmare: + { + return DIFFICULTYMODIFIER_NIGHTMARE; + } + + case Difficulty_Apollyon: + { + return DIFFICULTYMODIFIER_APOLLYON; + } + } + + return DIFFICULTYMODIFIER_NORMAL; +} + MRESReturn Hook_GlowUpdateTransmitState(int glow, DHookReturn returnHook) { returnHook.Value = SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); From a20f7b0a7bb0d6b80c5085fa5f0dd0cec48a8ea4 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 2 Dec 2023 04:06:36 -0700 Subject: [PATCH 22/86] Proxy crits disabled for good --- addons/sourcemod/scripting/sf2/client.sp | 2 +- addons/sourcemod/scripting/sf2/client/proxy.sp | 2 -- addons/sourcemod/scripting/sf2/extras/game_events.sp | 5 ----- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index b090af4e..f42293fc 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -182,7 +182,7 @@ Action Hook_ClientSetTransmit(int client,int other) public Action TF2_CalcIsAttackCritical(int client, int weapon, char[] weaponName, bool &result) { - if (!g_Enabled || g_RestartSessionEnabled || g_PlayerEliminated[client]) + if (!g_Enabled || g_RestartSessionEnabled || (g_PlayerEliminated[client] && !g_PlayerProxy[client])) { return Plugin_Continue; } diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 2f9d2801..7a767043 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -808,8 +808,6 @@ Action Timer_ApplyCustomModel(Handle timer, any userid) SetEntProp(client, Prop_Send, "m_iAirDash", 99999); - TF2Attrib_SetByName(client, "crit mod disabled", 0.0); - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[client]); if (g_PlayerProxy[client] && master != -1) diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index f8ac056f..4629e8d0 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -652,11 +652,6 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) #endif int client = GetClientOfUserId(event.GetInt("userid")); - if (g_PlayerProxy[client]) - { - TF2Attrib_RemoveByName(client, "crit mod disabled"); - } - for (int i = 0; i < view_as(TFCond_PowerupModeDominant); i++) { g_ClientInCondition[client][i] = false; From 9b2cd2a88e5f688ee3e27154bf8c5bafa0425be6 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 2 Dec 2023 04:12:15 -0700 Subject: [PATCH 23/86] Patch --- addons/sourcemod/scripting/sf2/client.sp | 4 ++++ addons/sourcemod/scripting/sf2/client/proxy.sp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index f42293fc..3daaa595 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -991,6 +991,10 @@ Action Timer_ClientPageDetector(Handle timer, int userid) CBaseEntity pageEnt = CBaseEntity(pageEntities.Get(i)); pageEnt.GetAbsOrigin(pagePos); goalArea = TheNavMesh.GetNearestNavArea(pagePos); + if (goalArea == NULL_AREA) + { + continue; + } TheNavMesh.BuildPath(area, goalArea, pagePos, _, _, 5000.0); float cost = goalArea.GetCostSoFar(); diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 7a767043..3379ecae 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -789,7 +789,7 @@ Action Timer_ClientProxyControl(Handle timer, any userid) if (g_PlayerProxyControl[client] <= 0) { // ForcePlayerSuicide isn't really dependable, since the player doesn't suicide until several seconds after spawning has passed. - SDKHooks_TakeDamage(client, client, client, 9001.0, DMG_PREVENT_PHYSICS_FORCE, _, view_as({ 0.0, 0.0, 0.0 })); + KillClient(client); return Plugin_Stop; } From e9be495549d33692d10098fb04cdf4257aa18787 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sun, 10 Dec 2023 19:24:02 -0700 Subject: [PATCH 24/86] Alpha 2.4 --- addons/sourcemod/configs/sf2/class_stats.cfg | 18 +- addons/sourcemod/scripting/include/sf2.inc | 446 ++++++++++++++- .../include/sf2/profiles/profiles.inc | 4 +- .../include/sf2/profiles/profiles_chaser.inc | 6 +- addons/sourcemod/scripting/sf2.sp | 17 +- .../sourcemod/scripting/sf2/classconfigs.sp | 4 +- addons/sourcemod/scripting/sf2/client.sp | 13 +- .../scripting/sf2/client/breathing.sp | 2 +- .../sourcemod/scripting/sf2/client/sprint.sp | 533 ++++++++---------- .../sourcemod/scripting/sf2/client/think.sp | 2 +- addons/sourcemod/scripting/sf2/effects.sp | 12 +- .../scripting/sf2/entities/initialize.sp | 13 + .../projectiles/sf2_arrow_projectile.sp | 29 +- .../projectiles/sf2_baseball_projectile.sp | 16 + .../projectiles/sf2_cow_mangler_projectile.sp | 18 + .../projectiles/sf2_fireball_projectile.sp | 17 + .../projectiles/sf2_grenade_projectile.sp | 20 +- .../projectiles/sf2_iceball_projectile.sp | 20 +- .../projectiles/sf2_rocket_projectile.sp | 19 + .../sf2_sentry_rocket_projectile.sp | 14 + .../sf2/entities/sf2_base_projectile.sp | 55 +- .../scripting/sf2/extras/commands.sp | 39 +- .../sourcemod/scripting/sf2/extras/natives.sp | 11 +- addons/sourcemod/scripting/sf2/methodmaps.sp | 39 +- addons/sourcemod/scripting/sf2/npc.sp | 27 +- .../scripting/sf2/npc/entities/base/entity.sp | 107 +++- .../sf2/npc/entities/chaser/actions/attack.sp | 10 +- .../sf2/npc/entities/chaser/actions/chase.sp | 6 + .../sf2/npc/entities/chaser/entity.sp | 167 +++--- .../sf2/npc/entities/statue/actions/chase.sp | 5 + .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 19 + addons/sourcemod/scripting/sf2/profiles.sp | 2 +- .../sf2/profiles/profiles_boss_functions.sp | 4 +- addons/sourcemod/scripting/sf2/stocks.sp | 24 + 34 files changed, 1221 insertions(+), 517 deletions(-) diff --git a/addons/sourcemod/configs/sf2/class_stats.cfg b/addons/sourcemod/configs/sf2/class_stats.cfg index 35a2429f..9d1f9f5c 100644 --- a/addons/sourcemod/configs/sf2/class_stats.cfg +++ b/addons/sourcemod/configs/sf2/class_stats.cfg @@ -82,7 +82,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.05" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -123,7 +123,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "0" + "sprint_loss_while_jumping" "0.0" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -164,7 +164,7 @@ "danger_speed_multiplier" "1.33" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -205,7 +205,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.1" "scare_sprint_multiplier" "1.667" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -246,7 +246,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -287,7 +287,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -328,7 +328,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.15" "can_pickup_health" "0" @@ -369,7 +369,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" @@ -410,7 +410,7 @@ "danger_speed_multiplier" "1.34" "sprint_multiplier" "1.0" "scare_sprint_multiplier" "1.0" - "sprint_loss_while_jumping" "7" + "sprint_loss_while_jumping" "0.07" "proxy_damage_vulnerability" "0.0" "can_pickup_health" "1" diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 8e376e45..4c917381 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -368,6 +368,15 @@ forward void SF2_OnBossDespawn(int bossIndex); */ forward void SF2_OnBossChangeState(int bossIndex, int oldState, int newState); +/** + * Called each time a boss update its animation. + * + * @param bossIndex Boss index. + * @param animation The animation name used. + * @return If Plugin_Handled will not update the boss animation. + */ +forward Action SF2_OnBossAnimationUpdate(int bossIndex, const char[] animation); + /** * Called to determine the boss's run speed. * @@ -1621,6 +1630,16 @@ native bool SF2_GetChaserProfileFromName(const char[] profile, any data[sizeof(S */ native bool SF2_GetStatueProfileFromName(const char[] profile, any data[sizeof(SF2ChaserBossProfileData)]); +/** + * Forces a boss to enter in the chase state and immediately go after the target. + * If the boss is already chasing a target, then the target will be queued up for it to be auto chased. + * + * @param bossIndex Boss index. + * @param target Entity to use. + * @param value Force chase state + */ +native void SF2_SetEntityForceChaseState(int bossIndex, CBaseEntity target, bool value); + /** * Methodmap responsible for all boss entities. */ @@ -1665,6 +1684,17 @@ methodmap SF2_BaseBossEntity < CBaseCombatCharacter property float CurrentChaseDuration { public native get(); + public native set(float value); + } + + /** + * If set will override the chase duration next time the boss goes into the chase state. + * This value will reset to 0 once the boss enters the chase state. + */ + property float InitialChaseDuration + { + public native get(); + public native set(float value); } /** @@ -2568,21 +2598,23 @@ methodmap SF2_Player < CBaseCombatCharacter public native void HandleSprint(bool sprint); /** - * Sets the player's sprint timer - * - * @param recharge Whether or not the timer should be recharging the player's sprint + * Gets/sets the player's stamina */ - public native void SetSprintTimer(bool recharge = false); + property float Stamina + { + public native get(); - /** - * Returns the player's sprint points from 0-100 - */ - public native int GetSprintPoints(); + public native set(float value); + } /** - * Sets the player's sprint points + * Sets the stamina's next recharge time + * + * @param time Amount of time it will take for the stamina to continue to recharge + * @param checkTime Whether or not to check if the current recharge time is greater than the + * inputted recharge time */ - public native void SetSprintPoints(int value); + public native void SetStaminaRechargeTime(float time, bool checkTime = true); /** * Whether or not the player's blink HUD element is showing @@ -2785,6 +2817,400 @@ methodmap SF2_Player < CBaseCombatCharacter public native bool IsLookingAtBoss(int bossIndex); } +/** + * Interface to SF2_ProjectileBase + * + * Useful for creating custom projectiles, see sf2/entities/projectiles to see how projectiles are created + */ +methodmap SF2_Projectile_Base < CBaseAnimating +{ + /** + * Initializes a projectile's stats + * + * @param type Projectile type + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius If configured to explode, the blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param explosion Explosion particle + * @param impactSound Impact/explosion sound + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + */ + public native void Initialize(const int type = SF2BossProjectileType_Custom, + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Triggers a explosion on the projectile and removes it + */ + public native void DoExplosion(); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Base(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for arrow projectiles + */ +methodmap SF2_Projectile_Arrow < SF2_Projectile_Base +{ + /** + * Creates a arrow projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param impactSound Impact sound when it hits a player + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Arrow Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] trail, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Arrow(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for baseball projectiles + */ +methodmap SF2_Projectile_Baseball < SF2_Projectile_Base +{ + /** + * Creates a baseball projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Baseball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const bool isCrits, + const char[] model, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Baseball(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for cow mangler projectiles + */ +methodmap SF2_Projectile_CowMangler < SF2_Projectile_Base +{ + /** + * Creates a cow mangler projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_CowMangler Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_CowMangler(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for fireball projectiles + */ +methodmap SF2_Projectile_Fireball < SF2_Projectile_Base +{ + /** + * Creates a fireball projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param impactSound Impact sound when it hits a player + * @param trail Trail material name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Fireball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Fireball(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for grenade projectiles + */ +methodmap SF2_Projectile_Grenade < SF2_Projectile_Base +{ + /** + * Creates a grenade projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param explosion Explosion particle + * @param impactSound Impact/explosion sound + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Grenade Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Grenade(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for iceball projectiles + */ +methodmap SF2_Projectile_Iceball < SF2_Projectile_Base +{ + /** + * Creates a iceball projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param impactSound Impact sound when it hits a player + * @param trail Trail material name + * @param slowDuration How long the slowdown effect lasts + * @param slowMultiplier The speed multiplier of the slowdown effect + * @param freezeSound The sound that plays when a player gets slowed down + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Iceball Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const char[] impactSound, + const char[] trail, + const float slowDuration, + const float slowMultiplier, + const char[] freezeSound, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Iceball(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for rocket projectiles + */ +methodmap SF2_Projectile_Rocket < SF2_Projectile_Base +{ + /** + * Creates a rocket projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param trail Trail material name + * @param explosion Explosion particle + * @param impactSound Impact/explosion sound + * @param model Model name + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_Rocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const char[] trail, + const char[] explosion, + const char[] impactSound, + const char[] model, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_Rocket(int entity) + { + return view_as(entity); + } +} + +/** + * The methodmap for sentry rocket projectiles + */ +methodmap SF2_Projectile_SentryRocket < SF2_Projectile_Base +{ + /** + * Creates a sentry rocket projectile. + * + * @param owner Owner entity + * @param pos Spawn position + * @param ang Spawn angles + * @param speed Speed of the projectile + * @param damage Damage of the projectile + * @param blastRadius The blast radius of the projectile + * @param isCrits Whether or not to use the crit damage type + * @param attackWaiters Whether or not the BLU team should be hurt + * @return Projectile entity + */ + public static native SF2_Projectile_SentryRocket Create( + const CBaseEntity owner, + const float pos[3], + const float ang[3], + const float speed, + const float damage, + const float blastRadius, + const bool isCrits, + const bool attackWaiters = false); + + /** + * Attempts to cast an entity to be this entity. + * + * @param entity Entity to use + */ + public SF2_Projectile_SentryRocket(int entity) + { + return view_as(entity); + } +} + /** * Returns the distance of 2 vectors without utilizing the square root function and converting the values properly. * diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index a5cb499c..f16394bc 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -2060,7 +2060,7 @@ enum struct SF2BossProfileData char ScareReactionCustom[PLATFORM_MAX_PATH]; bool ScareReplenishSprint; - int ScareReplenishSprintAmount; + float ScareReplenishSprintAmount; float StaticRadius[Difficulty_Max]; float StaticRate[Difficulty_Max]; @@ -2308,7 +2308,7 @@ enum struct SF2BossProfileData this.ScareReactionCustom[0] = '\0'; this.ScareReplenishSprint = false; - this.ScareReplenishSprintAmount = 0; + this.ScareReplenishSprintAmount = 0.0; this.StaticScareAmount = 0.0; diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index a7e0860d..7c7f1950 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -472,7 +472,7 @@ enum struct SF2ChaserBossProfileShockwaveData float Radius[Difficulty_Max]; float Force[Difficulty_Max]; float BatteryDrainPercent[Difficulty_Max]; - int StaminaDrainPercent[Difficulty_Max]; + float StaminaDrainPercent[Difficulty_Max]; ArrayList Effects; ArrayList DamageEffects; @@ -486,7 +486,7 @@ enum struct SF2ChaserBossProfileShockwaveData this.Radius[i] = 200.0; this.Force[i] = 600.0; this.BatteryDrainPercent[i] = 0.0; - this.StaminaDrainPercent[i] = 0; + this.StaminaDrainPercent[i] = 0.0; } this.Effects = null; @@ -500,7 +500,7 @@ enum struct SF2ChaserBossProfileShockwaveData GetProfileDifficultyFloatValues(kv, "radius", this.Radius, this.Radius); GetProfileDifficultyFloatValues(kv, "force", this.Force, this.Force); GetProfileDifficultyFloatValues(kv, "battery_drain", this.BatteryDrainPercent, this.BatteryDrainPercent); - GetProfileDifficultyNumValues(kv, "stamina_drain", this.StaminaDrainPercent, this.StaminaDrainPercent); + GetProfileDifficultyFloatValues(kv, "stamina_drain", this.StaminaDrainPercent, this.StaminaDrainPercent); if (kv.JumpToKey("effects")) { diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 3ab64173..fdcf931a 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -118,7 +118,7 @@ static const char g_PageCollectDuckSounds[][] = }; bool g_ClientInGame[MAXTF2PLAYERS] = { false, ... }; -bool g_ClientInCondition[MAXTF2PLAYERS][TFCond_PowerupModeDominant]; +bool g_ClientInCondition[MAXTF2PLAYERS][TFCond_PowerupModeDominant + view_as(1)]; //Command bool g_PlayerNoPoints[MAXTF2PLAYERS] = { false, ... }; @@ -624,6 +624,7 @@ GlobalForward g_OnBossPreAttackFwd; GlobalForward g_OnBossAttackedFwd; GlobalForward g_OnBossPreTakeDamageFwd; GlobalForward g_OnBossPreFlashlightDamageFwd; +GlobalForward g_OnBossAnimationUpdateFwd; GlobalForward g_OnChaserBossGetSuspendActionFwd; GlobalForward g_OnPagesSpawnedFwd; GlobalForward g_OnRoundStateChangeFwd; @@ -3186,9 +3187,7 @@ static void GiveRandomPageReward(int player) case 15, 16: { EmitSoundToClient(player, LOSE_SPRINT_ROLL, player, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); - ClientStopSprint(player); - ClientSetSprintPoints(player, 0); - ClientStopSprint(player); + SF2_Player(player).Stamina = 0.0; } case 17: { @@ -4105,16 +4104,6 @@ bool IsRoundEnding() return GetRoundState() == SF2RoundState_Outro; } -bool IsInfiniteBlinkEnabled() -{ - return g_RoundInfiniteBlink || (g_PlayerInfiniteBlinkOverrideConVar.IntValue == 1); -} - -bool IsInfiniteSprintEnabled() -{ - return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1); -} - bool IsClientParticipating(int client) { if (!IsValidClient(client)) diff --git a/addons/sourcemod/scripting/sf2/classconfigs.sp b/addons/sourcemod/scripting/sf2/classconfigs.sp index 4702f847..b2a8b1dd 100644 --- a/addons/sourcemod/scripting/sf2/classconfigs.sp +++ b/addons/sourcemod/scripting/sf2/classconfigs.sp @@ -10,7 +10,7 @@ float g_ClassWalkSpeed[MAX_CLASSES + 1]; float g_ClassDangerSpeedMultipler[MAX_CLASSES + 1]; float g_ClassSprintDurationMultipler[MAX_CLASSES + 1]; float g_ClassScareSprintDurationMultipler[MAX_CLASSES + 1]; -int g_ClassSprintPointLossJumping[MAX_CLASSES + 1]; +float g_ClassSprintPointLossJumping[MAX_CLASSES + 1]; float g_ClassProxyDamageVulnerability[MAX_CLASSES + 1]; bool g_ClassCanPickUpHealth[MAX_CLASSES + 1]; @@ -242,7 +242,7 @@ static void PrecacheClassProfiles() g_ClassDangerSpeedMultipler[i] = GetClassStatFloat(className, "danger_speed_multiplier", 1.34); g_ClassSprintDurationMultipler[i] = GetClassStatFloat(className, "sprint_multiplier", 1.0); g_ClassScareSprintDurationMultipler[i] = GetClassStatFloat(className, "scare_sprint_multiplier", 1.0); - g_ClassSprintPointLossJumping[i] = GetClassStatNum(className, "sprint_loss_while_jumping", 7); + g_ClassSprintPointLossJumping[i] = GetClassStatFloat(className, "sprint_loss_while_jumping", 0.07); g_ClassProxyDamageVulnerability[i] = GetClassStatFloat(className, "proxy_damage_vulnerability"); g_ClassCanPickUpHealth[i] = GetClassStatNum(className, "can_pickup_health", 1) != 0; diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index 3daaa595..0974d60d 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -107,6 +107,11 @@ Action Hook_HealthKitOnTouch(int healthKit, int client) { if (IsValidClient(client)) { + if (IsClientInPvE(client) || IsClientInPvP(client)) + { + return Plugin_Continue; + } + TFClassType class = TF2_GetPlayerClass(client); int classToInt = view_as(class); if (!SF_IsBoxingMap()) @@ -126,6 +131,7 @@ Action Hook_HealthKitOnTouch(int healthKit, int client) } } } + if (IsClientInGhostMode(client)) { return Plugin_Handled; @@ -584,8 +590,7 @@ void ClientProcessVisibility(int client) if (NPCGetScareReplenishSprintState(master)) { - int clientSprintPoints = ClientGetSprintPoints(client); - ClientSetSprintPoints(client, clientSprintPoints + NPCGetScareReplenishSprintAmount(master)); + SF2_BasePlayer(client).Stamina += NPCGetScareReplenishSprintAmount(master); } float value = NPCGetAttributeValue(master, SF2Attribute_IgnitePlayerOnScare); @@ -593,16 +598,19 @@ void ClientProcessVisibility(int client) { TF2_IgnitePlayer(client, client); } + value = NPCGetAttributeValue(master, SF2Attribute_MarkPlayerForDeathOnScare); if (value > 0.0) { TF2_AddCondition(client, TFCond_MarkedForDeath, value); } + value = NPCGetAttributeValue(master, SF2Attribute_SilentMarkPlayerForDeathOnScare); if (value > 0.0) { TF2_AddCondition(client, TFCond_MarkedForDeathSilent, value); } + if (NPCHasAttribute(master, SF2Attribute_ChaseTargetOnScare)) { SF2_ChaserEntity chaser = SF2_ChaserEntity(boss); @@ -611,6 +619,7 @@ void ClientProcessVisibility(int client) SF2_BasePlayer(client).SetForceChaseState(SF2NPC_BaseNPC(i), true); } } + if (NPCGetJumpscareOnScare(master)) { float jumpScareDuration = NPCGetJumpscareDuration(master, difficulty); diff --git a/addons/sourcemod/scripting/sf2/client/breathing.sp b/addons/sourcemod/scripting/sf2/client/breathing.sp index 63fdbe3f..d3e6dce3 100644 --- a/addons/sourcemod/scripting/sf2/client/breathing.sp +++ b/addons/sourcemod/scripting/sf2/client/breathing.sp @@ -56,7 +56,7 @@ static float ClientCalculateBreathingCooldown(int client) int averageNum = 0; // Sprinting only, for now. - average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow((float(ClientGetSprintPoints(client)) / 100.0), 1.65)); + average += (SF2_PLAYER_BREATH_COOLDOWN_MAX * 6.7765 * Pow(SF2_BasePlayer(client).Stamina, 1.65)); averageNum++; average /= float(averageNum); diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp index 1a9526b8..b56bc450 100644 --- a/addons/sourcemod/scripting/sf2/client/sprint.sp +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -1,8 +1,9 @@ #pragma semicolon 1 static bool g_PlayerSprint[MAXTF2PLAYERS] = { false, ... }; -static int g_PlayerSprintPoints[MAXTF2PLAYERS] = { 100, ... }; -static Handle g_PlayerSprintTimer[MAXTF2PLAYERS] = { null, ... }; +static bool g_WantsToSprint[MAXTF2PLAYERS] = { false, ... }; +static float g_Stamina[MAXTF2PLAYERS] = { 1.0, ... }; +static float g_StaminaRechargeTime[MAXTF2PLAYERS] = { 1.0, ... }; static ConVar g_PlayerScareSprintBoost; @@ -19,8 +20,9 @@ void SetupSprint() static void OnPutInServer(SF2_BasePlayer client) { - ClientResetSprint(client.index); + ClientResetSprint(client); + SDKHook(client.index, SDKHook_PreThink, Hook_SpeedThink); SDKHook(client.index, SDKHook_PreThink, Hook_SprintThink); } @@ -31,62 +33,168 @@ static void OnJump(SF2_BasePlayer client) return; } - int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; - if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0) + if (!IsInfiniteSprintEnabled() && client.Stamina > 0.0) { - if (client.GetSprintPoints() >= 2) + TFClassType classType = client.Class; + int classToInt = view_as(classType); + if (!IsClassConfigsValid()) { - TFClassType classType = client.Class; - int classToInt = view_as(classType); - if (!IsClassConfigsValid()) + if (classType != TFClass_Soldier || client.Stamina <= 0.1 || client.IsSprinting) { - if (classType != TFClass_Soldier || client.GetSprintPoints() <= 10 || client.IsSprinting) - { - client.SetSprintPoints(client.GetSprintPoints() - 7); - } + client.SetStaminaRechargeTime(GetGameTime() + 1.5); + + client.Stamina -= 0.07; } - else + } + else + { + float sprintPointsLoss = g_ClassSprintPointLossJumping[classToInt]; + if (client.Stamina <= 0.1 || client.IsSprinting) { - int sprintPointsLoss = g_ClassSprintPointLossJumping[classToInt]; - if (client.GetSprintPoints() <= 10 || client.IsSprinting) - { - sprintPointsLoss = 7; - } - client.SetSprintPoints(client.GetSprintPoints() - sprintPointsLoss); + sprintPointsLoss = 0.07; } - if (client.GetSprintPoints() <= 0) + + if (sprintPointsLoss > 0.0) { - client.SetSprintPoints(0); + client.SetStaminaRechargeTime(GetGameTime() + 1.5); } - } - } - if (!client.IsSprinting) - { - if (g_PlayerSprintTimer[client.index] == null) - { - // If the player hasn't sprinted recently, force us to regenerate the stamina. - client.SetSprintTimer(true); + client.Stamina -= sprintPointsLoss; } } } static void OnPlayerSpawn(SF2_BasePlayer client) { - ClientResetSprint(client.index); + ClientResetSprint(client); + + if (client.IsAlive && !client.IsEliminated && !client.HasEscaped) + { + if (SF_IsBoxingMap() || SF_IsRaidMap()) + { + g_WantsToSprint[client.index] = true; + } + } } static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { if (!fake) { - ClientResetSprint(client.index); + ClientResetSprint(client); } } static void OnPlayerEscape(SF2_BasePlayer client) { - ClientResetSprint(client.index); + ClientResetSprint(client); +} + +static void Hook_SprintThink(int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsAlive || player.IsEliminated || player.HasEscaped || player.IsInGhostMode) + { + return; + } + + bool wasSprinting = g_PlayerSprint[client]; + bool isSprinting = false; + + if (g_WantsToSprint[player.index] && !player.IsInDeathCam) + { + float velocity[3]; + player.GetAbsVelocity(velocity); + if (GetVectorLength(velocity, true) > Pow(30.0, 2.0)) + { + isSprinting = true; + } + + int fov = GetEntData(client, g_PlayerDefaultFOVOffset); + + int targetFov = g_PlayerDesiredFOV[client] + 10; + + if (fov < targetFov) + { + int diff = RoundFloat(FloatAbs(float(fov - targetFov))); + if (diff >= 1) + { + ClientSetFOV(client, fov + 1); + } + else + { + ClientSetFOV(client, targetFov); + } + } + else if (fov >= targetFov) + { + ClientSetFOV(client, targetFov); + } + } + else + { + int fov = GetEntData(client, g_PlayerDefaultFOVOffset); + if (fov > g_PlayerDesiredFOV[client]) + { + int diff = RoundFloat(FloatAbs(float(fov - g_PlayerDesiredFOV[client]))); + if (diff >= 1) + { + ClientSetFOV(client, fov - 1); + } + else + { + ClientSetFOV(client, g_PlayerDesiredFOV[client]); + } + } + else if (fov <= g_PlayerDesiredFOV[client]) + { + ClientSetFOV(client, g_PlayerDesiredFOV[client]); + } + } + + g_PlayerSprint[player.index] = isSprinting; + + if (wasSprinting != isSprinting) + { + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S)) + { + if (isSprinting) + { + Client90sMusicStart(client); + + Call_StartForward(g_OnClientStartSprintingFwd); + Call_PushCell(client); + Call_Finish(); + } + else + { + Client90sMusicStop(client); + + Call_StartForward(g_OnClientStopSprintingFwd); + Call_PushCell(client); + Call_Finish(); + } + } + } + + if (isSprinting) + { + if (!IsInfiniteSprintEnabled()) + { + player.SetStaminaRechargeTime(GetGameTime() + 1.5); + + player.Stamina -= GetStaminaDecreaseRate(player) * GetTickInterval(); + if (player.Stamina <= 0.0) + { + g_WantsToSprint[client] = false; + } + } + } + + if (player.Stamina < 1.0 && GetGameTime() > g_StaminaRechargeTime[player.index]) + { + player.Stamina += GetStaminaRechargeRate(player) * GetTickInterval(); + } } float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknown) @@ -161,171 +269,131 @@ bool IsClientSprinting(int client) return g_PlayerSprint[client]; } -int ClientGetSprintPoints(int client) +float ClientGetStamina(int client) { - return g_PlayerSprintPoints[client]; + return g_Stamina[client]; } -void ClientSetSprintPoints(int client, int value) +void ClientSetStamina(int client, float value) { - g_PlayerSprintPoints[client] = value; - if (g_PlayerSprintPoints[client] > 100) + g_Stamina[client] = value; + if (g_Stamina[client] > 1.0) { - g_PlayerSprintPoints[client] = 100; + g_Stamina[client] = 1.0; } - if (g_PlayerSprintPoints[client] < 0) + if (g_Stamina[client] < 0.0) { - g_PlayerSprintPoints[client] = 0; + g_Stamina[client] = 0.0; } } -void ClientResetSprint(int client) +void SetPlayerStaminaRechargeTime(SF2_BasePlayer client, float time, bool checkTime = true) { - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientResetSprint(%d)", client); - } - #endif - - bool wasSprinting = IsClientSprinting(client); - - g_PlayerSprint[client] = false; - g_PlayerSprintPoints[client] = 100; - g_PlayerSprintTimer[client] = null; - - if (IsValidClient(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } - - if (wasSprinting) + if (checkTime && g_StaminaRechargeTime[client.index] >= time) { - Call_StartForward(g_OnClientStopSprintingFwd); - Call_PushCell(client); - Call_Finish(); + return; } - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientResetSprint(%d)", client); - } - #endif + g_StaminaRechargeTime[client.index] = time; } -void ClientStartSprint(int client) +float GetStaminaDecreaseRate(SF2_BasePlayer client) { - if (IsClientSprinting(client)) - { - return; - } + float rate = 0.03; - g_PlayerSprint[client] = true; - g_PlayerSprintTimer[client] = null; - ClientSprintTimer(client); - TriggerTimer(g_PlayerSprintTimer[client], true); - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) + if (SF_SpecialRound(SPECIALROUND_COFFEE)) { - Client90sMusicStart(client); + rate *= 0.5; } - SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - - Call_StartForward(g_OnClientStartSprintingFwd); - Call_PushCell(client); - Call_Finish(); -} - -void ClientSprintTimer(int client, bool recharge = false) -{ - float rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 0.38 : 0.28; - if (recharge) + if (SF_IsProxyMap()) { - rate = (SF_SpecialRound(SPECIALROUND_COFFEE)) ? 1.4 : 0.8; + rate *= 0.25; } - TFClassType class = TF2_GetPlayerClass(client); + TFClassType class = client.Class; int classToInt = view_as(class); - - float velocity[3]; - GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); - - if (recharge) + if (!IsClassConfigsValid()) { - if (!(GetEntityFlags(client) & FL_ONGROUND)) + if (class == TFClass_DemoMan) { - rate *= 0.75; + rate *= 0.9; // Demoman gets a 10% sprint duration increase } - else if (GetVectorLength(velocity, true) == 0.0) + else if (class == TFClass_Scout) { - if (GetEntProp(client, Prop_Send, "m_bDucked")) - { - rate *= 0.66; - } - else - { - rate *= 0.75; - } + rate *= 0.95; // Scout gets a 5% sprint duration increase } } else { - if (!IsClassConfigsValid()) - { - if (class == TFClass_DemoMan) - { - rate *= 1.1; - } - else if (class == TFClass_Medic || class == TFClass_Spy) - { - rate *= 1.0; - } - else if (class == TFClass_Scout) - { - rate *= 1.05; - } - } - else + rate *= g_ClassSprintDurationMultipler[classToInt]; + } + + return rate; +} + +float GetStaminaRechargeRate(SF2_BasePlayer client) +{ + float rate = 0.02; + + if (client.Ducked) + { + float velocity[3]; + client.GetAbsVelocity(velocity); + if (GetVectorLength(velocity, true) == 0.0) { - rate *= g_ClassSprintDurationMultipler[classToInt]; + // Double the sprint rate if the player is crouching and holding still + rate = 0.04; } } - if (recharge) + if (SF_SpecialRound(SPECIALROUND_COFFEE)) { - g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientRechargeSprint, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + rate *= 0.35; } - else + + if (SF_IsProxyMap()) { - g_PlayerSprintTimer[client] = CreateTimer(rate, Timer_ClientSprinting, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + rate *= 0.25; } + + return rate; } -void ClientStopSprint(int client) +static void ClientResetSprint(SF2_BasePlayer client) { - if (!IsClientSprinting(client)) + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) { - return; + DebugMessage("START ClientResetSprint(%d)", client.index); } - g_PlayerSprint[client] = false; - g_PlayerSprintTimer[client] = null; - ClientSprintTimer(client, true); - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) + #endif + + bool wasSprinting = client.IsSprinting; + + client.SetStaminaRechargeTime(0.0, false); + g_PlayerSprint[client.index] = false; + g_WantsToSprint[client.index] = false; + client.Stamina = 1.0; + + if (client.IsValid) { - Client90sMusicStop(client); + ClientSetFOV(client.index, g_PlayerDesiredFOV[client.index]); } - SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); + if (wasSprinting) + { + Call_StartForward(g_OnClientStopSprintingFwd); + Call_PushCell(client.index); + Call_Finish(); + } - Call_StartForward(g_OnClientStopSprintingFwd); - Call_PushCell(client); - Call_Finish(); + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 2) + { + DebugMessage("END ClientResetSprint(%d)", client.index); + } + #endif } /** @@ -333,6 +401,11 @@ void ClientStopSprint(int client) */ void ClientHandleSprint(int client, bool sprint) { + if (IsRoundInWarmup() || IsRoundInIntro() || IsRoundEnding()) + { + return; + } + if (!IsPlayerAlive(client) || g_PlayerEliminated[client] || DidClientEscape(client) || @@ -342,11 +415,16 @@ void ClientHandleSprint(int client, bool sprint) return; } + if (SF_IsRaidMap() || SF_IsBoxingMap()) // On these maps we always sprint + { + return; + } + if (sprint) { - if (g_PlayerSprintPoints[client] > 0) + if (g_Stamina[client] > 0) { - ClientStartSprint(client); + g_WantsToSprint[client] = true; } else { @@ -355,10 +433,7 @@ void ClientHandleSprint(int client, bool sprint) } else { - if (IsClientSprinting(client)) - { - ClientStopSprint(client); - } + g_WantsToSprint[client] = false; } } @@ -383,153 +458,7 @@ bool IsClientReallySprinting(int client) return true; } -static Action Timer_ClientSprinting(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - if (timer != g_PlayerSprintTimer[client]) - { - return Plugin_Stop; - } - - if (!IsClientSprinting(client)) - { - return Plugin_Stop; - } - - if (g_PlayerSprintPoints[client] <= 0) - { - ClientStopSprint(client); - g_PlayerSprintPoints[client] = 0; - return Plugin_Stop; - } - - if (IsClientReallySprinting(client)) - { - int override = g_PlayerInfiniteSprintOverrideConVar.IntValue; - if ((!g_IsRoundInfiniteSprint && override != 1) || override == 0) - { - g_PlayerSprintPoints[client]--; - } - } - - ClientSprintTimer(client); - - return Plugin_Stop; -} - -static Action Timer_ClientRechargeSprint(Handle timer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client <= 0) - { - return Plugin_Stop; - } - - float velSpeed[3]; - GetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", velSpeed); - float speed = GetVectorLength(velSpeed, true); - - if (timer != g_PlayerSprintTimer[client]) - { - return Plugin_Stop; - } - - if (IsClientSprinting(client)) - { - g_PlayerSprintTimer[client] = null; - return Plugin_Stop; - } - - if (g_PlayerSprintPoints[client] >= 100) - { - g_PlayerSprintPoints[client] = 100; - g_PlayerSprintTimer[client] = null; - return Plugin_Stop; - } - if ((!GetEntProp(client, Prop_Send, "m_bDucking") && !GetEntProp(client, Prop_Send, "m_bDucked")) || (GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked") && IsClientReallySprinting(client) || speed > 0.0)) - { - g_PlayerSprintPoints[client]++; - } - else if ((GetEntProp(client, Prop_Send, "m_bDucking") || GetEntProp(client, Prop_Send, "m_bDucked")) && !IsClientReallySprinting(client) && speed == 0.0) - { - if (!SF_SpecialRound(SPECIALROUND_COFFEE)) - { - g_PlayerSprintPoints[client] += 2; - } - else - { - g_PlayerSprintPoints[client] += 1; - } - } - ClientSprintTimer(client, true); - return Plugin_Stop; -} - -static void Hook_ClientSprintingPreThink(int client) -{ - if (!IsClientSprinting(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - SDKHook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - return; - } - - int fov = GetEntData(client, g_PlayerDefaultFOVOffset); - - int targetFov = g_PlayerDesiredFOV[client] + 10; - - if (fov < targetFov) - { - int diff = RoundFloat(FloatAbs(float(fov - targetFov))); - if (diff >= 1) - { - ClientSetFOV(client, fov + 1); - } - else - { - ClientSetFOV(client, targetFov); - } - } - else if (fov >= targetFov) - { - ClientSetFOV(client, targetFov); - } -} - -static void Hook_ClientRechargeSprintPreThink(int client) -{ - if (IsClientReallySprinting(client)) - { - SDKUnhook(client, SDKHook_PreThink, Hook_ClientRechargeSprintPreThink); - SDKHook(client, SDKHook_PreThink, Hook_ClientSprintingPreThink); - return; - } - - int fov = GetEntData(client, g_PlayerDefaultFOVOffset); - if (fov > g_PlayerDesiredFOV[client]) - { - int diff = RoundFloat(FloatAbs(float(fov - g_PlayerDesiredFOV[client]))); - if (diff >= 1) - { - ClientSetFOV(client, fov - 1); - } - else - { - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } - } - else if (fov <= g_PlayerDesiredFOV[client]) - { - ClientSetFOV(client, g_PlayerDesiredFOV[client]); - } -} - -static void Hook_SprintThink(int client) +static void Hook_SpeedThink(int client) { if (!g_Enabled) { @@ -825,7 +754,7 @@ static void Hook_SprintThink(int client) } } - if (player.GetSprintPoints() <= 5) + if (player.Stamina <= 0.04) { float sprintSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.425); float walkSpeedSubtract = ((sprintSpeed - walkSpeed) * 0.3); diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index 7218948a..cb277299 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -910,7 +910,7 @@ Action Timer_ClientAverageUpdate(Handle timer) } } - bars = RoundToCeil(float(maxBars) * (float(player.GetSprintPoints()) / 100.0)); + bars = RoundToCeil(float(maxBars) * player.Stamina); if (bars > maxBars) { bars = maxBars; diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index 504b64a2..5149e718 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -31,7 +31,7 @@ static void EntityDestroyed(CBaseEntity ent, const char[] classname) } } -void SlenderSpawnEffects(ArrayList effects, int bossIndex, bool nonEffects = true, float overridePos[3] = NULL_VECTOR, float overrideAng[3] = NULL_VECTOR, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) +void SlenderSpawnEffects(ArrayList effects, int bossIndex, bool nonEffects = true, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) { if (bossIndex < 0 || bossIndex >= MAX_BOSSES) { @@ -226,7 +226,7 @@ static Action Timer_SpawnEffect(Handle timer, DataPack pack) return Plugin_Stop; } -static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, float overridePos[3] = NULL_VECTOR, float overrideAng[3] = NULL_VECTOR, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) +static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) { int slenderEnt = NPCGetEntIndex(bossIndex); int attacher = slenderEnt; @@ -236,7 +236,7 @@ static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, } int difficulty = GetLocalGlobalDifficulty(bossIndex); float basePos[3], baseAng[3]; - if (!IsNullVector(overridePos)) + if (!IsEmptyVector(overridePos)) { basePos = overridePos; } @@ -244,7 +244,7 @@ static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, { GetEntPropVector(attacher, Prop_Data, "m_vecAbsOrigin", basePos); } - if (!IsNullVector(overrideAng)) + if (!IsEmptyVector(overrideAng)) { baseAng = overrideAng; } @@ -542,7 +542,7 @@ static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, } TE_SetupBeamRingPoint(effectPos, effectsInfo.BeamRingStartRadius, effectsInfo.BeamRingEndRadius, effectsInfo.BeamRingBeamModel, effectsInfo.BeamRingHaloModel, effectsInfo.BeamRingStartFrame, effectsInfo.BeamRingFrameRate, effectsInfo.LifeTime, effectsInfo.BeamRingWidth, effectsInfo.BeamRingAmplitude, color, effectsInfo.BeamRingSpeed, effectsInfo.BeamRingFlags); - TE_SendToAll(effectsInfo.Delay); + TE_SendToAll(); } case EffectType_TempEntParticle: { @@ -573,7 +573,7 @@ static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, ang = effectAng; } TE_Particle(particle, pos, start, ang, attacher, effectsInfo.TEParticleAttachType, attachment, effectsInfo.TEParticleReset, effectsInfo.TEParticleHasControlPoint, effectsInfo.TEParticleControlPointAttachType, effectsInfo.TEParticleControlPointOffset); - TE_SendToAll(effectsInfo.Delay); + TE_SendToAll(); } case EffectType_Sound: { diff --git a/addons/sourcemod/scripting/sf2/entities/initialize.sp b/addons/sourcemod/scripting/sf2/entities/initialize.sp index 684eff08..87834773 100644 --- a/addons/sourcemod/scripting/sf2/entities/initialize.sp +++ b/addons/sourcemod/scripting/sf2/entities/initialize.sp @@ -11,4 +11,17 @@ void InitializeCustomEntities() SF2_ProjectileArrow.Initialize(); SF2_ProjectileGrenade.Initialize(); SF2_ProjectileBaseball.Initialize(); +} + +void SetupCustomEntitiesAPI() +{ + SF2_ProjectileBase.SetupAPI(); + SF2_ProjectileRocket.SetupAPI(); + SF2_ProjectileFireball.SetupAPI(); + SF2_ProjectileIceball.SetupAPI(); + SF2_ProjectileSentryRocket.SetupAPI(); + SF2_ProjectileCowMangler.SetupAPI(); + SF2_ProjectileArrow.SetupAPI(); + SF2_ProjectileGrenade.SetupAPI(); + SF2_ProjectileBaseball.SetupAPI(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp index 2b7adbac..7b9840e2 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp @@ -31,6 +31,11 @@ methodmap SF2_ProjectileArrow < SF2_ProjectileBase g_Factory.Install(); } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Arrow.Create", Native_Create); + } + property bool Touched { public get() @@ -102,7 +107,12 @@ static void StartTouch(int entity, int other) SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); if (otherPlayer.IsValid) { - if (otherPlayer.IsInGhostMode || (otherPlayer.IsProxy && !projectile.AttackWaiters)) + if (otherPlayer.IsInGhostMode) + { + return; + } + + if (otherPlayer.IsEliminated && !projectile.AttackWaiters) { return; } @@ -121,7 +131,9 @@ static void StartTouch(int entity, int other) hit = false; } - if (SF2_ProjectileBase(other).IsValid()) + char class[64]; + GetEntityClassname(other, class, sizeof(class)); + if (StrContains(class, "sf2_projectile", false) != -1) { hit = false; } @@ -162,4 +174,17 @@ static void StartTouch(int entity, int other) EmitSoundToAll(projectile.GetImpactSound(), projectile.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); RemoveEntity(projectile.index); } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char trail[64], impact[64], model[64]; + GetNativeString(7, trail, sizeof(trail)); + GetNativeString(8, impact, sizeof(impact)); + GetNativeString(9, model, sizeof(model)); + SF2_ProjectileArrow projectile = SF2_ProjectileArrow.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), trail, impact, model, GetNativeCell(10)); + return projectile; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp index 309ec7e7..fb49c982 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp @@ -31,6 +31,11 @@ methodmap SF2_ProjectileBaseball < SF2_ProjectileGrenade g_Factory.Install(); } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Baseball.Create", Native_Create); + } + property float TravelTime { public get() @@ -158,3 +163,14 @@ static void Think(int entity) } } } + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char model[64]; + GetNativeString(7, model, sizeof(model)); + SF2_ProjectileBaseball projectile = SF2_ProjectileBaseball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), model, GetNativeCell(8)); + return projectile; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp index a82669f3..4f88c1a3 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp @@ -28,6 +28,11 @@ methodmap SF2_ProjectileCowMangler < SF2_ProjectileRocket g_Factory.Install(); } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_CowMangler.Create", Native_Create); + } + public static SF2_ProjectileCowMangler Create( const CBaseEntity owner, const float pos[3], @@ -48,3 +53,16 @@ methodmap SF2_ProjectileCowMangler < SF2_ProjectileRocket return rocket; } } + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char impact[64], trail[64], freeze[64]; + GetNativeString(7, impact, sizeof(impact)); + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(9, freeze, sizeof(freeze)); + SF2_ProjectileCowMangler projectile = SF2_ProjectileCowMangler.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7)); + return projectile; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp index ac127fd5..b30e7761 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp @@ -29,6 +29,11 @@ methodmap SF2_ProjectileFireball < SF2_ProjectileBase g_OnPlayerDamagedByProjectilePFwd.AddFunction(null, OnPlayerDamagedByProjectile); } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Fireball.Create", Native_Create); + } + public void OnPlayerDamaged(SF2_BasePlayer player) { player.Ignite(true); @@ -64,4 +69,16 @@ static void OnPlayerDamagedByProjectile(SF2_BasePlayer player, SF2_ProjectileBas { player.Ignite(true); } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char impact[64], trail[64]; + GetNativeString(7, impact, sizeof(impact)); + GetNativeString(8, trail, sizeof(trail)); + SF2_ProjectileFireball projectile = SF2_ProjectileFireball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), impact, trail); + return projectile; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp index c65d0c72..6652cada 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -49,6 +49,11 @@ methodmap SF2_ProjectileGrenade < SF2_ProjectileBase return g_Factory; } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Grenade.Create", Native_Create); + } + property CBaseEntity TrailEntity { public get() @@ -264,7 +269,6 @@ methodmap SF2_ProjectileGrenade < SF2_ProjectileBase SetEntityOwner(grenade.index, owner.index); grenade.SetModel(model); grenade.SetProp(Prop_Send, "m_nSkin", 1); - grenade.SetProp(Prop_Send, "m_usSolidFlags", 12); grenade.KeyValue("solid", "2"); grenade.KeyValue("spawnflags", "4"); grenade.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); @@ -361,3 +365,17 @@ bool TraceRayGrenade(int entity, int mask, any data) return true; } + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char trail[64], explosion[64], impact[64], model[64]; + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(9, explosion, sizeof(explosion)); + GetNativeString(10, impact, sizeof(impact)); + GetNativeString(11, model, sizeof(model)); + SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), trail, explosion, impact, model, GetNativeCell(12)); + return projectile; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp index 1da050fa..c36f22fc 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp @@ -8,7 +8,7 @@ methodmap SF2_ProjectileIceball < SF2_ProjectileBase { public SF2_ProjectileIceball(int entIndex) { - return view_as(CBaseAnimating(entIndex)); + return view_as(entIndex); } public bool IsValid() @@ -34,6 +34,11 @@ methodmap SF2_ProjectileIceball < SF2_ProjectileBase g_OnPlayerDamagedByProjectilePFwd.AddFunction(null, OnPlayerDamagedByProjectile); } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Iceball.Create", Native_Create); + } + property float SlowDuration { public get() @@ -115,4 +120,17 @@ static void OnPlayerDamagedByProjectile(SF2_BasePlayer player, SF2_ProjectileBas EmitSoundToClient(player.index, iceball.GetFreezeSound(), _, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); player.Stun(iceball.SlowDuration, iceball.SlowMultiplier, TF_STUNFLAG_SLOWDOWN, player.index); } +} + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char impact[64], trail[64], freeze[64]; + GetNativeString(7, impact, sizeof(impact)); + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(11, freeze, sizeof(freeze)); + SF2_ProjectileIceball projectile = SF2_ProjectileIceball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), impact, trail, GetNativeCell(9), GetNativeCell(10), freeze, GetNativeCell(12)); + return projectile; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp index 631dc0a4..d3a1a799 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp @@ -28,6 +28,11 @@ methodmap SF2_ProjectileRocket < SF2_ProjectileBase g_Factory.Install(); } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Rocket.Create", Native_Create); + } + public static SF2_ProjectileRocket Create( const CBaseEntity owner, const float pos[3], @@ -53,3 +58,17 @@ methodmap SF2_ProjectileRocket < SF2_ProjectileBase return rocket; } } + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + char trail[64], explosion[64], impact[64], model[64]; + GetNativeString(8, trail, sizeof(trail)); + GetNativeString(9, explosion, sizeof(explosion)); + GetNativeString(10, impact, sizeof(impact)); + GetNativeString(11, model, sizeof(model)); + SF2_ProjectileRocket projectile = SF2_ProjectileRocket.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), trail, explosion, impact, model, GetNativeCell(12)); + return projectile; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp index 42a828e3..f08f24d9 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp @@ -28,6 +28,11 @@ methodmap SF2_ProjectileSentryRocket < SF2_ProjectileRocket g_Factory.Install(); } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_SentryRocket.Create", Native_Create); + } + public static SF2_ProjectileSentryRocket Create( const CBaseEntity owner, const float pos[3], @@ -51,3 +56,12 @@ methodmap SF2_ProjectileSentryRocket < SF2_ProjectileRocket return rocket; } } + +static any Native_Create(Handle plugin, int numParams) +{ + float pos[3], ang[3]; + GetNativeArray(2, pos, 3); + GetNativeArray(3, ang, 3); + SF2_ProjectileSentryRocket projectile = SF2_ProjectileSentryRocket.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), GetNativeCell(8)); + return projectile; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp index fbca82d6..150ec20f 100644 --- a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -41,6 +41,12 @@ methodmap SF2_ProjectileBase < CBaseAnimating return g_Factory; } + public static void SetupAPI() + { + CreateNative("SF2_Projectile_Base.Initialize", Native_Initialize); + CreateNative("SF2_Projectile_Base.DoExplosion", Native_DoExplosion); + } + property CBaseEntity TrailEntity { public get() @@ -244,6 +250,8 @@ methodmap SF2_ProjectileBase < CBaseAnimating this.SetVelocity(); SDKHook(this.index, SDKHook_StartTouch, StartTouch); + + CreateTimer(15.0, Timer_KillEntity, EntIndexToEntRef(this.index), TIMER_FLAG_NO_MAPCHANGE); } public void CreateTrail(bool teleport = false, const char[] override = "sprites/laserbeam.vmt", const char[] alpha = "0", const char[] mode = "10") @@ -410,7 +418,7 @@ methodmap SF2_ProjectileBase < CBaseAnimating delete hitList; if (this.Type != SF2BossProjectileType_Mangler) { - EmitSoundToAll(this.GetImpactSound(), this.index, SNDCHAN_ITEM, SNDLEVEL_SCREAMING); + EmitSoundToAll(this.GetImpactSound(), this.index, SNDCHAN_ITEM); } else { @@ -482,7 +490,12 @@ static void StartTouch(int entity, int other) SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); if (otherPlayer.IsValid) { - if (otherPlayer.IsInGhostMode || (otherPlayer.IsProxy && !projectile.AttackWaiters)) + if (otherPlayer.IsInGhostMode) + { + return; + } + + if (otherPlayer.IsEliminated && !projectile.AttackWaiters) { return; } @@ -501,7 +514,9 @@ static void StartTouch(int entity, int other) hit = false; } - if (SF2_ProjectileBase(other).IsValid()) + char class[64]; + GetEntityClassname(other, class, sizeof(class)); + if (StrContains(class, "sf2_projectile", false) != -1) { hit = false; } @@ -517,6 +532,40 @@ static void StartTouch(int entity, int other) } } +static any Native_Initialize(Handle plugin, int numParams) +{ + SF2_ProjectileBase projectile = SF2_ProjectileBase(GetNativeCell(1)); + if (!projectile.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid projectile index %d", projectile.index); + } + + float pos[3], ang[3]; + GetNativeArray(3, pos, 3); + GetNativeArray(4, ang, 3); + char trail[64], explosion[64], impact[64], model[64]; + GetNativeString(9, trail, sizeof(trail)); + GetNativeString(10, explosion, sizeof(explosion)); + GetNativeString(11, impact, sizeof(impact)); + GetNativeString(12, model, sizeof(model)); + projectile.InitializeProjectile(GetNativeCell(1), GetNativeCell(2), pos, ang, GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), GetNativeCell(8), trail, explosion, impact, model, GetNativeCell(13)); + + return 0; +} + +static any Native_DoExplosion(Handle plugin, int numParams) +{ + SF2_ProjectileBase projectile = SF2_ProjectileBase(GetNativeCell(1)); + if (!projectile.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid projectile index %d", projectile.index); + } + + projectile.DoExplosion(); + + return 0; +} + #include "projectiles/sf2_fireball_projectile.sp" #include "projectiles/sf2_iceball_projectile.sp" #include "projectiles/sf2_rocket_projectile.sp" diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index b5a74e63..dbdf256c 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -767,7 +767,7 @@ static Action Command_NoPointsAdmin(int client, int args) int target = target_list[i]; if (IsClientSourceTV(target)) { - continue;//Exclude the sourcetv bot + continue; // Exclude the sourcetv bot } g_AdminNoPoints[client] = args > 1 ? mode : !g_AdminNoPoints[client]; @@ -1243,7 +1243,10 @@ static Action Command_SpawnSlender(int client, int args) char profile[SF2_MAX_PROFILE_NAME_LENGTH]; npc.GetProfile(profile, sizeof(profile)); - CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Spawned Boss", client); + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Spawned Boss", client); + } return Plugin_Handled; } @@ -1398,7 +1401,10 @@ static Action Command_RemoveSlender(int client, int args) NPCRemove(bossIndex); - CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Removed Boss", client); + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Removed Boss", client); + } return Plugin_Handled; } @@ -1428,11 +1434,17 @@ static Action Command_RemoveAllSlenders(int client, int args) } npc.Remove(); } - CPrintToChat(client, "{royalblue}%t {default}Removed all bosses.", "SF2 Prefix", client); + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}Removed all bosses.", "SF2 Prefix", client); + } } else { - CPrintToChat(client, "{royalblue}%t {default}Cannot use this command in Boxing maps.", "SF2 Prefix", client); + if (IsValidClient(client)) + { + CPrintToChat(client, "{royalblue}%t {default}Cannot use this command in Boxing maps.", "SF2 Prefix", client); + } } if (MusicActive()) @@ -1676,21 +1688,10 @@ static Action Command_DebugLogicEscape(int client, int args) while ((ent = FindEntityByClassname(ent, "info_target")) != -1) { GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); - if (!SF_IsBoxingMap()) - { - if (strcmp(name, "sf2_logic_escape", false) == 0) - { - AcceptEntityInput(ent, "FireUser1"); - break; - } - } - else + if (strcmp(name, "sf2_logic_escape", false) == 0) { - if (strcmp(name, "sf2_logic_escape", false) == 0) - { - AcceptEntityInput(ent, "FireUser1"); - break; - } + AcceptEntityInput(ent, "FireUser1"); + break; } } CPrintToChat(client, "{royalblue}%t {default}Triggered sf2_logic_escape.", "SF2 Prefix", client); diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 3db11dc4..ab414fc7 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -26,6 +26,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnBossAttackedFwd = new GlobalForward("SF2_OnBossAttacked", ET_Ignore, Param_Cell, Param_Cell); g_OnBossPreTakeDamageFwd = new GlobalForward("SF2_OnBossPreTakeDamage", ET_Hook, Param_Cell, Param_Cell, Param_Cell, Param_Cell); g_OnBossPreFlashlightDamageFwd = new GlobalForward("SF2_OnBossPreFlashlightDamage", ET_Hook, Param_Cell, Param_Cell); + g_OnBossAnimationUpdateFwd = new GlobalForward("SF2_OnBossAnimationUpdate", ET_Hook, Param_Cell, Param_String); g_OnChaserBossGetSuspendActionFwd = new GlobalForward("SF2_OnChaserBossGetSuspendAction", ET_Hook, Param_Cell, Param_CellByRef); g_OnPagesSpawnedFwd = new GlobalForward("SF2_OnPagesSpawned", ET_Ignore); g_OnRoundStateChangeFwd = new GlobalForward("SF2_OnRoundStateChange", ET_Ignore, Param_Cell, Param_Cell); @@ -102,8 +103,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("SF2_SetClientFlashlightBatteryLife", Native_SetClientFlashlightBatteryLife); CreateNative("SF2_IsClientUsingFlashlight", Native_IsClientUsingFlashlight); - CreateNative("SF2_GetClientSprintPoints", Native_GetClientSprintPoints); - CreateNative("SF2_SetClientSprintPoints", Native_SetClientSprintPoints); + CreateNative("SF2_GetClientStaminaPoints", Native_GetClientSprintPoints); + CreateNative("SF2_SetClientStaminaPoints", Native_SetClientSprintPoints); CreateNative("SF2_IsClientSprinting", Native_IsClientSprinting); CreateNative("SF2_IsClientReallySprinting", Native_IsClientReallySprinting); CreateNative("SF2_SetClientSprintState", Native_SetClientSprintState); @@ -154,6 +155,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max SetupMethodmapAPI(); + SetupCustomEntitiesAPI(); + SetupBossProfileNatives(); #if defined _steamtools_included @@ -582,12 +585,12 @@ static any Native_IsClientUsingFlashlight(Handle plugin, int numParams) static any Native_GetClientSprintPoints(Handle plugin, int numParams) { - return ClientGetSprintPoints(GetNativeCell(1)); + return SF2_BasePlayer(GetNativeCell(1)).Stamina; } static any Native_SetClientSprintPoints(Handle plugin, int numParams) { - ClientSetSprintPoints(GetNativeCell(1), GetNativeCell(2)); + SF2_BasePlayer(GetNativeCell(1)).Stamina = GetNativeCell(2); return 0; } diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index 337a9b75..dd7a340e 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -908,19 +908,22 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter ClientHandleSprint(this.index, sprint); } - public void SetSprintTimer(bool recharge = false) + property float Stamina { - ClientSprintTimer(this.index, recharge); - } + public get() + { + return ClientGetStamina(this.index); + } - public int GetSprintPoints() - { - return ClientGetSprintPoints(this.index); + public set(float value) + { + ClientSetStamina(this.index, value); + } } - public void SetSprintPoints(int value) + public void SetStaminaRechargeTime(float time, bool checkTime = true) { - ClientSetSprintPoints(this.index, value); + SetPlayerStaminaRechargeTime(this, time, checkTime); } property bool HasStartedBlinking @@ -1269,9 +1272,9 @@ void SetupMethodmapAPI() CreateNative("SF2_Player.IsSprinting.get", Native_GetClientIsSprinting); CreateNative("SF2_Player.IsReallySprinting.get", Native_GetClientIsReallySprinting); CreateNative("SF2_Player.HandleSprint", Native_ClientHandleSprint); - CreateNative("SF2_Player.SetSprintTimer", Native_SetClientSprintTimer); - CreateNative("SF2_Player.GetSprintPoints", Native_GetClientSprintPoints); - CreateNative("SF2_Player.SetSprintPoints", Native_SetClientSprintPoints); + CreateNative("SF2_Player.Stamina.get", Native_GetClientSprintPoints); + CreateNative("SF2_Player.Stamina.set", Native_SetClientSprintPoints); + CreateNative("SF2_Player.SetStaminaRechargeTime", Native_SetClientStaminaRechargeTime); CreateNative("SF2_Player.HasStartedBlinking.get", Native_GetClientHasStartedBlinking); CreateNative("SF2_Player.IsBlinking.get", Native_GetClientIsBlinking); CreateNative("SF2_Player.IsHoldingBlink", Native_GetClientIsHoldingBlink); @@ -2229,7 +2232,7 @@ static any Native_ClientHandleSprint(Handle plugin, int numParams) return 0; } -static any Native_SetClientSprintTimer(Handle plugin, int numParams) +static any Native_GetClientSprintPoints(Handle plugin, int numParams) { int client = GetNativeCell(1); if (!IsValidClient(client)) @@ -2238,11 +2241,10 @@ static any Native_SetClientSprintTimer(Handle plugin, int numParams) } SF2_BasePlayer player = SF2_BasePlayer(client); - player.SetSprintTimer(GetNativeCell(2)); - return 0; + return player.Stamina; } -static any Native_GetClientSprintPoints(Handle plugin, int numParams) +static any Native_SetClientSprintPoints(Handle plugin, int numParams) { int client = GetNativeCell(1); if (!IsValidClient(client)) @@ -2251,10 +2253,11 @@ static any Native_GetClientSprintPoints(Handle plugin, int numParams) } SF2_BasePlayer player = SF2_BasePlayer(client); - return player.GetSprintPoints(); + player.Stamina = GetNativeCell(2); + return 0; } -static any Native_SetClientSprintPoints(Handle plugin, int numParams) +static any Native_SetClientStaminaRechargeTime(Handle plugin, int numParams) { int client = GetNativeCell(1); if (!IsValidClient(client)) @@ -2263,7 +2266,7 @@ static any Native_SetClientSprintPoints(Handle plugin, int numParams) } SF2_BasePlayer player = SF2_BasePlayer(client); - player.SetSprintPoints(GetNativeCell(2)); + player.SetStaminaRechargeTime(GetNativeCell(2), GetNativeCell(3)); return 0; } diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 1db1f9b2..b6b04ff0 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -79,7 +79,7 @@ static bool g_NpcHasPlayerScareReaction[MAX_BOSSES]; static int g_NpcPlayerScareReactionType[MAX_BOSSES]; static bool g_NpcHasPlayerScareReplenishSprint[MAX_BOSSES]; -static int g_NpcPlayerScareReplenishSprintAmount[MAX_BOSSES]; +static float g_NpcPlayerScareReplenishSprintAmount[MAX_BOSSES]; static int g_NpcTeleportType[MAX_BOSSES] = { -1, ... }; @@ -1458,7 +1458,7 @@ bool NPCGetScareReplenishSprintState(int npcIndex) return g_NpcHasPlayerScareReplenishSprint[npcIndex]; } -int NPCGetScareReplenishSprintAmount(int npcIndex) +float NPCGetScareReplenishSprintAmount(int npcIndex) { return g_NpcPlayerScareReplenishSprintAmount[npcIndex]; } @@ -2723,7 +2723,7 @@ void RemoveProfile(int bossIndex) g_NpcHasPlayerScareReaction[bossIndex] = false; g_NpcPlayerScareReactionType[bossIndex] = 0; g_NpcHasPlayerScareReplenishSprint[bossIndex] = false; - g_NpcPlayerScareReplenishSprintAmount[bossIndex] = 0; + g_NpcPlayerScareReplenishSprintAmount[bossIndex] = 0.0; g_SlenderRenderFX[bossIndex] = 0; g_SlenderRenderMode[bossIndex] = 0; @@ -3683,6 +3683,8 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) return Plugin_Continue; } + float gameTime = GetGameTime(); + int difficulty = GetLocalGlobalDifficulty(bossIndex); // Check to see if anyone's looking at me before doing anything. @@ -3716,23 +3718,23 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) int state = chaser.State; if (state == STATE_IDLE && chaser.IsAllowedToDespawn) { - if (GetGameTime() < g_SlenderTimeUntilKill[bossIndex]) + if (gameTime < g_SlenderTimeUntilKill[bossIndex]) { return Plugin_Continue; } } else { - if (GetGameTime() >= g_SlenderTimeUntilKill[bossIndex]) + if (gameTime >= g_SlenderTimeUntilKill[bossIndex]) { - g_SlenderTimeUntilKill[bossIndex] = GetGameTime() + NPCGetIdleLifetime(bossIndex, difficulty); + g_SlenderTimeUntilKill[bossIndex] = gameTime + NPCGetIdleLifetime(bossIndex, difficulty); } return Plugin_Continue; } } else if (NPCGetType(bossIndex) == SF2BossType_Statue) { - if (g_SlenderStatueIdleLifeTime[bossIndex] > GetGameTime()) + if (g_SlenderStatueIdleLifeTime[bossIndex] > gameTime) { return Plugin_Continue; } @@ -3745,7 +3747,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) if (IsRoundPlaying()) { - if (GetGameTime() >= g_SlenderNextTeleportTime[bossIndex]) + if (gameTime >= g_SlenderNextTeleportTime[bossIndex]) { if (!NPCIsTeleportAllowed(bossIndex, difficulty) && bossEnt && bossEnt != INVALID_ENT_REFERENCE) { @@ -3753,7 +3755,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) return Plugin_Continue; } float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); - g_SlenderNextTeleportTime[bossIndex] = GetGameTime() + teleportTime; + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; bool ignoreFuncNavPrefer = g_NpcHasIgnoreNavPrefer[bossIndex]; int teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[bossIndex]); @@ -4047,6 +4049,11 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) #endif } } + else + { + float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; + } return Plugin_Continue; } @@ -4424,7 +4431,7 @@ bool TraceRayDontHitAnyEntity_Pathing(int entity, int contentsMask, int desiredc return true; } -bool TraceRayDontHitCharactersOrEntity(int entity,int mask, any data) +bool TraceRayDontHitCharactersOrEntity(int entity, int mask, any data) { if (entity == data) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index 72799907..cda6237e 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -45,6 +45,7 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter .DefineBoolField("m_IsEntityInFOV", 2049) .DefineBoolField("m_IsEntityNear", 2049) .DefineFloatField("m_CurrentChaseDuration") + .DefineFloatField("m_InitialChaseDuration") .DefineFloatField("m_LegacyFootstepInterval") .DefineFloatField("m_LegacyFootstepTime") .DefineIntField("m_MovementType") @@ -210,6 +211,19 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter } } + property float InitialChaseDuration + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_InitialChaseDuration"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_InitialChaseDuration", value); + } + } + property float LegacyFootstepInterval { public get() @@ -466,6 +480,9 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter CreateNative("SF2_BaseBossEntity.Target.get", Native_GetTarget); CreateNative("SF2_BaseBossEntity.State.get", Native_GetState); CreateNative("SF2_BaseBossEntity.CurrentChaseDuration.get", Native_GetCurrentChaseDuration); + CreateNative("SF2_BaseBossEntity.CurrentChaseDuration.set", Native_SetCurrentChaseDuration); + CreateNative("SF2_BaseBossEntity.InitialChaseDuration.get", Native_GetInitialChaseDuration); + CreateNative("SF2_BaseBossEntity.InitialChaseDuration.set", Native_SetInitialChaseDuration); CreateNative("SF2_BaseBossEntity.EyePosition", Native_EyePosition); CreateNative("SF2_BaseBossEntity.GetProfileName", Native_GetProfileName); CreateNative("SF2_BaseBossEntity.GetName", Native_GetName); @@ -474,7 +491,7 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter } public int SelectProfileAnimation(const char[] animType, float &rate = 1.0, float &duration = 0.0, float &cycle = 0.0, float &footstepInterval = 0.0, - int &index = 0, int preDefinedIndex = -1, const char[] preDefinedName = "", const char[] posture = NULL_STRING, bool &overrideLoop = false, bool &loop = false) + int &index = 0, int preDefinedIndex = -1, const char[] preDefinedName = "", const char[] posture = NULL_STRING, bool &overrideLoop = false, bool &loop = false, char[] returnAnimation = "", int rtnAnimationLength = 0) { SF2NPC_BaseNPC controller = this.Controller; int difficulty = controller.Difficulty; @@ -513,6 +530,8 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter int sequence = LookupProfileAnimation(this.index, animation); + strcopy(returnAnimation, rtnAnimationLength, animation); + this.AnimationPlaybackRate = rate; return sequence; @@ -547,8 +566,9 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter float rate = 1.0, cycle = 0.0, footstepInterval = 0.0; bool overrideLoop, loop; int index = 0; + char animation[64]; - int sequence = this.SelectProfileAnimation(animType, rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, posture, overrideLoop, loop); + int sequence = this.SelectProfileAnimation(animType, rate, duration, cycle, footstepInterval, index, preDefinedIndex, preDefinedName, posture, overrideLoop, loop, animation, sizeof(animation)); if (sequence == -1) { return false; @@ -563,27 +583,36 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter rate = 12.0; } - bool isMovement = strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Walk]) == 0 || - strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Run]) == 0; + Action result = Plugin_Continue; + Call_StartForward(g_OnBossAnimationUpdateFwd); + Call_PushCell(this.Controller); + Call_PushString(animation); + Call_Finish(result); - bool shouldLoop = isMovement || strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Idle]) == 0; - this.ResetSequence(sequence); - this.SetPropFloat(Prop_Send, "m_flCycle", cycle); - if (strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Attack]) == 0 && this.MovementType == SF2NPCMoveType_Attack) - { - shouldLoop = true; - } - if (overrideLoop) + if (result != Plugin_Handled) { - this.SetProp(Prop_Data, "m_bSequenceLoops", loop); - } - else - { - this.SetProp(Prop_Data, "m_bSequenceLoops", shouldLoop); - } - this.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + bool isMovement = strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Walk]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Run]) == 0; - this.LegacyFootstepInterval = footstepInterval; + bool shouldLoop = isMovement || strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Idle]) == 0; + this.ResetSequence(sequence); + this.SetPropFloat(Prop_Send, "m_flCycle", cycle); + if (strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Attack]) == 0 && this.MovementType == SF2NPCMoveType_Attack) + { + shouldLoop = true; + } + if (overrideLoop) + { + this.SetProp(Prop_Data, "m_bSequenceLoops", loop); + } + else + { + this.SetProp(Prop_Data, "m_bSequenceLoops", shouldLoop); + } + this.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + + this.LegacyFootstepInterval = footstepInterval; + } return true; } @@ -848,6 +877,44 @@ static any Native_GetCurrentChaseDuration(Handle plugin, int numParams) return bossEntity.CurrentChaseDuration; } +static any Native_SetCurrentChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + bossEntity.CurrentChaseDuration = GetNativeCell(2); + return 0; +} + +static any Native_GetInitialChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.InitialChaseDuration; +} + +static any Native_SetInitialChaseDuration(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + bossEntity.InitialChaseDuration = GetNativeCell(2); + return 0; +} + static any Native_EyePosition(Handle plugin, int numParams) { int entity = GetNativeCell(1); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp index 592b2f3d..0470c421 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -234,6 +234,11 @@ static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBo actor.InvokeOnStartAttack(action.GetAttackName()); + if (attackData.StartEffects != null) + { + SlenderSpawnEffects(attackData.StartEffects, controller.Index, false); + } + if (attackData.Type == SF2BossAttackType_Custom) { return action.Continue(); @@ -257,11 +262,6 @@ static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBo action.PlayedBeginVoice = true; } - if (attackData.StartEffects != null) - { - SlenderSpawnEffects(attackData.StartEffects, controller.Index, false); - } - float duration = 0.0; NextBotAction newAction = actor.IsAttackTransitionPossible(action.GetAttackName(), _, duration); action.ShouldReplayAnimation = newAction != NULL_ACTION; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp index 64b7e729..64c5949e 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -43,6 +43,12 @@ static int OnStart(SF2_ChaserChaseAction action, SF2_ChaserEntity actor, NextBot actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + if (actor.InitialChaseDuration > 0.0) + { + actor.CurrentChaseDuration = actor.InitialChaseDuration; + actor.InitialChaseDuration = 0.0; + } + if (data.AlertOnChaseInfo.OnChangeState[difficulty]) { actor.ForceChaseOtherBosses(); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 2c3d5c75..851e519d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -1719,10 +1719,10 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss player.FlashlightBatteryLife -= amount; } - int sprintAmount = shockwaveData.StaminaDrainPercent[difficulty]; + float sprintAmount = shockwaveData.StaminaDrainPercent[difficulty]; if (sprintAmount > 0.0) { - player.SetSprintPoints(player.GetSprintPoints() - sprintAmount); + player.Stamina -= sprintAmount; } shockwaveData.ApplyDamageEffects(player, difficulty, SF2_ChaserBossEntity(this.index)); @@ -3356,73 +3356,98 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam if (player.IsValid) { CBaseEntity weaponEnt = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); - if (weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && player.Class == TFClass_Spy && (data.IsPvEBoss || SF_IsBoxingMap()) && chaser.State != STATE_DEATH) - { - float myEyePos[3], clientEyePos[3], buffer[3], myAng[3]; - player.GetEyePosition(clientEyePos); - chaser.Controller.GetEyePosition(myEyePos); - SubtractVectors(clientEyePos, myEyePos, buffer); - GetVectorAngles(buffer, buffer); - chaser.GetAbsAngles(myAng); - - if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 90.0 && chaserData.BackstabDamageScale > 0.0) - { - damageType = DMG_CRIT; - EmitSoundToClient(player.index, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - weaponEnt.SetPropFloat(Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); - player.SetPropFloat(Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - player.SetPropFloat(Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); - CBaseAnimating model = CBaseAnimating(player.GetPropEnt(Prop_Send, "m_hViewModel")); - if (model.IsValid() && model.index > MaxClients) + if (weaponEnt.IsValid() && weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) + { + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 447, 426, 1181: // Whip + Eviction Notice + Hot Hand { - int sequence = 0; - switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) - { - case 4, 194, 665, 727, 794, 803, 883, 892, 901, 910, 959, 968: // Butterfly knives - { - sequence = 42; - } - case 225, 356, 461, 574, 649: // Non-butterfly knives - { - sequence = 16; - } - case 423, 1071, 30758: // Multi-class knives - { - sequence = 21; - } - case 638: // Sharp Dresser - { - sequence = 32; - } - } - model.SetProp(Prop_Send, "m_nSequence", sequence); + player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); } - damage = chaser.MaxHealth * chaserData.BackstabDamageScale; - if (!chaserData.DeathData.Enabled[difficulty]) + case 416: // Market Gardener { - damage = chaser.MaxStunHealth * chaserData.BackstabDamageScale; + float zVelocity[3]; + player.GetPropVector(Prop_Data, "m_vecVelocity", zVelocity); + if (zVelocity[2] < 0.0) // A soldier has the market gardener and is currently falling down, like Minecraft with its critical hits. + { + damageType |= DMG_CRIT; + damage *= 2.0; + + return Plugin_Changed; + } } - switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + } + + if (player.Class == TFClass_Spy && (data.IsPvEBoss || SF_IsBoxingMap()) && chaser.State != STATE_DEATH) + { + float myEyePos[3], clientEyePos[3], buffer[3], myAng[3]; + player.GetEyePosition(clientEyePos); + chaser.Controller.GetEyePosition(myEyePos); + SubtractVectors(clientEyePos, myEyePos, buffer); + GetVectorAngles(buffer, buffer); + chaser.GetAbsAngles(myAng); + + if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 90.0 && chaserData.BackstabDamageScale > 0.0) { - case 356: // Kunai + damageType = DMG_CRIT; + EmitSoundToClient(player.index, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + weaponEnt.SetPropFloat(Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); + player.SetPropFloat(Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + player.SetPropFloat(Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); + CBaseAnimating model = CBaseAnimating(player.GetPropEnt(Prop_Send, "m_hViewModel")); + if (model.IsValid() && model.index > MaxClients) { - int health = player.Health + 100; - if (health > 210) + int sequence = 0; + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) { - health = 210; + case 4, 194, 665, 727, 794, 803, 883, 892, 901, 910, 959, 968: // Butterfly knives + { + sequence = 42; + } + case 225, 356, 461, 574, 649: // Non-butterfly knives + { + sequence = 16; + } + case 423, 1071, 30758: // Multi-class knives + { + sequence = 21; + } + case 638: // Sharp Dresser + { + sequence = 32; + } } - SetEntityHealth(player.index, health); + model.SetProp(Prop_Send, "m_nSequence", sequence); } - case 461: // Big Earner + damage = chaser.MaxHealth * chaserData.BackstabDamageScale; + if (!chaserData.DeathData.Enabled[difficulty]) { - player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); + damage = chaser.MaxStunHealth * chaserData.BackstabDamageScale; } - } + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 356: // Kunai + { + int health = player.Health + 100; + if (health > 210) + { + health = 210; + } + SetEntityHealth(player.index, health); + } - return Plugin_Changed; + case 461: // Big Earner + { + player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); + } + } + + return Plugin_Changed; + } } + } } @@ -3446,30 +3471,7 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float if (player.IsValid) { - CBaseEntity weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); - if (weapon.IsValid() && weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) - { - switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) - { - case 447, 426, 1181: // Whip + Eviction Notice + Hot Hand - { - player.ChangeCondition(TFCond_SpeedBuffAlly, _, 4.0); - } - - case 416: // Market Gardener - { - float zVelocity[3]; - player.GetPropVector(Prop_Data, "m_vecVelocity", zVelocity); - if (zVelocity[2] < 0.0) // A soldier has the market gardener and is currently falling down, like Minecraft with its critical hits. - { - damageType |= DMG_CRIT; - damage *= 2.0; - } - } - } - } - - weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); + CBaseEntity weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); if (weapon.IsValid() && weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) { switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) @@ -4421,21 +4423,28 @@ static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) SF2_ChaserEntity chaser = SF2_ChaserEntity(bot.GetEntity()); if (player.IsValid) { + if (player.IsInGhostMode) + { + return false; + } + if (chaser.IsValid() && chaser.Controller.IsValid()) { SF2BossProfileData data; SF2NPC_BaseNPC controller = view_as(chaser.Controller); data = controller.GetProfileData(); - if (data.IsPvEBoss && player.IsInPvE) + if ((data.IsPvEBoss && player.IsInPvE) || (controller.Flags & SFF_ATTACKWAITERS) != 0) { return true; } } + if (!SF_IsBoxingMap() && !player.IsProxy && !player.IsInGhostMode && player.Team != TFTeam_Blue && !player.IsInDeathCam) { return true; } } + if (chaser.IsValid() && chaser.Controller.IsValid()) { SF2BossProfileData data; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp index 1fd33ce1..e5d3c60f 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp @@ -24,6 +24,11 @@ static int OnStart(SF2_StatueChaseAction action, SF2_StatueEntity actor, NextBot SF2StatueBossProfileData data; data = controller.GetProfileData(); actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + if (actor.InitialChaseDuration > 0.0) + { + actor.CurrentChaseDuration = actor.InitialChaseDuration; + actor.InitialChaseDuration = 0.0; + } actor.State = STATE_CHASE; return action.Continue(); diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index b1e3f31b..2c5f83c6 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -567,6 +567,7 @@ void NPCChaser_InitializeAPI() CreateNative("SF2_GetBossCurrentAttackIndex", Native_GetBossCurrentAttackIndex); CreateNative("SF2_GetChaserProfileFromBossIndex", Native_GetProfileData); CreateNative("SF2_GetChaserProfileFromName", Native_GetProfileDataEx); + CreateNative("SF2_SetEntityForceChaseState", Native_SetForceChaseState); g_OnChaserBossStartAttackFwd = new GlobalForward("SF2_OnChaserBossStartAttack", ET_Ignore, Param_Cell, Param_String); g_OnChaserBossEndAttackFwd = new GlobalForward("SF2_OnChaserBossEndAttack", ET_Ignore, Param_Cell, Param_String); @@ -618,3 +619,21 @@ static any Native_GetProfileDataEx(Handle plugin, int numParams) SetNativeArray(2, data, sizeof(data)); return true; } + +static any Native_SetForceChaseState(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + CBaseEntity target = CBaseEntity(GetNativeCell(2)); + if (!target.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", controller.Index); + } + + SetClientForceChaseState(controller, target, GetNativeCell(3)); + return 0; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 01813a97..ae0d3245 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -1514,4 +1514,4 @@ static any Native_GetBossAttributeValue(Handle plugin,int numParams) return 0.0; } return NPCGetAttributeValue(GetNativeCell(1), attributeIndex); -} +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 22b3fcee..9a21adc1 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -305,7 +305,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.ScareReplenishSprint = !!kv.GetNum("scare_player_replenish_sprint", profileData.ScareReplenishSprint); if (profileData.ScareReplenishSprint) { - profileData.ScareReplenishSprintAmount = kv.GetNum("scare_player_replenish_sprint_amount", profileData.ScareReplenishSprintAmount); + profileData.ScareReplenishSprintAmount = kv.GetFloat("scare_player_replenish_sprint_amount", profileData.ScareReplenishSprintAmount); } GetProfileDifficultyFloatValues(kv, "static_radius", profileData.StaticRadius, profileData.StaticRadius); @@ -1935,7 +1935,7 @@ bool GetBossProfileScareReplenishState(const char[] profile) return g_CachedProfileData.ScareReplenishSprint; } -int GetBossProfileScareReplenishAmount(const char[] profile) +float GetBossProfileScareReplenishAmount(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); return g_CachedProfileData.ScareReplenishSprintAmount; diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 532f1f4f..e8a2e467 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -1149,6 +1149,20 @@ void GetPositionForward(float pos[3], float ang[3], float returnValue[3], float } } +bool IsEmptyVector(const float test[3]) +{ + int count = 0; + for (int i = 0; i < 3; i++) + { + if (test[i] == 0.0) + { + count++; + } + } + + return count == 3; +} + // ========================================================== // ANGLE FUNCTIONS // ========================================================== @@ -1360,6 +1374,16 @@ bool IsInfiniteFlashlightEnabled() return (g_RoundInfiniteFlashlight || (g_PlayerInfiniteFlashlightOverrideConVar.IntValue == 1) || SF_SpecialRound(SPECIALROUND_INFINITEFLASHLIGHT) || ((g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)) && g_NightvisionType == 1)); } +bool IsInfiniteBlinkEnabled() +{ + return g_RoundInfiniteBlink || (g_PlayerInfiniteBlinkOverrideConVar.IntValue == 1); +} + +bool IsInfiniteSprintEnabled() +{ + return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1); +} + static int g_ArraySpecialRoundType[SPECIALROUND_MAXROUNDS]; bool SF_SpecialRound(int specialRound) From 836b8526f55f445034146b8adc3cceeca48886f5 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sun, 10 Dec 2023 20:46:53 -0700 Subject: [PATCH 25/86] Update sf2_trigger_pve.sp --- addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp index bd005687..b29ee97e 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_pve.sp @@ -48,7 +48,7 @@ static void Initialize() g_EntityFactory = new CEntityFactory("sf2_trigger_pve", OnCreate); g_EntityFactory.DeriveFromClass("trigger_multiple"); g_EntityFactory.BeginDataMapDesc() - .DefineFloatField("m_IsBossPvE", _, "bosspve") + .DefineBoolField("m_IsBossPvE", _, "bosspve") .EndDataMapDesc(); g_EntityFactory.Install(); From 134472d22949cb1656bd67c2c40098e4c5972af0 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Fri, 15 Dec 2023 16:19:20 -0700 Subject: [PATCH 26/86] Collision patch --- addons/sourcemod/scripting/sf2/client/ghostmode.sp | 2 +- .../sf2/entities/projectiles/sf2_baseball_projectile.sp | 2 +- .../sf2/entities/projectiles/sf2_grenade_projectile.sp | 2 +- .../scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp | 2 +- .../scripting/sf2/npc/entities/chaser/actions/death.sp | 2 +- addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp | 2 +- addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp | 2 +- addons/sourcemod/scripting/sf2/traps.sp | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/client/ghostmode.sp b/addons/sourcemod/scripting/sf2/client/ghostmode.sp index 22958d5b..094c4df1 100644 --- a/addons/sourcemod/scripting/sf2/client/ghostmode.sp +++ b/addons/sourcemod/scripting/sf2/client/ghostmode.sp @@ -271,7 +271,7 @@ void ClientHandleGhostMode(int client, bool forceSpawn = false) SetEntProp(client, Prop_Send, "m_usSolidFlags", 4); SetEntProp(client, Prop_Data, "m_nSolidType", 0); SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); - SetEntProp(client, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityCollisionGroup(client, COLLISION_GROUP_DEBRIS_TRIGGER); SetEntityFlags(client, GetEntityFlags(client) | FL_NOTARGET); SetEntityRenderMode(client, RENDER_TRANSCOLOR); SetEntityRenderColor(client, _, _, _, 0); diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp index fb49c982..9e74b457 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp @@ -80,7 +80,7 @@ methodmap SF2_ProjectileBaseball < SF2_ProjectileGrenade ball.SetProp(Prop_Send, "m_usSolidFlags", 12); ball.KeyValue("solid", "2"); ball.KeyValue("spawnflags", "4"); - ball.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityCollisionGroup(ball.index, COLLISION_GROUP_DEBRIS_TRIGGER); ball.SetProp(Prop_Send, "m_usSolidFlags", 0); ball.Spawn(); diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp index 6652cada..95f11c6f 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -271,7 +271,7 @@ methodmap SF2_ProjectileGrenade < SF2_ProjectileBase grenade.SetProp(Prop_Send, "m_nSkin", 1); grenade.KeyValue("solid", "2"); grenade.KeyValue("spawnflags", "4"); - grenade.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityCollisionGroup(grenade.index, COLLISION_GROUP_DEBRIS_TRIGGER); grenade.SetProp(Prop_Send, "m_usSolidFlags", 0); CBaseEntity particle = grenade.CreateParticle(grenade.GetTrailName()); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp index 55c32814..5f893c2b 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp @@ -534,7 +534,7 @@ static void FireTongue(SF2_ChaserEntity actor, SF2_ChaserAttackAction_Tongue act trail.Spawn(); trail.Activate(); trail.SetProp(Prop_Send, "m_usSolidFlags", 22); - trail.SetProp(Prop_Send, "m_CollisionGroup", 1); + SetEntityCollisionGroup(trail.index, 1); trail.Teleport(effectPos, shootAng, velocity); SetVariantString("!activator"); tongueEnd.AcceptInput("SetParent", trail.index); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp index 30cf5499..723520ca 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp @@ -262,7 +262,7 @@ static void SpawnGibs(SF2_ChaserEntity actor) gib.Spawn(); gib.Teleport(NULL_VECTOR, NULL_VECTOR, vel); - gib.SetProp(Prop_Send, "m_CollisionGroup", 1); + SetEntityCollisionGroup(gib.index, 1); gib.SetProp(Prop_Send, "m_usSolidFlags", 0); gib.SetProp(Prop_Send, "m_nSolidType", 2); gib.SetProp(Prop_Send, "m_nSkin", data.DeathData.GibSkin); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 851e519d..0e2b6c78 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -2926,7 +2926,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss if (SF_IsBoxingMap() || originalData.IsPvEBoss) { - chaser.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityCollisionGroup(chaser.index, COLLISION_GROUP_DEBRIS_TRIGGER); } for (int difficulty2 = 0; difficulty2 < Difficulty_Max; difficulty2++) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index 4bb41166..e78b820d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -157,7 +157,7 @@ methodmap SF2_StatueEntity < SF2_BaseBoss npc.SetBodyMaxs(HULL_HUMAN_MAXS); statue.SetProp(Prop_Data, "m_nSolidType", SOLID_BBOX); - statue.SetProp(Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityCollisionGroup(statue.index, COLLISION_GROUP_DEBRIS_TRIGGER); statue.Teleport(pos, ang, NULL_VECTOR); diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index 66c10371..66214368 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -82,7 +82,7 @@ void Trap_SpawnTrap(float position[3], float direction[3], SF2NPC_Chaser control SetEntProp(trapEntity, Prop_Send, "m_usSolidFlags", FSOLID_TRIGGER_TOUCH_DEBRIS|FSOLID_TRIGGER|FSOLID_NOT_SOLID|FSOLID_CUSTOMBOXTEST); SetEntProp(trapEntity, Prop_Data, "m_nSolidType", SOLID_BBOX); - SetEntProp(trapEntity, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_DEBRIS_TRIGGER); // COLLISION_GROUP_DEBRIS + SetEntityCollisionGroup(trapEntity, COLLISION_GROUP_DEBRIS_TRIGGER); float mins[3], maxs[3]; mins[0] = -25.0; From 94b9ac3f450fb73a61824667eba0a40f5c76be42 Mon Sep 17 00:00:00 2001 From: Sandust60 <96904513+M60TM@users.noreply.github.com> Date: Thu, 28 Dec 2023 15:35:46 +0900 Subject: [PATCH 27/86] Move natives and forwards to API (#127) --- addons/sourcemod/scripting/sf2/extras/natives.sp | 1 + addons/sourcemod/scripting/sf2/npc.sp | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index ab414fc7..f5aa533c 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -143,6 +143,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max SF2_BaseBoss.SetupAPI(); SF2_PlaySequenceAndWait.SetupAPI(); + NPC_InitializeAPI(); NPCChaser_InitializeAPI(); NPCStatue_InitializeAPI(); diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index b6b04ff0..08804967 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -190,6 +190,14 @@ void NPCSetDeathCamEnabled(int npcIndex, bool state) } void NPCInitialize() +{ + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + g_OnEntityTeleportedPFwd.AddFunction(null, EntityTeleported); + + NPCChaserInitialize(); +} + +void NPC_InitializeAPI() { g_OnBossAddedFwd = new GlobalForward("SF2_OnBossAdded", ET_Ignore, Param_Cell); g_OnBossSpawnFwd = new GlobalForward("SF2_OnBossSpawn", ET_Ignore, Param_Cell); @@ -197,10 +205,7 @@ void NPCInitialize() g_OnBossRemovedFwd = new GlobalForward("SF2_OnBossRemoved", ET_Ignore, Param_Cell); g_OnBossFinishSpawningFwd = new GlobalForward("SF2_OnBossFinishSpawning", ET_Ignore, Param_Cell); g_OnClientCaughtByBossFwd = new GlobalForward("SF2_OnClientCaughtByBoss", ET_Ignore, Param_Cell, Param_Cell); - - g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); - g_OnEntityTeleportedPFwd.AddFunction(null, EntityTeleported); - + CreateNative("SF2_GetMaxBossCount", Native_GetMaxBosses); CreateNative("SF2_EntIndexToBossIndex", Native_EntIndexToBossIndex); CreateNative("SF2_BossIndexToEntIndex", Native_BossIndexToEntIndex); @@ -238,8 +243,6 @@ void NPCInitialize() CreateNative("SF2_GetProfileFromName", Native_GetProfileDataEx); CreateNative("SF2_SpawnBossEffects", Native_SpawnBossEffects); - - NPCChaserInitialize(); } static void EntityDestroyed(CBaseEntity ent, const char[] classname) From d50d90538e0cb9015b771c4057ee9bc9a2e9ef3c Mon Sep 17 00:00:00 2001 From: Ivan Zaitsev Date: Sun, 11 Feb 2024 15:01:43 +0500 Subject: [PATCH 28/86] Fix Native_SetClientFlashlightBatteryLife (#129) Fixes "Invalid parameter number: 3" error. --- addons/sourcemod/scripting/sf2/methodmaps.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index dd7a340e..e104227d 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -2166,7 +2166,7 @@ static any Native_SetClientFlashlightBatteryLife(Handle plugin, int numParams) } SF2_BasePlayer player = SF2_BasePlayer(client); - player.FlashlightBatteryLife = GetNativeCell(3); + player.FlashlightBatteryLife = GetNativeCell(2); return 0; } From ef5ecc3cc4d3050c2d2eef80200d5e962bef514f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sun, 11 Feb 2024 16:28:35 -0700 Subject: [PATCH 29/86] Alpha 2.5 --- addons/sourcemod/scripting/include/sf2.inc | 183 ++- .../scripting/include/sf2/defines.inc | 2 + .../include/sf2/profiles/profiles.inc | 196 ++- .../include/sf2/profiles/profiles_chaser.inc | 134 +- addons/sourcemod/scripting/sf2.sp | 610 ++++---- addons/sourcemod/scripting/sf2/adminmenu.sp | 6 + addons/sourcemod/scripting/sf2/anticamping.sp | 5 + addons/sourcemod/scripting/sf2/changelog.sp | 5 + addons/sourcemod/scripting/sf2/client.sp | 316 +--- .../sourcemod/scripting/sf2/client/blink.sp | 9 + .../scripting/sf2/client/breathing.sp | 9 + .../scripting/sf2/client/deathcam.sp | 52 +- .../scripting/sf2/client/flashlight.sp | 49 +- .../scripting/sf2/client/ghostmode.sp | 105 +- addons/sourcemod/scripting/sf2/client/glow.sp | 296 ++++ .../sourcemod/scripting/sf2/client/hints.sp | 10 + .../scripting/sf2/client/interactables.sp | 235 +++ .../sourcemod/scripting/sf2/client/music.sp | 47 +- .../sourcemod/scripting/sf2/client/proxy.sp | 133 +- .../sourcemod/scripting/sf2/client/sprint.sp | 120 +- .../sourcemod/scripting/sf2/client/static.sp | 15 + .../sourcemod/scripting/sf2/client/think.sp | 23 +- .../scripting/sf2/client/ultravision.sp | 30 +- addons/sourcemod/scripting/sf2/effects.sp | 2 +- .../projectiles/sf2_arrow_projectile.sp | 19 +- .../projectiles/sf2_baseball_projectile.sp | 42 +- .../projectiles/sf2_cow_mangler_projectile.sp | 6 + .../projectiles/sf2_fireball_projectile.sp | 6 + .../projectiles/sf2_grenade_projectile.sp | 37 +- .../projectiles/sf2_iceball_projectile.sp | 6 + .../projectiles/sf2_rocket_projectile.sp | 6 + .../sf2_sentry_rocket_projectile.sp | 6 + .../sf2/entities/sf2_base_projectile.sp | 37 +- .../scripting/sf2/extras/commands.sp | 75 +- .../scripting/sf2/extras/game_events.sp | 35 +- .../sourcemod/scripting/sf2/extras/natives.sp | 42 +- .../scripting/sf2/gamemodes/renevant.sp | 57 +- addons/sourcemod/scripting/sf2/glow.sp | 391 +++++ .../sf2/mapentities/sf2_boss_maker.sp | 2 +- addons/sourcemod/scripting/sf2/methodmaps.sp | 36 +- addons/sourcemod/scripting/sf2/npc.sp | 533 ++++--- .../scripting/sf2/npc/entities/base/entity.sp | 20 +- .../sf2/npc/entities/chaser/actions/alert.sp | 47 +- .../sf2/npc/entities/chaser/actions/attack.sp | 17 +- .../entities/chaser/actions/attacks/bullet.sp | 69 +- .../chaser/actions/attacks/laser_beam.sp | 8 + .../entities/chaser/actions/attacks/melee.sp | 34 +- .../chaser/actions/attacks/projectile.sp | 1 + .../entities/chaser/actions/attacks/tongue.sp | 7 +- .../sf2/npc/entities/chaser/actions/chase.sp | 47 +- .../sf2/npc/entities/chaser/actions/death.sp | 2 +- .../npc/entities/chaser/actions/despawn.sp | 44 + .../entities/chaser/actions/flee_to_heal.sp | 29 +- .../sf2/npc/entities/chaser/actions/idle.sp | 58 +- .../npc/entities/chaser/actions/mainlayer.sp | 53 +- .../sf2/npc/entities/chaser/actions/rage.sp | 3 + .../sf2/npc/entities/chaser/actions/smell.sp | 13 + .../sf2/npc/entities/chaser/actions/stun.sp | 16 +- .../sf2/npc/entities/chaser/entity.sp | 509 +++++-- .../npc/entities/statue/actions/mainlayer.sp | 2 +- .../sf2/npc/entities/statue/entity.sp | 16 +- addons/sourcemod/scripting/sf2/npc/glow.sp | 191 +++ .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 227 ++- addons/sourcemod/scripting/sf2/profiles.sp | 35 +- .../sf2/profiles/profile_chaser_precache.sp | 1296 +++++++++++++++-- .../scripting/sf2/profiles/profile_statue.sp | 11 + .../sf2/profiles/profiles_boss_functions.sp | 46 +- addons/sourcemod/scripting/sf2/pve.sp | 9 + addons/sourcemod/scripting/sf2/pvp.sp | 26 +- .../sourcemod/scripting/sf2/specialround.sp | 100 +- addons/sourcemod/scripting/sf2/stocks.sp | 117 +- addons/sourcemod/scripting/sf2/traps.sp | 27 +- 72 files changed, 5309 insertions(+), 1699 deletions(-) create mode 100644 addons/sourcemod/scripting/sf2/client/glow.sp create mode 100644 addons/sourcemod/scripting/sf2/client/interactables.sp create mode 100644 addons/sourcemod/scripting/sf2/glow.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/despawn.sp create mode 100644 addons/sourcemod/scripting/sf2/npc/glow.sp diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 4c917381..0ec3af03 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -7,8 +7,8 @@ #include #include -#define PLUGIN_VERSION "1.8.0 M Alpha 2" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2" +#define PLUGIN_VERSION "1.8.0 M Alpha 2.5" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.5" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 @@ -147,9 +147,14 @@ enum SF2BossSound_AttackKilled, SF2BossSound_Death, SF2BossSound_TauntKill, - SF2BossSound_Smell, + SF2BossSound_Smell = 10, SF2BossSound_AttackBegin, SF2BossSound_AttackEnd, + SF2BossSound_SelfHeal, + SF2BossSound_RageAll, + SF2BossSound_RageTwo = 15, + SF2BossSound_RageThree, + SF2BossSound_Despawn, SF2BossSound_MaxSounds }; @@ -171,6 +176,8 @@ enum SF2BossAnimation_Smell, SF2BossAnimation_AttackBegin, SF2BossAnimation_AttackEnd = 15, + SF2BossAnimation_ProjectileShoot, + SF2BossAnimation_Despawn, SF2BossAnimation_MaxAnimations } @@ -230,7 +237,34 @@ char g_SlenderVoiceList[SF2BossSound_MaxSounds][] = "sound_taunt_kill", "sound_smell", "sound_attack_begin", - "sound_attack_end" + "sound_attack_end", + "sound_heal_self", + "sound_rage", + "sound_rage_2", + "sound_rage_3", + "sound_despawn" +}; + +char g_SlenderAnimationsList[SF2BossAnimation_MaxAnimations][] = +{ + "idle", + "walk", + "attack", + "run", + "stun", + "chaseinitial", + "rage", + "spawn", + "fleestart", + "heal", + "deathcam", + "death", + "tauntkill", + "smell", + "attack_begin", + "attack_end", + "shoot", + "despawn" }; char g_AttributesList[SF2Attribute_Max][] = @@ -283,6 +317,22 @@ enum SF2NPCMoveTypes SF2NPCMoveType_Max } +enum RenevantWave +{ + RenevantWave_Normal = 0, + RenevantWave_IncreaseDifficulty, + RenevantWave_MultiEffect, + RenevantWave_BaconSpray, + RenevantWave_DoubleTrouble, + RenevantWave_DoomBox, + RenevantWave_90s, + RenevantWave_MarkForDeath, + RenevantWave_SingleBoss, + RenevantWave_AdminBoss, + RenevantWave_WallHax, + RenevantWave_Max +} + #include #include #include @@ -322,6 +372,7 @@ enum SF2NPCMoveTypes #define COND_ENEMYVISIBLE (1 << 12) #define COND_ENEMYRECHASE (1 << 13) #define COND_DEBUG (1 << 14) +#define COND_ALERT_TRIGGER_POS (1 << 15) /** * Called after a boss profile is successfully loaded. @@ -693,8 +744,9 @@ forward void SF2_OnClientSpawnedAsProxy(int client); * Called whenever the Renevant gamemode triggers a new wave. * * @param wave The wave number that got triggered. + * @param condition The wave condition used. */ -forward void SF2_OnRenevantWaveTrigger(int wave); +forward void SF2_OnRenevantWaveTrigger(int wave, RenevantWave condition); /** * Called whenever the boss pack vote starts @@ -762,6 +814,14 @@ forward Action SF2_OnIsBossCustomAttackPossible(SF2_ChaserBossEntity chaser, con */ forward Action SF2_OnBossGetCustomAttackAction(SF2_ChaserBossEntity chaser, const char[] attackName, SF2ChaserBossProfileAttackData data, CBaseEntity target, NextBotAction &action); +/** + * Called when a projectile from SF2 has touched another entity successfully and has either exploded or dealt damage. + * + * @param projectile Projectile entity + * @param other Entity the projectile touched + */ +forward void SF2_OnProjectileTouch(SF2_Projectile_Base projectile, CBaseEntity other); + /** * Fires each time a boss starts an attack. * @@ -1503,6 +1563,36 @@ native float SF2_GetBossAttributeValue(int bossIndex, int attribute); #pragma deprecated Use SF2_ChaserBossEntity.GetAttackName instead. native int SF2_GetBossCurrentAttackIndex(int bossIndex); +#pragma deprecated Use SF2ChaserBossProfileAttackData.Type instead. +native int SF2_GetBossAttackIndexType(int bossIndex, int attackIndex); + +#pragma deprecated This no longer does anything. +native float SF2_GetBossTimeUntilNoPersistence(int bossIndex); + +#pragma deprecated This no longer does anything. +native void SF2_SetBossTimeUntilNoPersistence(int bossIndex, float time); + +#pragma deprecated Use SF2_BaseBossEntity.CurrentChaseDuration instead. +native float SF2_GetBossTimeUntilAlert(int bossIndex); + +#pragma deprecated Use SF2_BaseBossEntity.CurrentChaseDuration instead. +native void SF2_SetBossTimeUntilAlert(int bossIndex, float time); + +#pragma deprecated Use SF2_ChaserBossEntity.PerformVoice instead. +native void SF2_PerformBossVoice(int bossIndex, const int attackIndex = -1, int soundType = -1); + +#pragma deprecated Use SF2_ChaserBossEntity.CreateSoundHint instead. +native void SF2_CreateBossSoundHint(int bossIndex, SoundType soundType, const float position[3], int difficulty); + +#pragma deprecated Use SF2ChaserBossProfileAttackData.Damage[Difficulty_Max] instead. +native float SF2_GetBossAttackIndexDamage(int bossIndex, int attackIndex, int difficulty); + +#pragma deprecated Use SF2ChaserBossProfileAttackData.DamageType[Difficulty_Max] instead. +native int SF2_GetBossAttackIndexDamageType(int bossIndex, int attackIndex); + +#pragma deprecated Use SF2_BaseBossEntity.ResetProfileAnimation instead. +native void SF2_UpdateBossAnimation(int bossIndex, int ent, int state, bool spawn = false); + /** * Returns the teleport think timer in Handle form * @@ -1738,7 +1828,7 @@ methodmap SF2_BaseBossEntity < CBaseCombatCharacter public native void ProfileData(any data[sizeof(SF2BossProfileData)]); /** - * Plays an animation on the boss from a specific animation section, even custom ones. + * Plays an animation on the boss from a specific animation section, even custom ones. Note this is limited to the main "animations" section. * * @param animType Animation section to search in. * @param preDefinedIndex Optional animation index to use. @@ -1915,6 +2005,14 @@ methodmap SF2_ChaserBossEntity < SF2_BaseBossEntity */ public native bool PerformVoice(int soundType = -1, const char[] attackName = "") + /** + * Forces the boss to perform a voice using a custom sound section. + * + * @param soundInfo Sound section to use. + * @return Whether or not the boss performed a voice. + */ + public native bool PerformCustomVoice(any soundInfo[sizeof(SF2BossProfileSoundInfo)]) + /** * Gets the default posture of the boss. The default posture of a boss * is named "default". @@ -1971,7 +2069,14 @@ methodmap SF2_ChaserBossEntity < SF2_BaseBossEntity * * @param death Whether or not to use the death state's item drop state */ - public native void DropItem(bool death = false) + public native void DropItem(bool death = false); + + /** + * Forces a boss to be alert at a specific position. + * + * @param position Position the boss should be alerted to. + */ + public native void CreateSoundHint(float position[3]); /** * Checks if an attack is possible the default way @@ -2903,6 +3008,14 @@ methodmap SF2_Projectile_Arrow < SF2_Projectile_Base const char[] model, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a arrow + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * @@ -2942,6 +3055,14 @@ methodmap SF2_Projectile_Baseball < SF2_Projectile_Base const char[] model, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a baseball + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * @@ -2979,6 +3100,14 @@ methodmap SF2_Projectile_CowMangler < SF2_Projectile_Base const float blastRadius, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a cow mangler projectile + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * @@ -3020,6 +3149,14 @@ methodmap SF2_Projectile_Fireball < SF2_Projectile_Base const char[] trail, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a fireball + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * @@ -3067,6 +3204,14 @@ methodmap SF2_Projectile_Grenade < SF2_Projectile_Base const char[] model, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a grenade + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * @@ -3114,6 +3259,14 @@ methodmap SF2_Projectile_Iceball < SF2_Projectile_Base const char[] freezeSound, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a iceball + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * @@ -3161,6 +3314,14 @@ methodmap SF2_Projectile_Rocket < SF2_Projectile_Base const char[] model, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a rocket + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * @@ -3200,6 +3361,14 @@ methodmap SF2_Projectile_SentryRocket < SF2_Projectile_Base const bool isCrits, const bool attackWaiters = false); + /** + * Whether or not the entity is actually a sentry rocket + */ + property bool IsValid + { + public native get(); + } + /** * Attempts to cast an entity to be this entity. * diff --git a/addons/sourcemod/scripting/include/sf2/defines.inc b/addons/sourcemod/scripting/include/sf2/defines.inc index 4c08161c..c1754678 100644 --- a/addons/sourcemod/scripting/include/sf2/defines.inc +++ b/addons/sourcemod/scripting/include/sf2/defines.inc @@ -59,6 +59,8 @@ #define ICEBALL_TRAIL "spell_fireball_small_blue" #define ROCKET_SHOOT ")weapons/rocket_shoot.wav" #define ROCKET_IMPACT ")weapons/explode1.wav" +#define ROCKET_IMPACT2 ")weapons/explode2.wav" +#define ROCKET_IMPACT3 ")weapons/explode3.wav" #define ROCKET_MODEL "models/weapons/w_models/w_rocket.mdl" #define ROCKET_TRAIL "rockettrail" #define ROCKET_EXPLODE_PARTICLE "ExplosionCore_MidAir" diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index f16394bc..0c1382ab 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -419,9 +419,16 @@ stock void GetProfileDifficultyNumValues(KeyValues kv, const char[] baseKeyName, } else { - if (i > 0) + if (defaultValues[i] != 0) { - buffer[i] = buffer[i - 1]; + buffer[i] = defaultValues[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } } } } @@ -464,9 +471,16 @@ stock void GetProfileDifficultyBoolValues(KeyValues kv, const char[] baseKeyName } else { - if (i > 0) + if (defaultValues[i]) { - buffer[i] = buffer[i - 1]; + buffer[i] = defaultValues[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } } } } @@ -509,9 +523,16 @@ stock void GetProfileDifficultyFloatValues(KeyValues kv, const char[] baseKeyNam } else { - if (i > 0) + if (defaultValues[i] != 0.0) { - buffer[i] = buffer[i - 1]; + buffer[i] = defaultValues[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } } } } @@ -570,9 +591,24 @@ stock void GetProfileDifficultyVectorValues(KeyValues kv, const char[] baseKeyNa } else { - if (i > 0) + bool def = false; + for (int k = 0; k < 3; k++) { - buffer[i] = buffer[i - 1]; + if (defaultValueVectors[i][k] != 0.0) + { + def = true; + } + } + if (def) + { + buffer[i] = defaultValueVectors[i]; + } + else + { + if (i > 0) + { + buffer[i] = buffer[i - 1]; + } } } } @@ -631,9 +667,16 @@ void GetProfileDifficultyStringValues(KeyValues kv, const char[] baseKeyName, ch } else { - if (i > 0) + if (defaultValueStrings[i][0] != '\0') { - strcopy(bufferArray[i], maxStringLen, bufferArray[i - 1]); + strcopy(bufferArray[i], maxStringLen, defaultValueStrings[i]); + } + else + { + if (i > 0) + { + strcopy(bufferArray[i], maxStringLen, bufferArray[i - 1]); + } } } } @@ -789,6 +832,7 @@ enum struct SF2BossProfileSoundInfo void Load(KeyValues kv, bool checkFile) { + kv.GetSectionName(this.SectionName, sizeof(this.SectionName)); if (kv.JumpToKey("paths")) { this.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); @@ -830,6 +874,59 @@ enum struct SF2BossProfileSoundInfo this.Chance = 1.0; } } + else + { + this.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char soundPath[PLATFORM_MAX_PATH], section[64], formatter[90]; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + kv.GetString(section, soundPath, sizeof(soundPath)); + if (soundPath[0] == '\0') + { + break; + } + + TryPrecacheBossProfileSoundPath(soundPath, checkFile); + + this.Paths.PushString(soundPath); + } + kv.GoBack(); + FormatEx(formatter, sizeof(formatter), "%s_channel", this.SectionName); + this.Channel = kv.GetNum(formatter, this.Channel); + FormatEx(formatter, sizeof(formatter), "%s_level", this.SectionName); + this.Level = kv.GetNum(formatter, this.Level); + FormatEx(formatter, sizeof(formatter), "%s_flags", this.SectionName); + this.Flags = kv.GetNum(formatter, this.Flags); + FormatEx(formatter, sizeof(formatter), "%s_volume", this.SectionName); + this.Volume = kv.GetFloat(formatter, this.Volume); + FormatEx(formatter, sizeof(formatter), "%s_pitch", this.SectionName); + this.Pitch = kv.GetNum(formatter, this.Pitch); + this.PitchRandomMin = this.Pitch; + this.PitchRandomMax = this.Pitch; + FormatEx(formatter, sizeof(formatter), "%s_cooldown_min", this.SectionName); + this.CooldownMin = kv.GetFloat(formatter, this.CooldownMin); + FormatEx(formatter, sizeof(formatter), "%s_cooldown_max", this.SectionName); + this.CooldownMax = kv.GetFloat(formatter, this.CooldownMax); + FormatEx(formatter, sizeof(formatter), "%s_pitch_random_min", this.SectionName); + this.PitchRandomMin = kv.GetNum(formatter, this.PitchRandomMin); + FormatEx(formatter, sizeof(formatter), "%s_pitch_random_max", this.SectionName); + this.PitchRandomMax = kv.GetNum(formatter, this.PitchRandomMax); + FormatEx(formatter, sizeof(formatter), "%s_radius", this.SectionName); + this.Radius = kv.GetFloat(formatter, this.Radius); + FormatEx(formatter, sizeof(formatter), "%s_chance", this.SectionName); + this.Chance = kv.GetFloat(formatter, this.Chance); + if (this.Chance < 0.0) + { + this.Chance = 0.0; + } + if (this.Chance > 1.0) + { + this.Chance = 1.0; + } + + kv.JumpToKey(this.SectionName); + } } void PostLoad() @@ -1211,9 +1308,9 @@ enum struct SF2BossProfileBaseEffectInfo this.BeamRingSpeed = 5; this.BeamRingFlags = 0; this.BeamRingColor = "255 255 255 255"; - this.BeamRingBeamSprite[0] = '\0'; + this.BeamRingBeamSprite = "sprites/laser.vmt"; this.BeamRingBeamModel = -1; - this.BeamRingHaloSprite[0] = '\0'; + this.BeamRingHaloSprite = "sprites/halo01.vmt"; this.BeamRingHaloModel = -1; this.TEParticleAttachType = 0; @@ -1517,15 +1614,15 @@ enum struct SF2BossProfileAnimationSectionNameData // This does NOT cover the en void Init(char optionalSectionName[64] = "", char optionalSubSection[64] = "") { this.Sequences = null; - if (optionalSectionName[0] != '\0') + if (optionalSectionName[0] == '\0') { this.SectionName[0] = '\0'; } else { - this.SectionName = optionalSectionName; + strcopy(this.SectionName, sizeof(this.SectionName), optionalSectionName); } - this.SubSectionName = optionalSubSection; + strcopy(this.SubSectionName, sizeof(this.SubSectionName), optionalSubSection); for (int i = 0; i < Difficulty_Max; i++) { @@ -1538,19 +1635,37 @@ enum struct SF2BossProfileAnimationSectionNameData // This does NOT cover the en } } - void Load(KeyValues kv) + void Load(KeyValues kv, bool newStuff = true) { - char animNames[Difficulty_Max][64]; + char animNames[Difficulty_Max][64], formatter[90]; this.Sequences = new ArrayList(ByteCountToCells(64)); this.Sequences.Resize(Difficulty_Max); - GetProfileDifficultyStringValues(kv, "name", animNames, sizeof(animNames[]), animNames); - GetProfileDifficultyFloatValues(kv, "playbackrate", this.Playbackrate, this.Playbackrate); - GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); - GetProfileDifficultyFloatValues(kv, "cycle", this.Cycle, this.Cycle); - GetProfileDifficultyFloatValues(kv, "footstepinterval", this.FootstepInterval, this.FootstepInterval); - GetProfileDifficultyBoolValues(kv, "override_loop", this.OverrideLoop, this.OverrideLoop); - GetProfileDifficultyBoolValues(kv, "loop", this.Loop, this.Loop); + if (newStuff) + { + GetProfileDifficultyStringValues(kv, "name", animNames, sizeof(animNames[]), animNames); + GetProfileDifficultyFloatValues(kv, "playbackrate", this.Playbackrate, this.Playbackrate); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); + GetProfileDifficultyFloatValues(kv, "cycle", this.Cycle, this.Cycle); + GetProfileDifficultyFloatValues(kv, "footstepinterval", this.FootstepInterval, this.FootstepInterval); + GetProfileDifficultyBoolValues(kv, "override_loop", this.OverrideLoop, this.OverrideLoop); + GetProfileDifficultyBoolValues(kv, "loop", this.Loop, this.Loop); + } + + FormatEx(formatter, sizeof(formatter), "animation_%s", this.SectionName); + GetProfileDifficultyStringValues(kv, formatter, animNames, sizeof(animNames[]), animNames); + + FormatEx(formatter, sizeof(formatter), "animation_%s_playbackrate", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.Playbackrate, this.Playbackrate); + + FormatEx(formatter, sizeof(formatter), "animation_%s_duration", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.Duration, this.Duration); + + FormatEx(formatter, sizeof(formatter), "animation_%s_cycle", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.Cycle, this.Cycle); + + FormatEx(formatter, sizeof(formatter), "animation_%s_cycle", this.SectionName); + GetProfileDifficultyFloatValues(kv, formatter, this.FootstepInterval, this.FootstepInterval); for (int i = 0; i < Difficulty_Max; i++) { @@ -1581,21 +1696,23 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima { StringMap Animations; StringMap Gestures; + bool Classic; void Init() { this.Animations = null; this.Gestures = null; + this.Classic = false; } - void Load(KeyValues kv) + void Load(KeyValues kv, bool newStuff = false) { + SF2BossProfileAnimationSectionNameData animData; + this.Animations = new StringMap(); if (kv.JumpToKey("animations")) { - this.Animations = new StringMap(); this.Gestures = new StringMap(); char animType[64]; - SF2BossProfileAnimationSectionNameData animData; SF2BossProfileAttackGestureData gestureData; if (kv.GotoFirstSubKey()) // Find the animation name @@ -1648,6 +1765,29 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima kv.GoBack(); } + else + { + if (!newStuff) + { + return; + } + for (int i = 0; i < SF2BossAnimation_MaxAnimations; i++) + { + char formatter[90], section[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s", g_SlenderAnimationsList[i]); + if (kv.GetDataType(formatter) == KvData_None) + { + continue; + } + ArrayList animDataArray = new ArrayList(sizeof(animData)); + strcopy(section, sizeof(section), g_SlenderAnimationsList[i]); + animData.Init(section); + animData.Load(kv, false); + animDataArray.PushArray(animData); + this.Animations.SetValue(section, animDataArray); + this.Classic = true; + } + } } void Destroy() @@ -1741,7 +1881,7 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima randomAnimation = preDefinedIndex; } SF2BossProfileAnimationSectionNameData animData; - if (preDefinedName[0] != '\0') + if (!this.Classic && preDefinedName[0] != '\0') { bool found = false; for (int i = 0; i < animDataArray.Length; i++) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 7c7f1950..ecb84f80 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -1255,10 +1255,10 @@ enum struct SF2ChaserBossProfileChaseOnLookData { this.Enabled[difficulty] = false; this.AddTargets[difficulty] = true; - this.MinimumXAngle[difficulty] = 90.0; + this.MinimumXAngle[difficulty] = -45.0; this.MaximumXAngle[difficulty] = 180.0; this.MinimumYAngle[difficulty] = 0.0; - this.MaximumYAngle[difficulty] = 90.0; + this.MaximumYAngle[difficulty] = 105.0; this.RequiredFOV[difficulty] = -1.0; } this.RequiredLookPosition[2] = 35.0; @@ -1493,7 +1493,7 @@ enum struct SF2ChaserBossProfilePostureInfo GetProfileDifficultyFloatValues(kv, "speed", this.Speed, this.Speed); GetProfileDifficultyFloatValues(kv, "walkspeed", this.WalkSpeed, this.WalkSpeed); GetProfileDifficultyFloatValues(kv, "acceleration", this.Acceleration, this.Acceleration); - this.Animations.Load(kv); + this.Animations.Load(kv, false); if (kv.JumpToKey("conditions")) { if (kv.JumpToKey("within_bounds")) @@ -1735,7 +1735,7 @@ enum struct SF2ChaserRageInfo kv.GoBack(); } - this.Animations.Load(kv); + this.Animations.Load(kv, false); } void PostLoad() @@ -1957,6 +1957,8 @@ enum struct SF2ChaserBossProfileData float ChaseDurationAddMaxRange[Difficulty_Max]; float ChaseDurationAddVisibleMin[Difficulty_Max]; float ChaseDurationAddVisibleMax[Difficulty_Max]; + float ChaseDurationAddOnAttack[Difficulty_Max]; + float ChaseDurationAddOnStun[Difficulty_Max]; int SoundCountToAlert[Difficulty_Max]; float SoundPosDiscardTime[Difficulty_Max]; @@ -2028,7 +2030,6 @@ enum struct SF2ChaserBossProfileData bool CriticalProjectiles; bool ShootGestures; char ShootGestureName[PLATFORM_MAX_PATH]; - bool ShootAnimations; bool ProjectileClips; int ProjectileClipSize[Difficulty_Max]; float ProjectileReloadTime[Difficulty_Max]; @@ -2109,6 +2110,11 @@ enum struct SF2ChaserBossProfileData SF2BossProfileSoundInfo TauntKillSounds; SF2BossProfileSoundInfo SmellSounds; SF2BossProfileSoundInfo FootstepSounds; + SF2BossProfileSoundInfo SelfHealSounds; + SF2BossProfileSoundInfo RageSounds1; + SF2BossProfileSoundInfo RageSounds2; + SF2BossProfileSoundInfo RageSounds3; + SF2BossProfileSoundInfo DespawnSounds; ArrayList AttackSounds; ArrayList HitSounds; ArrayList MissSounds; @@ -2147,6 +2153,8 @@ enum struct SF2ChaserBossProfileData this.ChaseDurationAddMaxRange[difficulty] = 1024.0; this.ChaseDurationAddVisibleMin[difficulty] = 0.01; this.ChaseDurationAddVisibleMax[difficulty] = 0.05; + this.ChaseDurationAddOnAttack[difficulty] = 0.0; + this.ChaseDurationAddOnStun[difficulty] = 0.0; this.SoundCountToAlert[difficulty] = 8; this.SoundPosDiscardTime[difficulty] = 2.0; @@ -2253,7 +2261,6 @@ enum struct SF2ChaserBossProfileData this.CriticalProjectiles = false; this.ShootGestures = false; this.ShootGestureName[0] = '\0'; - this.ShootAnimations = false; this.ProjectileClips = false; this.ChargeUpProjectiles = false; this.ProjectileRandomPosMin = 1; @@ -2328,6 +2335,11 @@ enum struct SF2ChaserBossProfileData this.DeathSounds.Init(); this.TauntKillSounds.Init(); this.SmellSounds.Init(); + this.SelfHealSounds.Init(); + this.RageSounds1.Init(); + this.RageSounds2.Init(); + this.RageSounds3.Init(); + this.DespawnSounds.Init(); this.FootstepSounds.Init(); this.AttackSounds = null; this.HitSounds = null; @@ -2370,6 +2382,11 @@ enum struct SF2ChaserBossProfileData this.DeathSounds.PostLoad(); this.TauntKillSounds.PostLoad(); this.SmellSounds.PostLoad(); + this.SelfHealSounds.PostLoad(); + this.RageSounds1.PostLoad(); + this.RageSounds2.PostLoad(); + this.RageSounds3.PostLoad(); + this.DespawnSounds.PostLoad(); this.FootstepSounds.PostLoad(); this.ChaseMusics.PostLoad(); @@ -2500,6 +2517,26 @@ enum struct SF2ChaserBossProfileData { this.SmellSounds.Load(kv, checkFile); } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_SelfHeal]) == 0) + { + this.SelfHealSounds.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageAll]) == 0) + { + this.RageSounds1.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageTwo]) == 0) + { + this.RageSounds2.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_RageThree]) == 0) + { + this.RageSounds3.Load(kv, checkFile); + } + else if (strcmp(sectionName, g_SlenderVoiceList[SF2BossSound_Despawn]) == 0) + { + this.DespawnSounds.Load(kv, checkFile); + } else if (strcmp(sectionName, "sound_footsteps") == 0) { this.FootstepSounds.Load(kv, checkFile); @@ -2522,9 +2559,69 @@ enum struct SF2ChaserBossProfileData } } - void LoadNestedSoundSections(KeyValues kv, bool checkFile, ArrayList &list, bool soundHook = false) + void LoadNestedSoundSections(const char[] section, KeyValues kv, bool checkFile, ArrayList &list, bool soundHook = false, SF2ChaserBossProfileData data) { list = new ArrayList(sizeof(SF2BossProfileSoundInfo)); + bool classic = false; + kv.GoBack(); + for (int i = 0; i < data.Attacks.Length; i++) + { + char formatter[90]; + SF2ChaserBossProfileAttackData attackData; + data.Attacks.GetArray(i, attackData, sizeof(attackData)); + if (strcmp(attackData.Name, "1") == 0) + { + continue; + } + FormatEx(formatter, sizeof(formatter), "%s_%s", section, attackData.Name); + + if (kv.JumpToKey(formatter)) + { + classic = true; + kv.GoBack(); + break; + } + } + if (classic) + { + for (int i = 0; i < data.Attacks.Length; i++) + { + char formatter[90]; + SF2ChaserBossProfileAttackData attackData; + data.Attacks.GetArray(i, attackData, sizeof(attackData)); + if (strcmp(attackData.Name, "1") == 0) + { + FormatEx(formatter, sizeof(formatter), "%s", section); + } + else + { + FormatEx(formatter, sizeof(formatter), "%s_%s", section, attackData.Name); + } + + if (kv.JumpToKey(formatter)) + { + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + strcopy(soundInfo.SectionName, sizeof(soundInfo.SectionName), attackData.Name); + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } + kv.GoBack(); + } + } + return; + } + else + { + kv.JumpToKey(section); + } if (kv.GotoFirstSubKey()) { char sectionName[64]; @@ -2540,6 +2637,7 @@ enum struct SF2ChaserBossProfileData } soundInfo.Load(kv, checkFile); soundInfo.PostLoad(); + soundInfo.SectionName[0] = '\0'; if (soundInfo.Paths != null) { list.PushArray(soundInfo, sizeof(soundInfo)); @@ -2567,6 +2665,23 @@ enum struct SF2ChaserBossProfileData while (kv.GotoNextKey()); kv.GoBack(); } + + } + else + { + SF2BossProfileSoundInfo soundInfo; + soundInfo.Init(); + if (soundHook) + { + soundInfo.Channel = SNDCHAN_VOICE; + } + soundInfo.Load(kv, checkFile); + soundInfo.PostLoad(); + soundInfo.SectionName[0] = '\0'; + if (soundInfo.Paths != null) + { + list.PushArray(soundInfo, sizeof(soundInfo)); + } } kv.GoBack(); } @@ -2604,6 +2719,11 @@ enum struct SF2ChaserBossProfileData this.DeathSounds.Destroy(); this.TauntKillSounds.Destroy(); this.SmellSounds.Destroy(); + this.SelfHealSounds.Destroy(); + this.RageSounds1.Destroy(); + this.RageSounds2.Destroy(); + this.RageSounds3.Destroy(); + this.DespawnSounds.Destroy(); this.FootstepSounds.Destroy(); this.ChaseMusics.Destroy(); diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index fdcf931a..ad154329 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -55,26 +55,6 @@ public Plugin myinfo = url = "https://discord.gg/7Zz7RYTCC4" } -char g_SlenderAnimationsList[SF2BossAnimation_MaxAnimations][] = -{ - "idle", - "walk", - "attack", - "run", - "stun", - "chaseinitial", - "rage", - "spawn", - "fleestart", - "heal", - "deathcam", - "death", - "tauntkill", - "smell", - "attack_begin", - "attack_end" -}; - enum struct MuteMode { int MuteMode_Normal; @@ -118,7 +98,7 @@ static const char g_PageCollectDuckSounds[][] = }; bool g_ClientInGame[MAXTF2PLAYERS] = { false, ... }; -bool g_ClientInCondition[MAXTF2PLAYERS][TFCond_PowerupModeDominant + view_as(1)]; +bool g_ClientInCondition[MAXTF2PLAYERS][TFCond_PowerupModeDominant + view_as(2)]; //Command bool g_PlayerNoPoints[MAXTF2PLAYERS] = { false, ... }; @@ -187,10 +167,6 @@ float g_SlenderDeathCamTime[MAX_BOSSES]; bool g_SlenderCustomOutroSong[MAX_BOSSES]; bool g_SlenderUseCustomOutlines[MAX_BOSSES]; -int g_SlenderOutlineColorR[MAX_BOSSES]; -int g_SlenderOutlineColorG[MAX_BOSSES]; -int g_SlenderOutlineColorB[MAX_BOSSES]; -int g_SlenderOutlineTransparency[MAX_BOSSES]; bool g_SlenderUseRainbowOutline[MAX_BOSSES]; int g_TrapEntityCount; @@ -210,8 +186,6 @@ float g_SlenderTeleportPlayersRestTime[MAX_BOSSES][MAXTF2PLAYERS]; bool g_SlenderTeleportIgnoreChases[MAX_BOSSES]; bool g_SlenderTeleportIgnoreVis[MAX_BOSSES]; -bool g_SlenderInDeathcam[MAX_BOSSES] = { false, ... }; - bool g_SlenderProxiesAllowNormalVoices[MAX_BOSSES]; int g_SlenderBoxingBossCount = 0; @@ -241,7 +215,7 @@ int g_SlenderProxyControlGainHitByEnemy[MAX_BOSSES][Difficulty_Max]; float g_SlenderProxyControlDrainRate[MAX_BOSSES][Difficulty_Max]; int g_SlenderMaxProxies[MAX_BOSSES][Difficulty_Max]; -int g_NightvisionType = 0; +int g_NightVisionType = 0; //Healthbar int g_HealthBar; @@ -570,6 +544,8 @@ ConVar g_DifficultyVoteRevoteConVar; ConVar g_DifficultyNoGracePageConVar; ConVar g_HighDifficultyPercentConVar; ConVar g_FileCheckConVar; +ConVar g_LoadOutsideMapsConVar; +ConVar g_DefaultBossTeamConVar; ConVar g_RestartSessionConVar; bool g_RestartSessionEnabled; @@ -578,14 +554,12 @@ ConVar g_PlayerInfiniteSprintOverrideConVar; ConVar g_PlayerInfiniteFlashlightOverrideConVar; ConVar g_PlayerInfiniteBlinkOverrideConVar; -ConVar g_GravityConVar; -float g_Gravity; ConVar g_MaxRoundsConVar; ConVar g_ForcedHolidayConVar; ConVar g_WeaponCriticalsConVar; ConVar g_PhysicsPushScaleConVar; -ConVar g_DragonsFuryBurningBonus; -ConVar g_DragonsFuryBurnDuration; +ConVar g_DragonsFuryBurningBonusConVar; +ConVar g_DragonsFuryBurnDurationConVar; bool g_IsPlayerShakeEnabled; bool g_PlayerViewbobHurtEnabled; @@ -604,6 +578,7 @@ int g_ShockwaveHalo; char g_DebugBeamSound[PLATFORM_MAX_PATH]; ArrayList g_Buildings; +ArrayList g_WhitelistedEntities; // Global forwards. GlobalForward g_OnBossAddedFwd; @@ -658,11 +633,13 @@ GlobalForward g_OnEverythingLoadedFwd; GlobalForward g_OnDifficultyVoteFinishedFwd; GlobalForward g_OnIsBossCustomAttackPossibleFwd; GlobalForward g_OnBossGetCustomAttackActionFwd; +GlobalForward g_OnProjectileTouchFwd; // Private forwards PrivateForward g_OnGamemodeStartPFwd; -PrivateForward g_OnGamemodeEnd; +PrivateForward g_OnGamemodeEndPFwd; PrivateForward g_OnMapStartPFwd; +PrivateForward g_OnMapEndPFwd; PrivateForward g_OnGameFramePFwd; PrivateForward g_OnRoundStartPFwd; PrivateForward g_OnRoundEndPFwd; @@ -679,10 +656,23 @@ PrivateForward g_OnPlayerEscapePFwd; PrivateForward g_OnPlayerTeamPFwd; PrivateForward g_OnPlayerClassPFwd; PrivateForward g_OnPlayerLookAtBossPFwd; +PrivateForward g_OnPlayerChangePlayStatePFwd; +PrivateForward g_OnPlayerChangeGhostStatePFwd; +PrivateForward g_OnPlayerChangeProxyStatePFwd; +PrivateForward g_OnPlayerConditionAddedPFwd; +PrivateForward g_OnPlayerConditionRemovedPFwd; +PrivateForward g_OnPlayerTurnOnFlashlightPFwd; +PrivateForward g_OnPlayerTurnOffFlashlightPFwd; +PrivateForward g_OnPlayerFlashlightBreakPFwd; +PrivateForward g_OnSpecialRoundStartPFwd; +PrivateForward g_OnBossSpawnPFwd; PrivateForward g_OnBossRemovedPFwd; PrivateForward g_OnChaserGetAttackActionPFwd; PrivateForward g_OnChaserGetCustomAttackPossibleStatePFwd; PrivateForward g_OnChaserUpdatePosturePFwd; +PrivateForward g_OnDifficultyChangePFwd; +PrivateForward g_OnRenevantTriggerWavePFwd; +PrivateForward g_OnWallHaxDebugPFwd; Handle g_SDKGetMaxHealth; Handle g_SDKEquipWearable; @@ -718,6 +708,7 @@ bool g_RenevantBeaconEffect = false; bool g_Renevant90sEffect = false; bool g_RenevantMarkForDeath = false; bool g_RenevantWallHax = false; +bool g_RenevantBossesChaseEndlessly = false; bool g_IsRenevantMap = false; Handle g_RenevantWaveTimer = null; ArrayList g_RenevantWaveList; @@ -736,6 +727,7 @@ int g_FlashlightHaloModel = -1; #endif #include "sf2/stocks.sp" #include "sf2/logging.sp" +#include "sf2/glow.sp" #include "sf2/methodmaps.sp" #include "sf2/classconfigs.sp" #include "sf2/profiles.sp" @@ -843,6 +835,50 @@ public void OnConfigsExecuted() { LogMessage("%s is not a Slender Fortress map. Plugin disabled!", map); StopPlugin(); + if (g_LoadOutsideMapsConVar.BoolValue) + { + InitializeLogging(); + PrecacheStuff(); + ReloadBossProfiles(); + NPCOnConfigsExecuted(); + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_sentrygun")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_teleporter")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "obj_dispenser")) != -1) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tank_boss")) != -1) + { + g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); + } + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i)) + { + continue; + } + g_ClientInGame[i] = true; + SDKHook(i, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); + Call_StartForward(g_OnPlayerPutInServerPFwd); + Call_PushCell(SF2_BasePlayer(i)); + Call_Finish(); + } + } } } else @@ -853,6 +889,10 @@ public void OnConfigsExecuted() for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) { + if (g_BossPathFollower[bossIndex]) + { + continue; + } g_BossPathFollower[bossIndex] = PathFollower(_, TraceRayDontHitAnyEntity_Pathing, Path_FilterOnlyActors); } } @@ -914,8 +954,6 @@ static void StartPlugin() } } - g_Gravity = g_GravityConVar.FloatValue; - g_IsPlayerShakeEnabled = g_PlayerShakeEnabledConVar.BoolValue; g_PlayerViewbobHurtEnabled = g_PlayerViewbobHurtEnabledConVar.BoolValue; g_PlayerViewbobSprintEnabled = g_PlayerViewbobSprintEnabledConVar.BoolValue; @@ -923,14 +961,14 @@ static void StartPlugin() #if defined _SteamWorks_Included if (steamworks) { - SteamWorks_SetGameDescription("SF2 Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); + SteamWorks_SetGameDescription("SF2M Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); steamtools = false; } #endif #if defined _steamtools_included if (steamtools) { - Steam_SetGameDescription("SF2 Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); + Steam_SetGameDescription("SF2M Rewrite ("...PLUGIN_VERSION_DISPLAY...")"); steamworks = false; } #endif @@ -1022,6 +1060,12 @@ static void StartPlugin() g_Buildings.Push(EntIndexToEntRef(ent)); } + ent = -1; + while ((ent = FindEntityByClassname(ent, "tank_boss")) != -1) + { + g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); + } + Call_StartForward(g_OnGamemodeStartPFwd); Call_Finish(); } @@ -1083,6 +1127,8 @@ static void PrecacheStuff() PrecacheSound(ICEBALL_IMPACT); PrecacheSound(ROCKET_SHOOT); PrecacheSound(ROCKET_IMPACT); + PrecacheSound(ROCKET_IMPACT2); + PrecacheSound(ROCKET_IMPACT3); PrecacheSound(GRENADE_SHOOT); PrecacheSound(SENTRYROCKET_SHOOT); PrecacheSound(ARROW_SHOOT); @@ -1236,8 +1282,6 @@ static void StopPlugin() { ClientResetFlashlight(i); ClientDeactivateUltravision(i); - ClientDisableConstantGlow(i); - ClientRemoveInteractiveGlow(i); g_TimerChangeClientName[i] = null; } @@ -1250,7 +1294,7 @@ static void StopPlugin() } } - Call_StartForward(g_OnGamemodeEnd); + Call_StartForward(g_OnGamemodeEndPFwd); Call_Finish(); if (g_FuncNavPrefer != null) @@ -1263,7 +1307,7 @@ static void StopPlugin() public void OnMapEnd() { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return; } @@ -1277,10 +1321,14 @@ public void OnMapEnd() g_RenevantBeaconEffect = false; g_Renevant90sEffect = false; g_RenevantMarkForDeath = false; + g_RenevantBossesChaseEndlessly = false; g_RenevantWallHax = false; BossProfilesOnMapEnd(); NPCRemoveAll(); + + Call_StartForward(g_OnMapEndPFwd); + Call_Finish(); } public void OnMapTimeLeftChanged() @@ -1318,6 +1366,7 @@ public void TF2_OnConditionAdded(int client, TFCond cond) player.HandleFlashlight(); } } + if (cond == TFCond_HalloweenKart) { if (player.IsProxy) @@ -1330,11 +1379,26 @@ public void TF2_OnConditionAdded(int client, TFCond cond) player.ChangeCondition(TFCond_SpawnOutline, true); } } + + Call_StartForward(g_OnPlayerConditionAddedPFwd); + Call_PushCell(player); + Call_PushCell(cond); + Call_Finish(); } public void TF2_OnConditionRemoved(int client, TFCond condition) { g_ClientInCondition[client][condition] = false; + + if (!g_Enabled) + { + return; + } + + Call_StartForward(g_OnPlayerConditionRemovedPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(condition); + Call_Finish(); } static Action Timer_GlobalGameFrame(Handle timer) @@ -1414,206 +1478,203 @@ static Action Timer_GlobalGameFrame(Handle timer) // Check if we can add some proxies. if (IsRoundPlaying() && !SF_IsRenevantMap() && !SF_IsSlaughterRunMap() && !SF_IsBoxingMap()) { - ArrayList proxyCandidates = new ArrayList(); + ArrayList proxyCandidates = new ArrayList(); - for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + for (int bossIndex = 0; bossIndex < MAX_BOSSES; bossIndex++) + { + if (NPCGetUniqueID(bossIndex) == -1) + { + continue; + } + + if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) + { + continue; + } + + if (g_SlenderCopyMaster[bossIndex] != -1) { - if (NPCGetUniqueID(bossIndex) == -1) + continue; // Copies cannot generate proxies. + } + + if (GetGameTime() < g_SlenderTimeUntilNextProxy[bossIndex]) + { + continue; // Proxy spawning hasn't cooled down yet. + } + + int teleportTarget = EntRefToEntIndex(g_SlenderProxyTarget[bossIndex]); + if (!teleportTarget || teleportTarget == INVALID_ENT_REFERENCE) + { + continue; // No teleport target. + } + + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; + if (g_InProxySurvivalRageMode) + { + maxProxies += 5; + } + + int numActiveProxies = 0; + + for (int client = 1; client <= MaxClients; client++) + { + if (!IsValidClient(client) || !g_PlayerEliminated[client]) { continue; } - - if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) + if (!g_PlayerProxy[client]) { continue; } - if (g_SlenderCopyMaster[bossIndex] != -1) + if (NPCGetFromUniqueID(g_PlayerProxyMaster[client]) == bossIndex) { - continue; // Copies cannot generate proxies. + numActiveProxies++; } + } + if (numActiveProxies >= maxProxies) + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d has too many active proxies!", bossIndex); + #endif + continue; + } - if (GetGameTime() < g_SlenderTimeUntilNextProxy[bossIndex]) - { - continue; // Proxy spawning hasn't cooled down yet. - } + float spawnChanceMin = NPCGetProxySpawnChanceMin(bossIndex, difficulty); + float spawnChanceMax = NPCGetProxySpawnChanceMax(bossIndex, difficulty); + float spawnChanceThreshold = NPCGetProxySpawnChanceThreshold(bossIndex, difficulty); - int teleportTarget = EntRefToEntIndex(g_SlenderProxyTarget[bossIndex]); - if (!teleportTarget || teleportTarget == INVALID_ENT_REFERENCE) - { - continue; // No teleport target. - } + float chance = GetRandomFloat(spawnChanceMin, spawnChanceMax); + if (chance > spawnChanceThreshold && !g_InProxySurvivalRageMode) + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d's chances weren't in his favor!", bossIndex); + #endif + continue; + } - int difficulty = GetLocalGlobalDifficulty(bossIndex); + int availableProxies = maxProxies - numActiveProxies; - int maxProxies = g_SlenderMaxProxies[bossIndex][difficulty]; - if (g_InProxySurvivalRageMode) - { - maxProxies += 5; - } + int spawnNumMin = NPCGetProxySpawnNumMin(bossIndex, difficulty); + int spawnNumMax = NPCGetProxySpawnNumMax(bossIndex, difficulty); - int numActiveProxies = 0; + int spawnNum = 0; - for (int client = 1; client <= MaxClients; client++) - { - if (!IsValidClient(client) || !g_PlayerEliminated[client]) - { - continue; - } - if (!g_PlayerProxy[client]) - { - continue; - } + // Get a list of people we can transform into a good Proxy. + proxyCandidates.Clear(); - if (NPCGetFromUniqueID(g_PlayerProxyMaster[client]) == bossIndex) - { - numActiveProxies++; - } + for (int client = 1; client <= MaxClients; client++) + { + if (!IsValidClient(client) || !g_PlayerEliminated[client] || GetClientTeam(client) == TFTeam_Red) + { + continue; } - if (numActiveProxies >= maxProxies) + if (g_PlayerProxy[client]) { - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d has too many active proxies!", bossIndex); - //PrintToChatAll("[PROXIES] Boss %d has too many active proxies!", bossIndex); - #endif continue; } - float spawnChanceMin = NPCGetProxySpawnChanceMin(bossIndex, difficulty); - float spawnChanceMax = NPCGetProxySpawnChanceMax(bossIndex, difficulty); - float spawnChanceThreshold = NPCGetProxySpawnChanceThreshold(bossIndex, difficulty); - - float chance = GetRandomFloat(spawnChanceMin, spawnChanceMax); - if (chance > spawnChanceThreshold && !g_InProxySurvivalRageMode) + if (!g_PlayerPreferences[client].PlayerPreference_EnableProxySelection && !IsFakeClient(client)) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d's chances weren't in his favor!", bossIndex); - //PrintToChatAll("[PROXIES] Boss %d's chances weren't in his favor!", bossIndex); + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your preferences.", bossIndex); #endif continue; } - int availableProxies = maxProxies - numActiveProxies; - - int spawnNumMin = NPCGetProxySpawnNumMin(bossIndex, difficulty); - int spawnNumMax = NPCGetProxySpawnNumMax(bossIndex, difficulty); - - int spawnNum = 0; - - // Get a list of people we can transform into a good Proxy. - proxyCandidates.Clear(); - - for (int client = 1; client <= MaxClients; client++) + if (!g_PlayerProxyAvailable[client]) { - if (!IsValidClient(client) || !g_PlayerEliminated[client] || GetClientTeam(client) == TFTeam_Red) - { - continue; - } - if (g_PlayerProxy[client]) - { - continue; - } - - if (!g_PlayerPreferences[client].PlayerPreference_EnableProxySelection && !IsFakeClient(client)) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your preferences.", bossIndex); - //PrintToChatAll("[PROXIES] You were rejected for being a proxy for boss %d because of your preferences.", bossIndex); - #endif - continue; - } - - if (!g_PlayerProxyAvailable[client]) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your cooldown.", bossIndex); - #endif - continue; - } - - if (g_PlayerProxyAvailableInForce[client]) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're already being forced into a Proxy.", bossIndex); - #endif - continue; - } - - if (!IsClientParticipating(client)) - { - #if defined DEBUG - SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're not participating.", bossIndex); - #endif - continue; - } - - proxyCandidates.Push(client); - spawnNum++; + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because of your cooldown.", bossIndex); + #endif + continue; } - if (spawnNum >= spawnNumMax) - { - spawnNum = GetRandomInt(spawnNumMin, spawnNumMax); - } - else if (spawnNum >= spawnNumMin) + if (g_PlayerProxyAvailableInForce[client]) { - spawnNum = GetRandomInt(spawnNumMin, spawnNum); + #if defined DEBUG + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're already being forced into a Proxy.", bossIndex); + #endif + continue; } - if (spawnNum <= 0) + if (!IsClientParticipating(client)) { #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d had a set spawn number of 0!", bossIndex); + SendDebugMessageToPlayer(client, DEBUG_BOSS_PROXIES, 0, "[PROXIES] You were rejected for being a proxy for boss %d because you're not participating.", bossIndex); #endif continue; } - bool cooldown = false; - // Randomize the array. - SortADTArray(proxyCandidates, Sort_Random, Sort_Integer); - float destinationPos[3]; + proxyCandidates.Push(client); + spawnNum++; + } - for (int num = 0; num < spawnNum && num < availableProxies; num++) - { - int client = proxyCandidates.Get(num); - int spawnPointEnt = -1; + if (spawnNum >= spawnNumMax) + { + spawnNum = GetRandomInt(spawnNumMin, spawnNumMax); + } + else if (spawnNum >= spawnNumMin) + { + spawnNum = GetRandomInt(spawnNumMin, spawnNum); + } - if (!SpawnProxy(client, bossIndex, destinationPos, spawnPointEnt)) - { - break; - } - cooldown = true; - if (!g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage) - { - ClientStartProxyForce(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); - } - else - { - if (!IsRoundEnding() && !IsRoundInWarmup() && !IsRoundInIntro()) - { - DisplayProxyAskMenu(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); - } - } + if (spawnNum <= 0) + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0, "[PROXIES] Boss %d had a set spawn number of 0!", bossIndex); + #endif + continue; + } + bool cooldown = false; + // Randomize the array. + SortADTArray(proxyCandidates, Sort_Random, Sort_Integer); + + float destinationPos[3]; + + for (int num = 0; num < spawnNum && num < availableProxies; num++) + { + int client = proxyCandidates.Get(num); + int spawnPointEnt = -1; + + if (!SpawnProxy(client, bossIndex, destinationPos, spawnPointEnt)) + { + break; } - // Set the cooldown time! - if (cooldown) + cooldown = true; + if (!g_PlayerPreferences[client].PlayerPreference_ProxyShowMessage) { - float spawnCooldownMin = NPCGetProxySpawnCooldownMin(bossIndex, difficulty); - float spawnCooldownMax = NPCGetProxySpawnCooldownMax(bossIndex, difficulty); - - g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(spawnCooldownMin, spawnCooldownMax); + ClientStartProxyForce(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); } else { - g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(3.0, 4.0); + if (!IsRoundEnding() && !IsRoundInWarmup() && !IsRoundInIntro()) + { + DisplayProxyAskMenu(client, NPCGetUniqueID(bossIndex), destinationPos, spawnPointEnt); + } } + } + // Set the cooldown time! + if (cooldown) + { + float spawnCooldownMin = NPCGetProxySpawnCooldownMin(bossIndex, difficulty); + float spawnCooldownMax = NPCGetProxySpawnCooldownMax(bossIndex, difficulty); - #if defined DEBUG - SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0,"[PROXIES] Boss %d finished proxy process!", bossIndex); - #endif + g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(spawnCooldownMin, spawnCooldownMax); + } + else + { + g_SlenderTimeUntilNextProxy[bossIndex] = GetGameTime() + GetRandomFloat(3.0, 4.0); } - delete proxyCandidates; + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_PROXIES, 0,"[PROXIES] Boss %d finished proxy process!", bossIndex); + #endif + } + + delete proxyCandidates; } Call_StartForward(g_OnGameFramePFwd); @@ -2135,26 +2196,6 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) g_RoundDifficultyModifier = DIFFICULTYMODIFIER_NORMAL; } } - CheckIfMusicValid(); - if (MusicActive()) - { - for (int i = 1; i <= MaxClients; i++) - { - SF2_BasePlayer client = SF2_BasePlayer(i); - if (!client.IsValid || client.IsSourceTV) - { - continue; - } - - char path[PLATFORM_MAX_PATH]; - GetBossMusic(path, sizeof(path)); - if (path[0] != '\0') - { - StopSound(i, MUSIC_CHAN, path); - } - client.UpdateMusicSystem(); - } - } ChangeAllSlenderModels(); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { @@ -2201,10 +2242,6 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { g_PlayerViewbobSprintEnabled = StringToInt(intValue) != 0; } - else if (cvar == g_GravityConVar) - { - g_Gravity = StringToFloat(intValue); - } else if (cvar == g_AllChatConVar || SF_IsBoxingMap()) { if (g_Enabled) @@ -2355,6 +2392,22 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) } } } + else if (cvar == g_NightvisionEnabledConVar) + { + if (!IsInfiniteFlashlightEnabled()) + { + g_NightVisionType = GetRandomInt(0, 2); + } + else + { + g_NightVisionType = 1; + } + } + else if (cvar == g_EnableWallHaxConVar) + { + Call_StartForward(g_OnWallHaxDebugPFwd); + Call_Finish(); + } } static void ToggleWeaponCooldowns(SF2_BasePlayer client, bool invert) @@ -2379,6 +2432,17 @@ static void ToggleWeaponCooldowns(SF2_BasePlayer client, bool invert) public void OnEntityCreated(int ent, const char[] classname) { + if (strcmp(classname, "obj_sentrygun", false) == 0 || strcmp(classname, "obj_dispenser", false) == 0 || + strcmp(classname, "obj_teleporter", false) == 0) + { + g_Buildings.Push(EntIndexToEntRef(ent)); + } + + if (strcmp(classname, "tank_boss", false) == 0) + { + g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); + } + if (!g_Enabled) { return; @@ -2409,11 +2473,6 @@ public void OnEntityCreated(int ent, const char[] classname) { SetEntProp(ent, Prop_Send, "m_bIceRagdoll", true); } - else if (strcmp(classname, "obj_sentrygun", false) == 0 || strcmp(classname, "obj_dispenser", false) == 0 || - strcmp(classname, "obj_teleporter", false) == 0) - { - g_Buildings.Push(EntIndexToEntRef(ent)); - } Call_StartForward(g_OnEntityCreatedPFwd); Call_PushCell(CBaseEntity(ent)); @@ -2456,19 +2515,36 @@ MRESReturn Hook_WeaponGetCustomDamageType(int weapon, DHookReturn returnHandle, public void OnEntityDestroyed(int ent) { - if (!g_Enabled) + if (!IsValidEntity(ent) || ent <= 0) { return; } - if (!IsValidEntity(ent) || ent <= 0) + int index = g_Buildings.FindValue(EntIndexToEntRef(ent)); + if (index != -1) { - return; + g_Buildings.Erase(index); + } + + index = g_WhitelistedEntities.FindValue(EntIndexToEntRef(ent)); + if (index != -1) + { + g_WhitelistedEntities.Erase(index); } char classname[64]; GetEntityClassname(ent, classname, sizeof(classname)); + Call_StartForward(g_OnEntityDestroyedPFwd); + Call_PushCell(CBaseEntity(ent)); + Call_PushString(classname); + Call_Finish(); + + if (!g_Enabled) + { + return; + } + if (strcmp(classname, "light_dynamic", false) == 0) { AcceptEntityInput(ent, "TurnOff"); @@ -2494,17 +2570,6 @@ public void OnEntityDestroyed(int ent) RemoveEntity(glow); } } - - int index = g_Buildings.FindValue(EntIndexToEntRef(ent)); - if (index != -1) - { - g_Buildings.Erase(index); - } - - Call_StartForward(g_OnEntityDestroyedPFwd); - Call_PushCell(CBaseEntity(ent)); - Call_PushString(classname); - Call_Finish(); } Action Hook_BlockUserMessage(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init) @@ -2696,7 +2761,7 @@ MRESReturn Hook_EntityShouldTransmit(int entity, DHookReturn returnHandle, DHook SF2_BasePlayer client = SF2_BasePlayer(entity); if (client.IsValid) { - if (client.HasConstantGlow) + if (DoesEntityHaveGlow(client.index)) { returnHandle.Value = FL_EDICT_ALWAYS; // Should always transmit, but our SetTransmit hook gets the final say. return MRES_Supercede; @@ -3467,6 +3532,14 @@ public void OnClientPutInServer(int client) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue) + { + g_ClientInGame[client] = true; + SDKHook(client, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); + Call_StartForward(g_OnPlayerPutInServerPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); + } return; } @@ -3535,9 +3608,6 @@ public void OnClientPutInServer(int client) ClientUpdateListeningFlags(client); ClientResetScare(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - ClientSetScareBoostEndTime(client, -1.0); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) @@ -3662,8 +3732,6 @@ public void OnClientDisconnect(int client) // Reset any client functions that may be still active. ClientResetOverlay(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); if (SF_IsBoxingMap() && IsRoundInEscapeObjective()) { @@ -3779,14 +3847,6 @@ void SetRoundState(SF2RoundState roundState) case SF2RoundState_Intro: { g_RoundIntroTimer = null; - if (!IsInfiniteFlashlightEnabled()) - { - g_NightvisionType = GetRandomInt(0, 1); - } - else - { - g_NightvisionType = 1; - } // Enable movement on players. for (int i = 1; i <= MaxClients; i++) @@ -3904,6 +3964,7 @@ void SetRoundState(SF2RoundState roundState) g_RenevantBeaconEffect = false; g_Renevant90sEffect = false; g_RenevantMarkForDeath = false; + g_RenevantBossesChaseEndlessly = false; g_DifficultyConVar.SetInt(Difficulty_Normal); if (g_RestartSessionConVar.BoolValue) { @@ -3931,6 +3992,15 @@ void SetRoundState(SF2RoundState roundState) } case SF2RoundState_Grace: { + if (!IsInfiniteFlashlightEnabled()) + { + g_NightVisionType = GetRandomInt(0, 2); + } + else + { + g_NightVisionType = 1; + } + // Start the grace period timer. g_RoundGraceTimer = CreateTimer(g_GraceTimeConVar.FloatValue, Timer_RoundGrace, _, TIMER_FLAG_NO_MAPCHANGE); @@ -4173,8 +4243,24 @@ ArrayList GetQueueList() return array; } -void SetClientPlayState(int client, bool state, bool enablePlay = true) +void SetClientPlayState(int client, bool state, bool enablePlay = true, bool queue = true) { + if (g_PlayerEliminated[client] == !state) + { + return; + } + + if (g_PlayerProxy[client]) + { + return; + } + + Call_StartForward(g_OnPlayerChangePlayStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(state); + Call_PushCell(queue); + Call_Finish(); + Handle message = StartMessageAll("PlayerTauntSoundLoopEnd", USERMSG_RELIABLE); BfWriteByte(message, client); delete message; @@ -4182,15 +4268,6 @@ void SetClientPlayState(int client, bool state, bool enablePlay = true) if (state) { - if (!g_PlayerEliminated[client]) - { - return; - } - if (g_PlayerProxy[client]) - { - return; - } - g_PlayerCalledForNightmare[client] = false; g_PlayerEliminated[client] = false; g_PlayerPlaying[client] = enablePlay; @@ -4220,17 +4297,11 @@ void SetClientPlayState(int client, bool state, bool enablePlay = true) } else { - if (g_PlayerEliminated[client]) - { - return; - } - g_PlayerEliminated[client] = true; g_PlayerPlaying[client] = false; ChangeClientTeamNoSuicide(client, TFTeam_Blue); } - } /* bool DidClientPlayNewBossRound(int client) @@ -4520,6 +4591,7 @@ void SlenderOnClientStressUpdate(int client) proxyArray.Push(i); } } + if (proxyArray.Length > 0) { int proxyClient = proxyArray.Get(GetRandomInt(0, proxyArray.Length - 1)); @@ -4534,7 +4606,16 @@ void SlenderOnClientStressUpdate(int client) Npc.GetProfile(profile, sizeof(profile)); - int teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[Npc.Index]); + int teleportTarget = EntRefToEntIndex(g_SlenderProxyTarget[Npc.Index]); + if (teleportTarget && teleportTarget != INVALID_ENT_REFERENCE) + { + if (g_PlayerEliminated[teleportTarget] || DidClientEscape(teleportTarget)) + { + g_SlenderProxyTarget[Npc.Index] = INVALID_ENT_REFERENCE; + } + } + + teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[Npc.Index]); if (teleportTarget && teleportTarget != INVALID_ENT_REFERENCE && !g_PlayerIsExitCamping[teleportTarget]) { if (g_PlayerEliminated[teleportTarget] || DidClientEscape(teleportTarget) || @@ -5157,7 +5238,7 @@ static Action Timer_SlaughterRunSpawnBosses(Handle timer) { continue; } - Npc.UnSpawn(); + Npc.UnSpawn(true); if (spawnPoint.Length > 0) { ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); @@ -7980,6 +8061,7 @@ void InitializeNewGame() g_RenevantBeaconEffect = false; g_Renevant90sEffect = false; g_RenevantMarkForDeath = false; + g_RenevantBossesChaseEndlessly = false; Renevant_SetWave(0); } diff --git a/addons/sourcemod/scripting/sf2/adminmenu.sp b/addons/sourcemod/scripting/sf2/adminmenu.sp index 5f5cc430..90211f03 100644 --- a/addons/sourcemod/scripting/sf2/adminmenu.sp +++ b/addons/sourcemod/scripting/sf2/adminmenu.sp @@ -920,6 +920,12 @@ static bool DisplayOverrideBossAdminMenu(int client) { strcopy(displayName, sizeof(displayName), profile); } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.IsPvEBoss) + { + continue; + } menuHandle.AddItem(profile, displayName); } diff --git a/addons/sourcemod/scripting/sf2/anticamping.sp b/addons/sourcemod/scripting/sf2/anticamping.sp index be5cc650..b7477de5 100644 --- a/addons/sourcemod/scripting/sf2/anticamping.sp +++ b/addons/sourcemod/scripting/sf2/anticamping.sp @@ -48,6 +48,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { ClientResetCampingStats(client.index); diff --git a/addons/sourcemod/scripting/sf2/changelog.sp b/addons/sourcemod/scripting/sf2/changelog.sp index 6617cbc0..9be812cc 100644 --- a/addons/sourcemod/scripting/sf2/changelog.sp +++ b/addons/sourcemod/scripting/sf2/changelog.sp @@ -20,6 +20,11 @@ void InitializeChangelog() static void OnPlayerSpawn(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + if (client.Team > 1 && !g_SeeUpdateMenu[client.index]) { g_SeeUpdateMenu[client.index] = true; diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index 0974d60d..ed100341 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -11,14 +11,6 @@ //Client Special Round Timer static Handle g_ClientSpecialRoundTimer[MAXTF2PLAYERS]; -// Interactive glow data. -static int g_PlayerInteractiveGlowEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static int g_PlayerInteractiveGlowTargetEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; - -// Constant glow data. -static int g_PlayerGlowEntity[MAXTF2PLAYERS] = { INVALID_ENT_REFERENCE, ... }; -static bool g_PlayerConstantGlowEnabled[MAXTF2PLAYERS] = { false, ... }; - // Jumpscare data. static int g_PlayerJumpScareBoss[MAXTF2PLAYERS] = { -1, ... }; static float g_PlayerJumpScareLifeTime[MAXTF2PLAYERS] = { -1.0, ... }; @@ -32,6 +24,8 @@ int g_ClientFrame[MAXTF2PLAYERS]; //Nav Data //static CNavArea g_lastNavArea[MAXTF2PLAYERS]; +#include "sf2/client/glow.sp" +#include "sf2/client/interactables.sp" #include "sf2/client/hints.sp" #include "sf2/client/think.sp" #include "sf2/client/static.sp" @@ -46,6 +40,11 @@ int g_ClientFrame[MAXTF2PLAYERS]; #include "sf2/client/music.sp" #include "sf2/client/proxy.sp" +void Client_SetupAPI() +{ + Sprint_SetupAPI(); +} + // ========================================================== // GENERAL CLIENT HOOK FUNCTIONS // ========================================================== @@ -278,8 +277,6 @@ void ClientEscape(int client) ClientResetScare(client); ClientDeactivateUltravision(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { @@ -746,7 +743,7 @@ void ClientProcessVisibility(int client) g_PlayerStaticMaster[client] = -1; } - if (bossNewStatic != bossLastStatic) + if (bossNewStatic != bossLastStatic && !DidClientEscape(client)) { if (strcmp(g_PlayerLastStaticSound[client], g_PlayerStaticSound[client], false) != 0) { @@ -1039,184 +1036,7 @@ Action Timer_ClientPageDetector(Handle timer, int userid) } // ========================================================== -// INTERACTIVE GLOW FUNCTIONS -// ========================================================== - -void ClientProcessInteractiveGlow(int client) -{ - if (!IsValidClient(client) || !IsPlayerAlive(client) || (g_PlayerEliminated[client] && !g_PlayerProxy[client]) || IsClientInGhostMode(client)) - { - return; - } - - int oldLookEntity = EntRefToEntIndex(g_PlayerInteractiveGlowTargetEntity[client]); - - float startPos[3], myEyeAng[3]; - GetClientEyePosition(client, startPos); - GetClientEyeAngles(client, myEyeAng); - - Handle trace = TR_TraceRayFilterEx(startPos, myEyeAng, MASK_VISIBLE, RayType_Infinite, TraceRayDontHitPlayers, -1); - int entity = TR_GetEntityIndex(trace); - delete trace; - - if (IsValidEntity(entity)) - { - g_PlayerInteractiveGlowTargetEntity[client] = EntRefToEntIndex(entity); - } - else - { - g_PlayerInteractiveGlowTargetEntity[client] = INVALID_ENT_REFERENCE; - } - - if (entity != oldLookEntity) - { - ClientRemoveInteractiveGlow(client); - - if (IsEntityClassname(entity, "prop_dynamic", false) || IsEntityClassname(entity, "tf_taunt_prop", false)) - { - char targetName[64]; - GetEntPropString(entity, Prop_Data, "m_iName", targetName, sizeof(targetName)); - - if (StrContains(targetName, "sf2_page", false) == 0 || StrContains(targetName, "sf2_interact", false) == 0) - { - ClientCreateInteractiveGlow(client, entity); - } - } - } -} - -void ClientResetInteractiveGlow(int client) -{ - ClientRemoveInteractiveGlow(client); - g_PlayerInteractiveGlowTargetEntity[client] = INVALID_ENT_REFERENCE; -} - -/** - * Removes the player's current interactive glow entity. - */ -void ClientRemoveInteractiveGlow(int client) -{ - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientRemoveInteractiveGlow(%d)", client); - } - #endif - - int ent = EntRefToEntIndex(g_PlayerInteractiveGlowEntity[client]); - if (ent && ent != INVALID_ENT_REFERENCE) - { - RemoveEntity(ent); - } - - g_PlayerInteractiveGlowEntity[client] = INVALID_ENT_REFERENCE; - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientRemoveInteractiveGlow(%d)", client); - } - #endif -} - -/** - * Creates an interactive glow for an entity to show to a player. - */ -static bool ClientCreateInteractiveGlow(int client,int entity, const char[] attachment="") -{ - ClientRemoveInteractiveGlow(client); - - if (!IsValidClient(client)) - { - return false; - } - - if (!entity || !IsValidEdict(entity)) - { - return false; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("START ClientCreateInteractiveGlow(%d)", client); - } - #endif - - char buffer[PLATFORM_MAX_PATH]; - GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - - if (buffer[0] == '\0') - { - return false; - } - - int ent = CreateEntityByName("tf_taunt_prop"); - if (ent != -1) - { - g_PlayerInteractiveGlowEntity[client] = EntIndexToEntRef(ent); - - float modelScale = GetEntPropFloat(entity, Prop_Send, "m_flModelScale"); - - SetEntityModel(ent, buffer); - DispatchSpawn(ent); - ActivateEntity(ent); - SetEntityRenderMode(ent, RENDER_TRANSCOLOR); - SetEntityRenderColor(ent, 0, 0, 0, 0); - SetEntPropFloat(ent, Prop_Send, "m_flModelScale", modelScale); - - int flags = GetEntProp(ent, Prop_Send, "m_fEffects"); - SetEntProp(ent, Prop_Send, "m_fEffects", flags | (1 << 0)); - SetEntProp(ent, Prop_Send, "m_bGlowEnabled", true); - - SetVariantString("!activator"); - AcceptEntityInput(ent, "SetParent", entity); - - if (attachment[0] != '\0') - { - SetVariantString(attachment); - AcceptEntityInput(ent, "SetParentAttachment"); - } - - SDKHook(ent, SDKHook_SetTransmit, Hook_InterativeGlowSetTransmit); - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientCreateInteractiveGlow(%d) -> true", client); - } - #endif - - return true; - } - - #if defined DEBUG - if (g_DebugDetailConVar.IntValue > 2) - { - DebugMessage("END ClientCreateInteractiveGlow(%d) -> false", client); - } - #endif - - return false; -} - -static Action Hook_InterativeGlowSetTransmit(int ent, int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - if (EntRefToEntIndex(g_PlayerInteractiveGlowEntity[other]) != ent) - { - return Plugin_Handled; - } - - return Plugin_Continue; -} - -// ========================================================== -// GHOST AND GLOW FUNCTIONS +// GHOST FUNCTIONS // ========================================================== Action Timer_ClassScramblePlayer(Handle timer, any userid) @@ -1266,66 +1086,6 @@ Action Timer_ClassScramblePlayer2(Handle timer, any userid) return Plugin_Stop; } -bool DoesClientHaveConstantGlow(int client) -{ - return g_PlayerConstantGlowEnabled[client]; -} - -void ClientDisableConstantGlow(int client) -{ - if (!DoesClientHaveConstantGlow(client)) - { - return; - } - - int glow = EntRefToEntIndex(g_PlayerGlowEntity[client]); - if (glow && glow != INVALID_ENT_REFERENCE) - { - RemoveEntity(glow); - g_PlayerGlowEntity[client] = INVALID_ENT_REFERENCE; - } - - g_PlayerConstantGlowEnabled[client] = false; -} - -void ClientEnableConstantGlow(int client, int color[4] = {255, 255, 255, 255}) -{ - if (!IsValidClient(client)) - { - return; - } - - if (DoesClientHaveConstantGlow(client) || g_PlayerGlowEntity[client] != INVALID_ENT_REFERENCE) - { - return; - } - - char model[PLATFORM_MAX_PATH]; - GetEntPropString(client, Prop_Data, "m_ModelName", model, sizeof(model)); - - if (model[0] == '\0') - { - // For some reason the model couldn't be found, so no. - return; - } - - int glow = TF2_CreateGlow(client); - if (IsValidEntity(glow)) - { - g_PlayerConstantGlowEnabled[client] = true; - - SDKHook(glow, SDKHook_SetTransmit, Hook_ConstantGlowSetTransmit); - - g_PlayerGlowEntity[client] = EntIndexToEntRef(glow); - //Set our desired glow color - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); - g_DHookShouldTransmit.HookEntity(Hook_Pre, glow, Hook_EntityShouldTransmit); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow, Hook_GlowUpdateTransmitState); - } -} - void ClientResetJumpScare(int client) { #if defined DEBUG @@ -1865,67 +1625,13 @@ void ClientViewPunch(int client, const float angleOffset[3]) SetEntDataVector(client, g_PlayerPunchAngleOffsetVel, offset, true); } -static Action Hook_ConstantGlowSetTransmit(int ent, int other) +void ClientSetFOV(int client, int fov) { if (!g_Enabled) { - return Plugin_Continue; - } - - int owner = GetEntPropEnt(ent, Prop_Send, "m_hTarget"); - if (owner == other) - { - return Plugin_Handled; - } - if (!IsValidClient(other)) - { - return Plugin_Handled; - } - if (!IsPlayerAlive(other)) - { - return Plugin_Handled; - } - if (g_PlayerProxy[other]) - { - if (!IsValidClient(owner)) - { - return Plugin_Handled; - } - if (g_PlayerProxy[owner] || TF2_IsPlayerInCondition(owner, TFCond_Taunting)) - { - return Plugin_Continue; - } - - float velocity[3], moveSpeed; - GetEntPropVector(other, Prop_Data, "m_vecAbsVelocity", velocity); - moveSpeed = GetVectorLength(velocity, true); - - if (moveSpeed <= SquareFloat(150.0)) - { - float ownerPos[3], proxyPos[3]; - GetClientEyePosition(owner, ownerPos); - GetClientEyePosition(other, proxyPos); - - float distance = SquareFloat(500.0); - if (GetVectorSquareMagnitude(ownerPos, proxyPos) <= distance) - { - return Plugin_Continue; - } - } - } - if (IsClientInGhostMode(other)) - { - return Plugin_Continue; - } - if ((SF_SpecialRound(SPECIALROUND_WALLHAX) || g_EnableWallHaxConVar.BoolValue || g_RenevantWallHax) && ((GetClientTeam(other) == TFTeam_Red && !DidClientEscape(other) && !g_PlayerEliminated[other]) || (g_PlayerProxy[other]))) - { - return Plugin_Continue; + return; } - return Plugin_Handled; -} -void ClientSetFOV(int client,int fov) -{ SetEntData(client, g_PlayerFOVOffset, fov); SetEntData(client, g_PlayerDefaultFOVOffset, fov); } diff --git a/addons/sourcemod/scripting/sf2/client/blink.sp b/addons/sourcemod/scripting/sf2/client/blink.sp index 6a7f72cd..e8e9935b 100644 --- a/addons/sourcemod/scripting/sf2/client/blink.sp +++ b/addons/sourcemod/scripting/sf2/client/blink.sp @@ -21,6 +21,10 @@ void SetupBlink() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } ClientResetBlink(client.index); if (!client.IsSourceTV) @@ -36,6 +40,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { ClientResetBlink(client.index); diff --git a/addons/sourcemod/scripting/sf2/client/breathing.sp b/addons/sourcemod/scripting/sf2/client/breathing.sp index d3e6dce3..7e841929 100644 --- a/addons/sourcemod/scripting/sf2/client/breathing.sp +++ b/addons/sourcemod/scripting/sf2/client/breathing.sp @@ -21,6 +21,10 @@ void SetupBreathing() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } ClientResetBreathing(client.index); SDKHook(client.index, SDKHook_PreThink, Hook_BreathingThink); @@ -33,6 +37,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { ClientResetBreathing(client.index); diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index 623162f3..421ae644 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -120,32 +120,35 @@ static void ClientResetDeathCam(int client) SetClientViewEntity(client, client); } - SF2BossProfileData data; - data = NPCGetProfileData(deathCamBoss); - if (data.DeathCamData.Enabled && data.DeathCamData.Blackout) + if (deathCamBoss != -1) { - UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); - CreateTimer(0.1, Timer_DeleteRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - TeleportEntity(client, {16000.0, 16000.0, 16000.0}); - - SF2BossProfileSoundInfo soundInfo; - soundInfo = data.DeathCamData.ExecutionSounds; - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + SF2BossProfileData data; + data = NPCGetProfileData(deathCamBoss); + if (data.DeathCamData.Enabled && data.DeathCamData.Blackout) { - for (int i = 0; i < soundInfo.Paths.Length; i++) + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + CreateTimer(0.1, Timer_DeleteRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + TeleportEntity(client, {16000.0, 16000.0, 16000.0}); + + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.DeathCamData.ExecutionSounds; + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) { - soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER, _, _, _, i); + for (int i = 0; i < soundInfo.Paths.Length; i++) + { + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER, _, _, _, i); + } } + + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); } - UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + Call_StartForward(g_OnClientEndDeathCamFwd); + Call_PushCell(client); + Call_PushCell(deathCamBoss); + Call_Finish(); } - Call_StartForward(g_OnClientEndDeathCamFwd); - Call_PushCell(client); - Call_PushCell(deathCamBoss); - Call_Finish(); - #if defined DEBUG if (g_DebugDetailConVar.IntValue > 2) { @@ -250,8 +253,8 @@ void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool } CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); - - SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); + float damage = float(GetEntProp(client, Prop_Send, "m_iHealth")) * 4.0; + SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, damage, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. KillClient(client); return; @@ -476,8 +479,8 @@ static void StopDeathCam(int client) Call_Finish(); CBaseEntity boss = CBaseEntity(deathCamBoss.EntIndex); - - SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, 9001.0, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); + float damage = float(GetEntProp(client, Prop_Send, "m_iHealth")) * 4.0; + SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, damage, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. KillClient(client); } @@ -510,11 +513,6 @@ static void StopDeathCam(int client) static void Hook_DeathCamThink(int client) { - if (!g_Enabled) - { - return; - } - SF2_BasePlayer player = SF2_BasePlayer(client); if (!player.IsValid || !player.IsInDeathCam || player.IsInGhostMode) { diff --git a/addons/sourcemod/scripting/sf2/client/flashlight.sp b/addons/sourcemod/scripting/sf2/client/flashlight.sp index 987d3353..23b78234 100644 --- a/addons/sourcemod/scripting/sf2/client/flashlight.sp +++ b/addons/sourcemod/scripting/sf2/client/flashlight.sp @@ -34,6 +34,10 @@ void SetupFlashlight() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } ClientResetFlashlight(client.index); } @@ -49,6 +53,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { ClientResetFlashlight(client.index); @@ -252,6 +261,10 @@ void ClientBreakFlashlight(int client) Call_StartForward(g_OnClientBreakFlashlightFwd); Call_PushCell(client); Call_Finish(); + + Call_StartForward(g_OnPlayerFlashlightBreakPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } /** @@ -763,6 +776,32 @@ void ClientStartDrainingFlashlightBattery(int client) { drainRate *= g_ClassFlashlightDrainRate[classToInt]; } + if (IsNightVisionEnabled() && g_NightVisionType == 2) //Blue nightvision + { + switch (g_DifficultyConVar.IntValue) + { + case Difficulty_Normal: + { + drainRate *= 0.3; + } + case Difficulty_Hard: + { + drainRate *= 0.25; + } + case Difficulty_Insane: + { + drainRate *= 0.2; + } + case Difficulty_Nightmare: + { + drainRate *= 0.15; + } + case Difficulty_Apollyon: + { + drainRate *= 0.1; + } + } + } g_PlayerFlashlightBatteryTimer[client] = CreateTimer(drainRate, Timer_DrainFlashlight, GetClientUserId(client), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); } @@ -779,7 +818,7 @@ void ClientHandleFlashlight(int client) return; } - bool nightVision = (g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)); + bool nightVision = IsNightVisionEnabled(); if (IsClientUsingFlashlight(client) || TF2_IsPlayerInCondition(client, TFCond_Taunting)) { @@ -801,6 +840,10 @@ void ClientHandleFlashlight(int client) EmitSoundToClient(client, FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } } + + Call_StartForward(g_OnPlayerTurnOffFlashlightPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } else { @@ -837,6 +880,10 @@ void ClientHandleFlashlight(int client) { EmitSoundToClient(client, (nightVision) ? FLASHLIGHT_CLICKSOUND_NIGHTVISION : FLASHLIGHT_CLICKSOUND, client, SNDCHAN_ITEM, SNDLEVEL_DRYER); } + + Call_StartForward(g_OnPlayerTurnOnFlashlightPFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_Finish(); } else { diff --git a/addons/sourcemod/scripting/sf2/client/ghostmode.sp b/addons/sourcemod/scripting/sf2/client/ghostmode.sp index 094c4df1..e1141430 100644 --- a/addons/sourcemod/scripting/sf2/client/ghostmode.sp +++ b/addons/sourcemod/scripting/sf2/client/ghostmode.sp @@ -20,6 +20,10 @@ void SetupGhost() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } ClientSetGhostModeState(client.index, false); } @@ -34,6 +38,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (fake || IsRoundInWarmup() || client.IsEliminated) { return; @@ -83,6 +92,11 @@ void ClientSetGhostModeState(int client, bool state) return; } + Call_StartForward(g_OnPlayerChangeGhostStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(state); + Call_Finish(); + g_PlayerInGhostMode[client] = state; g_PlayerGhostModeTarget[client] = INVALID_ENT_REFERENCE; g_PlayerGhostModeBossTarget[client] = INVALID_ENT_REFERENCE; @@ -154,92 +168,6 @@ void ClientSetGhostModeState(int client, bool state) SetEntityCollisionGroup(client, COLLISION_GROUP_PLAYER); } } - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - SF2BossProfileData data; - data = NPCGetProfileData(npcIndex); - if (data.IsPvEBoss) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - if (!NPCGetRainbowOutlineState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - SlenderAddGlow(npcIndex, view_as({0, 0, 0, 0})); - } - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } - } - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } - } } /** @@ -386,6 +314,11 @@ void ClientGhostModeNextTarget(int client, bool ignoreSetting = false) continue; } + if (NPCGetProfileData(bossIndex).IsPvEBoss) + { + continue; + } + if (firstTarget == -1) { firstTarget = bossIndex; diff --git a/addons/sourcemod/scripting/sf2/client/glow.sp b/addons/sourcemod/scripting/sf2/client/glow.sp new file mode 100644 index 00000000..3c0ec072 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/glow.sp @@ -0,0 +1,296 @@ +static const int g_DefaultRedColor[4] = { 184, 56, 59, 255 }; +static const int g_DefaultProxyColor[4] = { 255, 208, 0, 255 }; + +static float g_NextCheckTime[MAXTF2PLAYERS]; + +void SetupPlayerGlows() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerChangePlayStatePFwd.AddFunction(null, OnPlayerChangePlayState); + g_OnPlayerChangeProxyStatePFwd.AddFunction(null, OnPlayerChangeProxyState); + g_OnPlayerConditionAddedPFwd.AddFunction(null, OnPlayerConditionAdded); + g_OnPlayerConditionRemovedPFwd.AddFunction(null, OnPlayerConditionRemoved); + g_OnSpecialRoundStartPFwd.AddFunction(null, OnSpecialRoundStart); + g_OnRenevantTriggerWavePFwd.AddFunction(null, OnRenevantTriggerWave); + g_OnWallHaxDebugPFwd.AddFunction(null, OnWallHaxDebug); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + SDKHook(client.index, SDKHook_PreThinkPost, PreThinkPost); + + g_NextCheckTime[client.index] = -1.0; +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + DestroyGlowEntity(client.index); + + if (ShouldHaveGlow(client)) + { + int color[4]; + GetGlowColor(client, color); + CreateGlowEntity(client.index, color); + UpdateVisibility(client); + } + + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + DestroyGlowEntity(client.index); + UpdateVisibilityOfOtherGlows(client); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + DestroyGlowEntity(client.index); + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangePlayState(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangeProxyState(SF2_BasePlayer client, bool state) +{ + if (state) + { + if (!DoesEntityHaveGlow(client.index)) + { + CreateGlowEntity(client.index); + } + int color[4]; + GetGlowColor(client, color); + SetGlowColor(client.index, color); + UpdateVisibility(client); + UpdateVisibilityOfOtherGlows(client); + } +} + +static void OnPlayerConditionAdded(SF2_BasePlayer client, TFCond condition) +{ + if (DoesEntityHaveGlow(client.index) && (TF2_IsInvisible(client.index))) + { + UpdateVisibility(client); + } +} + +static void OnPlayerConditionRemoved(SF2_BasePlayer client, TFCond condition) +{ + if (DoesEntityHaveGlow(client.index) && (TF2_IsInvisible(client.index))) + { + UpdateVisibility(client); + } +} + +static void OnSpecialRoundStart(int specialRound) +{ + if (specialRound == SPECIALROUND_WALLHAX) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (!client.IsEliminated) + { + UpdateVisibilityOfOtherGlows(client); + } + } + } +} + +static void OnRenevantTriggerWave(int wave, RenevantWave condition) +{ + if (condition == RenevantWave_WallHax) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (!client.IsEliminated) + { + UpdateVisibilityOfOtherGlows(client); + } + } + } +} + +static void OnWallHaxDebug() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (!client.IsEliminated) + { + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static void PreThinkPost(int clientIndex) +{ + SF2_BasePlayer client = SF2_BasePlayer(clientIndex); + + float gameTime = GetGameTime(); + if (gameTime >= g_NextCheckTime[clientIndex]) + { + g_NextCheckTime[clientIndex] = gameTime + 0.1; + + if (client.IsProxy) + { + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static bool ShouldBeVisibleToPlayer(SF2_BasePlayer client, SF2_BasePlayer other) +{ + if (client.index == other.index) + { + return false; + } + + if (!client.IsEliminated) + { + if (other.IsInGhostMode) + { + return true; + } + + if (!other.IsEliminated) + { + if (other.HasEscaped) + { + return false; + } + + if (SF_SpecialRound(SPECIALROUND_WALLHAX) || g_EnableWallHaxConVar.BoolValue || g_RenevantWallHax) + { + return true; + } + + return false; + } + else if (other.IsProxy) + { + if (TF2_IsInvisible(client.index)) + { + return false; + } + + if (client.IsProxy || client.InCondition(TFCond_Taunting)) + { + return true; + } + + float vecVel[3], movespeed; + other.GetAbsVelocity(vecVel); + movespeed = GetVectorLength(vecVel, true); + + if (movespeed <= Pow(163.0, 2.0)) + { + float ownerPos[3], proxyPos[3]; + client.GetEyePosition(ownerPos); + other.GetEyePosition(proxyPos); + + float distance = Pow(500.0, 2.0); + return GetVectorSquareMagnitude(ownerPos, proxyPos) <= distance; + } + } + } + else + { + if (client.IsProxy) + { + if (other.IsProxy || other.IsInGhostMode) + { + return true; + } + } + } + + return false; +} + +static void UpdateVisibility(SF2_BasePlayer client) +{ + int clients[MAXPLAYERS + 1]; + int numClients = 0; + bool states[MAXPLAYERS + 1]; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer other = SF2_BasePlayer(i); + if (!other.IsValid) + { + continue; + } + + int i2 = numClients++; + clients[i2] = i; + states[i2] = ShouldBeVisibleToPlayer(client, other); + } + + SetGlowVisibility(client.index, clients, numClients, states); +} + +static void UpdateVisibilityOfOtherGlows(SF2_BasePlayer client) +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer other = SF2_BasePlayer(i); + if (!other.IsValid) + { + continue; + } + + if (ShouldHaveGlow(other)) + { + SetGlowVisibilityForClient(other.index, client.index, ShouldBeVisibleToPlayer(other, client)); + } + } +} + +static bool ShouldHaveGlow(SF2_BasePlayer client) +{ + return client.IsAlive && ((!client.IsEliminated && !client.HasEscaped) || client.IsProxy); +} + +static void GetGlowColor(SF2_BasePlayer client, int color[4]) +{ + if (!client.IsEliminated && !client.HasEscaped) + { + color = g_DefaultRedColor; + } + else if (client.IsProxy) + { + color = g_DefaultProxyColor; + } +} diff --git a/addons/sourcemod/scripting/sf2/client/hints.sp b/addons/sourcemod/scripting/sf2/client/hints.sp index f430f5e9..8de3399d 100644 --- a/addons/sourcemod/scripting/sf2/client/hints.sp +++ b/addons/sourcemod/scripting/sf2/client/hints.sp @@ -13,11 +13,21 @@ void SetupHints() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetHints(client.index); } static void OnPlayerSpawn(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetHints(client.index); } diff --git a/addons/sourcemod/scripting/sf2/client/interactables.sp b/addons/sourcemod/scripting/sf2/client/interactables.sp new file mode 100644 index 00000000..e9bbd7fb --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/interactables.sp @@ -0,0 +1,235 @@ +static int g_PlayerInteractiveGlowEntity[MAXPLAYERS + 1] = { INVALID_ENT_REFERENCE, ... }; +static int g_PlayerInteractiveGlowTargetEntity[MAXPLAYERS + 1] = { INVALID_ENT_REFERENCE, ... }; +static float g_NextInteractiveThink[MAXPLAYERS + 1] = { 0.0, ... }; + +static const char g_InteractableClasses[][64] = { + "prop_dynamic_override", + "func_button" +}; + +static StringMap g_Interactables; + +void SetupInteractables() +{ + g_Interactables = new StringMap(); + + g_OnMapEndPFwd.AddFunction(null, MapEnd); + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); +} + +static void MapEnd() +{ + g_Interactables.Clear(); +} + +static void OnGamemodeEnd() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + + RemoveInteractiveGlow(player); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + g_PlayerInteractiveGlowEntity[client.index] = INVALID_ENT_REFERENCE; + g_NextInteractiveThink[client.index] = 0.0; + + SDKHook(client.index, SDKHook_PreThinkPost, PreThinkPost); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + RemoveInteractiveGlow(client); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + g_NextInteractiveThink[client.index] = 0.0; + + RemoveInteractiveGlow(client); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!fake) + { + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + RemoveInteractiveGlow(client); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + g_PlayerInteractiveGlowTargetEntity[client.index] = INVALID_ENT_REFERENCE; + RemoveInteractiveGlow(client); +} + +static void EntityCreated(CBaseEntity ent, const char[] classname) +{ + for (int i = 0; i < sizeof(g_InteractableClasses); i++) + { + if (g_InteractableClasses[i][0] == classname[0] && strcmp(classname, g_InteractableClasses[i]) == 0) + { + // Possible interactable. Wait until it spawns. + SDKHook(ent.index, SDKHook_SpawnPost, CheckIfInteractableSpawnPost); + break; + } + } +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + int entRef = EnsureEntRef(ent.index); + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid) + { + continue; + } + + if (g_PlayerInteractiveGlowTargetEntity[player.index] == entRef) + { + g_PlayerInteractiveGlowTargetEntity[player.index] = INVALID_ENT_REFERENCE; + RemoveInteractiveGlow(player); + } + } + + char key[32]; + IntToString(entRef, key, sizeof(key)); + g_Interactables.Remove(key); +} + +static void CheckIfInteractableSpawnPost(int entity) +{ + if (IsValidEdict(entity)) + { + char className[64]; + GetEntityClassname(entity, className, sizeof(className)); + + bool isInteractable = false; + + if (strncmp(className, "prop_dynamic", 12) == 0) + { + char targetName[64]; + GetEntPropString(entity, Prop_Data, "m_iName", targetName, sizeof(targetName)); + + if (targetName[0] && (strncmp(targetName, "sf2_page", 8) == 0 || strncmp(targetName, "sf2_interact", 12) == 0)) + { + isInteractable = true; + } + } + else if (strncmp(className, "func_button", 11) == 0) + { + isInteractable = true; + } + + if (isInteractable) + { + char key[32]; + IntToString(EnsureEntRef(entity), key, sizeof(key)); + g_Interactables.SetValue(key, true); + } + } +} + +bool IsEntityInteractable(int ent) +{ + if (!IsValidEdict(ent)) + { + return false; + } + + bool value; + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + return g_Interactables.GetValue(key, value); +} + +static void PreThinkPost(int clientIndex) +{ + SF2_BasePlayer client = SF2_BasePlayer(clientIndex); + + if (!client.IsEliminated && !client.HasEscaped && client.IsAlive) + { + float gameTime = GetGameTime(); + if (gameTime >= g_NextInteractiveThink[clientIndex]) + { + g_NextInteractiveThink[clientIndex] = gameTime + 0.1; + UpdateInteractiveGlow(client); + } + } +} + +static void UpdateInteractiveGlow(SF2_BasePlayer client) +{ + float startPos[3], endPos[3]; + client.GetEyePosition(startPos); + client.GetEyeAngles(endPos); + + static const float maxRange = 2000.0; + GetAngleVectors(endPos, endPos, NULL_VECTOR, NULL_VECTOR); + ScaleVector(endPos, maxRange); + AddVectors(startPos, endPos, endPos); + + TR_TraceRayFilter(startPos, endPos, MASK_VISIBLE, RayType_EndPoint, TraceRayDontHitPlayers, -1); + int oldTargetEnt = EntRefToEntIndex(g_PlayerInteractiveGlowTargetEntity[client.index]); + int targetEnt = TR_GetEntityIndex(); + if (!IsEntityInteractable(targetEnt)) + { + targetEnt = -1; + } + + g_PlayerInteractiveGlowTargetEntity[client.index] = targetEnt != -1 ? EntIndexToEntRef(targetEnt) : INVALID_ENT_REFERENCE; + + if (targetEnt != oldTargetEnt) + { + RemoveInteractiveGlow(client); + + if (IsValidEntity(targetEnt)) + { + CBaseEntity glow = CBaseEntity(CreateGlowEntityDataless(targetEnt)); + SDKHook(glow.index, SDKHook_SetTransmit, InteractableSetTransmit); + glow.SetPropEnt(Prop_Data, "m_hOwnerEntity", client.index); + g_PlayerInteractiveGlowEntity[client.index] = EntIndexToEntRef(glow.index); + } + } + + if (targetEnt == -1) + { + RemoveInteractiveGlow(client); + } +} + +static Action InteractableSetTransmit(int ent, int other) +{ + return GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity") == other ? Plugin_Continue : Plugin_Handled; +} + +static void RemoveInteractiveGlow(SF2_BasePlayer player) +{ + int glow = EntRefToEntIndex(g_PlayerInteractiveGlowEntity[player.index]); + if (IsValidEntity(glow)) + { + RemoveEntity(glow); + } + + g_PlayerInteractiveGlowEntity[player.index] = INVALID_ENT_REFERENCE; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/music.sp b/addons/sourcemod/scripting/sf2/client/music.sp index c640480f..6e3e7c3b 100644 --- a/addons/sourcemod/scripting/sf2/client/music.sp +++ b/addons/sourcemod/scripting/sf2/client/music.sp @@ -7,12 +7,13 @@ void SetupMusic() { - g_OnGamemodeEnd.AddFunction(null, OnGamemodeEnd); + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyChange); } static void OnGamemodeEnd() @@ -30,12 +31,22 @@ static void OnGamemodeEnd() static void OnPlayerSpawn(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientUpdateMusicSystem(client.index); ClientResetChannels(client.index); } static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { ClientResetChannels(client.index); @@ -45,6 +56,11 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetChannels(client.index); ClientUpdateMusicSystem(client.index); } @@ -93,6 +109,30 @@ static void OnBossRemoved(SF2NPC_BaseNPC npc) } } +static void OnDifficultyChange(int oldDifficulty, int newDifficulty) +{ + CheckIfMusicValid(); + if (MusicActive()) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.IsSourceTV) + { + continue; + } + + char path[PLATFORM_MAX_PATH]; + GetBossMusic(path, sizeof(path)); + if (path[0] != '\0') + { + StopSound(i, MUSIC_CHAN, path); + } + client.UpdateMusicSystem(); + } + } +} + void ClientResetChannels(int client) { ClientChaseMusicReset(client); @@ -105,6 +145,11 @@ void ClientResetChannels(int client) void ClientUpdateMusicSystem(int client, bool initialize = false) { + if (!g_Enabled) + { + return; + } + int oldPageMusicMaster = EntRefToEntIndex(g_PlayerPageMusicMaster[client]); int oldPageMusicActiveIndex = g_PageMusicActiveIndex[client]; int oldMusicFlags = g_PlayerMusicFlags[client]; diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 3379ecae..1a3e463d 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -38,6 +38,10 @@ static void OnGamemodeStart() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } ClientResetProxy(client.index); ClientStartProxyAvailableTimer(client.index); } @@ -49,11 +53,21 @@ static void OnDisconnected(SF2_BasePlayer client) static void OnPlayerSpawn(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetProxy(client.index); } static void OnPlayerDeath(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + if (!client.IsProxy) { return; @@ -177,6 +191,14 @@ void ClientResetProxy(int client, bool resetFull = true) #endif bool oldProxy = g_PlayerProxy[client]; + if (IsValidClient(client) && oldProxy != g_PlayerProxy[client]) + { + Call_StartForward(g_OnPlayerChangeProxyStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(false); + Call_Finish(); + } + if (resetFull) { g_PlayerProxy[client] = false; @@ -196,7 +218,6 @@ void ClientResetProxy(int client, bool resetFull = true) if (resetFull) { - ClientDisableConstantGlow(client); SetVariantString(""); AcceptEntityInput(client, "SetCustomModel"); } @@ -485,14 +506,17 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP { return; } + if (!(NPCGetFlags(bossIndex) & SFF_PROXIES)) { return; } + if (GetClientTeam(client) != TFTeam_Blue) { return; } + if (g_PlayerProxy[client]) { return; @@ -511,13 +535,14 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP if (IsClientInKart(client)) { - TF2_RemoveCondition(client,TFCond_HalloweenKart); - TF2_RemoveCondition(client,TFCond_HalloweenKartDash); - TF2_RemoveCondition(client,TFCond_HalloweenKartNoTurn); - TF2_RemoveCondition(client,TFCond_HalloweenKartCage); + TF2_RemoveCondition(client, TFCond_HalloweenKart); + TF2_RemoveCondition(client, TFCond_HalloweenKartDash); + TF2_RemoveCondition(client, TFCond_HalloweenKartNoTurn); + TF2_RemoveCondition(client, TFCond_HalloweenKartCage); } g_PlayerProxy[client] = true; + ChangeClientTeamNoSuicide(client, TFTeam_Blue); PvP_SetPlayerPvPState(client, false, true, false); TF2_RespawnPlayer(client); @@ -526,6 +551,7 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP TF2_AddCondition(client, TFCond_SpeedBuffAlly, 0.001); g_PlayerProxy[client] = true; + g_PlayerProxyMaster[client] = NPCGetUniqueID(bossIndex); g_PlayerProxyControl[client] = 100; g_PlayerProxyControlRate[client] = g_SlenderProxyControlDrainRate[bossIndex][difficulty]; @@ -557,7 +583,6 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP UTIL_ScreenFade(client, 200, 1, FFADE_IN, 255, 255, 255, 100); EmitSoundToClient(client, "weapons/teleporter_send.wav", _, SNDCHAN_STATIC); - ClientDisableConstantGlow(client); ClientActivateUltravision(client); TF2Attrib_SetByDefIndex(client, 28, 1.0); @@ -569,78 +594,6 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP CreateTimer(1.0, Timer_GiveWeaponAll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - if (g_SlenderInDeathcam[npcIndex]) - { - continue; - } - SF2BossProfileData data; - data = NPCGetProfileData(npcIndex); - if (data.IsPvEBoss) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - if (!NPCGetRainbowOutlineState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - SlenderAddGlow(bossIndex, {0, 0, 0, 0}); - } - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } - } - //SDKHook(client, SDKHook_ShouldCollide, Hook_ClientProxyShouldCollide); SF2PlayerProxySpawnEntity spawnPoint = SF2PlayerProxySpawnEntity(spawnPointEnt); @@ -667,6 +620,11 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP Call_StartForward(g_OnClientSpawnedAsProxyFwd); Call_PushCell(client); Call_Finish(); + + Call_StartForward(g_OnPlayerChangeProxyStatePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(true); + Call_Finish(); } static Action Timer_GiveWeaponAll(Handle timer, any userid) @@ -1000,25 +958,6 @@ Action Timer_ApplyCustomModel(Handle timer, any userid) } } - for (int i = 1; i <= MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } - } - if (IsPlayerAlive(client)) { g_PlayerProxyNextVoiceSound[client] = GetGameTime(); diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp index b56bc450..bfd128ed 100644 --- a/addons/sourcemod/scripting/sf2/client/sprint.sp +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -15,11 +15,16 @@ void SetupSprint() g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); - g_PlayerScareSprintBoost = CreateConVar("sf2_player_scare_boost", "0", "If player is in danger, provide a small boost in speed.", _, true, 0.0, true, 1.0); + g_PlayerScareSprintBoost = CreateConVar("sf2_player_scare_boost", "0", "Determines if the player run speeds should be decreased slightly if they are not in danger.", _, true, 0.0, true, 1.0); } static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetSprint(client); SDKHook(client.index, SDKHook_PreThink, Hook_SpeedThink); @@ -66,6 +71,11 @@ static void OnJump(SF2_BasePlayer client) static void OnPlayerSpawn(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetSprint(client); if (client.IsAlive && !client.IsEliminated && !client.HasEscaped) @@ -79,6 +89,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { ClientResetSprint(client); @@ -481,7 +496,7 @@ static void Hook_SpeedThink(int client) if (!inDanger) { int state; - int bossTarget; + CBaseEntity bossTarget; for (int i = 0; i < MAX_BOSSES; i++) { @@ -499,11 +514,11 @@ static void Hook_SpeedThink(int client) if (NPCGetType(i) == SF2BossType_Chaser) { SF2_ChaserEntity chaser = SF2_ChaserEntity(ent); - bossTarget = chaser.Target.index; + bossTarget = chaser.Target; state = chaser.State; if ((state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) && - ((bossTarget && bossTarget != INVALID_ENT_REFERENCE && (bossTarget == client || ClientGetDistanceFromEntity(client, bossTarget) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) + ((bossTarget.IsValid() && (bossTarget.index == client || ClientGetDistanceFromEntity(client, bossTarget.index) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) { inDanger = true; ClientSetScareBoostEndTime(player.index, GetGameTime() + 5.0); @@ -686,38 +701,49 @@ static void Hook_SpeedThink(int client) } } - if (inDanger) + if (g_PlayerScareSprintBoost.BoolValue) { - if (g_PlayerScareSprintBoost.BoolValue) + if (!inDanger) + { + // We're not going to account for sprint speed for the scare sprint mechanic + // It's a bit of a headache for me to do something like that + float newValue = (walkSpeed + sprintSpeed) / 1.85; + sprintSpeed = newValue; + } + else { if (!IsClassConfigsValid()) { if (class != TFClass_Spy && class != TFClass_Pyro) { walkSpeed *= 1.34; - sprintSpeed *= 1.34; } else { - weaponEnt = INVALID_ENT_REFERENCE; - for (int slot = 0; slot <= 5; slot++) + if (class == TFClass_Spy) { - weaponEnt = player.GetWeaponSlot(slot); - if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) - { - continue; - } - - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) // Powerjack - { - walkSpeed *= 1.32; - sprintSpeed *= 1.32; - } - else + walkSpeed *= 1.28; + } + else + { + weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = 0; slot <= 5; slot++) { - walkSpeed *= 1.34; - sprintSpeed *= 1.34; + weaponEnt = player.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) + { + walkSpeed *= 1.32; + } + else + { + walkSpeed *= 1.34; + } } } } @@ -737,17 +763,19 @@ static void Hook_SpeedThink(int client) } int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) // Powerjack + if (itemDefInt == 214 && player.GetPropEnt(Prop_Send, "m_hActiveWeapon") == weaponEnt) { multiplier -= 0.02; } } } walkSpeed *= multiplier; - sprintSpeed *= multiplier; } } + } + if (inDanger) + { if (!player.HasHint(PlayerHint_Sprint)) { player.ShowHint(PlayerHint_Sprint); @@ -811,4 +839,42 @@ static void Hook_SpeedThink(int client) } player.SetPropFloat(Prop_Send, "m_flCurrentTauntMoveSpeed", 190.0); } -} \ No newline at end of file +} + +void Sprint_SetupAPI() +{ + CreateNative("SF2_GetClientSprintPoints", Native_GetSprintPoints); + CreateNative("SF2_SetClientSprintPoints", Native_SetSprintPoints); + + CreateNative("SF2_IsClientSprinting", Native_IsSprinting); + CreateNative("SF2_IsClientReallySprinting", Native_IsReallySprinting); + CreateNative("SF2_SetClientSprintState", Native_SetSprintState); +} + +static any Native_GetSprintPoints(Handle plugin, int numParams) +{ + return RoundToNearest(SF2_BasePlayer(GetNativeCell(1)).Stamina * 100.0); +} + +static any Native_SetSprintPoints(Handle plugin, int numParams) +{ + float newValue = GetNativeCell(2) / 100.0; + SF2_BasePlayer(GetNativeCell(1)).Stamina = newValue; + return 0; +} + +static any Native_IsSprinting(Handle plugin, int numParams) +{ + return IsClientSprinting(GetNativeCell(1)); +} + +static any Native_IsReallySprinting(Handle plugin, int numParams) +{ + return IsClientReallySprinting(GetNativeCell(1)); +} + +static any Native_SetSprintState(Handle plugin, int numParams) +{ + ClientHandleSprint(GetNativeCell(1), GetNativeCell(2)); + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/client/static.sp b/addons/sourcemod/scripting/sf2/client/static.sp index 4071a368..9459506b 100644 --- a/addons/sourcemod/scripting/sf2/client/static.sp +++ b/addons/sourcemod/scripting/sf2/client/static.sp @@ -30,11 +30,21 @@ void SetupStatic() static void OnPlayerSpawn(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetStatic(client.index); } static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { ClientResetStatic(client.index); @@ -43,6 +53,11 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientResetStatic(client.index); SDKHook(client.index, SDKHook_PreThink, Hook_StaticThink); diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index cb277299..11927dd2 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -13,7 +13,6 @@ void Hook_ClientPreThink(int client) } ClientProcessFlashlightAngles(client); - ClientProcessInteractiveGlow(client); ClientProcessStaticShake(client); ClientProcessViewAngles(client); @@ -231,13 +230,17 @@ void Hook_ClientPreThink(int client) Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { + SF2_BasePlayer victimPlayer = SF2_BasePlayer(victim); if (!g_Enabled) { + if (NPCGetFromEntIndex(attacker) != -1 && GetEntProp(attacker, Prop_Data, "m_iTeamNum") == victimPlayer.Team) + { + damage = 0.0; + return Plugin_Changed; + } return Plugin_Continue; } - SF2_BasePlayer victimPlayer = SF2_BasePlayer(victim); - Action action = Plugin_Continue; float damage2 = damage; @@ -366,18 +369,6 @@ Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float } } - // Prevent telefrags - if ((damagetype & DMG_CRUSH) && damage > 500.0) - { - SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(NPCGetFromEntIndex(attacker)); - if (Npc != SF2_INVALID_NPC && IsValidClient(victim)) - { - damage = 0.0; - Npc.UnSpawn(); - return Plugin_Changed; - } - } - bool canDamage = false; if (attacker != victim && IsValidClient(attacker)) { @@ -1007,7 +998,7 @@ Action Timer_ClientAverageUpdate(Handle timer) } } - SetHudTextParams(-1.0, 0.65, + SetHudTextParams(0.8, 0.83, 0.3, color[0], color[1], diff --git a/addons/sourcemod/scripting/sf2/client/ultravision.sp b/addons/sourcemod/scripting/sf2/client/ultravision.sp index f2a2cd69..53b1209e 100644 --- a/addons/sourcemod/scripting/sf2/client/ultravision.sp +++ b/addons/sourcemod/scripting/sf2/client/ultravision.sp @@ -38,6 +38,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } + ClientDeactivateUltravision(client.index); } @@ -48,6 +53,11 @@ bool IsClientUsingUltravision(int client) void ClientActivateUltravision(int client, bool nightVision = false) { + if (!g_Enabled) + { + return; + } + if (!IsValidClient(client) || IsClientUsingUltravision(client)) { return; @@ -79,16 +89,20 @@ void ClientActivateUltravision(int client, bool nightVision = false) TeleportEntity(ent, eyePos, { 90.0, 0.0, 0.0 }, NULL_VECTOR); if (nightVision && !g_PlayerEliminated[client]) { - switch (g_NightvisionType) + switch (g_NightVisionType) { - case 0: + case 0: // Green { DispatchKeyValue(ent, "rendercolor", "50 255 50"); } - case 1: + case 1: // Red { DispatchKeyValue(ent, "rendercolor", "255 50 50"); } + case 2: // Blue + { + DispatchKeyValue(ent, "rendercolor", "50 50 255"); + } } } else @@ -165,16 +179,20 @@ void ClientActivateUltravision(int client, bool nightVision = false) SetEntityRenderColor(ent, 150, 225, 255, 255); if (nightVision && !g_PlayerEliminated[client]) { - switch (g_NightvisionType) + switch (g_NightVisionType) { - case 0: + case 0: // Green { DispatchKeyValue(ent, "rendercolor", "50 255 50"); } - case 1: + case 1: // Red { DispatchKeyValue(ent, "rendercolor", "255 50 50"); } + case 2: // Blue + { + DispatchKeyValue(ent, "rendercolor", "50 50 255"); + } } } diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index 5149e718..d90b3de9 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -602,7 +602,7 @@ static Action Hook_EffectTransmit(int ent, int other) SF2_BasePlayer player = SF2_BasePlayer(other); if (player.IsValid && !player.IsEliminated && !player.IsInGhostMode && !player.HasEscaped) { - if (g_EntityEffectEvent[ent] == EffectEvent_PlayerSeesBoss && controller.PlayerCanSee(player.index)) + if (g_EntityEffectEvent[ent] == EffectEvent_PlayerSeesBoss && !controller.PlayerCanSee(player.index)) { return Plugin_Handled; } diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp index 7b9840e2..e2c552c1 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp @@ -34,6 +34,7 @@ methodmap SF2_ProjectileArrow < SF2_ProjectileBase public static void SetupAPI() { CreateNative("SF2_Projectile_Arrow.Create", Native_Create); + CreateNative("SF2_projectile_Arrow.IsValid.get", Native_IsValid); } property bool Touched @@ -105,6 +106,7 @@ static void StartTouch(int entity, int other) bool hit = true; SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); if (otherPlayer.IsValid) { if (otherPlayer.IsInGhostMode) @@ -116,6 +118,11 @@ static void StartTouch(int entity, int other) { return; } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == otherPlayer.Team) + { + return; + } } else { @@ -126,7 +133,7 @@ static void StartTouch(int entity, int other) } } - if (projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity") == other) + if (owner == other) { hit = false; } @@ -145,7 +152,10 @@ static void StartTouch(int entity, int other) if (hit) { - int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(other)); + Call_Finish(); int flags = DMG_BULLET; float pos[3]; projectile.GetAbsOrigin(pos); @@ -187,4 +197,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeString(9, model, sizeof(model)); SF2_ProjectileArrow projectile = SF2_ProjectileArrow.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), trail, impact, model, GetNativeCell(10)); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileArrow(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp index 9e74b457..0b2d7536 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp @@ -34,6 +34,7 @@ methodmap SF2_ProjectileBaseball < SF2_ProjectileGrenade public static void SetupAPI() { CreateNative("SF2_Projectile_Baseball.Create", Native_Create); + CreateNative("SF2_Projectile_Baseball.IsValid.get", Native_IsValid); } property float TravelTime @@ -98,6 +99,7 @@ methodmap SF2_ProjectileBaseball < SF2_ProjectileGrenade static void Think(int entity) { SF2_ProjectileBaseball projectile = SF2_ProjectileBaseball(entity); + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); if (projectile.Timer < GetGameTime()) { @@ -118,6 +120,14 @@ static void Think(int entity) TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, projectile.index); int hitIndex = TR_GetEntityIndex(); + if (IsValidEntity(hitIndex)) + { + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(hitIndex)); + Call_Finish(); + } + if (hitIndex == 0) { projectile.Touched = true; @@ -125,14 +135,29 @@ static void Think(int entity) } else { + int flags = DMG_CLUB; + if (projectile.IsCrits) + { + flags |= DMG_ACID; + } SF2_BasePlayer player = SF2_BasePlayer(hitIndex); if (player.IsValid) { - int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); - int flags = DMG_CLUB; - if (projectile.IsCrits) + if (player.IsInGhostMode) + { + return; + } + + if (player.IsEliminated && !projectile.AttackWaiters) { - flags |= DMG_ACID; + projectile.Touched = true; + return; + } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) + { + projectile.Touched = true; + return; } player.TakeDamage(_, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); float ratio = projectile.TravelTime / 1.0; @@ -161,6 +186,10 @@ static void Think(int entity) Call_Finish(); projectile.Touched = true; } + else + { + SDKHooks_TakeDamage(hitIndex, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + } } } @@ -173,4 +202,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeString(7, model, sizeof(model)); SF2_ProjectileBaseball projectile = SF2_ProjectileBaseball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), model, GetNativeCell(8)); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileBaseball(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp index 4f88c1a3..44401841 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_cow_mangler_projectile.sp @@ -31,6 +31,7 @@ methodmap SF2_ProjectileCowMangler < SF2_ProjectileRocket public static void SetupAPI() { CreateNative("SF2_Projectile_CowMangler.Create", Native_Create); + CreateNative("SF2_Projectile_CowMangler.IsValid.get", Native_IsValid); } public static SF2_ProjectileCowMangler Create( @@ -65,4 +66,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeString(9, freeze, sizeof(freeze)); SF2_ProjectileCowMangler projectile = SF2_ProjectileCowMangler.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7)); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileCowMangler(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp index b30e7761..3206cdbf 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_fireball_projectile.sp @@ -32,6 +32,7 @@ methodmap SF2_ProjectileFireball < SF2_ProjectileBase public static void SetupAPI() { CreateNative("SF2_Projectile_Fireball.Create", Native_Create); + CreateNative("SF2_Projectile_Fireball.IsValid.get", Native_IsValid); } public void OnPlayerDamaged(SF2_BasePlayer player) @@ -81,4 +82,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeString(8, trail, sizeof(trail)); SF2_ProjectileFireball projectile = SF2_ProjectileFireball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), impact, trail); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileFireball(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp index 95f11c6f..94d0231d 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -52,6 +52,7 @@ methodmap SF2_ProjectileGrenade < SF2_ProjectileBase public static void SetupAPI() { CreateNative("SF2_Projectile_Grenade.Create", Native_Create); + CreateNative("SF2_Projectile_Grenade.IsValid.get", Native_IsValid); } property CBaseEntity TrailEntity @@ -319,6 +320,14 @@ static Action Timer_Think(Handle timer, any ref) TR_TraceHullFilter(pos, pos, mins, maxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, TraceRayGrenade, projectile.index); int hitIndex = TR_GetEntityIndex(); + if (IsValidEntity(hitIndex)) + { + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(hitIndex)); + Call_Finish(); + } + if (hitIndex == 0) { projectile.Touched = true; @@ -326,11 +335,7 @@ static Action Timer_Think(Handle timer, any ref) } else { - SF2_BasePlayer player = SF2_BasePlayer(hitIndex); - if (player.IsValid) - { - projectile.DoExplosion(); - } + projectile.DoExplosion(); } return Plugin_Continue; @@ -344,6 +349,8 @@ bool TraceRayGrenade(int entity, int mask, any data) return false; } + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); + SF2_BasePlayer player = SF2_BasePlayer(entity); if (player.IsValid) { @@ -356,6 +363,11 @@ bool TraceRayGrenade(int entity, int mask, any data) { return false; } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) + { + return false; + } } if (IsValidEntity(entity) && NPCGetFromEntIndex(entity) != -1) @@ -363,6 +375,16 @@ bool TraceRayGrenade(int entity, int mask, any data) return false; } + if (g_WhitelistedEntities.FindValue(EntIndexToEntRef(entity)) != -1) + { + return true; + } + + if (g_Buildings.FindValue(EntIndexToEntRef(entity)) != -1) + { + return true; + } + return true; } @@ -378,4 +400,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeString(11, model, sizeof(model)); SF2_ProjectileGrenade projectile = SF2_ProjectileGrenade.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), trail, explosion, impact, model, GetNativeCell(12)); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileGrenade(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp index c36f22fc..664e40b4 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_iceball_projectile.sp @@ -37,6 +37,7 @@ methodmap SF2_ProjectileIceball < SF2_ProjectileBase public static void SetupAPI() { CreateNative("SF2_Projectile_Iceball.Create", Native_Create); + CreateNative("SF2_Projectile_Iceball.IsValid.get", Native_IsValid); } property float SlowDuration @@ -133,4 +134,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeString(11, freeze, sizeof(freeze)); SF2_ProjectileIceball projectile = SF2_ProjectileIceball.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), impact, trail, GetNativeCell(9), GetNativeCell(10), freeze, GetNativeCell(12)); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileIceball(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp index d3a1a799..21ddebee 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_rocket_projectile.sp @@ -31,6 +31,7 @@ methodmap SF2_ProjectileRocket < SF2_ProjectileBase public static void SetupAPI() { CreateNative("SF2_Projectile_Rocket.Create", Native_Create); + CreateNative("SF2_Projectile_Rocket.IsValid.get", Native_IsValid); } public static SF2_ProjectileRocket Create( @@ -71,4 +72,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeString(11, model, sizeof(model)); SF2_ProjectileRocket projectile = SF2_ProjectileRocket.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), trail, explosion, impact, model, GetNativeCell(12)); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileRocket(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp index f08f24d9..400c5ec4 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_sentry_rocket_projectile.sp @@ -31,6 +31,7 @@ methodmap SF2_ProjectileSentryRocket < SF2_ProjectileRocket public static void SetupAPI() { CreateNative("SF2_Projectile_SentryRocket.Create", Native_Create); + CreateNative("SF2_Projectile_SentryRocket.IsValid.get", Native_IsValid); } public static SF2_ProjectileSentryRocket Create( @@ -64,4 +65,9 @@ static any Native_Create(Handle plugin, int numParams) GetNativeArray(3, ang, 3); SF2_ProjectileSentryRocket projectile = SF2_ProjectileSentryRocket.Create(GetNativeCell(1), pos, ang, GetNativeCell(4), GetNativeCell(5), GetNativeCell(6), GetNativeCell(7), GetNativeCell(8)); return projectile; +} + +static any Native_IsValid(Handle plugin, int numParams) +{ + return SF2_ProjectileSentryRocket(GetNativeCell(1)).IsValid(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp index 150ec20f..0325552e 100644 --- a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -359,9 +359,19 @@ methodmap SF2_ProjectileBase < CBaseAnimating for (int i = 0; i < hitList.Length; i++) { SF2_BasePlayer player = SF2_BasePlayer(hitList.Get(i)); - if (!this.AttackWaiters && player.IsEliminated) + if (g_Enabled) { - continue; + if (!this.AttackWaiters && player.IsEliminated) + { + continue; + } + } + else + { + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) + { + continue; + } } float targetPos[3]; @@ -485,6 +495,7 @@ static void MapStart() static void StartTouch(int entity, int other) { SF2_ProjectileBase projectile = SF2_ProjectileBase(entity); + int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); bool hit = true; SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); @@ -499,35 +510,39 @@ static void StartTouch(int entity, int other) { return; } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == otherPlayer.Team) + { + return; + } } else { int hitIndex = NPCGetFromEntIndex(other); if (hitIndex != -1) { - hit = false; + return; } } - if (projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity") == other) + if (owner == other) { - hit = false; + return; } char class[64]; GetEntityClassname(other, class, sizeof(class)); if (StrContains(class, "sf2_projectile", false) != -1) { - hit = false; - } - - if (other == 0) - { - hit = true; + return; } if (hit) { + Call_StartForward(g_OnProjectileTouchFwd); + Call_PushCell(projectile); + Call_PushCell(CBaseEntity(other)); + Call_Finish(); projectile.DoExplosion(); } } diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index dbdf256c..a4568b3a 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -102,8 +102,6 @@ public void OnPluginStart() g_PlayerViewbobHurtEnabledConVar.AddChangeHook(OnConVarChanged); g_PlayerViewbobSprintEnabledConVar = CreateConVar("sf2_player_viewbob_sprint_enabled", "0", "Enable/Disable player step viewbobbing when sprinting.", _, true, 0.0, true, 1.0); g_PlayerViewbobSprintEnabledConVar.AddChangeHook(OnConVarChanged); - g_GravityConVar = FindConVar("sv_gravity"); - g_GravityConVar.AddChangeHook(OnConVarChanged); g_ForcedHolidayConVar = FindConVar("tf_forced_holiday"); @@ -111,8 +109,8 @@ public void OnPluginStart() g_PhysicsPushScaleConVar = FindConVar("phys_pushscale"); - g_DragonsFuryBurningBonus = FindConVar("tf_fireball_burning_bonus"); - g_DragonsFuryBurnDuration = FindConVar("tf_fireball_burn_duration"); + g_DragonsFuryBurningBonusConVar = FindConVar("tf_fireball_burning_bonus"); + g_DragonsFuryBurnDurationConVar = FindConVar("tf_fireball_burn_duration"); g_PlayerShakeEnabledConVar = CreateConVar("sf2_player_shake_enabled", "1", "Enable/Disable player view shake during boss encounters.", _, true, 0.0, true, 1.0); g_PlayerShakeEnabledConVar.AddChangeHook(OnConVarChanged); @@ -128,6 +126,7 @@ public void OnPluginStart() g_NightvisionRadiusConVar = CreateConVar("sf2_player_nightvision_radius", "900.0"); g_UltravisionBrightnessConVar = CreateConVar("sf2_player_ultravision_brightness", "-2"); g_NightvisionEnabledConVar = CreateConVar("sf2_player_flashlight_isnightvision", "0", "Enable/Disable flashlight replacement with nightvision",_, true, 0.0, true, 1.0); + g_NightvisionEnabledConVar.AddChangeHook(OnConVarChanged); g_GhostModeConnectionConVar = CreateConVar("sf2_ghostmode_no_tolerance", "0", "If set on 1, it will instant kick out the client of the Ghost mode if the client has timed out."); g_GhostModeConnectionCheckConVar = CreateConVar("sf2_ghostmode_check_connection", "1", "Checks a player's connection while in Ghost Mode. If the check fails, the client is booted out of Ghost Mode and the action and client's SteamID is logged in the main SF2 log."); @@ -164,6 +163,7 @@ public void OnPluginStart() g_IgnoreRedPlayerDeathSwapConVar.AddChangeHook(OnConVarChanged); g_EnableWallHaxConVar = CreateConVar("sf2_enable_wall_hax", "0", "Enables/disables the Wall Hax special round without needing to turn on Wall Hax. This will not force the difficulty to Insane and will show player + boss outlines.", _, true, 0.0, true, 1.0); + g_EnableWallHaxConVar.AddChangeHook(OnConVarChanged); g_TimeLimitConVar = CreateConVar("sf2_timelimit_default", "300", "The time limit of the round. Maps can change the time limit.", _, true, 0.0); g_TimeLimitEscapeConVar = CreateConVar("sf2_timelimit_escape_default", "90", "The time limit to escape. Maps can change the time limit.", _, true, 0.0); @@ -226,6 +226,9 @@ public void OnPluginStart() g_FileCheckConVar = CreateConVar("sf2_debug_file_checks", "0", "Determines if the gamemode should look for missing files when loading all the bosses. Note that turning this on leads to longer boss loading times.", _, true, 0.0, true, 1.0); + g_LoadOutsideMapsConVar = CreateConVar("sf2_load_outside_maps", "0", "Allow bosses to be loaded outside of Slender Fortress maps.", _, true, 0.0, true, 1.0); + g_DefaultBossTeamConVar = CreateConVar("sf2_default_boss_team", "1", "If bosses are loaded outside of SF2, determine what default team bosses should be with.", _, true, 1.0, true, 5.0); + g_MaxRoundsConVar = FindConVar("mp_maxrounds"); g_HudSync = CreateHudSynchronizer(); @@ -236,6 +239,7 @@ public void OnPluginStart() g_Cookie = RegClientCookie("sf2_newcookies", "", CookieAccess_Private); g_Buildings = new ArrayList(); + g_WhitelistedEntities = new ArrayList(); switch (g_DifficultyConVar.IntValue) { @@ -391,8 +395,9 @@ public void OnPluginStart() //HookUserMessage(GetUserMessageId("TextMsg"), Hook_BlockUserMessage, true); g_OnGamemodeStartPFwd = new PrivateForward(ET_Ignore); - g_OnGamemodeEnd = new PrivateForward(ET_Ignore); + g_OnGamemodeEndPFwd = new PrivateForward(ET_Ignore); g_OnMapStartPFwd = new PrivateForward(ET_Ignore); + g_OnMapEndPFwd = new PrivateForward(ET_Ignore); g_OnGameFramePFwd = new PrivateForward(ET_Ignore); g_OnRoundStartPFwd = new PrivateForward(ET_Ignore); g_OnRoundEndPFwd = new PrivateForward(ET_Ignore); @@ -408,10 +413,23 @@ public void OnPluginStart() g_OnPlayerTeamPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_OnPlayerClassPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerLookAtBossPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerChangePlayStatePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell); + g_OnPlayerChangeGhostStatePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerChangeProxyStatePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerConditionAddedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerConditionRemovedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnPlayerTurnOnFlashlightPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerTurnOffFlashlightPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerFlashlightBreakPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnSpecialRoundStartPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnBossSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnBossRemovedPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnChaserGetAttackActionPFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_CellByRef); g_OnChaserGetCustomAttackPossibleStatePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); g_OnChaserUpdatePosturePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); + g_OnDifficultyChangePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnRenevantTriggerWavePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnWallHaxDebugPFwd = new PrivateForward(ET_Ignore); // Hook sounds. AddNormalSoundHook(Hook_NormalSound); @@ -446,6 +464,8 @@ public void OnPluginStart() SetupMusic(); SetupSprint(); SetupUltravision(); + SetupPlayerGlows(); + SetupInteractables(); SetupAdminMenu(); @@ -464,6 +484,8 @@ public void OnPluginStart() SetupRenevantMode(); + SetupGlows(); + g_FuncNavPrefer = new ArrayList(); CreateTimer(0.1, Timer_OnEverythingLoaded, _, TIMER_FLAG_NO_MAPCHANGE); @@ -511,6 +533,11 @@ static void OnDifficultyConVarChangedForward(ConVar cvar, const char[] oldValue, Call_PushCell(difficulty); Call_PushCell(oldDifficulty); Call_Finish(); + + Call_StartForward(g_OnDifficultyChangePFwd); + Call_PushCell(difficulty); + Call_PushCell(oldDifficulty); + Call_Finish(); } // ========================================================== @@ -1202,7 +1229,7 @@ static Action Command_ClientPerformScare(int client, int args) static Action Command_SpawnSlender(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1256,7 +1283,7 @@ static int g_SpawnAllBossesCount = 0; static Action Command_SpawnAllSlenders(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1358,7 +1385,7 @@ static Action Timer_SpawnAllSlenders(Handle timer, any userid) static Action Command_RemoveSlender(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1411,7 +1438,7 @@ static Action Command_RemoveSlender(int client, int args) static Action Command_RemoveAllSlenders(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1563,6 +1590,11 @@ static Action Command_SlenderAttackWaiters(int client, int args) { NPCSetFlags(bossIndex, bossFlags | SFF_ATTACKWAITERS); CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Attack Waiters", client); + CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); + if (boss.IsValid()) + { + boss.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Spectator); + } } } else @@ -1571,6 +1603,11 @@ static Action Command_SlenderAttackWaiters(int client, int args) { NPCSetFlags(bossIndex, bossFlags & ~SFF_ATTACKWAITERS); CPrintToChat(client, "{royalblue}%t {default}%T", "SF2 Prefix", "SF2 Boss Do Not Attack Waiters", client); + CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); + if (boss.IsValid()) + { + boss.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + } } } @@ -1632,7 +1669,7 @@ static Action Command_SlenderNoTeleport(int client, int args) static Action Command_ToggleAllBossTeleports(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -1716,15 +1753,18 @@ static Action Command_ForceEndGrace(int client, int args) static Action Command_ReloadProfiles(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } ReloadBossProfiles(); - ReloadRestrictedWeapons(); - ReloadSpecialRounds(); - ReloadClassConfigs(); + if (g_Enabled) + { + ReloadRestrictedWeapons(); + ReloadSpecialRounds(); + ReloadClassConfigs(); + } CPrintToChatAll("{royalblue}%t {default} Reloaded all profiles successfully.", "SF2 Prefix"); return Plugin_Handled; @@ -1915,9 +1955,10 @@ static Action Command_ForceEscape(int client, int args) return Plugin_Handled; } + static Action Command_ForceDifficulty(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -2177,7 +2218,7 @@ static Action Command_ForceSpecialRound(int client, int args) static Action Command_AddSlender(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } @@ -2305,7 +2346,7 @@ static void NPCSpawn(const char[] output, int ent, int activator, float delay) static Action Command_AddSlenderFake(int client, int args) { - if (!g_Enabled) + if (!g_Enabled && !g_LoadOutsideMapsConVar.BoolValue) { return Plugin_Continue; } diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 4629e8d0..5dbf64ff 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -9,6 +9,10 @@ Action Event_RoundStart(Handle event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue) + { + NPCRemoveAll(); + } return Plugin_Continue; } @@ -351,6 +355,12 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue && GetClientOfUserId(GetEventInt(event, "userid")) > 0) + { + Call_StartForward(g_OnPlayerSpawnPFwd); + Call_PushCell(SF2_BasePlayer(GetClientOfUserId(GetEventInt(event, "userid")))); + Call_Finish(); + } return Plugin_Continue; } @@ -394,9 +404,6 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) ClientUpdateListeningFlags(client); ClientResetScare(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -474,9 +481,6 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) ClientUpdateListeningFlags(client); ClientResetScare(client); - ClientResetInteractiveGlow(client); - ClientDisableConstantGlow(client); - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { if (NPCGetUniqueID(npcIndex) == -1) @@ -536,11 +540,6 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) { CreateTimer(0.1, Timer_TeleportPlayerToEscapePoint, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } - else - { - int red[4] = { 184, 56, 59, 255 }; - ClientEnableConstantGlow(client, red); - } } else { @@ -557,7 +556,7 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } Call_StartForward(g_OnPlayerSpawnPFwd); - Call_PushCell(client); + Call_PushCell(SF2_BasePlayer(client)); Call_Finish(); #if defined DEBUG @@ -861,6 +860,15 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue && GetClientOfUserId(event.GetInt("userid")) > 0) + { + Call_StartForward(g_OnPlayerDeathPFwd); + Call_PushCell(SF2_BasePlayer(GetClientOfUserId(event.GetInt("userid")))); + Call_PushCell(event.GetInt("attacker")); + Call_PushCell(event.GetInt("inflictor_entindex")); + Call_PushCell((event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0); + Call_Finish(); + } return Plugin_Continue; } @@ -894,9 +902,6 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) ClientResetSlenderStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); - ClientResetInteractiveGlow(client); - - ClientDisableConstantGlow(client); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index f5aa533c..d0fdea84 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -59,6 +59,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnDifficultyVoteFinishedFwd = new GlobalForward("SF2_OnDifficultyVoteFinished", ET_Ignore, Param_Cell, Param_Cell); g_OnIsBossCustomAttackPossibleFwd = new GlobalForward("SF2_OnIsBossCustomAttackPossible", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell); g_OnBossGetCustomAttackActionFwd = new GlobalForward("SF2_OnBossGetCustomAttackAction", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell, Param_CellByRef); + g_OnProjectileTouchFwd = new GlobalForward("SF2_OnProjectileTouch", ET_Ignore, Param_Cell, Param_Cell); CreateNative("SF2_GetConfig", Native_GetConfig); CreateNative("SF2_IsRunning", Native_IsRunning); @@ -103,12 +104,6 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("SF2_SetClientFlashlightBatteryLife", Native_SetClientFlashlightBatteryLife); CreateNative("SF2_IsClientUsingFlashlight", Native_IsClientUsingFlashlight); - CreateNative("SF2_GetClientStaminaPoints", Native_GetClientSprintPoints); - CreateNative("SF2_SetClientStaminaPoints", Native_SetClientSprintPoints); - CreateNative("SF2_IsClientSprinting", Native_IsClientSprinting); - CreateNative("SF2_IsClientReallySprinting", Native_IsClientReallySprinting); - CreateNative("SF2_SetClientSprintState", Native_SetClientSprintState); - CreateNative("SF2_IsClientTrapped", Native_IsClientTrapped); CreateNative("SF2_IsClientInDeathCam", Native_IsClientInDeathCam); CreateNative("SF2_ClientStartDeathCam", Native_ClientStartDeathCam); @@ -131,7 +126,6 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("SF2_IsBossCloaked", Native_IsBossCloaked); CreateNative("SF2_GetBossStunHealth", Native_GetBossStunHealth); CreateNative("SF2_SetBossStunHealth", Native_SetBossStunHealth); - CreateNative("SF2_GetBossGoalPosition", Native_GetBossGoalPosition); CreateNative("SF2_GetVectorSquareMagnitude", Native_GetVectorSquareMagnitude); CreateNative("SF2_InitiateBossPackVote", Native_InitiateBossPackVote); @@ -147,6 +141,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max NPCChaser_InitializeAPI(); NPCStatue_InitializeAPI(); + Client_SetupAPI(); + PvP_InitializeAPI(); PvE_InitializeAPI(); @@ -584,33 +580,6 @@ static any Native_IsClientUsingFlashlight(Handle plugin, int numParams) return IsClientUsingFlashlight(GetNativeCell(1)); } -static any Native_GetClientSprintPoints(Handle plugin, int numParams) -{ - return SF2_BasePlayer(GetNativeCell(1)).Stamina; -} - -static any Native_SetClientSprintPoints(Handle plugin, int numParams) -{ - SF2_BasePlayer(GetNativeCell(1)).Stamina = GetNativeCell(2); - return 0; -} - -static any Native_IsClientSprinting(Handle plugin, int numParams) -{ - return IsClientSprinting(GetNativeCell(1)); -} - -static any Native_IsClientReallySprinting(Handle plugin, int numParams) -{ - return IsClientReallySprinting(GetNativeCell(1)); -} - -static any Native_SetClientSprintState(Handle plugin, int numParams) -{ - ClientHandleSprint(GetNativeCell(1), GetNativeCell(2)); - return 0; -} - static any Native_IsClientTrapped(Handle plugin, int numParams) { return g_PlayerTrapped[GetNativeCell(1)]; @@ -721,11 +690,6 @@ static any Native_SetBossStunHealth(Handle plugin, int numParams) return 0; } -static any Native_GetBossGoalPosition(Handle plugin, int numParams) -{ - return 0; -} - static any Native_GetVectorSquareMagnitude(Handle plugin, int numParams) { float vec1[3], vec2[3]; diff --git a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp index 61ae09fa..243638c9 100644 --- a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp +++ b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp @@ -17,7 +17,7 @@ void SetupRenevantMode() void Renevant_InitializeAPI() { - g_OnRenevantTriggerWaveFwd = new GlobalForward("SF2_OnRenevantWaveTrigger", ET_Ignore, Param_Cell); + g_OnRenevantTriggerWaveFwd = new GlobalForward("SF2_OnRenevantWaveTrigger", ET_Ignore, Param_Cell, Param_Cell); CreateNative("SF2_IsRenevantMap", Native_IsRenevantMap); } @@ -32,6 +32,11 @@ static void OnRoundEnd() static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (fake) { return; @@ -116,22 +121,6 @@ static void Renevant_BroadcastMessage(const char[] message, int params, ...) } } -enum RenevantWave -{ - RenevantWave_Normal = 0, - RenevantWave_IncreaseDifficulty, - RenevantWave_MultiEffect, - RenevantWave_BaconSpray, - RenevantWave_DoubleTrouble, - RenevantWave_DoomBox, - RenevantWave_90s, - RenevantWave_MarkForDeath, - RenevantWave_SingleBoss, - RenevantWave_AdminBoss, - RenevantWave_WallHax, - RenevantWave_Max -} - static void Renevant_DoWaveAction(RenevantWave action) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH], buffer2[SF2_MAX_PROFILE_NAME_LENGTH], buffer3[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -189,7 +178,7 @@ static void Renevant_DoWaveAction(RenevantWave action) { g_DifficultyConVar.IntValue = Difficulty_Apollyon; - g_BossesChaseEndlessly = true; + g_RenevantBossesChaseEndlessly = true; g_IsRoundInfiniteSprint = true; char nightmareDisplay[256]; @@ -447,6 +436,13 @@ void Renevant_SetWave(int wave, bool resetTimer = false) g_RenevantWaveTimer = null; // At zero/max wave so stop it. } + if (wave == 0) + { + return; + } + + RenevantWave randomWave = RenevantWave_Normal; + switch (g_RenevantWaveNumber) { case 1: //Wave 1 @@ -482,10 +478,14 @@ void Renevant_SetWave(int wave, bool resetTimer = false) } delete selectableBosses; } + case 0: + { + // Do nothing + } default: //Waves except 1 { - int randomWave = g_RenevantWaveList.Get(GetRandomInt(0, g_RenevantWaveList.Length - 1)); - Renevant_DoWaveAction(view_as(randomWave)); + randomWave = view_as(g_RenevantWaveList.Get(GetRandomInt(0, g_RenevantWaveList.Length - 1))); + Renevant_DoWaveAction(randomWave); } } @@ -493,12 +493,23 @@ void Renevant_SetWave(int wave, bool resetTimer = false) Call_StartForward(g_OnRenevantTriggerWaveFwd); Call_PushCell(g_RenevantWaveNumber); + Call_PushCell(randomWave); + Call_Finish(); + + Call_StartForward(g_OnRenevantTriggerWavePFwd); + Call_PushCell(g_RenevantWaveNumber); + Call_PushCell(randomWave); Call_Finish(); } static Action Timer_RenevantWave(Handle timer, any data) { - if (timer != g_RenevantWaveTimer || IsRoundEnding()) + if (timer != g_RenevantWaveTimer) + { + return Plugin_Stop; + } + + if (!IsRoundInEscapeObjective()) { return Plugin_Stop; } @@ -549,8 +560,8 @@ static void Renevant_SpawnApollyon() { continue; } - Npc.UnSpawn(); - SpawnSlender(Npc, teleportPos); + Npc.UnSpawn(true); + Npc.Spawn(teleportPos); } } } diff --git a/addons/sourcemod/scripting/sf2/glow.sp b/addons/sourcemod/scripting/sf2/glow.sp new file mode 100644 index 00000000..311d9290 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/glow.sp @@ -0,0 +1,391 @@ +enum struct SF2GlowData +{ + int Ref; + int Color[4]; + bool VisibleTo[MAXTF2PLAYERS]; + + void Init() + { + this.Ref = INVALID_ENT_REFERENCE; + for (int i = 0; i < 4; i++) + { + this.Color[i] = 255; + } + + for (int i = 1; i <= MaxClients; i++) + { + this.VisibleTo[i] = false; + } + } + + bool IsVisibleTo(int client) + { + return this.VisibleTo[client]; + } + + void SetVisibleTo(int client, bool state) + { + this.VisibleTo[client] = state; + } +} + +static StringMap g_GlowMap; + +void SetupGlows() +{ + g_GlowMap = new StringMap(); + + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); + g_OnMapEndPFwd.AddFunction(null, MapEnd); + + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); +} + +static void OnGamemodeEnd() +{ + StringMapSnapshot snapshot = g_GlowMap.Snapshot(); + + SF2GlowData data; + char value[32]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, value, sizeof(value)); + + if (!g_GlowMap.GetArray(value, data, sizeof(data))) + { + continue; + } + + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (entity.IsValid()) + { + RemoveEntity(entity.index); + } + } + + delete snapshot; +} + +static void MapEnd() +{ + g_GlowMap.Clear(); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + StringMapSnapshot snapshot = g_GlowMap.Snapshot(); + + SF2GlowData data; + char value[32]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, value, sizeof(value)); + + if (!g_GlowMap.GetArray(value, data, sizeof(data))) + { + continue; + } + + data.SetVisibleTo(client.index, false); + + g_GlowMap.SetArray(value, data, sizeof(data)); + } + + delete snapshot; +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + DestroyGlowEntity(ent.index); +} + +bool DoesEntityHaveGlow(int ent) +{ + SF2GlowData data; + return GetGlowData(ent, data); +} + +static bool GetGlowData(int ent, SF2GlowData data, bool create = false) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + + if (g_GlowMap.GetArray(key, data, sizeof(data))) + { + return true; + } + + if (create) + { + data.Init(); + + if (g_GlowMap.SetArray(key, data, sizeof(data))) + { + return true; + } + } + + return false; +} + +static bool SetGlowData(int ent, const SF2GlowData data) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + + return g_GlowMap.SetArray(key, data, sizeof(data)); +} + +static bool RemoveGlowData(int ent) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + char key[32]; + IntToString(EnsureEntRef(ent), key, sizeof(key)); + + return g_GlowMap.Remove(key); +} + +void CreateGlowEntity(int ent, const int color[4] = { 255, 255, 255, 255 }, bool preserveColor = false) +{ + if (!g_Enabled) + { + return; + } + + if (!IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data, true)) + { + return; + } + + if (!preserveColor) + { + for (int i = 0; i < 4; i++) + { + data.Color[i] = color[i]; + } + } + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + RemoveEntity(glow.index); + } + + glow = CBaseEntity(TF2_CreateGlow(ent)); + SDKHook(glow.index, SDKHook_SetTransmit, SetTransmit); + data.Ref = EnsureEntRef(glow.index); + SetEntityTransmitState(glow.index, FL_EDICT_FULLCHECK); + + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow.index, UpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, glow.index, ShouldTransmit); + + SetVariantColor(data.Color); + glow.AcceptInput("SetGlowColor"); + glow.AcceptInput("Enable"); + + SetGlowData(ent, data); +} + +int CreateGlowEntityDataless(int ent, const int color[4] = { 255, 255, 255, 255 }) +{ + CBaseEntity glow = CBaseEntity(TF2_CreateGlow(ent)); + SetEntityTransmitState(glow.index, FL_EDICT_FULLCHECK); + + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow.index, UpdateTransmitState); + g_DHookShouldTransmit.HookEntity(Hook_Pre, glow.index, ShouldTransmit); + + SetVariantColor(color); + glow.AcceptInput("SetGlowColor"); + glow.AcceptInput("Enable"); + + return glow.index; +} + +void DestroyGlowEntity(int ent) +{ + if (!IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return; + } + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + RemoveEntity(glow.index); + } + + RemoveGlowData(ent); +} + +void SetGlowVisibility(int ent, const int[] clients, int numClients, const bool[] visibleStates) +{ + if (numClients == 0 || !IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return; + } + + bool update = false; + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + + if (!glow.IsValid()) + { + update = true; + } + + for (int i = 0; i < numClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(clients[i]); + + bool oldVisible = data.IsVisibleTo(player.index); + bool visible = visibleStates[i]; + if (oldVisible == visible) + { + continue; + } + + data.SetVisibleTo(player.index, visible); + + if (!update) + { + if (!visible && player.IsValid && !player.IsBot) + { + update = true; + } + } + } + + SetGlowData(ent, data); + + if (update) + { + CreateGlowEntity(ent, .preserveColor = true); + } +} + +void SetGlowVisibilityForClient(int ent, int client, bool visible) +{ + int clients[1]; + clients[0] = client; + bool visibleStates[1]; + visibleStates[0] = visible; + + SetGlowVisibility(ent, clients, 1, visibleStates); +} + +bool SetGlowColor(int ent, const int color[4]) +{ + if (!IsValidEntity(ent)) + { + return false; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return false; + } + + for (int i = 0; i < 4; i++) + { + data.Color[i] = color[i]; + } + + SetGlowData(ent, data); + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + SetVariantColor(color); + glow.AcceptInput("SetGlowColor"); + } + + return true; +} + +void SetGlowModel(int ent, const char[] model) +{ + if (!IsValidEntity(ent)) + { + return; + } + + SF2GlowData data; + if (!GetGlowData(ent, data)) + { + return; + } + + CBaseEntity glow = CBaseEntity(EntRefToEntIndex(data.Ref)); + if (glow.IsValid()) + { + glow.SetModel(model); + } +} + +static Action SetTransmit(int ent, int other) +{ + CBaseEntity glow = CBaseEntity(ent); + CBaseEntity target = CBaseEntity(glow.GetPropEnt(Prop_Send, "m_hTarget")); + if (!target.IsValid()) + { + return Plugin_Handled; + } + + SF2GlowData data; + if (!GetGlowData(target.index, data)) + { + return Plugin_Handled; + } + + if (glow.GetProp(Prop_Send, "m_bDisabled")) + { + return Plugin_Continue; + } + + return data.IsVisibleTo(other) ? Plugin_Continue : Plugin_Handled; +} + +static MRESReturn UpdateTransmitState(int glow, DHookReturn returnHook) +{ + returnHook.Value = SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn ShouldTransmit(int glow, DHookReturn returnHook, DHookParam param) +{ + returnHook.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp index 7b6733d1..6ceb00b2 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_boss_maker.sp @@ -289,7 +289,7 @@ methodmap SF2BossMakerEntity < SF2SpawnPointBaseEntity continue; } - boss.UnSpawn(); + boss.UnSpawn(true); } } diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index e104227d..89481150 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -263,11 +263,19 @@ methodmap SF2NPC_BaseNPC SpawnSlender(this, pos); } - public void UnSpawn() + public void UnSpawn(bool instant = false) { if (IsValidEntity(this.EntIndex)) { - RemoveEntity(this.EntIndex); + SF2_ChaserEntity chaser = SF2_ChaserEntity(this.EntIndex); + if (chaser.IsValid() && !instant) + { + chaser.ShouldDespawn = true; + } + else + { + RemoveEntity(this.EntIndex); + } } } @@ -417,6 +425,19 @@ methodmap SF2NPC_BaseNPC { NPCSetAffectedBySightState(this.Index, state); } + + property int DefaultTeam + { + public get() + { + return NPCGetDefaultTeam(this.Index); + } + + public set(int value) + { + NPCSetDefaultTeam(this.Index, value); + } + } } methodmap SF2_BasePlayer < CBaseCombatCharacter @@ -1081,14 +1102,6 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter ClientUpdateMusicSystem(this.index, initialize); } - property bool HasConstantGlow - { - public get() - { - return DoesClientHaveConstantGlow(this.index); - } - } - public void SetPlayState(bool state, bool enablePlay = true) { SetClientPlayState(this.index, state, enablePlay); @@ -2579,8 +2592,7 @@ static any Native_GetClientHasConstantGlow(Handle plugin, int numParams) return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); } - SF2_BasePlayer player = SF2_BasePlayer(client); - return player.HasConstantGlow; + return DoesEntityHaveGlow(client); } static any Native_SetClientPlayState(Handle plugin, int numParams) diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 08804967..d860a2e5 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -26,7 +26,6 @@ static int g_NpcModelBodyGroups[MAX_BOSSES] = { 0, ... }; static int g_NpcModelBodyGroupsDifficulty[MAX_BOSSES][Difficulty_Max]; static int g_NpcModelBodyGroupsMax[MAX_BOSSES] = { 0, ... }; bool g_NpcRaidHitbox[MAX_BOSSES] = { false, ... }; -int g_NpcGlowEntity[MAX_BOSSES] = { INVALID_ENT_REFERENCE, ... }; static float g_NpcSoundMusicLoop[MAX_BOSSES][Difficulty_Max]; static int g_NpcAllowMusicOnDifficulty[MAX_BOSSES]; static char g_NpcName[MAX_BOSSES][Difficulty_Max][SF2_MAX_PROFILE_NAME_LENGTH]; @@ -107,6 +106,8 @@ static float g_NpcProxySpawnEffectZOffset[MAX_BOSSES]; static bool g_NpcShouldBeAffectedBySight[MAX_BOSSES]; +static int g_NpcDefaultTeam[MAX_BOSSES]; + Handle timerMusic = null;//Planning to add a bosses array on. bool NPCGetBossName(int npcIndex = -1, char[] buffer, int bufferLen, char profile[SF2_MAX_PROFILE_NAME_LENGTH] = "") @@ -195,6 +196,7 @@ void NPCInitialize() g_OnEntityTeleportedPFwd.AddFunction(null, EntityTeleported); NPCChaserInitialize(); + SetupNPCGlows(); } void NPC_InitializeAPI() @@ -205,7 +207,7 @@ void NPC_InitializeAPI() g_OnBossRemovedFwd = new GlobalForward("SF2_OnBossRemoved", ET_Ignore, Param_Cell); g_OnBossFinishSpawningFwd = new GlobalForward("SF2_OnBossFinishSpawning", ET_Ignore, Param_Cell); g_OnClientCaughtByBossFwd = new GlobalForward("SF2_OnClientCaughtByBoss", ET_Ignore, Param_Cell, Param_Cell); - + CreateNative("SF2_GetMaxBossCount", Native_GetMaxBosses); CreateNative("SF2_EntIndexToBossIndex", Native_EntIndexToBossIndex); CreateNative("SF2_BossIndexToEntIndex", Native_BossIndexToEntIndex); @@ -239,6 +241,13 @@ void NPC_InitializeAPI() CreateNative("SF2_SetBossTeleportThinkTimer", Native_SetBossTeleportThinkTimer); CreateNative("SF2_GetBossTeleportTarget", Native_GetBossTeleportTarget); + CreateNative("SF2_GetBossGoalPosition", Native_GetBossGoalPosition); + + CreateNative("SF2_GetBossTimeUntilNoPersistence", Native_Nothing); + CreateNative("SF2_SetBossTimeUntilNoPersistence", Native_Nothing); + CreateNative("SF2_GetBossTimeUntilAlert", Native_GetBossCurrentChaseDuration); + CreateNative("SF2_SetBossTimeUntilAlert", Native_SetBossCurrentChaseDuration); + CreateNative("SF2_GetProfileFromBossIndex", Native_GetProfileData); CreateNative("SF2_GetProfileFromName", Native_GetProfileDataEx); @@ -363,13 +372,6 @@ void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity) g_BossPathFollower[bossIndex].Invalidate(); } - int glowEnt = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - if (glowEnt && glowEnt != INVALID_ENT_REFERENCE) - { - RemoveEntity(glowEnt); - } - g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; - if (GetBossProfileDespawnParticleState(profile)) { SlenderCreateParticleSpawnEffect(bossIndex, true); @@ -576,6 +578,12 @@ void CheckIfMusicValid() { continue; } + SF2BossProfileData data; + data = NPCGetProfileData(i); + if (data.IsPvEBoss) + { + continue; + } if (g_NpcAllowMusicOnDifficulty[i] & difficulty) { currentMusicTrackNormal[0] = '\0'; @@ -1135,11 +1143,14 @@ void GetBossMusic(char[] buffer,int bufferLen) } } } -static Action BossMusic(Handle timer,any bossIndex) + +static Action BossMusic(Handle timer, any bossIndex) { int difficulty = g_DifficultyConVar.IntValue; float time = NPCGetSoundMusicLoop(bossIndex, difficulty); - if (time > 0.0 && (g_NpcAllowMusicOnDifficulty[bossIndex] & difficulty)) + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); + if (!data.IsPvEBoss && time > 0.0 && (g_NpcAllowMusicOnDifficulty[bossIndex] & difficulty)) { if (bossIndex > -1) { @@ -1294,26 +1305,6 @@ bool NPCGetCustomOutlinesState(int npcIndex) return g_SlenderUseCustomOutlines[npcIndex]; } -int NPCGetOutlineColorR(int npcIndex) -{ - return g_SlenderOutlineColorR[npcIndex]; -} - -int NPCGetOutlineColorG(int npcIndex) -{ - return g_SlenderOutlineColorG[npcIndex]; -} - -int NPCGetOutlineColorB(int npcIndex) -{ - return g_SlenderOutlineColorB[npcIndex]; -} - -int NPCGetOutlineTransparency(int npcIndex) -{ - return g_SlenderOutlineTransparency[npcIndex]; -} - bool NPCGetRainbowOutlineState(int npcIndex) { return g_SlenderUseRainbowOutline[npcIndex]; @@ -1543,6 +1534,16 @@ void NPCSetAffectedBySightState(int npcIndex, bool state) g_NpcShouldBeAffectedBySight[npcIndex] = state; } +int NPCGetDefaultTeam(int npcIndex) +{ + return g_NpcDefaultTeam[npcIndex]; +} + +void NPCSetDefaultTeam(int npcIndex, int team) +{ + g_NpcDefaultTeam[npcIndex] = team; +} + bool NPCShouldSeeEntity(int npcIndex, int entity) { if (!IsValidEntity(entity)) @@ -1815,10 +1816,6 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF GetBossProfileFestiveLightAngles(profile, g_NpcFestiveLightAng[npc.Index]); g_SlenderUseCustomOutlines[npc.Index] = GetBossProfileCustomOutlinesState(profile); - g_SlenderOutlineColorR[npc.Index] = GetBossProfileOutlineColorR(profile); - g_SlenderOutlineColorG[npc.Index] = GetBossProfileOutlineColorG(profile); - g_SlenderOutlineColorB[npc.Index] = GetBossProfileOutlineColorB(profile); - g_SlenderOutlineTransparency[npc.Index] = GetBossProfileOutlineTransparency(profile); g_SlenderUseRainbowOutline[npc.Index] = GetBossProfileRainbowOutlineState(profile); g_NpcHasProxyWeaponsEnabled[npc.Index] = GetBossProfileProxyWeapons(profile); @@ -1852,6 +1849,8 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF g_NpcShouldBeAffectedBySight[npc.Index] = false; + g_NpcDefaultTeam[npc.Index] = g_DefaultBossTeamConVar.IntValue; + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { g_NpcHasFakeCopiesEnabled[npc.Index][difficulty] = GetBossProfileFakeCopies(profile, difficulty); @@ -1980,7 +1979,21 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF if (!profileData.IsPvEBoss) { - g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + if (g_Enabled) + { + g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + else + { + if (g_NpcDefaultTeam[npc.Index] == TFTeam_Blue || g_NpcDefaultTeam[npc.Index] == TFTeam_Red) + { + g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderRespawnThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + else + { + g_SlenderThink[npc.Index] = CreateTimer(0.3, Timer_SlenderTeleportThink, npc.UniqueID, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + } + } } switch (bossType) @@ -2043,14 +2056,17 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF { continue; } - if (profileData.IsPvEBoss && !g_PlayerEliminated[i]) + if (g_Enabled) { - continue; + if (profileData.IsPvEBoss && !g_PlayerEliminated[i]) + { + continue; + } } soundInfo.EmitSound(true, i); } } - if (!profileData.IsPvEBoss && timerMusic == null) + if (g_Enabled && !profileData.IsPvEBoss && timerMusic == null) { bool allowMusic = false; float time; @@ -2548,6 +2564,69 @@ bool NPCFindUnstuckPosition(SF2_BaseBoss boss, float lastPos[3], float destinati } } + SF2BossProfileData data; + data = controller.GetProfileData(); + + int ent = -1; + char targetName[64]; + if (data.IsPvEBoss) + { + ArrayList spawnPointList = new ArrayList(); + + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (!StrContains(targetName, "minigames_boss_spawnpoint", false)) + { + spawnPointList.Push(ent); + } + } + + if (spawnPointList.Length > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, spawnPointList.Length - 1)); + } + + delete spawnPointList; + + if (!IsValidEntity(ent)) + { + return false; + } + + CBaseEntity(ent).GetAbsOrigin(destination); + return true; + } + + if (SF_IsBoxingMap()) + { + ArrayList spawnPointList = new ArrayList(); + + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if (!StrContains(targetName, "sf2_boss_respawnpoint", false)) + { + spawnPointList.Push(ent); + } + } + + if (spawnPointList.Length > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, spawnPointList.Length - 1)); + } + + delete spawnPointList; + + if (!IsValidEntity(ent)) + { + return false; + } + + CBaseEntity(ent).GetAbsOrigin(destination); + return true; + } + return false; } @@ -2570,7 +2649,12 @@ void ChangeAllSlenderModels() NPCGetProfile(npcIndex, profile, sizeof(profile)); GetSlenderModel(npcIndex, _, buffer, sizeof(buffer)); SetEntityModel(slender, buffer); - SetEntityModel(EntRefToEntIndex(g_NpcGlowEntity[npcIndex]), buffer); + + if (SF2_ChaserEntity(slender).IsValid()) + { + SF2_ChaserEntity(slender).UpdateMovementAnimation(); + } + SetGlowModel(slender, buffer); if (NPCGetModelSkinMax(npcIndex) > 0) { int randomSkin = GetRandomInt(0, NPCGetModelSkinMax(npcIndex)); @@ -2633,7 +2717,7 @@ void ChangeAllSlenderModels() void RemoveProfile(int bossIndex) { SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(bossIndex); - controller.UnSpawn(); + controller.UnSpawn(true); // Call our forward. Call_StartForward(g_OnBossRemovedFwd); @@ -2761,7 +2845,7 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) } char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - npc.UnSpawn(); + npc.UnSpawn(true); npc.GetProfile(profile,sizeof(profile)); float truePos[3], trueAng[3]; @@ -3006,17 +3090,29 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) SF2BossProfileData data; data = npc.GetProfileData(); - if (!data.IsPvEBoss) + if (g_Enabled) { - entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + if (!data.IsPvEBoss && (npc.Flags & SFF_ATTACKWAITERS) == 0) + { + entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Blue); + } + else + { + entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Spectator); + } } else { - entity.SetProp(Prop_Data, "m_iTeamNum", TFTeam_Spectator); + entity.SetProp(Prop_Data, "m_iTeamNum", NPCGetDefaultTeam(bossIndex)); } int difficulty = GetLocalGlobalDifficulty(bossIndex); + if (g_BossPathFollower[bossIndex] == view_as(0)) + { + g_BossPathFollower[bossIndex] = PathFollower(_, TraceRayDontHitAnyEntity_Pathing, Path_FilterOnlyActors); + } + g_BossPathFollower[bossIndex].SetMinLookAheadDistance(GetBossProfileNodeDistanceLookAhead(profile)); if (NPCGetModelSkinMax(bossIndex) > 0) @@ -3053,9 +3149,16 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) } entity.AddFlag(FL_NPC); - if (!data.IsPvEBoss) + if (g_Enabled) { - entity.AddFlag(FL_NOTARGET); + if (!data.IsPvEBoss) + { + entity.AddFlag(FL_NOTARGET); + } + } + else + { + entity.AddFlag(FL_CLIENT); } //SetEntityTransmitState(entity.index, FL_EDICT_ALWAYS); @@ -3072,63 +3175,6 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) SlenderCreateParticleSpawnEffect(bossIndex); } - if (!data.IsPvEBoss) - { - if (NPCGetCustomOutlinesState(bossIndex)) - { - if (!NPCGetRainbowOutlineState(bossIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(bossIndex); - color[1] = NPCGetOutlineColorG(bossIndex); - color[2] = NPCGetOutlineColorB(bossIndex); - color[3] = NPCGetOutlineTransparency(bossIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(bossIndex, color); - } - else - { - SlenderAddGlow(bossIndex, {0, 0, 0, 0}); - } - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(bossIndex, purple); - } - } - int master = g_SlenderCopyMaster[bossIndex]; int flags = NPCGetFlags(bossIndex); @@ -3154,6 +3200,10 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) Call_StartForward(g_OnBossSpawnFwd); Call_PushCell(bossIndex); Call_Finish(); + + Call_StartForward(g_OnBossSpawnPFwd); + Call_PushCell(npc); + Call_Finish(); } bool ClimbUpCBase(CBaseNPC_Locomotion loco, const float goal[3], const float fwd[3], int entity) @@ -3220,6 +3270,15 @@ void UpdateHealthBar(int bossIndex, int optionalSetPercent = -1) SetEntProp(g_HealthBar, Prop_Send, "m_iBossHealthPercentageByte", healthPercent); } +void SetHealthBarColor(bool green) +{ + if (g_HealthBar == -1) + { + return; + } + SetEntProp(g_HealthBar, Prop_Send, "m_iBossState", view_as(green)); +} + /*Action Timer_BossBurn(Handle timer, any entref) { if (!g_Enabled) @@ -3381,31 +3440,6 @@ Action Timer_BossMarked(Handle timer, any entref) return Plugin_Stop; }*/ -static Action Hook_SlenderGlowSetTransmit(int entity,int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - if (g_PlayerProxy[other]) - { - return Plugin_Continue; - } - if (IsClientInGhostMode(other)) - { - return Plugin_Continue; - } - if ((SF_SpecialRound(SPECIALROUND_WALLHAX) || g_EnableWallHaxConVar.BoolValue || g_RenevantWallHax) && GetClientTeam(other) == TFTeam_Red && !DidClientEscape(other) && !g_PlayerEliminated[other]) - { - return Plugin_Continue; - } - if (g_RestartSessionEnabled) - { - return Plugin_Continue; - } - return Plugin_Handled; -} - bool SlenderUsesBlink(int bossIndex) { if (NPCGetType(bossIndex) == SF2BossType_Statue) @@ -3417,6 +3451,11 @@ bool SlenderUsesBlink(int bossIndex) void SlenderPrintChatMessage(int bossIndex, int player) { + if (g_Enabled && GetClientTeam(player) != TFTeam_Red) + { + return; + } + if (bossIndex == -1) { return; @@ -3466,11 +3505,23 @@ void SlenderPrintChatMessage(int bossIndex, int player) { prefix = "[SF2]"; } - if (buffer[0] != '\0' && GetClientTeam(player) == 2) + if (buffer[0] != '\0') { if (StrContains(buffer, "[PLAYER]", true) != -1) { - FormatEx(replacePlayer, sizeof(replacePlayer), "{red}%s{default}", playerName); + switch (GetClientTeam(player)) + { + case TFTeam_Red: + { + FormatEx(replacePlayer, sizeof(replacePlayer), "{red}%s{default}", playerName); + } + + case TFTeam_Blue: + { + FormatEx(replacePlayer, sizeof(replacePlayer), "{blue}%s{default}", playerName); + } + } + ReplaceString(buffer, sizeof(buffer), "[PLAYER]", replacePlayer); } if (StrContains(buffer, "[BOSS]", true) != -1) @@ -3659,6 +3710,11 @@ void SlenderCreateParticleSpawnEffect(int bossIndex, bool despawn = false) static Action Timer_SlenderTeleportThink(Handle timer, any id) { + if (!g_Enabled) + { + return Plugin_Stop; + } + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC.FromUniqueId(id); if (!controller.IsValid()) { @@ -4061,6 +4117,90 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) return Plugin_Continue; } +static Action Timer_SlenderRespawnThink(Handle timer, any id) +{ + if (g_Enabled) + { + return Plugin_Stop; + } + + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC.FromUniqueId(id); + if (!controller.IsValid()) + { + return Plugin_Stop; + } + + int bossIndex = controller.Index; + if (timer != g_SlenderThink[bossIndex]) + { + return Plugin_Stop; + } + + float gameTime = GetGameTime(); + + int difficulty = GetLocalGlobalDifficulty(bossIndex); + + bool cont = false; + + int bossEnt = controller.EntIndex; + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE && controller.TeleportType == 2) + { + cont = true; + } + + if (!NPCIsTeleportAllowed(bossIndex, difficulty) && (!bossEnt || bossEnt == INVALID_ENT_REFERENCE)) + { + cont = true; + } + + if (cont) + { + float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; + return Plugin_Continue; + } + + if (gameTime >= g_SlenderNextTeleportTime[bossIndex]) + { + float teleportTime = GetRandomFloat(NPCGetTeleportTimeMin(bossIndex, difficulty), NPCGetTeleportTimeMax(bossIndex, difficulty)); + g_SlenderNextTeleportTime[bossIndex] = gameTime + teleportTime; + + if (bossEnt && bossEnt != INVALID_ENT_REFERENCE) // For teleport type 0 + { + controller.UnSpawn(); + return Plugin_Continue; + } + + ArrayList spawnPoint = new ArrayList(); + float teleportPos[3]; + int ent = -1, spawnTeam = 0; + while ((ent = FindEntityByClassname(ent, "info_player_teamspawn")) != -1) + { + spawnTeam = GetEntProp(ent, Prop_Data, "m_iInitialTeamNum"); + if (spawnTeam == NPCGetDefaultTeam(bossIndex)) + { + spawnPoint.Push(ent); + } + } + + ent = -1; + if (spawnPoint.Length > 0) + { + ent = spawnPoint.Get(GetRandomInt(0, spawnPoint.Length - 1)); + } + + delete spawnPoint; + + if (IsValidEntity(ent)) + { + CBaseEntity(ent).GetAbsOrigin(teleportPos); + controller.Spawn(teleportPos); + } + } + + return Plugin_Continue; +} + bool SlenderMarkAsFake(int bossIndex) { int bossFlags = NPCGetFlags(bossIndex); @@ -4253,58 +4393,6 @@ bool PeopleCanSeeSlender(int bossIndex, bool checkFOV = true, bool checkBlink = return IsNPCVisibleToAPlayer(bossIndex, checkFOV, checkBlink, checkEliminated); } -bool SlenderAddGlow(int bossIndex, int color[4] = {255, 255, 255, 255}) -{ - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return false; - } - - char buffer[PLATFORM_MAX_PATH]; - GetEntPropString(slender, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - - if (buffer[0] == '\0') - { - return false; - } - - // This is so much simpler, I don't know why I didn't make this earlier. - int glow = TF2_CreateGlow(slender); - if (IsValidEntity(glow)) - { - SDKHook(glow, SDKHook_SetTransmit, Hook_SlenderGlowSetTransmit); - - g_NpcGlowEntity[bossIndex] = EntIndexToEntRef(glow); - //Set our desired glow color - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - g_DHookShouldTransmit.HookEntity(Hook_Pre, glow, Hook_EntityShouldTransmit); - g_DHookUpdateTransmitState.HookEntity(Hook_Pre, glow, Hook_GlowUpdateTransmitState); - SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); - - return true; - } - - return false; -} - -void SlenderRemoveGlow(int bossIndex) -{ - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - int glow = EntRefToEntIndex(g_NpcGlowEntity[bossIndex]); - if (glow && glow != INVALID_ENT_REFERENCE) - { - RemoveEntity(glow); - g_NpcGlowEntity[bossIndex] = INVALID_ENT_REFERENCE; - } -} - bool IsNPCVisibleToPlayer(int npcIndex, int client, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true) { if (!NPCIsValid(npcIndex)) @@ -4787,8 +4875,8 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint return true; } +#include "npc/glow.sp" #include "sf2/npc/npc_chaser.sp" -//#include "sf2/npc/npc_chaser_takedamage.sp" #include "sf2/npc/entities/initialize.sp" static any Native_GetMaxBosses(Handle plugin, int numParams) @@ -4919,13 +5007,25 @@ static any Native_GetBossIdleLifetime(Handle plugin, int numParams) static any Native_GetBossState(Handle plugin, int numParams) { - SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + int entity = NPCGetEntIndex(GetNativeCell(1)); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss boss = SF2_BaseBoss(entity); return boss.State; } static any Native_SetBossState(Handle plugin, int numParams) { - SF2_BaseBoss boss = SF2_BaseBoss(NPCGetEntIndex(GetNativeCell(1))); + int entity = NPCGetEntIndex(GetNativeCell(1)); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss boss = SF2_BaseBoss(entity); boss.State = GetNativeCell(2); return 0; } @@ -4977,6 +5077,67 @@ static any Native_GetBossTeleportTarget(Handle plugin, int numParams) return EntRefToEntIndex(g_SlenderTeleportTarget[GetNativeCell(1)]); } +static any Native_GetBossGoalPosition(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + if (!controller.Path.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid path for boss index %d", controller.Index); + } + + float ret[3]; + controller.Path.GetEndPosition(ret); + SetNativeArray(2, ret, 3); + return 0; +} + +static any Native_Nothing(Handle plugin, int numParams) +{ + return 0; +} + +static any Native_GetBossCurrentChaseDuration(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + int entity = controller.EntIndex; + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.CurrentChaseDuration; +} + +static any Native_SetBossCurrentChaseDuration(Handle plugin, int numParams) +{ + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + int entity = controller.EntIndex; + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + bossEntity.CurrentChaseDuration = GetNativeCell(2); + return 0; +} + static any Native_GetProfileData(Handle plugin, int numParams) { SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(GetNativeCell(1)); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index cda6237e..c7a56483 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -473,6 +473,19 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter this.Controller.GetEyePosition(buffer, defaultValue); } + property int Team + { + public get() + { + return this.GetProp(Prop_Data, "m_iTeamNum"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_iTeamNum", value); + } + } + public static void SetupAPI() { CreateNative("SF2_BaseBossEntity.IsValid.get", Native_GetIsValid); @@ -694,17 +707,12 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter SF2NPC_BaseNPC controller = this.Controller; SF2BossProfileData data; data = controller.GetProfileData(); - int glow = EntRefToEntIndex(g_NpcGlowEntity[controller.Index]); int color[4]; color[0] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 0) * 127.5 + 127.5); color[1] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 2) * 127.5 + 127.5); color[2] = RoundToNearest(Cosine((GetGameTime() * data.RainbowOutlineCycle) + controller.Index + 4) * 127.5 + 127.5); color[3] = 255; - if (glow && glow != INVALID_ENT_REFERENCE) - { - SetVariantColor(color); - AcceptEntityInput(glow, "SetGlowColor"); - } + SetGlowColor(this.index, color); } public void CheckVelocityCancel() diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp index ad614dff..d99aad52 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp @@ -200,15 +200,23 @@ static int OnStart(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBot static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float interval) { - if (IsBeatBoxBeating(2)) + SF2NPC_Chaser controller = actor.Controller; + if (!controller.IsValid()) + { + return action.Continue(); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + + if (!originalData.IsPvEBoss && IsBeatBoxBeating(2)) { return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); } INextBot bot = actor.MyNextBotPointer(); ILocomotion loco = bot.GetLocomotionInterface(); - SF2NPC_Chaser controller = actor.Controller; - SF2ChaserBossProfileData data; - data = controller.GetProfileData(); int difficulty = controller.Difficulty; float gameTime = GetGameTime(); CBaseEntity target = actor.Target; @@ -295,9 +303,15 @@ static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float in actor.GetAlertTriggerPosition(alertTarget, pos); UpdateAlertPosition(action, actor, pos, data.AlertRunOnHearSound[difficulty]); - action.IsRunning = false; } } + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0) + { + float pos[3]; + actor.GetAlertTriggerPositionEx(pos); + + UpdateAlertPosition(action, actor, pos, data.AlertRunOnHearSound[difficulty]); + } if ((interruptConditions & COND_DEBUG) != 0) { @@ -307,7 +321,17 @@ static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float in UpdateAlertPosition(action, actor, pos, data.AlertRunOnWander[difficulty]); } - if (action.HasReachedAlertPosition && data.CanWander[difficulty]) + bool isAbleToWander = action.HasReachedAlertPosition && data.CanWander[difficulty]; + if (controller.HasAttribute(SF2Attribute_BlockWalkSpeedUnderDifficulty)) + { + int value = RoundToNearest(controller.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)); + if (difficulty < value) + { + isAbleToWander = false; + } + } + + if (isAbleToWander) { if (gameTime >= action.NextWanderTime) { @@ -406,6 +430,17 @@ static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float in static void UpdateAlertPosition(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, const float newGoalPos[3], bool shouldRun) { + SF2NPC_Chaser controller = actor.Controller; + int difficulty = controller.Difficulty; + if (controller.HasAttribute(SF2Attribute_BlockWalkSpeedUnderDifficulty)) + { + int value = RoundToNearest(controller.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)); + if (difficulty < value) + { + return; + } + } + INextBot bot = actor.MyNextBotPointer(); PathFollower path = actor.Controller.Path; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp index 0470c421..07093f78 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -210,10 +210,6 @@ static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBo return action.Done("No attack action was given"); } - action.OldState = actor.State; - action.OldMovementType = actor.MovementType; - actor.State = STATE_ATTACK; - actor.SetAttackName(action.GetAttackName()); actor.AttackIndex = action.AttackIndex; actor.IsAttacking = true; @@ -232,6 +228,19 @@ static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBo actor.MyNextBotPointer().GetLocomotionInterface().Stop(); controller.Path.Invalidate(); + if (actor.State == STATE_CHASE) + { + actor.CurrentChaseDuration += data.ChaseDurationAddOnAttack[difficulty]; + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + + action.OldState = actor.State; + action.OldMovementType = actor.MovementType; + actor.State = STATE_ATTACK; + actor.InvokeOnStartAttack(action.GetAttackName()); if (attackData.StartEffects != null) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp index 054ba585..8148e30e 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp @@ -219,22 +219,22 @@ static void DoBulletAttack(SF2_ChaserEntity actor, const char[] attackName) NormalizeVector(direction, direction); GetVectorAngles(direction, angle); + float fwd[3], right[3], up[3]; + GetAngleVectors(angle, fwd, right, up); + + float dir[3], end[3]; + float x, y; + for (int i = 0; i < attackData.BulletCount[difficulty]; i++) { - float x, y; x = GetRandomFloat(-0.5, 0.5) + GetRandomFloat(-0.5, 0.5); y = GetRandomFloat(-0.5, 0.5) + GetRandomFloat(-0.5, 0.5); - float fwd[3], right[3], up[3]; - GetAngleVectors(angle, fwd, right, up); - - float dir[3]; dir[0] = fwd[0] + x * spread * right[0] + y * spread * up[0]; dir[1] = fwd[1] + x * spread * right[1] + y * spread * up[1]; dir[2] = fwd[2] + x * spread * right[2] + y * spread * up[2]; NormalizeVector(dir, dir); - float end[3]; end[0] = effectPos[0] + dir[0] * 9001.0; end[1] = effectPos[1] + dir[1] * 9001.0; end[2] = effectPos[2] + dir[2] * 9001.0; @@ -247,73 +247,52 @@ static void DoBulletAttack(SF2_ChaserEntity actor, const char[] attackName) if (hitTarget == -1) { delete trace; - return; + continue; } float endPos[3]; TR_GetEndPosition(endPos, trace); - if (hitTarget == 0) - { - float normal[3]; - TR_GetPlaneNormal(trace, normal); - GetVectorAngles(normal, normal); - CreateParticle("impact_concrete", endPos, normal); - } + TE_SetupEffectDispatch(GetEffectDispatchStringTableIndex("Impact"), + .origin = endPos, + .start = effectPos, + .damageType = DMG_BULLET, + .surfaceProp = TR_GetSurfaceProps(trace), + .hitbox = TR_GetHitGroup(trace), + .entindex = TR_GetEntityIndex(trace)); + TE_SendToAll(); SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.BulletDamage[difficulty], DMG_BULLET, _, CalculateBulletDamageForce(dir, 1.0), endPos); attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); - char effect[PLATFORM_MAX_PATH]; - FormatEx(effect, sizeof(effect), "%s", attackData.BulletTrace); if (attackData.BulletTrace[0] == '\0') { delete trace; - return; + continue; } int table = FindStringTable("ParticleEffectNames"); - if (table == INVALID_STRING_TABLE) - { - LogError("Could not find string table: ParticleEffectNames"); - delete trace; - return; - } - char tmp[256]; int count = GetStringTableNumStrings(table); - int strIndex = INVALID_STRING_INDEX; + int index = INVALID_STRING_INDEX; for (int i2 = 0; i2 < count; i2++) { ReadStringTable(table, i2, tmp, sizeof(tmp)); - if (strcmp(tmp, effect, false) == 0) + if (strcmp(tmp, attackData.BulletTrace, false) == 0) { - strIndex = i2; + index = i2; break; } } - if (strIndex == INVALID_STRING_INDEX) + + if (index == INVALID_STRING_INDEX) { - LogError("Could not find particle: %s", effect); + LogError("Could not find particle: %s", attackData.BulletTrace); delete trace; - return; + continue; } - - TE_Start("TFParticleEffect"); - TE_WriteFloat("m_vecOrigin[0]", effectPos[0]); - TE_WriteFloat("m_vecOrigin[1]", effectPos[1]); - TE_WriteFloat("m_vecOrigin[2]", effectPos[2]); - TE_WriteNum("m_iParticleSystemIndex", strIndex); - TE_WriteNum("entindex", actor.index); - TE_WriteNum("m_iAttachType", 2); - TE_WriteNum("m_iAttachmentPointIndex", 0); - TE_WriteNum("m_bResetParticles", false); - TE_WriteNum("m_bControlPoint1", 1); - TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", 5); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", endPos[0]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", endPos[1]); - TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", endPos[2]); + TE_Particle(index, effectPos, effectPos, dir, actor.index, view_as(PATTACH_CUSTOMORIGIN), _, _, true, view_as(PATTACH_CUSTOMORIGIN), endPos); TE_SendToAll(); } delete trace; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp index ea4fae7c..82504be0 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp @@ -163,6 +163,14 @@ static int Update(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, f } } + CBaseEntity target = actor.Target; + if (target.IsValid()) + { + float lookAt[3]; + target.GetAbsOrigin(lookAt); + actor.MyNextBotPointer().GetLocomotionInterface().FaceTowards(lookAt); + } + return action.Continue(); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp index 25d59147..b2eed628 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -186,11 +186,9 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity attackEliminated = true; } float damage = attackData.Damage[difficulty]; - float damageVsProps = attackData.DamageVsProps; if (SF_SpecialRound(SPECIALROUND_TINYBOSSES)) { damage *= 0.5; - damageVsProps *= 0.5; } int damageType = attackData.DamageType[difficulty]; @@ -218,13 +216,23 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity { CBaseEntity prop = CBaseEntity(targets.Get(i)); + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, prop, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(prop)) + { + continue; + } + if (!IsTargetInMeleeChecks(actor, attackData, prop, range, spread)) { continue; } hit = true; - SDKHooks_TakeDamage(prop.index, actor.index, actor.index, damageVsProps, 64, _, _, myEyePos, false); + SDKHooks_TakeDamage(prop.index, actor.index, actor.index, damage, 64, _, _, myEyePos, false); } targets.Clear(); @@ -234,7 +242,12 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity { SF2_BasePlayer player = targets.Get(i); - if (!attackEliminated && player.IsEliminated) + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, player, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(player)) { continue; } @@ -276,6 +289,12 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity player.StartDeathCam(controller.Index, myPos); } } + + if (attackData.HitEffects != null) + { + SlenderSpawnEffects(attackData.HitEffects, controller.Index, false, _, _, _, player.index); + } + player.TakeDamage(_, actor.index, actor.index, damage, damageType, _, direction, myEyePos, false); player.ViewPunch(viewPunch); @@ -292,11 +311,6 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity attackData.ApplyDamageEffects(player, difficulty, SF2_ChaserBossEntity(actor.index)); - if (attackData.HitEffects != null) - { - SlenderSpawnEffects(attackData.HitEffects, controller.Index, false, _, _, _, player.index); - } - // Add stress float stressScalar = damage / 125.0; if (stressScalar > 1.0) @@ -342,7 +356,7 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity if (attackData.Disappear[difficulty]) { - controller.UnSpawn(); + controller.UnSpawn(true); } } else diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp index f0d3e6b2..cf079605 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/projectile.sp @@ -136,6 +136,7 @@ static int Update(SF2_ChaserAttackAction_Projectile action, SF2_ChaserEntity act if (action.NextFireTime >= 0.0 && gameTime > action.NextFireTime && attackData.EventNumber == -1) { FireProjectile(actor, action.GetAttackName()); + actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_ProjectileShoot], _, action.GetAttackName()); int repeatState = attackData.Repeat; if (repeatState > 0) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp index 5f893c2b..a4286682 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/tongue.sp @@ -300,9 +300,12 @@ static int Update(SF2_ChaserAttackAction_Tongue action, SF2_ChaserEntity actor, { SF2_BasePlayer player = players.Get(i); - if (!attackEliminated && player.IsEliminated) + if (g_Enabled) { - continue; + if (!attackEliminated && player.IsEliminated) + { + continue; + } } float worldSpace[3]; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp index 64c5949e..3dec2a27 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -61,25 +61,26 @@ static int OnStart(SF2_ChaserChaseAction action, SF2_ChaserEntity actor, NextBot static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) { - if (IsBeatBoxBeating(2) && actor.State != STATE_ATTACK && !actor.IsInChaseInitial) - { - return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); - } SF2NPC_Chaser controller = actor.Controller; - if (!controller.IsValid()) { return action.Continue(); } - CBaseEntity target = actor.Target; - float gameTime = GetGameTime(); - int difficulty = controller.Difficulty; - INextBot bot = actor.MyNextBotPointer(); SF2ChaserBossProfileData data; data = controller.GetProfileData(); SF2BossProfileData originalData; originalData = view_as(controller).GetProfileData(); + + if (!originalData.IsPvEBoss && IsBeatBoxBeating(2) && actor.State != STATE_ATTACK && !actor.IsInChaseInitial) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + + CBaseEntity target = actor.Target; + float gameTime = GetGameTime(); + int difficulty = controller.Difficulty; + INextBot bot = actor.MyNextBotPointer(); bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; if (originalData.IsPvEBoss) { @@ -90,14 +91,13 @@ static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) { actor.ProcessCloak(target); + if (ShouldClientBeForceChased(controller, target)) + { + SetClientForceChaseState(controller, target, false); + } if (IsValidClient(target.index)) { - SF2_BasePlayer player = SF2_BasePlayer(target.index); - if (player.ShouldBeForceChased(controller)) - { - player.SetForceChaseState(controller, false); - } - actor.SetAutoChaseCount(player, 0); + actor.SetAutoChaseCount(SF2_BasePlayer(target.index), 0); } if (!actor.IsAttacking) @@ -109,8 +109,10 @@ static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) } } + actor.RegisterProjectiles(); + SF2_BasePlayer player = SF2_BasePlayer(target.index); - if (player.IsValid && player.HasEscaped) + if (!originalData.IsPvEBoss && player.IsValid && player.HasEscaped) { actor.State = STATE_IDLE; return action.ChangeTo(SF2_ChaserIdleAction(), "Our target escaped, that is no good!"); @@ -132,8 +134,6 @@ static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; } } - - actor.RegisterProjectiles(); } else { @@ -191,6 +191,7 @@ static void OnEnd(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) { return; } + SF2NPC_Chaser controller = actor.Controller; actor.EndCloak(); PathFollower path = actor.Controller.Path; float gameTime = GetGameTime(); @@ -230,5 +231,15 @@ static void OnEnd(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) actor.SetAutoChaseCooldown(client, gameTime + cooldown); } } + + for (int i = 1; i < GetMaxEntities(); i++) + { + if (!IsValidEntity(i)) + { + continue; + } + + SetTargetMarkState(controller, CBaseEntity(i), false); + } } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp index 723520ca..3bc06189 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp @@ -208,7 +208,7 @@ static void OnEnd(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) else if (data.DeathData.DisappearOnDeath) { SpawnGibs(actor); - controller.UnSpawn(); + controller.UnSpawn(true); } else if (data.DeathData.RagdollOnDeath) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/despawn.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/despawn.sp new file mode 100644 index 00000000..85ba7791 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/despawn.sp @@ -0,0 +1,44 @@ +#pragma semicolon 1 + +static NextBotActionFactory g_Factory; + +methodmap SF2_ChaserDespawnAction < NextBotAction +{ + public SF2_ChaserDespawnAction() + { + if (g_Factory == null) + { + g_Factory = new NextBotActionFactory("Chaser_Despawn"); + g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); + g_Factory.SetCallback(NextBotActionCallbackType_OnResume, OnResume); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); + } + return view_as(g_Factory.Create()); + } +} + +static int OnStart(SF2_ChaserDespawnAction action, SF2_ChaserEntity actor, NextBotAction priorAction) +{ + actor.State = STATE_IDLE; + + actor.PerformVoice(SF2BossSound_Despawn); + + float rate = 1.0, duration = 0.0, cycle = 0.0; + int sequence = actor.SelectProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Despawn], rate, duration, cycle); + if (sequence != -1) + { + return action.SuspendFor(SF2_PlaySequenceAndWait(sequence, duration, rate, cycle)); + } + + return action.Done(); +} + +static int OnResume(SF2_ChaserDespawnAction action, SF2_ChaserEntity actor, NextBotAction interruptingAction) +{ + return action.Done("I am done despawning"); +} + +static void OnEnd(SF2_ChaserSpawnAction action, SF2_ChaserEntity actor) +{ + RemoveEntity(actor.index); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp index 8158eca5..8d95907d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp @@ -171,6 +171,8 @@ static int Update(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, flo SF2NPC_Chaser controller = actor.Controller; SF2ChaserBossProfileData data; data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); SF2ChaserRageInfo rageInfo; data.Rages.GetArray(actor.RageIndex, rageInfo, sizeof(rageInfo)); PathFollower path = controller.Path; @@ -264,11 +266,31 @@ static int Update(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, flo action.PlayedSound = true; } float amount = actor.MaxHealth * rageInfo.HealAmount; + if (!data.DeathData.Enabled[difficulty]) + { + amount = actor.MaxStunHealth * rageInfo.HealAmount; + } float increase = LerpFloats(0.0, amount, GetGameFrameTime() * (1.0 / rageInfo.HealDuration)); - actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(increase + actor.GetProp(Prop_Data, "m_iHealth"))); - if (float(actor.GetProp(Prop_Data, "m_iHealth")) > actor.MaxHealth) + if (data.DeathData.Enabled[difficulty]) + { + actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(increase + actor.GetProp(Prop_Data, "m_iHealth"))); + if (float(actor.GetProp(Prop_Data, "m_iHealth")) > actor.MaxHealth) + { + actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(actor.MaxHealth)); + } + } + else + { + actor.StunHealth += increase; + if (actor.StunHealth > actor.MaxStunHealth) + { + actor.StunHealth = actor.MaxStunHealth; + } + } + if (originalData.Healthbar) { - actor.SetProp(Prop_Data, "m_iHealth", RoundToFloor(actor.MaxHealth)); + UpdateHealthBar(controller.Index); + SetHealthBarColor(true); } action.HealDuration -= GetGameFrameTime(); } @@ -276,6 +298,7 @@ static int Update(SF2_ChaserFleeToHealAction action, SF2_ChaserEntity actor, flo if (action.HealAnimationDuration < gameTime) { actor.IsSelfHealing = false; + SetHealthBarColor(false); return action.Done(); } } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp index 2ad18b44..9c09e74a 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp @@ -92,19 +92,23 @@ static int OnStart(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, NextBotA static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float interval) { - if (IsBeatBoxBeating(2)) - { - return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); - } - INextBot bot = actor.MyNextBotPointer(); - ILocomotion loco = bot.GetLocomotionInterface(); SF2NPC_Chaser controller = actor.Controller; if (!controller.IsValid()) { return action.Continue(); } + SF2ChaserBossProfileData data; data = controller.GetProfileData(); + SF2BossProfileData originalData; + originalData = view_as(controller).GetProfileData(); + + if (!originalData.IsPvEBoss && IsBeatBoxBeating(2)) + { + return action.SuspendFor(SF2_ChaserBeatBoxFreezeAction(actor.IsAttemptingToMove)); + } + INextBot bot = actor.MyNextBotPointer(); + ILocomotion loco = bot.GetLocomotionInterface(); int difficulty = controller.Difficulty; float gameTime = GetGameTime(); int interruptConditions = actor.InterruptConditions; @@ -191,6 +195,19 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int } return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Someone made noise, let's go!"); } + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0) + { + float pos[3]; + actor.GetAlertTriggerPositionEx(pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "We got a sound hint, let's go!"); + } if ((interruptConditions & COND_DEBUG) != 0) { @@ -236,7 +253,19 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int } } - if (actor.DebugShouldGoToPos) + bool isAbleToWander = data.CanWander[difficulty]; + bool canWalk = true; + if (controller.HasAttribute(SF2Attribute_BlockWalkSpeedUnderDifficulty)) + { + int value = RoundToNearest(controller.GetAttributeValue(SF2Attribute_BlockWalkSpeedUnderDifficulty)); + if (difficulty < value) + { + isAbleToWander = false; + canWalk = false; + } + } + + if (actor.DebugShouldGoToPos && canWalk) { float debugPos[3]; actor.GetForceWanderPosition(debugPos); @@ -267,7 +296,7 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int } } - if (data.CanWander[difficulty]) + if (isAbleToWander) { if (gameTime >= action.NextWanderTime && GetRandomFloat(0.0, 1.0) <= 0.25) { @@ -334,12 +363,15 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int } else { - float lookPos[3]; - action.GetLookPosition(lookPos); - loco.FaceTowards(lookPos); - if (action.NextTurnTime <= 0.0) + if (data.IdleData.TurnEnabled[difficulty]) { - action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + float lookPos[3]; + action.GetLookPosition(lookPos); + loco.FaceTowards(lookPos); + if (action.NextTurnTime <= 0.0) + { + action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + } } } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp index 192d7687..2181546a 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -94,6 +94,11 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) return action.Continue(); } + if (actor.ShouldDespawn) + { + return action.ChangeTo(SF2_ChaserDespawnAction()); + } + float gameTime = GetGameTime(); int difficulty = controller.Difficulty; @@ -111,7 +116,7 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) data = controller.GetProfileData(); SF2BossProfileData originalData; originalData = view_as(controller).GetProfileData(); - if (data.FlashlightStun[difficulty] && actor.CanBeStunned() && actor.CanTakeDamage()) + if (data.FlashlightStun[difficulty] && actor.CanBeStunned() && actor.CanTakeDamage() && actor.FlashlightTick < gameTime) { bool inFlashlight = false; float customDamage = 1.0; @@ -120,7 +125,7 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); - if (!IsTargetValidForSlender(player, attackEliminated)) + if (!IsTargetValidForSlender(actor, player, attackEliminated)) { continue; } @@ -186,6 +191,7 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) if (inFlashlight) { actor.StunHealth -= data.FlashlightDamage[difficulty] * customDamage; + actor.FlashlightTick = gameTime + 0.1; if (actor.StunHealth <= 0.0) { return action.SuspendFor(SF2_ChaserStunnedAction(), "I was stunned by a flashlight"); @@ -210,7 +216,7 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) for (int i = 0; i < playersList.Length; i++) { SF2_BasePlayer client = SF2_BasePlayer(playersList.Get(i)); - if (!attackEliminated && client.IsEliminated) + if (!IsTargetValidForSlender(actor, client, attackEliminated)) { continue; } @@ -334,12 +340,22 @@ static int OnResume(SF2_ChaserMainAction action, SF2_ChaserEntity actor, NextBot static int OnInjured(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseEntity attacker, CBaseEntity inflictor, float damage, int damageType) { - if (actor.CanBeStunned() && IsValidClient(attacker.index) && actor.CanTakeDamage(attacker, inflictor, damage) && !actor.IsRaging) + if (actor.CanBeStunned() && IsValidClient(attacker.index) && actor.CanTakeDamage(attacker, inflictor, damage)) { actor.StunHealth -= damage; if (actor.StunHealth <= 0.0) { - return action.TrySuspendFor(SF2_ChaserStunnedAction(), RESULT_IMPORTANT, "I was stunned by someone"); + if (actor.GetProp(Prop_Data, "m_takedamage") == DAMAGE_EVENTS_ONLY) // Stun health only + { + return action.TrySuspendFor(SF2_ChaserStunnedAction(attacker), RESULT_IMPORTANT, "I was stunned by someone"); + } + else + { + if (!actor.IsRaging) + { + return action.TrySuspendFor(SF2_ChaserStunnedAction(attacker), RESULT_IMPORTANT, "I was stunned by someone"); + } + } } } @@ -352,7 +368,14 @@ static int OnInjured(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBaseE { SF2ChaserRageInfo rageInfo; data.Rages.GetArray(search, rageInfo, sizeof(rageInfo)); - if (float(actor.GetProp(Prop_Data, "m_iHealth")) <= actor.MaxHealth * rageInfo.PercentageThreshold) + float maxHealth = actor.MaxHealth; + float health = float(actor.GetProp(Prop_Data, "m_iHealth")); + if (!data.DeathData.Enabled[controller.Difficulty]) + { + maxHealth = actor.MaxStunHealth; + health = actor.StunHealth; + } + if (health <= maxHealth * rageInfo.PercentageThreshold) { return action.TrySuspendFor(SF2_ChaserRageAction(), RESULT_IMPORTANT, "I need to rage!"); } @@ -405,7 +428,7 @@ static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) float destination[3]; if (!NPCFindUnstuckPosition(actor, lastPos, destination)) { - controller.UnSpawn(); + controller.UnSpawn(true); return; } action.LastStuckTime = gameTime + 0.75; @@ -432,21 +455,19 @@ static void OnContact(SF2_ChaserMainAction action, SF2_ChaserEntity actor, CBase char classname[64]; other.GetClassname(classname, sizeof(classname)); - if (strcmp(classname, "obj_sentrygun", false) == 0 || strcmp(classname, "obj_dispenser", false) == 0 || + if (strcmp(classname, "obj_dispenser", false) == 0 || strcmp(classname, "obj_teleporter", false) == 0 || strcmp(classname, "func_breakable", false) == 0) { - int health = other.GetProp(Prop_Data, "m_iHealth"); - SDKHooks_TakeDamage(other.index, actor.index, actor.index, health * 4.0); + SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); } - if (strcmp(classname, "prop_physics") == 0 || strcmp(classname, "prop_dynamic") == 0) + // Destroy mini sentires, not non-mini sentries + if (strcmp(classname, "obj_sentrygun", false) == 0 && other.GetProp(Prop_Send, "m_bMiniBuilding")) { - if (other.GetProp(Prop_Data, "m_iHealth") > 0) - { - SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); - } + SDKHooks_TakeDamage(other.index, actor.index, actor.index, other.GetProp(Prop_Data, "m_iHealth") * 4.0); } - if (strncmp(classname, "obj_", 4) == 0) + + if (strcmp(classname, "prop_physics") == 0 || strcmp(classname, "prop_dynamic") == 0) { if (other.GetProp(Prop_Data, "m_iHealth") > 0) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp index 3f01a119..558ff469 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/rage.sp @@ -79,7 +79,9 @@ static NextBotAction InitialContainedAction(SF2_ChaserRageAction action, SF2_Cha if (rageInfo.IsHealing) { + SetHealthBarColor(true); action.IsHealing = true; + actor.IsGoingToHeal = true; return SF2_ChaserFleeToHealAction(); } @@ -119,6 +121,7 @@ static void OnEnd(SF2_ChaserRageAction action, SF2_ChaserEntity actor) { actor.IsRaging = false; actor.OverrideInvincible = false; + actor.IsGoingToHeal = false; if (actor.Controller.IsValid()) { SF2ChaserBossProfileData data; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp index ace6e722..de79fdfb 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp @@ -162,6 +162,19 @@ static int Update(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotA } return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Stop! I heard someone!"); } + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0) + { + float pos[3]; + actor.GetAlertTriggerPositionEx(pos); + + actor.State = STATE_ALERT; + path.Invalidate(); + if (data.NormalSoundHook) + { + actor.NextVoiceTime = 0.0; + } + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Stop! I got a sound hint!"); + } return action.Continue(); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp index 71fd0e2e..c619ed96 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp @@ -29,12 +29,12 @@ methodmap SF2_ChaserStunnedAction < NextBotAction { public get() { - return this.GetDataEnt("m_Attacker"); + return EntRefToEntIndex(this.GetDataEnt("m_Attacker")); } public set(int value) { - this.SetDataEnt("m_Attacker", value); + this.SetDataEnt("m_Attacker", EnsureEntRef(value)); } } @@ -94,6 +94,7 @@ static int OnStart(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextB { SF2NPC_Chaser controller = actor.Controller; SF2ChaserBossProfileData data; + int difficulty = controller.Difficulty; data = controller.GetProfileData(); if (data.KeyDrop) { @@ -117,6 +118,15 @@ static int OnStart(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextB actor.RemoveAllGestures(); CBaseNPC_RemoveAllLayers(actor.index); + if (actor.State == STATE_CHASE) + { + actor.CurrentChaseDuration += data.ChaseDurationAddOnStun[difficulty]; + if (actor.CurrentChaseDuration > data.ChaseDuration[difficulty]) + { + actor.CurrentChaseDuration = data.ChaseDuration[difficulty]; + } + } + actor.State = STATE_STUN; if (controller.HasAttribute(SF2Attribute_AddStunHealthOnStun)) @@ -151,7 +161,7 @@ static int Update(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, float if (actor.Controller.GetProfileData().DisappearOnStun) { - actor.Controller.UnSpawn(); + actor.Controller.UnSpawn(true); } return action.Done("I am no longer stunned"); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 0e2b6c78..56bc8ae5 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -16,6 +16,7 @@ #include "actions/beatbox_freeze.sp" #include "actions/rage.sp" #include "actions/flee_to_heal.sp" +#include "actions/despawn.sp" #include "postures/rage_phase.sp" #include "postures/running_away.sp" @@ -78,6 +79,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss .DefineVectorField("m_AlertTriggerPosition", MAXTF2PLAYERS) .DefineEntityField("m_AlertTriggerTarget") .DefineFloatField("m_AlertSoundTriggerCooldown", MAXTF2PLAYERS) + .DefineVectorField("m_AlertTriggerPositionEx") + .DefineFloatField("m_AlertChangePositionCooldown") .DefineIntField("m_AutoChaseCount", MAXTF2PLAYERS) .DefineFloatField("m_AutoChaseAddCooldown", MAXTF2PLAYERS) .DefineFloatField("m_AutoChaseCooldown", MAXTF2PLAYERS) @@ -99,16 +102,20 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss .DefineBoolField("m_PlayCloakAnimation") .DefineIntField("m_RageIndex") .DefineBoolField("m_IsRaging") + .DefineBoolField("m_IsGoingToHeal") .DefineBoolField("m_IsRunningAway") .DefineBoolField("m_IsSelfHealing") .DefineBoolField("m_OverrideInvincible") .DefineFloatField("m_TrapCooldown") .DefineBoolField("m_WasInBacon") + .DefineFloatField("m_FlashlightTick") + .DefineBoolField("m_ShouldDespawn") .EndDataMapDesc(); g_Factory.Install(); AddNormalSoundHook(Hook_ChaserSoundHook); + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); @@ -621,6 +628,29 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss this.SetPropFloat(Prop_Data, "m_AlertSoundTriggerCooldown", value, player.index); } + public void GetAlertTriggerPositionEx(float buffer[3]) + { + this.GetPropVector(Prop_Data, "m_AlertTriggerPositionEx", buffer); + } + + public void SetAlertTriggerPositionEx(float buffer[3]) + { + this.SetPropVector(Prop_Data, "m_AlertTriggerPositionEx", buffer); + } + + property float AlertChangePositionCooldown + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_AlertChangePositionCooldown"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_AlertChangePositionCooldown", value); + } + } + public int GetAutoChaseCount(SF2_BasePlayer player) { return this.GetProp(Prop_Data, "m_AutoChaseCount", _, player.index); @@ -898,6 +928,19 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } } + property bool IsGoingToHeal + { + public get() + { + return this.GetProp(Prop_Data, "m_IsGoingToHeal") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_IsGoingToHeal", value); + } + } + property bool IsRunningAway { public get() @@ -977,6 +1020,32 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } } + property float FlashlightTick + { + public get() + { + return this.GetPropFloat(Prop_Data, "m_FlashlightTick"); + } + + public set(float value) + { + this.SetPropFloat(Prop_Data, "m_FlashlightTick", value); + } + } + + property bool ShouldDespawn + { + public get() + { + return this.GetProp(Prop_Data, "m_ShouldDespawn") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_ShouldDespawn", value); + } + } + public SF2_BasePlayer GetClosestPlayer() { SF2NPC_Chaser controller = this.Controller; @@ -1132,6 +1201,26 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss { return this.CheckNestedSoundSection(data.AttackEndSounds, attackName, soundInfo, soundList); } + case SF2BossSound_SelfHeal: + { + soundInfo = data.SelfHealSounds; + } + case SF2BossSound_RageAll: + { + soundInfo = data.RageSounds1; + } + case SF2BossSound_RageTwo: + { + soundInfo = data.RageSounds2; + } + case SF2BossSound_RageThree: + { + soundInfo = data.RageSounds3; + } + case SF2BossSound_Despawn: + { + soundInfo = data.DespawnSounds; + } } } soundList = soundInfo.Paths; @@ -1268,7 +1357,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss this.SetAlertTriggerCount(player, this.GetAlertTriggerCount(player) + amount); - if (this.GetAlertTriggerCount(player) >= threshold) + float gameTime = GetGameTime(); + if (this.GetAlertTriggerCount(player) >= threshold && this.AlertChangePositionCooldown < gameTime) { this.InterruptConditions |= COND_ALERT_TRIGGER; @@ -1277,7 +1367,25 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss this.SetAlertTriggerPosition(player, pos); this.AlertTriggerTarget = player; + this.AlertChangePositionCooldown = gameTime + 0.3; + } + } + + public void UpdateAlertTriggerCountEx(float pos[3]) + { + if (this.State != STATE_IDLE && this.State != STATE_ALERT) + { + return; + } + + if (!this.Controller.IsValid()) + { + return; } + + this.InterruptConditions |= COND_ALERT_TRIGGER_POS; + + this.SetAlertTriggerPositionEx(pos); } public void UpdateAutoChaseCount(SF2_BasePlayer player, int amount) @@ -1311,6 +1419,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss if (this.GetAutoChaseCount(player) >= data.AutoChaseCount[difficulty]) { player.SetForceChaseState(this.Controller, true); + SetTargetMarkState(this.Controller, player, true); this.SetAutoChaseCount(player, 0); } } @@ -1333,6 +1442,9 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss { return NULL_ACTION; } + + char class[64]; + target.GetClassname(class, sizeof(class)); float gameTime = GetGameTime(); char attackName[64], posture[64]; this.GetPosture(posture, sizeof(posture)); @@ -1403,7 +1515,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss if (!props) { - float health = float(target.GetProp(Prop_Send, "m_iHealth")); + // Why must tanks use a different prop data, WHY? + float health = strcmp(class, "tank_boss", false) != 0 ? float(target.GetProp(Prop_Send, "m_iHealth")) : float(target.GetProp(Prop_Data, "m_iHealth")); if (attackData.UseOnHealth != -1.0 && health < attackData.UseOnHealth) { continue; @@ -1679,7 +1792,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss for (int i = 0; i < hitList.Length; i++) { SF2_BasePlayer player = SF2_BasePlayer(hitList.Get(i)); - if (!eliminated && player.IsEliminated) + + if (!IsTargetValidForSlender(this, player, eliminated)) { continue; } @@ -1820,6 +1934,11 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss return; } + if (!this.IsLOSClearFromTarget(this.Target)) + { + return; + } + if (!data.ProjectileClips) { if (controller.Flags & SFF_FAKE) @@ -2812,7 +2931,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss CBaseNPC_Locomotion locomotion = npc.GetLocomotion(); npc.flStepSize = 18.0; - npc.flGravity = g_Gravity; + npc.flGravity = 800.0; npc.flDeathDropHeight = 99999.0; npc.flJumpHeight = 512.0; @@ -2981,6 +3100,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss CreateNative("SF2_ChaserBossEntity.IsSelfHealing.get", Native_GetIsSelfHealing); CreateNative("SF2_ChaserBossEntity.ProfileData", Native_GetProfileData); CreateNative("SF2_ChaserBossEntity.PerformVoice", Native_PerformVoice); + CreateNative("SF2_ChaserBossEntity.PerformCustomVoice", Native_PerformCustomVoice); CreateNative("SF2_ChaserBossEntity.GetDefaultPosture", Native_GetDefaultPosture); CreateNative("SF2_ChaserBossEntity.SetDefaultPosture", Native_SetDefaultPosture); CreateNative("SF2_ChaserBossEntity.GetAttackName", Native_GetAttackName); @@ -2988,6 +3108,31 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss CreateNative("SF2_ChaserBossEntity.GetNextAttackTime", Native_GetNextAttackTime); CreateNative("SF2_ChaserBossEntity.SetNextAttackTime", Native_SetNextAttackTime); CreateNative("SF2_ChaserBossEntity.DropItem", Native_DropItem); + CreateNative("SF2_ChaserBossEntity.CreateSoundHint", Native_CreateSoundHint); + } +} + +static void EntityCreated(CBaseEntity ent, const char[] classname) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + SetClientForceChaseState(SF2NPC_BaseNPC(i), ent, false); + SetTargetMarkState(SF2NPC_BaseNPC(i), ent, false); } } @@ -3010,11 +3155,20 @@ static void OnPlayerSpawn(SF2_BasePlayer client) { continue; } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); chaser.SetAlertTriggerCount(client, 0); chaser.SetAlertSoundTriggerCooldown(client, 0.0); chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); chaser.SetAutoChaseCount(client, 0); chaser.SetAutoChaseCooldown(client, -1.0); + client.SetForceChaseState(controller, false); + SetTargetMarkState(controller, client, false); + + int index = controller.ChaseOnLookTargets.FindValue(entity); + if (index != -1) + { + controller.ChaseOnLookTargets.Erase(index); + } } } @@ -3041,7 +3195,14 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo { continue; } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); + + int index = controller.ChaseOnLookTargets.FindValue(entity); + if (index != -1) + { + controller.ChaseOnLookTargets.Erase(index); + } } SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); @@ -3347,6 +3508,14 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam return Plugin_Handled; } + if (player.IsValid || g_Buildings.FindValue(EntIndexToEntRef(inflictor)) != -1) + { + if (CBaseEntity(inflictor).GetProp(Prop_Data, "m_iTeamNum") == chaser.Team && (chaser.Controller.Flags & SFF_ATTACKWAITERS) == 0) + { + return Plugin_Handled; + } + } + SF2BossProfileData data; data = view_as(chaser.Controller).GetProfileData(); SF2ChaserBossProfileData chaserData; @@ -3372,7 +3541,7 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam if (zVelocity[2] < 0.0) // A soldier has the market gardener and is currently falling down, like Minecraft with its critical hits. { damageType |= DMG_CRIT; - damage *= 2.0; + damage *= 0.667; return Plugin_Changed; } @@ -3478,13 +3647,17 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float { case 594: // Phlog { - float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); - rage += (damage / 30.00); - if (rage > 100.0) + bool isInRage = player.GetProp(Prop_Send, "m_bRageDraining") != 0; + if (!isInRage) { - rage = 100.0; + float rage = player.GetPropFloat(Prop_Send, "m_flRageMeter"); + rage += (damage / 10.0); + if (rage > 100.0) + { + rage = 100.0; + } + player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); } - player.SetPropFloat(Prop_Send, "m_flRageMeter", rage); } } } @@ -3555,16 +3728,6 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float { bool miniCrit = false; - if (player.IsValid && player.IsMiniCritBoosted()) - { - miniCrit = true; - } - - if (player.IsValid && player.IsCritBoosted() || (damageType & DMG_CRIT)) - { - miniCrit = false; - } - Event event = CreateEvent("npc_hurt"); if (event) { @@ -3589,7 +3752,9 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float if (player.IsValid) { - if ((damageType & DMG_CRIT) || miniCrit) + miniCrit = player.IsMiniCritBoosted() && !player.IsCritBoosted(); + + if ((damageType & DMG_CRIT) != 0) { float myEyePos[3]; chaser.GetAbsOrigin(myEyePos); @@ -3597,14 +3762,14 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float chaser.GetPropVector(Prop_Send, "m_vecMaxs", myEyePosEx); myEyePos[2] += myEyePosEx[2]; - if ((damageType & DMG_CRIT) && !miniCrit) + if (!miniCrit) { TE_Particle(g_Particles[CriticalHit], myEyePos, myEyePos); TE_SendToClient(player.index); EmitSoundToClient(player.index, CRIT_SOUND, _, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); } - else if (miniCrit) + else { TE_Particle(g_Particles[MiniCritHit], myEyePos, myEyePos); TE_SendToClient(player.index); @@ -3618,6 +3783,10 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float if (data.Healthbar) { UpdateHealthBar(chaser.Controller.Index); + if (chaser.IsGoingToHeal) + { + SetHealthBarColor(true); + } } } @@ -3705,8 +3874,9 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio traceMaxs[2] = 0.0; float buffer[3], myEyeAng[3]; chaser.GetAbsAngles(myEyeAng); - float traceStartPos[3], traceEndPos[3]; + float traceStartPos[3], traceEndPos[3], myPos[3], targetPos[3]; controller.GetEyePosition(traceStartPos); + chaser.GetAbsOrigin(myPos); if (originalData.EyeData.Type == 1) { @@ -3722,7 +3892,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } int oldTarget = chaser.OldTarget.index; - if (!IsTargetValidForSlender(CBaseEntity(oldTarget), attackEliminated)) + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, CBaseEntity(oldTarget), attackEliminated)) { chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); oldTarget = INVALID_ENT_REFERENCE; @@ -3732,9 +3902,35 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); oldTarget = INVALID_ENT_REFERENCE; } + + // The m_iState is extra hacky and has the potential to be eliviated by Red Tape Recorders, but who cares + if (IsValidEntity(oldTarget) && g_Buildings.FindValue(EntIndexToEntRef(oldTarget)) != -1 && (GetEntProp(oldTarget, Prop_Send, "m_bCarried") || GetEntProp(oldTarget, Prop_Send, "m_iState") == 0)) + { + int owner = GetEntPropEnt(oldTarget, Prop_Send, "m_hBuilder"); + if (IsValidEntity(owner) && IsPlayerAlive(owner)) + { + chaser.OldTarget = CBaseEntity(owner); + oldTarget = owner; + } + else + { + chaser.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); + oldTarget = INVALID_ENT_REFERENCE; + } + } + int bestNewTarget = oldTarget; float searchRange = originalData.SearchRange[difficulty]; float bestNewTargetDist = Pow(searchRange, 2.0); + if (IsValidEntity(bestNewTarget)) + { + CBaseEntity(bestNewTarget).GetAbsOrigin(targetPos); + bestNewTargetDist = GetVectorSquareMagnitude(myPos, targetPos); + if (bestNewTargetDist > Pow(searchRange, 2.0)) + { + bestNewTargetDist = Pow(searchRange, 2.0); + } + } if (chaser.SmellPlayerList != null) { @@ -3746,7 +3942,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio { SF2_BasePlayer client = SF2_BasePlayer(i); - if (!IsTargetValidForSlender(client, attackEliminated) && !originalData.IsPvEBoss) + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, client, attackEliminated)) { continue; } @@ -3755,13 +3951,13 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio continue; } - valids.Push(client.index); + valids.Push(EntIndexToEntRef(client.index)); } for (int i = 0; i < g_Buildings.Length; i++) { CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); - if (!IsTargetValidForSlender(entity, attackEliminated) && !originalData.IsPvEBoss) + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) { continue; } @@ -3770,12 +3966,31 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio continue; } - valids.Push(entity.index); + valids.Push(EntIndexToEntRef(entity.index)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) + { + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(entity)) + { + continue; + } + + valids.Push(EntIndexToEntRef(entity.index)); } for (int i = 0; i < valids.Length; i++) { - CBaseEntity entity = CBaseEntity(valids.Get(i)); + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(valids.Get(i))); + if (!entity.IsValid()) + { + continue; + } SF2_BasePlayer player = SF2_BasePlayer(entity.index); if (player.IsValid && g_PlayerDebugFlags[player.index] & DEBUG_BOSS_EYES) @@ -3801,7 +4016,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio entity.WorldSpaceCenter(traceEndPos); } - float dist = 99999999999.9; + entity.GetAbsOrigin(targetPos); bool isVisible, isTraceVisible; int traceHitEntity; @@ -3827,7 +4042,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio isVisible = NPCShouldSeeEntity(controller.Index, entity.index); } - dist = GetVectorSquareMagnitude(traceStartPos, traceEndPos); + float dist = GetVectorSquareMagnitude(myPos, targetPos); if (player.IsValid && g_PlayerFogCtrlOffset != -1 && g_FogCtrlEnableOffset != -1 && g_FogCtrlEndOffset != -1) { @@ -3896,6 +4111,48 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio && (chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) && dist <= SquareFloat(searchRange)) { player.SetForceChaseState(controller, true); + SetTargetMarkState(controller, player, true); + } + + if (data.ChaseOnLookData.Enabled[difficulty] && isTraceVisible && player.IsValid && controller.ChaseOnLookTargets.FindValue(player.index) == -1) + { + bool shouldCalculate = false; + if (data.ChaseOnLookData.RequiredFOV[difficulty] <= 0.0) + { + shouldCalculate = chaser.GetInFOV(player); + } + else + { + shouldCalculate = FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (data.ChaseOnLookData.RequiredFOV[difficulty] * 0.5); + } + if (shouldCalculate) + { + float eyeAng[3], expectedAng[3], lookPos[3]; + player.GetEyeAngles(eyeAng); + chaser.GetAbsOrigin(myPos); + lookPos = data.ChaseOnLookData.RequiredLookPosition; + VectorTransform(lookPos, myPos, myEyeAng, lookPos); + SubtractVectors(lookPos, traceEndPos, expectedAng); + GetVectorAngles(expectedAng, expectedAng); + float minimumXAng = data.ChaseOnLookData.MinimumXAngle[difficulty] * 0.5; + float maximumXAng = data.ChaseOnLookData.MaximumXAngle[difficulty] * 0.5; + float minimumYAng = data.ChaseOnLookData.MinimumYAngle[difficulty] * 0.5; + float maximumYAng = data.ChaseOnLookData.MaximumYAngle[difficulty] * 0.5; + float xAng, yAng; + xAng = AngleDiff(eyeAng[0], expectedAng[0]); + yAng = FloatAbs(AngleDiff(eyeAng[1], expectedAng[1])); + if ((xAng >= minimumXAng && xAng < maximumXAng) && (yAng >= minimumYAng && yAng < maximumYAng) && + ((data.ChaseOnLookData.AddTargets[difficulty]) || (!data.ChaseOnLookData.AddTargets[difficulty] && controller.ChaseOnLookTargets.Length == 0))) + { + controller.ChaseOnLookTargets.Push(player.index); + SF2BossProfileSoundInfo soundInfo; + soundInfo = originalData.ScareSounds; + soundInfo.EmitSound(true, player.index); + player.ChangeCondition(TFCond_MarkedForDeathSilent); + player.SetForceChaseState(controller, true); + SetTargetMarkState(controller, player, true); + } + } } if (ShouldClientBeForceChased(controller, entity)) @@ -3909,7 +4166,16 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio playerInterruptFlags[entity.index] |= COND_NEWENEMY; } - if (player.IsValid && !SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !data.ChasesEndlessly) + if (controller.HasAttribute(SF2Attribute_IgnoreNonMarkedForChase)) + { + if (!IsTargetMarked(controller, entity) && controller.ChaseOnLookTargets.FindValue(entity) == -1) + { + playerInterruptFlags[entity.index] = 0; + continue; + } + } + + if (player.IsValid && !SF_IsRaidMap() && !SF_BossesChaseEndlessly() && !SF_IsProxyMap() && !SF_IsBoxingMap() && !SF_IsSlaughterRunMap() && !data.ChasesEndlessly && !g_RenevantBossesChaseEndlessly) { priorityValue = g_PageMax > 0 ? ((float(player.PageCount) / float(g_PageMax)) / 4.0) : 0.0; } @@ -3942,8 +4208,6 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (chaser.GetIsNear(entity) || (chaser.GetIsVisible(entity) && chaser.GetInFOV(entity))) { - float targetPos[3]; - entity.GetAbsOrigin(targetPos); if (dist <= SquareFloat(searchRange)) { // Subtract distance to increase priority. @@ -3957,86 +4221,65 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } } } - - if (data.ChaseOnLookData.Enabled[difficulty] && isTraceVisible && player.IsValid) - { - bool shouldCalculate = false; - if (data.ChaseOnLookData.RequiredFOV[difficulty] <= 0.0) - { - shouldCalculate = chaser.GetInFOV(player); - } - else - { - shouldCalculate = FloatAbs(AngleDiff(myEyeAng[1], buffer[1])) <= (data.ChaseOnLookData.RequiredFOV[difficulty] * 0.5); - } - if (shouldCalculate) - { - float eyeAng[3], expectedAng[3], lookPos[3], myPos[3]; - player.GetEyeAngles(eyeAng); - chaser.GetAbsOrigin(myPos); - lookPos = data.ChaseOnLookData.RequiredLookPosition; - VectorTransform(lookPos, myPos, myEyeAng, lookPos); - SubtractVectors(lookPos, traceEndPos, expectedAng); - GetVectorAngles(expectedAng, expectedAng); - float minimumXAng = data.ChaseOnLookData.MinimumXAngle[difficulty] * 0.5; - float maximumXAng = data.ChaseOnLookData.MaximumXAngle[difficulty] * 0.5; - float minimumYAng = data.ChaseOnLookData.MinimumYAngle[difficulty] * 0.5; - float maximumYAng = data.ChaseOnLookData.MaximumYAngle[difficulty] * 0.5; - float xAng, yAng; - xAng = AngleDiff(eyeAng[0], expectedAng[0]); - yAng = FloatAbs(AngleDiff(eyeAng[1], expectedAng[1])); - if ((xAng >= minimumXAng && xAng < maximumXAng) && (yAng >= minimumYAng && yAng < maximumYAng) && - ((data.ChaseOnLookData.AddTargets[difficulty]) || (!data.ChaseOnLookData.AddTargets[difficulty] && controller.ChaseOnLookTargets.Length == 0))) - { - controller.ChaseOnLookTargets.Push(player.index); - SF2BossProfileSoundInfo soundInfo; - soundInfo = originalData.ScareSounds; - soundInfo.EmitSound(true, player.index); - player.ChangeCondition(TFCond_MarkedForDeathSilent); - } - } - } } delete valids; - if (bestNewTarget != INVALID_ENT_REFERENCE) + if (!originalData.IsPvEBoss && (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly || g_RenevantBossesChaseEndlessly)) { - interruptConditions = playerInterruptFlags[bestNewTarget]; - if (bestNewTarget != oldTarget) + if (!IsTargetValidForSlender(chaser, CBaseEntity(bestNewTarget), attackEliminated)) { - chaser.Teleporters.Clear(); - } - chaser.OldTarget = CBaseEntity(bestNewTarget); - } - - if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly) - { - if (!IsTargetValidForSlender(CBaseEntity(bestNewTarget), attackEliminated)) - { - if (chaser.State != STATE_CHASE && NPCAreAvailablePlayersAlive()) + if (chaser.State != STATE_CHASE && (NPCAreAvailablePlayersAlive() || g_Buildings.Length > 0 || g_WhitelistedEntities.Length > 0)) { ArrayList arrayRaidTargets = new ArrayList(); for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i) || - !IsPlayerAlive(i) || - g_PlayerEliminated[i] || - IsClientInGhostMode(i) || - DidClientEscape(i)) + if (!IsTargetValidForSlender(chaser, CBaseEntity(i), false)) { continue; } arrayRaidTargets.Push(i); } + + for (int i = 0; i < g_Buildings.Length; i++) + { + if (!IsTargetValidForSlender(chaser, CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))), false)) + { + continue; + } + + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + // Do not go looking for buildings that are carried or are in an inactive state + if (building.GetProp(Prop_Send, "m_bCarried") || building.GetProp(Prop_Send, "m_iState") == 0) + { + continue; + } + arrayRaidTargets.Push(building.index); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + if (!IsTargetValidForSlender(chaser, CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))), false)) + { + continue; + } + arrayRaidTargets.Push(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + } + if (arrayRaidTargets.Length > 0) { int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); - if (IsValidClient(raidTarget) && !g_PlayerEliminated[raidTarget]) + if (IsValidClient(raidTarget)) + { + if (!g_PlayerEliminated[raidTarget]) + { + bestNewTarget = raidTarget; + } + } + else { bestNewTarget = raidTarget; - SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); } } delete arrayRaidTargets; @@ -4055,9 +4298,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio for (int i = 1; i <= MaxClients; i++) { - if (!IsValidClient(i) || - !IsPlayerAlive(i) || - !IsClientInPvE(i)) + if (!IsPvETargetValid(CBaseEntity(i))) { continue; } @@ -4069,7 +4310,6 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (IsValidClient(raidTarget) && IsClientInPvE(raidTarget)) { bestNewTarget = raidTarget; - SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); } } delete arrayRaidTargets; @@ -4078,6 +4318,16 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio chaser.CurrentChaseDuration = data.ChaseDuration[difficulty] + GetGameTime(); } + if (bestNewTarget != INVALID_ENT_REFERENCE) + { + interruptConditions = playerInterruptFlags[bestNewTarget]; + if (bestNewTarget != oldTarget) + { + chaser.Teleporters.Clear(); + } + chaser.OldTarget = CBaseEntity(bestNewTarget); + } + return CBaseEntity(bestNewTarget); } @@ -4203,7 +4453,7 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) if (moveType == SF2NPCMoveType_Run) { - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S)) + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) { if (speed < 520.0) { @@ -4221,7 +4471,7 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) } } - if (IsBeatBoxBeating(2) || chaser.IsKillingSomeone) + if ((!originalData.IsPvEBoss && IsBeatBoxBeating(2)) || chaser.IsKillingSomeone) { speed = 0.0; } @@ -4421,6 +4671,9 @@ static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) SF2_BasePlayer player = SF2_BasePlayer(other); INextBot bot = loco.GetBot(); SF2_ChaserEntity chaser = SF2_ChaserEntity(bot.GetEntity()); + + char class[64]; + GetEntityClassname(other, class, sizeof(class)); if (player.IsValid) { if (player.IsInGhostMode) @@ -4439,7 +4692,12 @@ static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) } } - if (!SF_IsBoxingMap() && !player.IsProxy && !player.IsInGhostMode && player.Team != TFTeam_Blue && !player.IsInDeathCam) + if (g_Enabled && !SF_IsBoxingMap() && !player.IsProxy && !player.IsInGhostMode && player.Team != TFTeam_Blue && !player.IsInDeathCam) + { + return true; + } + + if (!g_Enabled && player.Team != chaser.Team) { return true; } @@ -4454,6 +4712,23 @@ static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) { return false; } + + if (strcmp(class, "obj_sentrygun", false) || strcmp(class, "obj_dispenser", false) || strcmp(class, "obj_teleporter", false)) + { + if (chaser.Team != CBaseEntity(other).GetProp(Prop_Data, "m_iTeamNum")) + { + return true; + } + else + { + return false; + } + } + } + + if (strcmp(class, "tank_boss", false) == 0) + { + return true; } } return loco.CallBaseFunction(other); @@ -4950,6 +5225,21 @@ static any Native_PerformVoice(Handle plugin, int numParams) return bossEntity.PerformVoice(GetNativeCell(2), attackName); } +static any Native_PerformCustomVoice(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + SF2BossProfileSoundInfo soundInfo; + GetNativeArray(2, soundInfo, sizeof(soundInfo)); + return bossEntity.PerformVoiceCooldown(soundInfo, soundInfo.Paths); +} + static any Native_GetDefaultPosture(Handle plugin, int numParams) { int entity = GetNativeCell(1); @@ -5068,5 +5358,22 @@ static any Native_DropItem(Handle plugin, int numParams) SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); bossEntity.DropItem(GetNativeCell(2)); + return 0; +} + +static any Native_CreateSoundHint(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + + float position[3]; + GetNativeArray(2, position, 3); + bossEntity.UpdateAlertTriggerCountEx(position); + return 0; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp index 592456f9..03b2cd07 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/mainlayer.sp @@ -161,7 +161,7 @@ static void UnstuckCheck(SF2_StatueBaseAction action, SF2_StatueEntity actor) float destination[3]; if (!NPCFindUnstuckPosition(actor, lastPos, destination)) { - controller.UnSpawn(); + controller.UnSpawn(true); return; } action.LastStuckTime = gameTime + 0.75; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index e78b820d..f27f9cc7 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -141,7 +141,7 @@ methodmap SF2_StatueEntity < SF2_BaseBoss npc.flMaxYawRate = 0.0; npc.flStepSize = 18.0; - npc.flGravity = g_Gravity; + npc.flGravity = 800.0; npc.flDeathDropHeight = 99999.0; npc.flJumpHeight = 512.0; npc.flMaxYawRate = originalData.TurnRate; @@ -235,7 +235,7 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) traceMaxs[2] = 0.0; int oldTarget = statue.OldTarget.index; - if (!IsTargetValidForSlender(SF2_BasePlayer(oldTarget), attackEliminated)) + if (!IsTargetValidForSlender(statue, SF2_BasePlayer(oldTarget), attackEliminated)) { statue.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); oldTarget = INVALID_ENT_REFERENCE; @@ -252,7 +252,7 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer client = SF2_BasePlayer(i); - if (!IsTargetValidForSlender(client, attackEliminated) && !originalData.IsPvEBoss) + if (!IsTargetValidForSlender(statue, client, attackEliminated) && !originalData.IsPvEBoss) { continue; } @@ -340,9 +340,9 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) statue.OldTarget = CBaseEntity(bestNewTarget); } - if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) + if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || g_RenevantBossesChaseEndlessly) { - if (!IsTargetValidForSlender(SF2_BasePlayer(bestNewTarget), attackEliminated)) + if (!IsTargetValidForSlender(statue, SF2_BasePlayer(bestNewTarget), attackEliminated)) { if (statue.State != STATE_CHASE && NPCAreAvailablePlayersAlive()) { @@ -396,7 +396,7 @@ static void ProcessSpeed(SF2_StatueEntity statue) } acceleration += controller.GetAddAcceleration(); - speed = originalData.RunSpeed[difficulty]; + speed = originalData.RunSpeed[difficulty] * 10.0; // Backwards compatibility if (controller.HasAttribute(SF2Attribute_ReducedSpeedOnLook) && controller.CanBeSeen(_, true)) { speed *= controller.GetAttributeValue(SF2Attribute_ReducedSpeedOnLook); @@ -423,7 +423,7 @@ static void ProcessSpeed(SF2_StatueEntity statue) speed = (speed + (speed * GetDifficultyModifier(difficulty)) / 15.0); acceleration = (acceleration + (acceleration * GetDifficultyModifier(difficulty)) / 15.0); - if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S)) + if (SF_SpecialRound(SPECIALROUND_RUNNINGINTHE90S) || g_Renevant90sEffect) { if (speed < 520.0) { @@ -440,7 +440,7 @@ static void ProcessSpeed(SF2_StatueEntity statue) acceleration += 10000.0; } - if (IsBeatBoxBeating(2) || statue.IsKillingSomeone || !statue.IsMoving) + if ((!originalData.IsPvEBoss && IsBeatBoxBeating(2)) || statue.IsKillingSomeone || !statue.IsMoving) { speed = 0.0; } diff --git a/addons/sourcemod/scripting/sf2/npc/glow.sp b/addons/sourcemod/scripting/sf2/npc/glow.sp new file mode 100644 index 00000000..627000a1 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/npc/glow.sp @@ -0,0 +1,191 @@ +static const int g_DefaultColor[4] = { 150, 0, 255, 255 }; + +void SetupNPCGlows() +{ + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyChange); + g_OnSpecialRoundStartPFwd.AddFunction(null, OnSpecialRoundStart); + g_OnBossSpawnPFwd.AddFunction(null, OnBossSpawn); + g_OnPlayerChangePlayStatePFwd.AddFunction(null, OnPlayerChangePlayState); + g_OnPlayerChangeGhostStatePFwd.AddFunction(null, OnPlayerChangeGhostState); + g_OnPlayerChangeProxyStatePFwd.AddFunction(null, OnPlayerChangeProxyState); + g_OnPlayerTurnOnFlashlightPFwd.AddFunction(null, OnPlayerTurnOnFlashlight); + g_OnPlayerTurnOffFlashlightPFwd.AddFunction(null, OnPlayerTurnOffFlashlight); + g_OnPlayerFlashlightBreakPFwd.AddFunction(null, OnPlayerFlashlightBreak); + g_OnRenevantTriggerWavePFwd.AddFunction(null, OnRenevantTriggerWave); + g_OnWallHaxDebugPFwd.AddFunction(null, OnWallHaxDebug); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnDifficultyChange(int oldDifficulty, int newDifficulty) +{ + +} + +static void OnSpecialRoundStart(int specialRound) +{ + if (SF_SpecialRound(SPECIALROUND_WALLHAX)) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static void OnBossSpawn(SF2NPC_BaseNPC controller) +{ + SF2BossProfileData data; + data = controller.GetProfileData(); + if (data.IsPvEBoss) + { + return; + } + + int color[4]; + color = g_DefaultColor; + if (data.CustomOutlines) + { + color = data.OutlineColor; + } + CreateGlowEntity(controller.EntIndex, color); + UpdateVisibility(controller); +} + +static void OnPlayerChangePlayState(SF2_BasePlayer client, bool state, bool queue) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangeGhostState(SF2_BasePlayer client, bool state) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerChangeProxyState(SF2_BasePlayer client, bool state) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerTurnOnFlashlight(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerTurnOffFlashlight(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnPlayerFlashlightBreak(SF2_BasePlayer client) +{ + UpdateVisibilityOfOtherGlows(client); +} + +static void OnRenevantTriggerWave(int wave, RenevantWave condition) +{ + if (condition == RenevantWave_WallHax) + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + UpdateVisibilityOfOtherGlows(client); + } + } +} + +static void OnWallHaxDebug() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + UpdateVisibilityOfOtherGlows(client); + } +} + +static bool ShouldBeVisibleToPlayer(SF2NPC_BaseNPC controller, SF2_BasePlayer other) +{ + CBaseEntity bossEntity = CBaseEntity(controller.EntIndex); + if (!bossEntity.IsValid()) + { + return false; + } + + if (!other.IsEliminated) + { + if (IsNightVisionEnabled() && g_NightVisionType == 2 && other.UsingFlashlight) + { + return true; + } + return SF_SpecialRound(SPECIALROUND_WALLHAX) || g_RenevantWallHax || g_EnableWallHaxConVar.BoolValue; + } + + return other.IsProxy || other.IsInGhostMode || other.IsSourceTV; +} + +static void UpdateVisibility(SF2NPC_BaseNPC controller) +{ + CBaseEntity bossEntity = CBaseEntity(controller.EntIndex); + if (!bossEntity.IsValid()) + { + return; + } + + int clients[MAXPLAYERS + 1]; + int numClients = 0; + bool states[MAXPLAYERS + 1]; + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer other = SF2_BasePlayer(i); + if (!other.IsValid) + { + continue; + } + + int i2 = numClients++; + clients[i2] = i; + states[i2] = ShouldBeVisibleToPlayer(controller, other); + } + + SetGlowVisibility(bossEntity.index, clients, numClients, states); +} + +static void UpdateVisibilityOfOtherGlows(SF2_BasePlayer client) +{ + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i); + if (!controller.IsValid()) + { + continue; + } + + CBaseEntity bossEntity = CBaseEntity(controller.EntIndex); + if (!bossEntity.IsValid()) + { + continue; + } + + SetGlowVisibilityForClient(bossEntity.index, client.index, ShouldBeVisibleToPlayer(controller, client)); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index 2c5f83c6..6c9a6a3c 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -33,6 +33,7 @@ static int g_NpcBoxingCurrentDifficulty[MAX_BOSSES]; static int g_NpcBoxingRagePhase[MAX_BOSSES]; static bool g_ClientShouldBeForceChased[MAX_BOSSES][2049]; +static bool g_IsTargetMarked[MAX_BOSSES][2049]; GlobalForward g_OnChaserBossStartAttackFwd; GlobalForward g_OnChaserBossEndAttackFwd; @@ -148,6 +149,16 @@ void SetClientForceChaseState(SF2NPC_BaseNPC controller, CBaseEntity client, boo g_ClientShouldBeForceChased[controller.Index][client.index] = value; } +bool IsTargetMarked(SF2NPC_BaseNPC controller, CBaseEntity entity) +{ + return g_IsTargetMarked[controller.Index][entity.index]; +} + +void SetTargetMarkState(SF2NPC_BaseNPC controller, CBaseEntity entity, bool value) +{ + g_IsTargetMarked[controller.Index][entity.index] = value; +} + ArrayList NPCChaserGetAutoChaseTargets(int npcIndex) { return g_NpcChaseOnLookTarget[npcIndex]; @@ -245,6 +256,11 @@ static void NPCChaserResetValues(int npcIndex) SF2_ChaserEntity Spawn_Chaser(SF2NPC_BaseNPC controller, const float pos[3], const float ang[3]) { + int bossIndex = controller.Index; + + NPCSetAddSpeed(bossIndex, -NPCGetAddSpeed(bossIndex)); + NPCSetAddAcceleration(bossIndex, -NPCGetAddAcceleration(bossIndex)); + NPCChaserSetAddStunHealth(bossIndex, -NPCChaserGetAddStunHealth(bossIndex)); /*char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -256,10 +272,6 @@ SF2_ChaserEntity Spawn_Chaser(SF2NPC_BaseNPC controller, const float pos[3], con g_NpcChangeToCrawl[bossIndex] = false; g_SlenderSoundPositionSetCooldown[bossIndex] = 0.0; - NPCSetAddSpeed(bossIndex, -NPCGetAddSpeed(bossIndex)); - NPCSetAddAcceleration(bossIndex, -NPCGetAddAcceleration(bossIndex)); - NPCChaserSetAddStunHealth(bossIndex, -NPCChaserGetAddStunHealth(bossIndex)); - //g_NpcInstantKillThink[bossIndex] = CreateTimer(0.0, Timer_InstantKillThink, bossIndex, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) @@ -300,23 +312,44 @@ void Despawn_Chaser(int bossIndex) // - If I lose sight or I'm unable to traverse safely, find paths around obstacles and follow memorized path. // - If I reach the end of my path and I still don't see him and I still want to pursue him, keep on going in the direction I'm going. -bool IsTargetValidForSlender(CBaseEntity target, bool includeEliminated = false) +bool IsTargetValidForSlender(SF2_BaseBoss boss, CBaseEntity target, bool includeEliminated = false) { if (!target.IsValid()) { return false; } + if (target.index <= 0) + { + return false; + } + SF2_BasePlayer player = SF2_BasePlayer(target.index); if (player.IsValid && (!player.IsAlive || player.IsInDeathCam || - (!includeEliminated && player.IsEliminated) || player.IsInGhostMode || - player.HasEscaped)) + player.HasEscaped || + player.InCondition(view_as(130)) || + player.Team == TFTeam_Spectator)) { return false; } + if (!g_Enabled) + { + if (target.GetProp(Prop_Data, "m_iTeamNum") == boss.Team) + { + return false; + } + } + else + { + if (!includeEliminated && player.IsValid && player.IsEliminated) + { + return false; + } + } + return true; } @@ -565,6 +598,14 @@ static void TriggerKey(int caller) void NPCChaser_InitializeAPI() { CreateNative("SF2_GetBossCurrentAttackIndex", Native_GetBossCurrentAttackIndex); + CreateNative("SF2_GetBossAttackIndexType", Native_GetBossAttackIndexType); + CreateNative("SF2_GetBossAttackIndexDamage", Native_GetBossAttackIndexDamage); + CreateNative("SF2_UpdateBossAnimation", Native_UpdateBossAnimation); + CreateNative("SF2_GetBossAttackIndexDamageType", Native_GetBossAttackIndexDamageType); + + CreateNative("SF2_PerformBossVoice", Native_PerformVoice); + CreateNative("SF2_CreateBossSoundHint", Native_CreateBossSoundHint); + CreateNative("SF2_GetChaserProfileFromBossIndex", Native_GetProfileData); CreateNative("SF2_GetChaserProfileFromName", Native_GetProfileDataEx); CreateNative("SF2_SetEntityForceChaseState", Native_SetForceChaseState); @@ -592,6 +633,178 @@ static any Native_GetBossCurrentAttackIndex(Handle plugin, int numParams) return chaser.AttackIndex; } +static any Native_PerformVoice(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(3), attackData); + + return chaser.PerformVoice(GetNativeCell(2), attackData.Name); +} + +static any Native_CreateBossSoundHint(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + } + + float position[3]; + GetNativeArray(3, position, 3); + + chaser.UpdateAlertTriggerCountEx(position); + + return 0; +} + +static any Native_GetBossAttackIndexType(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(2), attackData); + + return attackData.Type; +} + +static any Native_GetBossAttackIndexDamage(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(2), attackData); + + return attackData.Damage[GetNativeCell(3)]; +} + +static any Native_UpdateBossAnimation(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + } + + char value[64]; + bool spawn = GetNativeCell(4); + switch (GetNativeCell(3)) + { + case STATE_IDLE, STATE_ALERT, STATE_CHASE: + { + if (!chaser.IsInChaseInitial && !spawn) + { + chaser.UpdateMovementAnimation(); + return 0; + } + else + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial]); + } + } + + case STATE_ATTACK: + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Attack]); + } + + case STATE_STUN: + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Stun]); + } + + case STATE_DEATHCAM: + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_DeathCam]); + } + + case STATE_DEATH: + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Death]); + } + } + + if (spawn) + { + strcopy(value, sizeof(value), g_SlenderAnimationsList[SF2BossAnimation_Spawn]); + } + + chaser.ResetProfileAnimation(value, _, chaser.GetAttackName()); + + return 0; +} + +static any Native_GetBossAttackIndexDamageType(Handle plugin, int numParams) +{ + SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); + if (!controller.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid boss index %d", controller.Index); + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + if (!chaser.IsValid()) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + SF2ChaserBossProfileAttackData attackData; + data.GetAttackFromIndex(GetNativeCell(2), attackData); + + return attackData.DamageType[1]; +} + static any Native_GetProfileData(Handle plugin, int numParams) { SF2NPC_Chaser controller = SF2NPC_Chaser(GetNativeCell(1)); diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index ae0d3245..b6c5413a 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -19,7 +19,7 @@ static ArrayList g_SelectableAdminBossProfileList = null; static ArrayList g_SelectableBoxingBossProfileList = null; static ArrayList g_SelectableRenevantBossProfileList = null; static ArrayList g_SelectableRenevantBossAdminProfileList = null; -static ArrayList g_hSelectableBossProfileQueueList = null; +static ArrayList g_SelectableBossProfileQueueList = null; StringMap g_BossProfileData = null; @@ -296,9 +296,9 @@ Action Command_Pack(int client,int args) g_BossPackConfig.GetString("name", bossPackName, sizeof(bossPackName), mapBossPack); if (bossPackName[0] == '\0') { - FormatEx(bossPackName,sizeof(bossPackName),"Core Pack"); + FormatEx(bossPackName, sizeof(bossPackName), "Core Pack"); } - CPrintToChat(client,"{dodgerblue}Pack: {lightblue}%s",bossPackName); + CPrintToChat(client, "{dodgerblue}Pack: {lightblue}%s", bossPackName); return Plugin_Handled; } @@ -332,9 +332,9 @@ Action Command_NextPack(int client,int args) g_BossPackConfig.GetString("name", bossPackName, sizeof(bossPackName), nextpack); if (bossPackName[0] == '\0') { - FormatEx(bossPackName,sizeof(bossPackName),"Core Pack"); + FormatEx(bossPackName, sizeof(bossPackName), "Core Pack"); } - CPrintToChat(client,"{dodgerblue}Next pack: {lightblue}%s",bossPackName); + CPrintToChat(client, "{dodgerblue}Next pack: {lightblue}%s", bossPackName); return Plugin_Handled; } @@ -348,6 +348,8 @@ static void PreUnloadBossProfile(const char[] profile) SF2BossProfileData profileData; g_BossProfileData.GetArray(profile, profileData, sizeof(profileData)); + LogSF2Message("Unloading %s...", profile); + int bossType = GetBossProfileType(profile); switch (bossType) { @@ -437,6 +439,11 @@ void UnloadBossProfile(const char[] profile) void ClearBossProfiles() { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + if (g_BossProfileList == null) + { + return; + } + for (int i = 0; i < g_BossProfileList.Length; i++) { g_BossProfileList.GetString(i, profile, sizeof(profile)); @@ -533,9 +540,9 @@ void ReloadBossProfiles() g_SelectableRenevantBossAdminProfileList = new ArrayList(SF2_MAX_PROFILE_NAME_LENGTH); } - if (g_hSelectableBossProfileQueueList != null) + if (g_SelectableBossProfileQueueList != null) { - delete g_hSelectableBossProfileQueueList; + delete g_SelectableBossProfileQueueList; } char configPath[PLATFORM_MAX_PATH]; @@ -667,7 +674,7 @@ void ReloadBossProfiles() { g_BossPackVoteEnabled = false; } - g_hSelectableBossProfileQueueList = g_SelectableBossProfileList.Clone(); + g_SelectableBossProfileQueueList = g_SelectableBossProfileList.Clone(); g_BossProfilePackConVar.SetString(""); @@ -1299,18 +1306,18 @@ bool GetRandomRenevantBossProfile(char[] sBuffer, int iBufferLen) */ ArrayList GetSelectableBossProfileQueueList() { - if (g_hSelectableBossProfileQueueList.Length <= 0) //If every boss were selected at least once, refill the list. + if (g_SelectableBossProfileQueueList.Length <= 0) //If every boss were selected at least once, refill the list. { - delete g_hSelectableBossProfileQueueList; - g_hSelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); + delete g_SelectableBossProfileQueueList; + g_SelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); } - if (g_hSelectableBossProfileQueueList == null) + if (g_SelectableBossProfileQueueList == null) { - g_hSelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); + g_SelectableBossProfileQueueList = GetSelectableBossProfileList().Clone(); } - return g_hSelectableBossProfileQueueList; + return g_SelectableBossProfileQueueList; } void RemoveBossProfileFromQueueList(const char[] profile) diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index 79d0cb1d..adc057f2 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -34,7 +34,9 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } GetProfileDifficultyFloatValues(kv, "alert_gracetime", profileData.AlertGracetime, profileData.AlertGracetime); + GetProfileDifficultyFloatValues(kv, "search_alert_gracetime", profileData.AlertGracetime, profileData.AlertGracetime); GetProfileDifficultyFloatValues(kv, "alert_duration", profileData.AlertDuration, profileData.AlertDuration); + GetProfileDifficultyFloatValues(kv, "search_alert_duration", profileData.AlertDuration, profileData.AlertDuration); if (kv.JumpToKey("alert")) { @@ -60,17 +62,38 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } profileData.AlertOnChaseInfo.Load(kv); + if (kv.JumpToKey("duration")) + { + GetProfileDifficultyFloatValues(kv, "max", profileData.ChaseDuration, profileData.ChaseDuration); + + if (kv.JumpToKey("add")) + { + GetProfileDifficultyFloatValues(kv, "target_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); + GetProfileDifficultyFloatValues(kv, "visible_target_near", profileData.ChaseDurationAddVisibleMin, profileData.ChaseDurationAddVisibleMin); + GetProfileDifficultyFloatValues(kv, "visible_target_far", profileData.ChaseDurationAddVisibleMax, profileData.ChaseDurationAddVisibleMax); + GetProfileDifficultyFloatValues(kv, "attack", profileData.ChaseDurationAddOnAttack, profileData.ChaseDurationAddOnAttack); + GetProfileDifficultyFloatValues(kv, "stunned", profileData.ChaseDurationAddOnStun, profileData.ChaseDurationAddOnStun); + + kv.GoBack(); + } + + kv.GoBack(); + } + kv.GoBack(); } - - GetProfileDifficultyFloatValues(kv, "chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); - for (int i = 0; i < Difficulty_Max; i++) + else { - profileData.ChaseDurationAddMaxRange[i] = baseData.SearchRange[i]; + GetProfileDifficultyFloatValues(kv, "chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); + GetProfileDifficultyFloatValues(kv, "search_chase_duration", profileData.ChaseDuration, profileData.ChaseDuration); + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.ChaseDurationAddMaxRange[i] = baseData.SearchRange[i]; + } + GetProfileDifficultyFloatValues(kv, "chase_duration_add_max_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); + GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_min", profileData.ChaseDurationAddVisibleMin, profileData.ChaseDurationAddVisibleMin); + GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_max", profileData.ChaseDurationAddVisibleMax, profileData.ChaseDurationAddVisibleMax); } - GetProfileDifficultyFloatValues(kv, "chase_duration_add_max_range", profileData.ChaseDurationAddMaxRange, profileData.ChaseDurationAddMaxRange); - GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_min", profileData.ChaseDurationAddVisibleMin, profileData.ChaseDurationAddVisibleMin); - GetProfileDifficultyFloatValues(kv, "chase_duration_add_visible_max", profileData.ChaseDurationAddVisibleMax, profileData.ChaseDurationAddVisibleMax); if (kv.JumpToKey("senses")) { @@ -187,6 +210,80 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.CloakData.Load(kv, g_FileCheckConVar.BoolValue); kv.GoBack(); } + else if (kv.GetNum("cloak_enable", false) != 0) + { + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.CloakData.Enabled[i] = true; + } + GetProfileDifficultyFloatValues(kv, "cloak_cooldown", profileData.CloakData.Cooldown, profileData.CloakData.Cooldown); + GetProfileDifficultyFloatValues(kv, "cloak_range", profileData.CloakData.CloakRange, profileData.CloakData.CloakRange); + GetProfileDifficultyFloatValues(kv, "cloak_decloak_range", profileData.CloakData.DecloakRange, profileData.CloakData.DecloakRange); + GetProfileDifficultyFloatValues(kv, "cloak_duration", profileData.CloakData.Duration, profileData.CloakData.Duration); + + GetProfileColorNoBacks(kv, "cloak_rendercolor", profileData.CloakData.CloakRenderColor[0], profileData.CloakData.CloakRenderColor[1], profileData.CloakData.CloakRenderColor[2], profileData.CloakData.CloakRenderColor[3], + profileData.CloakData.CloakRenderColor[0], profileData.CloakData.CloakRenderColor[1], profileData.CloakData.CloakRenderColor[2], profileData.CloakData.CloakRenderColor[3]); + + profileData.CloakData.CloakRenderMode = kv.GetNum("cloak_rendermode", profileData.CloakData.CloakRenderMode); + + profileData.CloakData.CloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + SF2BossProfileBaseEffectInfo particle1; + particle1.Init(); + particle1.Type = EffectType_Particle; + particle1.Event = EffectEvent_Constant; + kv.GetString("cloak_particle", particle1.ParticleName, sizeof(particle1.ParticleName), "drg_cow_explosioncore_charged_blue"); + particle1.LifeTime = 0.1; + particle1.Origin = {0.0, 0.0, 35.0}; + particle1.PostLoad(); + profileData.CloakData.CloakEffects.PushArray(particle1); + + char sound[PLATFORM_MAX_PATH]; + SF2BossProfileBaseEffectInfo sound1; + sound1.Init(); + sound1.Type = EffectType_Sound; + sound1.Event = EffectEvent_Constant; + kv.GetString("cloak_on_sound", sound, sizeof(sound), "weapons/medi_shield_deploy.wav"); + TryPrecacheBossProfileSoundPath(sound, g_FileCheckConVar.BoolValue); + sound1.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound1.SoundSounds.Paths.PushString(sound); + sound1.PostLoad(); + profileData.CloakData.CloakEffects.PushArray(sound1); + + profileData.CloakData.DecloakEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + profileData.CloakData.DecloakEffects.PushArray(particle1); + + SF2BossProfileBaseEffectInfo sound2; + sound2.Init(); + sound2.Type = EffectType_Sound; + sound2.Event = EffectEvent_Constant; + kv.GetString("cloak_off_sound", sound, sizeof(sound), "weapons/medi_shield_retract.wav"); + TryPrecacheBossProfileSoundPath(sound, g_FileCheckConVar.BoolValue); + sound2.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound2.SoundSounds.Paths.PushString(sound); + sound2.PostLoad(); + profileData.CloakData.DecloakEffects.PushArray(sound2); + + SF2ChaserBossProfilePostureInfo posture; + posture.Init(); + float multipliers[Difficulty_Max] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + GetProfileDifficultyFloatValues(kv, "cloak_speed_multiplier", multipliers, multipliers); + for (int i = 0; i < Difficulty_Max; i++) + { + posture.Speed[i] = baseData.RunSpeed[i] * multipliers[i]; + posture.WalkSpeed[i] = profileData.WalkSpeed[i] * multipliers[i]; + posture.Acceleration[i] = baseData.Acceleration[i] * multipliers[i]; + posture.CloakInfo.Enabled[i] = true; + } + posture.Name = "Legacy Cloak"; + + if (profileData.Postures == null) + { + profileData.Postures = new StringMap(); + } + profileData.Postures.SetArray(posture.Name, posture, sizeof(posture)); + } profileData.ProjectilesEnabled = !!kv.GetNum("projectile_enable", profileData.ProjectilesEnabled); if (profileData.ProjectilesEnabled) @@ -209,14 +306,14 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GetString("gesture_shootprojectile", profileData.ShootGestureName, sizeof(profileData.ShootGestureName), profileData.ShootGestureName); } - profileData.ProjectileClips = !!kv.GetNum("projectile_clips_enable", profileData.ProjectileClips); + profileData.ProjectileClips = kv.GetNum("projectile_clips_enable", profileData.ProjectileClips) != 0; if (profileData.ProjectileClips) { GetProfileDifficultyNumValues(kv, "projectile_ammo_loaded", profileData.ProjectileClipSize, profileData.ProjectileClipSize); GetProfileDifficultyFloatValues(kv, "projectile_reload_time", profileData.ProjectileReloadTime, profileData.ProjectileReloadTime); } - profileData.ChargeUpProjectiles = !!kv.GetNum("projectile_chargeup_enable", profileData.ChargeUpProjectiles); + profileData.ChargeUpProjectiles = kv.GetNum("projectile_chargeup_enable", profileData.ChargeUpProjectiles) != 0; if (profileData.ChargeUpProjectiles) { GetProfileDifficultyFloatValues(kv, "projectile_chargeup_duration", profileData.ProjectileChargeUp, profileData.ProjectileChargeUp); @@ -333,9 +430,7 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } } - profileData.ShootAnimations = !!kv.GetNum("use_shoot_animations", profileData.ShootAnimations); - - profileData.XenobladeCombo = !!kv.GetNum("xenoblade_chain_art_combo", profileData.XenobladeCombo); + profileData.XenobladeCombo = kv.GetNum("xenoblade_chain_art_combo", profileData.XenobladeCombo) != 0; if (profileData.XenobladeCombo) { profileData.XenobladeDuration = kv.GetFloat("xenoblade_break_duration", profileData.XenobladeDuration); @@ -344,14 +439,14 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.XenobladeDazeDuration = kv.GetFloat("xenoblade_daze_duration", profileData.XenobladeDazeDuration); } - profileData.EarthquakeFootsteps = !!kv.GetNum("earthquake_footsteps", profileData.EarthquakeFootsteps); + profileData.EarthquakeFootsteps = kv.GetNum("earthquake_footsteps", profileData.EarthquakeFootsteps) != 0; if (profileData.EarthquakeFootsteps) { profileData.EarthquakeFootstepAmplitude = kv.GetFloat("earthquake_footsteps_amplitude", profileData.EarthquakeFootstepAmplitude); profileData.EarthquakeFootstepFrequency = kv.GetFloat("earthquake_footsteps_frequency", profileData.EarthquakeFootstepFrequency); profileData.EarthquakeFootstepDuration = kv.GetFloat("earthquake_footsteps_duration", profileData.EarthquakeFootstepDuration); profileData.EarthquakeFootstepRadius = kv.GetFloat("earthquake_footsteps_radius", profileData.EarthquakeFootstepRadius); - profileData.EarthquakeFootstepAirShake = !!kv.GetNum("earthquake_footsteps_airshake", profileData.EarthquakeFootstepAirShake); + profileData.EarthquakeFootstepAirShake = kv.GetNum("earthquake_footsteps_airshake", profileData.EarthquakeFootstepAirShake) != 0; } GetProfileDifficultyBoolValues(kv, "traps_enabled", profileData.Traps, profileData.Traps); @@ -412,19 +507,27 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe GetProfileDifficultyFloatValues(kv, "auto_chase_cooldown_after_chase", profileData.AutoChaseAfterChaseCooldown, profileData.AutoChaseAfterChaseCooldown); } - profileData.ChasesEndlessly = !!KvGetNum(kv, "boss_chases_endlessly", profileData.ChasesEndlessly); + profileData.ChasesEndlessly = KvGetNum(kv, "boss_chases_endlessly", profileData.ChasesEndlessly) != 0; if (kv.JumpToKey("chase_on_look")) { profileData.ChaseOnLookData.Load(kv); kv.GoBack(); } + else + { + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.ChaseOnLookData.Enabled[i] = kv.GetNum("chase_upon_look") != 0; + profileData.ChaseOnLookData.Enabled[i] = kv.GetNum("auto_chase_upon_look", profileData.ChaseOnLookData.Enabled[i]) != 0; + } + } profileData.BoxingBoss = kv.GetNum("boxing_boss", profileData.BoxingBoss) != 0; profileData.NormalSoundHook = kv.GetNum("normal_sound_hook", profileData.NormalSoundHook) != 0; - profileData.OldAnimationAI = !!kv.GetNum("old_animation_ai", profileData.OldAnimationAI); + profileData.OldAnimationAI = kv.GetNum("old_animation_ai", profileData.OldAnimationAI) != 0; if (kv.JumpToKey("rages")) { @@ -444,6 +547,222 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe } kv.GoBack(); } + else + { + if (profileData.BoxingBoss) + { + profileData.Rages = new ArrayList(sizeof(SF2ChaserRageInfo)); + SF2ChaserRageInfo rage1, rage2, rage3; + rage1.Init(); + rage2.Init(); + rage3.Init(); + rage2.PercentageThreshold = 0.5; + rage3.PercentageThreshold = 0.25; + + rage1.Name = "1"; + rage2.Name = "2"; + rage3.Name = "3"; + + rage1.Invincible = true; + rage2.Invincible = true; + rage3.Invincible = true; + + if (kv.GetNum("self_heal_enabled", false) != 0) + { + rage1.Invincible = false; + rage2.Invincible = false; + rage3.Invincible = false; + + rage1.IsHealing = true; + rage2.IsHealing = true; + rage3.IsHealing = true; + + rage1.HealDelay = kv.GetFloat("heal_timer", 0.0); + rage2.HealDelay = rage1.HealDelay; + rage3.HealDelay = rage1.HealDelay; + + rage1.FleeRange[0] = kv.GetFloat("heal_range_min", 512.0); + rage1.FleeRange[1] = kv.GetFloat("heal_range_max", 1024.0); + rage2.FleeRange[0] = rage1.FleeRange[0]; + rage2.FleeRange[1] = rage1.FleeRange[1]; + rage3.FleeRange[0] = rage1.FleeRange[0]; + rage3.FleeRange[1] = rage1.FleeRange[1]; + + rage1.PercentageThreshold = kv.GetFloat("health_percentage_to_heal", 0.35); + rage2.PercentageThreshold = rage1.PercentageThreshold; + rage3.PercentageThreshold = rage1.PercentageThreshold; + + rage1.HealAmount = kv.GetFloat("heal_percentage_one", 0.5); + rage2.HealAmount = kv.GetFloat("heal_percentage_two", 0.5); + rage3.HealAmount = kv.GetFloat("heal_percentage_three", 0.5); + + rage1.HealCloak = kv.GetNum("cloak_to_heal", false) != 0; + rage2.HealCloak = rage1.HealCloak; + rage3.HealCloak = rage1.HealCloak; + } + + if (kv.JumpToKey("sound_rage")) + { + rage1.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + + if (kv.JumpToKey("sound_rage_2")) + { + rage2.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + else + { + if (kv.JumpToKey("sound_rage")) + { + rage2.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + } + + if (kv.JumpToKey("sound_rage_3")) + { + rage3.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + else + { + if (kv.JumpToKey("sound_rage")) + { + rage3.StartSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + } + + if (kv.JumpToKey("sound_heal_self")) + { + rage1.HealSounds.Load(kv, g_FileCheckConVar.BoolValue); + rage2.HealSounds.Load(kv, g_FileCheckConVar.BoolValue); + rage3.HealSounds.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + + rage1.Animations.Init(); + rage2.Animations.Init(); + rage3.Animations.Init(); + rage1.Animations.Load(kv); + rage2.Animations.Load(kv); + rage3.Animations.Load(kv); + + StringMapSnapshot snapshot = rage1.Animations.Animations.Snapshot(); + SF2BossProfileAnimationSectionNameData animData; + char animType[64]; + ArrayList animDataArray; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "rage", false) == 0 || strcmp(animType, "heal", false) == 0 || strcmp(animType, "fleestart", false) == 0) + { + continue; + } + + rage1.Animations.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + rage1.Animations.Animations.Remove(animType); + } + if (rage1.Animations.Animations.GetValue("rage", animDataArray) || rage1.Animations.Animations.GetValue("fleestart", animDataArray)) + { + rage1.Animations.Animations.SetValue("start", animDataArray); + rage1.Animations.Animations.Remove("rage"); + rage1.Animations.Animations.Remove("fleestart"); + } + if (rage1.Animations.Animations.GetValue("heal", animDataArray)) + { + rage1.Animations.Animations.SetValue("healing", animDataArray); + rage1.Animations.Animations.Remove("heal"); + } + delete snapshot; + + snapshot = rage2.Animations.Animations.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "rage", false) == 0 || strcmp(animType, "heal", false) == 0 || strcmp(animType, "fleestart", false) == 0) + { + continue; + } + + rage2.Animations.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + rage2.Animations.Animations.Remove(animType); + } + if (rage2.Animations.Animations.GetValue("rage", animDataArray) || rage1.Animations.Animations.GetValue("fleestart", animDataArray)) + { + rage2.Animations.Animations.SetValue("start", animDataArray); + rage2.Animations.Animations.Remove("rage"); + rage2.Animations.Animations.Remove("fleestart"); + } + if (rage2.Animations.Animations.GetValue("heal", animDataArray)) + { + rage2.Animations.Animations.SetValue("healing", animDataArray); + rage2.Animations.Animations.Remove("heal"); + } + delete snapshot; + + snapshot = rage3.Animations.Animations.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "rage", false) == 0 || strcmp(animType, "heal", false) == 0 || strcmp(animType, "fleestart", false) == 0) + { + continue; + } + + rage3.Animations.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + rage3.Animations.Animations.Remove(animType); + } + if (rage3.Animations.Animations.GetValue("rage", animDataArray) || rage1.Animations.Animations.GetValue("fleestart", animDataArray)) + { + rage3.Animations.Animations.SetValue("start", animDataArray); + rage3.Animations.Animations.Remove("rage"); + rage3.Animations.Animations.Remove("fleestart"); + } + if (rage3.Animations.Animations.GetValue("heal", animDataArray)) + { + rage3.Animations.Animations.SetValue("healing", animDataArray); + rage3.Animations.Animations.Remove("heal"); + } + delete snapshot; + + profileData.Rages.PushArray(rage1, sizeof(rage1)); + profileData.Rages.PushArray(rage2, sizeof(rage2)); + profileData.Rages.PushArray(rage3, sizeof(rage3)); + } + } profileData.Attacks = new ArrayList(sizeof(SF2ChaserBossProfileAttackData)); @@ -483,8 +802,14 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe profileData.DeathSounds.Channel = SNDCHAN_VOICE; profileData.TauntKillSounds.Channel = SNDCHAN_VOICE; profileData.SmellSounds.Channel = SNDCHAN_VOICE; + profileData.SelfHealSounds.Channel = SNDCHAN_VOICE; + profileData.RageSounds1.Channel = SNDCHAN_VOICE; + profileData.RageSounds2.Channel = SNDCHAN_VOICE; + profileData.RageSounds3.Channel = SNDCHAN_VOICE; } + ArrayList validSections = new ArrayList(ByteCountToCells(128)); + // We have to copy and paste here, the previous kv.GotoFirstSubKey() in profile_boss_functions.sp will somehow reset all values of these sound sections if (kv.GotoFirstSubKey()) { @@ -494,6 +819,13 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { kv.GetSectionName(s2, sizeof(s2)); + if (validSections.FindString(s2) != -1) + { + continue; + } + + validSections.PushString(s2); + if (!StrContains(s2, "sound_")) { profileData.SortSoundSections(kv, s2, g_FileCheckConVar.BoolValue); @@ -504,45 +836,223 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } + delete validSections; + if (attackNums > 0) { if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_Attack])) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackSounds, profileData.NormalSoundHook); + profileData.LoadNestedSoundSections(g_SlenderVoiceList[SF2BossSound_Attack], kv, g_FileCheckConVar.BoolValue, profileData.AttackSounds, profileData.NormalSoundHook, profileData); } if (kv.JumpToKey("sound_hitenemy")) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.HitSounds, false); + profileData.LoadNestedSoundSections("sound_hitenemy", kv, g_FileCheckConVar.BoolValue, profileData.HitSounds, false, profileData); } if (kv.JumpToKey("sound_missenemy")) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.MissSounds, false); + profileData.LoadNestedSoundSections("sound_missenemy", kv, g_FileCheckConVar.BoolValue, profileData.MissSounds, false, profileData); } if (kv.JumpToKey("sound_bulletshoot")) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.BulletShootSounds, false); + profileData.LoadNestedSoundSections("sound_bulletshoot", kv, g_FileCheckConVar.BoolValue, profileData.BulletShootSounds, false, profileData); } if (kv.JumpToKey("sound_attackshootprojectile")) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.ProjectileShootSounds, false); + profileData.LoadNestedSoundSections("sound_attackshootprojectile", kv, g_FileCheckConVar.BoolValue, profileData.ProjectileShootSounds, false, profileData); } if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_AttackBegin])) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackBeginSounds, profileData.NormalSoundHook); + profileData.LoadNestedSoundSections(g_SlenderVoiceList[SF2BossSound_AttackBegin], kv, g_FileCheckConVar.BoolValue, profileData.AttackBeginSounds, profileData.NormalSoundHook, profileData); } if (kv.JumpToKey(g_SlenderVoiceList[SF2BossSound_AttackEnd])) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackEndSounds, profileData.NormalSoundHook); + profileData.LoadNestedSoundSections(g_SlenderVoiceList[SF2BossSound_AttackEnd], kv, g_FileCheckConVar.BoolValue, profileData.AttackEndSounds, profileData.NormalSoundHook, profileData); } if (kv.JumpToKey("sound_attack_loop")) { - profileData.LoadNestedSoundSections(kv, g_FileCheckConVar.BoolValue, profileData.AttackLoopSounds, false); + profileData.LoadNestedSoundSections("sound_attack_loop", kv, g_FileCheckConVar.BoolValue, profileData.AttackLoopSounds, false, profileData); + } + + if (kv.GetNum("player_damage_effects", false) != 0) + { + if (kv.GetNum("player_damage_random_effects", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Random, _, attackNums); + } + + if (kv.GetNum("player_jarate_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Jarate, _, attackNums); + } + + if (kv.GetNum("player_milk_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Milk, _, attackNums); + } + + if (kv.GetNum("player_gas_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Gas, _, attackNums); + } + + if (kv.GetNum("player_mark_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Mark, _, attackNums); + } + + if (kv.GetNum("player_silent_mark_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Mark, true, attackNums); + } + + if (kv.GetNum("player_ignite_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Ignite, _, attackNums); + } + + if (kv.GetNum("player_stun_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Stun, _, attackNums); + } + + if (kv.GetNum("player_bleed_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Bleed, _, attackNums); + } + + if (kv.GetNum("player_electric_slow_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Stun, true, attackNums); + } + + if (kv.GetNum("player_smite_on_hit", false) != 0) + { + LoadLegacyEffects(kv, profileData, SF2DamageType_Smite, _, attackNums); + } + } + + if (kv.GetNum("shockwave", false) != 0) + { + char indexes[128], index[64][64], buffer[PLATFORM_MAX_PATH]; + kv.GetString("shockwave_attack_index", indexes, sizeof(indexes), "1"); + int nums = ExplodeString(indexes, " ", index, sizeof(index), sizeof(index)); + + for (int i = 0; i < attackNums; i++) + { + SF2ChaserBossProfileAttackData attackData; + profileData.GetAttackFromIndex(i, attackData); + + for (int i2 = 0; i2 < nums; i2++) + { + if (strcmp(attackData.Name, index[i2]) != 0) + { + continue; + } + + attackData.Shockwave.Enabled = true; + + GetProfileDifficultyFloatValues(kv, "shockwave_height", attackData.Shockwave.Height, attackData.Shockwave.Height); + GetProfileDifficultyFloatValues(kv, "shockwave_range", attackData.Shockwave.Radius, attackData.Shockwave.Radius); + GetProfileDifficultyFloatValues(kv, "shockwave_drain", attackData.Shockwave.BatteryDrainPercent, attackData.Shockwave.BatteryDrainPercent); + GetProfileDifficultyFloatValues(kv, "shockwave_force", attackData.Shockwave.Force, attackData.Shockwave.Force); + + if (kv.GetNum("shockwave_stun", false) != 0) + { + if (attackData.Shockwave.DamageEffects == null) + { + attackData.Shockwave.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + } + SF2ChaserBossProfileDamageEffectData damageData; + damageData.Init(); + damageData.SetType("stun"); + damageData.StunFlags = new ArrayList(ByteCountToCells(256)); + for (int i3 = 0; i3 < Difficulty_Max; i3++) + { + damageData.StunFlags.PushString("slow"); + } + + GetProfileDifficultyFloatValues(kv, "shockwave_stun_duration", damageData.Duration, damageData.Duration); + GetProfileDifficultyFloatValues(kv, "shockwave_stun_slowdown", damageData.StunSlowdown, damageData.StunSlowdown); + + attackData.Shockwave.DamageEffects.PushArray(damageData, sizeof(damageData)); + } + + if (attackData.Shockwave.Effects == null) + { + attackData.Shockwave.Effects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + } + SF2BossProfileBaseEffectInfo effect1; + effect1.Init(); + effect1.Type = EffectType_TempEntBeamRing; + effect1.Event = EffectEvent_Constant; + effect1.BeamRingWidth = kv.GetFloat("shockwave_width_1", 40.0); + effect1.BeamRingEndRadius = attackData.Shockwave.Radius[1]; + effect1.BeamRingSpeed = RoundToNearest(attackData.Shockwave.Radius[1]); + effect1.BeamRingAmplitude = kv.GetFloat("shockwave_amplitude", 5.0); + effect1.LifeTime = 0.3; + + kv.GetString("shockwave_beam_sprite", effect1.BeamRingBeamSprite, sizeof(effect1.BeamRingBeamSprite), effect1.BeamRingBeamSprite); + if (effect1.BeamRingBeamSprite[0] != '\0') + { + effect1.BeamRingBeamModel = PrecacheModel(effect1.BeamRingBeamSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", effect1.BeamRingBeamSprite); + AddFileToDownloadsTable(buffer); + } + + kv.GetString("shockwave_halo_sprite", effect1.BeamRingHaloSprite, sizeof(effect1.BeamRingHaloSprite), effect1.BeamRingHaloSprite); + if (effect1.BeamRingHaloSprite[0] != '\0') + { + effect1.BeamRingHaloModel = PrecacheModel(effect1.BeamRingHaloSprite, true); + FormatEx(buffer, sizeof(buffer), "materials/%s", effect1.BeamRingHaloSprite); + AddFileToDownloadsTable(buffer); + } + + char color[32]; + kv.GetString("shockwave_color_1", buffer, sizeof(buffer), "128 128 128"); + FormatEx(color, sizeof(color), "%s %i", buffer, kv.GetNum("shockwave_alpha_1", 255)); + strcopy(effect1.BeamRingColor, sizeof(effect1.BeamRingColor), color); + + effect1.BeamRingFrameRate = 30; + + attackData.Shockwave.Effects.PushArray(effect1, sizeof(effect1)); + + SF2BossProfileBaseEffectInfo effect2; + effect2.Init(); + effect2.Type = EffectType_TempEntBeamRing; + effect2.Event = EffectEvent_Constant; + effect2.BeamRingWidth = kv.GetFloat("shockwave_width_2", 20.0); + effect2.BeamRingEndRadius = attackData.Shockwave.Radius[1]; + effect2.BeamRingSpeed = RoundToNearest(attackData.Shockwave.Radius[1]); + effect2.BeamRingAmplitude = effect1.BeamRingAmplitude; + effect2.LifeTime = 0.2; + + strcopy(effect2.BeamRingBeamSprite, sizeof(effect2.BeamRingBeamSprite), effect1.BeamRingBeamSprite); + effect2.BeamRingBeamModel = effect1.BeamRingBeamModel; + + strcopy(effect2.BeamRingHaloSprite, sizeof(effect2.BeamRingHaloSprite), effect1.BeamRingHaloSprite); + effect2.BeamRingHaloModel = effect1.BeamRingHaloModel; + + kv.GetString("shockwave_color_2", buffer, sizeof(buffer), "255 255 255"); + FormatEx(color, sizeof(color), "%s %i", buffer, kv.GetNum("shockwave_alpha_2", 255)); + strcopy(effect2.BeamRingColor, sizeof(effect2.BeamRingColor), color); + + effect2.BeamRingFrameRate = 30; + + attackData.Shockwave.Effects.PushArray(effect2, sizeof(effect2)); + + profileData.Attacks.SetArray(i, attackData, sizeof(attackData)); + } + } } } if (kv.JumpToKey("postures")) { - profileData.Postures = new StringMap(); + if (profileData.Postures == null) + { + profileData.Postures = new StringMap(); + } + if (kv.GotoFirstSubKey()) { do @@ -565,6 +1075,57 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } + if (kv.GetNum("use_alert_walking_animation", false) != 0) + { + if (profileData.Postures == null) + { + profileData.Postures = new StringMap(); + } + SF2ChaserBossProfilePostureInfo posture; + posture.Init(); + for (int i = 0; i < Difficulty_Max; i++) + { + posture.Speed[i] = baseData.RunSpeed[i]; + posture.WalkSpeed[i] = profileData.WalkSpeed[i]; + posture.Acceleration[i] = baseData.Acceleration[i]; + posture.AlertInfo.Enabled[i] = true; + } + posture.Name = "Legacy Walk Alert"; + posture.Animations.Load(kv); + StringMapSnapshot snapshot = posture.Animations.Animations.Snapshot(); + SF2BossProfileAnimationSectionNameData animData; + char animType[64]; + ArrayList animDataArray; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, animType, sizeof(animType)); + if (strcmp(animType, "walkalert", false) == 0) + { + continue; + } + posture.Animations.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } + posture.Animations.Animations.Remove(animType); + } + + if (posture.Animations.Animations.GetValue("walkalert", animDataArray)) + { + posture.Animations.Animations.SetValue("walk", animDataArray); + posture.Animations.Animations.Remove("walkalert"); + profileData.Postures.SetArray(posture.Name, posture, sizeof(posture)); + } + delete snapshot; + } + profileData.PostLoad(); g_ChaserBossProfileData.SetArray(profile, profileData, sizeof(profileData)); @@ -572,73 +1133,630 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe return true; } -static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chaserProfileData, SF2BossProfileData baseData) +// #define THIS_IS_A_FUCKING_MESS +static void LoadLegacyEffects(KeyValues kv, SF2ChaserBossProfileData profileData, int type = SF2DamageType_Jarate, bool alt = false, int maxAttacks = 0) { - // Create the array. - ArrayList attacks = chaserProfileData.Attacks; + char key[64]; - if (!kv.JumpToKey("attacks")) + bool attach = kv.GetNum("player_attach_particle", true) != 0; + char flag[32]; + flag[0] = '\0'; + switch (kv.GetNum("player_stun_type", 0)) { - return -1; - } + case 1: + { + flag = "slow"; + } - if (!kv.GotoFirstSubKey()) - { - LogSF2Message("[SF2 PROFILES PARSER] Critical error, found \"attacks\" section with no attacks inside of it!"); - kv.GoBack(); - return -1; + case 2: + { + flag = "loser"; + } + + case 3: + { + flag = "no_fx stuck"; + } + + case 4: + { + flag = "boo"; + } } - char attackName[64]; - int index = 0; - do + switch (type) { - kv.GetSectionName(attackName, sizeof(attackName)); + case SF2DamageType_Bleed: + { + key = "player_bleed_attack_indexs"; + } - SF2ChaserBossProfileAttackData attackData; - attackData.Init(); + case SF2DamageType_Gas: + { + key = "player_gas_attack_indexs"; + } - attackData.Index = index; - attackData.Name = attackName; + case SF2DamageType_Ignite: + { + key = "player_ignite_attack_indexs"; + } - attackData.Type = kv.GetNum("type", attackData.Type); + case SF2DamageType_Jarate: + { + key = "player_jarate_attack_indexs"; + } - GetProfileDifficultyFloatValues(kv, "range", attackData.Range, attackData.Range); + case SF2DamageType_Mark: + { + key = "player_mark_attack_indexs"; + if (alt) + { + key = "player_silent_mark_attack_indexs"; + } + } - GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); + case SF2DamageType_Milk: + { + key = "player_milk_attack_indexs"; + } - attackData.DamageVsProps = kv.GetFloat("damage_vs_props", attackData.DamageVsProps); - GetProfileDifficultyFloatValues(kv, "damageforce", attackData.DamageForce, attackData.DamageForce); + case SF2DamageType_Random: + { + key = "player_random_attack_indexes"; + } + + case SF2DamageType_Smite: + { + key = "player_smite_attack_indexs"; + } + + case SF2DamageType_Stun: + { + key = "player_stun_attack_indexs"; + if (alt) + { + key = "player_electrocute_attack_indexs"; + } + } + } + + char indexes[128], index[64][64]; + kv.GetString(key, indexes, sizeof(indexes), "1"); + int nums = ExplodeString(indexes, " ", index, sizeof(index), sizeof(index)); + + for (int i = 0; i < maxAttacks; i++) + { + SF2ChaserBossProfileAttackData attackData; + profileData.GetAttackFromIndex(i, attackData); + + for (int i2 = 0; i2 < nums; i2++) + { + if (strcmp(attackData.Name, index[i2]) != 0) + { + continue; + } + + SF2ChaserBossProfileDamageEffectData damageEffect; + damageEffect.Init(); + switch (type) + { + case SF2DamageType_Bleed: + { + key = "player_bleed_attack_indexs"; + damageEffect.SetType("bleed"); + } + + case SF2DamageType_Gas: + { + key = "player_gas_attack_indexs"; + damageEffect.SetType("gas"); + } + + case SF2DamageType_Ignite: + { + key = "player_ignite_attack_indexs"; + damageEffect.SetType("ignite"); + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_attack_indexs"; + damageEffect.SetType("jarate"); + } + + case SF2DamageType_Mark: + { + key = "player_mark_attack_indexs"; + if (alt) + { + key = "player_silent_mark_attack_indexs"; + } + damageEffect.SetType("mark"); + } + + case SF2DamageType_Milk: + { + key = "player_milk_attack_indexs"; + damageEffect.SetType("milk"); + } + + case SF2DamageType_Random: + { + key = "player_random_attack_indexes"; + damageEffect.SetType("random"); + damageEffect.Types.Push(SF2DamageType_Ignite); + damageEffect.Types.Push(SF2DamageType_Gas); + damageEffect.Types.Push(SF2DamageType_Bleed); + damageEffect.Types.Push(SF2DamageType_Mark); + damageEffect.Types.Push(SF2DamageType_Jarate); + damageEffect.Types.Push(SF2DamageType_Milk); + damageEffect.Types.Push(SF2DamageType_Stun); + } + + case SF2DamageType_Smite: + { + key = "player_smite_attack_indexs"; + damageEffect.SetType("smite"); + } + + case SF2DamageType_Stun: + { + key = "player_stun_attack_indexs"; + if (alt) + { + key = "player_electrocute_attack_indexs"; + } + damageEffect.SetType("stun"); + } + } + + if (attackData.DamageEffects == null) + { + attackData.DamageEffects = new ArrayList(sizeof(SF2ChaserBossProfileDamageEffectData)); + } + + switch (damageEffect.Type) + { + case SF2DamageType_Bleed: + { + key = "player_bleed_duration"; + } + + case SF2DamageType_Gas: + { + key = "player_gas_duration"; + } + + case SF2DamageType_Ignite: + { + key = "player_ignite_duration"; + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_duration"; + } + + case SF2DamageType_Mark: + { + key = "player_mark_duration"; + if (alt) + { + key = "player_silent_mark_duration"; + } + } + + case SF2DamageType_Milk: + { + key = "player_milk_duration"; + } - GetProfileDifficultyNumValues(kv, "damagetype", attackData.DamageType, attackData.DamageType); + case SF2DamageType_Random: + { + key = "player_random_duration"; + } + + case SF2DamageType_Stun: + { + key = "player_stun_duration"; + if (alt) + { + key = "player_electric_slow_duration"; + } + } + + default: + { + key[0] = '\0'; + } + } + + if (key[0] != '\0') + { + GetProfileDifficultyFloatValues(kv, key, damageEffect.Duration, damageEffect.Duration); + } + + if (damageEffect.Type == SF2DamageType_Mark && alt) + { + for (int i3 = 0; i3 < Difficulty_Max; i3++) + { + damageEffect.MarkSilent[i3] = true; + } + } + + if (damageEffect.Type == SF2DamageType_Stun) + { + if (alt) + { + key = "player_electric_slow_slowdown"; + } + else + { + key = "player_stun_duration"; + } + + if (damageEffect.StunFlags == null) + { + damageEffect.StunFlags = new ArrayList(ByteCountToCells(256)); + } + + GetProfileDifficultyFloatValues(kv, key, damageEffect.StunSlowdown, damageEffect.StunSlowdown); + + for (int i3 = 0; i3 < Difficulty_Max; i3++) + { + damageEffect.StunFlags.PushString(flag); + } + } + else if (damageEffect.Type == SF2DamageType_Random) + { + switch (kv.GetNum("player_random_stun_type", 0)) + { + case 1: + { + flag = "slow"; + } + + case 2: + { + flag = "loser"; + } + + case 3: + { + flag = "no_fx stuck"; + } + + case 4: + { + flag = "boo"; + } + } + + if (damageEffect.StunFlags == null) + { + damageEffect.StunFlags = new ArrayList(ByteCountToCells(256)); + } + + GetProfileDifficultyFloatValues(kv, "player_random_slowdown", damageEffect.StunSlowdown, damageEffect.StunSlowdown); + + for (int i3 = 0; i3 < Difficulty_Max; i3++) + { + damageEffect.StunFlags.PushString(flag); + } + } + + if (damageEffect.Type == SF2DamageType_Smite) + { + GetProfileDifficultyFloatValues(kv, "player_smite_damage", damageEffect.SmiteDamage, damageEffect.SmiteDamage); + GetProfileDifficultyNumValues(kv, "player_smite_damage_type", damageEffect.SmiteDamageType, damageEffect.SmiteDamageType); - attackData.CanUseAgainstProps = !!kv.GetNum("props", attackData.CanUseAgainstProps); + damageEffect.SmiteColor[0] = kv.GetNum("player_smite_color_r", 255); + damageEffect.SmiteColor[1] = kv.GetNum("player_smite_color_g", 255); + damageEffect.SmiteColor[2] = kv.GetNum("player_smite_color_b", 255); + damageEffect.SmiteColor[3] = kv.GetNum("player_smite_transparency", 255); - kv.GetVector("punchvel", attackData.PunchVelocity, attackData.PunchVelocity); + damageEffect.SmiteMessage = kv.GetNum("player_smite_message", damageEffect.SmiteMessage) != 0; + + kv.GetString("hit_sound", damageEffect.SmiteHitSound, sizeof(damageEffect.SmiteHitSound), damageEffect.SmiteHitSound); + } + + bool create = false; + switch (damageEffect.Type) + { + case SF2DamageType_Gas, SF2DamageType_Jarate, SF2DamageType_Milk, SF2DamageType_Stun: + { + create = true; + } + } + + if (create) + { + switch (damageEffect.Type) + { + case SF2DamageType_Gas: + { + key = "player_gas_beam_particle"; + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_beam_particle"; + } + + case SF2DamageType_Milk: + { + key = "player_milk_beam_particle"; + } + + case SF2DamageType_Stun: + { + key = "player_stun_beam_particle"; + if (alt) + { + key = "player_electric_beam_particle"; + } + } + } + + SF2ParticleData particle; + particle.Init(); + particle.BeamParticles = kv.GetNum(key, false) != 0; + particle.AttachParticles = attach; + + char particleName[128]; + switch (damageEffect.Type) + { + case SF2DamageType_Gas: + { + key = "player_gas_particle"; + particleName = "gas_can_impact_blue"; + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_particle"; + particleName = "peejar_impact"; + } + + case SF2DamageType_Milk: + { + key = "player_milk_particle"; + particleName = "peejar_impact_milk"; + } + + case SF2DamageType_Stun: + { + key = "player_stun_particle"; + particleName = "xms_icicle_melt"; + if (alt) + { + key = "player_electric_red_particle"; + particleName = "electrocuted_gibbed_red"; + } + } + } + + kv.GetString(key, particle.ParticleName, sizeof(particle.ParticleName), particleName); + + if (particleName[0] != '\0') + { + if (damageEffect.Particles == null) + { + damageEffect.Particles = new ArrayList(sizeof(SF2ParticleData)); + } + damageEffect.Particles.PushArray(particle, sizeof(particle)); + } + + if (damageEffect.Sounds.Paths == null && !alt) + { + damageEffect.Sounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + } + + char sound[PLATFORM_MAX_PATH]; + switch (damageEffect.Type) + { + case SF2DamageType_Gas: + { + key = "player_gas_sound"; + sound = "weapons/jar_single.wav"; + } + + case SF2DamageType_Jarate: + { + key = "player_jarate_sound"; + sound = "weapons/jar_single.wav"; + } + + case SF2DamageType_Milk: + { + key = "player_milk_sound"; + sound = "weapons/jar_single.wav"; + } + + case SF2DamageType_Stun: + { + key = "player_stun_sound"; + sound = "weapons/icicle_freeze_victim_01.wav"; + } + } + + if (!alt) + { + kv.GetString(key, sound, sizeof(sound), sound); + if (sound[0] != '\0') + { + TryPrecacheBossProfileSoundPath(sound, g_FileCheckConVar.BoolValue); + damageEffect.Sounds.Paths.PushString(sound); + damageEffect.Sounds.PostLoad(); + } + } + } + + attackData.DamageEffects.PushArray(damageEffect); + + profileData.Attacks.SetArray(i, attackData, sizeof(attackData)); + } + } +} + +static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chaserProfileData, SF2BossProfileData baseData) +{ + // Create the array. + ArrayList attacks = chaserProfileData.Attacks; + + bool oldStuff = false; + if (!kv.JumpToKey("attacks")) + { + oldStuff = true; + if (kv.GetDataType("attack_damage") == KvData_None) + { + return -1; + } + } + + if (!oldStuff && !kv.GotoFirstSubKey()) + { + LogSF2Message("[SF2 PROFILES PARSER] Critical error, found \"attacks\" section with no attacks inside of it!"); + kv.GoBack(); + return -1; + } + + char attackName[64]; + int index = 0; + do + { + kv.GetSectionName(attackName, sizeof(attackName)); + + SF2ChaserBossProfileAttackData attackData; + attackData.Init(); + + attackData.Index = index; + attackData.Name = attackName; + + if (!oldStuff) + { + attackData.Type = kv.GetNum("type", attackData.Type); + GetProfileDifficultyFloatValues(kv, "range", attackData.Range, attackData.Range); + GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); + attackData.DamageVsProps = kv.GetFloat("damage_vs_props", attackData.DamageVsProps); + GetProfileDifficultyFloatValues(kv, "damageforce", attackData.DamageForce, attackData.DamageForce); + GetProfileDifficultyNumValues(kv, "damagetype", attackData.DamageType, attackData.DamageType); + + attackData.CanUseAgainstProps = kv.GetNum("props", attackData.CanUseAgainstProps) != 0; + + GetProfileDifficultyFloatValues(kv, "delay", attackData.DamageDelay, attackData.DamageDelay); + + kv.GetVector("punchvel", attackData.PunchVelocity, attackData.PunchVelocity); + + GetProfileDifficultyFloatValues(kv, "duration", attackData.Duration, attackData.Duration); + + GetProfileDifficultyFloatValues(kv, "fov", attackData.Spread, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "spread", attackData.Spread, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "begin_range", attackData.BeginRange, attackData.BeginRange); + GetProfileDifficultyFloatValues(kv, "begin_fov", attackData.BeginFOV, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "cooldown", attackData.Cooldown, attackData.Cooldown); + + GetProfileDifficultyBoolValues(kv, "disappear_upon_damaging", attackData.Disappear, attackData.Disappear); + } + attackData.Type = kv.GetNum("attack_type", attackData.Type); + + GetProfileDifficultyFloatValues(kv, "attack_range", attackData.Range, attackData.Range); + + GetProfileDifficultyFloatValues(kv, "attack_damage", attackData.Damage, attackData.Damage); + + attackData.DamageVsProps = kv.GetFloat("attack_damage_vs_props", attackData.DamageVsProps); + GetProfileDifficultyFloatValues(kv, "attack_damageforce", attackData.DamageForce, attackData.DamageForce); + + GetProfileDifficultyNumValues(kv, "attack_damagetype", attackData.DamageType, attackData.DamageType); + + attackData.CanUseAgainstProps = kv.GetNum("attack_props", attackData.CanUseAgainstProps) != 0; + + kv.GetVector("attack_punchvel", attackData.PunchVelocity, attackData.PunchVelocity); + + GetProfileDifficultyFloatValues(kv, "attack_delay", attackData.DamageDelay, attackData.DamageDelay); + + GetProfileDifficultyFloatValues(kv, "attack_duration", attackData.Duration, attackData.Duration); + bool backwardsCompatibility = true; + for (int i = 0; i < Difficulty_Max; i++) + { + if (attackData.Duration[i] > 0.0) + { + backwardsCompatibility = false; + } + } + if (backwardsCompatibility) // Backward compatibility + { + float endAfter[Difficulty_Max]; + if (!oldStuff) + { + GetProfileDifficultyFloatValues(kv, "endafter", endAfter, endAfter); + } + GetProfileDifficultyFloatValues(kv, "attack_endafter", endAfter, endAfter); + for (int i = 0; i < Difficulty_Max; i++) + { + attackData.Duration[i] = attackData.DamageDelay[i] + endAfter[i]; + } + } + + GetProfileDifficultyFloatValues(kv, "attack_fov", attackData.Spread, attackData.Spread); + GetProfileDifficultyFloatValues(kv, "attack_spread", attackData.Spread, attackData.Spread); + + GetProfileDifficultyFloatValues(kv, "attack_begin_range", attackData.BeginRange, attackData.BeginRange); + + GetProfileDifficultyFloatValues(kv, "attack_begin_fov", attackData.BeginFOV, attackData.Spread); + + GetProfileDifficultyFloatValues(kv, "attack_cooldown", attackData.Cooldown, attackData.Cooldown); + + GetProfileDifficultyBoolValues(kv, "attack_disappear_upon_damaging", attackData.Disappear, attackData.Disappear); + + if (oldStuff) + { + attacks.PushArray(attackData, sizeof(attackData)); + index++; + continue; + } GetProfileDifficultyBoolValues(kv, "lifesteal", attackData.LifeSteal, attackData.LifeSteal); + GetProfileDifficultyBoolValues(kv, "attack_lifesteal", attackData.LifeSteal, attackData.LifeSteal); GetProfileDifficultyFloatValues(kv, "lifesteal_duration", attackData.LifeStealDuration, attackData.LifeStealDuration); + GetProfileDifficultyFloatValues(kv, "attack_lifesteal_duration", attackData.LifeStealDuration, attackData.LifeStealDuration); - attackData.PullIn = !!kv.GetNum("pull_player_in", attackData.PullIn); + attackData.PullIn = kv.GetNum("pull_player_in", attackData.PullIn) != 0; - attackData.DeathCamLowHealth = !!kv.GetNum("deathcam_on_low_health", attackData.DeathCamLowHealth); + attackData.DeathCamLowHealth = kv.GetNum("deathcam_on_low_health", attackData.DeathCamLowHealth) != 0; + attackData.DeathCamLowHealth = kv.GetNum("attack_deathcam_on_low_health", attackData.DeathCamLowHealth) != 0; GetProfileDifficultyNumValues(kv, "bullet_count", attackData.BulletCount, attackData.BulletCount); + GetProfileDifficultyNumValues(kv, "attack_bullet_count", attackData.BulletCount, attackData.BulletCount); GetProfileDifficultyFloatValues(kv, "bullet_damage", attackData.BulletDamage, attackData.BulletDamage); + GetProfileDifficultyFloatValues(kv, "attack_bullet_damage", attackData.BulletDamage, attackData.BulletDamage); GetProfileDifficultyFloatValues(kv, "bullet_spread", attackData.BulletSpread, attackData.BulletSpread); + GetProfileDifficultyFloatValues(kv, "attack_bullet_spread", attackData.BulletSpread, attackData.BulletSpread); kv.GetVector("bullet_offset", attackData.BulletOffset, attackData.BulletOffset); + kv.GetVector("attack_bullet_offset", attackData.BulletOffset, attackData.BulletOffset); kv.GetString("bullet_tracer", attackData.BulletTrace, sizeof(attackData.BulletTrace), attackData.BulletTrace); + kv.GetString("attack_bullet_tracer", attackData.BulletTrace, sizeof(attackData.BulletTrace), attackData.BulletTrace); GetProfileDifficultyFloatValues(kv, "projectile_damage", attackData.ProjectileDamage, attackData.ProjectileDamage); + GetProfileDifficultyFloatValues(kv, "attack_projectile_damage", attackData.ProjectileDamage, attackData.ProjectileDamage); GetProfileDifficultyFloatValues(kv, "projectile_speed", attackData.ProjectileSpeed, attackData.ProjectileSpeed); + GetProfileDifficultyFloatValues(kv, "attack_projectile_speed", attackData.ProjectileSpeed, attackData.ProjectileSpeed); GetProfileDifficultyFloatValues(kv, "projectile_radius", attackData.ProjectileRadius, attackData.ProjectileRadius); + GetProfileDifficultyFloatValues(kv, "attack_projectile_radius", attackData.ProjectileRadius, attackData.ProjectileRadius); GetProfileDifficultyFloatValues(kv, "projectile_deviation", attackData.ProjectileDeviation, attackData.ProjectileDeviation); + GetProfileDifficultyFloatValues(kv, "attack_projectile_deviation", attackData.ProjectileDeviation, attackData.ProjectileDeviation); GetProfileDifficultyBoolValues(kv, "projectile_crits", attackData.CritProjectiles, attackData.CritProjectiles); + GetProfileDifficultyBoolValues(kv, "attack_projectile_crits", attackData.CritProjectiles, attackData.CritProjectiles); GetProfileDifficultyFloatValues(kv, "projectile_iceslow_percent", attackData.IceballSlowdownPercent, attackData.IceballSlowdownPercent); + GetProfileDifficultyFloatValues(kv, "attack_projectile_iceslow_percent", attackData.IceballSlowdownPercent, attackData.IceballSlowdownPercent); GetProfileDifficultyFloatValues(kv, "projectile_iceslow_duration", attackData.IceballSlowdownDuration, attackData.IceballSlowdownDuration); + GetProfileDifficultyFloatValues(kv, "attack_projectile_iceslow_duration", attackData.IceballSlowdownDuration, attackData.IceballSlowdownDuration); GetProfileDifficultyNumValues(kv, "projectile_count", attackData.ProjectileCount, attackData.ProjectileCount); + GetProfileDifficultyNumValues(kv, "attack_projectile_count", attackData.ProjectileCount, attackData.ProjectileCount); attackData.ProjectileType = kv.GetNum("projectiletype", attackData.ProjectileType); + attackData.ProjectileType = kv.GetNum("attack_projectiletype", attackData.ProjectileType); kv.GetVector("projectile_offset", attackData.ProjectileOffset, attackData.ProjectileOffset); + kv.GetVector("attack_projectile_offset", attackData.ProjectileOffset, attackData.ProjectileOffset); kv.GetString("fire_trail", attackData.FireballTrail, sizeof(attackData.FireballTrail), attackData.FireballTrail); kv.GetString("fire_iceball_trail", attackData.IceballTrail, sizeof(attackData.IceballTrail), attackData.IceballTrail); kv.GetString("rocket_model", attackData.RocketModel, sizeof(attackData.RocketModel), attackData.RocketModel); @@ -656,62 +1774,44 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas } GetProfileDifficultyFloatValues(kv, "explosivedance_radius", attackData.ExplosiveDanceRadius, attackData.ExplosiveDanceRadius); + GetProfileDifficultyFloatValues(kv, "attack_explosivedance_radius", attackData.ExplosiveDanceRadius, attackData.ExplosiveDanceRadius); GetProfileDifficultyFloatValues(kv, "laser_damage", attackData.LaserDamage, attackData.LaserDamage); + GetProfileDifficultyFloatValues(kv, "attack_laser_damage", attackData.LaserDamage, attackData.LaserDamage); attackData.LaserSize = kv.GetFloat("laser_size", attackData.LaserSize); + attackData.LaserSize = kv.GetFloat("attack_laser_size", attackData.LaserSize); if (attackData.LaserSize < 0.0) { attackData.LaserSize = 0.0; } attackData.LaserColor[0] = kv.GetNum("laser_color_r", attackData.LaserColor[0]); + attackData.LaserColor[0] = kv.GetNum("attack_laser_color_r", attackData.LaserColor[0]); attackData.LaserColor[1] = kv.GetNum("laser_color_g", attackData.LaserColor[1]); + attackData.LaserColor[1] = kv.GetNum("attack_laser_color_g", attackData.LaserColor[1]); attackData.LaserColor[2] = kv.GetNum("laser_color_b", attackData.LaserColor[2]); - attackData.LaserAttachment = !!kv.GetNum("laser_attachment", attackData.LaserAttachment); + attackData.LaserColor[2] = kv.GetNum("attack_laser_color_b", attackData.LaserColor[2]); + attackData.LaserAttachment = kv.GetNum("laser_attachment", attackData.LaserAttachment) != 0; + attackData.LaserAttachment = kv.GetNum("attack_laser_attachment", attackData.LaserAttachment) != 0; if (attackData.LaserAttachment) { kv.GetString("laser_attachment_name", attackData.LaserAttachmentName, sizeof(attackData.LaserAttachmentName), attackData.LaserAttachmentName); + kv.GetString("attack_laser_attachment_name", attackData.LaserAttachmentName, sizeof(attackData.LaserAttachmentName), attackData.LaserAttachmentName); } else { kv.GetVector("laser_offset", attackData.LaserOffset, attackData.LaserOffset); + kv.GetVector("attack_laser_offset", attackData.LaserOffset, attackData.LaserOffset); } GetProfileDifficultyFloatValues(kv, "laser_duration", attackData.LaserDuration, attackData.LaserDuration); + GetProfileDifficultyFloatValues(kv, "attack_laser_duration", attackData.LaserDuration, attackData.LaserDuration); attackData.LaserNoise = kv.GetFloat("laser_noise", attackData.LaserNoise); + attackData.LaserNoise = kv.GetFloat("attack_laser_noise", attackData.LaserNoise); GetProfileDifficultyFloatValues(kv, "laser_tick_delay", attackData.LaserTicks, attackData.LaserTicks); GetProfileDifficultyBoolValues(kv, "dont_interrupt_chaseinitial", attackData.DontInterruptChaseInitial, attackData.DontInterruptChaseInitial); - GetProfileDifficultyFloatValues(kv, "delay", attackData.DamageDelay, attackData.DamageDelay); - - GetProfileDifficultyFloatValues(kv, "duration", attackData.Duration, attackData.Duration); - bool backwardsCompatibility = true; - for (int i = 0; i < Difficulty_Max; i++) - { - if (attackData.Duration[i] > 0.0) - { - backwardsCompatibility = false; - } - } - if (backwardsCompatibility) // Backward compatibility - { - float endAfter[Difficulty_Max]; - GetProfileDifficultyFloatValues(kv, "endafter", endAfter, endAfter); - for (int i = 0; i < Difficulty_Max; i++) - { - attackData.Duration[i] = attackData.DamageDelay[i] + endAfter[i]; - } - } - - GetProfileDifficultyFloatValues(kv, "fov", attackData.Spread, attackData.Spread); - GetProfileDifficultyFloatValues(kv, "spread", attackData.Spread, attackData.Spread); - - GetProfileDifficultyFloatValues(kv, "begin_range", attackData.BeginRange, attackData.Range); - - GetProfileDifficultyFloatValues(kv, "begin_fov", attackData.BeginFOV, attackData.Spread); - - GetProfileDifficultyFloatValues(kv, "cooldown", attackData.Cooldown, attackData.Cooldown); - attackData.Repeat = kv.GetNum("repeat", attackData.Repeat); + attackData.Repeat = kv.GetNum("attack_repeat", attackData.Repeat); if (attackData.Repeat < 0) { attackData.Repeat = 0; @@ -721,6 +1821,7 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas attackData.Repeat = 2; } attackData.MaxRepeats = kv.GetNum("max_repeats", attackData.MaxRepeats); + attackData.MaxRepeats = kv.GetNum("attack_max_repeats", attackData.MaxRepeats); if (attackData.MaxRepeats < 0) { attackData.MaxRepeats = 0; @@ -734,26 +1835,33 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas { FormatEx(attackBuffer, sizeof(attackBuffer), "repeat_%i_delay", repeats + 1); delay = kv.GetFloat(attackBuffer, delay); + FormatEx(attackBuffer, sizeof(attackBuffer), "attack_repeat_%i_delay", repeats + 1); + delay = kv.GetFloat(attackBuffer, delay); attackData.RepeatTimers.Push(delay); } } GetProfileDifficultyBoolValues(kv, "ignore_always_looking", attackData.IgnoreAlwaysLooking, attackData.IgnoreAlwaysLooking); - GetProfileDifficultyBoolValues(kv, "disappear_upon_damaging", attackData.Disappear, attackData.Disappear); + GetProfileDifficultyBoolValues(kv, "attack_ignore_always_looking", attackData.IgnoreAlwaysLooking, attackData.IgnoreAlwaysLooking); GetProfileDifficultyBoolValues(kv, "start_through_walls", attackData.StartThroughWalls, attackData.StartThroughWalls); GetProfileDifficultyBoolValues(kv, "hit_through_walls", attackData.HitThroughWalls, attackData.HitThroughWalls); attackData.WeaponInt = kv.GetNum("weapontypeint", attackData.WeaponInt); + attackData.WeaponInt = kv.GetNum("attack_weapontypeint", attackData.WeaponInt); if (attackData.WeaponInt < 0) { attackData.WeaponInt = 0; } + kv.GetString("attack_weapontype", attackData.WeaponString, sizeof(attackData.WeaponString), attackData.WeaponString); kv.GetString("weapontype", attackData.WeaponString, sizeof(attackData.WeaponString), attackData.WeaponString); GetProfileDifficultyFloatValues(kv, "run_speed", attackData.RunSpeed, attackData.RunSpeed); + GetProfileDifficultyFloatValues(kv, "attack_run_speed", attackData.RunSpeed, attackData.RunSpeed); GetProfileDifficultyFloatValues(kv, "run_duration", attackData.RunDuration, attackData.RunDuration); + GetProfileDifficultyFloatValues(kv, "attack_run_duration", attackData.RunDuration, attackData.RunDuration); GetProfileDifficultyFloatValues(kv, "run_delay", attackData.RunDelay, attackData.RunDelay); + GetProfileDifficultyFloatValues(kv, "attack_run_delay", attackData.RunDelay, attackData.RunDelay); for (int i = 0; i < Difficulty_Max; i++) { attackData.RunAcceleration[i] = baseData.Acceleration[i]; @@ -762,11 +1870,16 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas GetProfileDifficultyBoolValues(kv, "run_ground_speed", attackData.RunGroundSpeed, attackData.RunGroundSpeed); attackData.UseOnDifficulty = kv.GetNum("use_on_difficulty", attackData.UseOnDifficulty); + attackData.UseOnDifficulty = kv.GetNum("attack_use_on_difficulty", attackData.UseOnDifficulty); attackData.BlockOnDifficulty = kv.GetNum("block_on_difficulty", attackData.BlockOnDifficulty); + attackData.BlockOnDifficulty = kv.GetNum("attack_block_on_difficulty", attackData.BlockOnDifficulty); attackData.UseOnHealth = kv.GetFloat("use_on_health", attackData.UseOnHealth); + attackData.UseOnHealth = kv.GetFloat("attack_use_on_health", attackData.UseOnHealth); attackData.BlockOnHealth = kv.GetFloat("block_on_health", attackData.BlockOnHealth); + attackData.BlockOnHealth = kv.GetFloat("attack_block_on_health", attackData.BlockOnHealth); attackData.Gestures = kv.GetNum("gestures", attackData.Gestures) != 0; + attackData.Gestures = kv.GetNum("attack_gestures", attackData.Gestures) != 0; GetProfileDifficultyBoolValues(kv, "cancel_los", attackData.CancelLos, attackData.CancelLos); GetProfileDifficultyFloatValues(kv, "cancel_distance_max", attackData.CancelDistance, attackData.CancelDistance); @@ -920,7 +2033,7 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas } attackData.Animations.Init(); - attackData.Animations.Load(kv); + attackData.Animations.Load(kv, false); kv.GoBack(); } @@ -928,10 +2041,13 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas attacks.PushArray(attackData, sizeof(attackData)); index++; } - while (kv.GotoNextKey()); + while (kv.GotoNextKey() && !oldStuff); - kv.GoBack(); - kv.GoBack(); + if (!oldStuff) + { + kv.GoBack(); + kv.GoBack(); + } return index; } diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp b/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp index 83e5b2e4..9f40351f 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_statue.sp @@ -67,6 +67,8 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe GetProfileDifficultyFloatValues(kv, "idle_lifetime", profileData.IdleLifeTime, profileData.IdleLifeTime); + ArrayList validSections = new ArrayList(ByteCountToCells(128)); + if (kv.GotoFirstSubKey()) { char s2[64]; @@ -75,6 +77,13 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { kv.GetSectionName(s2, sizeof(s2)); + if (validSections.FindString(s2) != -1) + { + continue; + } + + validSections.PushString(s2); + if (!StrContains(s2, "sound_")) { profileData.SortSoundSections(kv, s2, g_FileCheckConVar.BoolValue); @@ -85,6 +94,8 @@ bool LoadStatueBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } + delete validSections; + profileData.PostLoad(); g_StatueBossProfileData.SetArray(profile, profileData, sizeof(profileData)); diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 9a21adc1..94ef23a3 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -129,6 +129,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } profileData.TurnRate = kv.GetFloat("maxyawrate", profileData.TurnRate); + profileData.TurnRate = kv.GetFloat("turnrate", profileData.TurnRate); profileData.ScareCooldown = kv.GetFloat("scare_cooldown", profileData.ScareCooldown); if (profileData.ScareCooldown < 0.0) @@ -237,7 +238,9 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu GetProfileDifficultyFloatValues(kv, "kill_cooldown", profileData.InstantKillCooldown, profileData.InstantKillCooldown); GetProfileDifficultyFloatValues(kv, "search_view_distance", profileData.SearchRange, profileData.SearchRange); + GetProfileDifficultyFloatValues(kv, "search_range", profileData.SearchRange, profileData.SearchRange); GetProfileDifficultyFloatValues(kv, "hearing_range", profileData.SearchSoundRange, profileData.SearchSoundRange); + GetProfileDifficultyFloatValues(kv, "search_sound_range", profileData.SearchSoundRange, profileData.SearchSoundRange); GetProfileDifficultyFloatValues(kv, "taunt_alert_range", profileData.TauntAlertRange, profileData.TauntAlertRange); GetProfileDifficultyBoolValues(kv, "teleport_allowed", profileData.TeleportAllowed, profileData.TeleportAllowed); @@ -382,7 +385,9 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.OutroMusic = kv.GetNum("sound_music_outro_enabled", profileData.OutroMusic) != 0; profileData.EngineSoundLevel = kv.GetNum("constant_sound_level", profileData.EngineSoundLevel); + profileData.EngineSoundLevel = kv.GetNum("engine_sound_level", profileData.EngineSoundLevel); profileData.EngineSoundVolume = kv.GetFloat("constant_sound_volume", profileData.EngineSoundVolume); + profileData.EngineSoundVolume = kv.GetFloat("engine_sound_volume", profileData.EngineSoundVolume); kv.GetVector("eye_pos", profileData.EyePosOffset, profileData.EyePosOffset); kv.GetVector("eye_ang_offset", profileData.EyeAngOffset, profileData.EyeAngOffset); @@ -700,6 +705,8 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu UnloadBossProfile(profile); + profileData.AnimationData.Load(kv, true); + switch (profileData.Type) { case SF2BossType_Statue: @@ -717,13 +724,13 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } } - // Add the section to our config. g_Config.Rewind(); g_Config.JumpToKey(profile, true); g_Config.Import(kv); kv.GetString("constant_sound", profileData.EngineSound, sizeof(profileData.EngineSound), profileData.EngineSound); + kv.GetString("engine_sound", profileData.EngineSound, sizeof(profileData.EngineSound), profileData.EngineSound); TryPrecacheBossProfileSoundPath(profileData.EngineSound, g_FileCheckConVar.BoolValue); @@ -874,6 +881,8 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } + ArrayList validSections = new ArrayList(ByteCountToCells(128)); + if (kv.GotoFirstSubKey()) //Special thanks to Fire for modifying the code for download errors. { char s2[64], s3[64], s4[PLATFORM_MAX_PATH], s5[PLATFORM_MAX_PATH]; @@ -882,6 +891,13 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { kv.GetSectionName(s2, sizeof(s2)); + if (validSections.FindString(s2) != -1) + { + continue; + } + + validSections.PushString(s2); + if (StrContains(s2, "sound_") != -1) { bool doBack = false; @@ -1099,6 +1115,8 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GoBack(); } + delete validSections; + if (kv.JumpToKey("companions")) { profileData.CompanionsArray = new ArrayList(sizeof(SF2BossProfileCompanionsInfo)); @@ -1124,8 +1142,6 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.AttributesInfo.Load(kv); } - profileData.AnimationData.Load(kv); - if (kv.JumpToKey("effects")) { profileData.EffectsArray = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); @@ -1953,30 +1969,6 @@ bool GetBossProfileCustomOutlinesState(const char[] profile) return g_CachedProfileData.CustomOutlines; } -int GetBossProfileOutlineColorR(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[0]; -} - -int GetBossProfileOutlineColorG(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[1]; -} - -int GetBossProfileOutlineColorB(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[2]; -} - -int GetBossProfileOutlineTransparency(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.OutlineColor[3]; -} - bool GetBossProfileRainbowOutlineState(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); diff --git a/addons/sourcemod/scripting/sf2/pve.sp b/addons/sourcemod/scripting/sf2/pve.sp index b343515a..297dc2bf 100644 --- a/addons/sourcemod/scripting/sf2/pve.sp +++ b/addons/sourcemod/scripting/sf2/pve.sp @@ -314,6 +314,10 @@ static void RoundStart() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } g_PlayerEnteredPvETriggers[client.index] = new ArrayList(); PvE_ForceResetPlayerPvEData(client.index); @@ -384,6 +388,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { if (client.IsInPvE) diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index 4569b74c..42c09372 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -88,14 +88,14 @@ enum struct PvPProjectile_BallOfFire if (IsValidClient(otherEntity) && IsClientInPvP(otherEntity) && GetEntProp(otherEntity, Prop_Send, "m_iTeamNum") == GetEntProp(ownerEntity, Prop_Send, "m_iTeamNum")) { float damage = GetEntDataFloat(ent, FindSendPropInfo("CTFProjectile_BallOfFire", "m_iDeflected") + 4); - float damageBonus = TF2_IsPlayerInCondition(otherEntity, TFCond_OnFire) ? g_DragonsFuryBurningBonus.FloatValue : 1.0; + float damageBonus = TF2_IsPlayerInCondition(otherEntity, TFCond_OnFire) ? g_DragonsFuryBurningBonusConVar.FloatValue : 1.0; float damagePos[3]; GetEntPropVector(ent, Prop_Data, "m_vecOrigin", damagePos); // int damageCustom = 79; if (TF2_GetPlayerClass(otherEntity) == TFClass_Pyro) { - TF2_AddCondition(otherEntity, TFCond_BurningPyro, g_DragonsFuryBurnDuration.FloatValue * 0.5, ownerEntity); + TF2_AddCondition(otherEntity, TFCond_BurningPyro, g_DragonsFuryBurnDurationConVar.FloatValue * 0.5, ownerEntity); } TF2_IgnitePlayer(otherEntity, ownerEntity); @@ -218,6 +218,10 @@ void PvP_Precache() static void OnPutInServer(SF2_BasePlayer client) { + if (!g_Enabled) + { + return; + } g_PlayerEnteredPvPTriggers[client.index] = new ArrayList(); PvP_ForceResetPlayerPvPData(client.index); @@ -363,19 +367,6 @@ static Action Hook_PvPProjectile_OnTouch(int projectile, int client) return Plugin_Handled; } - int throwerOffset = FindDataMapInfo(projectile, "m_hThrower"); - int ownerEntity = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); - if (ownerEntity != client && throwerOffset != -1) - { - ownerEntity = GetEntDataEnt2(projectile, throwerOffset); - } - - if (ownerEntity == client) - { - RemoveEntity(projectile); - return Plugin_Handled; - } - return Plugin_Continue; } @@ -553,6 +544,11 @@ void PvP_ZapProjectile(int projectile, bool effects=true) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (!fake) { if (client.IsInPvP) diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index b0738348..33de756d 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -292,6 +292,11 @@ static void OnPlayerSpawn(SF2_BasePlayer client) static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { + if (!g_Enabled) + { + return; + } + if (fake) { return; @@ -1527,100 +1532,15 @@ void SpecialRoundStart() { g_DifficultyConVar.SetString("3"); // Override difficulty to Hardcore. } - for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) - { - if (NPCGetUniqueID(npcIndex) == -1) - { - continue; - } - SF2BossProfileData data; - data = NPCGetProfileData(npcIndex); - if (data.IsPvEBoss) - { - continue; - } - SlenderRemoveGlow(npcIndex); - if (NPCGetCustomOutlinesState(npcIndex)) - { - if (!NPCGetRainbowOutlineState(npcIndex)) - { - int color[4]; - color[0] = NPCGetOutlineColorR(npcIndex); - color[1] = NPCGetOutlineColorG(npcIndex); - color[2] = NPCGetOutlineColorB(npcIndex); - color[3] = NPCGetOutlineTransparency(npcIndex); - if (color[0] < 0) - { - color[0] = 0; - } - if (color[1] < 0) - { - color[1] = 0; - } - if (color[2] < 0) - { - color[2] = 0; - } - if (color[3] < 0) - { - color[3] = 0; - } - if (color[0] > 255) - { - color[0] = 255; - } - if (color[1] > 255) - { - color[1] = 255; - } - if (color[2] > 255) - { - color[2] = 255; - } - if (color[3] > 255) - { - color[3] = 255; - } - SlenderAddGlow(npcIndex, color); - } - else - { - SlenderAddGlow(npcIndex, {0, 0, 0, 0}); - } - } - else - { - int purple[4] = {150, 0, 255, 255}; - SlenderAddGlow(npcIndex, purple); - } - } - for (int i = 1; i <= MaxClients; i++) - { - if (!IsValidClient(i)) - { - continue; - } - ClientDisableConstantGlow(i); - if (!g_PlayerProxy[i] && !DidClientEscape(i) && !g_PlayerEliminated[i]) - { - int red[4] = {184, 56, 59, 255}; - ClientEnableConstantGlow(i, red); - } - else if ((g_PlayerProxy[i] && GetClientTeam(i) == TFTeam_Blue)) - { - int yellow[4] = {255, 208, 0, 255}; - ClientEnableConstantGlow(i, yellow); - } - } + SF_AddSpecialRound(SPECIALROUND_WALLHAX); } case SPECIALROUND_INFINITEFLASHLIGHT: { SF_RemoveSpecialRound(SPECIALROUND_LIGHTSOUT); - bool nightVision = (g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)); - if (nightVision && g_NightvisionType != 1) + if (IsNightVisionEnabled() && g_NightVisionType != 1) { - g_NightvisionType = 1; + g_NightVisionType = 1; for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i)) @@ -1767,6 +1687,10 @@ void SpecialRoundStart() { SpecialRoundCycleStart(); } + + Call_StartForward(g_OnSpecialRoundStartPFwd); + Call_PushCell(g_SpecialRoundType); + Call_Finish(); } static Action Timer_SpecialRoundVoteLoop(Handle timer) diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index e8a2e467..9b9d6ecf 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -216,19 +216,6 @@ int SetEntityTransmitState(int entity, int newFlags) return flags; } -bool IsEntityClassname(int entIndex, const char[] classname, bool caseSensitive=true) -{ - if (!IsValidEntity(entIndex)) - { - return false; - } - - char buffer[256]; - GetEntityClassname(entIndex, buffer, sizeof(buffer)); - - return strcmp(buffer, classname, caseSensitive) == 0; -} - int FindEntityByTargetname(const char[] targetName, const char[] className, bool caseSensitive=true) { int ent = -1; @@ -658,7 +645,7 @@ void TF2_DestroySpyWeapons() } } -void ClientSwitchToWeaponSlot(int client,int slot) +void ClientSwitchToWeaponSlot(int client, int slot) { int weaponEnt = GetPlayerWeaponSlot(client, slot); if (weaponEnt <= MaxClients) @@ -669,7 +656,7 @@ void ClientSwitchToWeaponSlot(int client,int slot) SDK_SwitchWeapon(client, weaponEnt); } -void ChangeClientTeamNoSuicide(int client,int team, bool respawn=true) +void ChangeClientTeamNoSuicide(int client, int team, bool respawn = true) { if (!IsValidClient(client)) { @@ -749,6 +736,15 @@ bool TF2_IsMiniCritBuffed(int client) ); } +bool TF2_IsInvisible(int client) +{ + return ((TF2_IsPlayerInCondition(client, TFCond_Cloaked) || + TF2_IsPlayerInCondition(client, TFCond_DeadRingered) || + TF2_IsPlayerInCondition(client, TFCond_Stealthed)) + && !TF2_IsPlayerInCondition(client, TFCond_StealthedUserBuffFade) + && !TF2_IsPlayerInCondition(client, TFCond_CloakFlicker)); +} + bool IsTauntWep(int weaponEnt) { int index = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); @@ -934,7 +930,7 @@ void TE_Particle(int particleIndex, } } -void CreateParticle(char[] particle, float pos[3], float ang[3]) +/*void CreateParticle(char[] particle, float pos[3], float ang[3]) { int tblidx = FindStringTable("ParticleEffectNames"); char tmp[256]; @@ -960,6 +956,80 @@ void CreateParticle(char[] particle, float pos[3], float ang[3]) TE_WriteNum("entindex", -1); TE_WriteNum("m_iAttachType", 5); TE_SendToAll(); +}*/ + +void TE_SetupEffectDispatch(int effectName, + const float origin[3] = NULL_VECTOR, + const float start[3] = NULL_VECTOR, + const float angles[3] = NULL_VECTOR, + const float normal[3] = NULL_VECTOR, + int flags = 0, + float magnitude = 0.0, + float scale = 1.0, + int attachmentIndex = 0, + int surfaceProp = 0, + int material = 0, + int damageType = 0, + int hitbox = 0, + int entindex = 0, + int color = 0, + float radius = 0.0, + bool customColors = false, + bool controlPoint1 = false, + int cp1AttachType = -1, + const float cp1Offset[3] = NULL_VECTOR) +{ + TE_Start("EffectDispatch"); + TE_WriteFloat("m_vOrigin[0]", origin[0]); + TE_WriteFloat("m_vOrigin[1]", origin[1]); + TE_WriteFloat("m_vOrigin[2]", origin[2]); + TE_WriteFloat("m_vStart[0]", start[0]); + TE_WriteFloat("m_vStart[1]", start[1]); + TE_WriteFloat("m_vStart[2]", start[2]); + TE_WriteVector("m_vAngles", angles); + TE_WriteVector("m_vNormal", normal); + TE_WriteNum("m_fFlags", flags); + TE_WriteFloat("m_flMagnitude", magnitude); + TE_WriteFloat("m_flScale", scale); + TE_WriteNum("m_nAttachmentIndex", attachmentIndex); + TE_WriteNum("m_nSurfaceProp", surfaceProp); + TE_WriteNum("m_iEffectName", effectName); + TE_WriteNum("m_nMaterial", material); + TE_WriteNum("m_nDamageType", damageType); + TE_WriteNum("m_nHitBox", hitbox); + TE_WriteNum("entindex", entindex); + TE_WriteNum("m_nColor", color); + TE_WriteFloat("m_flRadius", radius); + TE_WriteNum("m_bCustomColors", customColors); + TE_WriteNum("m_bControlPoint1", controlPoint1); + TE_WriteNum("m_ControlPoint1.m_eParticleAttachment", cp1AttachType); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[0]", cp1Offset[0]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[1]", cp1Offset[1]); + TE_WriteFloat("m_ControlPoint1.m_vecOffset[2]", cp1Offset[2]); +} + +int GetEffectDispatchStringTableIndex(const char[] effectName) +{ + static int table = INVALID_STRING_TABLE; + if (table == INVALID_STRING_TABLE) + { + table = FindStringTable("EffectDispatch"); + } + + int index = FindStringIndex2(table, effectName); + if (index == INVALID_STRING_INDEX) + { + int numStrings = GetStringTableNumStrings(table); + if (numStrings >= GetStringTableMaxStrings(table)) + { + return INVALID_STRING_INDEX; + } + + AddToStringTable(table, effectName); + index = numStrings; + } + + return index; } void UTIL_ScreenShake(float center[3], float amplitude, float frequency, float duration, float radius, int command, bool airShake) @@ -1319,7 +1389,7 @@ bool EnumerateBreakableEntities(int entIndex, ArrayList array) GetEntityClassname(entIndex, className, sizeof(className)); if (strcmp(className, "prop_physics") == 0 || strcmp(className, "prop_dynamic") == 0 || - strcmp(className, "func_breakable", false) == 0) + strcmp(className, "func_breakable", false) == 0 || strcmp(className, "tank_boss", false) == 0) { if (GetEntProp(entIndex, Prop_Data, "m_iHealth") > 0) { @@ -1371,7 +1441,7 @@ Action Timer_KillEdict(Handle timer, any entref) // ========================================================== bool IsInfiniteFlashlightEnabled() { - return (g_RoundInfiniteFlashlight || (g_PlayerInfiniteFlashlightOverrideConVar.IntValue == 1) || SF_SpecialRound(SPECIALROUND_INFINITEFLASHLIGHT) || ((g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION)) && g_NightvisionType == 1)); + return (g_RoundInfiniteFlashlight || (g_PlayerInfiniteFlashlightOverrideConVar.IntValue == 1) || SF_SpecialRound(SPECIALROUND_INFINITEFLASHLIGHT) || (IsNightVisionEnabled() && g_NightVisionType == 1)); } bool IsInfiniteBlinkEnabled() @@ -1384,6 +1454,11 @@ bool IsInfiniteSprintEnabled() return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1); } +bool IsNightVisionEnabled() +{ + return g_NightvisionEnabledConVar.BoolValue || SF_SpecialRound(SPECIALROUND_NIGHTVISION); +} + static int g_ArraySpecialRoundType[SPECIALROUND_MAXROUNDS]; bool SF_SpecialRound(int specialRound) @@ -1493,12 +1568,6 @@ float GetDifficultyModifier(int difficulty) return DIFFICULTYMODIFIER_NORMAL; } -MRESReturn Hook_GlowUpdateTransmitState(int glow, DHookReturn returnHook) -{ - returnHook.Value = SetEntityTransmitState(glow, FL_EDICT_FULLCHECK); - return MRES_Supercede; -} - void PlayNightmareSound() { for (int i = 0; i < sizeof(g_SoundNightmareMode); i++) diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index 66214368..ae91adfe 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -133,11 +133,6 @@ void Trap_SpawnTrap(float position[3], float direction[3], SF2NPC_Chaser control static Action Timer_TrapThink(Handle timer, any entref) { - if (!g_Enabled) - { - return Plugin_Stop; - } - int trapEntity = EntRefToEntIndex(entref); if (!trapEntity || trapEntity == INVALID_ENT_REFERENCE) { @@ -160,15 +155,29 @@ static Action Timer_TrapThink(Handle timer, any entref) for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); - if (!player.IsValid || - !player.IsAlive || - player.IsEliminated || + if (player.IsValid && (!player.IsAlive || + player.IsInDeathCam || player.IsInGhostMode || - player.HasEscaped) + player.HasEscaped || + player.InCondition(view_as(130)) || + player.Team == TFTeam_Spectator)) { continue; } + if (!g_Enabled) + { + if (!player.IsValid) + { + continue; + } + + if (player.GetProp(Prop_Data, "m_iTeamNum") == controller.DefaultTeam) + { + continue; + } + } + float entPos[3], otherPos[3]; player.GetAbsOrigin(otherPos); GetEntPropVector(trapEntity, Prop_Data, "m_vecAbsOrigin", entPos); From 13ef60e18a239aee78cab0c2318796fdeed4b94a Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Tue, 13 Feb 2024 16:14:32 -0700 Subject: [PATCH 30/86] Alpha 2.51 --- addons/sourcemod/scripting/sf2/client.sp | 30 ++++--------------- .../scripting/sf2/extras/commands.sp | 3 +- addons/sourcemod/scripting/sf2/npc.sp | 8 ++--- .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 14 ++++----- addons/sourcemod/scripting/sf2/pve.sp | 9 ++---- .../sourcemod/scripting/sf2/specialround.sp | 2 +- 6 files changed, 22 insertions(+), 44 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index ed100341..98ea622b 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -960,6 +960,7 @@ Action Timer_ClientPageDetector(Handle timer, int userid) } int client = GetClientOfUserId(userid); + if (!IsValidClient(client)) { return Plugin_Stop; @@ -975,52 +976,33 @@ Action Timer_ClientPageDetector(Handle timer, int userid) return Plugin_Stop; } - CNavArea area = CBaseCombatCharacter(client).GetLastKnownArea(); - if (area == NULL_AREA) - { - g_ClientSpecialRoundTimer[client] = CreateTimer(0.1, Timer_ClientPageDetector, userid, TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Stop; - } - int closestPageEntIndex = -1; - float bestCost = 9999999.9; + float distance = SquareFloat(99999.0); float clientPos[3], pagePos[3]; GetClientAbsOrigin(client, clientPos); ArrayList pageEntities = new ArrayList(); GetPageEntities(pageEntities); - CNavArea goalArea; for (int i = 0; i < pageEntities.Length; i++) { CBaseEntity pageEnt = CBaseEntity(pageEntities.Get(i)); pageEnt.GetAbsOrigin(pagePos); - goalArea = TheNavMesh.GetNearestNavArea(pagePos); - if (goalArea == NULL_AREA) - { - continue; - } - TheNavMesh.BuildPath(area, goalArea, pagePos, _, _, 5000.0); - float cost = goalArea.GetCostSoFar(); + float squareDistance = GetVectorSquareMagnitude(clientPos, pagePos); - if (closestPageEntIndex == -1 || cost < bestCost) + if (closestPageEntIndex == -1 || squareDistance < distance) { closestPageEntIndex = pageEnt.index; - bestCost = cost; + distance = squareDistance; } } delete pageEntities; - float nextBeepTime = bestCost / 1200.0; + float nextBeepTime = distance / SquareFloat(800.0); - if (nextBeepTime == 0.0) - { - g_ClientSpecialRoundTimer[client] = CreateTimer(0.1, Timer_ClientPageDetector, userid, TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Stop; - } if (nextBeepTime > 5.0) { nextBeepTime = 5.0; diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index a4568b3a..000b5392 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -2052,7 +2052,8 @@ static Action Command_ForceSpecialRound(int client, int args) { specialRound = 38; } - else if (specialRound > 0 && specialRound < 39) + + if (specialRound > 0 && specialRound < 39) { g_SpecialRoundOverrideConVar.SetInt(specialRound); } diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index d860a2e5..ff897960 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -5010,7 +5010,7 @@ static any Native_GetBossState(Handle plugin, int numParams) int entity = NPCGetEntIndex(GetNativeCell(1)); if (!IsValidEntity(entity)) { - return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + return 0; } SF2_BaseBoss boss = SF2_BaseBoss(entity); @@ -5022,7 +5022,7 @@ static any Native_SetBossState(Handle plugin, int numParams) int entity = NPCGetEntIndex(GetNativeCell(1)); if (!IsValidEntity(entity)) { - return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + return 0; } SF2_BaseBoss boss = SF2_BaseBoss(entity); @@ -5112,7 +5112,7 @@ static any Native_GetBossCurrentChaseDuration(Handle plugin, int numParams) int entity = controller.EntIndex; if (!IsValidEntity(entity)) { - return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + return 0; } SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); @@ -5130,7 +5130,7 @@ static any Native_SetBossCurrentChaseDuration(Handle plugin, int numParams) int entity = controller.EntIndex; if (!IsValidEntity(entity)) { - return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + return 0; } SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index 6c9a6a3c..ee7d69ea 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -627,7 +627,7 @@ static any Native_GetBossCurrentAttackIndex(Handle plugin, int numParams) SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); if (!chaser.IsValid()) { - return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + return 0; } return chaser.AttackIndex; @@ -644,7 +644,7 @@ static any Native_PerformVoice(Handle plugin, int numParams) SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); if (!chaser.IsValid()) { - return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + return 0; } SF2ChaserBossProfileData data; @@ -666,7 +666,7 @@ static any Native_CreateBossSoundHint(Handle plugin, int numParams) SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); if (!chaser.IsValid()) { - return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + return 0; } float position[3]; @@ -688,7 +688,7 @@ static any Native_GetBossAttackIndexType(Handle plugin, int numParams) SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); if (!chaser.IsValid()) { - return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + return 0; } SF2ChaserBossProfileData data; @@ -710,7 +710,7 @@ static any Native_GetBossAttackIndexDamage(Handle plugin, int numParams) SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); if (!chaser.IsValid()) { - return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + return 0; } SF2ChaserBossProfileData data; @@ -732,7 +732,7 @@ static any Native_UpdateBossAnimation(Handle plugin, int numParams) SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); if (!chaser.IsValid()) { - return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + return 0; } char value[64]; @@ -794,7 +794,7 @@ static any Native_GetBossAttackIndexDamageType(Handle plugin, int numParams) SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); if (!chaser.IsValid()) { - return ThrowNativeError(SP_ERROR_NATIVE, "Boss index %d does not have an entity", controller.Index); + return 0; } SF2ChaserBossProfileData data; diff --git a/addons/sourcemod/scripting/sf2/pve.sp b/addons/sourcemod/scripting/sf2/pve.sp index 297dc2bf..81189b2b 100644 --- a/addons/sourcemod/scripting/sf2/pve.sp +++ b/addons/sourcemod/scripting/sf2/pve.sp @@ -120,7 +120,7 @@ static void OnGamemodeStart() static void GameFrame() { - if (g_TimeUntilBossSpawns < GetGameTime() && !g_HasBossSpawned && GetRoundState() != SF2RoundState_Waiting) + if (g_DoesPvEExist && g_TimeUntilBossSpawns < GetGameTime() && !g_HasBossSpawned && GetRoundState() != SF2RoundState_Waiting) { SpawnPvEBoss(); } @@ -276,6 +276,7 @@ bool IsPvEBoxing() static void RoundStart() { + g_TimeUntilBossSpawns = GetRandomFloat(10.0, 20.0) + GetGameTime(); g_IsPvEActive = false; g_HasBossSpawned = false; g_ActiveBosses.Clear(); @@ -304,12 +305,6 @@ static void RoundStart() SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); } } - if (!g_DoesPvEExist) - { - return; - } - - g_TimeUntilBossSpawns = GetRandomFloat(10.0, 20.0) + GetGameTime(); } static void OnPutInServer(SF2_BasePlayer client) diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index 33de756d..14ec598e 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -1924,7 +1924,7 @@ static void SpecialCreateVote() } for (int bit = 0; bit < 30; bit++) { - if (strcmp(item[bit],"-") == 0 ||strcmp(item[bit],":") == 0) + if (strcmp(item[bit], "-") == 0 || strcmp(item[bit], ":") == 0) { break; } From a4b144a52a17d2801e822141417c8d6cfe6a3887 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Wed, 21 Feb 2024 19:49:24 -0700 Subject: [PATCH 31/86] Alpha 2.6 --- addons/sourcemod/scripting/include/sf2.inc | 27 +- .../include/sf2/profiles/profiles.inc | 27 ++ .../include/sf2/profiles/profiles_chaser.inc | 6 + addons/sourcemod/scripting/sf2.sp | 80 ++-- .../scripting/sf2/client/deathcam.sp | 6 +- .../sourcemod/scripting/sf2/client/music.sp | 8 +- .../sourcemod/scripting/sf2/client/proxy.sp | 2 + .../sourcemod/scripting/sf2/client/think.sp | 55 +-- .../scripting/sf2/extras/commands.sp | 46 ++- .../scripting/sf2/extras/game_events.sp | 2 +- .../sourcemod/scripting/sf2/extras/natives.sp | 2 +- addons/sourcemod/scripting/sf2/menus.sp | 196 ++++++++-- addons/sourcemod/scripting/sf2/npc.sp | 87 ++++- .../scripting/sf2/npc/entities/base/entity.sp | 13 + .../entities/chaser/actions/attacks/bullet.sp | 5 +- .../sf2/npc/entities/chaser/actions/chase.sp | 1 + .../entities/chaser/actions/flee_to_heal.sp | 2 +- .../sf2/npc/entities/chaser/actions/idle.sp | 9 +- .../sf2/npc/entities/chaser/actions/smell.sp | 1 + .../sf2/npc/entities/chaser/entity.sp | 354 +++++++++++------- .../sf2/npc/entities/statue/entity.sp | 6 + .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 82 +++- .../scripting/sf2/npc/npc_creeper.sp | 18 +- addons/sourcemod/scripting/sf2/profiles.sp | 100 ++++- .../sf2/profiles/profile_chaser_precache.sp | 2 + .../sf2/profiles/profiles_boss_functions.sp | 216 +++++++++-- addons/sourcemod/scripting/sf2/pve.sp | 117 +++++- addons/sourcemod/scripting/sf2/pvp.sp | 15 + .../sourcemod/scripting/sf2/specialround.sp | 14 +- addons/sourcemod/scripting/sf2/stocks.sp | 135 ++++++- addons/sourcemod/scripting/sf2/traps.sp | 14 +- 31 files changed, 1239 insertions(+), 409 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 0ec3af03..18746f15 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -7,8 +7,8 @@ #include #include -#define PLUGIN_VERSION "1.8.0 M Alpha 2.5" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.5" +#define PLUGIN_VERSION "1.8.0 M Alpha 2.6" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.6" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 @@ -682,9 +682,10 @@ forward void SF2_OnClientPreKillDeathCam(int client, int bossIndex); * Called when the player gets exits a boss's deathcam. * * @param client Client index. - * @param bossIndex Boss index. + * @param bossIndex Boss index. + * @param antiCamp Whether the client died due to the anti camping timer. */ -forward void SF2_OnClientEndDeathCam(int client, int bossIndex); +forward void SF2_OnClientEndDeathCam(int client, int bossIndex, bool antiCamp); /** * Called to determine a player's default walk speed. @@ -1655,6 +1656,16 @@ native StringMap SF2_GetStatueBossProfileData(); */ native void SF2_SpawnBossEffects(ArrayList effects, int bossIndex, float overridePos[3] = NULL_VECTOR, float overrideAng[3] = NULL_VECTOR, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE); +/** + * Returns if a boss can be seen by any player + * + * @param bossIndex Boss to use + * @param checkFOV Use player FOV to check if the boss can be seen + * @param checkBlink Use player blink to check if the boss can be seen + * @param checkEliminated Include/exclude eliminated players + */ +native bool SF2_CanBossBeSeen(int bossIndex, bool checkFOV = true, bool checkBlink = false, bool checkEliminated = true); + /** * Translates an activity name string into an Activity. * @@ -1787,6 +1798,14 @@ methodmap SF2_BaseBossEntity < CBaseCombatCharacter public native set(float value); } + /** + * Determines if the boss is currently in a public death cam. + */ + property bool IsKillingSomeone + { + public native get(); + } + /** * Gets the boss's eye position. * diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 0c1382ab..2852cf5a 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -2093,6 +2093,27 @@ enum struct SF2BossProfileEyeData } } +enum struct SF2BossProfileDescriptionData +{ + bool Hidden; + char Type[32]; + char Information[128]; + + void Init() + { + this.Hidden = false; + this.Type[0] = '\0'; + this.Information = "No description provided."; + } + + void Load(KeyValues kv) + { + this.Hidden = kv.GetNum("hidden", this.Hidden) != 0; + kv.GetString("type", this.Type, sizeof(this.Type), this.Type); + kv.GetString("description", this.Information, sizeof(this.Information), this.Information); + } +} + enum struct SF2BossProfileData { ArrayList Models; @@ -2115,6 +2136,8 @@ enum struct SF2BossProfileData int RenderFX; int RenderMode; + SF2BossProfileDescriptionData Description; + char WeaponString[PLATFORM_MAX_PATH]; int WeaponInt; @@ -2309,6 +2332,7 @@ enum struct SF2BossProfileData ArrayList PvESpawnMessagesArray; char PvESpawnMessagePrefix[PLATFORM_MAX_PATH]; bool DisplayPvEHealth; + float PvETeleportEndTimer; // Sounds SF2BossProfileSoundInfo LocalDeathCamSounds; @@ -2376,6 +2400,8 @@ enum struct SF2BossProfileData this.RenderFX = view_as(RENDERFX_NONE); this.RenderMode = view_as(RENDER_NORMAL); + this.Description.Init(); + this.WeaponString[0] = '\0'; this.WeaponInt = 0; @@ -2521,6 +2547,7 @@ enum struct SF2BossProfileData this.PvESpawnMessagesArray = null; this.PvESpawnMessagePrefix[0] = '\0'; this.DisplayPvEHealth = true; + this.PvETeleportEndTimer = 5.0; this.EngineSound[0] = '\0'; this.EngineSoundLevel = 83; diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index ecb84f80..f7ee6467 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -1080,6 +1080,8 @@ enum struct SF2ChaserBossProfileSmellData bool Enabled[Difficulty_Max]; float CooldownMin[Difficulty_Max]; float CooldownMax[Difficulty_Max]; + float CooldownAfterStateMin[Difficulty_Max]; + float CooldownAfterStateMax[Difficulty_Max]; int PlayerCount[Difficulty_Max]; float PlayerRange[Difficulty_Max]; float SmellRange[Difficulty_Max]; @@ -1092,6 +1094,8 @@ enum struct SF2ChaserBossProfileSmellData this.Enabled[difficulty] = false; this.CooldownMin[difficulty] = 6.0; this.CooldownMax[difficulty] = 12.0; + this.CooldownAfterStateMin[difficulty] = 16.0; + this.CooldownAfterStateMax[difficulty] = 24.0; this.PlayerCount[difficulty] = 1; this.PlayerRange[difficulty] = 1000.0; this.SmellRange[difficulty] = 1500.0; @@ -1104,6 +1108,8 @@ enum struct SF2ChaserBossProfileSmellData GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); GetProfileDifficultyFloatValues(kv, "cooldown_min", this.CooldownMin, this.CooldownMin); GetProfileDifficultyFloatValues(kv, "cooldown_max", this.CooldownMax, this.CooldownMax); + GetProfileDifficultyFloatValues(kv, "cooldown_after_state_min", this.CooldownAfterStateMin, this.CooldownAfterStateMin); + GetProfileDifficultyFloatValues(kv, "cooldown_after_state_max", this.CooldownAfterStateMax, this.CooldownAfterStateMax); GetProfileDifficultyNumValues(kv, "required_players", this.PlayerCount, this.PlayerCount); GetProfileDifficultyFloatValues(kv, "required_player_range", this.PlayerRange, this.PlayerRange); GetProfileDifficultyFloatValues(kv, "smelling_range", this.SmellRange, this.SmellRange); diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index ad154329..57b8e56f 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -11,6 +11,7 @@ #include #include #include +#include #pragma semicolon 1 @@ -546,6 +547,7 @@ ConVar g_HighDifficultyPercentConVar; ConVar g_FileCheckConVar; ConVar g_LoadOutsideMapsConVar; ConVar g_DefaultBossTeamConVar; +ConVar g_EngineerBuildInBLUConVar; ConVar g_RestartSessionConVar; bool g_RestartSessionEnabled; @@ -664,6 +666,7 @@ PrivateForward g_OnPlayerConditionRemovedPFwd; PrivateForward g_OnPlayerTurnOnFlashlightPFwd; PrivateForward g_OnPlayerTurnOffFlashlightPFwd; PrivateForward g_OnPlayerFlashlightBreakPFwd; +PrivateForward g_OnPlayerAverageUpdatePFwd; PrivateForward g_OnSpecialRoundStartPFwd; PrivateForward g_OnBossSpawnPFwd; PrivateForward g_OnBossRemovedPFwd; @@ -1689,9 +1692,31 @@ Action Hook_CommandBuild(int client, const char[] command, int argc) { return Plugin_Continue; } - if (!IsClientInPvP(client) && !IsClientInPvE(client)) + + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsEliminated) { - return Plugin_Handled; + if (player.HasEscaped && !player.IsInPvE && !player.IsInPvE) + { + return Plugin_Handled; + } + } + else + { + if (g_EngineerBuildInBLUConVar.BoolValue) + { + if (!player.IsEliminated || player.IsInGhostMode) + { + return Plugin_Handled; + } + } + else + { + if (!player.IsInPvP && !player.IsInPvE) + { + return Plugin_Handled; + } + } } return Plugin_Continue; @@ -2816,6 +2841,7 @@ static Action Hook_TriggerOnTouchEx(int trigger, int other) } return Plugin_Continue; } + static Action Hook_TriggerOnEndTouchEx(int trigger, int other) { if (MaxClients >= other >= 1 && IsClientInGhostMode(other)) @@ -4582,7 +4608,7 @@ void SlenderOnClientStressUpdate(int client) } if ((bossFlags & SFF_NOTELEPORT) != 0 && (bossFlags & SFF_PROXIES) != 0 && !Npc.IsCopy) { - //Go get a proxy target anyways + // Go get a proxy target anyways ArrayList proxyArray = new ArrayList(); for (int i = 1; i <= MaxClients; i++) { @@ -4620,7 +4646,7 @@ void SlenderOnClientStressUpdate(int client) { if (g_PlayerEliminated[teleportTarget] || DidClientEscape(teleportTarget) || (!SF_BossesChaseEndlessly() && !SF_IsRenevantMap() && !SF_IsSurvivalMap() && !g_SlenderTeleportIgnoreChases[Npc.Index] && stress >= g_SlenderTeleportMaxTargetStress[bossIndex]) || - gameTime >= g_SlenderTeleportMaxTargetTime[Npc.Index]) + (g_SlenderTeleportMaxTargetTime[Npc.Index] > 0.0 && gameTime >= g_SlenderTeleportMaxTargetTime[Npc.Index])) { // Queue for a new target and mark the old target in the rest period. float restPeriod = Npc.GetTeleportRestPeriod(difficulty); @@ -4692,13 +4718,8 @@ void SlenderOnClientStressUpdate(int client) if (IsValidClient(preferredTeleportTarget)) { // Set our preferred target to the new guy. - float targetDuration = Npc.GetTeleportPersistencyPeriod(difficulty); - float deviation = GetRandomFloat(0.92, 1.08); - targetDuration = Pow(deviation * targetDuration, ((g_RoundDifficultyModifier) / 2.0)) + ((deviation * targetDuration) - 1.0); - g_SlenderTeleportTarget[Npc.Index] = EntIndexToEntRef(preferredTeleportTarget); g_SlenderTeleportPlayersRestTime[Npc.Index][preferredTeleportTarget] = -1.0; - g_SlenderTeleportMaxTargetTime[Npc.Index] = gameTime + targetDuration; g_SlenderTeleportTargetTime[Npc.Index] = gameTime; g_SlenderTeleportMaxTargetStress[Npc.Index] = targetStress; @@ -6557,7 +6578,6 @@ static Action Timer_RoundStart(Handle timer) { if (g_PageMax > 0) { - ArrayList arrayClients = new ArrayList(); int clients[MAXTF2PLAYERS]; int clientsNum = 0; @@ -6569,7 +6589,6 @@ static Action Timer_RoundStart(Handle timer) continue; } - arrayClients.Push(player.UserID); clients[clientsNum] = i; clientsNum++; } @@ -6577,9 +6596,9 @@ static Action Timer_RoundStart(Handle timer) // Show difficulty menu. if (!SF_IsBoxingMap() && !SF_IsRenevantMap() && !SF_SpecialRound(SPECIALROUND_MODBOSSES)) { - if (clientsNum) + if (clientsNum > 0) { - g_VoteTimer = CreateTimer(1.0, Timer_VoteDifficulty, arrayClients, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_VoteTimer = CreateTimer(1.0, Timer_VoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); TriggerTimer(g_VoteTimer, true); int gameText = -1; @@ -6604,14 +6623,6 @@ static Action Timer_RoundStart(Handle timer) } } } - else - { - delete arrayClients; - } - } - else - { - delete arrayClients; } } @@ -6863,41 +6874,36 @@ static Action Timer_RoundTimeEscape(Handle timer) return Plugin_Continue; } -static Action Timer_VoteDifficulty(Handle timer, any data) +static Action Timer_VoteDifficulty(Handle timer) { - ArrayList arrayClients = view_as(data); - if (timer != g_VoteTimer || IsRoundEnding()) { - delete arrayClients; return Plugin_Stop; } - if (NativeVotes_IsVoteInProgress()) + if (IsVoteInProgress() || NativeVotes_IsVoteInProgress()) { return Plugin_Continue; // There's another vote in progess. Wait. } - if (arrayClients == null) - { - return Plugin_Stop; - } - int clients[MAXTF2PLAYERS] = { -1, ... }; int clientsNum; - for (int i = 0, size = arrayClients.Length; i < size; i++) + for (int i = 1; i <= MaxClients; i++) { - int client = GetClientOfUserId(arrayClients.Get(i)); - if (client <= 0) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsBot || player.IsEliminated) { continue; } - clients[clientsNum] = client; + clients[clientsNum] = player.index; clientsNum++; } - delete arrayClients; + if (clientsNum == 0) + { + return Plugin_Stop; + } RandomizeVoteMenu(); g_MenuVoteDifficulty.DisplayVote(clients, clientsNum, 15); @@ -6934,7 +6940,7 @@ void SF_FailEnd() static Action Timer_Fail(Handle timer) { - LogSF2Message("Wow you hit a rare bug, where the round doesn't end after the timer ran out. Collecting info on your game...\nContact Mentrillum or The Gaben and give them the following log:"); + LogSF2Message("Wow you hit a rare bug, where the round doesn't end after the timer ran out. Collecting info on your game...\nContact Mentrillum and give them the following log:"); int escapedPlayers = 0; int clientInGame = 0; int redPlayers = 0; diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index 421ae644..e5a75423 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -115,10 +115,7 @@ static void ClientResetDeathCam(int client) g_PlayerDeathCamEnt2[client] = INVALID_ENT_REFERENCE; g_PlayerDeathCamTarget[client] = INVALID_ENT_REFERENCE; - if (IsClientInGame(client)) - { - SetClientViewEntity(client, client); - } + SetClientViewEntity(client, client); if (deathCamBoss != -1) { @@ -146,6 +143,7 @@ static void ClientResetDeathCam(int client) Call_StartForward(g_OnClientEndDeathCamFwd); Call_PushCell(client); Call_PushCell(deathCamBoss); + Call_PushCell(SF2_BaseBoss(ent).IsValid() || SF2_ChaserEntity(ent).IsValid() || SF2_StatueEntity(ent).IsValid()); Call_Finish(); } diff --git a/addons/sourcemod/scripting/sf2/client/music.sp b/addons/sourcemod/scripting/sf2/client/music.sp index 6e3e7c3b..5f926e89 100644 --- a/addons/sourcemod/scripting/sf2/client/music.sp +++ b/addons/sourcemod/scripting/sf2/client/music.sp @@ -799,7 +799,7 @@ void ClientAlertMusicReset(int client) g_PlayerAlertMusicVolumes[client][i] = 0.0; g_PlayerAlertMusicString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { @@ -903,7 +903,7 @@ void ClientIdleMusicReset(int client) g_PlayerIdleMusicVolumes[client][i] = 0.0; g_PlayerIdleMusicString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { @@ -1007,7 +1007,7 @@ void ClientChaseMusicReset(int client) g_PlayerChaseMusicTimer[client][i] = null; g_PlayerChaseMusicString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { @@ -1115,7 +1115,7 @@ void ClientChaseMusicSeeReset(int client) g_PlayerChaseMusicSeeVolumes[client][i] = 0.0; g_PlayerChaseMusicSeeString[client][i][0] = '\0'; - if (NPCGetUniqueID(i) != -1) + if (NPCGetUniqueID(i) != -1 && NPCGetType(i) == SF2BossType_Chaser) { if (IsValidClient(client)) { diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 1a3e463d..46ced711 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -1550,7 +1550,9 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) else { if (!g_PlayerHasRegenerationItem[client]) + { g_PlayerHasRegenerationItem[client] = IsRegenWeapon(weaponEnt); + } } } delete itemHandle; diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index 11927dd2..789d33f2 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -198,7 +198,7 @@ void Hook_ClientPreThink(int client) } // Calculate player stress levels. - if (GetGameTime() >= g_PlayerStressNextUpdateTime[client]) + if (!g_PlayerEliminated[client] && !DidClientEscape(client) && GetGameTime() >= g_PlayerStressNextUpdateTime[client]) { g_PlayerStressNextUpdateTime[client] = GetGameTime() + 0.33; ClientAddStress(client, -0.01); @@ -814,7 +814,6 @@ Action Timer_ClientAverageUpdate(Handle timer) static int hudColorHealthy[3]; static int hudColorCritical[3] = { 255, 10, 10 }; - static int hudColorBossBar[3] = { 43, 103, 255 }; for (int i = 1; i <= MaxClients; i++) { @@ -1049,58 +1048,14 @@ Action Timer_ClientAverageUpdate(Handle timer) 0.5); ShowSyncHudText(player.index, g_HudSync2, buffer); } - - if (player.IsInPvE) - { - ArrayList bosses = GetActivePvEBosses(); - buffer[0] = '\0'; - char buffer2[64]; - char formatter[128], name[SF2_MAX_NAME_LENGTH]; - for (int i2 = 0; i2 < bosses.Length; i2++) - { - if (i2 >= 4) - { - continue; - } - name[0] = '\0'; - SF2_ChaserEntity chaser = SF2_ChaserEntity(EntRefToEntIndex(bosses.Get(i2))); - if (chaser.IsValid() && chaser.Controller.IsValid()) - { - SF2BossProfileData data; - data = view_as(chaser.Controller).GetProfileData(); - data.Names.GetString(1, name, sizeof(name)); - if (!data.DisplayPvEHealth) - { - continue; - } - } - - if (bosses.Length > 1 && i2 > 0) - { - FormatEx(buffer2, sizeof(buffer2), "\n"); - } - - CBaseCombatCharacter boss = CBaseCombatCharacter(EntRefToEntIndex(bosses.Get(i2))); - int health = boss.GetProp(Prop_Data, "m_iHealth"); - if (name[0] != '\0') - { - FormatEx(formatter, sizeof(formatter), "%s: %d", name, health); - } - else - { - FormatEx(formatter, sizeof(formatter), "%d", health); - } - - StrCat(buffer2, sizeof(buffer2), formatter); - StrCat(buffer, sizeof(buffer), buffer2); - SetHudTextParams(-1.0, 0.15, 0.25, hudColorBossBar[0], hudColorBossBar[1], hudColorBossBar[2], 225, 0, 1.0, 0.07, 0.3); - ShowSyncHudText(i, g_HudSync2, buffer); - } - } } } player.UpdateListeningFlags(); player.UpdateMusicSystem(); + + Call_StartForward(g_OnPlayerAverageUpdatePFwd); + Call_PushCell(player); + Call_Finish(); } return Plugin_Continue; diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 000b5392..5f42d759 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -229,6 +229,8 @@ public void OnPluginStart() g_LoadOutsideMapsConVar = CreateConVar("sf2_load_outside_maps", "0", "Allow bosses to be loaded outside of Slender Fortress maps.", _, true, 0.0, true, 1.0); g_DefaultBossTeamConVar = CreateConVar("sf2_default_boss_team", "1", "If bosses are loaded outside of SF2, determine what default team bosses should be with.", _, true, 1.0, true, 5.0); + g_EngineerBuildInBLUConVar = CreateConVar("sf2_engineer_build_in_blue", "0", "Allows BLU engineers to build outside of the PvP and PvE arena.", _, true, 0.0, true, 1.0); + g_MaxRoundsConVar = FindConVar("mp_maxrounds"); g_HudSync = CreateHudSynchronizer(); @@ -421,6 +423,7 @@ public void OnPluginStart() g_OnPlayerTurnOnFlashlightPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerTurnOffFlashlightPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerFlashlightBreakPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerAverageUpdatePFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnSpecialRoundStartPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnBossSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnBossRemovedPFwd = new PrivateForward(ET_Ignore, Param_Cell); @@ -1013,6 +1016,10 @@ static Action Hook_CommandSuicideAttempt(int client, const char[] command,int ar { return Plugin_Continue; } + if (!IsValidClient(client)) + { + return Plugin_Continue; + } if (GetClientTeam(client) == TFTeam_Spectator) { return Plugin_Continue; @@ -1416,16 +1423,6 @@ static Action Command_RemoveSlender(int client, int args) return Plugin_Handled; } - if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(bossIndex)) - { - g_SlenderBoxingBossCount -= 1; - } - - if (MusicActive() && BossHasMusic(profile) && BossMatchesCurrentMusic(profile)) - { - NPCStopMusic(); - } - NPCRemove(bossIndex); if (IsValidClient(client)) @@ -1765,6 +1762,35 @@ static Action Command_ReloadProfiles(int client, int args) ReloadSpecialRounds(); ReloadClassConfigs(); } + + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + + if (!npc.IsValid()) + { + continue; + } + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + npc.GetProfile(profile, sizeof(profile)); + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + NPCSetProfileData(npc.Index, data); + + if (npc.Type == SF2BossType_Chaser) + { + SF2ChaserBossProfileData chaserData; + g_ChaserBossProfileData.GetArray(profile, chaserData, sizeof(chaserData)); + NPCChaserSetProfileData(npc.Index, chaserData); + } + else if (npc.Type == SF2BossType_Statue) + { + SF2StatueBossProfileData statueData; + g_StatueBossProfileData.GetArray(profile, statueData, sizeof(statueData)); + NPCStatueSetProfileData(npc.Index, statueData); + } + } CPrintToChatAll("{royalblue}%t {default} Reloaded all profiles successfully.", "SF2 Prefix"); return Plugin_Handled; diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 5dbf64ff..34afb1fc 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -659,7 +659,7 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) int inflictor = event.GetInt("inflictor_entindex"); int owner = inflictor; - if (!SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) + if (IsValidEntity(inflictor) && !SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) { owner = GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity"); } diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index d0fdea84..bd7c7d41 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -44,7 +44,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnClientLooksAwayFromBossFwd = new GlobalForward("SF2_OnClientLooksAwayFromBoss", ET_Ignore, Param_Cell, Param_Cell); g_OnClientStartDeathCamFwd = new GlobalForward("SF2_OnClientStartDeathCam", ET_Ignore, Param_Cell, Param_Cell); g_OnClientPreKillDeathCamFwd = new GlobalForward("SF2_OnClientPreKillDeathCam", ET_Ignore, Param_Cell, Param_Cell); - g_OnClientEndDeathCamFwd = new GlobalForward("SF2_OnClientEndDeathCam", ET_Ignore, Param_Cell, Param_Cell); + g_OnClientEndDeathCamFwd = new GlobalForward("SF2_OnClientEndDeathCam", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); g_OnClientGetDefaultWalkSpeedFwd = new GlobalForward("SF2_OnClientGetDefaultWalkSpeed", ET_Hook, Param_Cell, Param_CellByRef); g_OnClientGetDefaultSprintSpeedFwd = new GlobalForward("SF2_OnClientGetDefaultSprintSpeed", ET_Hook, Param_Cell, Param_CellByRef); g_OnClientTakeDamageFwd = new GlobalForward("SF2_OnClientTakeDamage", ET_Hook, Param_Cell, Param_CellByRef, Param_CellByRef, Param_FloatByRef); diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 0cb4daff..3e4e540f 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -168,7 +168,7 @@ void SetupMenus() g_MenuCredits = new Menu(Menu_Credits); - FormatEx(buffer, sizeof(buffer), "Credits\n"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Coders: KitRifty, Kenzzer, Mentrillum, The Gaben\n"); StrCat(buffer, sizeof(buffer), "Mark J. Hadley - The creator of the Slender game\n"); StrCat(buffer, sizeof(buffer), "Mark Steen - Compositing the intro music\n"); @@ -182,7 +182,7 @@ void SetupMenus() g_MenuCredits1 = new Menu(Menu_Credits1); - FormatEx(buffer, sizeof(buffer), "Credits\n"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Glubbable - For working on a ton of maps\n"); StrCat(buffer, sizeof(buffer), "Somberguy - Suggestions and support\n"); StrCat(buffer, sizeof(buffer), "Omi-Box - Materials, maps, current Slender Man model, and more\n"); @@ -197,7 +197,7 @@ void SetupMenus() g_MenuCredits2 = new Menu(Menu_Credits2); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "To all the peeps who alpha-tested this thing!\n \n"); StrCat(buffer, sizeof(buffer), "Tofu\n"); StrCat(buffer, sizeof(buffer), "Ace-Dashie\n"); @@ -216,7 +216,7 @@ void SetupMenus() g_MenuCredits3 = new Menu(Menu_Credits3); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Credits to all peeps who gave special round suggestions!\n \n"); StrCat(buffer, sizeof(buffer), "TehPlayer14\n"); StrCat(buffer, sizeof(buffer), "SirAnthony\n"); @@ -235,14 +235,13 @@ void SetupMenus() g_MenuCredits4 = new Menu(Menu_Credits4); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "Major special thanks to all official Modified server owners!\n \n"); StrCat(buffer, sizeof(buffer), "Demon Hamster Eating My Wafflez\n"); StrCat(buffer, sizeof(buffer), "Munt\n"); StrCat(buffer, sizeof(buffer), "KanP\n"); StrCat(buffer, sizeof(buffer), "SAXY GIBUS MAN\n"); StrCat(buffer, sizeof(buffer), "Fire\n"); - StrCat(buffer, sizeof(buffer), "[NxN]Nameless\n"); g_MenuCredits4.SetTitle(buffer); g_MenuCredits4.AddItem("0", "Next"); @@ -250,7 +249,7 @@ void SetupMenus() g_MenuCredits5 = new Menu(Menu_Credits5); - FormatEx(buffer, sizeof(buffer), "%tCredits\n \n", "SF2 Prefix"); + FormatEx(buffer, sizeof(buffer), "%t Credits\n \n", "SF2 Prefix"); StrCat(buffer, sizeof(buffer), "And finally to all of these people that helped out this version one way or another!\n \n"); StrCat(buffer, sizeof(buffer), "KitRifty\n"); StrCat(buffer, sizeof(buffer), "Spook\n"); @@ -285,7 +284,7 @@ void RandomizeVoteMenu(bool excludeHigh = false) g_IsRunOff = false; g_MenuVoteDifficulty = new Menu((g_DifficultyVoteRevoteConVar.FloatValue > 0.0) ? Menu_VoteNoneDifficulty : Menu_VoteDifficulty); - g_MenuVoteDifficulty.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); + g_MenuVoteDifficulty.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); g_DifficultyVoteOptionsConVar.GetString(buffer, sizeof(buffer)); @@ -499,7 +498,7 @@ static void Menu_VoteRunoffDifficulty(Menu oldmenu, int votes, int clients, cons { g_IsRunOff = true; Menu newmenu = new Menu(Menu_VoteDifficulty); - newmenu.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); + newmenu.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); ArrayList list = new ArrayList(); for(int i = 0; i < items; i++) @@ -521,27 +520,7 @@ static void Menu_VoteRunoffDifficulty(Menu oldmenu, int votes, int clients, cons delete list; - list = new ArrayList(); - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || IsFakeClient(i) || g_PlayerEliminated[i]) - { - continue; - } - - list.Push(GetClientUserId(i)); - } - - if (list.Length) - { - //delete g_VoteTimer; - g_VoteTimer = CreateTimer(1.0, Timer_ReVoteDifficulty, list, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE); - } - else - { - delete list; - } + g_VoteTimer = CreateTimer(1.0, Timer_ReVoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); if (GetMenuItemCount(g_MenuVoteDifficulty) > 1) { @@ -563,7 +542,7 @@ static void Menu_VoteRunoffDifficulty(Menu oldmenu, int votes, int clients, cons Menu_VoteDifficulty(oldmenu, MenuAction_VoteEnd, itemInfo[0][VOTEINFO_ITEM_INDEX], 0); } -static Action Timer_ReVoteDifficulty(Handle timer, ArrayList arrayClients) +static Action Timer_ReVoteDifficulty(Handle timer) { if (timer != g_VoteTimer || IsRoundEnding()) { @@ -571,25 +550,31 @@ static Action Timer_ReVoteDifficulty(Handle timer, ArrayList arrayClients) return Plugin_Stop; } - if (IsVoteInProgress()) + if (IsVoteInProgress() || NativeVotes_IsVoteInProgress()) { return Plugin_Continue; // There's another vote in progess. Wait. } - int clients[MAXPLAYERS + 1] = { -1, ... }; + int clients[MAXTF2PLAYERS] = { -1, ... }; int clientsNum; - for (int i = 0, size = arrayClients.Length; i < size; i++) + for (int i = 1; i <= MaxClients; i++) { - int client = GetClientOfUserId(arrayClients.Get(i)); - if (client <= 0) + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || player.IsBot || player.IsEliminated) { continue; } - clients[clientsNum] = client; + clients[clientsNum] = player.index; clientsNum++; } + if (clientsNum == 0) + { + g_VoteTimer = null; + return Plugin_Stop; + } + VoteMenu(g_MenuVoteDifficulty, clients, clientsNum, 7); g_VoteTimer = null; return Plugin_Stop; @@ -1109,7 +1094,7 @@ static int Menu_ClassInfo(Menu menu, MenuAction action, int param1, int param2) FormatEx(title, sizeof(title), "SF2 Help %s Class Info Menu Title", info); FormatEx(description, sizeof(description), "SF2 Help %s Class Info Description", info); - menuHandle.SetTitle("%t%t\n \n%t\n \n", "SF2 Prefix", title, description); + menuHandle.SetTitle("%t %t\n \n%t\n \n", "SF2 Prefix", title, description); menuHandle.AddItem("0", "Back"); menuHandle.Display(param1, 30); } @@ -1764,7 +1749,7 @@ void DisplayQueuePointsMenu(int client) delete queueList; - menu.SetTitle("%t%T\n \n", "SF2 Prefix", "SF2 Queue Menu Title", client); + menu.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Queue Menu Title", client); menu.ExitBackButton = true; menu.Display(client, MENU_TIME_FOREVER); } @@ -1803,7 +1788,7 @@ void DisplayViewGroupMembersQueueMenu(int client,int groupIndex) GetPlayerGroupName(groupIndex, groupName, sizeof(groupName)); Menu menuHandle = new Menu(Menu_ViewGroupMembersQueue); - menuHandle.SetTitle("%t%T (%s)\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, groupName); + menuHandle.SetTitle("%t %T (%s)\n \n", "SF2 Prefix", "SF2 View Group Members Menu Title", client, groupName); char userId[32]; char name[MAX_NAME_LENGTH * 2]; @@ -1968,6 +1953,10 @@ void DisplayBossList(int client) } SF2BossProfileData data; g_BossProfileData.GetArray(profile, data, sizeof(data)); + if (data.Description.Hidden) + { + continue; + } if (data.IsPvEBoss) { StrCat(displayName, sizeof(displayName), " (PvE Boss)"); @@ -1975,7 +1964,7 @@ void DisplayBossList(int client) menu.AddItem(profile, displayName); } } - menu.SetTitle("%t%T\n \n", "SF2 Prefix", "SF2 Boss List Menu Title", client); + menu.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Boss List Menu Title", client); menu.ExitBackButton = true; menu.Display(client, MENU_TIME_FOREVER); } @@ -1991,6 +1980,131 @@ static int Menu_BossList(Menu menu, MenuAction action, int param1, int param2) g_MenuMain.Display(param1, 30); } } + + case MenuAction_Select: + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + menu.GetItem(param2, profile, sizeof(profile)); + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + Menu newMenu = new Menu(Menu_BossDisplay); + char buffer[256], buffer2[128]; + data.Names.GetString(Difficulty_Normal, buffer2, sizeof(buffer2)); + FormatEx(buffer, sizeof(buffer), "%t %s\n \n", "SF2 Prefix", buffer2); + FormatEx(buffer2, sizeof(buffer2), "Type: %s\n \n", data.Description.Type); + StrCat(buffer, sizeof(buffer), buffer2); + SF2ChaserBossProfileData chaserData; + if (g_ChaserBossProfileData.GetArray(profile, chaserData, sizeof(chaserData))) + { + FormatEx(buffer2, sizeof(buffer2), "Walk speed: %s\n", ConvertWalkSpeedToDescription(chaserData.WalkSpeed[Difficulty_Normal])); + } + StrCat(buffer, sizeof(buffer), buffer2); + float speed = data.RunSpeed[Difficulty_Normal]; + SF2StatueBossProfileData statueData; + if (g_StatueBossProfileData.GetArray(profile, statueData, sizeof(statueData))) + { + speed *= 10.0; + } + FormatEx(buffer2, sizeof(buffer2), "Run speed: %s\n \n", ConvertRunSpeedToDescription(speed)); + StrCat(buffer, sizeof(buffer), buffer2); + FormatEx(buffer2, sizeof(buffer2), "%s", data.Description.Information); + ReplaceString(buffer2, sizeof(buffer2), "\\n", "\n"); + StrCat(buffer, sizeof(buffer), buffer2); + newMenu.SetTitle(buffer); + newMenu.AddItem("", "", ITEMDRAW_SPACER); + newMenu.ExitBackButton = true; + newMenu.Display(param1, MENU_TIME_FOREVER); + } + + case MenuAction_End: + { + delete menu; + } + } + return 0; +} + +static char[] ConvertWalkSpeedToDescription(float speed) +{ + char buffer[128]; + buffer = "None"; + if (speed > 0.0 && speed <= 45.0) + { + buffer = "Very slow"; + } + else if (speed > 45.0 && speed <= 65.0) + { + buffer = "Slow"; + } + else if (speed > 65.0 && speed <= 85.0) + { + buffer = "Moderate"; + } + else if (speed > 85.0 && speed <= 120.0) + { + buffer = "Average"; + } + else if (speed > 120.0 && speed <= 150.0) + { + buffer = "Fast"; + } + else + { + buffer = "Very fast"; + } + + return buffer; +} + +static char[] ConvertRunSpeedToDescription(float speed) +{ + char buffer[128]; + buffer = "None"; + if (speed > 0.0 && speed <= 50.0) + { + buffer = "Very slow"; + } + else if (speed > 50.0 && speed <= 150.0) + { + buffer = "Slow"; + } + else if (speed > 150.0 && speed <= 250.0) + { + buffer = "Moderate"; + } + else if (speed > 250.0 && speed <= 300.0) + { + buffer = "Average"; + } + else if (speed > 300.0 && speed <= 350.0) + { + buffer = "Fast"; + } + else + { + buffer = "Very fast"; + } + + return buffer; +} + +static int Menu_BossDisplay(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack) + { + DisplayBossList(param1); + } + } + + case MenuAction_Select: + { + DisplayBossList(param1); + } + case MenuAction_End: { delete menu; diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index ff897960..4e10bdbe 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -12,6 +12,8 @@ static int g_NpcGlobalUniqueID = 0; +static SF2BossProfileData g_NpcProfileData[MAX_BOSSES]; + static int g_NpcUniqueID[MAX_BOSSES] = { -1, ... }; static char g_SlenderProfile[MAX_BOSSES][SF2_MAX_PROFILE_NAME_LENGTH]; static int g_NpcProfileIndex[MAX_BOSSES] = { -1, ... }; @@ -252,6 +254,8 @@ void NPC_InitializeAPI() CreateNative("SF2_GetProfileFromName", Native_GetProfileDataEx); CreateNative("SF2_SpawnBossEffects", Native_SpawnBossEffects); + + CreateNative("SF2_CanBossBeSeen", Native_CanBossBeSeen); } static void EntityDestroyed(CBaseEntity ent, const char[] classname) @@ -367,7 +371,7 @@ void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity) Despawn_Chaser(bossIndex); } - if (g_BossPathFollower[bossIndex].IsValid()) + if (g_BossPathFollower[bossIndex] != view_as(0) && g_BossPathFollower[bossIndex].IsValid()) { g_BossPathFollower[bossIndex].Invalidate(); } @@ -525,12 +529,12 @@ bool NPCGetProfile(int npcIndex, char[] buffer, int bufferLen) SF2BossProfileData NPCGetProfileData(int npcIndex) { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(npcIndex, profile, sizeof(profile)); + return g_NpcProfileData[npcIndex]; +} - SF2BossProfileData profileData; - g_BossProfileData.GetArray(profile, profileData, sizeof(profileData)); - return profileData; +void NPCSetProfileData(int npcIndex, SF2BossProfileData value) +{ + g_NpcProfileData[npcIndex] = value; } void NPCSetProfile(int npcIndex, const char[] profile) @@ -1572,6 +1576,11 @@ bool NPCShouldHearEntity(int npcIndex, int entity, SoundType soundType) return false; } + if (NPCGetType(npcIndex) == SF2BossType_Statue) + { + return false; + } + Action result = Plugin_Continue; Call_StartForward(g_OnBossHearEntityFwd); Call_PushCell(npcIndex); @@ -1777,11 +1786,12 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF npc.SetProfile(profile); SF2BossProfileData profileData; - profileData = npc.GetProfileData(); + g_BossProfileData.GetArray(profile, profileData, sizeof(profileData)); int bossType = GetBossProfileType(profile); g_NpcUniqueID[npc.Index] = g_NpcGlobalUniqueID++; + g_NpcProfileData[npc.Index] = profileData; g_NpcType[npc.Index] = bossType; g_NpcModelScale[npc.Index] = GetBossProfileModelScale(profile); @@ -2717,6 +2727,24 @@ void ChangeAllSlenderModels() void RemoveProfile(int bossIndex) { SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(bossIndex); + if (IsValidEntity(controller.EntIndex)) + { + KillPvEBoss(controller.EntIndex); + } + + if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(bossIndex)) + { + g_SlenderBoxingBossCount -= 1; + } + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + controller.GetProfile(profile, sizeof(profile)); + + if (MusicActive() && BossHasMusic(profile) && BossMatchesCurrentMusic(profile)) + { + NPCStopMusic(); + } + controller.UnSpawn(true); // Call our forward. @@ -2728,9 +2756,6 @@ void RemoveProfile(int bossIndex) Call_PushCell(controller); Call_Finish(); - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - controller.GetProfile(profile, sizeof(profile)); - // Clean up on the clients. for (int i = 1; i <= MaxClients; i++) { @@ -3151,7 +3176,7 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) entity.AddFlag(FL_NPC); if (g_Enabled) { - if (!data.IsPvEBoss) + if (!data.IsPvEBoss && !SF_IsRaidMap()) { entity.AddFlag(FL_NOTARGET); } @@ -3819,7 +3844,7 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) int teleportTarget = EntRefToEntIndex(g_SlenderTeleportTarget[bossIndex]); - if (!teleportTarget || teleportTarget == INVALID_ENT_REFERENCE) + if (!IsTargetValidForSlenderEx(CBaseEntity(teleportTarget), bossIndex)) { // We don't have any good targets. Remove myself for now. if (SlenderCanRemove(bossIndex)) @@ -3827,6 +3852,8 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) controller.UnSpawn(); } + g_SlenderTeleportTarget[bossIndex] = INVALID_ENT_REFERENCE; + #if defined DEBUG SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Teleport for boss %d: no good target, removing...", bossIndex); #endif @@ -3838,6 +3865,13 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) controller.UnSpawn(); return Plugin_Continue; } + + // Let's start the persistency timer here, so that way we won't have infinite looping impossible to spawn bosses + float targetDuration = controller.GetTeleportPersistencyPeriod(difficulty); + float deviation = GetRandomFloat(0.92, 1.08); + targetDuration = Pow(deviation * targetDuration, ((g_RoundDifficultyModifier) / 2.0)) + ((deviation * targetDuration) - 1.0); + g_SlenderTeleportMaxTargetTime[controller.Index] = gameTime + targetDuration; + float teleportMinRange = g_SlenderTeleportMinRange[bossIndex][difficulty]; bool shouldBeBehindObstruction = false; if (NPCGetTeleportType(bossIndex) == 2) @@ -3922,9 +3956,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) NavCornerType cornerB = corners.Get(GetRandomInt(0, corners.Length - 1)); area.GetCorner(cornerA, spawnPos); area.GetCorner(cornerB, cornerPosition); - LerpVectors(spawnPos, cornerPosition, spawnPos, GetURandomFloat()); + LerpVectors(spawnPos, cornerPosition, spawnPos, GetRandomFloat(0.3, 0.7)); area.GetCorner(invert, cornerPosition); - LerpVectors(spawnPos, cornerPosition, spawnPos, GetURandomFloat()); + LerpVectors(spawnPos, cornerPosition, spawnPos, GetRandomFloat(0.3, 0.7)); delete corners;*/ float traceMins[3]; @@ -3946,6 +3980,10 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) if (findValue != -1) { areaArray.Erase(findValue); + + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: boss index %d collides with something", findValue, bossIndex); + #endif } } else @@ -3972,8 +4010,12 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) if (findValue != -1 && findValue < validAreaCount) { areaArray.Erase(findValue); + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: too close to %N", findValue, i); + #endif } canSpawn = false; + break; } } @@ -3986,6 +4028,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) if (findValue != -1) { areaArray.Erase(findValue); + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: is visible to someone", findValue); + #endif } canSpawn = false; } @@ -4037,6 +4082,9 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) if (findValue != -1) { areaArray.Erase(findValue); + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Area index %d: boss index %d is too close to copy %d", findValue, bossIndex, bossCheck); + #endif } canSpawn = false; break; @@ -4088,6 +4136,12 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) } break; } + else + { + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_BOSS_TELEPORTATION, 0, "Teleport for boss %d: can't spawn due to no available areas", bossIndex); + #endif + } } } @@ -5174,4 +5228,9 @@ static any Native_SpawnBossEffects(Handle plugin, int numParams) ArrayList output = GetNativeCellRef(5); SlenderSpawnEffects(GetNativeCell(1), GetNativeCell(2), false, pos, ang, output, GetNativeCell(6)); return 0; +} + +static any Native_CanBossBeSeen(Handle plugin, int numParams) +{ + return PeopleCanSeeSlender(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index c7a56483..45938bba 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -496,6 +496,7 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter CreateNative("SF2_BaseBossEntity.CurrentChaseDuration.set", Native_SetCurrentChaseDuration); CreateNative("SF2_BaseBossEntity.InitialChaseDuration.get", Native_GetInitialChaseDuration); CreateNative("SF2_BaseBossEntity.InitialChaseDuration.set", Native_SetInitialChaseDuration); + CreateNative("SF2_BaseBossEntity.IsKillingSomeone.get", Native_GetIsKillingSomeone); CreateNative("SF2_BaseBossEntity.EyePosition", Native_EyePosition); CreateNative("SF2_BaseBossEntity.GetProfileName", Native_GetProfileName); CreateNative("SF2_BaseBossEntity.GetName", Native_GetName); @@ -923,6 +924,18 @@ static any Native_SetInitialChaseDuration(Handle plugin, int numParams) return 0; } +static any Native_GetIsKillingSomeone(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); + return bossEntity.IsKillingSomeone; +} + static any Native_EyePosition(Handle plugin, int numParams) { int entity = GetNativeCell(1); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp index 8148e30e..9958a200 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp @@ -264,7 +264,10 @@ static void DoBulletAttack(SF2_ChaserEntity actor, const char[] attackName) SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.BulletDamage[difficulty], DMG_BULLET, _, CalculateBulletDamageForce(dir, 1.0), endPos); - attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); + if (SF2_BasePlayer(hitTarget).IsValid) + { + attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); + } if (attackData.BulletTrace[0] == '\0') { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp index 3dec2a27..6550c4ff 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -200,6 +200,7 @@ static void OnEnd(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) path.Invalidate(); } actor.MaintainTarget = false; + actor.HasSmelled = false; int difficulty = actor.Controller.Difficulty; if (actor.FollowedCompanionChase) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp index 8d95907d..40bc5c64 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/flee_to_heal.sp @@ -146,7 +146,7 @@ static NextBotAction InitialContainedAction(SF2_ChaserFleeToHealAction action, S int difficulty = controller.Difficulty; action.IsHealing = false; action.HasPath = false; - action.FleeTime = GetRandomFloat(10.0, 15.0); + action.FleeTime = GetRandomFloat(5.0, 10.0); if (rageInfo.Animations.GetAnimation("start", difficulty, animName, sizeof(animName), rate, duration, cycle)) { int sequence = LookupProfileAnimation(actor.index, animName); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp index 9c09e74a..0bc71e0f 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp @@ -83,7 +83,14 @@ static int OnStart(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, NextBotA } } - actor.SmellCooldown = gameTime + GetRandomFloat(data.SmellData.CooldownMin[difficulty], data.SmellData.CooldownMax[difficulty]); + if (!actor.HasSmelled) + { + actor.SmellCooldown = gameTime + GetRandomFloat(data.SmellData.CooldownMin[difficulty], data.SmellData.CooldownMax[difficulty]); + } + else + { + actor.SmellCooldown = gameTime + GetRandomFloat(data.SmellData.CooldownAfterStateMin[difficulty], data.SmellData.CooldownAfterStateMax[difficulty]); + } g_SlenderTimeUntilKill[controller.Index] = GetGameTime() + NPCGetIdleLifetime(controller.Index, difficulty); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp index de79fdfb..feae580e 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp @@ -187,4 +187,5 @@ static int OnSuspend(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextB static void OnEnd(SF2_ChaserSmellAction action, SF2_ChaserEntity actor) { actor.IsAllowedToDespawn = true; + actor.HasSmelled = true; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 56bc8ae5..42aa7adb 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -92,6 +92,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss .DefineFloatField("m_FollowCooldownChase") .DefineIntField("m_SmellPlayerList") .DefineFloatField("m_SmellCooldown") + .DefineBoolField("m_HasSmelled") .DefineFloatField("m_ProjectileCooldown") .DefineBoolField("m_IsReloadingProjectiles") .DefineIntField("m_ProjectileAmmo") @@ -118,8 +119,6 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); - g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); - g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); SF2_ChaserAttackAction.Initialize(); InitializePostureRagePhase(); @@ -798,6 +797,19 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } } + property bool HasSmelled + { + public get() + { + return this.GetProp(Prop_Data, "m_HasSmelled") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_HasSmelled", value); + } + } + property float ProjectileCooldown { public get() @@ -1694,6 +1706,11 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss aimAtTarget = true; } + if (attackData.Type == SF2BossAttackType_LaserBeam || attackData.Type == SF2BossAttackType_Projectile || attackData.Type == SF2BossAttackType_Ranged) + { + aimAtTarget = true; + } + if (aimAtTarget) { float pos[3]; @@ -3158,7 +3175,6 @@ static void OnPlayerSpawn(SF2_BasePlayer client) SF2NPC_Chaser controller = SF2NPC_Chaser(i); chaser.SetAlertTriggerCount(client, 0); chaser.SetAlertSoundTriggerCooldown(client, 0.0); - chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); chaser.SetAutoChaseCount(client, 0); chaser.SetAutoChaseCooldown(client, -1.0); client.SetForceChaseState(controller, false); @@ -3196,7 +3212,6 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo continue; } SF2NPC_Chaser controller = SF2NPC_Chaser(i); - chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); int index = controller.ChaseOnLookTargets.FindValue(entity); if (index != -1) @@ -3236,50 +3251,24 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo } } -static void OnPlayerEscape(SF2_BasePlayer client) +static bool EnumerateTargets(int entIndex, ArrayList players) { - for (int i = 0; i < MAX_BOSSES; i++) + if (IsValidClient(entIndex) && IsPlayerAlive(entIndex) && !IsClientInGhostMode(entIndex)) { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - int entity = NPCGetEntIndex(i); - if (!entity || entity == INVALID_ENT_REFERENCE) - { - continue; - } - - SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); - if (!chaser.IsValid()) - { - continue; - } - chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); + players.Push(entIndex); } -} -static void OnDisconnected(SF2_BasePlayer client) -{ - for (int i = 0; i < MAX_BOSSES; i++) + if (g_Buildings.FindValue(EnsureEntRef(entIndex)) != -1) { - if (NPCGetUniqueID(i) == -1) - { - continue; - } - int entity = NPCGetEntIndex(i); - if (!entity || entity == INVALID_ENT_REFERENCE) - { - continue; - } + players.Push(entIndex); + } - SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); - if (!chaser.IsValid()) - { - continue; - } - chaser.MyNextBotPointer().GetVisionInterface().ForgetEntity(client.index); + if (g_WhitelistedEntities.FindValue(EnsureEntRef(entIndex)) != -1) + { + players.Push(entIndex); } + + return true; } static void OnCreate(SF2_ChaserEntity ent) @@ -3294,7 +3283,6 @@ static void OnCreate(SF2_ChaserEntity ent) SDKHook(ent.index, SDKHook_OnTakeDamage, OnTakeDamage); SDKHook(ent.index, SDKHook_OnTakeDamageAlivePost, OnTakeDamageAlivePost); SDKHook(ent.index, SDKHook_TraceAttack, TraceOnHit); - SDKHook(ent.index, SDKHook_SetTransmit, SetTransmit); SetEntityTransmitState(ent.index, FL_EDICT_FULLCHECK); g_DHookShouldTransmit.HookEntity(Hook_Pre, ent.index, ShouldTransmit); g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent.index, UpdateTransmitState); @@ -3497,6 +3485,8 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam { SF2_ChaserEntity chaser = SF2_ChaserEntity(victim); SF2_BasePlayer player = SF2_BasePlayer(attacker); + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); if (player.IsValid && player.IsProxy) { @@ -3514,18 +3504,29 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam { return Plugin_Handled; } + + if (data.IsPvEBoss && player.IsValid && !player.IsInPvE) + { + SDKHooks_TakeDamage(inflictor, chaser.index, chaser.index, CBaseEntity(inflictor).GetProp(Prop_Data, "m_iHealth") * 4.0); + return Plugin_Handled; + } } - SF2BossProfileData data; - data = view_as(chaser.Controller).GetProfileData(); SF2ChaserBossProfileData chaserData; chaserData = chaser.Controller.GetProfileData(); int difficulty = chaser.Controller.Difficulty; if (player.IsValid) { + float myEyePos[3], clientEyePos[3], buffer[3], myAng[3]; + player.GetEyePosition(clientEyePos); + chaser.Controller.GetEyePosition(myEyePos); + SubtractVectors(clientEyePos, myEyePos, buffer); + GetVectorAngles(buffer, buffer); + chaser.GetAbsAngles(myAng); + CBaseEntity weaponEnt = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); - if (weaponEnt.IsValid() && weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) + if (weaponEnt.IsValid() && weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon == weaponEnt.index) { switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) { @@ -3550,14 +3551,7 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam if (player.Class == TFClass_Spy && (data.IsPvEBoss || SF_IsBoxingMap()) && chaser.State != STATE_DEATH) { - float myEyePos[3], clientEyePos[3], buffer[3], myAng[3]; - player.GetEyePosition(clientEyePos); - chaser.Controller.GetEyePosition(myEyePos); - SubtractVectors(clientEyePos, myEyePos, buffer); - GetVectorAngles(buffer, buffer); - chaser.GetAbsAngles(myAng); - - if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 90.0 && chaserData.BackstabDamageScale > 0.0) + if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 75.0 && chaserData.BackstabDamageScale > 0.0) { damageType = DMG_CRIT; EmitSoundToClient(player.index, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); @@ -3613,10 +3607,33 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam } } + player.SetProp(Prop_Send, "m_iRevengeCrits", player.GetProp(Prop_Send, "m_iRevengeCrits") + 1); + return Plugin_Changed; } } + } + weaponEnt = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); + if (weaponEnt.IsValid() && weaponEnt.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon == weaponEnt.index) + { + switch (weaponEnt.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 15, 202, 41, 298, 312, 424, 654, 793, 802, 811, 832, 850, 882, 891, 900, 909, 958, 967: // Miniguns + { + damage *= 0.65; + return Plugin_Changed; + } + + case 40, 1146: // Backburner + { + if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 60.0 && chaserData.BackstabDamageScale > 0.0) + { + damageType |= DMG_CRIT; + return Plugin_Changed; + } + } + } } } @@ -3641,9 +3658,10 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float if (player.IsValid) { CBaseEntity weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Primary)); - if (weapon.IsValid() && weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon")) + if (weapon.IsValid()) { - switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + int index = weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex"); + switch (index) { case 594: // Phlog { @@ -3660,17 +3678,66 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float } } } + + if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon.index == weaponEntIndex) + { + switch (index) + { + case 527: // Widowmaker + { + GivePlayerAmmo(player.index, RoundToNearest(damage), 3, true); + } + + case 228, 1085: // Black Box + { + int health = player.Health; + float regen = FloatClamp(damage / 90.0, 0.0, 1.0); + int newHealth = health + RoundToNearest(20.0 * regen); + if (newHealth <= player.GetProp(Prop_Data, "m_iMaxHealth")) + { + SetEntityHealth(player.index, newHealth); + } + else + { + SetEntityHealth(player.index, player.GetProp(Prop_Data, "m_iMaxHealth")); + } + ShowHealthRegen(player.index, RoundToNearest(20.0 * regen), index); + } + + case 448: // Soda Popper + { + if (!player.InCondition(TFCond_CritHype)) + { + float hype = player.GetPropFloat(Prop_Send, "m_flHypeMeter"); + float add = (damage / 350.0) * 100.0; + float result = FloatClamp(hype + add, 0.0, 100.0); + player.SetPropFloat(Prop_Send, "m_flHypeMeter", result); + } + } + + case 772: // Baby Face's Blaster + { + float hype = player.GetPropFloat(Prop_Send, "m_flHypeMeter"); + float add = (damage / 100.0) * 100.0; + float result = FloatClamp(hype + add, 0.0, 100.0); + player.SetPropFloat(Prop_Send, "m_flHypeMeter", result); + result /= 100.0; + player.SetPropFloat(Prop_Send, "m_flMaxspeed", 360.0 + (160.0 * result)); + } + } + } } weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Secondary)); if (weapon.IsValid()) { - switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + int index = weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex"); + switch (index) { case 129, 1001, 226, 354: // Banners { float requiredRage = 6.0; - if (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex") == 354) + if (index == 354) { requiredRage = 4.8; } @@ -3687,6 +3754,47 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float } } } + + if (weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon.index == weaponEntIndex) + { + switch (index) + { + case 773: // Pocket pistol + { + ShowHealthRegen(player.index, 3, index); + int health = player.Health; + int newHealth = health + 3; + if (newHealth <= player.GetProp(Prop_Data, "m_iMaxHealth")) + { + SetEntityHealth(player.index, newHealth); + } + else + { + SetEntityHealth(player.index, player.GetProp(Prop_Data, "m_iMaxHealth")); + } + } + } + } + } + + weapon = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Melee)); + if (weapon.IsValid() && weapon.index == player.GetPropEnt(Prop_Send, "m_hActiveWeapon") && weapon.index == weaponEntIndex) + { + switch (weapon.GetProp(Prop_Send, "m_iItemDefinitionIndex")) + { + case 37: // Ubersaw + { + CBaseEntity secondary = CBaseEntity(player.GetWeaponSlot(TFWeaponSlot_Secondary)); + if (secondary.IsValid()) + { + secondary.SetPropFloat(Prop_Send, "m_flChargeLevel", secondary.GetPropFloat(Prop_Send, "m_flChargeLevel") + 0.25); + if (secondary.GetPropFloat(Prop_Send, "m_flChargeLevel") > 1.0) + { + secondary.SetPropFloat(Prop_Send, "m_flChargeLevel", 1.0); + } + } + } + } } } @@ -3722,6 +3830,7 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float { SetEntityHealth(player.index, player.GetProp(Prop_Data, "m_iMaxHealth")); } + ShowHealthRegen(player.index, RoundToCeil(mult)); } if (broadcastDamage) @@ -3803,24 +3912,6 @@ static Action TraceOnHit(int victim, int& attacker, int& inflictor, float& damag return Plugin_Continue; } -static Action SetTransmit(int ent, int other) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - SF2_BasePlayer player = SF2_BasePlayer(other); - SF2_ChaserEntity chaser = SF2_ChaserEntity(ent); - - if (player.IsValid && player.IsInDeathCam && chaser.KillTarget != player) - { - return Plugin_Handled; - } - - return Plugin_Continue; -} - static MRESReturn UpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) { if (entIndex == -1) @@ -3878,6 +3969,8 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio controller.GetEyePosition(traceStartPos); chaser.GetAbsOrigin(myPos); + int state = chaser.State; + if (originalData.EyeData.Type == 1) { if (chaser.EyeBoneIndex <= 0) @@ -3938,40 +4031,10 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } ArrayList valids = new ArrayList(); - for (int i = 1; i <= MaxClients; i++) - { - SF2_BasePlayer client = SF2_BasePlayer(i); - - if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, client, attackEliminated)) - { - continue; - } - if (originalData.IsPvEBoss && !IsPvETargetValid(client)) - { - continue; - } - - valids.Push(EntIndexToEntRef(client.index)); - } - - for (int i = 0; i < g_Buildings.Length; i++) - { - CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); - if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) - { - continue; - } - if (originalData.IsPvEBoss && !IsPvETargetValid(entity)) - { - continue; - } - - valids.Push(EntIndexToEntRef(entity.index)); - } - - for (int i = 0; i < g_WhitelistedEntities.Length; i++) + TR_EnumerateEntitiesSphere(traceStartPos, searchRange, PARTITION_SOLID_EDICTS, EnumerateTargets, valids); + for (int i = 0; i < valids.Length; i++) { - CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(valids.Get(i))); if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) { continue; @@ -3981,16 +4044,6 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio continue; } - valids.Push(EntIndexToEntRef(entity.index)); - } - - for (int i = 0; i < valids.Length; i++) - { - CBaseEntity entity = CBaseEntity(EntRefToEntIndex(valids.Get(i))); - if (!entity.IsValid()) - { - continue; - } SF2_BasePlayer player = SF2_BasePlayer(entity.index); if (player.IsValid && g_PlayerDebugFlags[player.index] & DEBUG_BOSS_EYES) @@ -4229,7 +4282,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio { if (!IsTargetValidForSlender(chaser, CBaseEntity(bestNewTarget), attackEliminated)) { - if (chaser.State != STATE_CHASE && (NPCAreAvailablePlayersAlive() || g_Buildings.Length > 0 || g_WhitelistedEntities.Length > 0)) + if (state != STATE_CHASE && (NPCAreAvailablePlayersAlive() || g_Buildings.Length > 0 || g_WhitelistedEntities.Length > 0)) { ArrayList arrayRaidTargets = new ArrayList(); @@ -4239,37 +4292,40 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio { continue; } - arrayRaidTargets.Push(i); + arrayRaidTargets.Push(EntIndexToEntRef(i)); } - for (int i = 0; i < g_Buildings.Length; i++) + if (!g_Enabled) { - if (!IsTargetValidForSlender(chaser, CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))), false)) + for (int i = 0; i < g_Buildings.Length; i++) { - continue; - } + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!IsTargetValidForSlender(chaser, building, false)) + { + continue; + } - CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); - // Do not go looking for buildings that are carried or are in an inactive state - if (building.GetProp(Prop_Send, "m_bCarried") || building.GetProp(Prop_Send, "m_iState") == 0) - { - continue; + // Do not go looking for buildings that are carried or are in an inactive state + if (building.GetProp(Prop_Send, "m_bCarried") || building.GetProp(Prop_Send, "m_iState") == 0) + { + continue; + } + arrayRaidTargets.Push(g_Buildings.Get(i)); } - arrayRaidTargets.Push(building.index); - } - for (int i = 0; i < g_WhitelistedEntities.Length; i++) - { - if (!IsTargetValidForSlender(chaser, CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))), false)) + for (int i = 0; i < g_WhitelistedEntities.Length; i++) { - continue; + if (!IsTargetValidForSlender(chaser, CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))), false)) + { + continue; + } + arrayRaidTargets.Push(g_WhitelistedEntities.Get(i)); } - arrayRaidTargets.Push(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); } if (arrayRaidTargets.Length > 0) { - int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); + int raidTarget = EntRefToEntIndex(arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1))); if (IsValidClient(raidTarget)) { if (!g_PlayerEliminated[raidTarget]) @@ -4292,7 +4348,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio { if (!IsPvETargetValid(SF2_BasePlayer(bestNewTarget))) { - if (chaser.State != STATE_CHASE) + if (state != STATE_CHASE) { ArrayList arrayRaidTargets = new ArrayList(); @@ -4302,11 +4358,12 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio { continue; } - arrayRaidTargets.Push(i); + arrayRaidTargets.Push(EntIndexToEntRef(i)); } + if (arrayRaidTargets.Length > 0) { - int raidTarget = arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1)); + int raidTarget = EntRefToEntIndex(arrayRaidTargets.Get(GetRandomInt(0, arrayRaidTargets.Length - 1))); if (IsValidClient(raidTarget) && IsClientInPvE(raidTarget)) { bestNewTarget = raidTarget; @@ -4349,7 +4406,10 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) originalData = baseController.GetProfileData(); SF2ChaserBossProfilePostureInfo postureInfo; char posture[64]; - chaser.GetPosture(posture, sizeof(posture)); + if (data.Postures != null) + { + chaser.GetPosture(posture, sizeof(posture)); + } float speed, acceleration; @@ -4489,7 +4549,10 @@ static void ProcessBody(SF2_ChaserEntity chaser) return; } - if (chaser.CanUpdatePosture()) + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + if (data.Postures != null && chaser.CanUpdatePosture()) { char posture[64], resultPosture[64]; chaser.GetDefaultPosture(posture, sizeof(posture)); @@ -4512,8 +4575,6 @@ static void ProcessBody(SF2_ChaserEntity chaser) INextBot bot = chaser.MyNextBotPointer(); ILocomotion loco = bot.GetLocomotionInterface(); CBaseNPC npc = TheNPCs.FindNPCByEntIndex(chaser.index); - SF2ChaserBossProfileData data; - data = controller.GetProfileData(); float speed = loco.GetGroundSpeed(); @@ -4666,6 +4727,11 @@ static void ProcessBody(SF2_ChaserEntity chaser) static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) { + if (SF2_ChaserEntity(other).IsValid() || SF2_StatueEntity(other).IsValid()) + { + return false; + } + if (IsValidEntity(other)) { SF2_BasePlayer player = SF2_BasePlayer(other); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index f27f9cc7..da2ce9e4 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -145,6 +145,7 @@ methodmap SF2_StatueEntity < SF2_BaseBoss npc.flDeathDropHeight = 99999.0; npc.flJumpHeight = 512.0; npc.flMaxYawRate = originalData.TurnRate; + loco.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); loco.SetCallback(LocomotionCallback_ClimbUpToLedge, ClimbUpCBase); statue.SetPropVector(Prop_Send, "m_vecMins", HULL_HUMAN_MINS); @@ -176,6 +177,11 @@ methodmap SF2_StatueEntity < SF2_BaseBoss } } +static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) +{ + return false; +} + static void OnCreate(SF2_StatueEntity ent) { ent.LastKillTime = 0.0; diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index ee7d69ea..4b1ef8ca 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -5,6 +5,8 @@ #pragma semicolon 1 +static SF2ChaserBossProfileData g_NpcChaserProfileData[MAX_BOSSES]; + static float g_NpcStunAddHealth[MAX_BOSSES]; static float g_NpcDeathInitialHealth[MAX_BOSSES][Difficulty_Max]; @@ -91,12 +93,12 @@ static void OnBossRemoved(SF2NPC_BaseNPC npc) SF2ChaserBossProfileData NPCChaserGetProfileData(int npcIndex) { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(npcIndex, profile, sizeof(profile)); + return g_NpcChaserProfileData[npcIndex]; +} - SF2ChaserBossProfileData profileData; - g_ChaserBossProfileData.GetArray(profile, profileData, sizeof(profileData)); - return profileData; +void NPCChaserSetProfileData(int npcIndex, SF2ChaserBossProfileData value) +{ + g_NpcChaserProfileData[npcIndex] = value; } float NPCChaserGetAddStunHealth(int npcIndex) @@ -177,6 +179,9 @@ void NPCChaserOnSelectProfile(int npcIndex) SF2NPC_Chaser chaser = SF2NPC_Chaser(npcIndex); char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(npcIndex, profile, sizeof(profile)); + SF2ChaserBossProfileData profileData; + g_ChaserBossProfileData.GetArray(profile, profileData, sizeof(profileData)); + g_NpcChaserProfileData[npcIndex] = profileData; for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { @@ -353,6 +358,64 @@ bool IsTargetValidForSlender(SF2_BaseBoss boss, CBaseEntity target, bool include return true; } +bool IsTargetValidForSlenderEx(CBaseEntity target, int bossIndex, bool includeEliminated = false) +{ + if (!target.IsValid()) + { + return false; + } + + if (target.index <= 0) + { + return false; + } + + SF2_BasePlayer player = SF2_BasePlayer(target.index); + if (player.IsValid && (!player.IsAlive || + player.IsInDeathCam || + player.IsInGhostMode || + player.HasEscaped || + player.InCondition(view_as(130)) || + player.Team == TFTeam_Spectator)) + { + return false; + } + + if (g_Enabled) + { + if (!includeEliminated && player.IsValid && player.IsEliminated) + { + return false; + } + + if (!g_SlenderTeleportIgnoreChases[bossIndex]) + { + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_Chaser npc = SF2NPC_Chaser(i); + if (!npc.IsValid()) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + + int state = chaser.State; + if (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) + { + return false; + } + } + } + } + + return true; +} + bool IsPvETargetValid(CBaseEntity target) { if (!target.IsValid()) @@ -367,6 +430,15 @@ bool IsPvETargetValid(CBaseEntity target) return false; } + if (g_Buildings.FindValue(EntIndexToEntRef(target.index)) != -1) + { + int owner = target.GetPropEnt(Prop_Send, "m_hBuilder"); + if (IsValidClient(owner) && !IsClientInPvE(owner)) + { + return false; + } + } + return true; } diff --git a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp index d17cc662..3aabc616 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_creeper.sp @@ -6,15 +6,16 @@ #pragma semicolon 1 static float g_NpcStatueIdleLifetime[MAX_BOSSES][Difficulty_Max]; +static SF2StatueBossProfileData g_StatueProfileData[MAX_BOSSES]; SF2StatueBossProfileData NPCStatueGetProfileData(int npcIndex) { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(npcIndex, profile, sizeof(profile)); + return g_StatueProfileData[npcIndex]; +} - SF2StatueBossProfileData profileData; - g_StatueBossProfileData.GetArray(profile, profileData, sizeof(profileData)); - return profileData; +void NPCStatueSetProfileData(int npcIndex, SF2StatueBossProfileData value) +{ + g_StatueProfileData[npcIndex] = value; } float NPCStatueGetIdleLifetime(int npcIndex, int difficulty) @@ -25,11 +26,12 @@ float NPCStatueGetIdleLifetime(int npcIndex, int difficulty) void NPCStatueOnSelectProfile(int npcIndex) { SF2NPC_Statue statue = SF2NPC_Statue(npcIndex); - SF2StatueBossProfileData data; - data = statue.GetProfileData(); + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + NPCGetProfile(npcIndex, profile, sizeof(profile)); + g_StatueBossProfileData.GetArray(profile, g_StatueProfileData[npcIndex], sizeof(g_StatueProfileData[])); for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { - g_NpcStatueIdleLifetime[npcIndex][difficulty] = data.IdleLifeTime[difficulty]; + g_NpcStatueIdleLifetime[npcIndex][difficulty] = g_StatueProfileData[npcIndex].IdleLifeTime[difficulty]; } statue.SetAffectedBySight(true); diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index b6c5413a..5df0a2ad 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -32,6 +32,7 @@ ConVar g_BossPackEndOfMapVoteConVar; ConVar g_BossPackVoteStartTimeConVar; ConVar g_BossPackVoteStartRoundConVar; ConVar g_BossPackVoteShuffleConVar; +ConVar g_MaxCorePackBosses; static bool g_BossPackVoteEnabled = false; @@ -87,6 +88,8 @@ void InitializeBossProfiles() g_BossPackVoteStartRoundConVar = CreateConVar("sf2_boss_profile_pack_endvote_startround", "2", "Specifies when to start the vote based on rounds remaining on the map.", FCVAR_NOTIFY); g_BossPackVoteShuffleConVar = CreateConVar("sf2_boss_profile_pack_endvote_shuffle", "0", "Shuffles the menu options of boss pack endvotes if enabled."); + g_MaxCorePackBosses = CreateConVar("sf2_max_core_pack_bosses", "-1", "Determines how many bosses can load randomly from the core pack, if set to less than 0 will keep this feature off. Note that companion bosses will still load if needed."); + InitializeStatueProfiles(); InitializeChaserProfiles(); } @@ -550,11 +553,11 @@ void ReloadBossProfiles() // Only load profiles individually from configs/sf2/profiles or data/sf2/profiles directory. if (!g_UseAlternateConfigDirectoryConVar.BoolValue) { - LoadProfilesFromDirectory(FILE_PROFILES_DIR); + LoadProfilesFromDirectory(FILE_PROFILES_DIR, g_MaxCorePackBosses.IntValue); } else { - LoadProfilesFromDirectory(FILE_PROFILES_DIR_DATA); + LoadProfilesFromDirectory(FILE_PROFILES_DIR_DATA, g_MaxCorePackBosses.IntValue); } if (!g_UseAlternateConfigDirectoryConVar.BoolValue) @@ -608,7 +611,12 @@ void ReloadBossProfiles() if (DirExists(configPath)) { FormatEx(packConfigFilePath, sizeof(packConfigFilePath), "%s/%s", !g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_PROFILES_PACKS_DIR : FILE_PROFILES_PACKS_DIR_DATA, packConfigFile); - LoadProfilesFromDirectory(packConfigFilePath); + int maxLoadedBosses = -1; + if (g_BossPackConfig.JumpToKey("shuffler")) + { + maxLoadedBosses = g_BossPackConfig.GetNum("max", maxLoadedBosses); + } + LoadProfilesFromDirectory(packConfigFilePath, maxLoadedBosses); } if (!voteBossPackLoaded) @@ -654,7 +662,12 @@ void ReloadBossProfiles() if (DirExists(configPath)) { FormatEx(packConfigFilePath, sizeof(packConfigFilePath), "%s/%s", !g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_PROFILES_PACKS_DIR : FILE_PROFILES_PACKS_DIR_DATA, packConfigFile); - LoadProfilesFromDirectory(packConfigFilePath); + int maxLoadedBosses = -1; + if (g_BossPackConfig.JumpToKey("shuffler")) + { + maxLoadedBosses = g_BossPackConfig.GetNum("max", maxLoadedBosses); + } + LoadProfilesFromDirectory(packConfigFilePath, maxLoadedBosses); } } } @@ -687,7 +700,7 @@ void ReloadBossProfiles() /** * Loads a profile from the specified file. */ -static bool LoadProfileFile(const char[] profilePath, char[] profileName, int profileNameLen, char[] errorReason, int errorReasonLen) +static bool LoadProfileFile(const char[] profilePath, char[] profileName, int profileNameLen, char[] errorReason, int errorReasonLen, bool lookIntoLoads = false, const char[] originalDir) { if (!FileExists(profilePath)) { @@ -705,14 +718,14 @@ static bool LoadProfileFile(const char[] profilePath, char[] profileName, int pr kv.GetSectionName(profileName, profileNameLen); - bool result = LoadBossProfile(kv, profileName, errorReason, errorReasonLen); + bool result = LoadBossProfile(kv, profileName, errorReason, errorReasonLen, lookIntoLoads, originalDir); delete kv; return result; } -static void LoadProfilesFromDirectory(const char[] relDirPath) +static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBosses = -1) { LogSF2Message("Loading boss profile files from directory %s...", relDirPath); @@ -740,6 +753,8 @@ static void LoadProfilesFromDirectory(const char[] relDirPath) char errorReason[512]; FileType fileType; + ArrayList directories = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + while (directory.GetNext(fileName, sizeof(fileName), fileType)) { if (fileType == FileType_Directory) @@ -750,7 +765,50 @@ static void LoadProfilesFromDirectory(const char[] relDirPath) FormatEx(filePath, sizeof(filePath), "%s/%s", relDirPath, fileName); BuildPath(Path_SM, filePath, sizeof(filePath), filePath); - if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason))) + directories.PushString(filePath); + } + + delete directory; + + ArrayList alwaysLoad; + + if (maxLoadedBosses > 0) + { + directories.Sort(Sort_Random, Sort_String); + alwaysLoad = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + + for (int i = 0; i < directories.Length; i++) + { + directories.GetString(i, filePath, sizeof(filePath)); + + if (FileExists(filePath)) + { + KeyValues kv = new KeyValues("root"); + if (FileToKeyValues(kv, filePath) && kv.GetNum("always_load", false) != 0) + { + int index = directories.FindString(filePath); + if (index != -1) + { + directories.Erase(index); + } + alwaysLoad.PushString(filePath); + } + + delete kv; + } + } + } + + for (int i = 0; i < directories.Length; i++) + { + if (maxLoadedBosses > 0 && count == maxLoadedBosses) + { + break; + } + + directories.GetString(i, filePath, sizeof(filePath)); + + if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) { LogSF2Message("%s...FAILED (reason: %s)", filePath, errorReason); } @@ -761,9 +819,29 @@ static void LoadProfilesFromDirectory(const char[] relDirPath) } } - delete directory; + if (alwaysLoad != null) + { + for (int i = 0; i < alwaysLoad.Length; i++) + { + alwaysLoad.GetString(i, filePath, sizeof(filePath)); + + if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) + { + LogSF2Message("(ALWAYS LOAD) %s...FAILED (reason: %s)", filePath, errorReason); + } + else + { + LogSF2Message("(ALWAYS LOAD) %s...", profileName, filePath); + count++; + } + } + + delete alwaysLoad; + } + + delete directories; - LogSF2Message("Loaded %d boss profile(s) from directory!", count, relDirPath); + LogSF2Message("Loaded %d boss profile(s) from directory %s!", count, relDirPath); } Handle g_BossPackVoteMapTimer = null; @@ -881,7 +959,7 @@ void InitiateBossPackVote(int initiator) do { - if (!g_BossPackConfig.GetNum("autoload") && g_BossPackConfig.GetNum("show_in_vote", 1) != 0) + if (g_BossPackConfig.GetNum("autoload", false) == 0 && g_BossPackConfig.GetNum("show_in_vote", true) != 0) { char bossPack[128]; g_BossPackConfig.GetSectionName(bossPack, sizeof(bossPack)); diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index adc057f2..0083cb56 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -205,6 +205,8 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } + profileData.BackstabDamageScale = kv.GetFloat("backstab_damage_scale", profileData.BackstabDamageScale); + if (kv.JumpToKey("cloaking")) { profileData.CloakData.Load(kv, g_FileCheckConVar.BoolValue); diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 94ef23a3..6adbef68 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -8,7 +8,7 @@ /** * Loads a profile in the current KeyValues position in kv. */ -bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen) +bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen, bool lookIntoLoads = false, const char[] originalDir = "") { SF2BossProfileData profileData; profileData.Init(); @@ -36,6 +36,59 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GoBack(); } + if (lookIntoLoads) + { + // In this, we're basically just gonna go look for companion bosses and skip them here + bool skip = true; + + if (kv.GetNum("enable_random_selection", true) != 0) + { + skip = false; + } + + if (kv.GetNum("admin_only", false) != 0) + { + skip = false; + } + + if (kv.GetNum("enable_random_selection_boxing", false) != 0) + { + skip = false; + } + + if (kv.GetNum("enable_random_selection_renevant", false) != 0) + { + skip = false; + } + + if (kv.GetNum("enable_random_selection_renevant_admin", false) != 0) + { + skip = false; + } + + if (kv.GetNum("is_pve", false) != 0) + { + skip = false; + } + + if (kv.GetNum("always_load", false) != 0) + { + skip = false; + } + + if (kv.JumpToKey("pve")) + { + kv.GoBack(); + skip = false; + } + + if (skip) + { + FormatEx(loadFailReasonBuffer, loadFailReasonBufferLen, "is not selectable, skipping!"); + return false; + } + } + profileData.Models = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); profileData.Names = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); SetProfileDifficultyStringArrayValues(kv, "model", profileData.Models, true); @@ -131,6 +184,25 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.TurnRate = kv.GetFloat("maxyawrate", profileData.TurnRate); profileData.TurnRate = kv.GetFloat("turnrate", profileData.TurnRate); + switch (profileData.Type) + { + case SF2BossType_Chaser: + { + profileData.Description.Type = "Chaser"; + } + + case SF2BossType_Statue: + { + profileData.Description.Type = "Statue"; + } + } + + if (kv.JumpToKey("description")) + { + profileData.Description.Load(kv); + kv.GoBack(); + } + profileData.ScareCooldown = kv.GetFloat("scare_cooldown", profileData.ScareCooldown); if (profileData.ScareCooldown < 0.0) { @@ -252,21 +324,21 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu GetProfileDifficultyFloatValues(kv, "teleport_target_stress_min", profileData.TeleportStressMin, profileData.TeleportStressMin); GetProfileDifficultyFloatValues(kv, "teleport_target_stress_max", profileData.TeleportStressMax, profileData.TeleportStressMax); GetProfileDifficultyFloatValues(kv, "teleport_target_persistency_period", profileData.TeleportPersistencyPeriod, profileData.TeleportPersistencyPeriod); - profileData.TeleportIgnoreChases = !!kv.GetNum("teleport_target_ignore_chases", profileData.TeleportIgnoreChases); - profileData.TeleportIgnoreVis = !!kv.GetNum("teleport_target_ignore_visibility", profileData.TeleportIgnoreVis); + profileData.TeleportIgnoreChases = kv.GetNum("teleport_target_ignore_chases", profileData.TeleportIgnoreChases) != 0; + profileData.TeleportIgnoreVis = kv.GetNum("teleport_target_ignore_visibility", profileData.TeleportIgnoreVis) != 0; GetProfileDifficultyFloatValues(kv, "jumpscare_distance", profileData.JumpscareDistance, profileData.JumpscareDistance); GetProfileDifficultyFloatValues(kv, "jumpscare_duration", profileData.JumpscareDuration, profileData.JumpscareDuration); GetProfileDifficultyFloatValues(kv, "jumpscare_cooldown", profileData.JumpscareCooldown, profileData.JumpscareCooldown); - profileData.JumpscareOnScare = !!kv.GetNum("jumpscare_on_scare", profileData.JumpscareOnScare); - profileData.JumpscareNoSight = !!kv.GetNum("jumpscare_no_sight", profileData.JumpscareNoSight); + profileData.JumpscareOnScare = kv.GetNum("jumpscare_on_scare", profileData.JumpscareOnScare) != 0; + profileData.JumpscareNoSight = kv.GetNum("jumpscare_no_sight", profileData.JumpscareNoSight) != 0; GetProfileDifficultyFloatValues(kv, "speed", profileData.RunSpeed, profileData.RunSpeed); GetProfileDifficultyFloatValues(kv, "acceleration", profileData.Acceleration, profileData.Acceleration); GetProfileDifficultyFloatValues(kv, "idle_lifetime", profileData.IdleLifeTime, profileData.IdleLifeTime); - profileData.CustomOutlines = !!kv.GetNum("customizable_outlines", profileData.CustomOutlines); + profileData.CustomOutlines = kv.GetNum("customizable_outlines", profileData.CustomOutlines) != 0; if (profileData.CustomOutlines) { profileData.OutlineColor[0] = kv.GetNum("outline_color_r", profileData.OutlineColor[0]); @@ -305,7 +377,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetString("scare_player_reaction_response_custom", profileData.ScareReactionCustom, sizeof(profileData.ScareReactionCustom), profileData.ScareReactionCustom); } - profileData.ScareReplenishSprint = !!kv.GetNum("scare_player_replenish_sprint", profileData.ScareReplenishSprint); + profileData.ScareReplenishSprint = kv.GetNum("scare_player_replenish_sprint", profileData.ScareReplenishSprint) != 0; if (profileData.ScareReplenishSprint) { profileData.ScareReplenishSprintAmount = kv.GetFloat("scare_player_replenish_sprint_amount", profileData.ScareReplenishSprintAmount); @@ -740,7 +812,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu g_BossProfileList.PushString(profile); } - if (!!kv.GetNum("enable_random_selection", 1)) + if (kv.GetNum("enable_random_selection", true) != 0) { if (GetSelectableBossProfileList().FindString(profile) == -1) { @@ -757,7 +829,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("admin_only", 0)) + if (kv.GetNum("admin_only", false) != 0) { if (GetSelectableAdminBossProfileList().FindString(profile) == -1) { @@ -774,7 +846,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("enable_random_selection_boxing", 0)) + if (kv.GetNum("enable_random_selection_boxing", false) != 0) { if (GetSelectableBoxingBossProfileList().FindString(profile) == -1) { @@ -791,7 +863,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("enable_random_selection_renevant", 0)) + if (kv.GetNum("enable_random_selection_renevant", false) != 0) { if (GetSelectableRenevantBossProfileList().FindString(profile) == -1) { @@ -808,7 +880,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (!!kv.GetNum("enable_random_selection_renevant_admin", 0)) + if (kv.GetNum("enable_random_selection_renevant_admin", false) != 0) { if (GetSelectableRenevantBossAdminProfileList().FindString(profile) == -1) { @@ -825,35 +897,78 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - profileData.IsPvEBoss = kv.GetNum("is_pve", profileData.IsPvEBoss) != 0; + if (kv.JumpToKey("pve")) + { + profileData.IsPvEBoss = true; + kv.GoBack(); + } + else + { + profileData.IsPvEBoss = kv.GetNum("is_pve", profileData.IsPvEBoss) != 0; + } + if (profileData.IsPvEBoss) { profileData.Flags = profileData.Flags & ~SFF_PROXIES; - if (kv.JumpToKey("pve_spawn_messages")) + if (kv.JumpToKey("pve")) { - profileData.PvESpawnMessagesArray = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); - char message[256], section[64]; - for (int i = 1;; i++) + if (kv.JumpToKey("spawn_messages")) { - FormatEx(section, sizeof(section), "%d", i); - kv.GetString(section, message, sizeof(message)); - if (message[0] == '\0') + profileData.PvESpawnMessagesArray = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char message[256], section[64]; + for (int i = 1;; i++) { - break; - } + FormatEx(section, sizeof(section), "%d", i); + kv.GetString(section, message, sizeof(message)); + if (message[0] == '\0') + { + break; + } - profileData.PvESpawnMessagesArray.PushString(message); + profileData.PvESpawnMessagesArray.PushString(message); + } + kv.GoBack(); + } + kv.GetString("spawn_message_prefix", profileData.PvESpawnMessagePrefix, sizeof(profileData.PvESpawnMessagePrefix), profileData.PvESpawnMessagePrefix); + profileData.DisplayPvEHealth = kv.GetNum("health_bar", profileData.DisplayPvEHealth) != 0; + char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + strcopy(setProfile, sizeof(setProfile), profile); + if (kv.GetNum("selectable", 1) != 0) + { + RegisterPvESlenderBoss(setProfile); } + profileData.PvETeleportEndTimer = kv.GetFloat("teleport_players_time", profileData.PvETeleportEndTimer); kv.GoBack(); } - kv.GetString("pve_spawn_message_prefix", profileData.PvESpawnMessagePrefix, sizeof(profileData.PvESpawnMessagePrefix), profileData.PvESpawnMessagePrefix); - profileData.DisplayPvEHealth = kv.GetNum("pve_health_bar", profileData.DisplayPvEHealth) != 0; - char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; - strcopy(setProfile, sizeof(setProfile), profile); - if (kv.GetNum("pve_selectable", 1) != 0) + else { - RegisterPvESlenderBoss(setProfile); + if (kv.JumpToKey("pve_spawn_messages")) + { + profileData.PvESpawnMessagesArray = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + char message[256], section[64]; + for (int i = 1;; i++) + { + FormatEx(section, sizeof(section), "%d", i); + kv.GetString(section, message, sizeof(message)); + if (message[0] == '\0') + { + break; + } + + profileData.PvESpawnMessagesArray.PushString(message); + } + kv.GoBack(); + } + kv.GetString("pve_spawn_message_prefix", profileData.PvESpawnMessagePrefix, sizeof(profileData.PvESpawnMessagePrefix), profileData.PvESpawnMessagePrefix); + profileData.DisplayPvEHealth = kv.GetNum("pve_health_bar", profileData.DisplayPvEHealth) != 0; + char setProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + strcopy(setProfile, sizeof(setProfile), profile); + if (kv.GetNum("pve_selectable", 1) != 0) + { + RegisterPvESlenderBoss(setProfile); + } } + index = GetSelectableBossProfileList().FindString(profile); if (index != -1) { @@ -1130,6 +1245,49 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu companions.Init(); companions.Load(kv); profileData.CompanionsArray.PushArray(companions); + if (lookIntoLoads) + { + char compProfile[SF2_MAX_PROFILE_NAME_LENGTH], otherProfile[SF2_MAX_PROFILE_NAME_LENGTH], dir[PLATFORM_MAX_PATH], file[PLATFORM_MAX_PATH]; + FileType fileType; + DirectoryListing directory = OpenDirectory(originalDir); + while (directory.GetNext(file, sizeof(file), fileType)) + { + if (fileType == FileType_Directory) + { + continue; + } + + FormatEx(dir, sizeof(dir), "%s/%s", originalDir, file); + + for (int i = 0; i < companions.Bosses.Length; i++) + { + companions.Bosses.GetString(i, compProfile, sizeof(compProfile)); + + KeyValues otherKeys = new KeyValues("root"); + if (!FileToKeyValues(otherKeys, dir)) + { + delete otherKeys; + continue; + } + + otherKeys.GetSectionName(otherProfile, sizeof(otherProfile)); + + if (strcmp(compProfile, otherProfile) == 0) + { + if (!LoadBossProfile(otherKeys, otherProfile, loadFailReasonBuffer, loadFailReasonBufferLen)) + { + LogSF2Message("(COMPANION) %s...FAILED (reason: %s)", dir, loadFailReasonBuffer); + } + else + { + LogSF2Message("(COMPANION) %s...", otherProfile); + } + } + + delete otherKeys; + } + } + } } while (kv.GotoNextKey()); kv.GoBack(); diff --git a/addons/sourcemod/scripting/sf2/pve.sp b/addons/sourcemod/scripting/sf2/pve.sp index 81189b2b..87c081da 100644 --- a/addons/sourcemod/scripting/sf2/pve.sp +++ b/addons/sourcemod/scripting/sf2/pve.sp @@ -2,6 +2,7 @@ static bool g_PlayerInPvE[MAXTF2PLAYERS]; static bool g_PlayerIsLeavingPvE[MAXTF2PLAYERS]; +static bool g_PlayerShowBossHealthPvE[MAXTF2PLAYERS]; static Handle g_PlayerPvETimer[MAXTF2PLAYERS]; static Handle g_PlayerPvERespawnTimer[MAXTF2PLAYERS]; static int g_PlayerPvETimerCount[MAXTF2PLAYERS]; @@ -42,6 +43,7 @@ void PvE_Initialize() g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerAverageUpdatePFwd.AddFunction(null, OnPlayerAverageUpdate); g_SlenderBosses = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); g_CustomBosses = new ArrayList(ByteCountToCells(64)); @@ -50,6 +52,7 @@ void PvE_Initialize() g_PvETriggers = new ArrayList(); RegConsoleCmd("sm_sf2_add_pve_music", Command_AddPvEMusic); + RegConsoleCmd("sm_sf2_pve_show_health", Command_ShowHealth); RegAdminCmd("sm_sf2_do_pve", Command_DoPvE, ADMFLAG_SLAY); } @@ -128,7 +131,6 @@ static void GameFrame() static void MapStart() { - g_PvETriggers.Clear(); g_DoesPvEExist = false; int ent = -1; while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) @@ -146,8 +148,6 @@ static void MapStart() float range = ((maxs[0] + maxs[1]) / 2.0) + (FloatAbs(mins[0] + mins[1]) / 2.0); if (range > 2000.0) { - // Temporary fix for maps like Cliffroad and Mountain Complex - g_PvETriggers.Push(ent); g_DoesPvEExist = true; } SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); @@ -163,7 +163,6 @@ static void MapStart() { if (trigger.IsBossPvE) { - g_PvETriggers.Push(ent); g_DoesPvEExist = true; } SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); @@ -276,6 +275,7 @@ bool IsPvEBoxing() static void RoundStart() { + g_PvETriggers.Clear(); g_TimeUntilBossSpawns = GetRandomFloat(10.0, 20.0) + GetGameTime(); g_IsPvEActive = false; g_HasBossSpawned = false; @@ -291,6 +291,14 @@ static void RoundStart() trigger.GetPropString(Prop_Data, "m_iName", name, sizeof(name)); if (StrContains(name, "sf2_pve_trigger", false) == 0) { + float mins[3], maxs[3]; + trigger.GetPropVector(Prop_Send, "m_vecMins", mins); + trigger.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + float range = ((maxs[0] + maxs[1]) / 2.0) + (FloatAbs(mins[0] + mins[1]) / 2.0); + if (range > 2000.0) + { + g_PvETriggers.Push(EntIndexToEntRef(ent)); + } SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); } } @@ -302,6 +310,10 @@ static void RoundStart() SF2TriggerPvEEntity trigger = SF2TriggerPvEEntity(ent); if (trigger.IsValid()) { + if (trigger.IsBossPvE) + { + g_PvETriggers.Push(EntIndexToEntRef(ent)); + } SDKHook(ent, SDKHook_EndTouch, PvE_OnTriggerEndTouch); } } @@ -315,6 +327,8 @@ static void OnPutInServer(SF2_BasePlayer client) } g_PlayerEnteredPvETriggers[client.index] = new ArrayList(); + g_PlayerShowBossHealthPvE[client.index] = false; + PvE_ForceResetPlayerPvEData(client.index); if (IsClientAuthorized(client.index)) @@ -366,11 +380,6 @@ static void OnDisconnected(SF2_BasePlayer client) static void OnPlayerSpawn(SF2_BasePlayer client) { - if (!g_DoesPvEExist) - { - return; - } - if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) { return; @@ -399,6 +408,66 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo } } +static void OnPlayerAverageUpdate(SF2_BasePlayer client) +{ + if (!client.IsInPvE && !g_PlayerShowBossHealthPvE[client.index]) + { + return; + } + + if (!client.IsEliminated && g_PlayerShowBossHealthPvE[client.index]) + { + return; + } + + static int hudColorBossBar[3] = { 43, 103, 255 }; + char buffer[256]; + + ArrayList bosses = GetActivePvEBosses(); + char buffer2[64]; + char formatter[128], name[SF2_MAX_NAME_LENGTH]; + for (int i2 = 0; i2 < bosses.Length; i2++) + { + if (i2 >= 4) + { + continue; + } + name[0] = '\0'; + SF2_ChaserEntity chaser = SF2_ChaserEntity(EntRefToEntIndex(bosses.Get(i2))); + if (chaser.IsValid() && chaser.Controller.IsValid()) + { + SF2BossProfileData data; + data = view_as(chaser.Controller).GetProfileData(); + data.Names.GetString(1, name, sizeof(name)); + if (!data.DisplayPvEHealth) + { + continue; + } + } + + if (bosses.Length > 1 && i2 > 0) + { + FormatEx(buffer2, sizeof(buffer2), "\n"); + } + + CBaseCombatCharacter boss = CBaseCombatCharacter(EntRefToEntIndex(bosses.Get(i2))); + int health = boss.GetProp(Prop_Data, "m_iHealth"); + if (name[0] != '\0') + { + FormatEx(formatter, sizeof(formatter), "%s: %d", name, health); + } + else + { + FormatEx(formatter, sizeof(formatter), "%d", health); + } + + StrCat(buffer2, sizeof(buffer2), formatter); + StrCat(buffer, sizeof(buffer), buffer2); + SetHudTextParams(-1.0, 0.15, 0.25, hudColorBossBar[0], hudColorBossBar[1], hudColorBossBar[2], 225, 0, 1.0, 0.07, 0.3); + ShowSyncHudText(client.index, g_HudSync2, buffer); + } +} + static Action Timer_TeleportPlayerToPvE(Handle timer, any userid) { int client = GetClientOfUserId(userid); @@ -515,7 +584,6 @@ void PvE_SetPlayerPvEState(int client, bool status, bool regenerate = true) player.ChangeCondition(TFCond_Ubercharged, true); player.ChangeCondition(TFCond_UberchargedOnTakeDamage, true); player.ChangeCondition(TFCond_Taunting, true); - player.ChangeCondition(TFCond_UberchargedCanteen, _, 1.0); } @@ -557,7 +625,7 @@ void PvE_OnTriggerStartTouch(int trigger, int other) SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); - int entRef = EnsureEntRef(trigger); + int entRef = EntIndexToEntRef(trigger); if (g_PlayerEnteredPvETriggers[other].FindValue(entRef) == -1) { g_PlayerEnteredPvETriggers[other].Push(entRef); @@ -576,7 +644,7 @@ void PvE_OnTriggerStartTouch(int trigger, int other) else { PvE_SetPlayerPvEState(other, true); - if (g_IsPvEActive && g_PvETriggers.FindValue(trigger) != -1) + if (g_IsPvEActive && g_PvETriggers.FindValue(entRef) != -1) { if (g_CustomMusicOverride[0] != '\0') { @@ -920,6 +988,19 @@ static Action Command_AddPvEMusic(int client, int args) return Plugin_Handled; } +static Action Command_ShowHealth(int client, int args) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + g_PlayerShowBossHealthPvE[client] = !g_PlayerShowBossHealthPvE[client]; + CPrintToChat(client, "{royalblue}%t {default}%s seeing the PvE boss health anywhere.", "SF2 Prefix", g_PlayerShowBossHealthPvE[client] ? "Enabled" : "Disabled"); + + return Plugin_Handled; +} + static void DisplayMusicSelectionPvE(int client) { Menu newMenu = new Menu(PvEMenu_MusicSelection); @@ -1143,6 +1224,14 @@ void KillPvEBoss(int boss) g_CustomMusicOverride[0] = '\0'; g_IsPvEActive = false; char targetName[64]; + int bossIndex = NPCGetFromEntIndex(boss); + float time = 5.0; + if (bossIndex != -1) + { + SF2BossProfileData data; + data = NPCGetProfileData(bossIndex); + time = data.PvETeleportEndTimer; + } for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer client = SF2_BasePlayer(i); @@ -1156,7 +1245,7 @@ void KillPvEBoss(int boss) StopSound(client.index, SNDCHAN_AUTO, g_PlayerCurrentPvEMusic[client.index]); } - g_PlayerPvERespawnTimer[client.index] = CreateTimer(5.0, Timer_TeleportPlayerToPvE, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + g_PlayerPvERespawnTimer[client.index] = CreateTimer(time, Timer_TeleportPlayerToPvE, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } int ent = -1; @@ -1170,7 +1259,7 @@ void KillPvEBoss(int boss) } } - CreateTimer(5.0, Timer_RemoveAllPvEBosses, _, TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(time, Timer_RemoveAllPvEBosses, _, TIMER_FLAG_NO_MAPCHANGE); } } diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index 42c09372..e09e023c 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -361,8 +361,23 @@ static Action Hook_PvPProjectile_OnTouch(int projectile, int client) { // Check if the projectile hit a player outside of pvp area // Without that, cannon balls can bounce players which should not happen because they are outside of pvp. + bool remove = false; if (IsValidClient(client) && !IsClientInPvP(client)) { + remove = true; + } + + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (owner == client) + { + remove = false; + } + + if (remove) + { + float vel[3]; + CBaseEntity(client).GetAbsVelocity(vel); + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vel); RemoveEntity(projectile); return Plugin_Handled; } diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index 14ec598e..118c83b4 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -107,32 +107,40 @@ static Action Timer_BeatBoxMasterTimer(Handle timer) { continue; } + float damage = 20.0; switch (g_DifficultyConVar.IntValue) { case Difficulty_Normal: { client.Stun(2.0, 0.25, TF_STUNFLAGS_SMALLBONK, client.index); + damage = 20.0; } case Difficulty_Hard: { client.Stun(3.0, 0.45, TF_STUNFLAGS_SMALLBONK, client.index); + damage = 30.0; } case Difficulty_Insane: { client.Stun(3.5, 0.6, TF_STUNFLAGS_SMALLBONK, client.index); - client.TakeDamage(true, _, _, 20.0, DMG_CLUB); + damage = 40.0; } case Difficulty_Nightmare: { client.Stun(5.5, 0.7, TF_STUNFLAGS_SMALLBONK, client.index); - client.TakeDamage(true, _, _, 50.0, DMG_CLUB); + damage = 50.0; } case Difficulty_Apollyon: { client.Stun(7.5, 0.8, TF_STUNFLAGS_SMALLBONK, client.index); - client.TakeDamage(true, _, _, 50.0, DMG_CLUB); + damage = 60.0; } } + if (client.Class == TFClass_Medic) + { + damage *= 1.5; + } + client.TakeDamage(true, _, _, damage, DMG_CLUB); } DoBeatBoxBeat("Stop moving!", true); g_BeatBoxMasterTime = CreateTimer(0.45, Timer_BeatBoxMasterTimer, _, TIMER_FLAG_NO_MAPCHANGE); diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 9b9d6ecf..4ee059a8 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -266,37 +266,113 @@ float EntityDistanceFromEntity(int ent1, int ent2) bool IsSpaceOccupiedIgnorePlayers(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitPlayersOrEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) + { + if (list.Get(i) == entity) + { + continue; + } + + check = true; + break; + } + if (check) + { + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitPlayersOrEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; + } + + delete list; return hit; } bool IsSpaceOccupiedIgnorePlayersAndEnts(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitCharactersOrEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) + { + if (list.Get(i) == entity) + { + continue; + } + + check = true; + break; + } + if (check) + { + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_VISIBLE, TraceRayDontHitCharactersOrEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; + } + + delete list; return hit; } bool IsSpaceOccupiedPlayer(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) + { + if (list.Get(i) == entity) + { + continue; + } + + check = true; + break; + } + if (check) + { + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; + } + + delete list; return hit; } bool IsSpaceOccupiedNPC(const float pos[3], const float mins[3], const float maxs[3],int entity=-1,int &ref=-1) { - Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_NPCSOLID, TraceRayDontHitEntity, entity); - bool hit = TR_DidHit(trace); - ref = TR_GetEntityIndex(trace); - delete trace; + bool check = false; + bool hit = false; + ArrayList list = new ArrayList(); + TR_EnumerateEntitiesHull(pos, pos, mins, maxs, PARTITION_SOLID_EDICTS, EnumerateEverything, list); + for (int i = 0; i < list.Length; i++) + { + if (list.Get(i) == entity) + { + continue; + } + + check = true; + break; + } + if (check) + { + Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_NPCSOLID, TraceRayDontHitEntity, entity); + hit = TR_DidHit(trace); + ref = TR_GetEntityIndex(trace); + delete trace; + } + + delete list; return hit; } @@ -725,13 +801,24 @@ void PrintToSourceTV(const char[] message) CPrintToChat(client, message); } } + +void ShowHealthRegen(int client, int amount, int weaponIndex = -1) +{ + Event event = CreateEvent("player_healonhit"); + if (event) + { + event.SetInt("amount", amount); + event.SetInt("entindex", client); + event.SetInt("weapon_def_index", weaponIndex); + event.Fire(); + } +} // ========================================================== // TF2-SPECIFIC FUNCTIONS // ========================================================== bool TF2_IsMiniCritBuffed(int client) { return (TF2_IsPlayerInCondition(client, TFCond_CritCola) - || TF2_IsPlayerInCondition(client, TFCond_CritHype) || TF2_IsPlayerInCondition(client, TFCond_Buffed) ); } @@ -1368,6 +1455,16 @@ bool TraceRayDontHitPlayersOrEntityEx(int entity, int mask, any data) return true; } +bool EnumerateEverything(int entIndex, ArrayList array) +{ + if (IsValidEntity(entIndex)) + { + array.Push(entIndex); + } + + return true; +} + bool EnumerateLivingPlayers(int entIndex, ArrayList players) { if (IsValidClient(entIndex) && IsPlayerAlive(entIndex) && !IsClientInGhostMode(entIndex)) @@ -1382,7 +1479,7 @@ bool EnumerateBreakableEntities(int entIndex, ArrayList array) { if (!IsValidEntity(entIndex)) { - return true; + return false; } char className[64]; @@ -1522,7 +1619,7 @@ int GetLocalGlobalDifficulty(int npcIndex = -1) SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(npcIndex); data = controller.GetProfileData(); SF2ChaserBossProfileData chaserData; - if (controller.Type == SF2BossType_Chaser) + if (data.Type == SF2BossType_Chaser) { chaserData = view_as(controller).GetProfileData(); } diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index ae91adfe..345992e4 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -155,23 +155,23 @@ static Action Timer_TrapThink(Handle timer, any entref) for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(i); - if (player.IsValid && (!player.IsAlive || + if (!player.IsValid) + { + continue; + } + + if (!player.IsAlive || player.IsInDeathCam || player.IsInGhostMode || player.HasEscaped || player.InCondition(view_as(130)) || - player.Team == TFTeam_Spectator)) + player.Team == TFTeam_Spectator) { continue; } if (!g_Enabled) { - if (!player.IsValid) - { - continue; - } - if (player.GetProp(Prop_Data, "m_iTeamNum") == controller.DefaultTeam) { continue; From ad5b8ef26275034ff4dd0f76b5f0995b5c56250c Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Thu, 22 Feb 2024 06:29:41 -0700 Subject: [PATCH 32/86] Alpha 2.61 --- addons/sourcemod/scripting/sf2.sp | 2 + addons/sourcemod/scripting/sf2/adminmenu.sp | 43 +++-- .../sourcemod/scripting/sf2/client/hints.sp | 161 ++++++++++++++++++ .../sourcemod/scripting/sf2/client/proxy.sp | 1 + .../sourcemod/scripting/sf2/client/sprint.sp | 13 +- .../sf2/entities/sf2_base_projectile.sp | 1 - .../scripting/sf2/extras/commands.sp | 1 + addons/sourcemod/scripting/sf2/menus.sp | 97 ++++++----- addons/sourcemod/scripting/sf2/npc.sp | 15 ++ addons/sourcemod/scripting/sf2/pvp.sp | 2 +- .../sourcemod/scripting/sf2/specialround.sp | 64 ++++++- addons/sourcemod/scripting/sf2/traps.sp | 2 +- addons/sourcemod/translations/sf2.phrases.txt | 12 +- 13 files changed, 330 insertions(+), 84 deletions(-) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 57b8e56f..95b9d5f3 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -674,6 +674,7 @@ PrivateForward g_OnChaserGetAttackActionPFwd; PrivateForward g_OnChaserGetCustomAttackPossibleStatePFwd; PrivateForward g_OnChaserUpdatePosturePFwd; PrivateForward g_OnDifficultyChangePFwd; +PrivateForward g_OnDifficultyVoteFinishedPFwd; PrivateForward g_OnRenevantTriggerWavePFwd; PrivateForward g_OnWallHaxDebugPFwd; @@ -4302,6 +4303,7 @@ void SetClientPlayState(int client, bool state, bool enablePlay = true, bool que ClientSetGhostModeState(client, false); PvP_SetPlayerPvPState(client, false, false, false); + PvE_SetPlayerPvEState(client, false, false); if (g_IsSpecialRound) { diff --git a/addons/sourcemod/scripting/sf2/adminmenu.sp b/addons/sourcemod/scripting/sf2/adminmenu.sp index 90211f03..857b11a9 100644 --- a/addons/sourcemod/scripting/sf2/adminmenu.sp +++ b/addons/sourcemod/scripting/sf2/adminmenu.sp @@ -13,35 +13,30 @@ void SetupAdminMenu() { g_OnAdminMenuCreateOptionsPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); /* Account for late loading */ - Handle topMenu = null; - if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) + Handle topMenuHndl = null; + if (LibraryExists("adminmenu") && ((topMenuHndl = GetAdminTopMenu()) != null)) { - OnAdminMenuReady(topMenu); - } -} - -public void OnAdminMenuReady(Handle topMenuHndl) -{ - TopMenu topMenu = TopMenu.FromHandle(topMenuHndl); + TopMenu topMenu = TopMenu.FromHandle(topMenuHndl); - if (topMenu == g_TopMenu) - { - return; - } + if (topMenu == g_TopMenu) + { + return; + } - g_TopMenu = topMenu; + g_TopMenu = topMenu; - TopMenuObject commands = topMenu.FindCategory("SF2Commands"); - if (commands == INVALID_TOPMENUOBJECT) - { - commands = topMenu.AddCategory("SF2Commands", AdminTopMenu_Main); - } + TopMenuObject commands = topMenu.FindCategory("SF2Commands"); + if (commands == INVALID_TOPMENUOBJECT) + { + commands = topMenu.AddCategory("SF2Commands", AdminTopMenu_Main); + } - if (commands != INVALID_TOPMENUOBJECT) - { - topMenu.AddItem("sf2_boss_admin_main", AdminTopMenu_BossMain, commands, "sm_sf2_add_boss", ADMFLAG_SLAY); - topMenu.AddItem("sf2_player_setplaystate", AdminTopMenu_PlayerSetPlayState, commands, "sm_sf2_setplaystate", ADMFLAG_SLAY); - topMenu.AddItem("sf2_player_force_proxy", AdminTopMenu_PlayerForceProxy, commands, "sm_sf2_force_proxy", ADMFLAG_SLAY); + if (commands != INVALID_TOPMENUOBJECT) + { + topMenu.AddItem("sf2_boss_admin_main", AdminTopMenu_BossMain, commands, "sm_sf2_add_boss", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_setplaystate", AdminTopMenu_PlayerSetPlayState, commands, "sm_sf2_setplaystate", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_force_proxy", AdminTopMenu_PlayerForceProxy, commands, "sm_sf2_force_proxy", ADMFLAG_SLAY); + } } } diff --git a/addons/sourcemod/scripting/sf2/client/hints.sp b/addons/sourcemod/scripting/sf2/client/hints.sp index 8de3399d..8216e32b 100644 --- a/addons/sourcemod/scripting/sf2/client/hints.sp +++ b/addons/sourcemod/scripting/sf2/client/hints.sp @@ -4,11 +4,17 @@ #define _sf2_client_hints_included bool g_PlayerHints[MAXTF2PLAYERS][PlayerHint_MaxNum]; +static Handle g_ShowHintTimer[MAXPLAYERS + 1]; void SetupHints() { g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerChangePlayStatePFwd.AddFunction(null, OnPlayerChangePlayState); + g_OnDifficultyVoteFinishedPFwd.AddFunction(null, OnDifficultyVoteFinished); } static void OnPutInServer(SF2_BasePlayer client) @@ -19,6 +25,18 @@ static void OnPutInServer(SF2_BasePlayer client) } ClientResetHints(client.index); + + g_ShowHintTimer[client.index] = null; +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClientResetHints(client.index); } static void OnPlayerSpawn(SF2_BasePlayer client) @@ -31,6 +49,139 @@ static void OnPlayerSpawn(SF2_BasePlayer client) ClientResetHints(client.index); } +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClientResetHints(client.index); + } +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClientResetHints(client.index); +} + +static void OnPlayerChangePlayState(SF2_BasePlayer client, bool state, bool queue) +{ + if (!state) + { + g_ShowHintTimer[client.index] = null; + } +} + +static Action OnDifficultyVoteFinished(int& difficulty) +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer player = SF2_BasePlayer(i); + if (!player.IsValid || !g_PlayerPreferences[player.index].PlayerPreference_ShowHints) + { + continue; + } + + if (player.IsEliminated) + { + if (!player.HasHint(PlayerHint_MainMenu)) + { + player.ShowHint(PlayerHint_MainMenu); + } + } + else + { + if (!player.HasHint(PlayerHint_Flashlight)) + { + player.ShowHint(PlayerHint_Flashlight); + + if (!player.HasHint(PlayerHint_Sprint)) + { + g_ShowHintTimer[player.index] = CreateTimer(10.0, Timer_ShowSprintHint, player.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + else if (!player.HasHint(PlayerHint_Sprint)) + { + player.ShowHint(PlayerHint_Sprint); + + if (!player.HasHint(PlayerHint_Noises)) + { + g_ShowHintTimer[player.index] = CreateTimer(10.0, Timer_ShowNoisesHint, player.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + else if (!player.HasHint(PlayerHint_Noises)) + { + player.ShowHint(PlayerHint_Noises); + } + } + } + return Plugin_Continue; +} + +static Action Timer_ShowSprintHint(Handle timer, any userId) +{ + int clientIndex = GetClientOfUserId(userId); + if (clientIndex == 0) + { + return Plugin_Stop; + } + + if (timer != g_ShowHintTimer[clientIndex]) + { + return Plugin_Stop; + } + + SF2_BasePlayer player = SF2_BasePlayer(clientIndex); + + if (player.IsEliminated || player.HasEscaped || !g_PlayerPreferences[player.index].PlayerPreference_ShowHints) + { + return Plugin_Stop; + } + + if (!player.HasHint(PlayerHint_Sprint)) + { + player.ShowHint(PlayerHint_Sprint); + } + + if (!player.HasHint(PlayerHint_Noises)) + { + g_ShowHintTimer[player.index] = CreateTimer(10.0, Timer_ShowNoisesHint, player.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + + return Plugin_Stop; +} + +static Action Timer_ShowNoisesHint(Handle timer, any userId) +{ + int clientIndex = GetClientOfUserId(userId); + if (clientIndex == 0) + { + return Plugin_Stop; + } + + if (timer != g_ShowHintTimer[clientIndex]) + { + return Plugin_Stop; + } + + SF2_BasePlayer player = SF2_BasePlayer(clientIndex); + + if (player.IsEliminated || player.HasEscaped || !g_PlayerPreferences[player.index].PlayerPreference_ShowHints) + { + return Plugin_Stop; + } + + if (!player.HasHint(PlayerHint_Noises)) + { + player.ShowHint(PlayerHint_Noises); + } + + return Plugin_Stop; +} + // Hint data. enum { @@ -39,6 +190,8 @@ enum PlayerHint_MainMenu, PlayerHint_Blink, PlayerHint_Trap, + PlayerHint_Noises, + PlayerHint_Crouch, PlayerHint_MaxNum }; @@ -90,6 +243,14 @@ void ClientShowHint(int client, int hint) { PrintHintText(client, "%T", "SF2 Hint Trap", client); } + case PlayerHint_Noises: + { + PrintHintText(client, "%T", "SF2 Hint Noises", client); + } + case PlayerHint_Crouch: + { + PrintHintText(client, "%T", "SF2 Hint Crouch", client); + } } } diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 46ced711..14367353 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -545,6 +545,7 @@ void ClientEnableProxy(int client, int bossIndex, const float pos[3], int spawnP ChangeClientTeamNoSuicide(client, TFTeam_Blue); PvP_SetPlayerPvPState(client, false, true, false); + PvE_SetPlayerPvEState(client, false, false); TF2_RespawnPlayer(client); // Speed recalculation. Props to the creators of FF2/VSH for this snippet. diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp index bfd128ed..fd466359 100644 --- a/addons/sourcemod/scripting/sf2/client/sprint.sp +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -492,6 +492,7 @@ static void Hook_SpeedThink(int client) int classToInt = view_as(class); bool inDanger = false; + bool inChase = false; if (!inDanger) { @@ -521,6 +522,10 @@ static void Hook_SpeedThink(int client) ((bossTarget.IsValid() && (bossTarget.index == client || ClientGetDistanceFromEntity(client, bossTarget.index) < SquareFloat(512.0))) || NPCGetDistanceFromEntity(i, client) < SquareFloat(512.0) || PlayerCanSeeSlender(client, i, false))) { inDanger = true; + if (bossTarget.index == client) + { + inChase = true; + } ClientSetScareBoostEndTime(player.index, GetGameTime() + 5.0); // Induce client stress levels. @@ -774,9 +779,13 @@ static void Hook_SpeedThink(int client) } } - if (inDanger) + if (g_PlayerPreferences[player.index].PlayerPreference_ShowHints && inDanger) { - if (!player.HasHint(PlayerHint_Sprint)) + if (!inChase && !player.HasHint(PlayerHint_Crouch)) + { + player.ShowHint(PlayerHint_Crouch); + } + else if (inChase && !player.HasHint(PlayerHint_Sprint)) { player.ShowHint(PlayerHint_Sprint); } diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp index 0325552e..247e20e4 100644 --- a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -29,7 +29,6 @@ methodmap SF2_ProjectileBase < CBaseAnimating .DefineStringField("m_ImpactSound") .DefineBoolField("m_AttackWaiters") .EndDataMapDesc(); - g_Factory.IsAbstract = true; g_Factory.Install(); g_OnMapStartPFwd.AddFunction(null, MapStart); diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 5f42d759..431063b2 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -431,6 +431,7 @@ public void OnPluginStart() g_OnChaserGetCustomAttackPossibleStatePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); g_OnChaserUpdatePosturePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); g_OnDifficultyChangePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); + g_OnDifficultyVoteFinishedPFwd = new PrivateForward(ET_Hook, Param_CellByRef); g_OnRenevantTriggerWavePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_OnWallHaxDebugPFwd = new PrivateForward(ET_Ignore); diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 3e4e540f..c5cf799d 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -660,32 +660,11 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par char info[64], display[256], color[32], nightmareDisplay[256]; menu.GetItem(param1, info, sizeof(info), _, display, sizeof(display)); + int difficulty = Difficulty_Normal; + bool rng = false, change = false; - if (IsSpecialRoundRunning() && (SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) || SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_WALLHAX) || SF_SpecialRound(SPECIALROUND_ESCAPETICKETS))) - { - g_DifficultyConVar.SetInt(Difficulty_Insane); - } - else if (!SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) && - !SF_SpecialRound(SPECIALROUND_2DOUBLE) && !SF_SpecialRound(SPECIALROUND_ESCAPETICKETS) && !SF_SpecialRound(SPECIALROUND_NOGRACE) && - !SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) && !SF_SpecialRound(SPECIALROUND_WALLHAX) && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && - (GetRandomInt(1, 200) <= 2 || playersCalledForNightmare)) - { - if (GetRandomInt(1, 20) <= 1) - { - rng = true; - g_DifficultyConVar.SetInt(Difficulty_Apollyon); - } - else - { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); - } - } - else if (IsSpecialRoundRunning() && (SF_SpecialRound(SPECIALROUND_NOGRACE))) - { - g_DifficultyConVar.SetInt(Difficulty_Hard); - } - else if (info[0] != '\0') + if (info[0] != '\0') { if (g_HighDifficultyPercentConVar.FloatValue > 0.0 && (strcmp(info, "4") == 0 || strcmp(info, "5") == 0)) { @@ -727,22 +706,20 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par } else { - char winner[2] = "1"; + int winner = Difficulty_Normal; if (hrd > ins && hrd > nrm) { - winner = "2"; + winner = Difficulty_Hard; } else if (ins > hrd && ins > nrm) { - winner = "3"; + winner = Difficulty_Insane; } - strcopy(info, sizeof(info), winner); + difficulty = winner; change = true; } } } - - g_DifficultyConVar.SetString(info); } else { @@ -763,85 +740,109 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par { case 5: { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } case 4: { if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } case 3: { if (insane) { - g_DifficultyConVar.SetInt(Difficulty_Insane); + difficulty = Difficulty_Insane; } else if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } case 2: { if (hard) { - g_DifficultyConVar.SetInt(Difficulty_Hard); + difficulty = Difficulty_Hard; } else if (insane) { - g_DifficultyConVar.SetInt(Difficulty_Insane); + difficulty = Difficulty_Insane; } else if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } case 1: { if (normal) { - g_DifficultyConVar.SetInt(Difficulty_Normal); + difficulty = Difficulty_Normal; } else if (hard) { - g_DifficultyConVar.SetInt(Difficulty_Hard); + difficulty = Difficulty_Hard; } else if (insane) { - g_DifficultyConVar.SetInt(Difficulty_Insane); + difficulty = Difficulty_Insane; } else if (nightmare) { - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + difficulty = Difficulty_Nightmare; } else { - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + difficulty = Difficulty_Apollyon; } } } } else { - g_DifficultyConVar.SetInt(GetRandomInt(Difficulty_Normal, Difficulty_Insane)); + difficulty = GetRandomInt(Difficulty_Normal, Difficulty_Insane); + } + } + + Action fwdAction = Plugin_Continue; + int difficulty2 = difficulty; + + Call_StartForward(g_OnDifficultyVoteFinishedPFwd); + Call_PushCellRef(difficulty2); + Call_Finish(fwdAction); + + if (fwdAction == Plugin_Changed) + { + difficulty = difficulty2; + } + + if (GetRandomInt(1, 200) <= 2 || playersCalledForNightmare) + { + if (GetRandomInt(1, 20) <= 1) + { + rng = true; + difficulty = Difficulty_Apollyon; + } + else + { + difficulty = Difficulty_Nightmare; } } - int difficulty = g_DifficultyConVar.IntValue; switch (difficulty) { case Difficulty_Hard: @@ -924,6 +925,8 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par } } + g_DifficultyConVar.SetInt(difficulty); + if (!change) { CPrintToChatAll("%t %s%s", "SF2 Difficulty Vote Finished", color, display); diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 4e10bdbe..029ef362 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -196,6 +196,7 @@ void NPCInitialize() { g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); g_OnEntityTeleportedPFwd.AddFunction(null, EntityTeleported); + g_OnPlayerLookAtBossPFwd.AddFunction(null, OnPlayerLookAtBoss); NPCChaserInitialize(); SetupNPCGlows(); @@ -348,6 +349,20 @@ static void EntityTeleported(CBaseEntity teleporter, CBaseEntity activator) } } +static void OnPlayerLookAtBoss(SF2_BasePlayer client, SF2NPC_BaseNPC boss) +{ + switch (boss.Type) + { + case SF2BossType_Statue: + { + if (g_PlayerPreferences[client.index].PlayerPreference_ShowHints && !client.HasHint(PlayerHint_Blink)) + { + client.ShowHint(PlayerHint_Blink); + } + } + } +} + void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index e09e023c..b3086438 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -430,7 +430,7 @@ static void Hook_PvPProjectileSpawnPost(int ent) if (IsValidClient(ownerEntity)) { - if (IsClientInPvP(ownerEntity)) + if (IsRoundInWarmup() || IsClientInPvP(ownerEntity)) { static const char fixWeaponNotCollidingWithTeammates[][] = { diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index 118c83b4..f958c1d8 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -32,9 +32,12 @@ static Handle g_BeatBoxMasterTime = null; static Handle g_BeatBoxMusicTimer = null; static int g_BeatBoxCueIndex = 0; +static int g_OverrideDifficulty = -1; + void SetupSpecialRounds() { g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + g_OnRoundStartPFwd.AddFunction(null, OnRoundStart); g_OnRoundEndPFwd.AddFunction(null, OnRoundEnd); g_OnAdminMenuCreateOptionsPFwd.AddFunction(null, OnAdminMenuCreateOptions); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); @@ -42,6 +45,7 @@ void SetupSpecialRounds() g_OnPlayerTeamPFwd.AddFunction(null, OnPlayerTeam); g_OnPlayerClassPFwd.AddFunction(null, OnPlayerClass); g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyVoteFinished); } static void OnGamemodeStart() @@ -256,6 +260,11 @@ bool IsBeatBoxBeating(int index = 0) return g_BeatBoxCueIndex > index && SF_SpecialRound(SPECIALROUND_BEATBOX); } +static void OnRoundStart() +{ + g_OverrideDifficulty = -1; +} + static void OnRoundEnd() { StopBeatBoxMusicForAll(); @@ -388,6 +397,17 @@ static void OnPlayerEscape(SF2_BasePlayer client) } } +static Action OnDifficultyVoteFinished(int& difficulty) +{ + if (g_OverrideDifficulty != -1 && difficulty < g_OverrideDifficulty) + { + difficulty = g_OverrideDifficulty; + return Plugin_Changed; + } + + return Plugin_Continue; +} + static void ChangeThanatophobiaClass(SF2_BasePlayer client) { TFClassType class = client.Class; @@ -864,7 +884,7 @@ static ArrayList SpecialEnabledList() } } - if (GetActivePlayerCount() <= g_MaxPlayersConVar.IntValue * 2 && g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) + if (GetActivePlayerCount() <= g_MaxPlayersConVar.IntValue * 2 && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_DOUBLEMAXPLAYERS, enabledRounds); } @@ -872,10 +892,7 @@ static ArrayList SpecialEnabledList() { if (GetSelectableBossProfileList().Length > 0 && GetActivePlayerCount() <= g_MaxPlayersConVar.IntValue * 2) { - if (g_DifficultyConVar.IntValue < 3) - { - AddSpecialRoundToList(SPECIALROUND_2DOUBLE, enabledRounds); - } + AddSpecialRoundToList(SPECIALROUND_2DOUBLE, enabledRounds); } } else @@ -1006,7 +1023,7 @@ static ArrayList SpecialEnabledList() { AddSpecialRoundToList(SPECIALROUND_BOSSROULETTE, enabledRounds); } - if (!SF_SpecialRound(SPECIALROUND_WALLHAX) && !SF_IsRaidMap() && !SF_IsBoxingMap() && g_DifficultyConVar.IntValue < 3) + if (!SF_SpecialRound(SPECIALROUND_WALLHAX) && !SF_IsRaidMap() && !SF_IsBoxingMap()) { AddSpecialRoundToList(SPECIALROUND_WALLHAX, enabledRounds); } @@ -1079,6 +1096,10 @@ void SpecialRoundStart() { g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 2) + { + g_OverrideDifficulty = 2; + } char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); @@ -1217,6 +1238,10 @@ void SpecialRoundStart() { g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; + } SF_AddSpecialRound(SPECIALROUND_INSANEDIFFICULTY); } case SPECIALROUND_NOGRACE: @@ -1225,6 +1250,10 @@ void SpecialRoundStart() { g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 2) + { + g_OverrideDifficulty = 2; + } if (g_RoundGraceTimer != null) { TriggerTimer(g_RoundGraceTimer); @@ -1237,6 +1266,10 @@ void SpecialRoundStart() { g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; + } if (g_RoundGraceTimer != null) { TriggerTimer(g_RoundGraceTimer); @@ -1250,6 +1283,10 @@ void SpecialRoundStart() { g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; + } char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); ArrayList selectableBoxingBosses = GetSelectableBossProfileList().Clone(); @@ -1285,6 +1322,10 @@ void SpecialRoundStart() { g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; + } SF_AddSpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS); } case SPECIALROUND_CLASSSCRAMBLE: @@ -1478,6 +1519,11 @@ void SpecialRoundStart() } } } + + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < randomDifficulty) + { + g_OverrideDifficulty = randomDifficulty; + } } delete selectableBosses; SF_AddSpecialRound(SPECIALROUND_MODBOSSES); @@ -1538,7 +1584,11 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Hardcore. + g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + } + if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) + { + g_OverrideDifficulty = 3; } SF_AddSpecialRound(SPECIALROUND_WALLHAX); diff --git a/addons/sourcemod/scripting/sf2/traps.sp b/addons/sourcemod/scripting/sf2/traps.sp index 345992e4..56f246da 100644 --- a/addons/sourcemod/scripting/sf2/traps.sp +++ b/addons/sourcemod/scripting/sf2/traps.sp @@ -277,7 +277,7 @@ static void OnTrapOpenComplete(const char[] output, int caller, int activator, f } } -static Action Hook_TrapOnTakeDamage(int trapEntity,int &attacker,int &inflictor,float &damage,int &damagetype,int &weapon, float damageForce[3], float damagePosition[3],int damagecustom) +static Action Hook_TrapOnTakeDamage(int trapEntity, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { if (!g_Enabled) { diff --git a/addons/sourcemod/translations/sf2.phrases.txt b/addons/sourcemod/translations/sf2.phrases.txt index 1dca3361..3c0617d3 100644 --- a/addons/sourcemod/translations/sf2.phrases.txt +++ b/addons/sourcemod/translations/sf2.phrases.txt @@ -985,7 +985,7 @@ "SF2 Hint Sprint" { - "en" "Sprint - HOLD Special Attack" + "en" "Sprint - HOLD Special Attack (Middle Mouse Button) or %%sprint%%" "ru" "Бег - Удерж. кнопку \"Специальная атака\"" "fi" "Juokse - Pidä Pohjassa Erityis Hyökkäystäsi" "chi" "冲刺 - 按住特殊攻击键" @@ -1033,6 +1033,16 @@ "ko" "함정 벗어나기 - 점프 키를 연타하세요." } + "SF2 Hint Noises" + { + "en" "WARNING - Voice commands, weapon sounds, flashlight sounds, and footstep sounds can alert bosses." + } + + "SF2 Hint Crouch" + { + "en" "Crouch - %%duck%%\nSneak around quietly" + } + "SF2 Admin Menu Title" { "en" "SF2 Commands" From 7edd970992711c8820a4ff47255e0bf399b3af68 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Thu, 22 Feb 2024 07:18:18 -0700 Subject: [PATCH 33/86] Wait let me try that again --- addons/sourcemod/scripting/sf2/client/hints.sp | 2 +- addons/sourcemod/scripting/sf2/extras/commands.sp | 2 +- addons/sourcemod/scripting/sf2/menus.sp | 13 +++++++++++++ addons/sourcemod/scripting/sf2/specialround.sp | 6 +++--- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/client/hints.sp b/addons/sourcemod/scripting/sf2/client/hints.sp index 8216e32b..71202ee4 100644 --- a/addons/sourcemod/scripting/sf2/client/hints.sp +++ b/addons/sourcemod/scripting/sf2/client/hints.sp @@ -75,7 +75,7 @@ static void OnPlayerChangePlayState(SF2_BasePlayer client, bool state, bool queu } } -static Action OnDifficultyVoteFinished(int& difficulty) +static Action OnDifficultyVoteFinished(int difficulty, int& newDifficulty) { for (int i = 1; i <= MaxClients; i++) { diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 431063b2..6e518d0d 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -431,7 +431,7 @@ public void OnPluginStart() g_OnChaserGetCustomAttackPossibleStatePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); g_OnChaserUpdatePosturePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_String, Param_Cell); g_OnDifficultyChangePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - g_OnDifficultyVoteFinishedPFwd = new PrivateForward(ET_Hook, Param_CellByRef); + g_OnDifficultyVoteFinishedPFwd = new PrivateForward(ET_Hook, Param_Cell, Param_CellByRef); g_OnRenevantTriggerWavePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_OnWallHaxDebugPFwd = new PrivateForward(ET_Ignore); diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index c5cf799d..0dddc347 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -641,6 +641,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par int value = StringToInt(option); g_Voters.Set(index, value, 1); } + if (action == MenuAction_VoteEnd && !SF_SpecialRound(SPECIALROUND_MODBOSSES) && !g_RestartSessionConVar.BoolValue) { int clientInGame = 0, clientCallingForNightmare = 0; @@ -821,7 +822,9 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par Action fwdAction = Plugin_Continue; int difficulty2 = difficulty; + // This cell ref shit does not work, why? Call_StartForward(g_OnDifficultyVoteFinishedPFwd); + Call_PushCell(difficulty); Call_PushCellRef(difficulty2); Call_Finish(fwdAction); @@ -830,6 +833,16 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par difficulty = difficulty2; } + if ((SF_SpecialRound(SPECIALROUND_SILENTSLENDER) || SF_SpecialRound(SPECIALROUND_NOGRACE)) && difficulty < Difficulty_Hard) + { + difficulty = Difficulty_Hard; + } + + if ((SF_SpecialRound(SPECIALROUND_INSANEDIFFICULTY) || SF_SpecialRound(SPECIALROUND_ESCAPETICKETS) || SF_SpecialRound(SPECIALROUND_2DOUBLE) || SF_SpecialRound(SPECIALROUND_DOUBLEMAXPLAYERS) || SF_SpecialRound(SPECIALROUND_WALLHAX)) && difficulty < Difficulty_Insane) + { + difficulty = Difficulty_Insane; + } + if (GetRandomInt(1, 200) <= 2 || playersCalledForNightmare) { if (GetRandomInt(1, 20) <= 1) diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index f958c1d8..e45ad534 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -397,11 +397,11 @@ static void OnPlayerEscape(SF2_BasePlayer client) } } -static Action OnDifficultyVoteFinished(int& difficulty) +static Action OnDifficultyVoteFinished(int difficulty, int& newDifficulty) { - if (g_OverrideDifficulty != -1 && difficulty < g_OverrideDifficulty) + if (g_OverrideDifficulty > -1 && difficulty < g_OverrideDifficulty) { - difficulty = g_OverrideDifficulty; + newDifficulty = g_OverrideDifficulty; return Plugin_Changed; } From 479db9ce34995ee55a78fa714ef720f3dcd4ad07 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Thu, 22 Feb 2024 16:47:09 -0700 Subject: [PATCH 34/86] Alpha 2.62 --- addons/sourcemod/scripting/sf2/adminmenu.sp | 43 ++++++++++++--------- addons/sourcemod/scripting/sf2/menus.sp | 2 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/adminmenu.sp b/addons/sourcemod/scripting/sf2/adminmenu.sp index 857b11a9..90211f03 100644 --- a/addons/sourcemod/scripting/sf2/adminmenu.sp +++ b/addons/sourcemod/scripting/sf2/adminmenu.sp @@ -13,30 +13,35 @@ void SetupAdminMenu() { g_OnAdminMenuCreateOptionsPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); /* Account for late loading */ - Handle topMenuHndl = null; - if (LibraryExists("adminmenu") && ((topMenuHndl = GetAdminTopMenu()) != null)) + Handle topMenu = null; + if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) { - TopMenu topMenu = TopMenu.FromHandle(topMenuHndl); + OnAdminMenuReady(topMenu); + } +} - if (topMenu == g_TopMenu) - { - return; - } +public void OnAdminMenuReady(Handle topMenuHndl) +{ + TopMenu topMenu = TopMenu.FromHandle(topMenuHndl); - g_TopMenu = topMenu; + if (topMenu == g_TopMenu) + { + return; + } - TopMenuObject commands = topMenu.FindCategory("SF2Commands"); - if (commands == INVALID_TOPMENUOBJECT) - { - commands = topMenu.AddCategory("SF2Commands", AdminTopMenu_Main); - } + g_TopMenu = topMenu; - if (commands != INVALID_TOPMENUOBJECT) - { - topMenu.AddItem("sf2_boss_admin_main", AdminTopMenu_BossMain, commands, "sm_sf2_add_boss", ADMFLAG_SLAY); - topMenu.AddItem("sf2_player_setplaystate", AdminTopMenu_PlayerSetPlayState, commands, "sm_sf2_setplaystate", ADMFLAG_SLAY); - topMenu.AddItem("sf2_player_force_proxy", AdminTopMenu_PlayerForceProxy, commands, "sm_sf2_force_proxy", ADMFLAG_SLAY); - } + TopMenuObject commands = topMenu.FindCategory("SF2Commands"); + if (commands == INVALID_TOPMENUOBJECT) + { + commands = topMenu.AddCategory("SF2Commands", AdminTopMenu_Main); + } + + if (commands != INVALID_TOPMENUOBJECT) + { + topMenu.AddItem("sf2_boss_admin_main", AdminTopMenu_BossMain, commands, "sm_sf2_add_boss", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_setplaystate", AdminTopMenu_PlayerSetPlayState, commands, "sm_sf2_setplaystate", ADMFLAG_SLAY); + topMenu.AddItem("sf2_player_force_proxy", AdminTopMenu_PlayerForceProxy, commands, "sm_sf2_force_proxy", ADMFLAG_SLAY); } } diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 0dddc347..7d07bd13 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -661,7 +661,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par char info[64], display[256], color[32], nightmareDisplay[256]; menu.GetItem(param1, info, sizeof(info), _, display, sizeof(display)); - int difficulty = Difficulty_Normal; + int difficulty = StringToInt(info); bool rng = false, change = false; From 8dea35d795c5fa7a9512468c3d35601f04dab197 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Fri, 23 Feb 2024 03:45:53 -0700 Subject: [PATCH 35/86] Alpha 2.63 --- addons/sourcemod/scripting/include/sf2.inc | 37 ++++++++++++++++++ addons/sourcemod/scripting/sf2/npc.sp | 10 ++--- .../sf2/npc/entities/chaser/entity.sp | 15 ++++--- .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 5 ++- .../sf2/profiles/profiles_boss_functions.sp | 4 +- addons/sourcemod/scripting/sf2/pve.sp | 39 +++++++++++++++++++ addons/sourcemod/scripting/sf2/pvp.sp | 18 +++++++++ 7 files changed, 114 insertions(+), 14 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 18746f15..8aff5416 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -738,9 +738,39 @@ forward void SF2_OnClientDamagedByBoss(int client, int bossIndex, int inflictor, /** * Called when a player spawns as a proxy. + * + * @param client The player who spawned as a proxy */ forward void SF2_OnClientSpawnedAsProxy(int client); +/** + * Called when a player enters a PvP arena + * + * @param client The player who entered the PvP arena + */ +forward void SF2_OnClientEnterPvP(int client); + +/** + * Called when a player exits a PvP arena + * + * @param client The player who exits the PvP arena + */ +forward void SF2_OnClientExitPvP(int client); + +/** + * Called when a player enters a PvE arena + * + * @param client The player who entered the PvE arena + */ +forward void SF2_OnClientEnterPvE(int client); + +/** + * Called when a player exits a PvE arena + * + * @param client The player who exits the PvE arena + */ +forward void SF2_OnClientExitPvE(int client); + /** * Called whenever the Renevant gamemode triggers a new wave. * @@ -3443,6 +3473,13 @@ native void SF2_AddPvEBoss(int bossEnt); */ native void SF2_KillPvEBoss(int bossEnt); +/** + * Returns the array list of the current active bosses, this list only contains entity references. + * + * @return List of active PvE bosses or null if no bosses are active. + */ +native ArrayList SF2_GetActivePvEBosses(); + /** * Returns if current map is a survival map or not. * diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index 029ef362..fe8bf3e9 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -1786,7 +1786,7 @@ bool SlenderCanRemove(int bossIndex) return true; } -bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true) +bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true, bool showPvEMessage = true) { if (!IsProfileValid(profile)) { @@ -2040,7 +2040,7 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF } else { - if (profileData.IsPvEBoss && profileData.PvESpawnMessagesArray != null && profileData.PvESpawnMessagesArray.Length > 0) + if (profileData.IsPvEBoss && showPvEMessage && profileData.PvESpawnMessagesArray != null && profileData.PvESpawnMessagesArray.Length > 0) { char prefix[PLATFORM_MAX_PATH], message[PLATFORM_MAX_PATH]; strcopy(prefix, sizeof(prefix), profileData.PvESpawnMessagePrefix); @@ -2319,7 +2319,7 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF companionInfo.Bosses.GetString(i, compProfile, sizeof(compProfile)); if (IsProfileValid(compProfile)) { - SF2NPC_BaseNPC npcCompanion = AddProfile(compProfile, _, _, false, false); + SF2NPC_BaseNPC npcCompanion = AddProfile(compProfile, _, _, false, false, false); if (npcCompanion.IsValid()) { npcCompanion.CompanionMaster = npc; @@ -2359,7 +2359,7 @@ bool SelectProfile(SF2NPC_BaseNPC npc, const char[] profile, int additionalBossF return true; } -SF2NPC_BaseNPC AddProfile(const char[] name, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true) +SF2NPC_BaseNPC AddProfile(const char[] name, int additionalBossFlags = 0, SF2NPC_BaseNPC npcCopyMaster = SF2_INVALID_NPC, bool spawnCompanions = true, bool playSpawnSound = true, bool showPvEMessage = true) { if (SF_IsRenevantMap() && GetRoundState() != SF2RoundState_Escape) { @@ -2370,7 +2370,7 @@ SF2NPC_BaseNPC AddProfile(const char[] name, int additionalBossFlags = 0, SF2NPC SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); if (!npc.IsValid()) { - if (SelectProfile(npc, name, additionalBossFlags, npcCopyMaster, spawnCompanions, playSpawnSound)) + if (SelectProfile(npc, name, additionalBossFlags, npcCopyMaster, spawnCompanions, playSpawnSound, showPvEMessage)) { return npc; } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 42aa7adb..93bd85d8 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -3691,15 +3691,16 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float case 228, 1085: // Black Box { int health = player.Health; + int maxHealth = player.GetProp(Prop_Data, "m_iMaxHealth"); float regen = FloatClamp(damage / 90.0, 0.0, 1.0); int newHealth = health + RoundToNearest(20.0 * regen); - if (newHealth <= player.GetProp(Prop_Data, "m_iMaxHealth")) + if (newHealth <= maxHealth) { SetEntityHealth(player.index, newHealth); } else { - SetEntityHealth(player.index, player.GetProp(Prop_Data, "m_iMaxHealth")); + SetEntityHealth(player.index, maxHealth); } ShowHealthRegen(player.index, RoundToNearest(20.0 * regen), index); } @@ -3763,14 +3764,15 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float { ShowHealthRegen(player.index, 3, index); int health = player.Health; + int maxHealth = player.GetProp(Prop_Data, "m_iMaxHealth"); int newHealth = health + 3; - if (newHealth <= player.GetProp(Prop_Data, "m_iMaxHealth")) + if (newHealth <= maxHealth) { SetEntityHealth(player.index, newHealth); } else { - SetEntityHealth(player.index, player.GetProp(Prop_Data, "m_iMaxHealth")); + SetEntityHealth(player.index, maxHealth); } } } @@ -3820,15 +3822,16 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float if (damage > 0.0 && player.IsValid && player.InCondition(TFCond_RegenBuffed) && (SF_IsBoxingMap() || data.IsPvEBoss)) { int health = player.Health; + int maxHealth = player.GetProp(Prop_Data, "m_iMaxHealth"); float mult = damage * 0.475; int newHealth = health + RoundToCeil(mult); - if (newHealth <= player.GetProp(Prop_Data, "m_iMaxHealth")) + if (newHealth <= maxHealth) { SetEntityHealth(player.index, newHealth); } else { - SetEntityHealth(player.index, player.GetProp(Prop_Data, "m_iMaxHealth")); + SetEntityHealth(player.index, maxHealth); } ShowHealthRegen(player.index, RoundToCeil(mult)); } diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index 4b1ef8ca..a797c344 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -404,8 +404,11 @@ bool IsTargetValidForSlenderEx(CBaseEntity target, int bossIndex, bool includeEl continue; } + SF2BossProfileData data; + data = view_as(npc).GetProfileData(); + int state = chaser.State; - if (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) + if (!data.IsPvEBoss && (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN)) { return false; } diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 6adbef68..f45f73fd 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -66,7 +66,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu skip = false; } - if (kv.GetNum("is_pve", false) != 0) + if (kv.GetNum("is_pve", false) != 0 && kv.GetNum("pve_selectable", 1) != 0) { skip = false; } @@ -76,7 +76,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu skip = false; } - if (kv.JumpToKey("pve")) + if (kv.JumpToKey("pve") && kv.GetNum("selectable", 1) != 0) { kv.GoBack(); skip = false; diff --git a/addons/sourcemod/scripting/sf2/pve.sp b/addons/sourcemod/scripting/sf2/pve.sp index 87c081da..00ef2e2b 100644 --- a/addons/sourcemod/scripting/sf2/pve.sp +++ b/addons/sourcemod/scripting/sf2/pve.sp @@ -28,6 +28,8 @@ static float g_MusicLoopTime[MAXTF2PLAYERS][MAX_MUSICS]; static char g_CustomMusicOverride[PLATFORM_MAX_PATH]; static GlobalForward g_OnSelectedPvEBoss; +static GlobalForward g_OnPlayerEnterPvE; +static GlobalForward g_OnPlayerExitPvE; static ArrayList g_PvETriggers; @@ -585,6 +587,16 @@ void PvE_SetPlayerPvEState(int client, bool status, bool regenerate = true) player.ChangeCondition(TFCond_UberchargedOnTakeDamage, true); player.ChangeCondition(TFCond_Taunting, true); player.ChangeCondition(TFCond_UberchargedCanteen, _, 1.0); + + Call_StartForward(g_OnPlayerEnterPvE); + Call_PushCell(player.index); + Call_Finish(); + } + else + { + Call_StartForward(g_OnPlayerExitPvE); + Call_PushCell(player.index); + Call_Finish(); } if (regenerate) @@ -887,6 +899,25 @@ static void SpawnPvEBoss(const char[] override = "") { testNPC.Spawn(spawnPos); g_ActiveBosses.Push(EntIndexToEntRef(testNPC.EntIndex)); + + SF2BossProfileData tempData; + tempData = testNPC.GetProfileData(); + + if (tempData.CopiesInfo.Enabled[1]) + { + char tempProfile[SF2_MAX_PROFILE_NAME_LENGTH]; + testNPC.GetProfile(tempProfile, sizeof(tempProfile)); + for (int i2 = 0; i2 < tempData.CopiesInfo.MaxCopies[1]; i2++) + { + SF2NPC_BaseNPC copy = AddProfile(tempProfile, _, testNPC); + if (!copy.IsValid()) + { + continue; + } + copy.Spawn(spawnPos); + g_ActiveBosses.Push(EntIndexToEntRef(copy.EntIndex)); + } + } } } @@ -1303,8 +1334,11 @@ void PvE_InitializeAPI() CreateNative("SF2_SetOverridePvEMusic", Native_SetOverridePvEMusic); CreateNative("SF2_UnregisterPvEBoss", Native_UnregisterPvEBoss); g_OnSelectedPvEBoss = new GlobalForward("SF2_OnSelectedPvEBoss", ET_Ignore, Param_String, Param_Array, Param_Array); + g_OnPlayerEnterPvE = new GlobalForward("SF2_OnClientEnterPvE", ET_Ignore, Param_Cell); + g_OnPlayerExitPvE = new GlobalForward("SF2_OnClientExitPvE", ET_Ignore, Param_Cell); CreateNative("SF2_AddPvEBoss", Native_AddPvEBoss); CreateNative("SF2_KillPvEBoss", Native_KillPvEBoss); + CreateNative("SF2_GetActivePvEBosses", Native_GetActivePvEBosses); } static any Native_IsClientInPvE(Handle plugin, int numParams) @@ -1346,4 +1380,9 @@ static any Native_KillPvEBoss(Handle plugin, int numParams) { KillPvEBoss(GetNativeCell(1)); return 0; +} + +static any Native_GetActivePvEBosses(Handle plugin, int numParams) +{ + return GetActivePvEBosses(); } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index b3086438..916f929a 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -54,6 +54,9 @@ static float g_TimeLastTrace; static ArrayList g_PvPBallsOfFire; +static GlobalForward g_OnPlayerEnterPvP; +static GlobalForward g_OnPlayerExitPvP; + enum struct PvPProjectile_BallOfFire { int EntIndex; @@ -784,6 +787,19 @@ void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles = tru PvP_RemovePlayerProjectiles(player.index); } + if (status) + { + Call_StartForward(g_OnPlayerEnterPvP); + Call_PushCell(player.index); + Call_Finish(); + } + else + { + Call_StartForward(g_OnPlayerExitPvP); + Call_PushCell(player.index); + Call_Finish(); + } + if (regenerate) { // Regenerate player but keep health the same. @@ -1176,6 +1192,8 @@ static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHoo void PvP_InitializeAPI() { CreateNative("SF2_IsClientInPvP", Native_IsClientInPvP); + g_OnPlayerEnterPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); + g_OnPlayerExitPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); } static int Native_IsClientInPvP(Handle plugin,int numParams) From 5ea583ac75a86ced694e5e3dd593437e40bf9624 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Fri, 23 Feb 2024 08:57:24 -0700 Subject: [PATCH 36/86] Alpha 2.64 --- .../sf2/npc/entities/chaser/entity.sp | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 93bd85d8..8254bbff 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -1691,10 +1691,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss data = controller.GetProfileData(); SF2ChaserBossProfileAttackData attackData; data.GetAttack(attackName, attackData); - PathFollower path = controller.Path; CBaseEntity target = this.Target; int difficulty = controller.Difficulty; - bool shouldPath = false; if (target.IsValid()) { @@ -1717,28 +1715,6 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss target.GetAbsOrigin(pos); loco.FaceTowards(pos); } - - if (attackData.RunSpeed[difficulty] > 0.0) - { - shouldPath = true; - } - - if (shouldPath) - { - float pos[3]; - target.GetAbsOrigin(pos); - - path.ComputeToPos(bot, pos); - } - } - - if (!path.IsValid()) - { - loco.Stop(); - } - else - { - path.Update(bot); } } From ed7d90df46814ba7049f41d438b76d7b8ff400e1 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 24 Feb 2024 23:24:21 -0700 Subject: [PATCH 37/86] Alpha 2.65 --- addons/sourcemod/scripting/sf2.sp | 70 +++++++++++ .../projectiles/sf2_grenade_projectile.sp | 5 + .../sf2/entities/sf2_base_projectile.sp | 118 +++++++++++++----- .../scripting/sf2/extras/commands.sp | 1 + .../sf2/npc/entities/chaser/actions/attack.sp | 2 +- .../entities/chaser/actions/attacks/melee.sp | 87 +++++++++++-- .../npc/entities/chaser/actions/mainlayer.sp | 31 +++-- .../sf2/npc/entities/chaser/entity.sp | 80 +++++++----- addons/sourcemod/scripting/sf2/profiles.sp | 2 +- addons/sourcemod/scripting/sf2/stocks.sp | 29 ----- 10 files changed, 304 insertions(+), 121 deletions(-) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 95b9d5f3..e8de93ba 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -581,6 +581,7 @@ char g_DebugBeamSound[PLATFORM_MAX_PATH]; ArrayList g_Buildings; ArrayList g_WhitelistedEntities; +ArrayList g_BreakableProps; // Global forwards. GlobalForward g_OnBossAddedFwd; @@ -870,6 +871,33 @@ public void OnConfigsExecuted() g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); } + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_physics")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_dynamic")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_breakable")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGame(i)) @@ -1070,6 +1098,33 @@ static void StartPlugin() g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); } + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_physics")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "prop_dynamic")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_breakable")) != -1) + { + if (GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + } + Call_StartForward(g_OnGamemodeStartPFwd); Call_Finish(); } @@ -2469,6 +2524,21 @@ public void OnEntityCreated(int ent, const char[] classname) g_WhitelistedEntities.Push(EntIndexToEntRef(ent)); } + if (strcmp(classname, "prop_physics", false) == 0 && GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + + if (strcmp(classname, "prop_dynamic", false) == 0 && GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + + if (strcmp(classname, "func_breakable", false) == 0 && GetEntProp(ent, Prop_Data, "m_iHealth") > 0) + { + g_BreakableProps.Push(EntIndexToEntRef(ent)); + } + if (!g_Enabled) { return; diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp index 94d0231d..4d62eafa 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -385,6 +385,11 @@ bool TraceRayGrenade(int entity, int mask, any data) return true; } + if (g_BreakableProps.FindValue(EntIndexToEntRef(entity)) != -1) + { + return true; + } + return true; } diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp index 247e20e4..c65667fa 100644 --- a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -348,16 +348,25 @@ methodmap SF2_ProjectileBase < CBaseAnimating public void DoExplosion() { int owner = this.GetPropEnt(Prop_Send, "m_hOwnerEntity"); - float pos[3]; + float pos[3], otherPos[3]; this.GetAbsOrigin(pos); - // Go for players + // Search all valid entities ArrayList hitList = new ArrayList(); - TR_EnumerateEntitiesSphere(pos, this.BlastRadius, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, hitList); - - for (int i = 0; i < hitList.Length; i++) + for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer player = SF2_BasePlayer(hitList.Get(i)); + if (!player.IsValid) + { + continue; + } + + player.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; + } + if (g_Enabled) { if (!this.AttackWaiters && player.IsEliminated) @@ -373,54 +382,97 @@ methodmap SF2_ProjectileBase < CBaseAnimating } } - float targetPos[3]; - player.GetEyePosition(targetPos); - TR_TraceRayFilter(pos, targetPos, - CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_GRATE, - RayType_EndPoint, TraceRayDontHitAnyEntity, this.index); + hitList.Push(EntIndexToEntRef(player.index)); + } - if (!TR_DidHit() || TR_GetEntityIndex() == player.index) + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!building.IsValid()) { - float damage = this.CalculateFallOff(player); - int flags = DMG_BLAST; - if (this.IsCrits) - { - flags |= DMG_ACID; - } - float force[3]; - this.GetDamageForce(player, force); - player.TakeDamage(_, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, damage, flags, _, force, pos); - Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); - Call_PushCell(player); - Call_PushCell(this); - Call_Finish(); + continue; + } + + building.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; } + + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == building.GetProp(Prop_Data, "m_iTeamNum")) + { + continue; + } + + hitList.Push(EntIndexToEntRef(building.index)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + if (!building.IsValid()) + { + continue; + } + + building.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; + } + + hitList.Push(EntIndexToEntRef(building.index)); + } + + for (int i = 0; i < g_BreakableProps.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_BreakableProps.Get(i))); + if (!building.IsValid()) + { + continue; + } + + building.GetAbsOrigin(otherPos); + if (GetVectorSquareMagnitude(pos, otherPos) > Pow(this.BlastRadius, 2.0)) + { + continue; + } + + hitList.Push(EntIndexToEntRef(building.index)); } - hitList.Clear(); - // Then do props - TR_EnumerateEntitiesSphere(pos, this.BlastRadius, PARTITION_SOLID_EDICTS, EnumerateBreakableEntities, hitList); for (int i = 0; i < hitList.Length; i++) { - CBaseEntity prop = CBaseEntity(hitList.Get(i)); + CBaseEntity valid = CBaseEntity(EntRefToEntIndex(hitList.Get(i))); + if (!valid.IsValid()) + { + continue; + } float targetPos[3]; - prop.WorldSpaceCenter(targetPos); + valid.WorldSpaceCenter(targetPos); TR_TraceRayFilter(pos, targetPos, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitAnyEntity, this.index); - if (!TR_DidHit() || TR_GetEntityIndex() == prop.index) + if (!TR_DidHit() || TR_GetEntityIndex() == valid.index) { - float damage = this.CalculateFallOff(prop); + float damage = this.CalculateFallOff(valid); int flags = DMG_BLAST; if (this.IsCrits) { flags |= DMG_ACID; } float force[3]; - this.GetDamageForce(prop, force); - SDKHooks_TakeDamage(prop.index, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, damage, flags, _, force, pos, false); + this.GetDamageForce(valid, force); + SDKHooks_TakeDamage(valid.index, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, damage, flags, _, force, pos); + if (SF2_BasePlayer(valid.index).IsValid) + { + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(SF2_BasePlayer(valid.index)); + Call_PushCell(this); + Call_Finish(); + } } } diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 6e518d0d..06bfcc25 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -242,6 +242,7 @@ public void OnPluginStart() g_Buildings = new ArrayList(); g_WhitelistedEntities = new ArrayList(); + g_BreakableProps = new ArrayList(); switch (g_DifficultyConVar.IntValue) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp index 07093f78..b9075f25 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -324,7 +324,7 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i end = true; } - if (attackData.CancelLos[difficulty] && !actor.IsLOSClearFromTarget(target)) + if (attackData.CancelLos[difficulty] && (actor.InterruptConditions & COND_ENEMYVISIBLE) == 0) { end = true; } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp index b2eed628..72853b55 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -211,22 +211,88 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity ArrayList targets = new ArrayList(); // Sweep the props - TR_EnumerateEntitiesSphere(myEyePos, range, PARTITION_SOLID_EDICTS, EnumerateBreakableEntities, targets); - for (int i = 0; i < targets.Length; i++) + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!building.IsValid()) + { + continue; + } + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, building, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(building)) + { + continue; + } + + if (!IsTargetInMeleeChecks(actor, attackData, building, range, spread)) + { + continue; + } + + targets.Push(g_Buildings.Get(i)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); + if (!building.IsValid()) + { + continue; + } + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, building, attackEliminated)) + { + continue; + } + + if (originalData.IsPvEBoss && !IsPvETargetValid(building)) + { + continue; + } + + if (!IsTargetInMeleeChecks(actor, attackData, building, range, spread)) + { + continue; + } + + targets.Push(g_WhitelistedEntities.Get(i)); + } + + for (int i = 0; i < g_BreakableProps.Length; i++) { - CBaseEntity prop = CBaseEntity(targets.Get(i)); + CBaseEntity building = CBaseEntity(EntRefToEntIndex(g_BreakableProps.Get(i))); + if (!building.IsValid()) + { + continue; + } - if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, prop, attackEliminated)) + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, building, attackEliminated)) { continue; } - if (originalData.IsPvEBoss && !IsPvETargetValid(prop)) + if (originalData.IsPvEBoss && !IsPvETargetValid(building)) { continue; } - if (!IsTargetInMeleeChecks(actor, attackData, prop, range, spread)) + if (!IsTargetInMeleeChecks(actor, attackData, building, range, spread)) + { + continue; + } + + targets.Push(g_BreakableProps.Get(i)); + } + + for (int i = 0; i < targets.Length; i++) + { + CBaseEntity prop = CBaseEntity(EntRefToEntIndex(targets.Get(i))); + if (!prop.IsValid()) { continue; } @@ -234,13 +300,12 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity hit = true; SDKHooks_TakeDamage(prop.index, actor.index, actor.index, damage, 64, _, _, myEyePos, false); } - targets.Clear(); + delete targets; // Sweep the players - TR_EnumerateEntitiesSphere(myEyePos, range, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, targets); - for (int i = 0; i < targets.Length; i++) + for (int i = 1; i <= MaxClients; i++) { - SF2_BasePlayer player = targets.Get(i); + SF2_BasePlayer player = SF2_BasePlayer(i); if (!originalData.IsPvEBoss && !IsTargetValidForSlender(actor, player, attackEliminated)) { @@ -366,8 +431,6 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity SlenderSpawnEffects(attackData.MissEffects, controller.Index, false); } } - - delete targets; } static void OnAnimationEvent(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, int event) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp index 2181546a..a4a364f0 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -211,11 +211,9 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) { attackEliminated = true; } - ArrayList playersList = new ArrayList(); - TR_EnumerateEntitiesSphere(worldSpace, originalData.InstantKillRadius, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, playersList); - for (int i = 0; i < playersList.Length; i++) + for (int i = 1; i <= MaxClients; i++) { - SF2_BasePlayer client = SF2_BasePlayer(playersList.Get(i)); + SF2_BasePlayer client = SF2_BasePlayer(i); if (!IsTargetValidForSlender(actor, client, attackEliminated)) { continue; @@ -236,7 +234,6 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) client.StartDeathCam(controller.Index, myPos); actor.CheckTauntKill(SF2_BasePlayer(client.index)); } - delete playersList; } } @@ -297,26 +294,34 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) if (!actor.IsAttacking && (controller.Flags & SFF_ATTACKPROPS) != 0) { - float myPos[3]; + float myPos[3], propPos[3]; actor.GetAbsOrigin(myPos); - ArrayList props = new ArrayList(); - TR_EnumerateEntitiesSphere(myPos, 2000.0, PARTITION_SOLID_EDICTS, EnumerateBreakableEntities, props); - for (int i = 0; i < props.Length; i++) + for (int i = 0; i < g_BreakableProps.Length; i++) { - if (!actor.IsLOSClearFromTarget(CBaseEntity(props.Get(i)))) + CBaseEntity prop = CBaseEntity(EntRefToEntIndex(g_BreakableProps.Get(i))); + if (!prop.IsValid()) { continue; } - NextBotAction attackAction = actor.GetAttackAction(props.Get(i), true); + prop.GetAbsOrigin(propPos); + if (GetVectorSquareMagnitude(myPos, propPos) > Pow(1500.0, 2.0)) + { + continue; + } + + if (!actor.IsLOSClearFromTarget(prop)) + { + continue; + } + + NextBotAction attackAction = actor.GetAttackAction(prop, true); if (attackAction != NULL_ACTION) { actor.EndCloak(); - delete props; return action.SuspendFor(attackAction, "Fuck you prop."); } } - delete props; } UnstuckCheck(action, actor); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 8254bbff..790dc3b4 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -1494,7 +1494,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss continue; } - if (!attackData.StartThroughWalls[difficulty] && !this.IsLOSClearFromTarget(target)) + if (!attackData.StartThroughWalls[difficulty] && (this.InterruptConditions & COND_ENEMYVISIBLE) == 0) { continue; } @@ -1774,17 +1774,15 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss float myWorldSpace[3], myPos[3]; this.WorldSpaceCenter(myWorldSpace); this.GetAbsOrigin(myPos); - ArrayList hitList = new ArrayList(); - TR_EnumerateEntitiesSphere(myWorldSpace, radius, PARTITION_SOLID_EDICTS, EnumerateLivingPlayers, hitList); bool eliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; if (originalData.IsPvEBoss) { eliminated = true; } - for (int i = 0; i < hitList.Length; i++) + for (int i = 1; i <= MaxClients; i++) { - SF2_BasePlayer player = SF2_BasePlayer(hitList.Get(i)); + SF2_BasePlayer player = SF2_BasePlayer(i); if (!IsTargetValidForSlender(this, player, eliminated)) { @@ -1835,8 +1833,6 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss shockwaveData.ApplyDamageEffects(player, difficulty, SF2_ChaserBossEntity(this.index)); } } - - delete hitList; } public bool SearchSoundsWithSectionName(ArrayList base, const char[] name, SF2BossProfileSoundInfo output) @@ -1890,7 +1886,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss return; } - if (!this.IsLOSClearFromTarget(target, false)) + if ((this.InterruptConditions & COND_ENEMYVISIBLE) == 0) { return; } @@ -1927,7 +1923,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss return; } - if (!this.IsLOSClearFromTarget(this.Target)) + if ((this.InterruptConditions & COND_ENEMYVISIBLE) == 0) { return; } @@ -3227,26 +3223,6 @@ static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bo } } -static bool EnumerateTargets(int entIndex, ArrayList players) -{ - if (IsValidClient(entIndex) && IsPlayerAlive(entIndex) && !IsClientInGhostMode(entIndex)) - { - players.Push(entIndex); - } - - if (g_Buildings.FindValue(EnsureEntRef(entIndex)) != -1) - { - players.Push(entIndex); - } - - if (g_WhitelistedEntities.FindValue(EnsureEntRef(entIndex)) != -1) - { - players.Push(entIndex); - } - - return true; -} - static void OnCreate(SF2_ChaserEntity ent) { ent.AttackIndex = -1; @@ -4010,10 +3986,40 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } ArrayList valids = new ArrayList(); - TR_EnumerateEntitiesSphere(traceStartPos, searchRange, PARTITION_SOLID_EDICTS, EnumerateTargets, valids); - for (int i = 0; i < valids.Length; i++) + for (int i = 1; i <= MaxClients; i++) { - CBaseEntity entity = CBaseEntity(EntRefToEntIndex(valids.Get(i))); + SF2_BasePlayer client = SF2_BasePlayer(i); + + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, client, attackEliminated)) + { + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(client)) + { + continue; + } + + valids.Push(EntIndexToEntRef(client.index)); + } + + for (int i = 0; i < g_Buildings.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_Buildings.Get(i))); + if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) + { + continue; + } + if (originalData.IsPvEBoss && !IsPvETargetValid(entity)) + { + continue; + } + + valids.Push(EntIndexToEntRef(entity.index)); + } + + for (int i = 0; i < g_WhitelistedEntities.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(g_WhitelistedEntities.Get(i))); if (!originalData.IsPvEBoss && !IsTargetValidForSlender(chaser, entity, attackEliminated)) { continue; @@ -4023,6 +4029,16 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio continue; } + valids.Push(EntIndexToEntRef(entity.index)); + } + + for (int i = 0; i < valids.Length; i++) + { + CBaseEntity entity = CBaseEntity(EntRefToEntIndex(valids.Get(i))); + if (!entity.IsValid()) + { + continue; + } SF2_BasePlayer player = SF2_BasePlayer(entity.index); if (player.IsValid && g_PlayerDebugFlags[player.index] & DEBUG_BOSS_EYES) diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 5df0a2ad..5b5ee11d 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -621,7 +621,7 @@ void ReloadBossProfiles() if (!voteBossPackLoaded) { - if (strcmp(mapBossPack, bossPackName) == 0) + if (!autoLoad && strcmp(mapBossPack, bossPackName) == 0) { voteBossPackLoaded = true; } diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 4ee059a8..e01823e3 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -1475,35 +1475,6 @@ bool EnumerateLivingPlayers(int entIndex, ArrayList players) return true; } -bool EnumerateBreakableEntities(int entIndex, ArrayList array) -{ - if (!IsValidEntity(entIndex)) - { - return false; - } - - char className[64]; - GetEntityClassname(entIndex, className, sizeof(className)); - - if (strcmp(className, "prop_physics") == 0 || strcmp(className, "prop_dynamic") == 0 || - strcmp(className, "func_breakable", false) == 0 || strcmp(className, "tank_boss", false) == 0) - { - if (GetEntProp(entIndex, Prop_Data, "m_iHealth") > 0) - { - array.Push(entIndex); - } - } - else if (strncmp(className, "obj_", 4) == 0) - { - if (GetEntProp(entIndex, Prop_Data, "m_iHealth") > 0) - { - array.Push(entIndex); - } - } - - return true; -} - // ========================================================== // TIMER/CALLBACK FUNCTIONS // ========================================================== From 7e25058c0592cf7e12e63be0685ec736dff8aaed Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sun, 25 Feb 2024 00:52:37 -0700 Subject: [PATCH 38/86] Fuck --- addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp index c65667fa..97311d28 100644 --- a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -355,7 +355,7 @@ methodmap SF2_ProjectileBase < CBaseAnimating ArrayList hitList = new ArrayList(); for (int i = 1; i <= MaxClients; i++) { - SF2_BasePlayer player = SF2_BasePlayer(hitList.Get(i)); + SF2_BasePlayer player = SF2_BasePlayer(i); if (!player.IsValid) { continue; From 0e75a7a4d37303b7885b1f8f81e8544591c4948f Mon Sep 17 00:00:00 2001 From: Batfoxkid Date: Mon, 4 Mar 2024 03:59:04 -0800 Subject: [PATCH 39/86] Add SF2_GetCurrentBossPack (#130) Adds a native to get the current boss pack name. --- addons/sourcemod/scripting/include/sf2.inc | 10 ++++++ .../sourcemod/scripting/sf2/extras/natives.sp | 10 ++++++ addons/sourcemod/scripting/sf2/profiles.sp | 33 +++++++++++-------- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 8aff5416..6916b1ea 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -951,6 +951,16 @@ native int SF2_GetSpecialRoundType(); */ native int SF2_IsInSpecialRound(int specialRound); +/** + * Retrieves name of the current boss pack + * + * @param buffer Buffer to store the pack name. + * @param bufferLen Max length of buffer. + * + * @return Number of bytes copied + */ +native int SF2_GetCurrentBossPack(char[] buffer, int bufferLen); + /** * Returns the current group a player is in. * diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index bd7c7d41..1901bc94 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -69,6 +69,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("SF2_GetBossDifficulty", Native_GetBossDifficulty); CreateNative("SF2_GetDifficultyModifier", Native_GetDifficultyModifier); CreateNative("SF2_IsInSpecialRound", Native_IsInSpecialRound); + CreateNative("SF2_GetCurrentBossPack", Native_GetCurrentBossPack); CreateNative("SF2_GetClientGroup", Native_GetClientGroup); CreateNative("SF2_GetClientQueuePoints", Native_GetClientQueuePoints); @@ -430,6 +431,15 @@ static any Native_IsInSpecialRound(Handle plugin, int numParams) return SF_SpecialRound(GetNativeCell(1)); } +static any Native_GetCurrentBossPack(Handle plugin, int numParams) +{ + int length = GetNativeCell(2); + char[] bossPackName = new int[++length]; + GetCurrentBossPack(bossPackName, length); + SetNativeString(1, bossPackName, length, _, length); + return length; +} + static any Native_GetClientGroup(Handle plugin, int numParams) { return ClientGetPlayerGroup(GetNativeCell(1)); diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 5b5ee11d..8cce6f96 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -276,31 +276,36 @@ int LookupProfileAnimation(int entity, const char[] animName) return sequence; } -/* -Command -*/ -Action Command_Pack(int client,int args) +void GetCurrentBossPack(char[] bossPackName, int length) { - if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) - { - CPrintToChat(client,"{red}Boss pack vote is disabled on this server."); - return Plugin_Handled; - } g_BossPackConfig.Rewind(); if (!g_BossPackConfig.JumpToKey("packs")) { - return Plugin_Handled; + return; } if (!g_BossPackConfig.JumpToKey(mapBossPack)) { - return Plugin_Handled; + return; } - char bossPackName[64]; - g_BossPackConfig.GetString("name", bossPackName, sizeof(bossPackName), mapBossPack); + g_BossPackConfig.GetString("name", bossPackName, length, mapBossPack); if (bossPackName[0] == '\0') { - FormatEx(bossPackName, sizeof(bossPackName), "Core Pack"); + FormatEx(bossPackName, length, "Core Pack"); + } +} + +/* +Command +*/ +Action Command_Pack(int client,int args) +{ + if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) + { + CPrintToChat(client,"{red}Boss pack vote is disabled on this server."); + return Plugin_Handled; } + char bossPackName[64]; + GetCurrentBossPack(bossPackName, sizeof(bossPackName)); CPrintToChat(client, "{dodgerblue}Pack: {lightblue}%s", bossPackName); return Plugin_Handled; } From 7308cbaf7d39675c80edd26a6ac38c9dd9e75472 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:33:41 -0700 Subject: [PATCH 40/86] How do you mess that up --- addons/sourcemod/scripting/sf2/extras/natives.sp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 1901bc94..de95ee3e 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -434,7 +434,7 @@ static any Native_IsInSpecialRound(Handle plugin, int numParams) static any Native_GetCurrentBossPack(Handle plugin, int numParams) { int length = GetNativeCell(2); - char[] bossPackName = new int[++length]; + char[] bossPackName = new char[++length]; GetCurrentBossPack(bossPackName, length); SetNativeString(1, bossPackName, length, _, length); return length; @@ -737,4 +737,4 @@ static any Native_IsProxyMap(Handle plugin, int numParams) static any Native_IsSlaughterRunMap(Handle plugin, int numParams) { return SF_IsSlaughterRunMap(); -} \ No newline at end of file +} From 54340e2492df9817395ea8d960b3998698a59386 Mon Sep 17 00:00:00 2001 From: Batfoxkid Date: Mon, 18 Mar 2024 09:09:00 -0700 Subject: [PATCH 41/86] Fix AFK Not Applying For Newly Connected (#131) OnPlayerRunCmd was updated with a IsValidClient which breaks AFK status for newly connected players. --- addons/sourcemod/scripting/sf2.sp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index e8de93ba..509682e8 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -3733,6 +3733,8 @@ public void OnClientPutInServer(int client) QueryClientConVar(client, "fov_desired", OnClientGetDesiredFOV); } + AFK_SetTime(client); + Call_StartForward(g_OnPlayerPutInServerPFwd); Call_PushCell(SF2_BasePlayer(client)); Call_Finish(); From bfd1cf1938c602ee9c30ab349938e17724cabe5f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Fri, 19 Apr 2024 21:01:37 -0700 Subject: [PATCH 42/86] Update sf2.txt --- addons/sourcemod/gamedata/sf2.txt | 36 +++++++++++++------------------ 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/addons/sourcemod/gamedata/sf2.txt b/addons/sourcemod/gamedata/sf2.txt index b5d3e1f3..8e98818f 100644 --- a/addons/sourcemod/gamedata/sf2.txt +++ b/addons/sourcemod/gamedata/sf2.txt @@ -22,12 +22,6 @@ "windows" "20" } - "CBaseEntity::GetSmoothedVelocity" - { - "linux" "146" - "windows" "145" - } - "CBaseEntity::UpdateTransmitState" { "linux" "22" @@ -57,38 +51,38 @@ { "CTFPlayer::PlaySpecificSequence" { - "library" "server" - "linux" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" - "windows" "\x55\x8B\xEC\x53\x56\x8B\x75\x08\x57\x56\x8B\xF9\xE8\x2A\x2A\x2A\x2A\x8B\xD8" + "library" "server" + "linux" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" + "windows" "\x55\x8B\xEC\x53\x56\x8B\x75\x08\x57\x56\x8B\xF9\xE8\x2A\x2A\x2A\x2A\x8B\xD8" } "CBaseTrigger::PointIsWithin" { - "library" "server" - "linux" "@_ZN12CBaseTrigger13PointIsWithinERK6Vector" - "windows" "\x53\x8B\xDC\x83\xEC\x2A\x83\xE4\x2A\x83\xC4\x2A\x55\x8B\x6B\x2A\x89\x6C\x2A\x2A\x8B\xEC\x81\xEC\x2A\x2A\x2A\x2A\x8B\x43\x2A\x56\x50\x8B\xF1" + "library" "server" + "linux" "@_ZN12CBaseTrigger13PointIsWithinERK6Vector" + "windows" "\x53\x8B\xDC\x83\xEC\x08\x83\xE4\xF0\x83\xC4\x04\x55\x8B\x6B\x04\x89\x6C\x24\x04\x8B\xEC\x81\xEC\xAC\x00\x00\x00" } // "CreateEntityByName( %s, %d ) - CreateEdict failed." "CreateEntityByName" { - "library" "server" - "linux" "@_Z18CreateEntityByNamePKci" - "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x0C\x83\xFE\xFF\x74\x24\x8B\x0D" + "library" "server" + "linux" "@_Z18CreateEntityByNamePKci" + "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x0C\x83\xFE\xFF\x74\x24\x8B\x0D" } "CBaseAnimating::LookupBone" // int CBaseAnimating::LookupBone( const char *szName ) { - "library" "server" - "windows" "\x55\x8B\xEC\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\x83\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x8B\xCE\xE8\x2A\x2A\x2A\x2A\x8B\x86\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x83\x38\x00\x74\x2A\xFF\x75\x2A\x50\xE8\x2A\x2A\x2A\x2A\x83\xC4\x08\x5E" - "linux" "@_ZN14CBaseAnimating10LookupBoneEPKc" + "library" "server" + "windows" "\x55\x8B\xEC\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\x83\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x8B\xCE\xE8\x2A\x2A\x2A\x2A\x8B\x86\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x83\x38\x00\x74\x2A\xFF\x75\x2A\x50\xE8\x2A\x2A\x2A\x2A\x83\xC4\x08\x5E" + "linux" "@_ZN14CBaseAnimating10LookupBoneEPKc" } "CBaseAnimating::GetBonePosition" // void CBaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) { - "library" "server" - "windows" "\x55\x8B\xEC\x83\xEC\x30\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A" - "linux" "@_ZN14CBaseAnimating15GetBonePositionEiR6VectorR6QAngle" + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x30\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A" + "linux" "@_ZN14CBaseAnimating15GetBonePositionEiR6VectorR6QAngle" } } } From 6f3959961741d462db548475ef653ed65bc80eeb Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:17:22 -0700 Subject: [PATCH 43/86] 4/24/24 changes, nice --- TERMSOFSERVICE.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/TERMSOFSERVICE.md b/TERMSOFSERVICE.md index 8d00752b..640f400e 100644 --- a/TERMSOFSERVICE.md +++ b/TERMSOFSERVICE.md @@ -8,7 +8,7 @@ Slender Fortress Modified is a Sourcemod plugin offered by Mentrillum for free. ### A. Stolen Content -Any bosses found on the Github wiki that have not been taken down are allowed to be used without permission from the original creators (Exceptions being the Yakuza 0 bosses created by Astolfo Alter where no one is allowed to use them period). Obtaining any boss after a download is terminated for public download will depend if the original creator wants that content on your server still and if no other outside variables determine that decision. Any maps found on the Github wiki follow the same rules as the bosses. If you specifically want to add a certain boss or map that is not found on any public source or the Github page, you have to talk to the creator of the content about that. Failing to do so and making recreations without reextracting the boss assets, not remaking a map from the ground up, or outright copying and pasting a map results in your server having stolen content. Removing the stolen content will no longer violate this rule. Consider this your warning. +As of April 24th, 2024, any content taken from other servers is now dependent on where the content came from. Server owners and or boss/map creators which port assets from other games or similar medias do not have the right to request a content removal for other servers or platforms like Garry's Mod. This doesn't apply to assets which were created by the map/boss creators as the creators do get to decide on how their content is handled. The latter of which has specific rules. Attempting to take self-made assets without the creator's intentions falls under content stealing. Removing the stolen content will no longer violate this rule. Consider this your warning. ### B. Explicit Content @@ -20,24 +20,16 @@ Hosing a server and promoting it with the sole purpose of luring children in to ## 2. Content Creation -Creating any form of content follows the same rules as hosting a server with extra considerations to take into account. If you do not plan to show the content off to anyone which includes servers even after the content is fully finished, you may skip this part of the Terms of Service. You may release any content publicly or privately. By allowing any server to take your boss that is public, you also agree to let server owners or developers edit your boss without your permission or consent, but they are not allowed to release the edited version unless they get permission from you as the original creator unless they reextract and reimport assets to make it different from your version. +Creating any form of content follows the same rules as hosting a server with extra considerations to take into account. If you do not plan to show the content off to anyone which includes servers even after the content is fully finished, you may skip this part of the Terms of Service. You may release any content publicly or privately. By allowing any server to take your boss, you agree to let server owners or developers edit your boss without your permission or consent. Unless the content is self-made in which you do have the right to enforce takedowns, but if you've done only ports, you cannot enforce takedowns. So these rules will only apply to self-made content. -### A. Remakes +### A. Content Submissions + Remakes -You are allowed to remake a boss if you specifically do not copy and paste the original boss and recreate the config as perfectly as possible if the boss is considered a private boss, this includes leaking private configs. It is also required to reextract and reimport assets if you can show that you did more than the original boss creator (EG adding new animations, changing the model's texture to be more appropriate with the original source, remaking a model by hand), it is also required you do more than just one of the example tasks allowing others to see that you did more than just copy and paste assets. Currently its debatable if you are unable to add something new to the boss if the original version added everything that you can't add, its best to ask the original porter and creator if you can remake the boss if this is the case. Remaking maps is allowed as long as you do not copy and paste a lot of map elements that impact the map heavily (EG the layout), designs and decorations are not considered as long as you are free to use the assets you want (EG assets found in TF2, HL2, etc). If you specifically want to make a new unofficial version of the map, it is best you talk with the map creator to negociate how both of you want to go about doing this. If the creator is impossible to reach in both creating areas, do your best to show that your unofficial version is unofficial. Restorations of long lost maps and bosses are allowed (EG Clocktower Office, Jaraxxus, Flame Atronach). +You may skip this part of the Terms of Service if you don't plan to submit any content to be displayed on the Github page for everyone to easily find and download or to any officially supported servers. Submitting any content to Mentrillum where it can be added on the Github wiki means you agree to follow the content creating guidelines before submitting your content. Any small detail that may hint at any of these violations will result in a request for removing that small detail. Failing to do so will not get your content added on the Github. If these details are noticed months after the content was added, it will be taken down immediately after the discovery and will only be readded if you remove those details. Content will also be reviewed based on the quality of the boss, we do not want carbon copy bosses sitting on the Github page. This does not mean your boss or map has to be balanced out perfectly. The same logic will apply to submitting content to servers that are officially supported (Disc-FF, Demon Hamster's server, etc). Fully remaking every asset a creation uses is allowed and doesn't need permission from the original self-made asset creator(s), which also includes restorations, sub plugin recreations, etc. -### B. Content Submissions - -You may skip this part of the Terms of Service if you don't plan to submit any content to be displayed on the Github page for everyone to easily find and download or to any officially supported servers. Submitting any content to Mentrillum where it can be added on the Github wiki means you agree to follow the content creating guidelines before submitting your content. Any small detail that may hint at any of these violations will result in a request for removing that small detail. Failing to do so will not get your content added on the Github. If these details are noticed months after the content was added, it will be taken down immediately after the discovery and will only be readded if you remove those details. Content will also be reviewed based on the quality of the boss, we do not want carbon copy bosses sitting on the Github page. This does not mean your boss or map has to be balanced out perfectly. The same logic will apply to submitting content to servers that are officially supported (Disc-FF and District Zen). - -## 3. Experimental Builds - -A [Patreon](https://www.patreon.com/Mentrillum) exists where it contains all experimental builds that have not yet been released to the general public. You are allowed to host these builds publicly on your server. You are not allowed to distribute these builds to other people unless they to donated to the Patreon with proof. You are free to distribute the experimental builds of a specific version when the final release of that version comes out (EG 1.7.5 experimental builds). - -## 4. Consequences +## 3. Consequences After you are caught violating any of these rules, you will be approached by one of the higher ups at the SF2M team and will be requested to take action to fix any of these violations which may or may not include deleting parts or all of a certain piece of content, adjusting your server rules, deleting parts or a whole video, etc. Going forth with these requests and proving you put in effort to fix these violations without question will not make anything major happen depending on if these violations were intentional or unintentional. Intentionally violating these terms and fixing the violations will result in a warning meaning you can still create content and host a server. Depending on if you just create content for servers or host a server and make videos about it, the second warning will be different. For just creating and submitting content boss or map related, you will be given a second warning if these violations intentionally happened which most of the time is the case. The third warning for creating and submitting content boss or map related or the second warning for hosting a server and making videos on that server will depend on which of the following actions you do. Creating and submitting content will result in a blacklist that every server owner can see which they can quickly make the choice to either avoid your content or carelessly add it either way, the blacklist will have an associated reason as to why this blacklist happened. Getting a second warning for hosting a server or making videos about a server showing content that violates these terms will also result in a blacklist but for average players. Neglecting to fix these actions in any way which includes ignoring any team member's request, being defiant about these requests, never responding to these team members, or continuing to violate these already violated terms to a greater degree will result in a immediate blacklist. -## 5. Appeals +## 4. Appeals If you are already blacklisted in any way but you believe you have changed enough to where you don't violate these terms anymore, you can negociate with Mentrillum proving these terms are no longer violated and you show you will not violate these terms any longer. These appeals can only be given after a one month grace period since the blacklist addition. If these appeals are accepted, your blacklist will be removed and you'll be back to square one in terms of the warning system. Rejected appeals typically mean either you haven't changed or you aren't trusted enough to create content, host a server, or make videos about both that may violate the terms, if this is the case you'll have to wait for an undetermined long period of time. Checking back in on if an appeal can be accepted in an annoying way will lessen the likelihood of the current and following appeals to be accepted. From 77e2e1666054bc84588b46e004df0264c4190352 Mon Sep 17 00:00:00 2001 From: Ivan Zaitsev Date: Mon, 6 May 2024 02:39:55 +0500 Subject: [PATCH 44/86] Mark natives as optional if library is not required & some fixes (#135) * Mark natives as optional if library is not required * Fix AttackIndex native (sf2.inc) * Fix AttackIndex native (chaser/entity.sp) * Fix compilation error if DEBUG is not defined --- addons/sourcemod/scripting/include/sf2.inc | 392 +++++++++++++++++- .../sf2/npc/entities/chaser/entity.sp | 6 +- 2 files changed, 395 insertions(+), 3 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 6916b1ea..9a246e77 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -1601,7 +1601,7 @@ native bool SF2_GetBossAttributeName(int bossIndex, int attribute); #pragma deprecated Use SF2BossProfileData instead. native float SF2_GetBossAttributeValue(int bossIndex, int attribute); -#pragma deprecated Use SF2_ChaserBossEntity.GetAttackName instead. +#pragma deprecated Use SF2_ChaserBossEntity.AttackIndex instead. native int SF2_GetBossCurrentAttackIndex(int bossIndex); #pragma deprecated Use SF2ChaserBossProfileAttackData.Type instead. @@ -3542,3 +3542,393 @@ public SharedPlugin __pl_sf2 = required = 0, #endif }; + +#if !defined REQUIRE_PLUGIN +public void __pl_sf2_SetNTVOptional() +{ + // sf2/methodmaps.sp + MarkNativeAsOptional("SF2_Player.UserID.get"); + MarkNativeAsOptional("SF2_Player.IsValid.get"); + MarkNativeAsOptional("SF2_Player.IsAlive.get"); + MarkNativeAsOptional("SF2_Player.IsInGame.get"); + MarkNativeAsOptional("SF2_Player.IsBot.get"); + MarkNativeAsOptional("SF2_Player.IsSourceTV.get"); + MarkNativeAsOptional("SF2_Player.IsReplay.get"); + MarkNativeAsOptional("SF2_Player.InThirdPerson.get"); + MarkNativeAsOptional("SF2_Player.Buttons.get"); + MarkNativeAsOptional("SF2_Player.IsMoving"); + MarkNativeAsOptional("SF2_Player.Health.get"); + MarkNativeAsOptional("SF2_Player.MaxHealth.get"); + MarkNativeAsOptional("SF2_Player.Ducking.get"); + MarkNativeAsOptional("SF2_Player.Ducked.get"); + MarkNativeAsOptional("SF2_Player.GetDataEnt"); + MarkNativeAsOptional("SF2_Player.Class.get"); + MarkNativeAsOptional("SF2_Player.Team.get"); + MarkNativeAsOptional("SF2_Player.HasRegenItem.get"); + MarkNativeAsOptional("SF2_Player.HasRegenItem.set"); + MarkNativeAsOptional("SF2_Player.InCondition"); + MarkNativeAsOptional("SF2_Player.ChangeCondition"); + MarkNativeAsOptional("SF2_Player.IsCritBoosted"); + MarkNativeAsOptional("SF2_Player.IsMiniCritBoosted"); + MarkNativeAsOptional("SF2_Player.Ignite"); + MarkNativeAsOptional("SF2_Player.Bleed"); + MarkNativeAsOptional("SF2_Player.Stun"); + MarkNativeAsOptional("SF2_Player.Regenerate"); + MarkNativeAsOptional("SF2_Player.SetClass"); + MarkNativeAsOptional("SF2_Player.GetEyePosition"); + MarkNativeAsOptional("SF2_Player.GetEyeAngles"); + MarkNativeAsOptional("SF2_Player.GetDataVector"); + MarkNativeAsOptional("SF2_Player.SetDataVector"); + MarkNativeAsOptional("SF2_Player.GetDistanceFromEntity"); + MarkNativeAsOptional("SF2_Player.GetWeaponSlot"); + MarkNativeAsOptional("SF2_Player.SwitchToWeaponSlot"); + MarkNativeAsOptional("SF2_Player.RemoveWeaponSlot"); + MarkNativeAsOptional("SF2_Player.ScreenShake"); + MarkNativeAsOptional("SF2_Player.ViewPunch"); + MarkNativeAsOptional("SF2_Player.TakeDamage"); + MarkNativeAsOptional("SF2_Player.Respawn"); + MarkNativeAsOptional("SF2_Player.UpdateListeningFlags"); + MarkNativeAsOptional("SF2_Player.IsParticipating.get"); + MarkNativeAsOptional("SF2_Player.GetName"); + MarkNativeAsOptional("SF2_Player.LastButtons.get"); + MarkNativeAsOptional("SF2_Player.LastButtons.set"); + MarkNativeAsOptional("SF2_Player.ScreenFade"); + MarkNativeAsOptional("SF2_Player.IsEliminated.get"); + MarkNativeAsOptional("SF2_Player.IsEliminated.set"); + MarkNativeAsOptional("SF2_Player.IsInGhostMode.get"); + MarkNativeAsOptional("SF2_Player.SetGhostState"); + MarkNativeAsOptional("SF2_Player.IsProxy.get"); + MarkNativeAsOptional("SF2_Player.IsProxy.set"); + MarkNativeAsOptional("SF2_Player.ProxyControl.get"); + MarkNativeAsOptional("SF2_Player.ProxyControl.set"); + MarkNativeAsOptional("SF2_Player.ProxyMaster.get"); + MarkNativeAsOptional("SF2_Player.ProxyMaster.set"); + MarkNativeAsOptional("SF2_Player.IsInPvP.get"); + MarkNativeAsOptional("SF2_Player.IsInPvE.get"); + MarkNativeAsOptional("SF2_Player.IsInDeathCam.get"); + MarkNativeAsOptional("SF2_Player.StartDeathCam"); + MarkNativeAsOptional("SF2_Player.HasEscaped.get"); + MarkNativeAsOptional("SF2_Player.Escape"); + MarkNativeAsOptional("SF2_Player.TeleportToEscapePoint"); + MarkNativeAsOptional("SF2_Player.ForceEscape"); + MarkNativeAsOptional("SF2_Player.UsingFlashlight.get"); + MarkNativeAsOptional("SF2_Player.HandleFlashlight"); + MarkNativeAsOptional("SF2_Player.FlashlightBatteryLife.get"); + MarkNativeAsOptional("SF2_Player.FlashlightBatteryLife.set"); + MarkNativeAsOptional("SF2_Player.ResetFlashlight"); + MarkNativeAsOptional("SF2_Player.GetFlashlightNextInputTime"); + MarkNativeAsOptional("SF2_Player.IsSprinting.get"); + MarkNativeAsOptional("SF2_Player.IsReallySprinting.get"); + MarkNativeAsOptional("SF2_Player.HandleSprint"); + MarkNativeAsOptional("SF2_Player.Stamina.get"); + MarkNativeAsOptional("SF2_Player.Stamina.set"); + MarkNativeAsOptional("SF2_Player.SetStaminaRechargeTime"); + MarkNativeAsOptional("SF2_Player.HasStartedBlinking.get"); + MarkNativeAsOptional("SF2_Player.IsBlinking.get"); + MarkNativeAsOptional("SF2_Player.IsHoldingBlink"); + MarkNativeAsOptional("SF2_Player.SetHoldingBlink"); + MarkNativeAsOptional("SF2_Player.BlinkMeter.get"); + MarkNativeAsOptional("SF2_Player.BlinkMeter.set"); + MarkNativeAsOptional("SF2_Player.BlinkCount.get"); + MarkNativeAsOptional("SF2_Player.Blink"); + MarkNativeAsOptional("SF2_Player.StartPeeking"); + MarkNativeAsOptional("SF2_Player.EndPeeking"); + MarkNativeAsOptional("SF2_Player.PageCount.get"); + MarkNativeAsOptional("SF2_Player.PageCount.set"); + MarkNativeAsOptional("SF2_Player.ShowHint"); + MarkNativeAsOptional("SF2_Player.IsTrapped.get"); + MarkNativeAsOptional("SF2_Player.IsTrapped.set"); + MarkNativeAsOptional("SF2_Player.TrapCount.get"); + MarkNativeAsOptional("SF2_Player.TrapCount.set"); + MarkNativeAsOptional("SF2_Player.IsLatched.get"); + MarkNativeAsOptional("SF2_Player.IsLatched.set"); + MarkNativeAsOptional("SF2_Player.LatchCount.get"); + MarkNativeAsOptional("SF2_Player.LatchCount.set"); + MarkNativeAsOptional("SF2_Player.Latcher.get"); + MarkNativeAsOptional("SF2_Player.Latcher.set"); + MarkNativeAsOptional("SF2_Player.UpdateMusicSystem"); + MarkNativeAsOptional("SF2_Player.HasConstantGlow.get"); + MarkNativeAsOptional("SF2_Player.SetPlayState"); + MarkNativeAsOptional("SF2_Player.CanSeeSlender"); + MarkNativeAsOptional("SF2_Player.SetAFKTime"); + MarkNativeAsOptional("SF2_Player.SetAFKState"); + MarkNativeAsOptional("SF2_Player.CheckAFKTime"); + MarkNativeAsOptional("SF2_Player.ShouldBeForceChased"); + MarkNativeAsOptional("SF2_Player.SetForceChaseState"); + MarkNativeAsOptional("SF2_Player.IsLookingAtBoss"); + + // sf2/npc.sp + MarkNativeAsOptional("SF2_GetMaxBossCount"); + MarkNativeAsOptional("SF2_EntIndexToBossIndex"); + MarkNativeAsOptional("SF2_BossIndexToEntIndex"); + MarkNativeAsOptional("SF2_BossIndexToEntIndexEx"); + MarkNativeAsOptional("SF2_BossIDToBossIndex"); + MarkNativeAsOptional("SF2_BossIndexToBossID"); + + MarkNativeAsOptional("SF2_AddBoss"); + MarkNativeAsOptional("SF2_RemoveBoss"); + + MarkNativeAsOptional("SF2_GetBossName"); + MarkNativeAsOptional("SF2_GetBossType"); + + MarkNativeAsOptional("SF2_GetBossFlags"); + MarkNativeAsOptional("SF2_SetBossFlags"); + + MarkNativeAsOptional("SF2_SpawnBoss"); + MarkNativeAsOptional("SF2_IsBossSpawning"); + MarkNativeAsOptional("SF2_DespawnBoss"); + + MarkNativeAsOptional("SF2_GetBossPathFollower"); + MarkNativeAsOptional("SF2_GetBossMaster"); + MarkNativeAsOptional("SF2_GetBossIdleLifetime"); + MarkNativeAsOptional("SF2_GetBossState"); + MarkNativeAsOptional("SF2_SetBossState"); + + MarkNativeAsOptional("SF2_GetBossEyePosition"); + MarkNativeAsOptional("SF2_GetBossEyePositionOffset"); + + MarkNativeAsOptional("SF2_GetBossTeleportThinkTimer"); + MarkNativeAsOptional("SF2_SetBossTeleportThinkTimer"); + MarkNativeAsOptional("SF2_GetBossTeleportTarget"); + + MarkNativeAsOptional("SF2_GetBossGoalPosition"); + + MarkNativeAsOptional("SF2_GetBossTimeUntilNoPersistence"); + MarkNativeAsOptional("SF2_SetBossTimeUntilNoPersistence"); + MarkNativeAsOptional("SF2_GetBossTimeUntilAlert"); + MarkNativeAsOptional("SF2_SetBossTimeUntilAlert"); + + MarkNativeAsOptional("SF2_GetProfileFromBossIndex"); + MarkNativeAsOptional("SF2_GetProfileFromName"); + + MarkNativeAsOptional("SF2_SpawnBossEffects"); + + MarkNativeAsOptional("SF2_CanBossBeSeen"); + + // sf2/profiles.sp + MarkNativeAsOptional("SF2_IsBossProfileValid"); + MarkNativeAsOptional("SF2_GetBossProfileNum"); + MarkNativeAsOptional("SF2_GetBossProfileFloat"); + MarkNativeAsOptional("SF2_GetBossProfileString"); + MarkNativeAsOptional("SF2_GetBossProfileVector"); + MarkNativeAsOptional("SF2_GetBossAttackProfileNum"); + MarkNativeAsOptional("SF2_GetBossAttackProfileFloat"); + MarkNativeAsOptional("SF2_GetBossAttackProfileString"); + MarkNativeAsOptional("SF2_GetBossAttackProfileVector"); + MarkNativeAsOptional("SF2_GetRandomStringFromBossProfile"); + MarkNativeAsOptional("SF2_GetBossAttributeName"); + MarkNativeAsOptional("SF2_GetBossAttributeValue"); + + MarkNativeAsOptional("SF2_GetBossProfileData"); + MarkNativeAsOptional("SF2_GetChaserBossProfileData"); + MarkNativeAsOptional("SF2_GetStatueBossProfileData"); + MarkNativeAsOptional("SF2_TranslateProfileActivityFromName"); + MarkNativeAsOptional("SF2_LookupProfileAnimation"); + + // sf2/pve.sp + MarkNativeAsOptional("SF2_IsClientInPvE"); + MarkNativeAsOptional("SF2_RegisterPvEBoss"); + MarkNativeAsOptional("SF2_SetOverridePvEMusic"); + MarkNativeAsOptional("SF2_UnregisterPvEBoss"); + MarkNativeAsOptional("SF2_AddPvEBoss"); + MarkNativeAsOptional("SF2_KillPvEBoss"); + MarkNativeAsOptional("SF2_GetActivePvEBosses"); + + // pvp.sp + MarkNativeAsOptional("SF2_IsClientInPvP"); + + // specialround.sp + MarkNativeAsOptional("SF2_IsSpecialRoundRunning"); + MarkNativeAsOptional("SF2_GetSpecialRoundType"); + + // client/sprint.sp + MarkNativeAsOptional("SF2_GetClientSprintPoints"); + MarkNativeAsOptional("SF2_SetClientSprintPoints"); + + MarkNativeAsOptional("SF2_IsClientSprinting"); + MarkNativeAsOptional("SF2_IsClientReallySprinting"); + MarkNativeAsOptional("SF2_SetClientSprintState"); + + // entities/sf2_base_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Base.Initialize"); + MarkNativeAsOptional("SF2_Projectile_Base.DoExplosion"); + + // entities/projectiles/sf2_arrow_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Arrow.Create"); + MarkNativeAsOptional("SF2_projectile_Arrow.IsValid.get"); + + // entities/projectiles/sf2_baseball_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Baseball.Create"); + MarkNativeAsOptional("SF2_Projectile_Baseball.IsValid.get"); + + // entities/projectiles/sf2_cow_mangler_projectile.sp + MarkNativeAsOptional("SF2_Projectile_CowMangler.Create"); + MarkNativeAsOptional("SF2_Projectile_CowMangler.IsValid.get"); + + // entities/projectiles/sf2_fireball_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Fireball.Create"); + MarkNativeAsOptional("SF2_Projectile_Fireball.IsValid.get"); + + // entities/projectiles/sf2_grenade_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Grenade.Create"); + MarkNativeAsOptional("SF2_Projectile_Grenade.IsValid.get"); + + // entities/projectiles/sf2_iceball_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Iceball.Create"); + MarkNativeAsOptional("SF2_Projectile_Iceball.IsValid.get"); + + // entities/projectiles/sf2_rocket_projectile.sp + MarkNativeAsOptional("SF2_Projectile_Rocket.Create"); + MarkNativeAsOptional("SF2_Projectile_Rocket.IsValid.get"); + + // entities/projectiles/sf2_sentry_rocket_projectile.sp + MarkNativeAsOptional("SF2_Projectile_SentryRocket.Create"); + MarkNativeAsOptional("SF2_Projectile_SentryRocket.IsValid.get"); + + // extras/natives.sp + MarkNativeAsOptional("SF2_GetConfig"); + MarkNativeAsOptional("SF2_IsRunning"); + MarkNativeAsOptional("SF2_GetRoundState"); + MarkNativeAsOptional("SF2_IsRoundInGracePeriod"); + MarkNativeAsOptional("SF2_GetCurrentDifficulty"); + MarkNativeAsOptional("SF2_GetBossDifficulty"); + MarkNativeAsOptional("SF2_GetDifficultyModifier"); + MarkNativeAsOptional("SF2_IsInSpecialRound"); + MarkNativeAsOptional("SF2_GetCurrentBossPack"); + + MarkNativeAsOptional("SF2_GetClientGroup"); + MarkNativeAsOptional("SF2_GetClientQueuePoints"); + MarkNativeAsOptional("SF2_SetClientQueuePoints"); + + MarkNativeAsOptional("SF2_IsValidClient"); + MarkNativeAsOptional("SF2_IsClientCritBoosted"); + MarkNativeAsOptional("SF2_IsClientMiniCritBoosted"); + MarkNativeAsOptional("SF2_IsClientUbercharged"); + MarkNativeAsOptional("SF2_IsClientInKart"); + MarkNativeAsOptional("SF2_IsClientInCondition"); + MarkNativeAsOptional("SF2_IsClientEliminated"); + MarkNativeAsOptional("SF2_IsClientInGhostMode"); + MarkNativeAsOptional("SF2_IsClientProxy"); + + MarkNativeAsOptional("SF2_GetClientBlinkCount"); + MarkNativeAsOptional("SF2_IsClientBlinking"); + MarkNativeAsOptional("SF2_GetClientBlinkMeter"); + MarkNativeAsOptional("SF2_SetClientBlinkMeter"); + + MarkNativeAsOptional("SF2_GetClientProxyMaster"); + MarkNativeAsOptional("SF2_GetClientProxyControlAmount"); + MarkNativeAsOptional("SF2_GetClientProxyControlRate"); + MarkNativeAsOptional("SF2_SetClientProxyMaster"); + MarkNativeAsOptional("SF2_SetClientProxyControlAmount"); + MarkNativeAsOptional("SF2_SetClientProxyControlRate"); + + MarkNativeAsOptional("SF2_IsClientLookingAtBoss"); + MarkNativeAsOptional("SF2_DidClientEscape"); + MarkNativeAsOptional("SF2_ForceClientEscape"); + + MarkNativeAsOptional("SF2_GetClientFlashlightBatteryLife"); + MarkNativeAsOptional("SF2_SetClientFlashlightBatteryLife"); + MarkNativeAsOptional("SF2_IsClientUsingFlashlight"); + + MarkNativeAsOptional("SF2_IsClientTrapped"); + MarkNativeAsOptional("SF2_IsClientInDeathCam"); + MarkNativeAsOptional("SF2_ClientStartDeathCam"); + + MarkNativeAsOptional("SF2_ClientSpawnProxy"); + MarkNativeAsOptional("SF2_ClientForceProxy"); + + MarkNativeAsOptional("SF2_CollectAsPage"); + MarkNativeAsOptional("SF2_GetEmptyPageSpawnPoints"); + + MarkNativeAsOptional("SF2_ForceBossJump"); + + MarkNativeAsOptional("SF2_GetBossModelEntity"); + + MarkNativeAsOptional("SF2_GetBossTarget"); + MarkNativeAsOptional("SF2_SetBossTarget"); + + MarkNativeAsOptional("SF2_IsBossStunnable"); + MarkNativeAsOptional("SF2_IsBossStunnableByFlashlight"); + MarkNativeAsOptional("SF2_IsBossCloaked"); + MarkNativeAsOptional("SF2_GetBossStunHealth"); + MarkNativeAsOptional("SF2_SetBossStunHealth"); + + MarkNativeAsOptional("SF2_GetVectorSquareMagnitude"); + MarkNativeAsOptional("SF2_InitiateBossPackVote"); + MarkNativeAsOptional("SF2_IsSurvivalMap"); + MarkNativeAsOptional("SF2_IsBoxingMap"); + MarkNativeAsOptional("SF2_IsRaidMap"); + MarkNativeAsOptional("SF2_IsProxyMap"); + MarkNativeAsOptional("SF2_IsSlaughterRunMap"); + + // gamemodes/renevant.sp + MarkNativeAsOptional("SF2_IsRenevantMap"); + + // npc/npc_chaser.sp + MarkNativeAsOptional("SF2_GetBossCurrentAttackIndex"); + MarkNativeAsOptional("SF2_GetBossAttackIndexType"); + MarkNativeAsOptional("SF2_GetBossAttackIndexDamage"); + MarkNativeAsOptional("SF2_UpdateBossAnimation"); + MarkNativeAsOptional("SF2_GetBossAttackIndexDamageType"); + + MarkNativeAsOptional("SF2_PerformBossVoice"); + MarkNativeAsOptional("SF2_CreateBossSoundHint"); + + MarkNativeAsOptional("SF2_GetChaserProfileFromBossIndex"); + MarkNativeAsOptional("SF2_GetChaserProfileFromName"); + MarkNativeAsOptional("SF2_SetEntityForceChaseState"); + + // npc/npc_creeper.sp + MarkNativeAsOptional("SF2_GetStatueProfileFromBossIndex"); + MarkNativeAsOptional("SF2_GetStatueProfileFromName"); + + // npc/entities/base/entity.sp + MarkNativeAsOptional("SF2_BaseBossEntity.IsValid.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.Controller.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.Target.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.State.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.CurrentChaseDuration.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.CurrentChaseDuration.set"); + MarkNativeAsOptional("SF2_BaseBossEntity.InitialChaseDuration.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.InitialChaseDuration.set"); + MarkNativeAsOptional("SF2_BaseBossEntity.IsKillingSomeone.get"); + MarkNativeAsOptional("SF2_BaseBossEntity.EyePosition"); + MarkNativeAsOptional("SF2_BaseBossEntity.GetProfileName"); + MarkNativeAsOptional("SF2_BaseBossEntity.GetName"); + MarkNativeAsOptional("SF2_BaseBossEntity.ProfileData"); + MarkNativeAsOptional("SF2_BaseBossEntity.ResetProfileAnimation"); + + // npc/entities/base/actions/playsequenceandwait.sp + MarkNativeAsOptional("SF2_PlaySequenceAndWaitAction.SF2_PlaySequenceAndWaitAction"); + + // npc/entities/chaser/entity.sp + MarkNativeAsOptional("SF2_ChaserBossEntity.IsValid.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsAttemptingToMove.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsAttacking.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsStunned.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.StunHealth.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.MaxStunHealth.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.CanBeStunned.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.CanTakeDamage.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsRaging.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsRunningAway.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.IsSelfHealing.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.ProfileData"); + MarkNativeAsOptional("SF2_ChaserBossEntity.PerformVoice"); + MarkNativeAsOptional("SF2_ChaserBossEntity.PerformCustomVoice"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GetDefaultPosture"); + MarkNativeAsOptional("SF2_ChaserBossEntity.SetDefaultPosture"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GetAttackName"); + MarkNativeAsOptional("SF2_ChaserBossEntity.AttackIndex.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GetNextAttackTime"); + MarkNativeAsOptional("SF2_ChaserBossEntity.SetNextAttackTime"); + MarkNativeAsOptional("SF2_ChaserBossEntity.DropItem"); + MarkNativeAsOptional("SF2_ChaserBossEntity.CreateSoundHint"); + + // npc/entities/statue/entity.sp + MarkNativeAsOptional("SF2_StatueBossEntity.IsValid.get"); + MarkNativeAsOptional("SF2_StatueBossEntity.IsMoving.get"); + MarkNativeAsOptional("SF2_StatueBossEntity.LastKillTime.get"); + MarkNativeAsOptional("SF2_StatueBossEntity.ProfileData"); +} +#endif diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 790dc3b4..d94d23c1 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -3093,7 +3093,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss CreateNative("SF2_ChaserBossEntity.GetDefaultPosture", Native_GetDefaultPosture); CreateNative("SF2_ChaserBossEntity.SetDefaultPosture", Native_SetDefaultPosture); CreateNative("SF2_ChaserBossEntity.GetAttackName", Native_GetAttackName); - CreateNative("SF2_ChaserBossEntity.GetAttackIndex", Native_GetAttackIndex); + CreateNative("SF2_ChaserBossEntity.AttackIndex.get", Native_GetAttackIndex); CreateNative("SF2_ChaserBossEntity.GetNextAttackTime", Native_GetNextAttackTime); CreateNative("SF2_ChaserBossEntity.SetNextAttackTime", Native_SetNextAttackTime); CreateNative("SF2_ChaserBossEntity.DropItem", Native_DropItem); @@ -4041,6 +4041,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } SF2_BasePlayer player = SF2_BasePlayer(entity.index); + #if defined DEBUG if (player.IsValid && g_PlayerDebugFlags[player.index] & DEBUG_BOSS_EYES) { float end[3]; @@ -4050,6 +4051,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio TE_SetupBeamPoints(traceStartPos, end, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); TE_SendToClient(player.index); } + #endif chaser.SetIsVisible(entity, false); chaser.SetInFOV(entity, false); @@ -5437,4 +5439,4 @@ static any Native_CreateSoundHint(Handle plugin, int numParams) bossEntity.UpdateAlertTriggerCountEx(position); return 0; -} \ No newline at end of file +} From a22756a69f31fb13ba1e805f95b75ddc56da8ba1 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 22:19:42 -0700 Subject: [PATCH 45/86] Alpha 2.7 --- addons/sourcemod/scripting/include/sf2.inc | 17 +- .../scripting/include/sf2/defines.inc | 4 - .../include/sf2/profiles/profiles.inc | 189 +- .../include/sf2/profiles/profiles_chaser.inc | 556 ++++- addons/sourcemod/scripting/sf2.sp | 93 +- addons/sourcemod/scripting/sf2/client.sp | 65 +- .../scripting/sf2/client/deathcam.sp | 84 +- .../scripting/sf2/client/ghostmode.sp | 4 +- .../sourcemod/scripting/sf2/client/music.sp | 6 +- .../sourcemod/scripting/sf2/client/proxy.sp | 4 +- .../sourcemod/scripting/sf2/client/sprint.sp | 11 +- .../sourcemod/scripting/sf2/client/think.sp | 20 +- addons/sourcemod/scripting/sf2/effects.sp | 55 +- .../sf2/entities/sf2_base_projectile.sp | 80 +- .../scripting/sf2/extras/commands.sp | 17 +- .../scripting/sf2/extras/game_events.sp | 62 +- .../sourcemod/scripting/sf2/extras/natives.sp | 36 +- .../scripting/sf2/gamemodes/renevant.sp | 6 + addons/sourcemod/scripting/sf2/mapentities.sp | 40 +- addons/sourcemod/scripting/sf2/menus.sp | 6 +- addons/sourcemod/scripting/sf2/methodmaps.sp | 13 + addons/sourcemod/scripting/sf2/npc.sp | 130 +- .../base/actions/playsequenceandwait.sp | 9 + .../scripting/sf2/npc/entities/base/entity.sp | 21 +- .../sf2/npc/entities/chaser/actions/alert.sp | 57 +- .../sf2/npc/entities/chaser/actions/attack.sp | 35 +- .../entities/chaser/actions/attacks/bullet.sp | 1 - .../entities/chaser/actions/attacks/melee.sp | 55 +- .../sf2/npc/entities/chaser/actions/chase.sp | 4 +- .../entities/chaser/actions/chaseinitial.sp | 5 + .../npc/entities/chaser/actions/chaselayer.sp | 4 +- .../sf2/npc/entities/chaser/actions/death.sp | 19 + .../sf2/npc/entities/chaser/actions/idle.sp | 40 +- .../npc/entities/chaser/actions/mainlayer.sp | 55 +- .../sf2/npc/entities/chaser/actions/smell.sp | 8 +- .../sf2/npc/entities/chaser/actions/stun.sp | 76 +- .../sf2/npc/entities/chaser/entity.sp | 402 +++- .../sf2/npc/entities/statue/actions/chase.sp | 13 +- .../sf2/npc/entities/statue/entity.sp | 128 +- .../sourcemod/scripting/sf2/npc/npc_chaser.sp | 42 +- addons/sourcemod/scripting/sf2/profiles.sp | 53 +- .../scripting/sf2/profiles/profile_chaser.sp | 2 +- .../sf2/profiles/profile_chaser_precache.sp | 160 +- .../sf2/profiles/profiles_boss_functions.sp | 250 ++- addons/sourcemod/scripting/sf2/pvp.sp | 1787 +++++++++++++---- addons/sourcemod/scripting/sf2/stocks.sp | 106 +- 46 files changed, 3637 insertions(+), 1193 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 9a246e77..93709576 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -7,8 +7,8 @@ #include #include -#define PLUGIN_VERSION "1.8.0 M Alpha 2.6" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.6" +#define PLUGIN_VERSION "1.8.0 M Alpha 2.7" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.7" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 @@ -2137,6 +2137,16 @@ methodmap SF2_ChaserBossEntity < SF2_BaseBossEntity */ public native void CreateSoundHint(float position[3]); + /** + * Enables/Disables the ability for bosses to utilize their ground movement as their speed. + * Only use this for situations where the boss does not constantly path towards someone. + */ + property bool GroundSpeedOverride + { + public native get(); + public native set(bool value); + } + /** * Checks if an attack is possible the default way * This only includes FOV and range checks @@ -3924,6 +3934,7 @@ public void __pl_sf2_SetNTVOptional() MarkNativeAsOptional("SF2_ChaserBossEntity.SetNextAttackTime"); MarkNativeAsOptional("SF2_ChaserBossEntity.DropItem"); MarkNativeAsOptional("SF2_ChaserBossEntity.CreateSoundHint"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GroundSpeedOverride"); // npc/entities/statue/entity.sp MarkNativeAsOptional("SF2_StatueBossEntity.IsValid.get"); @@ -3931,4 +3942,4 @@ public void __pl_sf2_SetNTVOptional() MarkNativeAsOptional("SF2_StatueBossEntity.LastKillTime.get"); MarkNativeAsOptional("SF2_StatueBossEntity.ProfileData"); } -#endif +#endif \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/sf2/defines.inc b/addons/sourcemod/scripting/include/sf2/defines.inc index c1754678..801baafa 100644 --- a/addons/sourcemod/scripting/include/sf2/defines.inc +++ b/addons/sourcemod/scripting/include/sf2/defines.inc @@ -1,8 +1,6 @@ #define MASK_RED 33640459 #define MASK_BLUE 33638411 -#define EF_ITEM_BLINK 0x100 - #define FILE_RESTRICTEDWEAPONS "configs/sf2/restrictedweapons.cfg" #define FILE_RESTRICTEDWEAPONS_DATA "data/sf2/restrictedweapons.cfg" @@ -134,6 +132,4 @@ #define SF2_HUD_TEXT_COLOR_B 141 #define SF2_HUD_TEXT_COLOR_A 255 -#define SF2_CHASER_BOSS_MAX_ATTACKS 32 - #define SF2_PROFILE_CHASER_DEFAULT_POSTURE "default" \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 2852cf5a..3f1aef03 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -1709,6 +1709,9 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima { SF2BossProfileAnimationSectionNameData animData; this.Animations = new StringMap(); + ArrayList temp = new ArrayList(sizeof(animData)); + temp.PushArray(animData); + this.Animations.SetValue("deathcorridor", temp); if (kv.JumpToKey("animations")) { this.Gestures = new StringMap(); @@ -1880,6 +1883,12 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima } randomAnimation = preDefinedIndex; } + + if (animDataArray == null || animDataArray.Length <= 0) + { + return false; + } + SF2BossProfileAnimationSectionNameData animData; if (!this.Classic && preDefinedName[0] != '\0') { @@ -2114,6 +2123,27 @@ enum struct SF2BossProfileDescriptionData } } +enum struct SF2BossProfileSlaughterRunData +{ + bool CustomMinimumSpeed[Difficulty_Max]; + float SpawnTime[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.CustomMinimumSpeed[i] = false; + this.SpawnTime[i] = -1.0; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyBoolValues(kv, "custom_minimum_speed", this.CustomMinimumSpeed, this.CustomMinimumSpeed); + GetProfileDifficultyFloatValues(kv, "spawn_time", this.SpawnTime, this.SpawnTime); + } +} + enum struct SF2BossProfileData { ArrayList Models; @@ -2160,19 +2190,6 @@ enum struct SF2BossProfileData float SoundMusicLoop[Difficulty_Max]; - bool EnableSpawnParticles; - char SpawnParticle[PLATFORM_MAX_PATH]; - char SpawnParticleSound[PLATFORM_MAX_PATH]; - float SpawnParticleSoundVolume; - int SpawnParticleSoundPitch; - float SpawnParticleOrigin[3]; - - bool EnableDespawnParticles; - char DespawnParticle[PLATFORM_MAX_PATH]; - char DespawnParticleSound[PLATFORM_MAX_PATH]; - float DespawnParticleSoundVolume; - int DespawnParticleSoundPitch; - bool DeathCam; bool DeathCamScareSound; bool PublicDeathCam; @@ -2380,8 +2397,14 @@ enum struct SF2BossProfileData ArrayList EffectsArray; + StringMap SpawnEffects; + StringMap DespawnEffects; + bool HideDespawnEffectsOnDeath; + SF2BossProfileEyeData EyeData; + SF2BossProfileSlaughterRunData SlaughterRunData; + void Init() { this.Models = null; @@ -2419,18 +2442,6 @@ enum struct SF2BossProfileData this.FestiveLightDistance = 0.0; this.FestiveLightRadius = 0.0; - this.EnableSpawnParticles = false; - this.SpawnParticle[0] = '\0'; - this.SpawnParticleSound[0] = '\0'; - this.SpawnParticleSoundVolume = 1.0; - this.SpawnParticleSoundPitch = 100; - - this.EnableDespawnParticles = false; - this.DespawnParticle[0] = '\0'; - this.DespawnParticleSound[0] = '\0'; - this.DespawnParticleSoundVolume = 1.0; - this.DespawnParticleSoundPitch = 100; - this.BlinkLookRate = 1.0; this.BlinkStaticRate = 1.0; @@ -2616,6 +2627,10 @@ enum struct SF2BossProfileData this.EffectsArray = null; + this.SpawnEffects = null; + this.DespawnEffects = null; + this.HideDespawnEffectsOnDeath = false; + this.EyeData.Init(); for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) @@ -2636,8 +2651,8 @@ enum struct SF2BossProfileData this.InstantKillCooldown[difficulty] = 0.0; - this.StaticRadius[difficulty] = 15.0; - this.StaticRate[difficulty] = 0.6; + this.StaticRadius[difficulty] = 150.0; + this.StaticRate[difficulty] = 0.65; this.StaticRateDecay[difficulty] = 0.2; this.StaticGraceTime[difficulty] = 1.0; @@ -2686,8 +2701,6 @@ enum struct SF2BossProfileData this.FestiveLightPos[i] = 0.0; this.FestiveLightAng[i] = 0.0; - this.SpawnParticleOrigin[i] = 0.0; - this.DeathCamPos[i] = 0.0; this.PushRagdollForce[i] = 0.0; @@ -2719,6 +2732,7 @@ enum struct SF2BossProfileData this.ProxyDeathSounds.PostLoad(); this.PlayerDeathCamOverlaySounds.PostLoad(); this.OutroMusics.PostLoad(); + if (this.CompanionsArray != null && this.CompanionsArray.Length <= 0) { delete this.CompanionsArray; @@ -2733,6 +2747,12 @@ enum struct SF2BossProfileData this.CompanionsArray.SetArray(i, companions, sizeof(companions)); } } + + if (this.EffectsArray != null && this.EffectsArray.Length <= 0) + { + delete this.EffectsArray; + } + if (this.EffectsArray != null) { SF2BossProfileBaseEffectInfo effects; @@ -2743,10 +2763,49 @@ enum struct SF2BossProfileData effects.PostLoad(); } } - if (this.EffectsArray != null && this.EffectsArray.Length <= 0) + + if (this.SpawnEffects != null) { - delete this.EffectsArray; + StringMapSnapshot snapshot = this.SpawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.SpawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + delete snapshot; + } + + if (this.DespawnEffects != null) + { + StringMapSnapshot snapshot = this.DespawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.DespawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + delete snapshot; } + for (int i = 0; i < 10; i++) { if (this.ProxyModels[i] != null && this.ProxyModels[i].Length <= 0) @@ -2770,38 +2829,47 @@ enum struct SF2BossProfileData delete this.ProxyModelsApollyon[i]; } } + if (this.FootstepEventSounds != null && this.FootstepEventSounds.Length <= 0) { delete this.FootstepEventSounds; } + if (this.FootstepEventIndexes != null && this.FootstepEventIndexes.Length <= 0) { delete this.FootstepEventIndexes; } + if (this.EventSounds != null && this.EventSounds.Length <= 0) { delete this.EventSounds; } + if (this.EventIndexes != null && this.EventIndexes.Length <= 0) { delete this.EventIndexes; } + if (this.ProxyWeaponClassNames != null && this.ProxyWeaponClassNames.Length <= 0) { delete this.ProxyWeaponClassNames; } + if (this.ProxyWeaponStats != null && this.ProxyWeaponStats.Length <= 0) { delete this.ProxyWeaponStats; } + if (this.ProxyDeathAnimations != null && this.ProxyDeathAnimations.Length <= 0) { delete this.ProxyDeathAnimations; } + if (this.DeathMessagesArray != null && this.DeathMessagesArray.Length <= 0) { delete this.DeathMessagesArray; } + if (this.PvESpawnMessagesArray != null && this.PvESpawnMessagesArray.Length <= 0) { delete this.PvESpawnMessagesArray; @@ -2831,6 +2899,7 @@ enum struct SF2BossProfileData this.PlayerDeathCamOverlaySounds.Destroy(); this.OutroMusics.Destroy(); this.AnimationData.Destroy(); + if (this.CompanionsArray != null) { SF2BossProfileCompanionsInfo companions; @@ -2842,6 +2911,7 @@ enum struct SF2BossProfileData } delete this.CompanionsArray; } + if (this.EffectsArray != null) { SF2BossProfileBaseEffectInfo effects; @@ -2853,34 +2923,88 @@ enum struct SF2BossProfileData } delete this.EffectsArray; } + + if (this.SpawnEffects != null) + { + StringMapSnapshot snapshot = this.SpawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.SpawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.Destroy(); + } + delete arrayEffects; + } + + delete snapshot; + delete this.SpawnEffects; + } + + if (this.DespawnEffects != null) + { + StringMapSnapshot snapshot = this.DespawnEffects.Snapshot(); + SF2BossProfileBaseEffectInfo effects; + ArrayList arrayEffects; + + char effectSection[128]; + for (int i = 0; i < snapshot.Length; i++) + { + snapshot.GetKey(i, effectSection, sizeof(effectSection)); + this.DespawnEffects.GetValue(effectSection, arrayEffects); + for (int i2 = 0; i2 < arrayEffects.Length; i2++) + { + arrayEffects.GetArray(i2, effects, sizeof(effects)); + effects.Destroy(); + } + delete arrayEffects; + } + + delete snapshot; + delete this.DespawnEffects; + } + if (this.Models != null) { delete this.Models; } + if (this.Names != null) { delete this.Names; } + if (this.DeathMessagesArray != null) { delete this.DeathMessagesArray; } + if (this.PvESpawnMessagesArray != null) { delete this.PvESpawnMessagesArray; } + if (this.ProxyWeaponClassNames != null) { delete this.ProxyWeaponClassNames; } + if (this.ProxyWeaponStats != null) { delete this.ProxyWeaponStats; } + if (this.ProxyDeathAnimations != null) { delete this.ProxyDeathAnimations; } + if (this.FootstepEventSounds != null) { SF2BossProfileSoundInfo soundInfo; @@ -2892,10 +3016,12 @@ enum struct SF2BossProfileData } delete this.FootstepEventSounds; } + if (this.FootstepEventIndexes != null) { delete this.FootstepEventIndexes; } + if (this.EventSounds != null) { SF2BossProfileSoundInfo soundInfo; @@ -2907,6 +3033,7 @@ enum struct SF2BossProfileData } delete this.EventSounds; } + if (this.EventIndexes != null) { delete this.EventIndexes; diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index f7ee6467..ee961b14 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -633,6 +633,7 @@ enum struct SF2ChaserBossProfileAttackData bool CanUseAgainstProps; float Damage[Difficulty_Max]; + float DamagePercent[Difficulty_Max]; float DamageVsProps; float DamageForce[Difficulty_Max]; int DamageType[Difficulty_Max]; @@ -648,6 +649,7 @@ enum struct SF2ChaserBossProfileAttackData float Cooldown[Difficulty_Max]; bool Disappear[Difficulty_Max]; + bool DisappearOnHit[Difficulty_Max]; int Repeat; int MaxRepeats; ArrayList RepeatTimers; @@ -728,6 +730,7 @@ enum struct SF2ChaserBossProfileAttackData ArrayList StartEffects; ArrayList HitEffects; ArrayList MissEffects; + ArrayList KillEffects; ArrayList DamageEffects; bool ImmuneToDamage[Difficulty_Max]; @@ -751,6 +754,7 @@ enum struct SF2ChaserBossProfileAttackData for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { this.Damage[difficulty] = 50.0; + this.DamagePercent[difficulty] = 0.0; this.DamageForce[difficulty] = 0.0; this.DamageType[difficulty] = 128; this.DamageDelay[difficulty] = 0.0; @@ -764,6 +768,7 @@ enum struct SF2ChaserBossProfileAttackData this.Cooldown[difficulty] = 0.0; this.Disappear[difficulty] = false; + this.DisappearOnHit[difficulty] = false; this.IgnoreAlwaysLooking[difficulty] = false; @@ -872,6 +877,7 @@ enum struct SF2ChaserBossProfileAttackData this.StartEffects = null; this.HitEffects = null; this.MissEffects = null; + this.KillEffects = null; this.DamageEffects = null; this.PostureWhitelist = null; @@ -901,6 +907,11 @@ enum struct SF2ChaserBossProfileAttackData delete this.MissEffects; } + if (this.KillEffects != null && this.KillEffects.Length <= 0) + { + delete this.KillEffects; + } + if (this.DamageEffects != null) { SF2ChaserBossProfileDamageEffectData damageData; @@ -944,6 +955,17 @@ enum struct SF2ChaserBossProfileAttackData } } + if (this.KillEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.KillEffects.Length; i++) + { + this.KillEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + if (this.PostureWhitelist != null && this.PostureWhitelist.Length == 0) { delete this.PostureWhitelist; @@ -997,6 +1019,18 @@ enum struct SF2ChaserBossProfileAttackData delete this.MissEffects; } + if (this.KillEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.KillEffects.Length; i++) + { + this.KillEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.KillEffects; + } + if (this.DamageEffects != null) { SF2ChaserBossProfileDamageEffectData damageData; @@ -1117,6 +1151,225 @@ enum struct SF2ChaserBossProfileSmellData } } +enum struct SF2ChaserBossProfileStunData +{ + bool Enabled[Difficulty_Max]; + float Health[Difficulty_Max]; + float Cooldown[Difficulty_Max]; + bool Disappear[Difficulty_Max]; + + bool FlashlightStun[Difficulty_Max]; + float FlashlightStunDamage[Difficulty_Max]; + + bool ChaseInitialOnEnd[Difficulty_Max]; + + bool KeyDrop; + char KeyModel[PLATFORM_MAX_PATH]; + char KeyTrigger[PLATFORM_MAX_PATH]; + + bool ItemDrop[Difficulty_Max]; + int ItemDropType[Difficulty_Max]; + + float AddHealthPerPlayer[Difficulty_Max]; + float AddHealthPerScout[Difficulty_Max]; + float AddHealthPerSoldier[Difficulty_Max]; + float AddHealthPerPyro[Difficulty_Max]; + float AddHealthPerDemoman[Difficulty_Max]; + float AddHealthPerHeavy[Difficulty_Max]; + float AddHealthPerEngineer[Difficulty_Max]; + float AddHealthPerMedic[Difficulty_Max]; + float AddHealthPerSniper[Difficulty_Max]; + float AddHealthPerSpy[Difficulty_Max]; + float AddHealthPerStun[Difficulty_Max]; + float AddSpeedPerStun[Difficulty_Max]; + float AddAccelerationPerStun[Difficulty_Max]; + + ArrayList StartEffects; + ArrayList EndEffects; + + void Init() + { + this.KeyDrop = false; + this.KeyModel = SF_KEYMODEL; + this.KeyTrigger[0] = '\0'; + + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) + { + this.Enabled[difficulty] = false; + this.Health[difficulty] = 50.0; + this.Cooldown[difficulty] = 3.5; + this.Disappear[difficulty] = false; + + this.FlashlightStun[difficulty] = false; + this.FlashlightStunDamage[difficulty] = 0.0; + + this.ChaseInitialOnEnd[difficulty] = false; + + this.ItemDrop[difficulty] = false; + this.ItemDropType[difficulty] = 1; + + this.AddHealthPerPlayer[difficulty] = 0.0; + this.AddHealthPerScout[difficulty] = 0.0; + this.AddHealthPerSoldier[difficulty] = 0.0; + this.AddHealthPerPyro[difficulty] = 0.0; + this.AddHealthPerDemoman[difficulty] = 0.0; + this.AddHealthPerHeavy[difficulty] = 0.0; + this.AddHealthPerEngineer[difficulty] = 0.0; + this.AddHealthPerMedic[difficulty] = 0.0; + this.AddHealthPerSniper[difficulty] = 0.0; + this.AddHealthPerSpy[difficulty] = 0.0; + this.AddHealthPerStun[difficulty] = 0.0; + this.AddSpeedPerStun[difficulty] = 0.0; + this.AddAccelerationPerStun[difficulty] = 0.0; + } + + this.StartEffects = null; + this.EndEffects = null; + } + + void Load(KeyValues kv, bool checkFile) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "health", this.Health, this.Health); + GetProfileDifficultyFloatValues(kv, "cooldown", this.Cooldown, this.Cooldown); + GetProfileDifficultyBoolValues(kv, "disappear", this.Disappear, this.Disappear); + + if (kv.JumpToKey("flashlight_stun")) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.FlashlightStun[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.FlashlightStun, this.FlashlightStun); + GetProfileDifficultyFloatValues(kv, "damage", this.FlashlightStunDamage, this.FlashlightStunDamage); + kv.GoBack(); + } + + GetProfileDifficultyBoolValues(kv, "chase_initial_on_end", this.ChaseInitialOnEnd, this.ChaseInitialOnEnd); + + this.KeyDrop = kv.GetNum("key_drop", this.KeyDrop) != 0; + kv.GetString("key_model", this.KeyModel, sizeof(this.KeyModel), this.KeyModel); + PrecacheModel2(this.KeyModel, _, _, checkFile); + kv.GetString("key_trigger", this.KeyTrigger, sizeof(this.KeyTrigger), this.KeyTrigger); + + GetProfileDifficultyBoolValues(kv, "item_drop", this.ItemDrop, this.ItemDrop); + GetProfileDifficultyNumValues(kv, "item_drop_type", this.ItemDropType, this.ItemDropType); + + if (kv.JumpToKey("effects")) + { + if (kv.JumpToKey("on_start")) + { + this.StartEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.StartEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("on_end")) + { + this.EndEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.EndEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("add_health")) + { + GetProfileDifficultyFloatValues(kv, "player", this.AddHealthPerPlayer, this.AddHealthPerPlayer); + GetProfileDifficultyFloatValues(kv, "scout", this.AddHealthPerScout, this.AddHealthPerScout); + GetProfileDifficultyFloatValues(kv, "soldier", this.AddHealthPerSoldier, this.AddHealthPerSoldier); + GetProfileDifficultyFloatValues(kv, "pyro", this.AddHealthPerPyro, this.AddHealthPerPyro); + GetProfileDifficultyFloatValues(kv, "demoman", this.AddHealthPerDemoman, this.AddHealthPerDemoman); + GetProfileDifficultyFloatValues(kv, "heavy", this.AddHealthPerHeavy, this.AddHealthPerHeavy); + GetProfileDifficultyFloatValues(kv, "engineer", this.AddHealthPerEngineer, this.AddHealthPerEngineer); + GetProfileDifficultyFloatValues(kv, "medic", this.AddHealthPerMedic, this.AddHealthPerMedic); + GetProfileDifficultyFloatValues(kv, "sniper", this.AddHealthPerSniper, this.AddHealthPerSniper); + GetProfileDifficultyFloatValues(kv, "spy", this.AddHealthPerSpy, this.AddHealthPerSpy); + GetProfileDifficultyFloatValues(kv, "on_stun", this.AddHealthPerStun, this.AddHealthPerStun); + kv.GoBack(); + } + + GetProfileDifficultyFloatValues(kv, "add_speed", this.AddSpeedPerStun, this.AddSpeedPerStun); + GetProfileDifficultyFloatValues(kv, "add_acceleration", this.AddAccelerationPerStun, this.AddAccelerationPerStun); + } + + void PostLoad() + { + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + } + + void Destroy() + { + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.StartEffects; + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.EndEffects; + } + } +} + enum struct SF2ChaserBossProfileDeathData { bool Enabled[Difficulty_Max]; @@ -1147,6 +1400,9 @@ enum struct SF2ChaserBossProfileDeathData ArrayList Gibs; int GibSkin; + ArrayList StartEffects; + ArrayList EndEffects; + void Init() { this.RemoveOnDeath = false; @@ -1180,6 +1436,9 @@ enum struct SF2ChaserBossProfileDeathData this.Gibs = null; this.GibSkin = 0; + + this.StartEffects = null; + this.EndEffects = null; } void Load(KeyValues kv, bool checkFile) @@ -1198,6 +1457,48 @@ enum struct SF2ChaserBossProfileDeathData GetProfileDifficultyBoolValues(kv, "item_drop", this.ItemDrop, this.ItemDrop); GetProfileDifficultyNumValues(kv, "item_drop_type", this.ItemDropType, this.ItemDropType); + if (kv.JumpToKey("effects")) + { + if (kv.JumpToKey("on_start")) + { + this.StartEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.StartEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("on_end")) + { + this.EndEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, checkFile); + this.EndEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + kv.GoBack(); + } + if (kv.JumpToKey("add_health")) { GetProfileDifficultyFloatValues(kv, "player", this.AddHealthPerPlayer, this.AddHealthPerPlayer); @@ -1234,12 +1535,61 @@ enum struct SF2ChaserBossProfileDeathData } } + void PostLoad() + { + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effects; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effects, sizeof(effects)); + effects.PostLoad(); + } + } + } + void Destroy() { if (this.Gibs != null) { delete this.Gibs; } + + if (this.StartEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.StartEffects.Length; i++) + { + this.StartEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.StartEffects; + } + + if (this.EndEffects != null) + { + SF2BossProfileBaseEffectInfo effect; + + for (int i = 0; i < this.EndEffects.Length; i++) + { + this.EndEffects.GetArray(i, effect, sizeof(effect)); + effect.Destroy(); + } + delete this.EndEffects; + } } } @@ -1944,6 +2294,155 @@ enum struct SF2ChaserBossProfileIdleData } } +enum struct SF2ChaserBossProfileAlertData +{ + float GraceTime[Difficulty_Max]; + float Duration[Difficulty_Max]; + bool RunOnWander[Difficulty_Max]; + bool RunOnSuspect[Difficulty_Max]; + + bool TurnEnabled[Difficulty_Max]; + float TurnAngle[Difficulty_Max]; + float TurnMinTime[Difficulty_Max]; + float TurnMaxTime[Difficulty_Max]; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Duration[i] = 10.0; + this.GraceTime[i] = 0.5; + this.RunOnSuspect[i] = false; + this.RunOnWander[i] = false; + + this.TurnEnabled[i] = true; + this.TurnAngle[i] = 360.0; + this.TurnMinTime[i] = 1.5; + this.TurnMaxTime[i] = 3.0; + } + } + + void Load(KeyValues kv) + { + GetProfileDifficultyFloatValues(kv, "gracetime", this.GraceTime, this.GraceTime); + GetProfileDifficultyFloatValues(kv, "duration", this.Duration, this.Duration); + GetProfileDifficultyBoolValues(kv, "run_on_wander", this.RunOnWander, this.RunOnWander); + GetProfileDifficultyBoolValues(kv, "run_on_suspect", this.RunOnSuspect, this.RunOnSuspect); + + if (kv.JumpToKey("turn")) + { + GetProfileDifficultyBoolValues(kv, "enabled", this.TurnEnabled, this.TurnEnabled); + GetProfileDifficultyFloatValues(kv, "angle", this.TurnAngle, this.TurnAngle); + GetProfileDifficultyFloatValues(kv, "cooldown_min", this.TurnMinTime, this.TurnMinTime); + GetProfileDifficultyFloatValues(kv, "cooldown_max", this.TurnMaxTime, this.TurnMaxTime); + kv.GoBack(); + } + } +} + +enum struct SF2ChaserBossProfileResistanceData +{ + bool Enabled[Difficulty_Max]; + float Multiplier[Difficulty_Max]; + + ArrayList DamageTypes; + ArrayList HitboxGroups; + ArrayList Weapons; + + void Init() + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = false; + this.Multiplier[i] = 1.0; + } + + this.DamageTypes = null; + this.HitboxGroups = null; + this.Weapons = null; + } + + void Load(KeyValues kv) + { + for (int i = 0; i < Difficulty_Max; i++) + { + this.Enabled[i] = true; + } + GetProfileDifficultyBoolValues(kv, "enabled", this.Enabled, this.Enabled); + GetProfileDifficultyFloatValues(kv, "multiplier", this.Multiplier, this.Multiplier); + char key[64]; + int resistance = -1; + if (kv.JumpToKey("damage_type")) + { + this.DamageTypes = new ArrayList(); + resistance = -1; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + resistance = kv.GetNum(key, -1); + if (resistance == -1) + { + break; + } + this.DamageTypes.Push(resistance); + } + kv.GoBack(); + } + + if (kv.JumpToKey("hitbox")) + { + this.HitboxGroups = new ArrayList(); + resistance = -1; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + resistance = kv.GetNum(key, -1); + if (resistance == -1) + { + break; + } + this.HitboxGroups.Push(resistance); + } + kv.GoBack(); + } + + if (kv.JumpToKey("weapon")) + { + this.Weapons = new ArrayList(); + resistance = -1; + for (int i = 1;; i++) + { + FormatEx(key, sizeof(key), "%d", i); + resistance = kv.GetNum(key, -1); + if (resistance == -1) + { + break; + } + this.Weapons.Push(resistance); + } + kv.GoBack(); + } + } + + void Destroy() + { + if (this.DamageTypes != null) + { + delete this.DamageTypes; + } + + if (this.HitboxGroups != null) + { + delete this.HitboxGroups; + } + + if (this.Weapons != null) + { + delete this.Weapons; + } + } +} + enum struct SF2ChaserBossProfileData { float WalkSpeed[Difficulty_Max]; @@ -1954,11 +2453,6 @@ enum struct SF2ChaserBossProfileData ArrayList AttackPropModels; - float AlertDuration[Difficulty_Max]; - float AlertGracetime[Difficulty_Max]; - bool AlertRunOnHearSound[Difficulty_Max]; - bool AlertRunOnWander[Difficulty_Max]; - float ChaseDuration[Difficulty_Max]; float ChaseDurationAddMaxRange[Difficulty_Max]; float ChaseDurationAddVisibleMin[Difficulty_Max]; @@ -1986,19 +2480,9 @@ enum struct SF2ChaserBossProfileData float WanderEnterTimeMin[Difficulty_Max]; float WanderEnterTimeMax[Difficulty_Max]; - bool StunEnabled; - float StunCooldown[Difficulty_Max]; - float StunHealth[Difficulty_Max]; - float StunHealthPerPlayer; - float StunHealthPerClass[10]; - bool FlashlightStun[Difficulty_Max]; - float FlashlightDamage[Difficulty_Max]; - bool DisappearOnStun; - bool ItemDropOnStun[Difficulty_Max]; - int StunItemDropType[Difficulty_Max]; - bool ChaseInitialOnStun; ArrayList DamageResistances; + SF2ChaserBossProfileStunData StunData; SF2ChaserBossProfileDeathData DeathData; bool KeyDrop; @@ -2143,6 +2627,7 @@ enum struct SF2ChaserBossProfileData SF2ChaserBossProfileCloakData CloakData; SF2ChaserBossProfileIdleData IdleData; + SF2ChaserBossProfileAlertData AlertData; void Init() { @@ -2150,11 +2635,6 @@ enum struct SF2ChaserBossProfileData { this.WalkSpeed[difficulty] = 90.0; - this.AlertDuration[difficulty] = 5.0; - this.AlertGracetime[difficulty] = 0.5; - this.AlertRunOnHearSound[difficulty] = false; - this.AlertRunOnWander[difficulty] = false; - this.ChaseDuration[difficulty] = 10.0; this.ChaseDurationAddMaxRange[difficulty] = 1024.0; this.ChaseDurationAddVisibleMin[difficulty] = 0.01; @@ -2167,20 +2647,13 @@ enum struct SF2ChaserBossProfileData this.SoundPosDistanceTolerance[difficulty] = 512.0; this.CanWander[difficulty] = true; - this.WanderRangeMin[difficulty] = 400.0; - this.WanderRangeMax[difficulty] = 1024.0; + this.WanderRangeMin[difficulty] = 800.0; + this.WanderRangeMax[difficulty] = 1600.0; this.WanderTimeMin[difficulty] = 8.0; this.WanderTimeMax[difficulty] = 12.0; this.WanderEnterTimeMin[difficulty] = 2.0; this.WanderEnterTimeMax[difficulty] = 4.5; - this.StunCooldown[difficulty] = 3.5; - this.StunHealth[difficulty] = 0.0; - this.FlashlightStun[difficulty] = false; - this.FlashlightDamage[difficulty] = 0.0; - this.ItemDropOnStun[difficulty] = false; - this.StunItemDropType[difficulty] = 1; - this.AutoChaseEnabled[difficulty] = false; this.AutoChaseCount[difficulty] = 100; this.AutoChaseAdd[difficulty] = 0; @@ -2237,16 +2710,9 @@ enum struct SF2ChaserBossProfileData this.FlashlightSenses.AddCount[difficulty] = 5; } - this.StunEnabled = false; - this.StunHealthPerPlayer = 0.0; - for (int i = 0; i < 10; i++) - { - this.StunHealthPerClass[i] = 0.0; - } - this.DisappearOnStun = false; - this.ChaseInitialOnStun = false; this.DamageResistances = null; + this.StunData.Init(); this.DeathData.Init(); this.KeyDrop = false; @@ -2373,6 +2839,7 @@ enum struct SF2ChaserBossProfileData this.CloakData.Init(); this.IdleData.Init(); + this.AlertData.Init(); } void PostLoad() @@ -2402,6 +2869,9 @@ enum struct SF2ChaserBossProfileData this.CloakData.PostLoad(); + this.StunData.PostLoad(); + this.DeathData.PostLoad(); + if (this.ProjectilePosOffsets != null && this.ProjectilePosOffsets.Length <= 0) { delete this.ProjectilePosOffsets; @@ -2739,6 +3209,7 @@ enum struct SF2ChaserBossProfileData this.CloakData.Destroy(); + this.StunData.Destroy(); this.DeathData.Destroy(); if (this.Attacks != null) @@ -2771,6 +3242,15 @@ enum struct SF2ChaserBossProfileData if (this.DamageResistances != null) { + SF2ChaserBossProfileResistanceData resistanceData; + + for (int i = 0; i < this.DamageResistances.Length; i++) + { + this.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); + + resistanceData.Destroy(); + } + delete this.DamageResistances; } diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 509682e8..5c6b0cb8 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -45,7 +45,7 @@ bool steamworks; #define TFTeam_Blue 3 #define TFTeam_Boss 5 -#define MAXTF2PLAYERS 36 +#define MAXTF2PLAYERS 101 public Plugin myinfo = { @@ -79,12 +79,12 @@ enum struct FlashlightTemperature char g_SoundNightmareMode[][] = { - "ambient/halloween/thunder_04.wav", - "ambient/halloween/thunder_05.wav", - "ambient/halloween/thunder_08.wav", - "ambient/halloween/mysterious_perc_09.wav", - "ambient/halloween/mysterious_perc_09.wav", - "ambient/halloween/windgust_08.wav" + "#ambient/halloween/thunder_04.wav", + "#ambient/halloween/thunder_05.wav", + "#ambient/halloween/thunder_08.wav", + "#ambient/halloween/mysterious_perc_09.wav", + "#ambient/halloween/mysterious_perc_09.wav", + "#ambient/halloween/windgust_08.wav" }; static const char g_PageCollectDuckSounds[][] = @@ -533,6 +533,8 @@ ConVar g_RenevantMaxWaves; ConVar g_SlaughterRunMapConVar; ConVar g_TimeEscapeSurvivalConVar; ConVar g_SlaughterRunDivisibleTimeConVar; +ConVar g_SlaughterRunDefaultClassRunSpeedConVar; +ConVar g_SlaughterRunMinimumBossRunSpeedConVar; ConVar g_UseAlternateConfigDirectoryConVar; ConVar g_PlayerKeepWeaponsConVar; ConVar g_FullyEnableSpectatorConVar; @@ -652,6 +654,8 @@ PrivateForward g_OnEntityTeleportedPFwd; PrivateForward g_OnAdminMenuCreateOptionsPFwd; PrivateForward g_OnPlayerJumpPFwd; PrivateForward g_OnPlayerSpawnPFwd; +PrivateForward g_OnPlayerTakeDamagePFwd; +PrivateForward g_OnPlayerDeathPrePFwd; PrivateForward g_OnPlayerDeathPFwd; PrivateForward g_OnPlayerPutInServerPFwd; PrivateForward g_OnPlayerDisconnectedPFwd; @@ -684,12 +688,14 @@ Handle g_SDKEquipWearable; Handle g_SDKPlaySpecificSequence; Handle g_SDKPointIsWithin; Handle g_SDKPassesTriggerFilters; -//Handle g_SDKGetSmoothedVelocity; Handle g_SDKLookupBone; Handle g_SDKGetBonePosition; +Handle g_SDKSequenceVelocity; Handle g_SDKStartTouch; Handle g_SDKEndTouch; Handle g_SDKWeaponSwitch; +Handle g_SDKGetWeaponID; +Handle g_SDKIsWeapon; DynamicHook g_DHookWantsLagCompensationOnEntity; DynamicHook g_DHookShouldTransmit; @@ -2565,10 +2571,6 @@ public void OnEntityCreated(int ent, const char[] classname) { RemoveEntity(ent); } - else if (strcmp(classname, "tf_ragdoll") == 0) - { - SetEntProp(ent, Prop_Send, "m_bIceRagdoll", true); - } Call_StartForward(g_OnEntityCreatedPFwd); Call_PushCell(CBaseEntity(ent)); @@ -2576,39 +2578,6 @@ public void OnEntityCreated(int ent, const char[] classname) Call_Finish(); } -MRESReturn Hook_WeaponGetCustomDamageType(int weapon, DHookReturn returnHandle, DHookParam params) -{ - if (!g_Enabled) - { - return MRES_Ignored; - } - - SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(weapon, Prop_Data, "m_hOwnerEntity")); - if (ownerEntity.IsValid && ownerEntity.IsInPvP && IsValidEntity(weapon) && ownerEntity) - { - int customDamageType = returnHandle.Value; - if (customDamageType != -1) - { - MRESReturn hookResult = PvP_GetWeaponCustomDamageType(weapon, ownerEntity.index, customDamageType); - if (hookResult != MRES_Ignored) - { - returnHandle.Value = customDamageType; - return hookResult; - } - } - else - { - return MRES_Ignored; - } - } - else - { - return MRES_Ignored; - } - - return MRES_Ignored; -} - public void OnEntityDestroyed(int ent) { if (!IsValidEntity(ent) || ent <= 0) @@ -3627,11 +3596,11 @@ public void OnClientCookiesCached(int client) public void OnClientPutInServer(int client) { + g_ClientInGame[client] = true; if (!g_Enabled) { if (g_LoadOutsideMapsConVar.BoolValue) { - g_ClientInGame[client] = true; SDKHook(client, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); Call_StartForward(g_OnPlayerPutInServerPFwd); Call_PushCell(SF2_BasePlayer(client)); @@ -3673,16 +3642,12 @@ public void OnClientPutInServer(int client) g_PlayerPageCount[client] = 0; g_PlayerDesiredFOV[client] = 90; - g_ClientInGame[client] = true; - SDKHook(client, SDKHook_PreThink, Hook_ClientPreThink); SDKHook(client, SDKHook_PreThinkPost, Hook_OnFlashlightThink); SDKHook(client, SDKHook_SetTransmit, Hook_ClientSetTransmit); SDKHook(client, SDKHook_TraceAttack, Hook_PvPPlayerTraceAttack); SDKHook(client, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); - SDKHook(client, SDKHook_WeaponEquipPost, Hook_ClientWeaponEquipPost); - g_DHookWantsLagCompensationOnEntity.HookEntity(Hook_Pre, client, Hook_ClientWantsLagCompensationOnEntity); g_DHookShouldTransmit.HookEntity(Hook_Pre, client, Hook_EntityShouldTransmit); @@ -5121,9 +5086,10 @@ void SetPageCount(int num) float originalSpeed, speed, timerCheck; originalSpeed = data.RunSpeed[difficulty] + NPCGetAddSpeed(npcIndex); - if (originalSpeed < 600.0) + float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; + if (originalSpeed < slaughterSpeed) { - originalSpeed = 600.0; + originalSpeed = slaughterSpeed; } if (g_RoundDifficultyModifier > 1.0) { @@ -5150,29 +5116,6 @@ void SetPageCount(int num) { averageTime += (times[i3] / GetRandomFloat(12.0, 22.0)); } - switch (g_DifficultyConVar.IntValue) - { - case Difficulty_Normal: - { - averageTime += 1.0; - } - case Difficulty_Hard: - { - averageTime += 2.0; - } - case Difficulty_Insane: - { - averageTime += 3.0; - } - case Difficulty_Nightmare: - { - averageTime += 4.0; - } - case Difficulty_Apollyon: - { - averageTime += 5.0; - } - } PrintToChatAll("Time before bosses spawn: %f seconds", averageTime); CreateTimer(averageTime, Timer_SlaughterRunSpawnBosses, _, TIMER_FLAG_NO_MAPCHANGE); } diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index 98ea622b..94130405 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -24,21 +24,21 @@ int g_ClientFrame[MAXTF2PLAYERS]; //Nav Data //static CNavArea g_lastNavArea[MAXTF2PLAYERS]; -#include "sf2/client/glow.sp" -#include "sf2/client/interactables.sp" -#include "sf2/client/hints.sp" -#include "sf2/client/think.sp" -#include "sf2/client/static.sp" -#include "sf2/client/blink.sp" -#include "sf2/client/deathcam.sp" -#include "sf2/client/ultravision.sp" -#include "sf2/client/flashlight.sp" -#include "sf2/client/peek.sp" -#include "sf2/client/sprint.sp" -#include "sf2/client/breathing.sp" -#include "sf2/client/ghostmode.sp" -#include "sf2/client/music.sp" -#include "sf2/client/proxy.sp" +#include "client/glow.sp" +#include "client/interactables.sp" +#include "client/hints.sp" +#include "client/think.sp" +#include "client/static.sp" +#include "client/blink.sp" +#include "client/deathcam.sp" +#include "client/ultravision.sp" +#include "client/flashlight.sp" +#include "client/peek.sp" +#include "client/sprint.sp" +#include "client/breathing.sp" +#include "client/ghostmode.sp" +#include "client/music.sp" +#include "client/proxy.sp" void Client_SetupAPI() { @@ -139,7 +139,7 @@ Action Hook_HealthKitOnTouch(int healthKit, int client) return Plugin_Continue; } -Action Hook_ClientSetTransmit(int client,int other) +Action Hook_ClientSetTransmit(int client, int other) { if (!g_Enabled) { @@ -180,6 +180,10 @@ Action Hook_ClientSetTransmit(int client,int other) return Plugin_Handled; } } + else + { + + } } return Plugin_Continue; @@ -221,16 +225,7 @@ public Action TF2_CalcIsAttackCritical(int client, int weapon, char[] weaponName return Plugin_Continue; } -void Hook_ClientWeaponEquipPost(int client, int weapon) -{ - if (!IsValidClient(client) || !IsValidEdict(weapon)) - { - return; - } - g_DHookWeaponGetCustomDamageType.HookEntity(Hook_Pre, weapon, Hook_WeaponGetCustomDamageType); -} - -Action Hook_TEFireBullets(const char[] te_name,const int[] players,int numClients, float delay) +Action Hook_TEFireBullets(const char[] te_name, const int[] players, int numClients, float delay) { if (!g_Enabled) { @@ -614,6 +609,7 @@ void ClientProcessVisibility(int client) if (chaser.IsValid() && chaser.State == STATE_IDLE || chaser.State == STATE_ALERT) { SF2_BasePlayer(client).SetForceChaseState(SF2NPC_BaseNPC(i), true); + SetTargetMarkState(SF2NPC_BaseNPC(i), CBaseEntity(client), true); } } @@ -725,6 +721,15 @@ void ClientProcessVisibility(int client) } } + if (bossNewStatic != -1) + { + SF2_ChaserEntity chaser = SF2_ChaserEntity(NPCGetEntIndex(bossNewStatic)); + if (chaser.IsValid() && chaser.State == STATE_DEATH) + { + bossNewStatic = -1; + } + } + if (bossNewStatic != -1) { int copyMaster = NPCGetFromUniqueID(g_SlenderCopyMaster[bossNewStatic]); @@ -1088,10 +1093,10 @@ void ClientResetJumpScare(int client) #endif } -void ClientDoJumpScare(int client,int bossIndex, float flLifeTime) +void ClientDoJumpScare(int client, int bossIndex, float lifeTime) { g_PlayerJumpScareBoss[client] = NPCGetUniqueID(bossIndex); - g_PlayerJumpScareLifeTime[client] = GetGameTime() + flLifeTime; + g_PlayerJumpScareLifeTime[client] = GetGameTime() + lifeTime; char profile[SF2_MAX_PROFILE_NAME_LENGTH]; NPCGetProfile(bossIndex, profile, sizeof(profile)); @@ -1109,7 +1114,7 @@ void ClientDoJumpScare(int client,int bossIndex, float flLifeTime) // SCARE FUNCTIONS // ========================================================== -void ClientPerformScare(int client,int bossIndex) +void ClientPerformScare(int client, int bossIndex) { if (NPCGetUniqueID(bossIndex) == -1) { @@ -1175,7 +1180,7 @@ void ClientPerformScare(int client,int bossIndex) Call_Finish(); } -static void ClientPerformSightSound(int client,int bossIndex) +static void ClientPerformSightSound(int client, int bossIndex) { if (NPCGetUniqueID(bossIndex) == -1) { diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index e5a75423..9b62acab 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -119,25 +119,28 @@ static void ClientResetDeathCam(int client) if (deathCamBoss != -1) { - SF2BossProfileData data; - data = NPCGetProfileData(deathCamBoss); - if (data.DeathCamData.Enabled && data.DeathCamData.Blackout) + if ((NPCGetFlags(deathCamBoss) & SFF_FAKE) == 0) { - UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); - CreateTimer(0.1, Timer_DeleteRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - TeleportEntity(client, {16000.0, 16000.0, 16000.0}); - - SF2BossProfileSoundInfo soundInfo; - soundInfo = data.DeathCamData.ExecutionSounds; - if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + SF2BossProfileData data; + data = NPCGetProfileData(deathCamBoss); + if (data.DeathCamData.Enabled && data.DeathCamData.Blackout) { - for (int i = 0; i < soundInfo.Paths.Length; i++) + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + CreateTimer(0.1, Timer_DeleteRagdoll, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + TeleportEntity(client, {16000.0, 16000.0, 16000.0}); + + SF2BossProfileSoundInfo soundInfo; + soundInfo = data.DeathCamData.ExecutionSounds; + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) { - soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER, _, _, _, i); + for (int i = 0; i < soundInfo.Paths.Length; i++) + { + soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER, _, _, _, i); + } } - } - UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + UTIL_ScreenFade(client, 10, 0, 0x0002 | 0x0010 | 0x0008, 0, 0, 0, 255); + } } Call_StartForward(g_OnClientEndDeathCamFwd); @@ -203,6 +206,7 @@ void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool { return; } + int difficulty = GetLocalGlobalDifficulty(bossIndex); GetClientAbsOrigin(client, g_PlayerOriginalDeathcamPosition[client]); @@ -225,14 +229,17 @@ void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool soundInfo = data.ClientDeathCamSounds; soundInfo.EmitSound(true, client, SOUND_FROM_PLAYER); - soundInfo = data.GlobalDeathCamSounds; - for (int i = 0; i <= MaxClients; i++) + if ((NPCGetFlags(bossIndex) & SFF_FAKE) == 0) { - if (!IsValidClient(i)) + soundInfo = data.GlobalDeathCamSounds; + for (int i = 0; i <= MaxClients; i++) { - continue; + if (!IsValidClient(i)) + { + continue; + } + soundInfo.EmitSound(true, i); } - soundInfo.EmitSound(true, i); } // Call our forward. @@ -241,25 +248,32 @@ void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool Call_PushCell(bossIndex); Call_Finish(); - if ((!NPCHasDeathCamEnabled(bossIndex) || antiCamp) && !(NPCGetFlags(bossIndex) & SFF_FAKE)) + if ((NPCGetFlags(bossIndex) & SFF_FAKE) == 0) { - SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. - - if (NPCHasAttribute(bossIndex, SF2Attribute_IgnitePlayerOnDeath)) + if ((!NPCHasDeathCamEnabled(bossIndex) || antiCamp)) { - TF2_IgnitePlayer(client, client); - } + SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. - CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); - float damage = float(GetEntProp(client, Prop_Send, "m_iHealth")) * 4.0; - SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, damage, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); - ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. - KillClient(client); - return; + if (NPCHasAttribute(bossIndex, SF2Attribute_IgnitePlayerOnDeath)) + { + TF2_IgnitePlayer(client, client); + } + + CBaseEntity boss = CBaseEntity(NPCGetEntIndex(bossIndex)); + float damage = float(GetEntProp(client, Prop_Send, "m_iHealth")) * 4.0; + SDKHooks_TakeDamage(client, boss.IsValid() ? boss.index : 0, boss.IsValid() ? boss.index : 0, damage, 0x80 | DMG_PREVENT_PHYSICS_FORCE, _, { 0.0, 0.0, 0.0 }); + ForcePlayerSuicide(client); // Sometimes SDKHooks_TakeDamage doesn't work (probably because of point_viewcontrol), the player is still alive and result in a endless round. + KillClient(client); + return; + } } - else if (NPCGetFlags(bossIndex) & SFF_FAKE) + else { - SlenderMarkAsFake(bossIndex); + if (!NPCHasDeathCamEnabled(bossIndex)) + { + SlenderMarkAsFake(bossIndex); + return; + } } g_PlayerDeathCamBoss[client] = NPCGetUniqueID(bossIndex); @@ -456,7 +470,7 @@ static void StopDeathCam(int client) SF2NPC_BaseNPC deathCamBoss = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_PlayerDeathCamBoss[client])); if (deathCamBoss != SF2_INVALID_NPC) { - if (!(deathCamBoss.Flags & SFF_FAKE)) + if ((deathCamBoss.Flags & SFF_FAKE) == 0) { if (deathCamBoss.HasAttribute(SF2Attribute_IgnitePlayerOnDeath)) { @@ -484,6 +498,7 @@ static void StopDeathCam(int client) } else { + deathCamBoss.MarkAsFake(); SetEntityMoveType(client, MOVETYPE_WALK); TeleportEntity(client, g_PlayerOriginalDeathcamPosition[client], NULL_VECTOR, { 0.0, 0.0, 0.0 }); int light = ClientGetFlashlightEntity(client); @@ -498,6 +513,7 @@ static void StopDeathCam(int client) SetVariantString("!activator"); AcceptEntityInput(light, "SetParent", client); } + PrintToChatAll("Fake"); } } else // The boss is invalid? But the player got a death cam? diff --git a/addons/sourcemod/scripting/sf2/client/ghostmode.sp b/addons/sourcemod/scripting/sf2/client/ghostmode.sp index e1141430..6a387e9c 100644 --- a/addons/sourcemod/scripting/sf2/client/ghostmode.sp +++ b/addons/sourcemod/scripting/sf2/client/ghostmode.sp @@ -199,7 +199,7 @@ void ClientHandleGhostMode(int client, bool forceSpawn = false) SetEntProp(client, Prop_Send, "m_usSolidFlags", 4); SetEntProp(client, Prop_Data, "m_nSolidType", 0); SetEntPropEnt(client, Prop_Send, "m_hGroundEntity", -1); - SetEntityCollisionGroup(client, COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityCollisionGroup(client, 26); SetEntityFlags(client, GetEntityFlags(client) | FL_NOTARGET); SetEntityRenderMode(client, RENDER_TRANSCOLOR); SetEntityRenderColor(client, _, _, _, 0); @@ -402,7 +402,7 @@ Action Timer_GhostModeConnectionCheck(Handle timer, any userid) TF2_RespawnPlayer(client); char authString[128]; - GetClientAuthId(client,AuthId_Engine, authString, sizeof(authString)); + GetClientAuthId(client, AuthId_Engine, authString, sizeof(authString)); LogSF2Message("Removed %N (%s) from ghost mode due to timing out for %f seconds", client, authString, g_GhostModeConnectionToleranceConVar.FloatValue); diff --git a/addons/sourcemod/scripting/sf2/client/music.sp b/addons/sourcemod/scripting/sf2/client/music.sp index 5f926e89..d0d3458f 100644 --- a/addons/sourcemod/scripting/sf2/client/music.sp +++ b/addons/sourcemod/scripting/sf2/client/music.sp @@ -329,7 +329,7 @@ void ClientUpdateMusicSystem(int client, bool initialize = false) { target.GetAbsOrigin(buffer2); - if ((chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN) && + if ((chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || (chaser.State == STATE_STUN && (chaser.PreviousState == STATE_CHASE || chaser.PreviousState == STATE_ATTACK))) && !(NPCGetFlags(i) & SFF_MARKEDASFAKE)) { GetChaserProfileChaseMusics(profile, soundInfo); @@ -357,7 +357,7 @@ void ClientUpdateMusicSystem(int client, bool initialize = false) } } - if (chaser.State == STATE_ALERT) + if (chaser.State == STATE_ALERT || (chaser.State == STATE_STUN && chaser.PreviousState == STATE_ALERT)) { GetChaserProfileAlertMusics(profile, soundInfo); soundList = soundInfo.Paths; @@ -375,7 +375,7 @@ void ClientUpdateMusicSystem(int client, bool initialize = false) } } - if (chaser.State == STATE_IDLE) + if (chaser.State == STATE_IDLE || (chaser.State == STATE_STUN && chaser.PreviousState == STATE_IDLE)) { GetChaserProfileIdleMusics(profile, soundInfo); soundList = soundInfo.Paths; diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 14367353..495ed120 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -16,7 +16,7 @@ static char g_ClientProxyModelApollyon[MAXTF2PLAYERS][PLATFORM_MAX_PATH]; void SetupProxy() { - g_OnGamemodeStartPFwd.AddFunction(null, OnGamemodeStart); + g_OnMapStartPFwd.AddFunction(null, MapStart); g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); @@ -26,7 +26,7 @@ void SetupProxy() AddNormalSoundHook(Hook_ProxySoundHook); } -static void OnGamemodeStart() +static void MapStart() { for (int i = 1; i <= 18; i++) { diff --git a/addons/sourcemod/scripting/sf2/client/sprint.sp b/addons/sourcemod/scripting/sf2/client/sprint.sp index fd466359..4b327ce8 100644 --- a/addons/sourcemod/scripting/sf2/client/sprint.sp +++ b/addons/sourcemod/scripting/sf2/client/sprint.sp @@ -80,7 +80,7 @@ static void OnPlayerSpawn(SF2_BasePlayer client) if (client.IsAlive && !client.IsEliminated && !client.HasEscaped) { - if (SF_IsBoxingMap() || SF_IsRaidMap()) + if (SF_IsBoxingMap() || SF_IsRaidMap() || SF_IsSlaughterRunMap()) { g_WantsToSprint[client.index] = true; } @@ -262,6 +262,11 @@ float ClientGetDefaultSprintSpeed(int client, TFClassType class = TFClass_Unknow } } + if (SF_IsSlaughterRunMap()) + { + returnFloat = g_SlaughterRunDefaultClassRunSpeedConVar.FloatValue; + } + if (IsValidClient(client)) { // Call our forward. @@ -430,7 +435,7 @@ void ClientHandleSprint(int client, bool sprint) return; } - if (SF_IsRaidMap() || SF_IsBoxingMap()) // On these maps we always sprint + if (SF_IsRaidMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap()) // On these maps we always sprint { return; } @@ -611,7 +616,7 @@ static void Hook_SpeedThink(int client) } float walkSpeed, sprintSpeed; - if (!IsClassConfigsValid()) + if (!IsClassConfigsValid() || SF_IsSlaughterRunMap()) { walkSpeed = ClientGetDefaultWalkSpeed(player.index); sprintSpeed = ClientGetDefaultSprintSpeed(player.index); diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index 789d33f2..e4cad356 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -75,7 +75,7 @@ void Hook_ClientPreThink(int client) if (weaponEnt && weaponEnt != INVALID_ENT_REFERENCE) { int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - if (itemDefInt == 775 || itemDefInt == 128) + if ((itemDefInt == 775 || itemDefInt == 128) && GetEntProp(client, Prop_Send, "m_iTauntIndex") == 0) { TF2_RemoveCondition(client, TFCond_Taunting); //Stop suiciding... } @@ -257,6 +257,20 @@ Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float return Plugin_Changed; } + Call_StartForward(g_OnPlayerTakeDamagePFwd); + Call_PushCell(victimPlayer); + Call_PushCellRef(attacker); + Call_PushCellRef(inflictor); + Call_PushFloatRef(damage2); + Call_PushCellRef(damagetype); + Call_Finish(action); + + if (action == Plugin_Changed) + { + damage = damage2; + return Plugin_Changed; + } + TFClassType class = victimPlayer.Class; int classToInt = view_as(class); @@ -380,10 +394,6 @@ Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float { canDamage = true; } - if (IsRoundInWarmup()) - { - canDamage = true; - } if (!IsRoundEnding()) { if (canDamage) diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index d90b3de9..4da83b6f 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -31,7 +31,7 @@ static void EntityDestroyed(CBaseEntity ent, const char[] classname) } } -void SlenderSpawnEffects(ArrayList effects, int bossIndex, bool nonEffects = true, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) +void SlenderSpawnEffects(ArrayList effects, int bossIndex, bool nonEffects = true, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE, bool noParenting = false) { if (bossIndex < 0 || bossIndex >= MAX_BOSSES) { @@ -197,10 +197,11 @@ void SlenderSpawnEffects(ArrayList effects, int bossIndex, bool nonEffects = tru pack.WriteCellArray(overrideAng, sizeof(overrideAng)); pack.WriteCell(output); pack.WriteCell(entityOverride); + pack.WriteCell(noParenting); } else { - SpawnEffect(effectsInfo, bossIndex, overridePos, overrideAng, output, entityOverride); + SpawnEffect(effectsInfo, bossIndex, overridePos, overrideAng, output, entityOverride, noParenting); } } else @@ -222,11 +223,12 @@ static Action Timer_SpawnEffect(Handle timer, DataPack pack) pack.ReadCellArray(overrideAng, sizeof(overrideAng)); ArrayList output = pack.ReadCell(); int entityOverride = pack.ReadCell(); - SpawnEffect(effect, bossIndex, overridePos, overrideAng, output, entityOverride); + bool noParenting = pack.ReadCell(); + SpawnEffect(effect, bossIndex, overridePos, overrideAng, output, entityOverride, noParenting); return Plugin_Stop; } -static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE) +static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, const float overridePos[3] = { 0.0, 0.0, 0.0 }, const float overrideAng[3] = { 0.0, 0.0, 0.0 }, ArrayList &output = null, int entityOverride = INVALID_ENT_REFERENCE, bool noParenting = false) { int slenderEnt = NPCGetEntIndex(bossIndex); int attacher = slenderEnt; @@ -467,26 +469,29 @@ static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, CreateTimer(lifeTime, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); } - if (!attacher || attacher == INVALID_ENT_REFERENCE) + if (!noParenting) { - LogError("Could not parent effect %s of boss %d to itself: boss entity does not exist!", effectsInfo.SectionName, bossIndex); - return; - } + if (!attacher || attacher == INVALID_ENT_REFERENCE) + { + LogError("Could not parent effect %s of boss %d to itself: boss entity does not exist!", effectsInfo.SectionName, bossIndex); + return; + } - SetVariantString("!activator"); - AcceptEntityInput(entity, "SetParent", attacher); - if (effectsInfo.Attachment) - { - if (effectsInfo.AttachmentName[0] != '\0') + SetVariantString("!activator"); + AcceptEntityInput(entity, "SetParent", attacher); + if (effectsInfo.Attachment) { - SetVariantString(effectsInfo.AttachmentName); - if (effectsInfo.Type != EffectType_PropDynamic && effectsInfo.Type != EffectType_PointSpotlight) - { - AcceptEntityInput(entity, "SetParentAttachment"); - } - else + if (effectsInfo.AttachmentName[0] != '\0') { - AcceptEntityInput(entity, "SetParentAttachmentMaintainOffset"); + SetVariantString(effectsInfo.AttachmentName); + if (effectsInfo.Type != EffectType_PropDynamic && effectsInfo.Type != EffectType_PointSpotlight) + { + AcceptEntityInput(entity, "SetParentAttachment"); + } + else + { + AcceptEntityInput(entity, "SetParentAttachmentMaintainOffset"); + } } } } @@ -518,7 +523,10 @@ static void SpawnEffect(SF2BossProfileBaseEffectInfo effectsInfo, int bossIndex, SDKHook(entity, SDKHook_SetTransmit, Hook_EffectTransmit); g_EntityEffectType[entity] = effectsInfo.Type; g_EntityEffectEvent[entity] = effectsInfo.Event; - g_NpcEffectsArray[bossIndex].Push(entity); + if (!noParenting) + { + g_NpcEffectsArray[bossIndex].Push(entity); + } if (output != null) { @@ -592,6 +600,11 @@ static Action Hook_EffectTransmit(int ent, int other) SF2_ChaserEntity slender = SF2_ChaserEntity(GetEntPropEnt(ent, Prop_Send, "moveparent")); + if (!slender.IsValid()) + { + return Plugin_Continue; + } + if (slender.IsValid() && slender.HasCloaked) { return Plugin_Handled; diff --git a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp index 97311d28..f6b6efde 100644 --- a/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/sf2_base_projectile.sp @@ -309,30 +309,6 @@ methodmap SF2_ProjectileBase < CBaseAnimating this.Teleport(NULL_VECTOR, NULL_VECTOR, velocity); } - public float CalculateFallOff(CBaseEntity target) - { - float myPos[3], targetPos[3]; - this.GetAbsOrigin(myPos); - target.WorldSpaceCenter(targetPos); - - float distance = GetVectorDistance(myPos, targetPos, true) / Pow(this.BlastRadius, 2.0); - if (distance > 1.0) - { - distance = 1.0; - } - if (distance < 0.5) - { - distance = 0.5; - } - - float returnValue = this.Damage / distance; - if (returnValue > this.Damage) - { - returnValue = this.Damage; - } - return returnValue; - } - public void GetDamageForce(CBaseEntity target, float buffer[3]) { target.GetAbsVelocity(buffer); @@ -350,6 +326,16 @@ methodmap SF2_ProjectileBase < CBaseAnimating int owner = this.GetPropEnt(Prop_Send, "m_hOwnerEntity"); float pos[3], otherPos[3]; this.GetAbsOrigin(pos); + float adjustedDamage, falloff; + float subtracted[3]; + if (this.BlastRadius) + { + falloff = this.Damage / this.BlastRadius; + } + else + { + falloff = 1.0; + } // Search all valid entities ArrayList hitList = new ArrayList(); @@ -455,24 +441,36 @@ methodmap SF2_ProjectileBase < CBaseAnimating CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_MONSTER | CONTENTS_GRATE, RayType_EndPoint, TraceRayDontHitAnyEntity, this.index); - if (!TR_DidHit() || TR_GetEntityIndex() == valid.index) + if (TR_DidHit() && TR_GetEntityIndex() != valid.index) { - float damage = this.CalculateFallOff(valid); - int flags = DMG_BLAST; - if (this.IsCrits) - { - flags |= DMG_ACID; - } - float force[3]; - this.GetDamageForce(valid, force); - SDKHooks_TakeDamage(valid.index, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, damage, flags, _, force, pos); - if (SF2_BasePlayer(valid.index).IsValid) - { - Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); - Call_PushCell(SF2_BasePlayer(valid.index)); - Call_PushCell(this); - Call_Finish(); - } + continue; + } + + TR_GetEndPosition(subtracted); + + SubtractVectors(pos, subtracted, subtracted); + adjustedDamage = GetVectorLength(subtracted) * falloff; + adjustedDamage = this.Damage - adjustedDamage; + + if (adjustedDamage <= 0.0) + { + continue; + } + + int flags = DMG_BLAST; + if (this.IsCrits) + { + flags |= DMG_ACID; + } + float force[3]; + this.GetDamageForce(valid, force); + SDKHooks_TakeDamage(valid.index, !IsValidEntity(owner) ? this.index : owner, !IsValidEntity(owner) ? this.index : owner, adjustedDamage, flags, _, force, pos); + if (SF2_BasePlayer(valid.index).IsValid) + { + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(SF2_BasePlayer(valid.index)); + Call_PushCell(this); + Call_Finish(); } } diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 06bfcc25..8c0792a0 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -196,8 +196,9 @@ public void OnPluginStart() g_RenevantMaxWaves = CreateConVar("sf2_renevant_maxwaves", valueToString, "Determines the maximum number of waves the Revenant gamemode can use.", _, true, 0.0); g_SlaughterRunMapConVar = CreateConVar("sf2_isslaughterrunmap", "0", "Set to 1 if the map is a slaughter run map.", _, true, 0.0, true, 1.0); - g_SlaughterRunDivisibleTimeConVar = CreateConVar("sf2_slaughterrun_divide_time", "125.0", "Determines how much the average time should be divided by in Slaughter Run, the lower the number, the longer the bosses spawn.", _, true, 0.0); + g_SlaughterRunDefaultClassRunSpeedConVar = CreateConVar("sf2_slaughterrun_class_run_speed", "450.0", "How fast every class will run if Slaughter Run is enabled.", _, true); + g_SlaughterRunMinimumBossRunSpeedConVar = CreateConVar("sf2_slaughterrun_min_boss_run_speed", "600.0", "The speed bosses will run at if their original run speed is less than this value.", _, true); g_UseAlternateConfigDirectoryConVar = CreateConVar("sf2_alternateconfigs", "0", "Set to 1 if the server should pick up the configs from data/.", _, true, 0.0, true, 1.0); @@ -383,6 +384,8 @@ public void OnPluginStart() HookEvent("player_death", Event_PlayerDeathPre, EventHookMode_Pre); HookEvent("player_death", Event_PlayerDeath); HookEvent("player_changeclass", Event_PlayerClass); + HookEvent("player_healed", Event_PlayerHealed, EventHookMode_Pre); + HookEvent("player_healonhit", Event_HealOnHit, EventHookMode_Pre); // Hook entities. HookEntityOutput("info_npc_spawn_destination", "OnUser1", NPCSpawn); @@ -409,6 +412,8 @@ public void OnPluginStart() g_OnEntityTeleportedPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); g_OnPlayerJumpPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); + g_OnPlayerTakeDamagePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_CellByRef, Param_CellByRef, Param_FloatByRef, Param_CellByRef); + g_OnPlayerDeathPrePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); g_OnPlayerDeathPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); g_OnPlayerPutInServerPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerDisconnectedPFwd = new PrivateForward(ET_Ignore, Param_Cell); @@ -2278,16 +2283,6 @@ static Action Command_AddSlender(int client, int args) SF2NPC_BaseNPC npc = AddProfile(profile); if (npc.IsValid()) { - float eyePos[3], eyeAng[3], pos[3]; - GetClientEyePosition(client, eyePos); - GetClientEyeAngles(client, eyeAng); - - Handle trace = TR_TraceRayFilterEx(eyePos, eyeAng, MASK_NPCSOLID, RayType_Infinite, TraceRayDontHitEntity, client); - TR_GetEndPosition(pos, trace); - delete trace; - - npc.Spawn(pos); - if (SF_IsBoxingMap() && (GetRoundState() == SF2RoundState_Escape) && NPCChaserIsBoxingBoss(npc.Index)) { g_SlenderBoxingBossCount++; diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index 34afb1fc..e2e4d491 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -640,6 +640,15 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) { if (!g_Enabled) { + if (g_LoadOutsideMapsConVar.BoolValue && GetClientOfUserId(event.GetInt("userid")) > 0) + { + Call_StartForward(g_OnPlayerDeathPrePFwd); + Call_PushCell(SF2_BasePlayer(GetClientOfUserId(event.GetInt("userid")))); + Call_PushCell(event.GetInt("attacker")); + Call_PushCell(event.GetInt("inflictor_entindex")); + Call_PushCell((event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0); + Call_Finish(); + } return Plugin_Continue; } @@ -663,11 +672,19 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) { owner = GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity"); } + + Call_StartForward(g_OnPlayerDeathPrePFwd); + Call_PushCell(SF2_BasePlayer(client)); + Call_PushCell(event.GetInt("attacker")); + Call_PushCell(inflictor); + Call_PushCell((event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) != 0); + Call_Finish(); + int npcIndex = NPCGetFromEntIndex(owner); if (npcIndex != -1) { int target = GetClientOfUserId(g_SourceTVUserID); - if (!IsValidClient(target) || !IsClientSourceTV(target)) //If the server has a source TV bot uses to print boss' name in kill feed. + if (!IsValidClient(target) || !IsClientSourceTV(target)) // If the server has a source TV bot uses to print boss' name in kill feed. { target = GetClientForDeath(client); } @@ -796,6 +813,7 @@ Action Event_PlayerDeathPre(Event event, const char[] name, bool dB) DebugMessage("EVENT END: Event_PlayerDeathPre"); } #endif + event.BroadcastDisabled = true; return Plugin_Changed; } @@ -1059,3 +1077,45 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) return Plugin_Continue; } + +static bool g_MuteHealOnHit = false; + +Action Event_PlayerHealed(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + int patient = GetClientOfUserId(event.GetInt("patient")); + int healer = GetClientOfUserId(event.GetInt("healer")); + if (!IsValidClient(patient) || !IsValidClient(healer)) + { + return Plugin_Continue; + } + + if ((IsClientInPvP(patient) && IsClientInPvP(healer)) || IsRoundInWarmup()) // Both in PvP + { + event.BroadcastDisabled = true; + g_MuteHealOnHit = true; + return Plugin_Handled; + } + + return Plugin_Continue; +} + +Action Event_HealOnHit(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (g_MuteHealOnHit) + { + event.BroadcastDisabled = true; + g_MuteHealOnHit = false; + return Plugin_Handled; + } + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index de95ee3e..a2b6fd36 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -313,6 +313,36 @@ void SDK_Init() LogError("Failed to setup CBaseAnimating::GetBonePosition call from gamedata"); } + StartPrepSDKCall(SDKCall_Static); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "Studio_SeqVelocity"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef, .encflags = VENCODE_FLAG_COPYBACK); + PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); + if ((g_SDKSequenceVelocity = EndPrepSDKCall()) == null) + { + LogError("Failed to setup Studio_SeqVelocity call from gamedata"); + } + + // From nosoop's TF2 Utils plugin + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTFWeaponBase::GetWeaponID"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + if ((g_SDKGetWeaponID = EndPrepSDKCall()) == null) + { + SetFailState("Failed to setup CTFWeaponBase::GetWeaponID call from gamedata"); + } + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CBaseEntity::IsBaseCombatWeapon"); + PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); + if ((g_SDKIsWeapon = EndPrepSDKCall()) == null) + { + SetFailState("Failed to setup CBaseEntity::IsBaseCombatWeapon call from gamedata"); + } + //Hook_ClientWantsLagCompensationOnEntity int offset = gameData.GetOffset("CTFPlayer::WantsLagCompensationOnEntity"); g_DHookWantsLagCompensationOnEntity = new DynamicHook(offset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity); @@ -664,14 +694,14 @@ static any Native_SetBossTarget(Handle plugin, int numParams) static any Native_IsBossStunnable(Handle plugin, int numParams) { - return SF2NPC_Chaser(GetNativeCell(1)).GetProfileData().StunEnabled; + return SF2NPC_Chaser(GetNativeCell(1)).GetProfileData().StunData.Enabled[1]; } static any Native_IsBossStunnableByFlashlight(Handle plugin, int numParams) { SF2ChaserBossProfileData data; data = NPCChaserGetProfileData(GetNativeCell(1)); - return data.FlashlightStun[1]; + return data.StunData.FlashlightStun[1]; } static any Native_IsBossCloaked(Handle plugin, int numParams) @@ -737,4 +767,4 @@ static any Native_IsProxyMap(Handle plugin, int numParams) static any Native_IsSlaughterRunMap(Handle plugin, int numParams) { return SF_IsSlaughterRunMap(); -} +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp index 243638c9..feb01335 100644 --- a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp +++ b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp @@ -243,6 +243,12 @@ static void Renevant_DoWaveAction(RenevantWave action) { g_RenevantWaveList.Erase(eraseWave); } + + eraseWave = g_RenevantWaveList.FindValue(RenevantWave_IncreaseDifficulty); + if (eraseWave != -1) + { + g_RenevantWaveList.Erase(eraseWave); + } } } } diff --git a/addons/sourcemod/scripting/sf2/mapentities.sp b/addons/sourcemod/scripting/sf2/mapentities.sp index 6539d827..0c58b292 100644 --- a/addons/sourcemod/scripting/sf2/mapentities.sp +++ b/addons/sourcemod/scripting/sf2/mapentities.sp @@ -92,28 +92,28 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) } } -#include "sf2/mapentities/base_spawnpoint.sp" -#include "sf2/mapentities/sf2_game_text.sp" -#include "sf2/mapentities/sf2_gamerules.sp" -#include "sf2/mapentities/sf2_trigger_escape.sp" -#include "sf2/mapentities/sf2_info_player_escapespawn.sp" -#include "sf2/mapentities/sf2_trigger_pvp.sp" -#include "sf2/mapentities/sf2_trigger_pve.sp" -#include "sf2/mapentities/sf2_info_player_pvpspawn.sp" -#include "sf2/mapentities/sf2_info_player_proxyspawn.sp" -#include "sf2/mapentities/sf2_info_boss_spawn.sp" -#include "sf2/mapentities/sf2_info_page_spawn.sp" -#include "sf2/mapentities/sf2_info_page_music.sp" -#include "sf2/mapentities/sf2_logic_proxy.sp" -#include "sf2/mapentities/sf2_logic_raid.sp" -#include "sf2/mapentities/sf2_boss_maker.sp" -#include "sf2/mapentities/sf2_trigger_boss_despawn.sp" +#include "mapentities/base_spawnpoint.sp" +#include "mapentities/sf2_game_text.sp" +#include "mapentities/sf2_gamerules.sp" +#include "mapentities/sf2_trigger_escape.sp" +#include "mapentities/sf2_info_player_escapespawn.sp" +#include "mapentities/sf2_trigger_pvp.sp" +#include "mapentities/sf2_trigger_pve.sp" +#include "mapentities/sf2_info_player_pvpspawn.sp" +#include "mapentities/sf2_info_player_proxyspawn.sp" +#include "mapentities/sf2_info_boss_spawn.sp" +#include "mapentities/sf2_info_page_spawn.sp" +#include "mapentities/sf2_info_page_music.sp" +#include "mapentities/sf2_logic_proxy.sp" +#include "mapentities/sf2_logic_raid.sp" +#include "mapentities/sf2_boss_maker.sp" +#include "mapentities/sf2_trigger_boss_despawn.sp" // Modified only -#include "sf2/mapentities/sf2_logic_arena.sp" -#include "sf2/mapentities/sf2_logic_boxing.sp" -#include "sf2/mapentities/sf2_logic_slaughter.sp" -#include "sf2/mapentities/sf2_point_spotlight.sp" +#include "mapentities/sf2_logic_arena.sp" +#include "mapentities/sf2_logic_boxing.sp" +#include "mapentities/sf2_logic_slaughter.sp" +#include "mapentities/sf2_point_spotlight.sp" void SetupCustomMapEntities() { diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 7d07bd13..04b80690 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -28,9 +28,9 @@ Menu g_MenuCredits5; static ArrayList g_Voters; static bool g_IsRunOff; -#include "sf2/playergroups/menus.sp" -#include "sf2/pvp/menus.sp" -#include "sf2/changelog.sp" +#include "playergroups/menus.sp" +#include "pvp/menus.sp" +#include "changelog.sp" void SetupMenus() { diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index 89481150..68cb4d7c 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -438,6 +438,19 @@ methodmap SF2NPC_BaseNPC NPCSetDefaultTeam(this.Index, value); } } + + property bool WasKilled + { + public get() + { + return NPCGetWasKilled(this.Index); + } + + public set(bool state) + { + NPCSetWasKilled(this.Index, state); + } + } } methodmap SF2_BasePlayer < CBaseCombatCharacter diff --git a/addons/sourcemod/scripting/sf2/npc.sp b/addons/sourcemod/scripting/sf2/npc.sp index fe8bf3e9..f6b5f452 100644 --- a/addons/sourcemod/scripting/sf2/npc.sp +++ b/addons/sourcemod/scripting/sf2/npc.sp @@ -110,7 +110,9 @@ static bool g_NpcShouldBeAffectedBySight[MAX_BOSSES]; static int g_NpcDefaultTeam[MAX_BOSSES]; -Handle timerMusic = null;//Planning to add a bosses array on. +static bool g_NpcWasKilled[MAX_BOSSES]; + +Handle timerMusic = null; //Planning to add a bosses array on. bool NPCGetBossName(int npcIndex = -1, char[] buffer, int bufferLen, char profile[SF2_MAX_PROFILE_NAME_LENGTH] = "") { @@ -363,7 +365,7 @@ static void OnPlayerLookAtBoss(SF2_BasePlayer client, SF2NPC_BaseNPC boss) } } -void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity) +void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity, bool killed = false) { char profile[SF2_MAX_PROFILE_NAME_LENGTH]; controller.GetProfile(profile, sizeof(profile)); @@ -391,9 +393,25 @@ void NPCOnDespawn(SF2NPC_BaseNPC controller, CBaseEntity entity) g_BossPathFollower[bossIndex].Invalidate(); } - if (GetBossProfileDespawnParticleState(profile)) + if (data.DespawnEffects != null) { - SlenderCreateParticleSpawnEffect(bossIndex, true); + if (killed && data.HideDespawnEffectsOnDeath) + { + // Do nothing + } + else + { + StringMapSnapshot snapshot = data.DespawnEffects.Snapshot(); + char key[64]; + snapshot.GetKey(GetRandomInt(0, snapshot.Length - 1), key, sizeof(key)); + ArrayList effects; + data.DespawnEffects.GetValue(key, effects); + float pos[3], ang[3]; + CBaseEntity(NPCGetEntIndex(bossIndex)).GetAbsOrigin(pos); + CBaseEntity(NPCGetEntIndex(bossIndex)).GetAbsAngles(ang); + SlenderSpawnEffects(effects, bossIndex, false, pos, ang, _, _, true); + delete snapshot; + } } if (data.EngineSound[0] != '\0') @@ -1563,6 +1581,16 @@ void NPCSetDefaultTeam(int npcIndex, int team) g_NpcDefaultTeam[npcIndex] = team; } +bool NPCGetWasKilled(int npcIndex) +{ + return g_NpcWasKilled[npcIndex]; +} + +void NPCSetWasKilled(int npcIndex, bool state) +{ + g_NpcWasKilled[npcIndex] = state; +} + bool NPCShouldSeeEntity(int npcIndex, int entity) { if (!IsValidEntity(entity)) @@ -2755,7 +2783,7 @@ void RemoveProfile(int bossIndex) char profile[SF2_MAX_PROFILE_NAME_LENGTH]; controller.GetProfile(profile, sizeof(profile)); - if (MusicActive() && BossHasMusic(profile) && BossMatchesCurrentMusic(profile)) + if (!controller.IsCopy && MusicActive() && BossHasMusic(profile) && BossMatchesCurrentMusic(profile)) { NPCStopMusic(); } @@ -2887,6 +2915,7 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) char profile[SF2_MAX_PROFILE_NAME_LENGTH]; npc.UnSpawn(true); npc.GetProfile(profile,sizeof(profile)); + npc.WasKilled = false; float truePos[3], trueAng[3]; trueAng[1] = GetRandomFloat(0.0, 360.0); @@ -3210,9 +3239,15 @@ void SpawnSlender(SF2NPC_BaseNPC npc, const float pos[3]) _, data.EngineSoundVolume); } - if (GetBossProfileSpawnParticleState(profile)) + if (data.SpawnEffects != null) { - SlenderCreateParticleSpawnEffect(bossIndex); + StringMapSnapshot snapshot = data.SpawnEffects.Snapshot(); + char key[64]; + snapshot.GetKey(GetRandomInt(0, snapshot.Length - 1), key, sizeof(key)); + ArrayList effects; + data.SpawnEffects.GetValue(key, effects); + SlenderSpawnEffects(effects, bossIndex, false); + delete snapshot; } int master = g_SlenderCopyMaster[bossIndex]; @@ -3678,70 +3713,6 @@ void SlenderCastAnimEvent(int bossIndex, int event, int slender) soundInfo.EmitSound(_, slender); } -void SlenderCreateParticleSpawnEffect(int bossIndex, bool despawn = false) -{ - if (bossIndex == -1) - { - return; - } - - int slender = NPCGetEntIndex(bossIndex); - if (!slender || slender == INVALID_ENT_REFERENCE) - { - return; - } - - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - NPCGetProfile(bossIndex, profile, sizeof(profile)); - - char particle[PLATFORM_MAX_PATH], soundEffect[PLATFORM_MAX_PATH]; - float volume; - int pitch; - if (!despawn) - { - GetBossProfileSpawnParticleName(profile, particle, sizeof(particle)); - GetBossProfileSpawnParticleSound(profile, soundEffect, sizeof(soundEffect)); - volume = GetBossProfileSpawnParticleSoundVolume(profile); - pitch = GetBossProfileSpawnParticleSoundPitch(profile); - } - else - { - GetBossProfileDespawnParticleName(profile, particle, sizeof(particle)); - GetBossProfileDespawnParticleSound(profile, soundEffect, sizeof(soundEffect)); - volume = GetBossProfileDespawnParticleSoundVolume(profile); - pitch = GetBossProfileDespawnParticleSoundPitch(profile); - } - - float slenderPosition[3], slenderAngles[3], offsetPos[3]; - GetEntPropVector(slender, Prop_Data, "m_vecAbsOrigin", slenderPosition); - GetEntPropVector(slender, Prop_Data, "m_angAbsRotation", slenderAngles); - GetBossProfileSpawnParticleOrigin(profile, offsetPos); - AddVectors(offsetPos, slenderPosition, slenderPosition); - - if (!DispatchParticleEffect(slender, particle, slenderPosition, slenderAngles, slenderPosition)) - { - int particleEnt = CreateEntityByName("info_particle_system"); - - if (IsValidEntity(particleEnt)) - { - TeleportEntity(particleEnt, slenderPosition, slenderAngles, NULL_VECTOR); - - DispatchKeyValue(particleEnt, "targetname", "tf2particle"); - DispatchKeyValue(particleEnt, "effect_name", particle); - DispatchSpawn(particleEnt); - ActivateEntity(particleEnt); - AcceptEntityInput(particleEnt, "start"); - - CreateTimer(0.1, Timer_KillEntity, particleEnt, TIMER_FLAG_NO_MAPCHANGE); - } - } - - if (soundEffect[0] != '\0') - { - EmitSoundToAll(soundEffect, slender, SNDCHAN_AUTO, SNDLEVEL_SCREAMING, _, volume, pitch); - } -} - // As time passes on, we have to get more aggressive in order to successfully peak the target's // stress level in the allotted duration we're given. Otherwise we'll be forced to place him // in a rest period. @@ -4067,20 +4038,25 @@ static Action Timer_SlenderTeleportThink(Handle timer, any id) SubtractVectors(spawnPos, g_SlenderEyePosOffset[bossIndex], spawnPos); // Look for copies - if (controller.GetProfileData().CopiesInfo.Enabled[difficulty] && canSpawn && (controller.Flags & SFF_NOCOPIES) == 0) + if (controller.GetProfileData().CopiesInfo.Enabled[difficulty] && canSpawn) { float minDistBetweenBosses = GetBossProfileTeleportCopyDistance(profile, difficulty); for (int bossCheck = 0; bossCheck < MAX_BOSSES; bossCheck++) { + SF2NPC_BaseNPC check = SF2NPC_BaseNPC(bossCheck); if (bossCheck == bossIndex || - NPCGetUniqueID(bossCheck) == -1 || - (g_SlenderCopyMaster[bossIndex] != bossCheck && g_SlenderCopyMaster[bossIndex] != g_SlenderCopyMaster[bossCheck])) + !check.IsValid()) + { + continue; + } + + if (check.CopyMaster != controller && controller.CopyMaster != check && controller.CopyMaster != check.CopyMaster) { continue; } - int bossEntCheck = NPCGetEntIndex(bossCheck); + int bossEntCheck = check.EntIndex; if (!bossEntCheck || bossEntCheck == INVALID_ENT_REFERENCE) { continue; @@ -4945,8 +4921,8 @@ bool SpawnProxy(int client, int bossIndex, float teleportPos[3], int &spawnPoint } #include "npc/glow.sp" -#include "sf2/npc/npc_chaser.sp" -#include "sf2/npc/entities/initialize.sp" +#include "npc/npc_chaser.sp" +#include "npc/entities/initialize.sp" static any Native_GetMaxBosses(Handle plugin, int numParams) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp index 5b537301..cdb283d3 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp @@ -120,6 +120,11 @@ static int OnStart(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAc duration *= (1.0 - action.Cycle); } + if (SF2_ChaserEntity(actor.index).IsValid()) + { + SF2_ChaserEntity(actor.index).GroundSpeedOverride = true; + } + action.EndTime = GetGameTime() + duration; return action.Continue(); @@ -129,6 +134,10 @@ static int Update(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, float inte { if (GetGameTime() > action.EndTime) { + if (SF2_ChaserEntity(actor.index).IsValid()) + { + SF2_ChaserEntity(actor.index).GroundSpeedOverride = false; + } return action.Done(); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index 45938bba..21f1e8a5 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -40,6 +40,7 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter .DefineIntField("m_PreviousState") .DefineEntityField("m_Target") .DefineEntityField("m_OldTarget") + .DefineIntField("m_InterruptConditions") .DefineBoolField("m_IsJumping") .DefineBoolField("m_IsEntityVisible", 2049) .DefineBoolField("m_IsEntityInFOV", 2049) @@ -155,6 +156,19 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter } } + property int InterruptConditions + { + public get() + { + return this.GetProp(Prop_Data, "m_InterruptConditions"); + } + + public set(int value) + { + this.SetProp(Prop_Data, "m_InterruptConditions", value); + } + } + property bool IsJumping { public get() @@ -859,7 +873,12 @@ static any Native_GetTarget(Handle plugin, int numParams) } SF2_BaseBoss bossEntity = SF2_BaseBoss(entity); - return bossEntity.Target; + CBaseEntity target = bossEntity.Target; + if (target.index == 0) + { + return CBaseEntity(-1); + } + return target; } static any Native_GetState(Handle plugin, int numParams) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp index d99aad52..c16faa3d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/alert.sp @@ -17,6 +17,7 @@ methodmap SF2_ChaserAlertAction < NextBotAction g_Factory.SetEventCallback(EventResponderType_OnMoveToSuccess, OnMoveToSuccess); g_Factory.SetEventCallback(EventResponderType_OnMoveToFailure, OnMoveToFailure); g_Factory.BeginDataMapDesc() + .DefineBoolField("m_InitialState") .DefineFloatField("m_EndTime") .DefineBoolField("m_IsGoalPosSet") .DefineVectorField("m_GoalPos") @@ -42,6 +43,19 @@ methodmap SF2_ChaserAlertAction < NextBotAction return action; } + property bool InitialState + { + public get() + { + return this.GetData("m_InitialState"); + } + + public set(bool value) + { + this.SetData("m_InitialState", value); + } + } + property float EndTime { public get() @@ -171,8 +185,9 @@ static int OnStart(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, NextBot actor.MovementType = SF2NPCMoveType_Run; } - action.EndTime = gameTime + data.AlertDuration[difficulty]; - action.TimeUntilChase = gameTime + data.AlertGracetime[difficulty]; + action.EndTime = gameTime + data.AlertData.Duration[difficulty]; + action.TimeUntilChase = gameTime + data.AlertData.GraceTime[difficulty]; + action.InitialState = true; if (data.AlertOnAlertInfo.OnChangeState[difficulty]) { @@ -302,15 +317,16 @@ static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float in float pos[3]; actor.GetAlertTriggerPosition(alertTarget, pos); - UpdateAlertPosition(action, actor, pos, data.AlertRunOnHearSound[difficulty]); + UpdateAlertPosition(action, actor, pos, data.AlertData.RunOnSuspect[difficulty]); } } - else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0) + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0 || actor.QueueForAlertState) { float pos[3]; actor.GetAlertTriggerPositionEx(pos); - UpdateAlertPosition(action, actor, pos, data.AlertRunOnHearSound[difficulty]); + UpdateAlertPosition(action, actor, pos, data.AlertData.RunOnSuspect[difficulty]); + actor.QueueForAlertState = false; } if ((interruptConditions & COND_DEBUG) != 0) @@ -318,7 +334,7 @@ static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float in float pos[3]; actor.GetForceWanderPosition(pos); - UpdateAlertPosition(action, actor, pos, data.AlertRunOnWander[difficulty]); + UpdateAlertPosition(action, actor, pos, data.AlertData.RunOnWander[difficulty]); } bool isAbleToWander = action.HasReachedAlertPosition && data.CanWander[difficulty]; @@ -372,7 +388,7 @@ static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float in { wanderArea.GetCenter(wanderPos); - UpdateAlertPosition(action, actor, wanderPos, data.AlertRunOnWander[difficulty]); + UpdateAlertPosition(action, actor, wanderPos, data.AlertData.RunOnWander[difficulty]); } } @@ -398,26 +414,33 @@ static int Update(SF2_ChaserAlertAction action, SF2_ChaserEntity actor, float in } else { - float lookPos[3]; - action.GetLookPosition(lookPos); - loco.FaceTowards(lookPos); - if (action.NextTurnTime <= 0.0) + if (data.AlertData.TurnEnabled[difficulty]) { - action.NextTurnTime = gameTime + GetRandomFloat(1.5, 3.0); + if (!action.InitialState) + { + float lookPos[3]; + action.GetLookPosition(lookPos); + loco.FaceTowards(lookPos); + } + if (action.NextTurnTime <= 0.0) + { + action.NextTurnTime = gameTime + GetRandomFloat(data.AlertData.TurnMinTime[difficulty], data.AlertData.TurnMaxTime[difficulty]); + } } } - if (action.NextTurnTime > 0.0 && action.NextTurnTime <= gameTime) + if (data.AlertData.TurnEnabled[difficulty] && action.NextTurnTime > 0.0 && action.NextTurnTime <= gameTime) { float myPos[3], myAng[3]; actor.GetAbsOrigin(myPos); actor.GetAbsAngles(myAng); + myAng[1] += GetRandomFloat(-data.AlertData.TurnAngle[difficulty], data.AlertData.TurnAngle[difficulty]); float lookAt[3]; - lookAt[0] = GetRandomFloat(-100.0, 100.0); - lookAt[1] = GetRandomFloat(-100.0, 100.0); + lookAt[0] = 50.0; VectorTransform(lookAt, myPos, myAng, lookAt); action.SetLookPosition(lookAt); - action.NextTurnTime = gameTime + GetRandomFloat(1.5, 3.0); + action.NextTurnTime = gameTime + GetRandomFloat(data.AlertData.TurnMinTime[difficulty], data.AlertData.TurnMaxTime[difficulty]); + action.InitialState = false; } if (gameTime >= actor.NextVoiceTime) @@ -496,7 +519,7 @@ static void OnReachedAlertPosition(SF2_ChaserAlertAction action, SF2_ChaserEntit int difficulty = controller.Difficulty; float gameTime = GetGameTime(); - action.NextTurnTime = gameTime + GetRandomFloat(1.5, 3.0); + action.NextTurnTime = gameTime + GetRandomFloat(data.AlertData.TurnMinTime[difficulty], data.AlertData.TurnMaxTime[difficulty]); action.NextWanderTime = gameTime + GetRandomFloat(data.WanderEnterTimeMin[difficulty], data.WanderEnterTimeMax[difficulty]); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp index b9075f25..0b5e620f 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attack.sp @@ -248,15 +248,16 @@ static int OnStart(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, NextBo SlenderSpawnEffects(attackData.StartEffects, controller.Index, false); } - if (attackData.Type == SF2BossAttackType_Custom) + if (attackData.RunSpeed[difficulty] > 0.0) { - return action.Continue(); + actor.MovementType = SF2NPCMoveType_Attack; } - if (attackData.RunSpeed[difficulty] > 0.0) + if (attackData.Type == SF2BossAttackType_Custom) { - actor.MovementType = SF2NPCMoveType_Attack; + return action.Continue(); } + actor.ResetProfileAnimation(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); actor.AddGesture(g_SlenderAnimationsList[SF2BossAnimation_Attack], _, action.GetAttackName()); @@ -329,12 +330,13 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i end = true; } - if (actor.MovementType == SF2NPCMoveType_Attack && attackData.Type != SF2BossAttackType_Custom) + if (actor.MovementType == SF2NPCMoveType_Attack) { if (actor.Teleporters.Length > 0) { CBaseEntity(actor.Teleporters.Get(0)).GetAbsOrigin(targetPos); } + if (!bot.IsRangeLessThanEx(targetPos, 8.0)) { if ((interrputConditions & COND_NEWENEMY) != 0 || path.GetAge() > 0.3 || (path.IsValid() && (path.GetLength() - path.GetCursorPosition()) < 256.0)) @@ -345,6 +347,17 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i } } + if (!path.IsValid()) + { + loco.Stop(); + actor.IsAttemptingToMove = false; + } + else + { + path.Update(bot); + actor.IsAttemptingToMove = true; + } + if (attackData.Type == SF2BossAttackType_Custom) { if (action.ActiveChild == NULL_ACTION || end) @@ -386,17 +399,6 @@ static int Update(SF2_ChaserAttackAction action, SF2_ChaserEntity actor, float i return action.Done("Attack finished"); } - if (!path.IsValid()) - { - loco.Stop(); - actor.IsAttemptingToMove = false; - } - else - { - path.Update(bot); - actor.IsAttemptingToMove = true; - } - return action.Continue(); } @@ -445,6 +447,7 @@ static void OnEnd(SF2_ChaserAttackAction action, SF2_ChaserEntity actor) actor.IsAttacking = false; actor.IsAttemptingToMove = false; actor.CancelAttack = false; + actor.GroundSpeedOverride = false; actor.MovementType = action.OldMovementType; char value[PLATFORM_MAX_PATH]; diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp index 9958a200..b48d9ad4 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/bullet.sp @@ -263,7 +263,6 @@ static void DoBulletAttack(SF2_ChaserEntity actor, const char[] attackName) TE_SendToAll(); SDKHooks_TakeDamage(hitTarget, actor.index, actor.index, attackData.BulletDamage[difficulty], DMG_BULLET, _, CalculateBulletDamageForce(dir, 1.0), endPos); - if (SF2_BasePlayer(hitTarget).IsValid) { attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp index 72853b55..a5da6338 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -298,7 +298,17 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity } hit = true; - SDKHooks_TakeDamage(prop.index, actor.index, actor.index, damage, 64, _, _, myEyePos, false); + + char class[64]; + prop.GetClassname(class, sizeof(class)); + + float realDamage = damage; + if (attackData.DamagePercent[difficulty] > 0.0) + { + realDamage = strcmp(class, "tank_boss", false) != 0 ? float(prop.GetProp(Prop_Send, "m_iMaxHealth")) : float(prop.GetProp(Prop_Data, "m_iMaxHealth")); + realDamage *= attackData.DamagePercent[difficulty]; + } + SDKHooks_TakeDamage(prop.index, actor.index, actor.index, realDamage, 64, _, _, myEyePos, false); } delete targets; @@ -332,9 +342,15 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity NormalizeVector(direction, direction); ScaleVector(direction, force); + float realDamage = damage; + if (attackData.DamagePercent[difficulty] > 0.0) + { + realDamage = float(player.GetProp(Prop_Data, "m_iMaxHealth")) * attackData.DamagePercent[difficulty]; + } + if (controller.HasAttribute(SF2Attribute_DeathCamOnLowHealth) || attackData.DeathCamLowHealth) { - float checkDamage = damage; + float checkDamage = realDamage; if ((damageType & DMG_ACID) && !player.InCondition(TFCond_DefenseBuffed)) { @@ -355,12 +371,7 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity } } - if (attackData.HitEffects != null) - { - SlenderSpawnEffects(attackData.HitEffects, controller.Index, false, _, _, _, player.index); - } - - player.TakeDamage(_, actor.index, actor.index, damage, damageType, _, direction, myEyePos, false); + player.TakeDamage(_, actor.index, actor.index, realDamage, damageType, _, direction, myEyePos, false); player.ViewPunch(viewPunch); if (player.InCondition(TFCond_UberchargedCanteen) && player.InCondition(TFCond_CritOnFirstBlood) && player.InCondition(TFCond_UberBulletResist) && player.InCondition(TFCond_UberBlastResist) && player.InCondition(TFCond_UberFireResist) && player.InCondition(TFCond_MegaHeal)) //Remove Powerplay @@ -383,14 +394,6 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity stressScalar = 1.0; } ClientAddStress(player.index, 0.33 * stressScalar); - - Call_StartForward(g_OnClientDamagedByBossFwd); - Call_PushCell(player.index); - Call_PushCell(controller); - Call_PushCell(actor.index); - Call_PushFloat(damage); - Call_PushCell(damageType); - Call_Finish(); } ArrayList hitSounds = hit ? data.HitSounds : data.MissSounds; @@ -418,11 +421,6 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity phys.AcceptInput("Explode"); RemoveEntity(phys.index); } - - if (attackData.Disappear[difficulty]) - { - controller.UnSpawn(true); - } } else { @@ -431,6 +429,21 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity SlenderSpawnEffects(attackData.MissEffects, controller.Index, false); } } + + if (!SF_IsSlaughterRunMap()) + { + if (attackData.Disappear[difficulty]) + { + controller.UnSpawn(true); + } + else + { + if (hit && attackData.DisappearOnHit[difficulty]) + { + controller.UnSpawn(true); + } + } + } } static void OnAnimationEvent(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity actor, int event) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp index 6550c4ff..c4b697e4 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chase.sp @@ -105,7 +105,7 @@ static int Update(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) if (actor.CurrentChaseDuration <= 0.0) { actor.State = STATE_ALERT; - return action.ChangeTo(SF2_ChaserAlertAction(_, data.AlertRunOnHearSound[difficulty]), "Oh he got away..."); + return action.ChangeTo(SF2_ChaserAlertAction(_, data.AlertData.RunOnSuspect[difficulty]), "Oh he got away..."); } } @@ -177,7 +177,7 @@ static void OnResume(SF2_ChaserChaseAction action, SF2_ChaserEntity actor) SF2NPC_Chaser controller = actor.Controller; if (controller.IsValid()) { - if (controller.GetProfileData().ChaseInitialOnStun) + if (controller.GetProfileData().StunData.ChaseInitialOnEnd[controller.Difficulty]) { actor.PerformVoice(SF2BossSound_ChaseInitial); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp index 2660e801..2f249f60 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaseinitial.sp @@ -20,6 +20,11 @@ methodmap SF2_ChaserChaseInitialAction < NextBotAction public static bool IsPossible(SF2_ChaserEntity actor) { + if (SF_IsSlaughterRunMap()) + { + return false; + } + SF2NPC_BaseNPC baseController = view_as(actor.Controller); SF2BossProfileData data; data = baseController.GetProfileData(); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp index 6f054f47..f6086962 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/chaselayer.sp @@ -47,7 +47,7 @@ static int Update(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, flo ILocomotion loco = bot.GetLocomotionInterface(); bool tooClose = target.IsValid() && - actor.GetIsVisible(SF2_BasePlayer(target.index)) && + (interrputConditions & COND_ENEMYVISIBLE) != 0 && bot.IsRangeLessThan(target.index, 8.0); if (tooClose && path.IsValid()) @@ -101,7 +101,7 @@ static int OnResume(SF2_ChaserChaseLayerAction action, SF2_ChaserEntity actor, N SF2NPC_Chaser controller = actor.Controller; if (controller.IsValid()) { - if (controller.GetProfileData().ChaseInitialOnStun && SF2_ChaserChaseInitialAction.IsPossible(actor)) + if (controller.GetProfileData().StunData.ChaseInitialOnEnd[controller.Difficulty] && SF2_ChaserChaseInitialAction.IsPossible(actor)) { return action.SuspendFor(SF2_ChaserChaseInitialAction()); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp index 3bc06189..1d0942d1 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/death.sp @@ -47,6 +47,8 @@ static NextBotAction InitialContainedAction(SF2_ChaserDeathAction action, SF2_Ch INextBot bot = actor.MyNextBotPointer(); ILocomotion loco = bot.GetLocomotionInterface(); + controller.WasKilled = true; + actor.IsAttemptingToMove = false; loco.Stop(); @@ -80,8 +82,17 @@ static int OnStart(SF2_ChaserDeathAction action, SF2_ChaserEntity actor, NextBot SF2NPC_Chaser controller = actor.Controller; SF2ChaserBossProfileData data; data = controller.GetProfileData(); + actor.GroundSpeedOverride = true; int difficulty = controller.Difficulty; + if (data.DeathData.StartEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.DeathData.StartEffects, controller.Index, false, pos, ang, _, _, true); + } + if (data.DeathData.KeyDrop) { if (SF_IsBoxingMap() && data.BoxingBoss && !g_SlenderBoxingBossIsKilled[controller.Index] && !view_as(controller).GetProfileData().IsPvEBoss) @@ -134,6 +145,14 @@ static void OnEnd(SF2_ChaserDeathAction action, SF2_ChaserEntity actor) SF2ChaserBossProfileData data; data = controller.GetProfileData(); + if (data.DeathData.EndEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.DeathData.EndEffects, controller.Index, false, pos, ang, _, _, true); + } + if (data.DeathData.RemoveOnDeath) { SpawnGibs(actor); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp index 0bc71e0f..b8c2d8b9 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/idle.sp @@ -12,6 +12,7 @@ methodmap SF2_ChaserIdleAction < NextBotAction g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); g_Factory.BeginDataMapDesc() + .DefineBoolField("m_InitialState") .DefineFloatField("m_NextWanderTime") .DefineFloatField("m_NextTurnTime") .DefineVectorField("m_LookPos") @@ -20,6 +21,19 @@ methodmap SF2_ChaserIdleAction < NextBotAction return view_as(g_Factory.Create()); } + property bool InitialState + { + public get() + { + return this.GetData("m_InitialState") != 0; + } + + public set(bool value) + { + this.SetData("m_InitialState", value); + } + } + property float NextWanderTime { public get() @@ -68,6 +82,7 @@ static int OnStart(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, NextBotA action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); action.NextWanderTime = gameTime + GetRandomFloat(data.WanderEnterTimeMin[difficulty], data.WanderEnterTimeMax[difficulty]); + action.InitialState = true; bot.GetLocomotionInterface().Stop(); @@ -184,7 +199,7 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "I saw someone!"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "I saw someone!"); } } @@ -200,9 +215,9 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Someone made noise, let's go!"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Someone made noise, let's go!"); } - else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0) + else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0 || actor.QueueForAlertState) { float pos[3]; actor.GetAlertTriggerPositionEx(pos); @@ -213,7 +228,8 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "We got a sound hint, let's go!"); + actor.QueueForAlertState = false; + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "We got a sound hint, let's go!"); } if ((interruptConditions & COND_DEBUG) != 0) @@ -227,7 +243,7 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnWander[difficulty]), "An admin told me to go here!"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnWander[difficulty]), "An admin told me to go here!"); } if (actor.FollowedCompanionAlert || actor.AlertWithBoss) @@ -246,7 +262,7 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnWander[difficulty]), "One of my mates found something!"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnWander[difficulty]), "One of my mates found something!"); } } @@ -299,7 +315,7 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int actor.NextVoiceTime = 0.0; } actor.WasInBacon = true; - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "What is this smell of bacon?"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "What is this smell of bacon?"); } } @@ -372,9 +388,12 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int { if (data.IdleData.TurnEnabled[difficulty]) { - float lookPos[3]; - action.GetLookPosition(lookPos); - loco.FaceTowards(lookPos); + if (!action.InitialState) + { + float lookPos[3]; + action.GetLookPosition(lookPos); + loco.FaceTowards(lookPos); + } if (action.NextTurnTime <= 0.0) { action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); @@ -393,6 +412,7 @@ static int Update(SF2_ChaserIdleAction action, SF2_ChaserEntity actor, float int VectorTransform(lookAt, myPos, myAng, lookAt); action.SetLookPosition(lookAt); action.NextTurnTime = gameTime + GetRandomFloat(data.IdleData.TurnMinTime[difficulty], data.IdleData.TurnMaxTime[difficulty]); + action.InitialState = false; } if (gameTime >= actor.NextVoiceTime) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp index a4a364f0..bf7dca70 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/mainlayer.sp @@ -116,11 +116,13 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) data = controller.GetProfileData(); SF2BossProfileData originalData; originalData = view_as(controller).GetProfileData(); - if (data.FlashlightStun[difficulty] && actor.CanBeStunned() && actor.CanTakeDamage() && actor.FlashlightTick < gameTime) + if (data.StunData.FlashlightStun[difficulty] && actor.CanBeStunned() && actor.CanTakeDamage() && actor.FlashlightTick < gameTime && + !IsNightVisionEnabled()) { bool inFlashlight = false; float customDamage = 1.0; bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; + int attacker = -1; for (int i = 1; i <= MaxClients; i++) { @@ -135,7 +137,7 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) continue; } - float hisPos[3], myPos[3], eyeAng[3], requiredAng[3]; + float hisPos[3], myPos[3], myEyeAng[3], hisEyeAng[3], requiredAng[3]; player.GetAbsOrigin(hisPos); actor.GetAbsOrigin(myPos); @@ -144,11 +146,12 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) continue; } - player.GetEyeAngles(eyeAng); + actor.GetAbsAngles(myEyeAng); + player.GetEyeAngles(hisEyeAng); SubtractVectors(hisPos, myPos, requiredAng); GetVectorAngles(requiredAng, requiredAng); - if (FloatAbs(AngleDiff(eyeAng[0], requiredAng[0])) <= 35.0 && FloatAbs(AngleDiff(requiredAng[1], eyeAng[1])) < 145.0) + if (FloatAbs(AngleDiff(myEyeAng[1], requiredAng[1])) > 70.0 || FloatAbs(AngleDiff(hisEyeAng[1], requiredAng[1])) < 130.0 || FloatAbs(AngleDiff(requiredAng[0], hisEyeAng[0])) > 30.0) { continue; } @@ -180,21 +183,40 @@ static int Update(SF2_ChaserMainAction action, SF2_ChaserEntity actor) { customDamage = g_ClassFlashlightDamageMultiplier[view_as(player.Class)]; } + attacker = i; break; } - if (SF_SpecialRound(SPECIALROUND_NIGHTVISION) || g_NightvisionEnabledConVar.BoolValue) - { - inFlashlight = false; - } - if (inFlashlight) { - actor.StunHealth -= data.FlashlightDamage[difficulty] * customDamage; + actor.StunHealth -= data.StunData.FlashlightStunDamage[difficulty] * customDamage; actor.FlashlightTick = gameTime + 0.1; + + Event event = CreateEvent("npc_hurt"); + if (event) + { + event.SetInt("entindex", actor.index); + event.SetInt("health", actor.GetProp(Prop_Data, "m_iHealth")); + event.SetInt("damageamount", RoundToFloor(data.StunData.FlashlightStunDamage[difficulty] * customDamage)); + event.SetBool("crit", false); + + if (IsValidClient(attacker)) + { + event.SetInt("attacker_player", GetClientUserId(attacker)); + event.SetInt("weaponid", 0); + } + else + { + event.SetInt("attacker_player", 0); + event.SetInt("weaponid", 0); + } + + event.Fire(); + } + if (actor.StunHealth <= 0.0) { - return action.SuspendFor(SF2_ChaserStunnedAction(), "I was stunned by a flashlight"); + return action.SuspendFor(SF2_ChaserStunnedAction(CBaseEntity(attacker)), "I was stunned by a flashlight"); } } } @@ -433,8 +455,15 @@ static void UnstuckCheck(SF2_ChaserMainAction action, SF2_ChaserEntity actor) float destination[3]; if (!NPCFindUnstuckPosition(actor, lastPos, destination)) { - controller.UnSpawn(true); - return; + float mins[3], maxs[3]; + actor.GetPropVector(Prop_Send, "m_vecMins", mins); + actor.GetPropVector(Prop_Send, "m_vecMaxs", maxs); + float range = ((maxs[0] + maxs[1]) / 2.0) + (FloatAbs(mins[0] + mins[1]) / 2.0); + if (GetVectorDistance(myPos, goalPos, true) > Pow(range + 16.0, 2.0)) + { + controller.UnSpawn(true); + return; + } } action.LastStuckTime = gameTime + 0.75; actor.Teleport(destination, NULL_VECTOR, NULL_VECTOR); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp index feae580e..1501333f 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/smell.sp @@ -106,7 +106,7 @@ static int Update(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotA { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(bestPos, data.AlertRunOnHearSound[difficulty]), "I smelled someone, what is it?"); + return action.ChangeTo(SF2_ChaserAlertAction(bestPos, data.AlertData.RunOnSuspect[difficulty]), "I smelled someone, what is it?"); } else { @@ -144,7 +144,7 @@ static int Update(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotA { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Abort abort, I saw someone!"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Abort abort, I saw someone!"); } } @@ -160,7 +160,7 @@ static int Update(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotA { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Stop! I heard someone!"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Stop! I heard someone!"); } else if ((interruptConditions & COND_ALERT_TRIGGER_POS) != 0) { @@ -173,7 +173,7 @@ static int Update(SF2_ChaserSmellAction action, SF2_ChaserEntity actor, NextBotA { actor.NextVoiceTime = 0.0; } - return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertRunOnHearSound[difficulty]), "Stop! I got a sound hint!"); + return action.ChangeTo(SF2_ChaserAlertAction(pos, data.AlertData.RunOnSuspect[difficulty]), "Stop! I got a sound hint!"); } return action.Continue(); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp index c619ed96..27ff1590 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/stun.sp @@ -21,20 +21,20 @@ methodmap SF2_ChaserStunnedAction < NextBotAction } SF2_ChaserStunnedAction action = view_as(g_Factory.Create()); - action.Attacker = attacker.index; + action.Attacker = attacker; return action; } - property int Attacker + property CBaseEntity Attacker { public get() { - return EntRefToEntIndex(this.GetDataEnt("m_Attacker")); + return CBaseEntity(EntRefToEntIndex(this.GetDataEnt("m_Attacker"))); } - public set(int value) + public set(CBaseEntity value) { - this.SetDataEnt("m_Attacker", EnsureEntRef(value)); + this.SetDataEnt("m_Attacker", EnsureEntRef(value.index)); } } @@ -71,6 +71,7 @@ static NextBotAction InitialContainedAction(SF2_ChaserStunnedAction action, SF2_ } action.OldState = actor.State; + actor.PreviousState = actor.State; actor.IsStunned = true; @@ -96,9 +97,18 @@ static int OnStart(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextB SF2ChaserBossProfileData data; int difficulty = controller.Difficulty; data = controller.GetProfileData(); + + if (data.StunData.StartEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.StunData.StartEffects, controller.Index, false, pos, ang, _, _, true); + } + if (data.KeyDrop) { - if (SF_IsBoxingMap() && data.DisappearOnStun && data.BoxingBoss && !g_SlenderBoxingBossIsKilled[controller.Index] && !view_as(controller).GetProfileData().IsPvEBoss) + if (SF_IsBoxingMap() && data.StunData.Disappear[difficulty] && data.BoxingBoss && !g_SlenderBoxingBossIsKilled[controller.Index] && !view_as(controller).GetProfileData().IsPvEBoss) { g_SlenderBoxingBossKilled++; if ((g_SlenderBoxingBossKilled == g_SlenderBoxingBossCount)) @@ -133,20 +143,34 @@ static int OnStart(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, NextB { actor.MaxStunHealth += controller.GetAttributeValue(SF2Attribute_AddStunHealthOnStun); } + else + { + actor.MaxStunHealth += data.StunData.AddHealthPerStun[difficulty]; + } if (controller.HasAttribute(SF2Attribute_AddSpeedOnStun)) { controller.SetAddSpeed(controller.GetAttributeValue(SF2Attribute_AddSpeedOnStun)); } + else + { + controller.SetAddSpeed(data.StunData.AddSpeedPerStun[difficulty]); + } if (controller.HasAttribute(SF2Attribute_AddAccelerationOnStun)) { controller.SetAddAcceleration(controller.GetAttributeValue(SF2Attribute_AddAccelerationOnStun)); } + else + { + controller.SetAddAcceleration(data.StunData.AddAccelerationPerStun[difficulty]); + } + + actor.GroundSpeedOverride = true; Call_StartForward(g_OnBossStunnedFwd); Call_PushCell(actor.Controller.Index); - Call_PushCell(action.Attacker); + Call_PushCell(action.Attacker.index); Call_Finish(); return action.Continue(); @@ -159,7 +183,7 @@ static int Update(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, float return action.Continue(); } - if (actor.Controller.GetProfileData().DisappearOnStun) + if (actor.Controller.GetProfileData().StunData.Disappear[actor.Controller.Difficulty]) { actor.Controller.UnSpawn(true); } @@ -173,14 +197,40 @@ static int OnSuspend(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor, Nex static void OnEnd(SF2_ChaserStunnedAction action, SF2_ChaserEntity actor) { - actor.IsStunned = false; - actor.StunHealth = actor.MaxStunHealth; - if (actor.Controller.IsValid()) + SF2NPC_Chaser controller = actor.Controller; + + if (!controller.IsValid()) { - actor.NextStunTime = GetGameTime() + actor.Controller.GetProfileData().StunCooldown[actor.Controller.Difficulty]; + return; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); - actor.UpdateMovementAnimation(); + if (data.StunData.EndEffects != null) + { + float pos[3], ang[3]; + actor.GetAbsOrigin(pos); + actor.GetAbsAngles(ang); + SlenderSpawnEffects(data.StunData.EndEffects, controller.Index, false, pos, ang, _, _, true); } + + actor.IsStunned = false; + actor.GroundSpeedOverride = false; + actor.StunHealth = actor.MaxStunHealth; + + actor.NextStunTime = GetGameTime() + data.StunData.Cooldown[actor.Controller.Difficulty]; + + actor.UpdateMovementAnimation(); + actor.WasStunned = true; actor.State = action.OldState; + + if (actor.State == STATE_IDLE && action.Attacker.IsValid()) + { + float pos[3]; + action.Attacker.GetAbsOrigin(pos); + actor.SetAlertTriggerPositionEx(pos); + actor.QueueForAlertState = true; + } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index d94d23c1..983d188a 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -74,7 +74,6 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss .DefineFloatField("m_AttackRunDelay") .DefineFloatField("m_NextVoiceTime") .DefineIntField("m_MovementType") - .DefineIntField("m_InterruptConditions") .DefineIntField("m_AlertTriggerCount", MAXTF2PLAYERS) .DefineVectorField("m_AlertTriggerPosition", MAXTF2PLAYERS) .DefineEntityField("m_AlertTriggerTarget") @@ -111,6 +110,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss .DefineBoolField("m_WasInBacon") .DefineFloatField("m_FlashlightTick") .DefineBoolField("m_ShouldDespawn") + .DefineBoolField("m_GroundSpeedOverride") + .DefineBoolField("m_QueueForAlertState") .EndDataMapDesc(); g_Factory.Install(); @@ -118,6 +119,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerTakeDamagePFwd.AddFunction(null, OnPlayerTakeDamage); + g_OnPlayerDeathPrePFwd.AddFunction(null, OnPlayerDeathPre); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); SF2_ChaserAttackAction.Initialize(); @@ -172,12 +175,17 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss public bool CanBeStunned() { + if (SF_IsSlaughterRunMap() && !view_as(this.Controller).GetProfileData().IsPvEBoss) + { + return false; + } + if (!this.Controller.IsValid()) { return false; } - if (!this.Controller.GetProfileData().StunEnabled) + if (!this.Controller.GetProfileData().StunData.Enabled[this.Controller.Difficulty]) { return false; } @@ -212,6 +220,11 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss public bool CanTakeDamage(CBaseEntity attacker = view_as(-1), CBaseEntity inflictor = view_as(-1), float damage = 0.0) { + if (SF_IsSlaughterRunMap() && !view_as(this.Controller).GetProfileData().IsPvEBoss) + { + return false; + } + if (!this.IsAttacking) { return true; @@ -584,19 +597,6 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } } - property int InterruptConditions - { - public get() - { - return this.GetProp(Prop_Data, "m_InterruptConditions"); - } - - public set(int value) - { - this.SetProp(Prop_Data, "m_InterruptConditions", value); - } - } - public int GetAlertTriggerCount(SF2_BasePlayer player) { return this.GetProp(Prop_Data, "m_AlertTriggerCount", _, player.index); @@ -1058,6 +1058,32 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } } + property bool GroundSpeedOverride + { + public get() + { + return this.GetProp(Prop_Data, "m_GroundSpeedOverride") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_GroundSpeedOverride", value); + } + } + + property bool QueueForAlertState + { + public get() + { + return this.GetProp(Prop_Data, "m_QueueForAlertState") != 0; + } + + public set(bool value) + { + this.SetProp(Prop_Data, "m_QueueForAlertState", value); + } + } + public SF2_BasePlayer GetClosestPlayer() { SF2NPC_Chaser controller = this.Controller; @@ -1792,6 +1818,11 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss float clientWorldSpace[3]; player.WorldSpaceCenter(clientWorldSpace); + if (GetVectorDistance(myWorldSpace, clientWorldSpace, true) > Pow(radius, 2.0)) + { + continue; + } + TR_TraceRayFilter(myWorldSpace, clientWorldSpace, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, RayType_EndPoint, TraceRayDontHitAnyEntity, this.index); @@ -1819,13 +1850,13 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } float amount = shockwaveData.BatteryDrainPercent[difficulty]; - if (amount > 0.0) + if (!IsInfiniteFlashlightEnabled() && amount > 0.0) { player.FlashlightBatteryLife -= amount; } float sprintAmount = shockwaveData.StaminaDrainPercent[difficulty]; - if (sprintAmount > 0.0) + if (!IsInfiniteSprintEnabled() && sprintAmount > 0.0) { player.Stamina -= sprintAmount; } @@ -2789,13 +2820,13 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss SF2ChaserBossProfileData data; data = controller.GetProfileData(); int difficulty = controller.Difficulty; - bool check = !death ? data.ItemDropOnStun[difficulty] : data.DeathData.ItemDrop[difficulty]; + bool check = !death ? data.StunData.ItemDrop[difficulty] : data.DeathData.ItemDrop[difficulty]; if (!check) { return; } - int type = !death ? data.StunItemDropType[difficulty] : data.DeathData.ItemDropType[difficulty]; + int type = !death ? data.StunData.ItemDropType[difficulty] : data.DeathData.ItemDropType[difficulty]; char class[64]; switch (type) { @@ -2896,6 +2927,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss char buffer[PLATFORM_MAX_PATH]; + int difficulty = g_DifficultyConVar.IntValue; + GetSlenderModel(controller.Index, _, buffer, sizeof(buffer)); chaser.SetModel(buffer); chaser.SetRenderMode(view_as(g_SlenderRenderMode[controller.Index])); @@ -2923,10 +2956,12 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss npc.flGravity = 800.0; npc.flDeathDropHeight = 99999.0; npc.flJumpHeight = 512.0; + npc.flFrictionForward = 0.0; + npc.flFrictionSideways = 3.0; npc.flMaxYawRate = originalData.TurnRate; - float addStunHealth = data.StunHealthPerPlayer; + float addStunHealth = data.StunData.AddHealthPerPlayer[difficulty]; float classAdd; int count; for (int i = 1; i <= MaxClients; i++) @@ -2952,53 +2987,53 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss { case TFClass_Scout: { - classAdd += data.StunHealthPerClass[1]; + classAdd += data.StunData.AddHealthPerScout[difficulty]; } case TFClass_Soldier: { - classAdd += data.StunHealthPerClass[3]; + classAdd += data.StunData.AddHealthPerSoldier[difficulty]; } case TFClass_Pyro: { - classAdd += data.StunHealthPerClass[7]; + classAdd += data.StunData.AddHealthPerPyro[difficulty]; } case TFClass_DemoMan: { - classAdd += data.StunHealthPerClass[4]; + classAdd += data.StunData.AddHealthPerDemoman[difficulty]; } case TFClass_Heavy: { - classAdd += data.StunHealthPerClass[6]; + classAdd += data.StunData.AddHealthPerHeavy[difficulty]; } case TFClass_Engineer: { - classAdd += data.StunHealthPerClass[9]; + classAdd += data.StunData.AddHealthPerEngineer[difficulty]; } case TFClass_Medic: { - classAdd += data.StunHealthPerClass[5]; + classAdd += data.StunData.AddHealthPerMedic[difficulty]; } case TFClass_Sniper: { - classAdd += data.StunHealthPerClass[2]; + classAdd += data.StunData.AddHealthPerSniper[difficulty]; } case TFClass_Spy: { - classAdd += data.StunHealthPerClass[8]; + classAdd += data.StunData.AddHealthPerSpy[difficulty]; } } } addStunHealth *= float(count); - chaser.StunHealth = data.StunHealth[controller.Difficulty] + addStunHealth + classAdd; + chaser.StunHealth = data.StunData.Health[controller.Difficulty] + addStunHealth + classAdd; chaser.MaxStunHealth = chaser.StunHealth; locomotion.SetCallback(LocomotionCallback_ShouldCollideWith, LocoCollideWith); @@ -3098,6 +3133,8 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss CreateNative("SF2_ChaserBossEntity.SetNextAttackTime", Native_SetNextAttackTime); CreateNative("SF2_ChaserBossEntity.DropItem", Native_DropItem); CreateNative("SF2_ChaserBossEntity.CreateSoundHint", Native_CreateSoundHint); + CreateNative("SF2_ChaserBossEntity.GroundSpeedOverride.get", Native_GetGroundSpeedOverride); + CreateNative("SF2_ChaserBossEntity.GroundSpeedOverride.set", Native_SetGroundSpeedOverride); } } @@ -3160,6 +3197,109 @@ static void OnPlayerSpawn(SF2_BasePlayer client) } } +static Action OnPlayerTakeDamage(SF2_BasePlayer client, int &attacker, int &inflictor, float &damage, int &damageType) +{ + SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); + if (!boss.IsValid()) + { + return Plugin_Continue; + } + + SF2NPC_Chaser controller = boss.Controller; + if (!controller.IsValid()) + { + return Plugin_Continue; + } + + Call_StartForward(g_OnClientDamagedByBossFwd); + Call_PushCell(client.index); + Call_PushCell(controller); + Call_PushCell(boss.index); + Call_PushFloat(damage); + Call_PushCell(damageType); + Call_Finish(); + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + char attack[64]; + strcopy(attack, sizeof(attack), boss.GetAttackName()); + if (attack[0] != '\0') + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attack, attackData); + + if (attackData.HitEffects != null) + { + SlenderSpawnEffects(attackData.HitEffects, controller.Index, false, _, _, _, client.index); + } + } + + return Plugin_Continue; +} + +static void OnPlayerDeathPre(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (fake) + { + return; + } + + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + int entity = NPCGetEntIndex(i); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(entity); + if (!chaser.IsValid()) + { + continue; + } + SF2NPC_Chaser controller = SF2NPC_Chaser(i); + + int index = controller.ChaseOnLookTargets.FindValue(entity); + if (index != -1) + { + controller.ChaseOnLookTargets.Erase(index); + } + } + + SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); + if (!boss.IsValid()) + { + return; + } + + SF2NPC_Chaser controller = boss.Controller; + if (!controller.IsValid()) + { + return; + } + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + + char attack[64]; + strcopy(attack, sizeof(attack), boss.GetAttackName()); + if (attack[0] != '\0') + { + SF2ChaserBossProfileAttackData attackData; + data.GetAttack(attack, attackData); + + if (attackData.KillEffects != null) + { + SlenderSpawnEffects(attackData.KillEffects, controller.Index, false, _, _, _, client.index); + } + } +} + static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { if (fake) @@ -3467,9 +3607,63 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam SF2ChaserBossProfileData chaserData; chaserData = chaser.Controller.GetProfileData(); int difficulty = chaser.Controller.Difficulty; + bool changed = false; + + if (chaserData.DamageResistances != null) + { + SF2ChaserBossProfileResistanceData resistanceData; + for (int i = 0; i < chaserData.DamageResistances.Length; i++) + { + chaserData.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); + if (resistanceData.DamageTypes == null) + { + continue; + } + + for (int i2 = 0; i2 < resistanceData.DamageTypes.Length; i++) + { + int type = resistanceData.DamageTypes.Get(i); + if (damageType & type || damageType == type) + { + damage *= resistanceData.Multiplier[difficulty]; + changed = true; + break; + } + } + + if (changed) + { + break; + } + } + } if (player.IsValid) { + CBaseEntity activeWeapon = CBaseEntity(player.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (activeWeapon.IsValid() && chaserData.DamageResistances != null) + { + SF2ChaserBossProfileResistanceData resistanceData; + int itemIndex = activeWeapon.GetProp(Prop_Send, "m_iItemDefinitionIndex"); + for (int i = 0; i < chaserData.DamageResistances.Length; i++) + { + chaserData.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); + if (resistanceData.Weapons == null) + { + continue; + } + + if (resistanceData.Weapons.FindValue(itemIndex) == -1) + { + continue; + } + + damage *= resistanceData.Multiplier[difficulty]; + changed = true; + break; + } + } + float myEyePos[3], clientEyePos[3], buffer[3], myAng[3]; player.GetEyePosition(clientEyePos); chaser.Controller.GetEyePosition(myEyePos); @@ -3496,7 +3690,7 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam damageType |= DMG_CRIT; damage *= 0.667; - return Plugin_Changed; + changed = true; } } } @@ -3561,7 +3755,7 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam player.SetProp(Prop_Send, "m_iRevengeCrits", player.GetProp(Prop_Send, "m_iRevengeCrits") + 1); - return Plugin_Changed; + changed = true; } } } @@ -3574,7 +3768,7 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam case 15, 202, 41, 298, 312, 424, 654, 793, 802, 811, 832, 850, 882, 891, 900, 909, 958, 967: // Miniguns { damage *= 0.65; - return Plugin_Changed; + changed = true; } case 40, 1146: // Backburner @@ -3582,14 +3776,14 @@ static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &dam if (FloatAbs(AngleDiff(myAng[1], buffer[1])) >= 60.0 && chaserData.BackstabDamageScale > 0.0) { damageType |= DMG_CRIT; - return Plugin_Changed; + changed = true; } } } } } - return Plugin_Continue; + return changed ? Plugin_Changed : Plugin_Continue; } static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float damage, int damageType, int weaponEntIndex, const float vecDamageForce[3], const float vecDamagePosition[3]) @@ -3856,15 +4050,50 @@ static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float static Action TraceOnHit(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& ammotype, int hitbox, int hitgroup) { + bool changed = false; + if (IsValidClient(attacker)) { if ((damagetype & DMG_USE_HITLOCATIONS) && hitgroup == 1) { damagetype = damagetype | DMG_CRIT; - return Plugin_Changed; + changed = true; } } - return Plugin_Continue; + + SF2_ChaserEntity chaser = SF2_ChaserEntity(victim); + SF2NPC_Chaser controller = chaser.Controller; + if (!controller.IsValid()) + { + return changed ? Plugin_Changed : Plugin_Continue; + } + + int difficulty = controller.Difficulty; + + SF2ChaserBossProfileData data; + data = controller.GetProfileData(); + if (data.DamageResistances != null) + { + SF2ChaserBossProfileResistanceData resistanceData; + for (int i = 0; i < data.DamageResistances.Length; i++) + { + data.DamageResistances.GetArray(i, resistanceData, sizeof(resistanceData)); + if (resistanceData.HitboxGroups == null) + { + continue; + } + + if (resistanceData.HitboxGroups.FindValue(hitgroup) == -1) + { + continue; + } + + damage *= resistanceData.Multiplier[difficulty]; + changed = true; + break; + } + } + return changed ? Plugin_Changed : Plugin_Continue; } static MRESReturn UpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) @@ -4140,13 +4369,17 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (chaser.GetIsVisible(entity)) { - playerInterruptFlags[entity.index] |= COND_ENEMYVISIBLE; if (chaser.GetInFOV(entity)) { playerInterruptFlags[entity.index] |= COND_SAWENEMY; } } + if (isTraceVisible) + { + playerInterruptFlags[entity.index] |= COND_ENEMYVISIBLE; + } + playerDists[entity.index] = dist; if (chaser.SmellPlayerList != null && data.SmellData.Enabled[difficulty]) @@ -4328,11 +4561,13 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (!g_PlayerEliminated[raidTarget]) { bestNewTarget = raidTarget; + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); } } else { bestNewTarget = raidTarget; + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); } } delete arrayRaidTargets; @@ -4364,6 +4599,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio if (IsValidClient(raidTarget) && IsClientInPvE(raidTarget)) { bestNewTarget = raidTarget; + SetClientForceChaseState(controller, CBaseEntity(bestNewTarget), true); } } delete arrayRaidTargets; @@ -4487,12 +4723,11 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) { attackSpeed = 0.0; } - speed = attackSpeed; - float groundSpeed = chaser.GetPropFloat(Prop_Data, "m_flGroundSpeed"); - if (groundSpeed != 0.0 && attackData.RunGroundSpeed[difficulty]) + if (attackData.RunGroundSpeed[difficulty]) { - speed += groundSpeed; + chaser.GroundSpeedOverride = true; } + speed = attackSpeed; acceleration = attackData.RunAcceleration[difficulty]; } } @@ -4520,9 +4755,10 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) } if (SF_IsSlaughterRunMap()) { - if (speed < 580.0) + float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; + if (speed < slaughterSpeed) { - speed = 580.0; + speed = slaughterSpeed; } acceleration += 10000.0; } @@ -4533,9 +4769,46 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) speed = 0.0; } - npc.flWalkSpeed = speed * 0.9; - npc.flRunSpeed = speed; - npc.flAcceleration = acceleration; + if (!chaser.GroundSpeedOverride) + { + npc.flWalkSpeed = speed * 0.9; + npc.flRunSpeed = speed; + npc.flAcceleration = acceleration; + } + else + { + float vel[3], myPos[3], res[3], matrix[3][4], ang[3]; + float cycle = chaser.GetPropFloat(Prop_Data, "m_flCycle"); + if (cycle < 1.0) + { + CBaseNPC_Locomotion loco = npc.GetLocomotion(); + Address poseParams = GetEntityAddress(chaser.index) + view_as
(FindSendPropInfo("CBaseAnimating", "m_flPoseParameter")); + int sequence = chaser.GetProp(Prop_Send, "m_nSequence"); + + CBaseAnimating_GetSequenceVelocity(chaser.GetModelPtr(), sequence, cycle, poseParams, vel); + chaser.GetAbsOrigin(myPos); + chaser.GetLocalAngles(ang); + AngleMatrix(ang, matrix); + VectorRotate(vel, matrix, res); + AddVectors(res, myPos, res); + if (moveType != SF2NPCMoveType_Attack) + { + speed = GetVectorLength(vel) * chaser.GetPropFloat(Prop_Send, "m_flPlaybackRate") * 0.9 * chaser.GetPropFloat(Prop_Send, "m_flModelScale"); + } + else + { + speed *= GetVectorLength(vel) * chaser.GetPropFloat(Prop_Send, "m_flPlaybackRate") * 0.9 * chaser.GetPropFloat(Prop_Send, "m_flModelScale"); + } + npc.flAcceleration = speed * 10.0; + npc.flWalkSpeed = speed; + npc.flRunSpeed = speed; + loco.Run(); + if (moveType != SF2NPCMoveType_Attack) + { + loco.Approach(res, 999999.9); + } + } + } } static void ProcessBody(SF2_ChaserEntity chaser) @@ -4735,6 +5008,16 @@ static bool LocoCollideWith(CBaseNPC_Locomotion loco, int other) INextBot bot = loco.GetBot(); SF2_ChaserEntity chaser = SF2_ChaserEntity(bot.GetEntity()); + if (chaser.Controller.IsValid() && (chaser.Controller.Flags & SFF_MARKEDASFAKE) != 0) + { + return false; + } + + if (chaser.State == STATE_DEATH) + { + return false; + } + char class[64]; GetEntityClassname(other, class, sizeof(class)); if (player.IsValid) @@ -5440,3 +5723,28 @@ static any Native_CreateSoundHint(Handle plugin, int numParams) return 0; } + +static any Native_GetGroundSpeedOverride(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + return bossEntity.GroundSpeedOverride; +} + +static any Native_SetGroundSpeedOverride(Handle plugin, int numParams) +{ + int entity = GetNativeCell(1); + if (!IsValidEntity(entity)) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid entity index %d", entity); + } + + SF2_ChaserEntity bossEntity = SF2_ChaserEntity(entity); + bossEntity.GroundSpeedOverride = GetNativeCell(2); + return 0; +} diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp index e5d3c60f..74817078 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/actions/chase.sp @@ -77,7 +77,14 @@ static int Update(SF2_StatueChaseAction action, SF2_StatueEntity actor) return action.ChangeTo(SF2_StatueIdleAction(), "Our target escaped, that is no good!"); } - if (actor.GetIsVisible(player)) + if (player.IsValid && player.ShouldBeForceChased(controller)) + { + player.SetForceChaseState(controller, false); + } + + bool visible = (actor.InterruptConditions & COND_ENEMYVISIBLE) != 0; + + if (visible) { float maxRange = data.ChaseDurationAddMaxRange[difficulty]; if (maxRange > 0.0 && player.IsValid && player.CanSeeSlender(controller.Index, false, _, !attackEliminated)) @@ -112,7 +119,7 @@ static int Update(SF2_StatueChaseAction action, SF2_StatueEntity actor) } bool tooClose = target.IsValid() && - actor.GetIsVisible(SF2_BasePlayer(target.index)) && + visible && bot.IsRangeLessThan(target.index, 8.0); if ((tooClose || !actor.IsMoving) && path.IsValid()) @@ -150,7 +157,7 @@ static int Update(SF2_StatueChaseAction action, SF2_StatueEntity actor) { g_SlenderStatueIdleLifeTime[controller.Index] = gameTime + data.IdleLifeTime[difficulty]; - if (bot.GetRangeSquaredTo(target.index) <= Pow(originalData.InstantKillRadius, 2.0) && actor.GetIsVisible(player)) + if (bot.GetRangeSquaredTo(target.index) <= Pow(originalData.InstantKillRadius, 2.0) && visible) { if (controller.Flags & SFF_FAKE) { diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index da2ce9e4..03d8fc35 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -88,7 +88,7 @@ methodmap SF2_StatueEntity < SF2_BaseBoss INextBot bot = this.MyNextBotPointer(); ILocomotion loco = bot.GetLocomotionInterface(); - if (!this.GetIsVisible(player)) + if ((this.InterruptConditions & COND_ENEMYVISIBLE) == 0) { return; } @@ -187,12 +187,18 @@ static void OnCreate(SF2_StatueEntity ent) ent.LastKillTime = 0.0; SDKHook(ent.index, SDKHook_Think, Think); SDKHook(ent.index, SDKHook_ThinkPost, ThinkPost); + SetEntityTransmitState(ent.index, FL_EDICT_FULLCHECK); + g_DHookShouldTransmit.HookEntity(Hook_Pre, ent.index, ShouldTransmit); + g_DHookUpdateTransmitState.HookEntity(Hook_Pre, ent.index, UpdateTransmitState); } static Action Think(int entIndex) { SF2_StatueEntity statue = SF2_StatueEntity(entIndex); - statue.Target = ProcessVision(statue); + int interruptConditions = 0; + CBaseEntity target = ProcessVision(statue, interruptConditions); + statue.InterruptConditions |= interruptConditions; + statue.Target = target; if (statue.IsMoving) { @@ -220,12 +226,40 @@ static void ThinkPost(int entIndex) statue.DoAlwaysLookAt(statue.Target); } + statue.InterruptConditions = 0; statue.SetNextThink(GetGameTime()); } -static CBaseEntity ProcessVision(SF2_StatueEntity statue) +static MRESReturn UpdateTransmitState(int entIndex, DHookReturn ret, DHookParam params) { + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = SetEntityTransmitState(entIndex, FL_EDICT_FULLCHECK); + return MRES_Supercede; +} + +static MRESReturn ShouldTransmit(int entIndex, DHookReturn ret, DHookParam params) +{ + if (entIndex == -1) + { + return MRES_Ignored; + } + + ret.Value = FL_EDICT_ALWAYS; + return MRES_Supercede; +} + +static CBaseEntity ProcessVision(SF2_StatueEntity statue, int &interruptConditions = 0) +{ + interruptConditions = 0; SF2NPC_Statue controller = statue.Controller; + if (!controller.IsValid()) + { + return CBaseEntity(-1); + } bool attackEliminated = (controller.Flags & SFF_ATTACKWAITERS) != 0; SF2StatueBossProfileData data; data = controller.GetProfileData(); @@ -234,31 +268,60 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) int difficulty = controller.Difficulty; float playerDists[MAXTF2PLAYERS]; + int playerInterruptFlags[MAXTF2PLAYERS]; float traceMins[3] = { -16.0, ... }; traceMins[2] = 0.0; float traceMaxs[3] = { 16.0, ... }; traceMaxs[2] = 0.0; + float myEyeAng[3]; + statue.GetAbsAngles(myEyeAng); + float traceStartPos[3], traceEndPos[3], myPos[3], targetPos[3]; + controller.GetEyePosition(traceStartPos); + statue.GetAbsOrigin(myPos); int oldTarget = statue.OldTarget.index; - if (!IsTargetValidForSlender(statue, SF2_BasePlayer(oldTarget), attackEliminated)) - { - statue.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); - oldTarget = INVALID_ENT_REFERENCE; - } - if (originalData.IsPvEBoss && !IsPvETargetValid(SF2_BasePlayer(oldTarget))) + if (!IsTargetValidForSlender(statue, CBaseEntity(oldTarget), attackEliminated)) { statue.OldTarget = CBaseEntity(INVALID_ENT_REFERENCE); oldTarget = INVALID_ENT_REFERENCE; } + int bestNewTarget = oldTarget; float searchRange = originalData.SearchRange[difficulty]; float bestNewTargetDist = Pow(searchRange, 2.0); + if (IsValidEntity(bestNewTarget)) + { + CBaseEntity(bestNewTarget).GetAbsOrigin(targetPos); + bestNewTargetDist = GetVectorSquareMagnitude(myPos, targetPos); + if (bestNewTargetDist > Pow(searchRange, 2.0)) + { + bestNewTargetDist = Pow(searchRange, 2.0); + } + } for (int i = 1; i <= MaxClients; i++) { SF2_BasePlayer client = SF2_BasePlayer(i); - if (!IsTargetValidForSlender(statue, client, attackEliminated) && !originalData.IsPvEBoss) + + if (client.IsValid && g_PlayerDebugFlags[client.index] & DEBUG_BOSS_EYES) + { + for (int i2 = 1; i2 <= MaxClients; i2++) + { + if (!IsTargetValidForSlender(statue, SF2_BasePlayer(i2), attackEliminated)) + { + continue; + } + float eyes[3]; + SF2_BasePlayer(i2).GetEyePosition(eyes); + int color[4] = { 0, 255, 0, 255 }; + TE_SetupBeamPoints(traceStartPos, eyes, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); + TE_SendToClient(client.index); + } + + } + + if (!IsTargetValidForSlender(statue, client, attackEliminated)) { continue; } @@ -267,25 +330,22 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) statue.SetInFOV(client, false); statue.SetIsNear(client, false); - float traceStartPos[3], traceEndPos[3]; - controller.GetEyePosition(traceStartPos); client.GetEyePosition(traceEndPos); float dist = 99999999999.9; - bool isVisible; + bool isVisible = false; int traceHitEntity; - - Handle trace = TR_TraceRayFilterEx(traceStartPos, + TR_TraceHullFilter(traceStartPos, traceEndPos, + traceMins, + traceMaxs, CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_MIST | CONTENTS_MONSTERCLIP, - RayType_EndPoint, TraceRayBossVisibility, statue.index); - isVisible = !TR_DidHit(trace); - traceHitEntity = TR_GetEntityIndex(trace); - delete trace; + isVisible = !TR_DidHit(); + traceHitEntity = TR_GetEntityIndex(); if (!isVisible && traceHitEntity == client.index) { @@ -312,6 +372,11 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) } } + if (dist > Pow(originalData.SearchRange[difficulty], 2.0)) + { + isVisible = false; + } + statue.SetIsVisible(client, isVisible); if (statue.GetIsVisible(client) && SF_SpecialRound(SPECIALROUND_BOO) && GetVectorSquareMagnitude(traceEndPos, traceStartPos) < SquareFloat(SPECIALROUND_BOO_DISTANCE)) @@ -322,30 +387,35 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) if (client.ShouldBeForceChased(controller)) { bestNewTarget = client.index; + playerInterruptFlags[client.index] |= COND_ENEMYRECHASE; + } + + if (statue.GetIsVisible(client)) + { + playerInterruptFlags[client.index] |= COND_ENEMYVISIBLE; + } + + if (client.index != oldTarget) + { + playerInterruptFlags[client.index] |= COND_NEWENEMY; } playerDists[client.index] = dist; if (statue.GetIsVisible(client)) { - float targetPos[3]; - client.GetAbsOrigin(targetPos); if (dist <= SquareFloat(searchRange)) { if (dist < bestNewTargetDist) { bestNewTarget = client.index; bestNewTargetDist = dist; + playerInterruptFlags[client.index] |= COND_SAWENEMY; } } } } - if (bestNewTarget != INVALID_ENT_REFERENCE) - { - statue.OldTarget = CBaseEntity(bestNewTarget); - } - if (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || g_RenevantBossesChaseEndlessly) { if (!IsTargetValidForSlender(statue, SF2_BasePlayer(bestNewTarget), attackEliminated)) @@ -381,6 +451,12 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue) statue.CurrentChaseDuration = data.ChaseDuration[difficulty]; } + if (bestNewTarget != INVALID_ENT_REFERENCE) + { + interruptConditions = playerInterruptFlags[bestNewTarget]; + statue.OldTarget = CBaseEntity(bestNewTarget); + } + return CBaseEntity(bestNewTarget); } diff --git a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp index a797c344..2d8ffd0c 100644 --- a/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp +++ b/addons/sourcemod/scripting/sf2/npc/npc_chaser.sp @@ -44,7 +44,7 @@ GlobalForward g_OnChaserBossEndAttackFwd; #include "sf2/npc/npc_chaser_attacks.sp" #include "sf2/npc/npc_chaser_pathing.sp" #include "sf2/npc/npc_chaser_projectiles.sp"*/ -#include "sf2/npc/npc_creeper.sp" +#include "npc_creeper.sp" void NPCChaserInitialize() { @@ -353,6 +353,11 @@ bool IsTargetValidForSlender(SF2_BaseBoss boss, CBaseEntity target, bool include { return false; } + + if (target.GetProp(Prop_Data, "m_iTeamNum") == boss.Team) + { + return false; + } } return true; @@ -635,41 +640,6 @@ static void TriggerKey(int caller) EmitSoundToAll("ui/itemcrate_smash_ultrarare_short.wav", caller, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); } -/*bool NPC_CanAttackProps(int bossIndex, float flAttackRange, float flAttackFOV) -{ - int prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_physics")) > MaxClients) - { - if (NPCAttackValidateTarget(bossIndex, prop, flAttackRange, flAttackFOV, true)) - { - if (NPCPropPhysicsAttack(bossIndex, prop)) - { - return true; - } - } - } - prop = -1; - while ((prop = FindEntityByClassname(prop, "prop_*")) > MaxClients) - { - if (GetEntProp(prop, Prop_Data, "m_iHealth") > 0) - { - if (NPCAttackValidateTarget(bossIndex, prop, flAttackRange, flAttackFOV, true)) - { - return true; - } - } - } - prop = -1; - while ((prop = FindEntityByClassname(prop, "obj_*")) > MaxClients) - { - if (NPCAttackValidateTarget(bossIndex, prop, flAttackRange, flAttackFOV, true)) - { - return true; - } - } - return false; -}*/ - void NPCChaser_InitializeAPI() { CreateNative("SF2_GetBossCurrentAttackIndex", Native_GetBossCurrentAttackIndex); diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 8cce6f96..fc50725d 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -43,9 +43,9 @@ static char mapBossPack[64]; GlobalForward g_OnBossProfileLoadedFwd; static GlobalForward g_OnBossProfileUnloadedFwd; -#include "sf2/profiles/profiles_boss_functions.sp" -#include "sf2/profiles/profile_chaser.sp" -#include "sf2/profiles/profile_statue.sp" +#include "profiles/profiles_boss_functions.sp" +#include "profiles/profile_chaser.sp" +#include "profiles/profile_statue.sp" void SetupBossProfileNatives() { @@ -779,7 +779,6 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss if (maxLoadedBosses > 0) { - directories.Sort(Sort_Random, Sort_String); alwaysLoad = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); for (int i = 0; i < directories.Length; i++) @@ -797,6 +796,7 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss directories.Erase(index); } alwaysLoad.PushString(filePath); + i--; } delete kv; @@ -804,26 +804,6 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss } } - for (int i = 0; i < directories.Length; i++) - { - if (maxLoadedBosses > 0 && count == maxLoadedBosses) - { - break; - } - - directories.GetString(i, filePath, sizeof(filePath)); - - if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) - { - LogSF2Message("%s...FAILED (reason: %s)", filePath, errorReason); - } - else - { - LogSF2Message("%s...", profileName, filePath); - count++; - } - } - if (alwaysLoad != null) { for (int i = 0; i < alwaysLoad.Length; i++) @@ -844,6 +824,31 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss delete alwaysLoad; } + if (maxLoadedBosses > 0) + { + directories.Sort(Sort_Random, Sort_String); + } + + for (int i = 0; i < directories.Length; i++) + { + if (maxLoadedBosses > 0 && count == maxLoadedBosses) + { + break; + } + + directories.GetString(i, filePath, sizeof(filePath)); + + if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) + { + LogSF2Message("%s...FAILED (reason: %s)", filePath, errorReason); + } + else + { + LogSF2Message("%s...", profileName, filePath); + count++; + } + } + delete directories; LogSF2Message("Loaded %d boss profile(s) from directory %s!", count, relDirPath); diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp index 1efbc9eb..9dfcb168 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser.sp @@ -8,7 +8,7 @@ StringMap g_ChaserBossProfileData; -#include "sf2/profiles/profile_chaser_precache.sp" +#include "profile_chaser_precache.sp" void InitializeChaserProfiles() { diff --git a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp index 0083cb56..5c7b17fe 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profile_chaser_precache.sp @@ -33,17 +33,9 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } - GetProfileDifficultyFloatValues(kv, "alert_gracetime", profileData.AlertGracetime, profileData.AlertGracetime); - GetProfileDifficultyFloatValues(kv, "search_alert_gracetime", profileData.AlertGracetime, profileData.AlertGracetime); - GetProfileDifficultyFloatValues(kv, "alert_duration", profileData.AlertDuration, profileData.AlertDuration); - GetProfileDifficultyFloatValues(kv, "search_alert_duration", profileData.AlertDuration, profileData.AlertDuration); - if (kv.JumpToKey("alert")) { - GetProfileDifficultyFloatValues(kv, "gracetime", profileData.AlertGracetime, profileData.AlertGracetime); - GetProfileDifficultyFloatValues(kv, "duration", profileData.AlertDuration, profileData.AlertDuration); - GetProfileDifficultyBoolValues(kv, "run_on_wander", profileData.AlertRunOnWander, profileData.AlertRunOnWander); - GetProfileDifficultyBoolValues(kv, "run_on_suspect", profileData.AlertRunOnHearSound, profileData.AlertRunOnHearSound); + profileData.AlertData.Load(kv); for (int i = 0; i < Difficulty_Max; i++) { @@ -53,6 +45,13 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe kv.GoBack(); } + else + { + GetProfileDifficultyFloatValues(kv, "alert_gracetime", profileData.AlertData.GraceTime, profileData.AlertData.GraceTime); + GetProfileDifficultyFloatValues(kv, "search_alert_gracetime", profileData.AlertData.GraceTime, profileData.AlertData.GraceTime); + GetProfileDifficultyFloatValues(kv, "alert_duration", profileData.AlertData.Duration, profileData.AlertData.Duration); + GetProfileDifficultyFloatValues(kv, "search_alert_duration", profileData.AlertData.Duration, profileData.AlertData.Duration); + } if (kv.JumpToKey("chase")) { @@ -144,37 +143,69 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe GetProfileDifficultyFloatValues(kv, "wander_enter_time_min", profileData.WanderEnterTimeMin, profileData.WanderEnterTimeMin); GetProfileDifficultyFloatValues(kv, "wander_enter_time_max", profileData.WanderEnterTimeMax, profileData.WanderEnterTimeMax); - profileData.StunEnabled = kv.GetNum("stun_enabled", profileData.StunEnabled) != 0; - if (profileData.StunEnabled) + if (kv.GetNum("stun_enabled", false) != 0) + { + SF2ChaserBossProfileStunData stunData; + stunData = profileData.StunData; + for (int i = 0; i < Difficulty_Max; i++) + { + stunData.Enabled[i] = true; + } + GetProfileDifficultyFloatValues(kv, "stun_cooldown", stunData.Cooldown, stunData.Cooldown); + GetProfileDifficultyFloatValues(kv, "stun_health", stunData.Health, stunData.Health); + + GetProfileDifficultyFloatValues(kv, "stun_health_per_player", stunData.AddHealthPerPlayer, stunData.AddHealthPerPlayer); + GetProfileDifficultyFloatValues(kv, "stun_health_per_scout", stunData.AddHealthPerScout, stunData.AddHealthPerScout); + GetProfileDifficultyFloatValues(kv, "stun_health_per_soldier", stunData.AddHealthPerSoldier, stunData.AddHealthPerSoldier); + GetProfileDifficultyFloatValues(kv, "stun_health_per_pyro", stunData.AddHealthPerPyro, stunData.AddHealthPerPyro); + GetProfileDifficultyFloatValues(kv, "stun_health_per_demoman", stunData.AddHealthPerDemoman, stunData.AddHealthPerDemoman); + GetProfileDifficultyFloatValues(kv, "stun_health_per_heavyweapons", stunData.AddHealthPerHeavy, stunData.AddHealthPerHeavy); + GetProfileDifficultyFloatValues(kv, "stun_health_per_engineer", stunData.AddHealthPerEngineer, stunData.AddHealthPerEngineer); + GetProfileDifficultyFloatValues(kv, "stun_health_per_medic", stunData.AddHealthPerMedic, stunData.AddHealthPerMedic); + GetProfileDifficultyFloatValues(kv, "stun_health_per_sniper", stunData.AddHealthPerSniper, stunData.AddHealthPerSniper); + GetProfileDifficultyFloatValues(kv, "stun_health_per_spy", stunData.AddHealthPerSpy, stunData.AddHealthPerSpy); + + GetProfileDifficultyBoolValues(kv, "stun_damage_flashlight_enabled", stunData.FlashlightStun, stunData.FlashlightStun); + GetProfileDifficultyFloatValues(kv, "stun_damage_flashlight", stunData.FlashlightStunDamage, stunData.FlashlightStunDamage); + + GetProfileDifficultyBoolValues(kv, "chase_initial_on_stun", stunData.ChaseInitialOnEnd, stunData.ChaseInitialOnEnd); + + GetProfileDifficultyBoolValues(kv, "drop_item_on_stun", stunData.ItemDrop, stunData.ItemDrop); + GetProfileDifficultyNumValues(kv, "drop_item_type", stunData.ItemDropType, stunData.ItemDropType); + + GetProfileDifficultyBoolValues(kv, "disappear_on_stun", stunData.Disappear, stunData.Disappear); + + stunData.KeyDrop = kv.GetNum("keydrop_enabled", stunData.KeyDrop) != 0; + kv.GetString("key_model", stunData.KeyModel, sizeof(stunData.KeyModel), stunData.KeyModel); + PrecacheModel2(stunData.KeyModel, _, _, g_FileCheckConVar.BoolValue); + kv.GetString("key_trigger", stunData.KeyTrigger, sizeof(stunData.KeyTrigger), stunData.KeyTrigger); + profileData.StunData = stunData; + } + else + { + if (kv.JumpToKey("stun")) + { + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.StunData.Enabled[i] = true; + } + profileData.StunData.Load(kv, g_FileCheckConVar.BoolValue); + kv.GoBack(); + } + } + + if (kv.JumpToKey("resistances")) { - GetProfileDifficultyFloatValues(kv, "stun_cooldown", profileData.StunCooldown, profileData.StunCooldown); - GetProfileDifficultyFloatValues(kv, "stun_health", profileData.StunHealth, profileData.StunHealth); - profileData.StunHealthPerPlayer = kv.GetFloat("stun_health_per_player", profileData.StunHealthPerPlayer); - if (profileData.StunHealthPerPlayer < 0.0) - { - profileData.StunHealthPerPlayer = 0.0; - } - profileData.StunHealthPerClass[1] = kv.GetFloat("stun_health_per_scout", profileData.StunHealthPerClass[1]); - profileData.StunHealthPerClass[2] = kv.GetFloat("stun_health_per_sniper", profileData.StunHealthPerClass[2]); - profileData.StunHealthPerClass[3] = kv.GetFloat("stun_health_per_soldier", profileData.StunHealthPerClass[3]); - profileData.StunHealthPerClass[4] = kv.GetFloat("stun_health_per_demoman", profileData.StunHealthPerClass[4]); - profileData.StunHealthPerClass[5] = kv.GetFloat("stun_health_per_medic", profileData.StunHealthPerClass[5]); - profileData.StunHealthPerClass[6] = kv.GetFloat("stun_health_per_heavyweapons", profileData.StunHealthPerClass[6]); - profileData.StunHealthPerClass[7] = kv.GetFloat("stun_health_per_pyro", profileData.StunHealthPerClass[7]); - profileData.StunHealthPerClass[8] = kv.GetFloat("stun_health_per_spy", profileData.StunHealthPerClass[8]); - profileData.StunHealthPerClass[9] = kv.GetFloat("stun_health_per_engineer", profileData.StunHealthPerClass[9]); - GetProfileDifficultyBoolValues(kv, "stun_damage_flashlight_enabled", profileData.FlashlightStun, profileData.FlashlightStun); - GetProfileDifficultyFloatValues(kv, "stun_damage_flashlight", profileData.FlashlightDamage, profileData.FlashlightDamage); - profileData.ChaseInitialOnStun = kv.GetNum("chase_initial_on_stun", profileData.ChaseInitialOnStun) != 0; - - GetProfileDifficultyBoolValues(kv, "drop_item_on_stun", profileData.ItemDropOnStun); - GetProfileDifficultyNumValues(kv, "drop_item_type", profileData.StunItemDropType, profileData.StunItemDropType); - - profileData.DisappearOnStun = kv.GetNum("disappear_on_stun", profileData.DisappearOnStun) != 0; - - if (kv.JumpToKey("resistances")) - { - profileData.DamageResistances = new ArrayList(); + profileData.DamageResistances = new ArrayList(sizeof(SF2ChaserBossProfileResistanceData)); + if (!kv.GotoFirstSubKey()) + { + SF2ChaserBossProfileResistanceData resistanceData; + resistanceData.Init(); + for (int i = 0; i < Difficulty_Max; i++) + { + resistanceData.Multiplier[i] = 0.0; + } + resistanceData.DamageTypes = new ArrayList(); char key[64]; int resistance = -1; for (int i = 1;; i++) @@ -185,22 +216,32 @@ bool LoadChaserBossProfile(KeyValues kv, const char[] profile, char[] loadFailRe { break; } - profileData.DamageResistances.Push(resistance); + resistanceData.DamageTypes.Push(resistance); } - kv.GoBack(); + profileData.DamageResistances.PushArray(resistanceData); } - - profileData.KeyDrop = kv.GetNum("keydrop_enabled", profileData.KeyDrop) != 0; - if (profileData.KeyDrop) + else { - kv.GetString("key_model", profileData.KeyModel, sizeof(profileData.KeyModel), profileData.KeyModel); - PrecacheModel2(profileData.KeyModel, _, _, g_FileCheckConVar.BoolValue); - kv.GetString("key_trigger", profileData.KeyTrigger, sizeof(profileData.KeyTrigger), profileData.KeyTrigger); + do + { + SF2ChaserBossProfileResistanceData resistance; + resistance.Init(); + resistance.Load(kv); + profileData.DamageResistances.PushArray(resistance); + } + while (kv.GotoNextKey()); + + kv.GoBack(); } + kv.GoBack(); } if (kv.JumpToKey("death")) { + for (int i = 0; i < Difficulty_Max; i++) + { + profileData.DeathData.Enabled[i] = true; + } profileData.DeathData.Load(kv, g_FileCheckConVar.BoolValue); kv.GoBack(); } @@ -1394,7 +1435,7 @@ static void LoadLegacyEffects(KeyValues kv, SF2ChaserBossProfileData profileData } else { - key = "player_stun_duration"; + key = "player_stun_slowdown"; } if (damageEffect.StunFlags == null) @@ -1640,6 +1681,7 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas attackData.Type = kv.GetNum("type", attackData.Type); GetProfileDifficultyFloatValues(kv, "range", attackData.Range, attackData.Range); GetProfileDifficultyFloatValues(kv, "damage", attackData.Damage, attackData.Damage); + GetProfileDifficultyFloatValues(kv, "damage_percent", attackData.DamagePercent, attackData.DamagePercent); attackData.DamageVsProps = kv.GetFloat("damage_vs_props", attackData.DamageVsProps); GetProfileDifficultyFloatValues(kv, "damageforce", attackData.DamageForce, attackData.DamageForce); GetProfileDifficultyNumValues(kv, "damagetype", attackData.DamageType, attackData.DamageType); @@ -1658,7 +1700,8 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas GetProfileDifficultyFloatValues(kv, "begin_fov", attackData.BeginFOV, attackData.Spread); GetProfileDifficultyFloatValues(kv, "cooldown", attackData.Cooldown, attackData.Cooldown); - GetProfileDifficultyBoolValues(kv, "disappear_upon_damaging", attackData.Disappear, attackData.Disappear); + GetProfileDifficultyBoolValues(kv, "disappear", attackData.Disappear, attackData.Disappear); + GetProfileDifficultyBoolValues(kv, "disappear_upon_damaging", attackData.DisappearOnHit, attackData.DisappearOnHit); } attackData.Type = kv.GetNum("attack_type", attackData.Type); @@ -1956,6 +1999,25 @@ static int ParseChaserProfileAttacks(KeyValues kv, SF2ChaserBossProfileData chas kv.GoBack(); } + if (kv.JumpToKey("on_kill")) + { + attackData.KillEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effect; + effect.Init(); + effect.Load(kv, g_FileCheckConVar.BoolValue); + attackData.KillEffects.PushArray(effect); + } + while (kv.GotoNextKey()); + + kv.GoBack(); + } + kv.GoBack(); + } + kv.GoBack(); } diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index f45f73fd..fe9c2177 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -243,29 +243,76 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GetVector("festive_lights_ang", profileData.FestiveLightAng, profileData.FestiveLightAng); } - profileData.EnableSpawnParticles = kv.GetNum("tp_effect_spawn", profileData.EnableSpawnParticles) != 0; - if (profileData.EnableSpawnParticles) + if (kv.GetNum("tp_effect_spawn", false) != 0) { - kv.GetString("tp_effect_spawn_particle", profileData.SpawnParticle, sizeof(profileData.SpawnParticle), profileData.SpawnParticle); - kv.GetString("tp_effect_spawn_sound", profileData.SpawnParticleSound, sizeof(profileData.SpawnParticleSound), profileData.SpawnParticleSound); - TryPrecacheBossProfileSoundPath(profileData.SpawnParticleSound, g_FileCheckConVar.BoolValue); - profileData.SpawnParticleSoundVolume = kv.GetFloat("tp_effect_spawn_sound_volume", profileData.SpawnParticleSoundVolume); - profileData.SpawnParticleSoundPitch = kv.GetNum("tp_effect_spawn_sound_pitch", profileData.SpawnParticleSoundPitch); - } - - profileData.EnableDespawnParticles = kv.GetNum("tp_effect_despawn", profileData.EnableDespawnParticles) != 0; - if (profileData.EnableDespawnParticles) - { - kv.GetString("tp_effect_despawn_particle", profileData.DespawnParticle, sizeof(profileData.DespawnParticle), profileData.DespawnParticle); - kv.GetString("tp_effect_despawn_sound", profileData.DespawnParticleSound, sizeof(profileData.DespawnParticleSound), profileData.DespawnParticleSound); - TryPrecacheBossProfileSoundPath(profileData.DespawnParticleSound, g_FileCheckConVar.BoolValue); - profileData.DespawnParticleSoundVolume = kv.GetFloat("tp_effect_despawn_sound_volume", profileData.DespawnParticleSoundVolume); - profileData.DespawnParticleSoundPitch = kv.GetNum("tp_effect_despawn_sound_pitch", profileData.DespawnParticleSoundPitch); - } - - if (profileData.EnableSpawnParticles || profileData.EnableDespawnParticles) - { - kv.GetVector("tp_effect_origin", profileData.SpawnParticleOrigin, profileData.SpawnParticleOrigin); + if (profileData.SpawnEffects == null) + { + profileData.SpawnEffects = new StringMap(); + } + ArrayList listEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + SF2BossProfileBaseEffectInfo particle; + SF2BossProfileBaseEffectInfo sound; + + particle.Init(); + particle.Type = EffectType_Particle; + particle.Event = EffectEvent_Constant; + kv.GetString("tp_effect_spawn_particle", particle.ParticleName, sizeof(particle.ParticleName), particle.ParticleName); + particle.LifeTime = 0.1; + kv.GetVector("tp_effect_origin", particle.Origin, particle.Origin); + particle.PostLoad(); + listEffects.PushArray(particle); + + char soundName[PLATFORM_MAX_PATH]; + sound.Init(); + sound.Type = EffectType_Sound; + sound.Event = EffectEvent_Constant; + kv.GetString("tp_effect_spawn_sound", soundName, sizeof(soundName), soundName); + TryPrecacheBossProfileSoundPath(soundName, g_FileCheckConVar.BoolValue); + sound.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound.SoundSounds.Paths.PushString(soundName); + sound.SoundSounds.Volume = kv.GetFloat("tp_effect_spawn_sound_volume", sound.SoundSounds.Volume); + sound.SoundSounds.Pitch = kv.GetNum("tp_effect_spawn_sound_pitch", sound.SoundSounds.Pitch); + sound.SoundSounds.PostLoad(); + listEffects.PushArray(sound); + + profileData.SpawnEffects.SetValue("TPEffectSpawnBackwards", listEffects); + } + + if (kv.GetNum("tp_effect_despawn", false) != 0) + { + if (profileData.DespawnEffects == null) + { + profileData.DespawnEffects = new StringMap(); + } + ArrayList listEffects = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + + SF2BossProfileBaseEffectInfo particle; + SF2BossProfileBaseEffectInfo sound; + + particle.Init(); + particle.Type = EffectType_Particle; + particle.Event = EffectEvent_Constant; + kv.GetString("tp_effect_despawn_particle", particle.ParticleName, sizeof(particle.ParticleName), particle.ParticleName); + particle.LifeTime = 0.1; + kv.GetVector("tp_effect_origin", particle.Origin, particle.Origin); + particle.PostLoad(); + listEffects.PushArray(particle); + + char soundName[PLATFORM_MAX_PATH]; + sound.Init(); + sound.Type = EffectType_Sound; + sound.Event = EffectEvent_Constant; + kv.GetString("tp_effect_despawn_sound", soundName, sizeof(soundName), soundName); + TryPrecacheBossProfileSoundPath(soundName, g_FileCheckConVar.BoolValue); + sound.SoundSounds.Paths = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + sound.SoundSounds.Paths.PushString(soundName); + sound.SoundSounds.Volume = kv.GetFloat("tp_effect_despawn_sound_volume", sound.SoundSounds.Volume); + sound.SoundSounds.Pitch = kv.GetNum("tp_effect_despawn_sound_pitch", sound.SoundSounds.Pitch); + sound.SoundSounds.PostLoad(); + listEffects.PushArray(sound); + + profileData.DespawnEffects.SetValue("TPEffectDespawnBackwards", listEffects); } profileData.BlinkLookRate = kv.GetFloat("blink_look_rate_multiply", profileData.BlinkLookRate); @@ -472,6 +519,12 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.EyeAngOffset = profileData.EyeData.OffsetAng; } + if (kv.JumpToKey("slaughter_run")) + { + profileData.SlaughterRunData.Load(kv); + kv.GoBack(); + } + // Parse through flags. int bossFlags = 0; if (kv.GetNum("static_on_look")) @@ -1320,6 +1373,93 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu kv.GoBack(); } + if (kv.JumpToKey("spawn_effects")) + { + if (profileData.SpawnEffects == null) + { + profileData.SpawnEffects = new StringMap(); + } + + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + kv.GetSectionName(section, sizeof(section)); + if (kv.JumpToKey("effects")) + { + ArrayList list = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effects; + effects.Init(); + effects.ModelScale = profileData.ModelScale; + effects.Load(kv, g_FileCheckConVar.BoolValue); + if (effects.Type == EffectType_Particle) + { + effects.LifeTime = 0.1; + } + list.PushArray(effects); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + profileData.SpawnEffects.SetValue(section, list); + } + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + + if (kv.JumpToKey("despawn_effects")) + { + if (profileData.DespawnEffects == null) + { + profileData.DespawnEffects = new StringMap(); + } + profileData.HideDespawnEffectsOnDeath = kv.GetNum("hide_on_death", profileData.HideDespawnEffectsOnDeath) != 0; + + if (kv.GotoFirstSubKey()) + { + do + { + char section[64]; + kv.GetSectionName(section, sizeof(section)); + if (kv.JumpToKey("effects")) + { + ArrayList list = new ArrayList(sizeof(SF2BossProfileBaseEffectInfo)); + if (kv.GotoFirstSubKey()) + { + do + { + SF2BossProfileBaseEffectInfo effects; + effects.Init(); + effects.ModelScale = profileData.ModelScale; + effects.Load(kv, g_FileCheckConVar.BoolValue); + if (effects.Type == EffectType_Particle) + { + effects.LifeTime = 0.1; + } + list.PushArray(effects); + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + profileData.DespawnEffects.SetValue(section, list); + } + } + while (kv.GotoNextKey()); + kv.GoBack(); + } + kv.GoBack(); + } + profileData.PostLoad(); g_BossProfileData.SetArray(profile, profileData, sizeof(profileData)); @@ -1526,72 +1666,6 @@ void GetBossProfileFestiveLightAngles(const char[] profile, float buffer[3]) buffer = g_CachedProfileData.FestiveLightAng; } -bool GetBossProfileSpawnParticleState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EnableSpawnParticles; -} - -int GetBossProfileSpawnParticleName(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SpawnParticle); -} - -int GetBossProfileSpawnParticleSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.SpawnParticleSound); -} - -float GetBossProfileSpawnParticleSoundVolume(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SpawnParticleSoundVolume; -} - -int GetBossProfileSpawnParticleSoundPitch(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.SpawnParticleSoundPitch; -} - -void GetBossProfileSpawnParticleOrigin(const char[] profile, float buffer[3]) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - buffer = g_CachedProfileData.SpawnParticleOrigin; -} - -bool GetBossProfileDespawnParticleState(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.EnableDespawnParticles; -} - -int GetBossProfileDespawnParticleName(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DespawnParticle); -} - -int GetBossProfileDespawnParticleSound(const char[] profile, char[] buffer, int bufferlen) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return strcopy(buffer, bufferlen, g_CachedProfileData.DespawnParticleSound); -} - -float GetBossProfileDespawnParticleSoundVolume(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DespawnParticleSoundVolume; -} - -int GetBossProfileDespawnParticleSoundPitch(const char[] profile) -{ - g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); - return g_CachedProfileData.DespawnParticleSoundPitch; -} - ArrayList GetBossProfileNames(const char[] profile) { g_BossProfileData.GetArray(profile, g_CachedProfileData, sizeof(g_CachedProfileData)); diff --git a/addons/sourcemod/scripting/sf2/pvp.sp b/addons/sourcemod/scripting/sf2/pvp.sp index 916f929a..0d359e80 100644 --- a/addons/sourcemod/scripting/sf2/pvp.sp +++ b/addons/sourcemod/scripting/sf2/pvp.sp @@ -6,7 +6,6 @@ #pragma semicolon 1 #define SF2_PVP_SPAWN_SOUND "items/pumpkin_drop.wav" -#define FLAME_HIT_DELAY 0.05 static ConVar g_PvPArenaLeaveTimeConVar = null; static ConVar g_PvPArenaProjectileZapConVar = null; @@ -23,6 +22,7 @@ static const char g_PvPProjectileClasses[][] = "tf_projectile_balloffire", "tf_projectile_jar", "tf_projectile_jar_milk", + "tf_projectile_jar_gas", "tf_projectile_pipe", "tf_projectile_pipe_remote", "tf_projectile_stun_ball", @@ -41,12 +41,39 @@ static const char g_PvPProjectileClassesNoTouch[][] = "tf_projectile_flare" }; +static const char g_PvPProjectileClassesNoHook[][] = +{ + "tf_projectile_pipe_remote", + "tf_projectile_balloffire" +}; + +static int g_SpectatorItemIDs[] = +{ + TF_WEAPON_BUFF_ITEM, // CTFPlayerShared::PulseRageBuff + TF_WEAPON_FLAMETHROWER, // CTFFlameThrower::SecondaryAttack + TF_WEAPON_FLAME_BALL, // CWeaponFlameBall::SecondaryAttack + TF_WEAPON_SNIPERRIFLE, // CTFPlayer::FireBullet + TF_WEAPON_KNIFE, // CTFKnife::BackstabVMThink + TF_WEAPON_RAYGUN_REVENGE, // CTFFlareGun_Revenge::ExtinguishPlayerInternal +}; + +static int g_EnemyItemIDs[] = +{ + TF_WEAPON_HANDGUN_SCOUT_PRIMARY, // CTFPistol_ScoutPrimary::Push + TF_WEAPON_GRAPPLINGHOOK, // CTFGrapplingHook::ActivateRune +}; + static bool g_PlayerInPvP[MAXTF2PLAYERS]; static bool g_PlayerIsLeavingPvP[MAXTF2PLAYERS]; Handle g_PlayerPvPTimer[MAXTF2PLAYERS]; Handle g_PlayerPvPRespawnTimer[MAXTF2PLAYERS]; static int g_PlayerPvPTimerCount[MAXTF2PLAYERS]; static ArrayList g_PlayerEnteredPvPTriggers[MAXTF2PLAYERS] = { null, ... }; +static float g_PlayerMedigunDrainTime[MAXTF2PLAYERS]; +static int g_PlayerOriginalTeam[MAXTF2PLAYERS]; + +static TFTeam g_PreHookTeam[2049]; +static TFTeam g_PreHookDisguiseTeam[MAXTF2PLAYERS]; //Blood static int g_PvPUserIdLastTrace; @@ -57,6 +84,42 @@ static ArrayList g_PvPBallsOfFire; static GlobalForward g_OnPlayerEnterPvP; static GlobalForward g_OnPlayerExitPvP; +#define TICK_NEVER_THINK -1.0 +static Handle g_SDKGetNextThink; +static Handle g_SDKGetGlobalTeam; +static Handle g_SDKChangeTeam; +static Handle g_SDKAddPlayer; +static Handle g_SDKRemovePlayer; +static Handle g_SDKAddObject; +static Handle g_SDKRemoveObject; +static Handle g_SDKGetPenetrationType; + +static DynamicHook g_DHookCTFBaseRocketExplode; +static DynamicHook g_DHookCBaseGrenadeExplode; +static DynamicHook g_DHookVPhysicsUpdate; + +static DynamicDetour g_DDetourPhysicsDispatchThink; +static DynamicDetour g_DDetourAllowedToHealTarget; + +enum ThinkFunction +{ + ThinkFunction_None, + ThinkFunction_DispenseThink, + ThinkFunction_SentryThink, + ThinkFunction_SapperThink, +} + +static ThinkFunction g_ThinkFunction = ThinkFunction_None; + +enum +{ + PostThinkType_None, + PostThinkType_Spectator, + PostThinkType_EnemyTeam, +} + +static int g_PostThinkType; + enum struct PvPProjectile_BallOfFire { int EntIndex; @@ -77,7 +140,12 @@ enum struct PvPProjectile_BallOfFire int ent = this.EntIndex; int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); - if (!IsValidClient(ownerEntity) || otherEntity == ownerEntity || !IsClientInPvP(ownerEntity)) + if (!IsValidClient(ownerEntity) || otherEntity == ownerEntity) + { + return; + } + + if (!IsClientInPvP(ownerEntity)) { return; } @@ -88,7 +156,7 @@ enum struct PvPProjectile_BallOfFire { this.TouchedEntities.Push(otherEntity); - if (IsValidClient(otherEntity) && IsClientInPvP(otherEntity) && GetEntProp(otherEntity, Prop_Send, "m_iTeamNum") == GetEntProp(ownerEntity, Prop_Send, "m_iTeamNum")) + if (IsValidClient(otherEntity) && (IsClientInPvP(otherEntity)) && GetEntProp(otherEntity, Prop_Send, "m_iTeamNum") == GetEntProp(ownerEntity, Prop_Send, "m_iTeamNum")) { float damage = GetEntDataFloat(ent, FindSendPropInfo("CTFProjectile_BallOfFire", "m_iDeflected") + 4); float damageBonus = TF2_IsPlayerInCondition(otherEntity, TFCond_OnFire) ? g_DragonsFuryBurningBonusConVar.FloatValue : 1.0; @@ -103,6 +171,11 @@ enum struct PvPProjectile_BallOfFire TF2_IgnitePlayer(otherEntity, ownerEntity); SDKHooks_TakeDamage(otherEntity, ownerEntity, ownerEntity, damage * damageBonus, 0x1220000, GetEntPropEnt(ownerEntity, Prop_Send, "m_hActiveWeapon"), NULL_VECTOR, damagePos); + CBaseEntity primary = CBaseEntity(GetPlayerWeaponSlot(ownerEntity, TFWeaponSlot_Primary)); + if (primary.IsValid()) + { + primary.SetPropFloat(Prop_Send, "m_flRechargeScale", 1.5); + } } } } @@ -129,11 +202,130 @@ void PvP_Initialize() g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPrePFwd.AddFunction(null, OnPlayerDeathPre); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + GameData gameData = new GameData("sf2"); + + g_DHookCTFBaseRocketExplode = DynamicHook.FromConf(gameData, "CTFBaseRocket::Explode"); + if (g_DHookCTFBaseRocketExplode == null) + { + SetFailState("Failed to create hook CTFBaseRocket::Explode from gamedata!"); + } + + g_DHookCBaseGrenadeExplode = DynamicHook.FromConf(gameData, "CBaseGrenade::Explode"); + if (g_DHookCBaseGrenadeExplode == null) + { + SetFailState("Failed to create hook CTFBaseRocket::Explode from gamedata!"); + } + + g_DHookVPhysicsUpdate = DynamicHook.FromConf(gameData, "CBaseEntity::VPhysicsUpdate"); + if (g_DHookVPhysicsUpdate == null) + { + SetFailState("Failed to create hook CBaseEntity::VPhysicsUpdate from gamedata!"); + } + + g_DDetourPhysicsDispatchThink = DynamicDetour.FromConf(gameData, "CBaseEntity::PhysicsDispatchThink"); + if (g_DDetourPhysicsDispatchThink == null) + { + SetFailState("Failed to create hook CBaseEntity::PhysicsDispatchThink from gamedata!"); + } + + g_DDetourPhysicsDispatchThink.Enable(Hook_Pre, PhysicsDispatchThinkPre); + g_DDetourPhysicsDispatchThink.Enable(Hook_Post, PhysicsDispatchThinkPost); + + g_DDetourAllowedToHealTarget = DynamicDetour.FromConf(gameData, "CWeaponMedigun::AllowedToHealTarget"); + if (g_DDetourAllowedToHealTarget == null) + { + SetFailState("Failed to create hook CWeaponMedigun::AllowedToHealTarget from gamedata!"); + } + + g_DDetourAllowedToHealTarget.Enable(Hook_Pre, AllowedToHealTargetPre); + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CBaseEntity::GetNextThink"); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer, VDECODE_FLAG_ALLOWNULL); + PrepSDKCall_SetReturnInfo(SDKType_Float, SDKPass_Plain); + g_SDKGetNextThink = EndPrepSDKCall(); + if (g_SDKGetNextThink == null) + { + SetFailState("Failed to retrieve CBaseEntity::GetNextThink offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Static); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "GetGlobalTeam"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + g_SDKGetGlobalTeam = EndPrepSDKCall(); + if (g_SDKGetGlobalTeam == null) + { + SetFailState("Failed to retrieve GetGlobalTeam offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CBaseEntity::ChangeTeam"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + g_SDKChangeTeam = EndPrepSDKCall(); + if (g_SDKChangeTeam == null) + { + SetFailState("Failed to retrieve CBaseEntity::ChangeTeam offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTeam::AddPlayer"); + PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer); + g_SDKAddPlayer = EndPrepSDKCall(); + if (g_SDKAddPlayer == null) + { + SetFailState("Failed to retrieve CTeam::AddPlayer offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTeam::RemovePlayer"); + PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer); + g_SDKRemovePlayer = EndPrepSDKCall(); + if (g_SDKRemovePlayer == null) + { + SetFailState("Failed to retrieve CTeam::RemovePlayer offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CTFTeam::AddObject"); + PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); + g_SDKAddObject = EndPrepSDKCall(); + if (g_SDKAddObject == null) + { + SetFailState("Failed to retrieve CTFTeam::AddObject offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CTFTeam::RemoveObject"); + PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); + g_SDKRemoveObject = EndPrepSDKCall(); + if (g_SDKRemoveObject == null) + { + SetFailState("Failed to retrieve CTFTeam::RemoveObject offset from gamedata!"); + } + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTFSniperRifle::GetPenetrateType"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + g_SDKGetPenetrationType = EndPrepSDKCall(); + if (g_SDKGetPenetrationType == null) + { + SetFailState("Failed to retrieve CTFSniperRifle::GetPenetrateType offset from gamedata!"); + } + + delete gameData; + AddTempEntHook("TFBlood", TempEntHook_PvPBlood); AddTempEntHook("World Decal", TempEntHook_PvPDecal); AddTempEntHook("Entity Decal", TempEntHook_PvPDecal); + + HookEvent("fish_notice", OnPlayerDeathEventPre, EventHookMode_Pre); + HookEvent("fish_notice__arm", OnPlayerDeathEventPre, EventHookMode_Pre); + HookEvent("slap_notice", OnPlayerDeathEventPre, EventHookMode_Pre); + HookEvent("player_death", OnPlayerDeathEventPre, EventHookMode_Pre); } void PvP_SetupMenus() @@ -227,6 +419,14 @@ static void OnPutInServer(SF2_BasePlayer client) } g_PlayerEnteredPvPTriggers[client.index] = new ArrayList(); + if (!client.IsSourceTV) + { + SDKHook(client.index, SDKHook_PreThink, ClientPreThink); + SDKHook(client.index, SDKHook_PreThinkPost, ClientPreThinkPost); + SDKHook(client.index, SDKHook_PostThink, ClientPostThink); + SDKHook(client.index, SDKHook_PostThinkPost, ClientPostThinkPost); + } + PvP_ForceResetPlayerPvPData(client.index); } @@ -266,53 +466,20 @@ static void GameFrame() } } - if (changeProjectileTeam) - { - SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); - SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); - } - } - } - - // Process through PvP flame entities. - { - static float mins[3] = { -6.0, ... }; - static float maxs[3] = { 6.0, ... }; - - float origin[3]; - - Handle trace = null; - int ent = -1; - int ownerEntity = INVALID_ENT_REFERENCE; - int hitEntity = INVALID_ENT_REFERENCE; - - while ((ent = FindEntityByClassname(ent, "tf_flame_manager")) != -1) - { - ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); - - if (IsValidEdict(ownerEntity)) + for (int i2 = 0; i2 < sizeof(g_PvPProjectileClassesNoHook); i2++) { - // tf_flame's initial owner SHOULD be the flamethrower that it originates from. - // If not, then something's completely bogus. - - ownerEntity = GetEntPropEnt(ownerEntity, Prop_Data, "m_hOwnerEntity"); + if (strcmp(g_PvPProjectileClasses[i], g_PvPProjectileClassesNoHook[i2], false) == 0) + { + changeProjectileTeam = false; + } } - if (IsValidClient(ownerEntity) && (IsRoundInWarmup() || IsClientInPvP(ownerEntity))) + if (changeProjectileTeam) { - GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", origin); - - trace = TR_TraceHullFilterEx(origin, origin, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, ownerEntity); - hitEntity = TR_GetEntityIndex(trace); - delete trace; - - if (IsValidEntity(hitEntity)) - { - PvP_OnFlameEntityStartTouchPost(ent, hitEntity); - } + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); } } - delete trace; } } @@ -325,7 +492,6 @@ static void EntityCreated(CBaseEntity ent, const char[] classname) { if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) { - SDKHook(ent.index, SDKHook_Spawn, Hook_PvPProjectileSpawn); SDKHook(ent.index, SDKHook_SpawnPost, Hook_PvPProjectileSpawnPost); break; } @@ -339,6 +505,61 @@ static void EntityCreated(CBaseEntity ent, const char[] classname) break; } } + + if (strcmp(classname, "tf_flame_manager", false) == 0) + { + SDKHook(ent.index, SDKHook_Touch, FlameStartTouch); + SDKHook(ent.index, SDKHook_TouchPost, FlameStartTouchPost); + } + else if (strcmp(classname, "tf_gas_manager", false) == 0) + { + SDKHook(ent.index, SDKHook_Touch, GasStartTouch); + } + + if (strncmp(classname, "tf_projectile_", 14) == 0) + { + g_DHookProjectileCanCollideWithTeammates.HookEntity(Hook_Post, ent.index, Hook_PvPProjectileCanCollideWithTeammates); + + if (strcmp(classname, "tf_projectile_pipe_remote") == 0) + { + SDKHook(ent.index, SDKHook_OnTakeDamage, PipeOnTakeDamage); + SDKHook(ent.index, SDKHook_OnTakeDamagePost, PipeOnTakeDamagePost); + } + else if (strcmp(classname, "tf_projectile_flare") == 0) + { + g_DHookCTFBaseRocketExplode.HookEntity(Hook_Pre, ent.index, FlareExplodePre); + g_DHookCTFBaseRocketExplode.HookEntity(Hook_Post, ent.index, FlareExplodePost); + } + else if (strncmp(classname, "tf_projectile_jar", 17) == 0) + { + g_DHookCBaseGrenadeExplode.HookEntity(Hook_Pre, ent.index, JarExplodePre); + g_DHookCBaseGrenadeExplode.HookEntity(Hook_Post, ent.index, JarExplodePost); + } + else if (strncmp(classname, "tf_projectile_pipe", 18) == 0) + { + g_DHookVPhysicsUpdate.HookEntity(Hook_Pre, ent.index, VPhysicsUpdatePre); + g_DHookVPhysicsUpdate.HookEntity(Hook_Post, ent.index, VPhysicsUpdatePost); + } + else if (strcmp(classname, "tf_projectile_cleaver") == 0) + { + SDKHook(ent.index, SDKHook_Touch, CleaverTouch); + SDKHook(ent.index, SDKHook_TouchPost, CleaverTouchPost); + } + else if (strcmp(classname, "tf_projectile_pipe") == 0) + { + SDKHook(ent.index, SDKHook_Touch, GrenadeTouch); + SDKHook(ent.index, SDKHook_TouchPost, GrenadeTouchPost); + } + } + else if (IsEntityWeapon(ent.index)) + { + int id = GetWeaponID(ent.index); + if (id == TF_WEAPON_SNIPERRIFLE || id == TF_WEAPON_SNIPERRIFLE_DECAP || id == TF_WEAPON_SNIPERRIFLE_CLASSIC) + { + // Fixes Sniper Rifles dealing no damage to teammates + g_DHookWeaponGetCustomDamageType.HookEntity(Hook_Post, ent.index, GetCustomDamageTypePost); + } + } } static void EntityDestroyed(CBaseEntity ent, const char[] classname) @@ -360,171 +581,333 @@ static void EntityDestroyed(CBaseEntity ent, const char[] classname) } } -static Action Hook_PvPProjectile_OnTouch(int projectile, int client) +static float GetNextThink(int entity, const char[] context = "") { - // Check if the projectile hit a player outside of pvp area - // Without that, cannon balls can bounce players which should not happen because they are outside of pvp. - bool remove = false; - if (IsValidClient(client) && !IsClientInPvP(client)) + if (g_SDKGetNextThink != null) { - remove = true; + return SDKCall(g_SDKGetNextThink, entity, context); } - int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); - if (owner == client) - { - remove = false; - } + return TICK_NEVER_THINK; +} - if (remove) +static Address SDKCall_GetGlobalTeam(TFTeam team) +{ + if (g_SDKGetGlobalTeam) { - float vel[3]; - CBaseEntity(client).GetAbsVelocity(vel); - TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vel); - RemoveEntity(projectile); - return Plugin_Handled; + return SDKCall(g_SDKGetGlobalTeam, team); } - return Plugin_Continue; + return Address_Null; } -static Action Hook_PvPProjectileSpawn(int ent) +static void CBaseEntity_ChangeTeam(int entity, TFTeam team) { - char class[64]; - GetEntityClassname(ent, class, sizeof(class)); + SDKCall(g_SDKChangeTeam, entity, team); +} - int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); - int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); +static void CTeam_AddPlayer(Address team, int client) +{ + SDKCall(g_SDKAddPlayer, team, client); +} - if (ownerEntity == -1 && throwerOffset != -1) - { - ownerEntity = GetEntDataEnt2(ent, throwerOffset); - } +static void CTeam_RemovePlayer(Address team, int client) +{ + SDKCall(g_SDKRemovePlayer, team, client); +} - if (IsValidClient(ownerEntity)) - { - if (IsClientInPvP(ownerEntity)) - { - SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); - SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); - } - } +static void CTeam_AddObject(Address team, int obj) +{ + SDKCall(g_SDKAddObject, team, obj); +} - return Plugin_Continue; +static void CTeam_RemoveObject(Address team, int obj) +{ + SDKCall(g_SDKRemoveObject, team, obj); } -static void Hook_PvPProjectileSpawnPost(int ent) +static void ClientPreThink(int client) { - if (!IsValidEntity(ent)) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) { return; } - char class[64]; - GetEntityClassname(ent, class, sizeof(class)); - - int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); - int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); - - if (ownerEntity == -1 && throwerOffset != -1) + if (player.Team == TFTeam_Red) { - ownerEntity = GetEntDataEnt2(ent, throwerOffset); + return; } - if (IsValidClient(ownerEntity)) + float gameTime = GetGameTime(); + CBaseEntity secondary = CBaseEntity(GetPlayerWeaponSlot(player.index, TFWeaponSlot_Secondary)); + if (secondary.IsValid() && GetWeaponID(secondary.index) == TF_WEAPON_MEDIGUN && g_PlayerMedigunDrainTime[player.index] <= gameTime) { - if (IsRoundInWarmup() || IsClientInPvP(ownerEntity)) + SF2_BasePlayer healTarget = SF2_BasePlayer(secondary.GetPropEnt(Prop_Send, "m_hHealingTarget")); + if (healTarget.IsValid && healTarget.IsAlive && (healTarget.IsInPvP)) { - static const char fixWeaponNotCollidingWithTeammates[][] = - { - "tf_projectile_rocket", - "tf_projectile_sentryrocket", - "tf_projectile_flare" - }; - - for (int i = 0; i < sizeof(fixWeaponNotCollidingWithTeammates); i++) - { - if (IsValidEntity(ent) && strcmp(class, fixWeaponNotCollidingWithTeammates[i], false) == 0) - { - DHookEntity(g_DHookProjectileCanCollideWithTeammates, false, ent, _, Hook_PvPProjectileCanCollideWithTeammates); - break; - } - } - - if (strcmp(class, "tf_projectile_pipe", false) == 0 && GetEntProp(ent, Prop_Send, "m_iType") == 3) + float damage = 6.0; + if (secondary.GetProp(Prop_Send, "m_iItemDefinitionIndex") == 411) // Quick-Fix { - /* - Loose Cannon's projectiles - KR: I'm assuming that stopping non-PvP players from getting bounced is the reason why this is implemented. - Despite hooking onto Touch, the knockback still happens half of the time. StartTouch yields the same result - so this is really the best that can be done. - */ - SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + damage *= 1.4; } - else if (strcmp(class, "tf_projectile_balloffire", false) == 0) + if (player.InCondition(TFCond_MegaHeal)) { - /* - Replicate projectile logic for Dragon's Fury projectiles. - KR: The projectile checks the team of its owner entity (the player), not itself. CBaseEntity::InSameTeam() - could be hooked to change this, but I think that would be too game-changing and not really worth doing for - a single case, so the enemy player logic is sort of replicated. - */ - - PvPProjectile_BallOfFire projectileData; - projectileData.Init(ent); - g_PvPBallsOfFire.PushArray(projectileData, sizeof(projectileData)); - - SDKHook(ent, SDKHook_TouchPost, Hook_PvPProjectileBallOfFireTouchPost); + damage *= 3.0; } - if (g_PlayerPvPTimer[ownerEntity] == null) - { - SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); - SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); - } - else + int damageType = DMG_ENERGYBEAM; + if (player.IsCritBoosted()) { - //Client is not in pvp, remove the projectile - if (g_PvPArenaProjectileZapConVar.BoolValue) - { - PvP_ZapProjectile(ent, false); - } + damageType |= DMG_CRIT; } + SDKHooks_TakeDamage(healTarget.index, player.index, player.index, damage, damageType, .bypassHooks = false); + g_PlayerMedigunDrainTime[player.index] = gameTime + 0.1; } } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); } -static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntity) +static void ClientPreThinkPost(int client) { - int index = g_PvPBallsOfFire.FindValue(projectile); - if (index != -1) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) { - PvPProjectile_BallOfFire projectileData; - g_PvPBallsOfFire.GetArray(index, projectileData, sizeof(projectileData)); - projectileData.OnTouchPost(otherEntity); + return; + } + + if (player.Team != TFTeam_Spectator) + { + return; } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); } -static void OnPlayerSpawn(SF2_BasePlayer client) +static void ClientPostThink(int client) { - if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) { return; } - PvP_SetPlayerPvPState(client.index, false, false, false); + if (player.Team == TFTeam_Red) + { + return; + } - g_PlayerIsLeavingPvP[client.index] = false; + // CTFPlayer::DoTauntAttack + if (TF2_IsPlayerInCondition(client, TFCond_Taunting)) + { + g_PostThinkType |= PostThinkType_Spectator; - if (client.IsAlive && client.IsParticipating) + // Allows taunt kill work on both teams + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + return; + } + + int activeWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); + if (!IsValidEntity(activeWeapon)) { - if (!client.IsInGhostMode && !client.IsProxy) + return; + } + + // For functions that use GetEnemyTeam(), move everyone else to the enemy team + // Disabled for now but it doesn't matter since this only applies to the Shortstop + for (int i = 0; i < sizeof(g_EnemyItemIDs); i++) + { + if (GetWeaponID(activeWeapon) == g_EnemyItemIDs[i]) { - if (client.IsEliminated || client.HasEscaped) - { - bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + g_PostThinkType |= PostThinkType_EnemyTeam; - if (autoSpawn) + TFTeam enemyTeam = GetEnemyTeam(TF2_GetClientTeam(client)); + + for (int other = 1; other <= MaxClients; other++) + { + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + if (otherPlayer.IsValid && otherPlayer.index != player.index && (otherPlayer.IsInPvP)) + { + otherPlayer.SetProp(Prop_Send, "m_iTeamNum", view_as(enemyTeam)); + } + } + } + } + + // For functions that do simple GetTeamNumber() checks, move ourselves to spectator team + for (int i = 0; i < sizeof(g_SpectatorItemIDs); i++) + { + // Don't let losing team attack with those weapons + if (GameRules_GetRoundState() == RoundState_TeamWin && TF2_GetClientTeam(client) != view_as(GameRules_GetProp("m_iWinningTeam"))) + { + break; + } + + if (GetWeaponID(activeWeapon) == g_SpectatorItemIDs[i]) + { + g_PostThinkType |= PostThinkType_Spectator; + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } +} + +static void ClientPostThinkPost(int client) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsInPvP) + { + return; + } + + // Change everything back to how it was accordingly + if ((g_PostThinkType & PostThinkType_Spectator) != 0) + { + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + + if ((g_PostThinkType & PostThinkType_EnemyTeam) != 0) + { + for (int other = 1; other <= MaxClients; other++) + { + SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); + if (otherPlayer.IsValid && otherPlayer.index != player.index && otherPlayer.IsInPvP) + { + otherPlayer.SetProp(Prop_Send, "m_iTeamNum", view_as(GetEnemyTeam(TF2_GetClientTeam(otherPlayer.index)))); + } + } + } + + g_PostThinkType = PostThinkType_None; +} + +static Action Hook_PvPProjectile_OnTouch(int projectile, int client) +{ + // Check if the projectile hit a player outside of pvp area + // Without that, cannon balls can bounce players which should not happen because they are outside of pvp. + bool remove = false; + if (IsValidClient(client) && !IsClientInPvP(client)) + { + remove = true; + } + + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (owner == client) + { + remove = false; + } + + if (remove) + { + float vel[3]; + CBaseEntity(client).GetAbsVelocity(vel); + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vel); + RemoveEntity(projectile); + return Plugin_Handled; + } + + return Plugin_Continue; +} + +static void Hook_PvPProjectileSpawnPost(int ent) +{ + if (!IsValidEntity(ent)) + { + return; + } + + char class[64]; + GetEntityClassname(ent, class, sizeof(class)); + + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (ownerEntity == -1 && throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + } + + if (IsValidClient(ownerEntity)) + { + if (IsClientInPvP(ownerEntity)) + { + if (strcmp(class, "tf_projectile_pipe", false) == 0 && GetEntProp(ent, Prop_Send, "m_iType") == 3) + { + /* + Loose Cannon's projectiles + KR: I'm assuming that stopping non-PvP players from getting bounced is the reason why this is implemented. + Despite hooking onto Touch, the knockback still happens half of the time. StartTouch yields the same result + so this is really the best that can be done. + */ + SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + } + else if (strcmp(class, "tf_projectile_balloffire", false) == 0) + { + /* + Replicate projectile logic for Dragon's Fury projectiles. + KR: The projectile checks the team of its owner entity (the player), not itself. CBaseEntity::InSameTeam() + could be hooked to change this, but I think that would be too game-changing and not really worth doing for + a single case, so the enemy player logic is sort of replicated. + */ + + PvPProjectile_BallOfFire projectileData; + projectileData.Init(ent); + g_PvPBallsOfFire.PushArray(projectileData, sizeof(projectileData)); + + SDKHook(ent, SDKHook_TouchPost, Hook_PvPProjectileBallOfFireTouchPost); + } + + if (g_PlayerPvPTimer[ownerEntity] == null) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + else + { + //Client is not in pvp, remove the projectile + if (g_PvPArenaProjectileZapConVar.BoolValue) + { + PvP_ZapProjectile(ent, false); + } + } + } + } +} + +static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntity) +{ + int index = g_PvPBallsOfFire.FindValue(projectile); + if (index != -1) + { + PvPProjectile_BallOfFire projectileData; + g_PvPBallsOfFire.GetArray(index, projectileData, sizeof(projectileData)); + projectileData.OnTouchPost(otherEntity); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + PvP_SetPlayerPvPState(client.index, false, false, false); + + g_PlayerIsLeavingPvP[client.index] = false; + + g_PlayerOriginalTeam[client.index] = client.Team; + + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + if (client.IsAlive && client.IsParticipating) + { + if (!client.IsInGhostMode && !client.IsProxy) + { + if (client.IsEliminated || client.HasEscaped) + { + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + + if (autoSpawn) { g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.12, Timer_TeleportPlayerToPvP, client.UserID, TIMER_FLAG_NO_MAPCHANGE); } @@ -560,6 +943,22 @@ void PvP_ZapProjectile(int projectile, bool effects=true) RemoveEntity(projectile); } +static void OnPlayerDeathPre(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + if (client.IsInPvP) + { + client.SetProp(Prop_Send, "m_iTeamNum", g_PlayerOriginalTeam[client.index]); + } + } +} + static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) { if (!g_Enabled) @@ -614,238 +1013,888 @@ static bool Hook_ClientPvPShouldCollide(int ent,int collisiongroup,int contentsm { return true; } - return originalResult; -} + return originalResult; +} + +void PvP_OnTriggerStartTouch(int trigger, int other) +{ + char name[64]; + GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); + + if (StrContains(name, "sf2_pvp_trigger", false) == 0 || SF2TriggerPvPEntity(trigger).IsValid()) + { + if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + { + if (!g_PlayerEliminated[other] && !DidClientEscape(other)) + { + return; + } + //Use valve's kill code if the player is stuck. + if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) + { + TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); + } + //Resize the player. + SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); + + int entRef = EnsureEntRef(trigger); + if (g_PlayerEnteredPvPTriggers[other].FindValue(entRef) == -1) + { + g_PlayerEnteredPvPTriggers[other].Push(entRef); + } + + if (IsClientInPvP(other)) + { + if (g_PlayerIsLeavingPvP[other]) + { + // Player left and came back again, but is still in PvP mode. + g_PlayerPvPTimerCount[other] = 0; + g_PlayerPvPTimer[other] = null; + g_PlayerIsLeavingPvP[other] = false; + } + } + else + { + PvP_SetPlayerPvPState(other, true); + } + } + } +} + +void PvP_OnTriggerEndTouch(int trigger, int other) +{ + if (IsValidClient(other)) + { + if (g_PlayerEnteredPvPTriggers[other] != null) + { + int triggerEntRef = EnsureEntRef(trigger); + for (int i = g_PlayerEnteredPvPTriggers[other].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredPvPTriggers[other].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + } + } + + if (IsClientInPvP(other)) + { + if (g_PlayerEnteredPvPTriggers[other].Length == 0) + { + g_PlayerPvPTimerCount[other] = g_PvPArenaLeaveTimeConVar.IntValue; + if (g_PlayerPvPTimerCount[other] != 0) + { + g_PlayerPvPTimer[other] = CreateTimer(1.0, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerIsLeavingPvP[other] = true; + } + else + { + g_PlayerPvPTimer[other] = CreateTimer(0.1, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + + //A projectile went off pvp area. + if (other > MaxClients && IsValidEntity(other)) + { + //Get entity's classname. + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + //Yup it's a projectile zap it! + //But we have to wait to prevent some bugs. + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static void PvP_OnTriggerStartTouchBoxing(int trigger, int other) +{ + if (other > MaxClients && IsValidEntity(other) && !IsRoundInWarmup() && !IsRoundEnding()) + { + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static Action EntityStillAlive(Handle timer, int ref) +{ + int ent = EntRefToEntIndex(ref); + if (ent && ent != INVALID_ENT_REFERENCE) + { + PvP_ZapProjectile(ent); + } + return Plugin_Stop; +} +/** + * Enables/Disables PvP mode on the player. + */ +void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles = true, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool oldInPvP = g_PlayerInPvP[player.index]; + if (status == oldInPvP) + { + return; // no change + } + + if (status && player.IsInPvE) + { + PvE_ForceResetPlayerPvEData(player.index); + PvE_SetPlayerPvEState(player.index, false, false); + } + + g_PlayerInPvP[player.index] = status; + g_PlayerPvPTimer[player.index] = null; + g_PlayerPvPRespawnTimer[player.index] = null; + g_PlayerPvPTimerCount[player.index] = 0; + + if (removeProjectiles) + { + // Remove previous projectiles. + PvP_RemovePlayerProjectiles(player.index); + } + + if (status) + { + Call_StartForward(g_OnPlayerEnterPvP); + Call_PushCell(player.index); + Call_Finish(); + } + else + { + Call_StartForward(g_OnPlayerExitPvP); + Call_PushCell(player.index); + Call_Finish(); + } + + if (regenerate) + { + // Regenerate player but keep health the same. + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +static void FlameStartTouch(int flame, int other) +{ + SF2_BasePlayer client = SF2_BasePlayer(other); + if (client.IsValid) + { + if ((client.IsInPvP) && !IsRoundEnding()) + { + int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); + if (IsValidEdict(flamethrower)) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.index != client.index && ownerEntity.IsValid) + { + if (ownerEntity.IsInPvP) + { + if (client.Team == ownerEntity.Team && ownerEntity.Team != TFTeam_Red) + { + ownerEntity.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } + } + } + } + } +} + +static void FlameStartTouchPost(int flame, int other) +{ + int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); + if (IsValidEdict(flamethrower)) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.IsValid && ownerEntity.Team == TFTeam_Spectator) + { + ownerEntity.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + } +} + +static Action GasStartTouch(int entity, int other) +{ + SF2_BasePlayer client = SF2_BasePlayer(other); + if (client.IsValid) + { + if ((client.IsInPvP) && !IsRoundEnding()) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.index == client.index) + { + return Plugin_Handled; + } + + if (ownerEntity.IsValid) + { + if (!ownerEntity.IsInPvP) + { + return Plugin_Handled; + } + } + } + } + + return Plugin_Continue; +} + +static Action PipeOnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) +{ + if (attacker != -1) + { + int owner = GetEntPropEnt(victim, Prop_Data, "m_hOwnerEntity"); + if (owner == attacker) + { + return Plugin_Handled; + } + + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) + { + if (player.Team == TFTeam_Red) + { + return Plugin_Handled; + } + + if (!player.IsInPvP) + { + return Plugin_Handled; + } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } + + return Plugin_Continue; +} + +static void PipeOnTakeDamagePost(int victim, int attacker, int inflictor, float damage, int damagetype) +{ + if (attacker != -1) + { + int owner = GetEntPropEnt(victim, Prop_Data, "m_hOwnerEntity"); + if (owner == attacker) + { + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) + { + if (player.Team == TFTeam_Red) + { + return; + } + + if (!player.IsInPvP) + { + return; + } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + } +} + +static MRESReturn PhysicsDispatchThinkPre(int entity) +{ + char classname[64]; + if (!GetEntityClassname(entity, classname, sizeof(classname))) + { + return MRES_Ignored; + } + + if (strcmp(classname, "obj_sentrygun") == 0) + { + // CObjectSentrygun::SentryThink + if (GetNextThink(entity, "SentryThink") != TICK_NEVER_THINK) + { + return MRES_Ignored; + } + + g_ThinkFunction = ThinkFunction_SentryThink; + + TFTeam myTeam = TF2_GetEntityTeam(entity); + TFTeam enemyTeam = GetEnemyTeam(myTeam); + Address globalTeam = SDKCall_GetGlobalTeam(enemyTeam); + + // CObjectSentrygun::FindTarget uses CTFTeamManager to collect valid players. + // Add all enemy players to the desired team. + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && (player.IsInPvP)) + { + TFTeam team = TF2_GetClientTeam(client); + g_PreHookTeam[client] = team; + bool friendly = IsObjectFriendly(entity, client); + + if (friendly && team == enemyTeam) + { + CTeam_RemovePlayer(globalTeam, client); + } + else if (!friendly && team != enemyTeam) + { + CTeam_AddPlayer(globalTeam, client); + } + + // Sentry Guns don't shoot spies disguised as the same team, spoof the disguise team + if (!friendly) + { + g_PreHookDisguiseTeam[client] = view_as(GetEntProp(client, Prop_Send, "m_nDisguiseTeam")); + SetEntProp(client, Prop_Send, "m_nDisguiseTeam", TFTeam_Spectator); + } + } + } + + // Buildings work in a similar way. + // NOTE: Previously, we would use CBaseObject::ChangeTeam, but we switched to AddObject/RemoveObject calls, + // due to ChangeTeam recreating the build points, causing issues with sapper placement. + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) + { + if (!GetEntProp(obj, Prop_Send, "m_bPlacing")) + { + TFTeam team = TF2_GetEntityTeam(obj); + g_PreHookTeam[obj] = team; + bool friendly = IsObjectFriendly(entity, obj); + + if (friendly && team == enemyTeam) + { + CTeam_RemoveObject(globalTeam, obj); + } + else if (!friendly && team != enemyTeam) + { + CTeam_AddObject(globalTeam, obj); + } + } + } + } + else if (strcmp(classname, "obj_dispenser") == 0) + { + // CObjectDispenser::DispenseThink + if (GetNextThink(entity, "DispenseThink") != TICK_NEVER_THINK) + { + return MRES_Ignored; + } + + if (!GetEntProp(entity, Prop_Send, "m_bPlacing") && !GetEntProp(entity, Prop_Send, "m_bBuilding")) + { + g_ThinkFunction = ThinkFunction_DispenseThink; + + // Disallow players able to be healed from dispenser + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && player.IsInPvP && player.Team != TFTeam_Red) + { + if (!IsObjectFriendly(entity, client)) + { + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + } + } + } + } + else if (strcmp(classname, "obj_attachment_sapper") == 0) + { + // CBaseObject::BaseObjectThink + if (GetNextThink(entity, "BaseObjectThink") != TICK_NEVER_THINK) + { + return MRES_Ignored; + } + + g_ThinkFunction = ThinkFunction_SapperThink; + + // Always set team to spectator so we can place sappers on buildings of both teams + CBaseEntity_ChangeTeam(entity, view_as(TFTeam_Spectator)); + } + + return MRES_Ignored; +} + +static MRESReturn PhysicsDispatchThinkPost(int entity) +{ + switch (g_ThinkFunction) + { + case ThinkFunction_SentryThink: + { + TFTeam myTeam = TF2_GetEntityTeam(entity); + TFTeam enemyTeam = GetEnemyTeam(myTeam); + Address globalTeam = SDKCall_GetGlobalTeam(enemyTeam); + + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && (player.IsInPvP)) + { + TFTeam team = g_PreHookTeam[client]; + g_PreHookTeam[client] = TFTeam_Unassigned; + bool friendly = IsObjectFriendly(entity, client); + + if (friendly && team == enemyTeam) + { + CTeam_AddPlayer(globalTeam, client); + } + else if (!friendly && team != enemyTeam) + { + CTeam_RemovePlayer(globalTeam, client); + } + + if (!friendly) + { + SetEntProp(client, Prop_Send, "m_nDisguiseTeam", g_PreHookDisguiseTeam[client]); + g_PreHookDisguiseTeam[client] = TFTeam_Unassigned; + } + } + } + + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) + { + if (!GetEntProp(obj, Prop_Send, "m_bPlacing")) + { + TFTeam team = g_PreHookTeam[obj]; + bool friendly = IsObjectFriendly(entity, obj); + + if (friendly && team == enemyTeam) + { + CTeam_AddObject(globalTeam, obj); + } + else if (!friendly && team != enemyTeam) + { + CTeam_RemoveObject(globalTeam, obj); + } + + g_PreHookTeam[obj] = TFTeam_Unassigned; + } + } + } + case ThinkFunction_DispenseThink: + { + for (int client = 1; client <= MaxClients; client++) + { + SF2_BasePlayer player = SF2_BasePlayer(client); + if (player.IsValid && (player.IsInPvP) && player.Team != TFTeam_Red) + { + if (!IsObjectFriendly(entity, client)) + { + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } + } + } + } + } + + g_ThinkFunction = ThinkFunction_None; + + return MRES_Ignored; +} + +static MRESReturn FlareExplodePre(int entity, DHookParam params) +{ + SF2_BasePlayer other = SF2_BasePlayer(params.Get(2)); + if (!other.IsValid) + { + return MRES_Ignored; + } + + if (!other.IsInPvP) + { + return MRES_Ignored; + } + + if (other.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + other.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + + return MRES_Ignored; +} + +static MRESReturn FlareExplodePost(int entity, DHookParam params) +{ + SF2_BasePlayer other = SF2_BasePlayer(params.Get(2)); + if (!other.IsValid) + { + return MRES_Ignored; + } + + if (!other.IsInPvP) + { + return MRES_Ignored; + } + + if (other.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + other.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + + return MRES_Ignored; +} + +static MRESReturn AllowedToHealTargetPre(int medigun, DHookReturn ret, DHookParam params) +{ + SF2_BasePlayer owner = SF2_BasePlayer(GetEntPropEnt(medigun, Prop_Send, "m_hOwnerEntity")); + if (!owner.IsValid) + { + owner = SF2_BasePlayer(GetEntPropEnt(medigun, Prop_Send, "m_hOwner")); + } + int entity = params.Get(1); + SF2_BasePlayer target = SF2_BasePlayer(entity); + if (owner.IsEliminated) + { + if (target.IsValid) + { + if (target.IsEliminated) + { + if (owner.IsInPvP && !target.IsInPvP) + { + ret.Value = false; + return MRES_Supercede; + } + + if (!owner.IsInPvP && target.IsInPvP) + { + ret.Value = false; + return MRES_Supercede; + } + } + else + { + ret.Value = true; + return MRES_Supercede; + } + } + else if (entity > MaxClients && NPCGetFromEntIndex(entity) != -1) + { + ret.Value = true; + return MRES_Supercede; + } + } + return MRES_Ignored; +} + +static MRESReturn JarExplodePre(int entity, DHookParam params) +{ + SF2_BasePlayer thrower = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Send, "m_hThrower")); + if (!thrower.IsValid) + { + return MRES_Ignored; + } + + if (!thrower.IsInPvP) + { + return MRES_Ignored; + } + + if (thrower.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + thrower.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + + return MRES_Ignored; +} + +static MRESReturn JarExplodePost(int entity, DHookParam params) +{ + SF2_BasePlayer thrower = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Send, "m_hThrower")); + if (!thrower.IsValid) + { + return MRES_Ignored; + } + + if (!thrower.IsInPvP) + { + return MRES_Ignored; + } + + if (thrower.Team == TFTeam_Red) + { + return MRES_Ignored; + } + + thrower.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + + return MRES_Ignored; +} + +static MRESReturn VPhysicsUpdatePre(int entity, DHookParam params) +{ + SF2_BasePlayer thrower = SF2_BasePlayer(GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity")); + if (!thrower.IsValid) + { + return MRES_Ignored; + } + + if (!thrower.IsInPvP) + { + return MRES_Ignored; + } + + if (thrower.Team == TFTeam_Red) + { + return MRES_Ignored; + } -void PvP_OnTriggerStartTouch(int trigger,int other) -{ - char name[64]; - GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); + TFTeam enemyTeam = GetEnemyTeam(TF2_GetEntityTeam(entity)); - if (StrContains(name, "sf2_pvp_trigger", false) == 0 || SF2TriggerPvPEntity(trigger).IsValid()) + // Not needed because of our CanCollideWithTeammates hook, but can't hurt + for (int i = 1; i <= MaxClients; i++) { - if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.Team == TFTeam_Red) { - if (!g_PlayerEliminated[other] && !DidClientEscape(other)) - { - return; - } - //Use valve's kill code if the player is stuck. - if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) - { - TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); - } - //Resize the player. - SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); - SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); - SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); - SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); + continue; + } - int entRef = EnsureEntRef(trigger); - if (g_PlayerEnteredPvPTriggers[other].FindValue(entRef) == -1) - { - g_PlayerEnteredPvPTriggers[other].Push(entRef); - } + if (!client.IsInPvP) + { + continue; + } - if (IsClientInPvP(other)) - { - if (g_PlayerIsLeavingPvP[other]) - { - // Player left and came back again, but is still in PvP mode. - g_PlayerPvPTimerCount[other] = 0; - g_PlayerPvPTimer[other] = null; - g_PlayerIsLeavingPvP[other] = false; - } - } - else - { - PvP_SetPlayerPvPState(other, true); - } + client.SetProp(Prop_Send, "m_iTeamNum", view_as(enemyTeam)); + } + + // Fix projectiles rarely bouncing off buildings + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) + { + if (!IsObjectFriendly(obj, thrower.index)) + { + continue; + } + + if (CBaseEntity(obj).GetProp(Prop_Send, "m_iTeamNum") == TFTeam_Red) + { + continue; } + + CBaseEntity(obj).SetProp(Prop_Send, "m_iTeamNum", view_as(enemyTeam)); } + + return MRES_Ignored; } -void PvP_OnTriggerEndTouch(int trigger, int other) +static MRESReturn VPhysicsUpdatePost(int entity, DHookParam params) { - if (IsValidClient(other)) + int thrower = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity"); + + for (int i = 1; i <= MaxClients; i++) { - if (g_PlayerEnteredPvPTriggers[other] != null) + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid || client.Team == TFTeam_Red) { - int triggerEntRef = EnsureEntRef(trigger); - for (int i = g_PlayerEnteredPvPTriggers[other].Length - 1; i >= 0; i--) - { - int entRef = g_PlayerEnteredPvPTriggers[other].Get(i); - if (entRef == triggerEntRef) - { - g_PlayerEnteredPvPTriggers[other].Erase(i); - } - else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) - { - g_PlayerEnteredPvPTriggers[other].Erase(i); - } - } + continue; } - if (IsClientInPvP(other)) + if (!client.IsInPvP) { - if (g_PlayerEnteredPvPTriggers[other].Length == 0) - { - g_PlayerPvPTimerCount[other] = g_PvPArenaLeaveTimeConVar.IntValue; - if (g_PlayerPvPTimerCount[other] != 0) - { - g_PlayerPvPTimer[other] = CreateTimer(1.0, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - g_PlayerIsLeavingPvP[other] = true; - } - else - { - g_PlayerPvPTimer[other] = CreateTimer(0.1, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - } - } + continue; } + + client.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); } - //A projectile went off pvp area. - if (other > MaxClients && IsValidEntity(other)) + int obj = -1; + while ((obj = FindEntityByClassname(obj, "obj_*")) != -1) { - //Get entity's classname. - char classname[50]; - GetEntityClassname(other, classname, sizeof(classname)); - for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + if (!IsObjectFriendly(obj, thrower)) { - if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) - { - if (!GetEntProp(other, Prop_Send, "m_iDeflected")) - { - //Yup it's a projectile zap it! - //But we have to wait to prevent some bugs. - CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); - } - } + continue; } + + CBaseEntity(obj).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); } + + return MRES_Ignored; } -static void PvP_OnTriggerStartTouchBoxing(int trigger, int other) +static Action CleaverTouch(int entity, int other) { - if (other > MaxClients && IsValidEntity(other) && !IsRoundInWarmup() && !IsRoundEnding()) + if (other == 0) { - char classname[50]; - GetEntityClassname(other, classname, sizeof(classname)); - for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) - { - if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) - { - if (!GetEntProp(other, Prop_Send, "m_iDeflected")) - { - CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); - } - } - } + return Plugin_Continue; } -} -static Action EntityStillAlive(Handle timer, int ref) -{ - int ent = EntRefToEntIndex(ref); - if (ent && ent != INVALID_ENT_REFERENCE) + int owner = GetEntPropEnt(entity, Prop_Data, "m_hThrower"); + if (owner == other) { - PvP_ZapProjectile(ent); + return Plugin_Continue; } - return Plugin_Stop; + + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) + { + if (player.Team == TFTeam_Red) + { + return Plugin_Continue; + } + + if (!player.IsInPvP) + { + return Plugin_Continue; + } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + } + + return Plugin_Continue; } -/** - * Enables/Disables PvP mode on the player. - */ -void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles = true, bool regenerate = true) + +static void CleaverTouchPost(int entity, int other) { - SF2_BasePlayer player = SF2_BasePlayer(client); - if (!player.IsValid) + if (other == 0) { return; } - bool oldInPvP = g_PlayerInPvP[player.index]; - if (status == oldInPvP) + int owner = GetEntPropEnt(entity, Prop_Data, "m_hThrower"); + if (owner == other) { - return; // no change + return; } - if (status && player.IsInPvE) + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) { - PvE_ForceResetPlayerPvEData(player.index); - PvE_SetPlayerPvEState(player.index, false, false); - } + if (player.Team == TFTeam_Red) + { + return; + } - g_PlayerInPvP[player.index] = status; - g_PlayerPvPTimer[player.index] = null; - g_PlayerPvPRespawnTimer[player.index] = null; - g_PlayerPvPTimerCount[player.index] = 0; + if (!player.IsInPvP) + { + return; + } - if (removeProjectiles) - { - // Remove previous projectiles. - PvP_RemovePlayerProjectiles(player.index); + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); } +} - if (status) +static Action GrenadeTouch(int entity, int other) +{ + if (other == 0) { - Call_StartForward(g_OnPlayerEnterPvP); - Call_PushCell(player.index); - Call_Finish(); + return Plugin_Continue; } - else + + int owner = GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity"); + if (owner == other) { - Call_StartForward(g_OnPlayerExitPvP); - Call_PushCell(player.index); - Call_Finish(); + return Plugin_Continue; } - if (regenerate) + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) { - // Regenerate player but keep health the same. - int health = player.GetProp(Prop_Send, "m_iHealth"); - player.RemoveWeaponSlot(TFWeaponSlot_Primary); - player.RemoveWeaponSlot(TFWeaponSlot_Secondary); - player.Regenerate(); - player.SetProp(Prop_Data, "m_iHealth", health); - player.SetProp(Prop_Send, "m_iHealth", health); + if (player.Team == TFTeam_Red) + { + return Plugin_Continue; + } + + if (!player.IsInPvP) + { + return Plugin_Continue; + } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Spectator); } + + return Plugin_Continue; } -static void PvP_OnFlameEntityStartTouchPost(int flame, int other) //Thanks Fire +static void GrenadeTouchPost(int entity, int other) { - static float lastHit[MAXPLAYERS + 1]; + if (other == 0) + { + return; + } - SF2_BasePlayer client = SF2_BasePlayer(other); - if (client.IsValid) + int owner = GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity"); + if (owner == other) { - float time = GetEngineTime(); - if (lastHit[client.index] < time) + return; + } + + SF2_BasePlayer player = SF2_BasePlayer(owner); + if (player.IsValid) + { + if (player.Team == TFTeam_Red) { - lastHit[client.index] = time + FLAME_HIT_DELAY; + return; + } - if ((IsRoundInWarmup() || client.IsInPvP) && !IsRoundEnding()) - { - int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); - if (IsValidEdict(flamethrower)) - { - SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); - if (ownerEntity.index != client.index && ownerEntity.IsValid) - { - if (IsRoundInWarmup() || ownerEntity.IsInPvP) - { - if (client.Team == ownerEntity.Team && ownerEntity.Team != TFTeam_Red) - { - //TF2_MakeBleed(other, ownerEntity, 4.0); - client.Ignite(_, ownerEntity.index); - client.TakeDamage(_, ownerEntity.index, ownerEntity.index, 10.0, ownerEntity.IsCritBoosted() ? (DMG_BURN | DMG_PREVENT_PHYSICS_FORCE | DMG_ACID) : DMG_BURN | DMG_PREVENT_PHYSICS_FORCE); - } - } - } - } - } + if (!player.IsInPvP) + { + return; } + + player.SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + CBaseEntity(entity).SetProp(Prop_Send, "m_iTeamNum", TFTeam_Blue); + } +} + +static MRESReturn GetCustomDamageTypePost(int entity, DHookReturn ret) +{ + // Allows Sniper Rifles to hit teammates, without breaking Machina penetration + int penetrateType = SDKCall(g_SDKGetPenetrationType, entity); + if (penetrateType == 0) + { + ret.Value = 0; + return MRES_Supercede; } + + return MRES_Ignored; } /** @@ -1091,6 +2140,7 @@ Action Hook_PvPPlayerTraceAttack(int victim, int &attacker, int &inflictor, floa return Plugin_Continue; } + static Action TempEntHook_PvPDecal(const char[] te_name, int[] players, int numPlayers, float delay) { if (!g_Enabled) @@ -1113,56 +2163,42 @@ static Action TempEntHook_PvPDecal(const char[] te_name, int[] players, int numP return Plugin_Continue; } -MRESReturn PvP_GetWeaponCustomDamageType(int weapon, int client, int &customDamageType) +// For mediguns, eventually I'll make proper assistants +static Action OnPlayerDeathEventPre(Event event, const char[] name, bool dontBroadcast) { - if (IsValidClient(client) && IsClientInPvP(client) && IsValidEntity(weapon) && IsValidEdict(weapon) && IsValidEntity(client)) + if (!g_Enabled) { - static const char fixWeaponPenetrationClasses[][] = - { - "tf_weapon_sniperrifle", - "tf_weapon_sniperrifle_decap", - "tf_weapon_sniperrifle_classic" - }; + return Plugin_Continue; + } - char weaponName[256]; - GetEntityClassname(weapon, weaponName, sizeof(weaponName)); + SF2_BasePlayer attacker = SF2_BasePlayer(GetClientOfUserId(event.GetInt("attacker"))); - /* - * Fixes the sniper rifle not damaging teammates. - * - * WHY? For every other hitscan weapon in the game, simply enforcing lag compensation in CTFPlayer::WantsLagCompensationOnEntity() - * works. However, when it comes to weapons that penetrate teammates, the bullet trace will not iterate through teammates. This is - * the case with all sniper rifles, and is the reason why damage is never normally dealt to teammates despite having friendly fire - * on and lag compensation. - * - * In this case, the type of penetration is determined by CTFWeaponBase::GetCustomDamageType(). For Snipers, default value is - * TF_DMG_CUSTOM_PENETRATE_MY_TEAM (11) (piss rifle is TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE (14)). This value specifies - * penetration of the bullet through teammates without damaging them. The damage type is switched to 0, and for the Machina at - * full charge, TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS (12). - * - */ - for (int i = 0; i < sizeof(fixWeaponPenetrationClasses); i++) + if (attacker.IsValid && attacker.IsInPvP) + { + CBaseEntity weapon = CBaseEntity(attacker.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (weapon.IsValid() && GetWeaponID(weapon.index) == TF_WEAPON_MEDIGUN) { - if (strcmp(weaponName, fixWeaponPenetrationClasses[i], false) == 0) - { - customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS - int itemDefIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); - - if ((itemDefIndex == 526 || itemDefIndex == 30665) && GetEntPropFloat(weapon, Prop_Send, "m_flChargedDamage") >= 150.0) // The Machina, Shooting Star - { - customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS - } - else - { - customDamageType = 0; // no penetration behavior. - } + event.SetString("weapon_logclassname", "tf_weapon_deathgun"); + event.SetString("weapon", "merasmus_zap"); + } + } - return MRES_Supercede; + SF2_BasePlayer assister = SF2_BasePlayer(GetClientOfUserId(event.GetInt("assister"))); + if (assister.IsValid && assister.IsInPvP) + { + CBaseEntity weapon = CBaseEntity(assister.GetPropEnt(Prop_Send, "m_hActiveWeapon")); + if (weapon.IsValid() && GetWeaponID(weapon.index) == TF_WEAPON_MEDIGUN) + { + CBaseEntity target = CBaseEntity(weapon.GetPropEnt(Prop_Send, "m_hHealingTarget")); + if (target.index == attacker.index) + { + event.SetInt("assister", -1); + event.SetString("assister_fallback", ""); } } } - return MRES_Ignored; + return Plugin_Changed; } static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHookReturn returnHandle, DHookParam params) @@ -1172,19 +2208,8 @@ static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHoo return MRES_Ignored; } - if (!IsValidEntity(projectile)) - { - return MRES_Ignored; - } - - int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); - if (IsValidClient(owner) && (IsRoundInWarmup() || IsClientInPvP(owner))) - { - returnHandle.Value = true; - return MRES_Supercede; - } - - return MRES_Ignored; + returnHandle.Value = true; + return MRES_Supercede; } // API diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index e01823e3..0314a30f 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -50,6 +50,7 @@ #define MAX_BUTTONS 26 +// m_nSolidType #define SOLID_NONE 0 // no solid model #define SOLID_BSP 1 // a BSP tree #define SOLID_BBOX 2 // an AABB @@ -69,14 +70,6 @@ #define TF_WEAPON_PHLOGISTINATOR 594 -// m_nSolidType -#define SOLID_NONE 0 // no solid model -#define SOLID_BSP 1 // a BSP tree -#define SOLID_BBOX 2 // an AABB -#define SOLID_OBB 3 // an OBB (not implemented yet) -#define SOLID_OBB_YAW 4 // an OBB, constrained so that it can only yaw -#define SOLID_CUSTOM 5 // Always call into the entity for tests -#define SOLID_VPHYSICS 6 // solid vphysics object, get vcollide from the model and collide with that // m_usSolidFlags #define FSOLID_CUSTOMRAYTEST 0x0001 // Ignore solid type + always call into the entity for ray tests #define FSOLID_CUSTOMBOXTEST 0x0002 // Ignore solid type + always call into the entity for swept box tests @@ -90,26 +83,6 @@ #define FSOLID_ROOT_PARENT_ALIGNED 0x0100 // Collisions are defined in root parent's local coordinate space #define FSOLID_TRIGGER_TOUCH_DEBRIS 0x0200 // This trigger will touch debris objects -#define DAMAGE_NO 0 -#define DAMAGE_EVENTS_ONLY 1 -#define DAMAGE_YES 2 -#define DAMAGE_AIM 3 - -#define EF_BONEMERGE 0x001 // Performs bone merge on client side -#define EF_BRIGHTLIGHT 0x002 // DLIGHT centered at entity origin -#define EF_DIMLIGHT 0x004 // player flashlight -#define EF_NOINTERP 0x008 // don't interpolate the next frame -#define EF_NOSHADOW 0x010 // Don't cast no shadow -#define EF_NODRAW 0x020 // don't draw entity -#define EF_NORECEIVESHADOW 0x040 // Don't receive no shadow -#define EF_BONEMERGE_FASTCULL 0x080 // For use with EF_BONEMERGE. If this is set, then it places this ent's origin at its - // parent and uses the parent's bbox + the max extents of the aiment. - // Otherwise, it sets up the parent's bones every frame to figure out where to place - // the aiment, which is inefficient because it'll setup the parent's bones even if - // the parent is not in the PVS. -#define EF_ITEM_BLINK 0x100 // blink an item so that the user notices it. -#define EF_PARENT_ANIMATES 0x200 // always assume that the parent entity is animating - // hull defines, mostly used for space checking. float HULL_HUMAN_MINS[3] = { -13.0, -13.0, 0.0 }; float HULL_HUMAN_MAXS[3] = { 13.0, 13.0, 72.0 }; @@ -439,6 +412,11 @@ void GetBonePosition(int entity, int bone, float origin[3], float angles[3]) SDKCall(g_SDKGetBonePosition, entity, bone, origin, angles); } +bool CBaseAnimating_GetSequenceVelocity(Address studioHdr, int sequence, float cycle, Address poseParameter, float velocity[3]) +{ + return SDKCall(g_SDKSequenceVelocity, studioHdr, sequence, cycle, poseParameter, velocity); +} + // ========================================================= // GLOW FUNCTIONS // ========================================================= @@ -545,6 +523,16 @@ int SDK_SwitchWeapon(int client, int weapon) return -1; } +int GetWeaponID(int entity) +{ + return SDKCall(g_SDKGetWeaponID, entity); +} + +bool IsEntityWeapon(int entity) +{ + return SDKCall(g_SDKIsWeapon, entity); +} + bool IsClientInKart(int client) { if (TF2_IsPlayerInCondition(client, TFCond_HalloweenKart) || @@ -813,14 +801,45 @@ void ShowHealthRegen(int client, int amount, int weaponIndex = -1) event.Fire(); } } + +bool IsObjectFriendly(int obj, int entity) +{ + if (IsValidEntity(entity)) + { + if (IsValidClient(entity)) + { + if (GetEntPropEnt(obj, Prop_Send, "m_hBuilder") == GetEntPropEnt(entity, Prop_Send, "m_hDisguiseTarget")) + { + return true; + } + else if (GetEntPropEnt(obj, Prop_Send, "m_hBuilder") == entity) // obj_dispenser + { + return true; + } + else if (GetEntPropEnt(obj, Prop_Data, "m_hParent") == entity) // pd_dispenser + { + return true; + } + } + else + { + if (GetEntPropEnt(obj, Prop_Send, "m_hBuilder") == GetEntPropEnt(entity, Prop_Send, "m_hBuilder")) + { + return true; + } + } + } + + return false; +} + // ========================================================== // TF2-SPECIFIC FUNCTIONS // ========================================================== bool TF2_IsMiniCritBuffed(int client) { return (TF2_IsPlayerInCondition(client, TFCond_CritCola) - || TF2_IsPlayerInCondition(client, TFCond_Buffed) - ); + || TF2_IsPlayerInCondition(client, TFCond_Buffed)); } bool TF2_IsInvisible(int client) @@ -919,6 +938,31 @@ void SpecialRoundGameText(const char[] message, const char[] icon = "") AcceptEntityInput(entity, "Display", entity, entity); //The only time I keep this. CreateTimer(2.0, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); } + +TFTeam TF2_GetEntityTeam(int entity) +{ + return view_as(GetEntProp(entity, Prop_Data, "m_iTeamNum")); +} + +TFTeam GetEnemyTeam(TFTeam team) +{ + switch (team) + { + case view_as(TFTeam_Red): + { + return view_as(TFTeam_Blue); + } + case view_as(TFTeam_Blue): + { + return view_as(TFTeam_Red); + } + default: + { + return team; + } + } +} + // Removes wearables such as botkillers from weapons. void TF2_RemoveWeaponSlotAndWearables(int client, int slot) { @@ -1519,7 +1563,7 @@ bool IsInfiniteBlinkEnabled() bool IsInfiniteSprintEnabled() { - return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1); + return g_IsRoundInfiniteSprint || (g_PlayerInfiniteSprintOverrideConVar.IntValue == 1) || SF_IsSlaughterRunMap() || SF_IsBoxingMap() || SF_IsRaidMap(); } bool IsNightVisionEnabled() From 2de9b34c3aada3dfdebb7fb4a85343cb43cbef2b Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 22:26:04 -0700 Subject: [PATCH 46/86] Forgot something --- addons/sourcemod/scripting/sf2.sp | 7 +++++++ addons/sourcemod/scripting/sf2/client.sp | 2 +- .../sourcemod/scripting/sf2/npc/entities/chaser/entity.sp | 2 +- .../sourcemod/scripting/sf2/npc/entities/statue/entity.sp | 5 +++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 5c6b0cb8..ca8fd954 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -5084,6 +5084,13 @@ void SetPageCount(int num) SF2BossProfileData data; data = NPCGetProfileData(npcIndex); + if (data.SlaughterRunData.SpawnTime[difficulty] > -1.0) + { + times[bosses] = data.SlaughterRunData.SpawnTime[difficulty]; + bosses++; + continue; + } + float originalSpeed, speed, timerCheck; originalSpeed = data.RunSpeed[difficulty] + NPCGetAddSpeed(npcIndex); float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index 94130405..fd42a215 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -65,7 +65,7 @@ MRESReturn Hook_ClientWantsLagCompensationOnEntity(int client, DHookReturn retur return MRES_Supercede; } -public Action CH_PassFilter(int ent1,int ent2, bool &result) +public Action CH_PassFilter(int ent1, int ent2, bool &result) { SF2RoundState state = GetRoundState(); if (state == SF2RoundState_Intro || state == SF2RoundState_Outro) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 983d188a..6bc4c68b 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -4756,7 +4756,7 @@ static void ProcessSpeed(SF2_ChaserEntity chaser) if (SF_IsSlaughterRunMap()) { float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; - if (speed < slaughterSpeed) + if (!originalData.SlaughterRunData.CustomMinimumSpeed[difficulty] && speed < slaughterSpeed) { speed = slaughterSpeed; } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index 03d8fc35..4e5d6ffd 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -515,9 +515,10 @@ static void ProcessSpeed(SF2_StatueEntity statue) } if (SF_IsSlaughterRunMap()) { - if (speed < 580.0) + float slaughterSpeed = g_SlaughterRunMinimumBossRunSpeedConVar.FloatValue; + if (!originalData.SlaughterRunData.CustomMinimumSpeed[difficulty] && speed < slaughterSpeed) { - speed = 580.0; + speed = slaughterSpeed; } acceleration += 10000.0; } From cf9c7f22e14c0a6d1ed7d855b57184077f99178c Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 22:33:39 -0700 Subject: [PATCH 47/86] Update deathcam.sp --- addons/sourcemod/scripting/sf2/client/deathcam.sp | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index 9b62acab..c169291c 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -513,7 +513,6 @@ static void StopDeathCam(int client) SetVariantString("!activator"); AcceptEntityInput(light, "SetParent", client); } - PrintToChatAll("Fake"); } } else // The boss is invalid? But the player got a death cam? From 50720ae37109e96d6211f9b48ef7304188f98dd7 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 22:44:15 -0700 Subject: [PATCH 48/86] Forgot the projectiles --- .../projectiles/sf2_arrow_projectile.sp | 2 +- .../projectiles/sf2_baseball_projectile.sp | 94 +++++++++---------- .../projectiles/sf2_grenade_projectile.sp | 39 ++++++-- 3 files changed, 78 insertions(+), 57 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp index e2c552c1..87d2464b 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_arrow_projectile.sp @@ -104,7 +104,7 @@ static void StartTouch(int entity, int other) return; } - bool hit = true; + bool hit = false; SF2_BasePlayer otherPlayer = SF2_BasePlayer(other); int owner = projectile.GetPropEnt(Prop_Send, "m_hOwnerEntity"); if (otherPlayer.IsValid) diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp index 0b2d7536..a9f6f33d 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp @@ -126,69 +126,69 @@ static void Think(int entity) Call_PushCell(projectile); Call_PushCell(CBaseEntity(hitIndex)); Call_Finish(); - } - if (hitIndex == 0) - { - projectile.Touched = true; - return; - } - else - { - int flags = DMG_CLUB; - if (projectile.IsCrits) + if (hitIndex == 0) { - flags |= DMG_ACID; + projectile.Touched = true; + return; } - SF2_BasePlayer player = SF2_BasePlayer(hitIndex); - if (player.IsValid) + else { - if (player.IsInGhostMode) - { - return; - } - - if (player.IsEliminated && !projectile.AttackWaiters) - { - projectile.Touched = true; - return; - } - - if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) + int flags = DMG_CLUB; + if (projectile.IsCrits) { - projectile.Touched = true; - return; + flags |= DMG_ACID; } - player.TakeDamage(_, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); - float ratio = projectile.TravelTime / 1.0; - if (ratio > 0.1) + SF2_BasePlayer player = SF2_BasePlayer(hitIndex); + if (player.IsValid) { - float time = 6.0 * ratio; - float stun = 0.5; - if (projectile.IsCrits) + if (player.IsInGhostMode) { - time += 2.0; + return; } - if (ratio >= 1.0) + if (player.IsEliminated && !projectile.AttackWaiters) { - time += 1.0; + projectile.Touched = true; + return; } - if (player.GetProp(Prop_Send, "m_nWaterLevel") < 3) + if (IsValidEntity(owner) && GetEntProp(owner, Prop_Data, "m_iTeamNum") == player.Team) { - player.Stun(time, stun, TF_STUNFLAG_SLOWDOWN, player.index); + projectile.Touched = true; + return; } + player.TakeDamage(_, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); + float ratio = projectile.TravelTime / 1.0; + if (ratio > 0.1) + { + float time = 6.0 * ratio; + float stun = 0.5; + if (projectile.IsCrits) + { + time += 2.0; + } + + if (ratio >= 1.0) + { + time += 1.0; + } + + if (player.GetProp(Prop_Send, "m_nWaterLevel") < 3) + { + player.Stun(time, stun, TF_STUNFLAG_SLOWDOWN, player.index); + } + } + Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); + Call_PushCell(player); + Call_PushCell(projectile); + Call_Finish(); + projectile.Touched = true; + } + else + { + SDKHooks_TakeDamage(hitIndex, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); } - Call_StartForward(g_OnPlayerDamagedByProjectilePFwd); - Call_PushCell(player); - Call_PushCell(projectile); - Call_Finish(); - projectile.Touched = true; - } - else - { - SDKHooks_TakeDamage(hitIndex, !IsValidEntity(owner) ? projectile.index : owner, !IsValidEntity(owner) ? projectile.index : owner, projectile.Damage, flags, _, _, pos); } } } diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp index 4d62eafa..65dcb688 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_grenade_projectile.sp @@ -326,16 +326,37 @@ static Action Timer_Think(Handle timer, any ref) Call_PushCell(projectile); Call_PushCell(CBaseEntity(hitIndex)); Call_Finish(); - } - if (hitIndex == 0) - { - projectile.Touched = true; - return Plugin_Continue; - } - else - { - projectile.DoExplosion(); + if (hitIndex == 0) + { + projectile.Touched = true; + return Plugin_Continue; + } + else + { + if (SF2_BasePlayer(hitIndex).IsValid) + { + if (!SF2_BasePlayer(hitIndex).IsEliminated || projectile.AttackWaiters) + { + projectile.DoExplosion(); + } + + if (g_Buildings.FindValue(EntIndexToEntRef(hitIndex)) != -1) + { + projectile.DoExplosion(); + } + + if (g_WhitelistedEntities.FindValue(EntIndexToEntRef(hitIndex)) != -1) + { + projectile.DoExplosion(); + } + + if (g_BreakableProps.FindValue(EntIndexToEntRef(hitIndex)) != -1) + { + projectile.DoExplosion(); + } + } + } } return Plugin_Continue; From 1a0ecdd6979c1950e3058354bdf134aff4740a8f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 22:55:12 -0700 Subject: [PATCH 49/86] Good thing I make changelogs --- addons/sourcemod/scripting/include/sf2.inc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 93709576..a5d9f029 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -3934,7 +3934,8 @@ public void __pl_sf2_SetNTVOptional() MarkNativeAsOptional("SF2_ChaserBossEntity.SetNextAttackTime"); MarkNativeAsOptional("SF2_ChaserBossEntity.DropItem"); MarkNativeAsOptional("SF2_ChaserBossEntity.CreateSoundHint"); - MarkNativeAsOptional("SF2_ChaserBossEntity.GroundSpeedOverride"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GroundSpeedOverride.get"); + MarkNativeAsOptional("SF2_ChaserBossEntity.GroundSpeedOverride.set"); // npc/entities/statue/entity.sp MarkNativeAsOptional("SF2_StatueBossEntity.IsValid.get"); From aee248adbe557c7a7b66f68f1cc291394adf1f1f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 23:00:36 -0700 Subject: [PATCH 50/86] Update entity.sp --- addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp index 4e5d6ffd..299c486d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/statue/entity.sp @@ -304,6 +304,7 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue, int &interruptConditio { SF2_BasePlayer client = SF2_BasePlayer(i); + #if defined DEBUG if (client.IsValid && g_PlayerDebugFlags[client.index] & DEBUG_BOSS_EYES) { for (int i2 = 1; i2 <= MaxClients; i2++) @@ -318,8 +319,8 @@ static CBaseEntity ProcessVision(SF2_StatueEntity statue, int &interruptConditio TE_SetupBeamPoints(traceStartPos, eyes, g_ShockwaveBeam, g_ShockwaveHalo, 0, 30, 0.1, 5.0, 5.0, 5, 0.0, color, 1); TE_SendToClient(client.index); } - } + #endif if (!IsTargetValidForSlender(statue, client, attackEliminated)) { From ab4c2e28ccc452ecc70e4310098091bfe0c5b6cd Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 23:16:17 -0700 Subject: [PATCH 51/86] Case sensitivity being renamed, my favorite! --- .../include/sf2/profiles/profiles_chaser.inc | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index ee961b14..8c301a9b 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -158,39 +158,39 @@ enum struct SF2ChaserBossProfileDamageEffectData // Can be any data bool SetType(const char[] section) { - if (strcmp(section, "ignite") == 0) + if (strcmp(section, "ignite", false) == 0) { this.Type = SF2DamageType_Ignite; } - else if (strcmp(section, "gas") == 0) + else if (strcmp(section, "gas", false) == 0) { this.Type = SF2DamageType_Gas; } - else if (strcmp(section, "bleed") == 0) + else if (strcmp(section, "bleed", false) == 0) { this.Type = SF2DamageType_Bleed; } - else if (strcmp(section, "mark") == 0) + else if (strcmp(section, "mark", false) == 0) { this.Type = SF2DamageType_Mark; } - else if (strcmp(section, "jarate") == 0) + else if (strcmp(section, "jarate", false) == 0) { this.Type = SF2DamageType_Jarate; } - else if (strcmp(section, "milk") == 0) + else if (strcmp(section, "milk", false) == 0) { this.Type = SF2DamageType_Milk; } - else if (strcmp(section, "stun") == 0) + else if (strcmp(section, "stun", false) == 0) { this.Type = SF2DamageType_Stun; } - else if (strcmp(section, "smite") == 0) + else if (strcmp(section, "smite", false) == 0) { this.Type = SF2DamageType_Smite; } - else if (strcmp(section, "random") == 0) + else if (strcmp(section, "random", false) == 0) { this.Types = new ArrayList(); this.Type = SF2DamageType_Random; @@ -245,35 +245,35 @@ enum struct SF2ChaserBossProfileDamageEffectData // Can be any data int nums = ExplodeString(type, " ", types, sizeof(types), sizeof(types[])); for (int i = 0; i < nums; i++) { - if (strcmp(types[i], "ignite") == 0) + if (strcmp(types[i], "ignite", false) == 0) { this.Types.Push(SF2DamageType_Ignite); } - else if (strcmp(types[i], "gas") == 0) + else if (strcmp(types[i], "gas", false) == 0) { this.Types.Push(SF2DamageType_Gas); } - else if (strcmp(types[i], "bleed") == 0) + else if (strcmp(types[i], "bleed", false) == 0) { this.Types.Push(SF2DamageType_Bleed); } - else if (strcmp(types[i], "mark") == 0) + else if (strcmp(types[i], "mark", false) == 0) { this.Types.Push(SF2DamageType_Mark); } - else if (strcmp(types[i], "jarate") == 0) + else if (strcmp(types[i], "jarate", false) == 0) { this.Types.Push(SF2DamageType_Jarate); } - else if (strcmp(types[i], "milk") == 0) + else if (strcmp(types[i], "milk", false) == 0) { this.Types.Push(SF2DamageType_Milk); } - else if (strcmp(types[i], "stun") == 0) + else if (strcmp(types[i], "stun", false) == 0) { this.Types.Push(SF2DamageType_Stun); } - else if (strcmp(types[i], "smite") == 0) + else if (strcmp(types[i], "smite", false) == 0) { this.Types.Push(SF2DamageType_Smite); } @@ -320,35 +320,35 @@ enum struct SF2ChaserBossProfileDamageEffectData // Can be any data for (int i = 0; i < nums; i++) { - if (strcmp(flags[i], "slow") == 0) + if (strcmp(flags[i], "slow", false) == 0) { value |= (1 << 0); } - else if (strcmp(flags[i], "stuck") == 0) + else if (strcmp(flags[i], "stuck", false) == 0) { value |= (1 << 1); } - else if (strcmp(flags[i], "cheer_fx") == 0) + else if (strcmp(flags[i], "cheer_fx", false) == 0) { value |= (1 << 3); } - else if (strcmp(flags[i], "no_fx") == 0) + else if (strcmp(flags[i], "no_fx", false) == 0) { value |= (1 << 5); } - else if (strcmp(flags[i], "thirdperson") == 0) + else if (strcmp(flags[i], "thirdperson", false) == 0) { value |= (1 << 6); } - else if (strcmp(flags[i], "ghost_fx") == 0) + else if (strcmp(flags[i], "ghost_fx", false) == 0) { value |= (1 << 7); } - else if (strcmp(flags[i], "loser") == 0) + else if (strcmp(flags[i], "loser", false) == 0) { value |= (1 << 0) | (1 << 5) | (1 << 6); } - else if (strcmp(flags[i], "boo") == 0) + else if (strcmp(flags[i], "boo", false) == 0) { value |= (1 << 7) | (1 << 6); } @@ -392,6 +392,7 @@ enum struct SF2ChaserBossProfileDamageEffectData // Can be any data { type = this.Types.Get(GetRandomInt(0, this.Types.Length - 1)); } + switch (type) { case SF2DamageType_Ignite: From 295134b4f01f073444cb2079da9c5981339e21c1 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 25 May 2024 23:38:24 -0700 Subject: [PATCH 52/86] I forgot the gamedata --- addons/sourcemod/gamedata/sf2.txt | 242 ++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) diff --git a/addons/sourcemod/gamedata/sf2.txt b/addons/sourcemod/gamedata/sf2.txt index 8e98818f..756c37f5 100644 --- a/addons/sourcemod/gamedata/sf2.txt +++ b/addons/sourcemod/gamedata/sf2.txt @@ -45,6 +45,65 @@ "linux" "208" "windows" "207" } + + "CTFWeaponBase::GetWeaponID" + { + "windows" "380" + "linux" "386" + } + + "CBaseEntity::IsBaseCombatWeapon" + { + "windows" "91" + "linux" "92" + } + + "CBaseEntity::ChangeTeam" + { + "library" "server" + "linux" "97" + "windows" "96" + } + + "CTeam::AddPlayer" + { + "linux" "209" + "windows" "208" + } + + "CTeam::RemovePlayer" + { + "linux" "210" + "windows" "209" + } + + "CBaseGrenade::Explode" + { + "library" "server" + "linux" "236" + "windows" "235" + } + + "CTFBaseRocket::Explode" + { + "library" "server" + "linux" "240" + "windows" "239" + } + + "CBaseEntity::VPhysicsUpdate" + { + "library" "server" + "linux" "164" + "windows" "163" + } + + "CTFSniperRifle::GetPenetrateType" + { + "library" "server" + "linux" "407" + "windows" "400" + } } "Signatures" @@ -53,14 +112,18 @@ { "library" "server" "linux" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" + "linux64" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" "windows" "\x55\x8B\xEC\x53\x56\x8B\x75\x08\x57\x56\x8B\xF9\xE8\x2A\x2A\x2A\x2A\x8B\xD8" + "windows64" "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x48\x8B\xFA\x48\x8B\xD9\xE8\x2A\x2A\x2A\x2A\x48\x8B\xCB\x83\xF8\xFF\x74\x2A" } "CBaseTrigger::PointIsWithin" { "library" "server" "linux" "@_ZN12CBaseTrigger13PointIsWithinERK6Vector" + "linux64" "@_ZN12CBaseTrigger13PointIsWithinERK6Vector" "windows" "\x53\x8B\xDC\x83\xEC\x08\x83\xE4\xF0\x83\xC4\x04\x55\x8B\x6B\x04\x89\x6C\x24\x04\x8B\xEC\x81\xEC\xAC\x00\x00\x00" + "windows64" "\x48\x81\xEC\xF8\x00\x00\x00\xF3\x0F\x10\x02" } // "CreateEntityByName( %s, %d ) - CreateEdict failed." @@ -68,21 +131,200 @@ { "library" "server" "linux" "@_Z18CreateEntityByNamePKci" + "linux64" "@_Z18CreateEntityByNamePKci" "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x0C\x83\xFE\xFF\x74\x24\x8B\x0D" + "windows64" "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x8B\xDA\x48\x8B\xF9\x83\xFA\xFF" } "CBaseAnimating::LookupBone" // int CBaseAnimating::LookupBone( const char *szName ) { "library" "server" "windows" "\x55\x8B\xEC\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\x83\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x8B\xCE\xE8\x2A\x2A\x2A\x2A\x8B\x86\x2A\x2A\x2A\x2A\x85\xC0\x74\x2A\x83\x38\x00\x74\x2A\xFF\x75\x2A\x50\xE8\x2A\x2A\x2A\x2A\x83\xC4\x08\x5E" + "windows64" "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x80\xB9\x65\x04\x00\x00\x00\x48\x8B\xFA\x48\x8B\xD9\x75\x2A\x48\x83\xB9\xC0\x05\x00\x00\x00\x75\x2A\xE8\x2A\x2A\x2A\x2A\x48\x85\xC0\x74\x2A\x48\x8B\xCB\xE8\x2A\x2A\x2A\x2A\x48\x8B\x8B\xC0\x05\x00\x00\x48\x85\xC9\x74\x2A\x48\x83\x39\x00\x74\x2A\x48\x8B\xD7\x48\x8B\x5C\x24\x30" "linux" "@_ZN14CBaseAnimating10LookupBoneEPKc" + "linux64" "@_ZN14CBaseAnimating10LookupBoneEPKc" } "CBaseAnimating::GetBonePosition" // void CBaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) { "library" "server" "windows" "\x55\x8B\xEC\x83\xEC\x30\x56\x8B\xF1\x80\xBE\x2A\x2A\x2A\x2A\x00\x75\x2A" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x6C\x24\x10\x48\x89\x74\x24\x18\x57\x48\x83\xEC\x50\x80\xB9\x65\x04\x00\x00\x00" "linux" "@_ZN14CBaseAnimating15GetBonePositionEiR6VectorR6QAngle" + "linux64" "@_ZN14CBaseAnimating15GetBonePositionEiR6VectorR6QAngle" + } + + "Studio_SeqVelocity" + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xEC\x20\x53\x56\x8B\x75\x0C\x57\x8B\x7D\x08" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x81\xEC\x80\x00\x00\x00\x0F\x29\x74\x24\x70\x49\x8B\xF9" + "linux" "@_Z18Studio_SeqVelocityPK10CStudioHdrifPKfR6Vector" + "linux64" "@_Z18Studio_SeqVelocityPK10CStudioHdrifPKfR6Vector" + } + + "CBaseEntity::PhysicsDispatchThink" + { + "library" "server" + "linux" "@_ZN11CBaseEntity20PhysicsDispatchThinkEMS_FvvE" + "linux64" "@_ZN11CBaseEntity20PhysicsDispatchThinkEMS_FvvE" + "windows" "\x55\x8B\xEC\xA1\x2A\x2A\x2A\x2A\x83\xEC\x08\x83\x78\x30\x00\x53" + "windows64" "\x48\x89\x74\x24\x18\x57\x48\x83\xEC\x60\x48\x8B\x05\x87\xD6\xA8\x00" + } + + "CBaseEntity::GetNextThink" + { + "library" "server" + "linux" "@_ZN11CBaseEntity12GetNextThinkEPKc" + "linux64" "@_ZN11CBaseEntity12GetNextThinkEPKc" + "windows" "\x55\x8B\xEC\x8B\x45\x08\x56\x8B\xF1\x85\xC0\x75\x2A\x8B\x86\xD0\x00\x00\x00\x89\x45\x08" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x83\xEC\x30\x0F\x29\x74\x24\x20\x0F\x28\xF2\x49\x8B\xF1" + } + + "GetGlobalTeam" + { + "library" "server" + "linux" "@_Z13GetGlobalTeami" + "linux64" "@_Z13GetGlobalTeami" + "windows" "\x55\x8B\xEC\x8B\x4D\x08\x85\xC9\x78\x2A\x3B\x0D\x2A\x2A\x2A\x2A\x7D\x2A\xA1\x2A\x2A\x2A\x2A\x8B\x04\x88\x5D\xC3" + "windows64" "\x85\xC9\x78\x2A\x3B\x0D\xBE\x0D\xA4\x00" + } + + "CTFTeam::AddObject" + { + "library" "server" + "linux" "@_ZN7CTFTeam9AddObjectEP11CBaseObject" + "linux64" "@_ZN7CTFTeam9AddObjectEP11CBaseObject" + "windows" "\x80\xB9\x72\x0C\x00\x00\x00\x74\x2A\x80\xB9\xD1\x03\x00\x00\x00" + "windows64" "\x48\x89\x5C\x24\x08\x48\x89\x6C\x24\x18\x48\x89\x74\x24\x20\x57\x48\x83\xEC\x50" + } + + "CTFTeam::RemoveObject" + { + "library" "server" + "linux" "@_ZN7CTFTeam12RemoveObjectEP11CBaseObject" + "linux64" "@_ZN7CTFTeam12RemoveObjectEP11CBaseObject" + "windows" "\x55\x8B\xEC\x56\x8B\xF1\x83\xBE\xF0\x03\x00\x00\x00" + "windows64" "\x40\x53\x56\x48\x83\xEC\x28\x83\xB9\x20\x05\x00\x00\x00" + } + + "CWeaponMedigun::AllowedToHealTarget" + { + "library" "server" + "linux" "@_ZN14CWeaponMedigun19AllowedToHealTargetEP11CBaseEntity" + "linux64" "@_ZN14CWeaponMedigun19AllowedToHealTargetEP11CBaseEntity" + "windows" "\x55\x8B\xEC\x53\x8B\xD9\x56\x57\x8B\x93\x10\x02\x00\x00\x85\xD2\x0F\x84\x2A\x2A\x2A\x2A\xB9\xFF\x1F\x00\x00\x83\xFA\xFF\x74\x2A\x0F\xB7\xCA\xA1\x2A\x2A\x2A\x2A\xC1\xE1\x04\x8D\x78\x04\x03\xF9\x0F\x84\x2A\x2A\x2A\x2A\xC1\xEA\x10\x39\x57\x04\x0F\x85\x2A\x2A\x2A\x2A\x8B\x3F\x85\xFF\x0F\x84\x2A\x2A\x2A\x2A\x8B\x07\x8B\xCF\x8B\x80\x48\x01\x00\x00\xFF\xD0\x84\xC0\x0F\x84\x2A\x2A\x2A\x2A\x8B\x75\x08\x85\xF6\x74\x2A" + "windows64" "\x48\x89\x5C\x24\x10\x48\x89\x6C\x24\x18\x48\x89\x74\x24\x20\x57\x48\x83\xEC\x30\x48\x8B\xDA" + } + } + + "Functions" + { + "CTFFlameThrower::DeflectPlayer" + { + "signature" "CTFFlameThrower::DeflectPlayer" + "callconv" "thiscall" + "hooktype" "entity" + "return" "bool" + "this" "entity" + "arguments" + { + "target" + { + "type" "cbaseentity" + } + "owner" + { + "type" "cbaseentity" + } + "pos" + { + "type" "vectorptr" + } + } + } + + "CBaseEntity::PhysicsDispatchThink" + { + "signature" "CBaseEntity::PhysicsDispatchThink" + "callconv" "thiscall" + "return" "void" + "this" "entity" + "arguments" + { + "thinkFunc" + { + "type" "int" + } + } + } + + "CBaseGrenade::Explode" + { + "offset" "CBaseGrenade::Explode" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pTrace" + { + "type" "int" + } + "bitsDamageType" + { + "type" "int" + } + } + } + + "CTFBaseRocket::Explode" + { + "offset" "CTFBaseRocket::Explode" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pTrace" + { + "type" "int" + } + "pOther" + { + "type" "cbaseentity" + } + } + } + + "CWeaponMedigun::AllowedToHealTarget" + { + "signature" "CWeaponMedigun::AllowedToHealTarget" + "callconv" "thiscall" + "return" "bool" + "this" "entity" + "arguments" + { + "pTarget" + { + "type" "cbaseentity" + } + } + } + + "CBaseEntity::VPhysicsUpdate" + { + "offset" "CBaseEntity::VPhysicsUpdate" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pPhysics" + { + "type" "objectptr" + } + } } } } From e2ead7ae20013d656c49f70bb1905e951c2b1733 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sun, 26 May 2024 10:51:35 -0700 Subject: [PATCH 53/86] Update sf2.sp --- addons/sourcemod/scripting/sf2.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index ca8fd954..7671d995 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -5084,7 +5084,7 @@ void SetPageCount(int num) SF2BossProfileData data; data = NPCGetProfileData(npcIndex); - if (data.SlaughterRunData.SpawnTime[difficulty] > -1.0) + if (data.SlaughterRunData.SpawnTime[difficulty] > 0.0) { times[bosses] = data.SlaughterRunData.SpawnTime[difficulty]; bosses++; From da2892b43b4c30aa571c8422a4ed52920542aa81 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sun, 26 May 2024 10:58:41 -0700 Subject: [PATCH 54/86] Update profiles.inc --- addons/sourcemod/scripting/include/sf2/profiles/profiles.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 3f1aef03..7a09cc3a 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -2633,6 +2633,8 @@ enum struct SF2BossProfileData this.EyeData.Init(); + this.SlaughterRunData.Init(); + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { this.Skin[difficulty] = 0; @@ -3142,4 +3144,4 @@ enum struct SF2BossProfileData } #include -#include \ No newline at end of file +#include From e2a4086a7d9d8c87873eb0112832d5d119a8ac2c Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sun, 26 May 2024 22:34:42 -0700 Subject: [PATCH 55/86] Update profiles.sp --- addons/sourcemod/scripting/sf2/profiles.sp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index fc50725d..9b6ffddf 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -817,7 +817,6 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss else { LogSF2Message("(ALWAYS LOAD) %s...", profileName, filePath); - count++; } } @@ -1609,4 +1608,4 @@ static any Native_GetBossAttributeValue(Handle plugin,int numParams) return 0.0; } return NPCGetAttributeValue(GetNativeCell(1), attributeIndex); -} \ No newline at end of file +} From 37c28cbf9422b44a8f8e57ad8645031f4e13021d Mon Sep 17 00:00:00 2001 From: JuegosPablo <40556796+JuegosPablo@users.noreply.github.com> Date: Thu, 25 Jul 2024 04:23:00 -0400 Subject: [PATCH 56/86] Deathcam Bug (#137) * Update playsequenceandwait.sp Small Fix to Deathcam Bug * Update playsequenceandwait.sp Forget Something --- .../sf2/npc/entities/base/actions/playsequenceandwait.sp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp index cdb283d3..5d6b5e6a 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp @@ -119,12 +119,10 @@ static int OnStart(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAc duration = actor.SequenceDuration(action.Sequence) / action.Rate; duration *= (1.0 - action.Cycle); } - if (SF2_ChaserEntity(actor.index).IsValid()) { SF2_ChaserEntity(actor.index).GroundSpeedOverride = true; } - action.EndTime = GetGameTime() + duration; return action.Continue(); @@ -146,6 +144,10 @@ static int Update(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, float inte static int OnSuspend(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAction interruptingAction) { + if (SF2_ChaserEntity(actor.index).IsValid()) + { + SF2_ChaserEntity(actor.index).GroundSpeedOverride = false; + } return action.Done(); } @@ -157,4 +159,4 @@ static any Native_Create(Handle plugin, int numParams) float cycle = GetNativeCell(4); return SF2_PlaySequenceAndWait(sequence, duration, rate, cycle); -} \ No newline at end of file +} From 16c8721d02fc5a30342caa1f980d9d08a9ffd4b4 Mon Sep 17 00:00:00 2001 From: NNinja1255 <50771176+NNinja1255@users.noreply.github.com> Date: Sat, 21 Dec 2024 16:59:27 -0500 Subject: [PATCH 57/86] Reimplement static_shake (#141) --- addons/sourcemod/scripting/include/sf2.inc | 1 + .../sourcemod/scripting/sf2/client/static.sp | 32 +++++++++++++++++-- .../sf2/profiles/profiles_boss_functions.sp | 4 +++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index a5d9f029..64621c15 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -355,6 +355,7 @@ enum RenevantWave #define SFF_ATTACKPROPS (1 << 14) #define SFF_WEAPONKILLS (1 << 15) #define SFF_WEAPONKILLSONRADIUS (1 << 16) +#define SFF_HASSTATICSHAKE (1 << 17) // Interrup conditions. #define COND_HEARDSUSPICIOUSSOUND (1 << 0) diff --git a/addons/sourcemod/scripting/sf2/client/static.sp b/addons/sourcemod/scripting/sf2/client/static.sp index 9459506b..b6b89084 100644 --- a/addons/sourcemod/scripting/sf2/client/static.sp +++ b/addons/sourcemod/scripting/sf2/client/static.sp @@ -153,6 +153,34 @@ void ClientProcessStaticShake(int client) newPunchAng[i] = oldPunchAng[i]; newPunchAngVel[i] = oldPunchAngVel[i]; } + + int staticMaster = NPCGetFromUniqueID(g_PlayerStaticMaster[client]); + if (staticMaster != -1 && NPCGetFlags(staticMaster) & SFF_HASSTATICSHAKE) + { + if (g_PlayerStaticMode[client][staticMaster] == Static_Increase) + { + newStaticShakeMaster = staticMaster; + } + } + for (int i = 0; i < MAX_BOSSES; i++) + { + if (NPCGetUniqueID(i) == -1) + { + continue; + } + + + if (NPCGetFlags(i) & SFF_HASSTATICSHAKE) + { + int master = NPCGetFromUniqueID(g_SlenderCopyMaster[i]); + if (master == -1) + { + master = i; + } + + + } + } if (newStaticShakeMaster != -1) { @@ -267,9 +295,9 @@ void ClientProcessStaticShake(int client) NormalizeVector(newPunchAng, newPunchAng); float angVelocityScalar = 5.0 * g_PlayerStaticAmount[client]; - if (angVelocityScalar < 1.0) + if (angVelocityScalar < 0.0) { - angVelocityScalar = 1.0; + angVelocityScalar = 0.0; } ScaleVector(newPunchAng, angVelocityScalar); diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index fe9c2177..01261cc4 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -567,6 +567,10 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { bossFlags |= SFF_WEAPONKILLSONRADIUS; } + if (kv.GetNum("static_shake"), 0) + { + bossFlags |= SFF_HASSTATICSHAKE; + } profileData.Flags = bossFlags; profileData.CopiesInfo.Load(kv); From 8bfcc5466fd8cd68ec20432e4a28296577b33555 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 21 Dec 2024 15:25:14 -0700 Subject: [PATCH 58/86] Alpha 2.73 --- addons/sourcemod/scripting/sf2.sp | 3 - .../scripting/sf2/client/new_music.sp | 1622 +++++++++ .../sourcemod/scripting/sf2/client/static.sp | 5 +- .../sourcemod/scripting/sf2/client/weapons.sp | 818 +++++ .../sourcemod/scripting/sf2/extras/natives.sp | 17 - .../base/actions/playsequenceandwait.sp | 13 +- addons/sourcemod/scripting/sf2/old_pvp.sp | 1230 +++++++ addons/sourcemod/scripting/sf2/profiles.sp | 10 +- .../scripting/sf2/profiles/keymap.sp | 901 +++++ .../scripting/sf2/profiles/objects.sp | 2964 +++++++++++++++++ addons/sourcemod/scripting/sf2/stocks.sp | 65 - 11 files changed, 7549 insertions(+), 99 deletions(-) create mode 100644 addons/sourcemod/scripting/sf2/client/new_music.sp create mode 100644 addons/sourcemod/scripting/sf2/client/weapons.sp create mode 100644 addons/sourcemod/scripting/sf2/old_pvp.sp create mode 100644 addons/sourcemod/scripting/sf2/profiles/keymap.sp create mode 100644 addons/sourcemod/scripting/sf2/profiles/objects.sp diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 7671d995..fe27e1cb 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -694,8 +694,6 @@ Handle g_SDKSequenceVelocity; Handle g_SDKStartTouch; Handle g_SDKEndTouch; Handle g_SDKWeaponSwitch; -Handle g_SDKGetWeaponID; -Handle g_SDKIsWeapon; DynamicHook g_DHookWantsLagCompensationOnEntity; DynamicHook g_DHookShouldTransmit; @@ -3645,7 +3643,6 @@ public void OnClientPutInServer(int client) SDKHook(client, SDKHook_PreThink, Hook_ClientPreThink); SDKHook(client, SDKHook_PreThinkPost, Hook_OnFlashlightThink); SDKHook(client, SDKHook_SetTransmit, Hook_ClientSetTransmit); - SDKHook(client, SDKHook_TraceAttack, Hook_PvPPlayerTraceAttack); SDKHook(client, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); g_DHookWantsLagCompensationOnEntity.HookEntity(Hook_Pre, client, Hook_ClientWantsLagCompensationOnEntity); diff --git a/addons/sourcemod/scripting/sf2/client/new_music.sp b/addons/sourcemod/scripting/sf2/client/new_music.sp new file mode 100644 index 00000000..1bc7e0a9 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/new_music.sp @@ -0,0 +1,1622 @@ +#pragma newdecls required +#pragma semicolon 1 + +enum SF2MusicFadeState +{ + SF2MusicFadeState_None = 0, + SF2MusicFadeState_FadeIn = 1, + SF2MusicFadeState_FadeOut = 2 +} + +enum SF2BossMusicState +{ + SF2BossMusicState_Invalid = -1, + SF2BossMusicState_Idle = 0, + SF2BossMusicState_Alert = 1, + SF2BossMusicState_Chase = 2, + SF2BossMusicState_Max = 3 +} + +enum SF2BossMusicTrackType +{ + SF2BossMusicTrackType_Unknown = 0, + SF2BossMusicTrackType_Normal, + SF2BossMusicTrackType_Visible, + SF2BossMusicTrackType_Global, + SF2BossMusicTrackType_Max +} + +enum SF2BossGlobalMusicShuffleType +{ + SF2BossGlobalMusicShuffleType_None = 0, + SF2BossGlobalMusicShuffleType_Order, + SF2BossGlobalMusicShuffleType_Random, + SF2BossGlobalMusicShuffleType_RandomOrder, + SF2BossGlobalMusicShuffleType_Range +} + +methodmap ProfileGlobalTracks < ProfileObject +{ + public SF2BossMusicState GetPriority(int difficulty = Difficulty_Normal) + { + return view_as(this.GetDifficultyInt("priority", difficulty, view_as(SF2BossMusicState_Max))); + } + + public SF2BossGlobalMusicShuffleType GetShuffleType(int difficulty) + { + char value[64]; + this.GetDifficultyString("shuffle_type", difficulty, value, sizeof(value), "none"); + + if (strcmp(value, "order", false) == 0) + { + return SF2BossGlobalMusicShuffleType_Order; + } + else if (strcmp(value, "random", false) == 0) + { + return SF2BossGlobalMusicShuffleType_Random; + } + else if (strcmp(value, "random_order", false) == 0) + { + return SF2BossGlobalMusicShuffleType_RandomOrder; + } + else if (strcmp(value, "pages", false) == 0) + { + return SF2BossGlobalMusicShuffleType_Range; + } + return SF2BossGlobalMusicShuffleType_None; + } + + public KeyMap_Array GetTracks() + { + return this.GetArray("tracks"); + } + + public void Precache() + { + this.ConvertSectionsSectionToArray("tracks"); + if (this.GetTracks() != null) + { + for (int i = 0; i < this.GetTracks().Length; i++) + { + ProfileGlobalTrack track = view_as(this.GetTracks().GetSection(i)); + if (track == null) + { + continue; + } + track.Precache(); + } + } + } +} + +methodmap ProfileGlobalTrack < ProfileObject +{ + public bool IsEnabled(int difficulty) + { + if (this == null) + { + return false; + } + return this.GetDifficultyBool("enabled", difficulty, true); + } + + public void GetPath(int difficulty, char[] buffer, int bufferSize) + { + this.GetDifficultyString("path", difficulty, buffer, bufferSize); + } + + public float GetVolume(int difficulty) + { + return this.GetDifficultyFloat("volume", difficulty, SNDVOL_NORMAL); + } + + public int GetPitch(int difficulty) + { + return this.GetDifficultyInt("pitch", difficulty, SNDPITCH_NORMAL); + } + + public float GetLength(int difficulty) + { + return this.GetDifficultyFloat("length", difficulty, -1.0); + } + + public float GetMinPageRange(int difficulty) + { + return this.GetDifficultyFloat("min_range", difficulty, 0.0); + } + + public float GetMaxPageRange(int difficulty) + { + return this.GetDifficultyFloat("max_range", difficulty, 0.0); + } + + public void Precache() + { + char path[PLATFORM_MAX_PATH]; + for (int i = 0; i < Difficulty_Max; i++) + { + this.GetPath(i, path, sizeof(path)); + if (path[0] != '\0') + { + PrecacheSound2(path, g_FileCheckConVar.BoolValue); + } + } + } +} + +methodmap ProfileMusic < ProfileSound +{ + public float GetRadius(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("radius", difficulty, 850.0); + } + + public float GetFadeSpeed(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("fade_speed", difficulty, 1.0); + } + + public bool CanInstantFade(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyBool("instant_fade", difficulty, false); + } + + public SF2BossMusicState GetGlobalMusicPriority(int difficulty = Difficulty_Normal) + { + return view_as(this.GetDifficultyInt("priority", difficulty, view_as(SF2BossMusicState_Max))); + } +} + +enum struct MusicState +{ + bool IsNull; + char MusicPath[PLATFORM_MAX_PATH]; + float MaxVolume; + float CurrentVolume; + int Pitch; + float FadeSpeed; + SF2MusicFadeState FadeState; + float Length; + bool StopAfterFade; + + void Init() + { + this.IsNull = false; + this.MusicPath[0] = '\0'; + this.MaxVolume = 1.0; + this.CurrentVolume = 0.0; + this.Pitch = 100; + this.FadeSpeed = 1.0; + this.FadeState = SF2MusicFadeState_None; + this.Length = -1.0; + this.StopAfterFade = false; + } + + void FadeIn(SF2_BasePlayer client, float delta, bool instant = false) + { + if (this.IsNull) + { + return; + } + + if (this.MusicPath[0] == '\0') + { + return; + } + + if (this.CurrentVolume >= this.MaxVolume) + { + this.SetFadeState(SF2MusicFadeState_None); + return; + } + + this.CurrentVolume += this.FadeSpeed * delta; + if (instant) + { + this.CurrentVolume = this.MaxVolume; + } + if (this.CurrentVolume >= this.MaxVolume) + { + this.CurrentVolume = this.MaxVolume; + this.SetFadeState(SF2MusicFadeState_None); + } + float volume = this.CurrentVolume * g_PlayerPreferences[client.index].PlayerPreference_MusicVolume; + EmitSoundToClient(client.index, this.MusicPath, _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, volume, this.Pitch); + } + + void FadeOut(SF2_BasePlayer client, float delta, bool instant = false) + { + if (this.IsNull) + { + return; + } + + if (this.MusicPath[0] == '\0') + { + return; + } + + this.CurrentVolume -= this.FadeSpeed * delta; + if (instant) + { + this.CurrentVolume = 0.0; + } + if (this.CurrentVolume <= 0.0) + { + this.CurrentVolume = 0.0; + this.SetFadeState(SF2MusicFadeState_None); + if (this.StopAfterFade) + { + EmitSoundToClient(client.index, this.MusicPath, _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, 0.015, this.Pitch); + this.Stop(client); + return; + } + } + float volume = this.CurrentVolume * g_PlayerPreferences[client.index].PlayerPreference_MusicVolume; + EmitSoundToClient(client.index, this.MusicPath, _, MUSIC_CHAN, SNDLEVEL_NONE, SND_CHANGEVOL, volume, this.Pitch); + } + + void Stop(SF2_BasePlayer client) + { + if (this.IsNull) + { + return; + } + + if (this.MusicPath[0] == '\0') + { + return; + } + + StopSound(client.index, MUSIC_CHAN, this.MusicPath); + this.SetFadeState(SF2MusicFadeState_None); + } + + void Reset() + { + if (this.MusicPath[0] != '\0') + { + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + continue; + } + + if (!client.IsEliminated && client.HasEscaped) + { + continue; + } + StopSound(client.index, MUSIC_CHAN, this.MusicPath); + } + } + this.Init(); + } + + void SetFadeState(SF2MusicFadeState state) + { + if (this.IsNull) + { + return; + } + + this.FadeState = state; + } + + bool IsValid() + { + return this.MusicPath[0] != '\0'; + } +} + +enum struct PageMusicInfo +{ + int MinRequiredPages; + int MaxRequiredPages; + MusicState State; +} + +static ArrayList g_PageMusicRanges = null; +static ArrayList g_BossesWithGlobalMusic = null; +static bool g_CanSwapBackToDefault[MAXTF2PLAYERS] = { true, ... }; +static float g_CurrentTrackLength[MAXTF2PLAYERS]; +static StringMap g_QueuedThemes[MAXTF2PLAYERS]; // Fade out queue +static StringMap g_LayeredThemes[MAXTF2PLAYERS]; // Layered tracks +static MusicState g_CurrentTheme[MAXTF2PLAYERS]; +static MusicState g_DefaultMusicState; // Pages +static MusicState g_OldDefaultMusicState; +static MusicState g_GlobalMusicState[MAXTF2PLAYERS]; // Boss global theme +static MusicState g_OldGlobalMusicState[MAXTF2PLAYERS]; +static MusicState g_NullMusicState; +static float g_LastMusicThink; +static int g_PlayerActiveBoss[MAXTF2PLAYERS] = { -1, ... }; +static int g_PlayerActiveLayerBoss[MAXTF2PLAYERS] = { -1, ... }; +static SF2BossMusicState g_PlayerActiveBossState[MAXTF2PLAYERS] = { SF2BossMusicState_Invalid, ... }; +static SF2BossMusicState g_PlayerActiveLayerBossState[MAXTF2PLAYERS] = { SF2BossMusicState_Invalid, ... }; +static SF2BossMusicState g_GlobalMusicUpdateLimit[MAXTF2PLAYERS] = { SF2BossMusicState_Max, ... }; +static int g_GlobalMusicTrackMasterID = -1; +static ArrayList g_CurrentTracks; +static int g_CurrentTrackIndex; +static SF2BossGlobalMusicShuffleType g_CurrentTrackShuffleType; + +void SetupNewMusic() +{ + g_OnGamemodeEndPFwd.AddFunction(null, OnGamemodeEnd); + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnConfigsExecutedPFwd.AddFunction(null, ConfigsExecuted); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerEscapePFwd.AddFunction(null, OnPlayerEscape); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPostInitMapEntitiesPFwd.AddFunction(null, OnPostInitMapEntities); + g_OnPostInitNewGamePFwd.AddFunction(null, OnPostInitNewGame); + g_OnPageCountChangedPFwd.AddFunction(null, OnPageCountChanged); + g_OnBossAddedPFwd.AddFunction(null, OnBossAdded); + g_OnBossRemovedPFwd.AddFunction(null, OnBossRemoved); + g_OnDifficultyChangePFwd.AddFunction(null, OnDifficultyChange); + + g_PageMusicRanges = new ArrayList(sizeof(PageMusicInfo)); + g_BossesWithGlobalMusic = new ArrayList(); + g_NullMusicState.Init(); + g_NullMusicState.IsNull = true; +} + +static void OnGamemodeEnd() +{ + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + ClearMusic(client); + } +} + +static void MapStart() +{ + g_LastMusicThink = -1.0; + RequestFrame(MusicThink); + g_BossesWithGlobalMusic.Clear(); +} + +static void ConfigsExecuted() +{ + g_GlobalMusicTrackMasterID = -1; +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + ClearMusic(client); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + ClearMusic(client); + } +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + g_QueuedThemes[client.index] = null; + g_LayeredThemes[client.index] = null; + ClearMusic(client); +} + +static void OnPlayerEscape(SF2_BasePlayer client) +{ + ClearMusic(client); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + if (g_QueuedThemes[client.index] != null) + { + delete g_QueuedThemes[client.index]; + } + + if (g_LayeredThemes[client.index] != null) + { + delete g_LayeredThemes[client.index]; + } +} + +static void OnPostInitMapEntities() +{ + GetPageMusicRanges(); +} + +static void OnPostInitNewGame() +{ + UpdatePageMusic(); +} + +static void OnPageCountChanged(int pageCount, int oldPageCount) +{ + UpdatePageMusic(); +} + +static void OnBossAdded(SF2NPC_BaseNPC npc) +{ + if (SF_IsRaidMap() || SF_IsRenevantMap() || SF_IsProxyMap()) + { + return; + } + + if (npc.GetProfileDataEx().IsPvEBoss) + { + return; + } + + if (npc.IsCopy) + { + return; + } + + int oldId = g_GlobalMusicTrackMasterID; + char path[PLATFORM_MAX_PATH]; + GetBossMusicTrack(npc, SF2BossMusicTrackType_Global, path, sizeof(path)); + + if (path[0] != '\0') + { + g_BossesWithGlobalMusic.Push(npc.UniqueID); + if (NPCGetFromUniqueID(oldId) == -1) + { + UpdateBossGlobalTracks(); + } + } +} + +static void OnBossRemoved(SF2NPC_BaseNPC npc) +{ + if (SF_IsRaidMap() || SF_IsRenevantMap() || SF_IsProxyMap()) + { + return; + } + + if (npc.GetProfileDataEx().IsPvEBoss) + { + return; + } + + if (npc.IsCopy) + { + return; + } + + int index; + if ((index = g_BossesWithGlobalMusic.FindValue(npc.UniqueID)) != -1) + { + g_BossesWithGlobalMusic.Erase(index); + } + + if (g_GlobalMusicTrackMasterID == npc.UniqueID) + { + UpdateBossGlobalTracks(); + } +} + +static void OnDifficultyChange(int oldDifficulty, int newDifficulty) +{ + if (SF_IsRaidMap() || SF_IsRenevantMap() || SF_IsProxyMap()) + { + return; + } + + g_BossesWithGlobalMusic.Clear(); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + if (npc.GetProfileDataEx().IsPvEBoss) + { + continue; + } + + char path[PLATFORM_MAX_PATH]; + GetBossMusicTrack(npc, SF2BossMusicTrackType_Global, path, sizeof(path)); + + if (path[0] != '\0') + { + g_BossesWithGlobalMusic.Push(npc.UniqueID); + UpdateBossGlobalTracks(); + } + } +} + +static void GetPageMusicRanges() +{ + g_PageMusicRanges.Clear(); + + char name[64], path[PLATFORM_MAX_PATH]; + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "ambient_generic")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); + + if (name[0] != '\0' && !StrContains(name, "sf2_page_music_", false)) + { + ReplaceString(name, sizeof(name), "sf2_page_music_", "", false); + + char pageRanges[2][32]; + ExplodeString(name, "-", pageRanges, 2, 32); + + PageMusicInfo musicInfo; + GetEntPropString(ent, Prop_Data, "m_iszSound", path, sizeof(path)); + + if (path[0] == '\0') + { + continue; + } + + if (path[0] != '#' && path[1] != '#') + { + Format(path, sizeof(path), "#%s", path); + } + + PrecacheSound(path, true); + + musicInfo.MinRequiredPages = StringToInt(pageRanges[0]); + musicInfo.MaxRequiredPages = StringToInt(pageRanges[1]); + musicInfo.State.MusicPath = path; + + g_PageMusicRanges.PushArray(musicInfo); + } + } + + /*while ((ent = FindEntityByClassname(ent, "sf2_info_page_music")) != -1) + { + SF2PageMusicEntity pageMusic = SF2PageMusicEntity(ent); + if (!pageMusic.IsValid()) + { + continue; + } + + pageMusic.InsertRanges(g_PageMusicRanges); + } + + // precache + if (g_PageMusicRanges.Length > 0) + { + char path[PLATFORM_MAX_PATH]; + + for (int i = 0; i < g_PageMusicRanges.Length; i++) + { + ent = EntRefToEntIndex(g_PageMusicRanges.Get(i)); + if (!ent || ent == INVALID_ENT_REFERENCE) + { + continue; + } + + SF2PageMusicEntity pageMusic = SF2PageMusicEntity(ent); + if (pageMusic.IsValid()) + { + // Don't do anything; entity already precached its own music. + } + else + { + GetEntPropString(ent, Prop_Data, "m_iszSound", path, sizeof(path)); + if (path[0] != '\0') + { + PrecacheSound(path); + } + } + } + }*/ + + if (g_PageMusicRanges.Length == 0 && g_PageMax > 0) + { + int page25Pct = RoundToFloor(float(g_PageMax) * 0.25); + int page50Pct = RoundToFloor(float(g_PageMax) * 0.5); + int page75Pct = RoundToFloor(float(g_PageMax) * 0.75); + + PageMusicInfo musicInfo; + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = 1; + musicInfo.MaxRequiredPages = page25Pct; + strcopy(path, sizeof(path), MUSIC_GOTPAGES1_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = page25Pct + 1; + musicInfo.MaxRequiredPages = page50Pct; + strcopy(path, sizeof(path), MUSIC_GOTPAGES2_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = page50Pct + 1; + musicInfo.MaxRequiredPages = page75Pct; + strcopy(path, sizeof(path), MUSIC_GOTPAGES3_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + + musicInfo.State.Init(); + musicInfo.MinRequiredPages = page75Pct + 1; + musicInfo.MaxRequiredPages = g_PageMax; + strcopy(path, sizeof(path), MUSIC_GOTPAGES4_SOUND); + PrecacheSound2(path, false); + musicInfo.State.MusicPath = path; + g_PageMusicRanges.PushArray(musicInfo); + } + + LogSF2Message("Loaded page music ranges successfully!"); +} + +static void ClearMusic(SF2_BasePlayer client) +{ + g_CanSwapBackToDefault[client.index] = true; + g_CurrentTrackLength[client.index] = 0.0; + g_PlayerActiveBoss[client.index] = -1; + g_PlayerActiveLayerBoss[client.index] = -1; + g_PlayerActiveBossState[client.index] = SF2BossMusicState_Invalid; + g_PlayerActiveLayerBossState[client.index] = SF2BossMusicState_Invalid; + + if (g_QueuedThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_QueuedThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState state; + g_QueuedThemes[client.index].GetArray(key, state, sizeof(state)); + state.Stop(client); + } + delete g_QueuedThemes[client.index]; + delete snapshot; + } + + if (g_LayeredThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState state; + g_LayeredThemes[client.index].GetArray(key, state, sizeof(state)); + state.Stop(client); + } + delete g_LayeredThemes[client.index]; + delete snapshot; + } + + g_CurrentTheme[client.index].Stop(client); + g_CurrentTheme[client.index] = g_NullMusicState; +} + +static void UpdatePageMusic() +{ + if (g_BossesWithGlobalMusic.Length != 0) + { + return; + } + + MusicState oldState; + oldState = g_DefaultMusicState; + g_OldDefaultMusicState = g_DefaultMusicState; + g_DefaultMusicState = g_NullMusicState; + + if (g_PageCount < g_PageMax || !g_RoundStopPageMusicOnEscape) + { + PageMusicInfo info; + + for (int i = 0; i < g_PageMusicRanges.Length; i++) + { + g_PageMusicRanges.GetArray(i, info); + if (g_PageCount >= info.MinRequiredPages && g_PageCount <= info.MaxRequiredPages) + { + g_DefaultMusicState = info.State; + if (strcmp(oldState.MusicPath, info.State.MusicPath, false) != 0) + { + for (int i2 = 1; i2 <= MaxClients; i2++) + { + SF2_BasePlayer client = SF2_BasePlayer(i2); + if (g_CanSwapBackToDefault[client.index]) + { + AddMusicToQueue(client, g_DefaultMusicState); + } + } + } + break; + } + } + } +} + +static void UpdateBossGlobalTracks() +{ + if (g_BossesWithGlobalMusic.Length == 0) + { + UpdatePageMusic(); + if (g_CurrentTracks != null) + { + delete g_CurrentTracks; + } + g_CurrentTrackIndex = 0; + g_GlobalMusicTrackMasterID = -1; + g_CurrentTrackShuffleType = SF2BossGlobalMusicShuffleType_None; + return; + } + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + UpdateBossGlobalTrackForPlayer(client); + } + + UpdatePageMusic(); +} + +static void UpdateBossGlobalTrackForPlayer(SF2_BasePlayer client) +{ + if (!client.IsValid) + { + return; + } + g_OldGlobalMusicState[client.index] = g_GlobalMusicState[client.index]; + g_OldGlobalMusicState[client.index].StopAfterFade = true; + + for (int i = 0; i < g_BossesWithGlobalMusic.Length; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(NPCGetFromUniqueID(g_BossesWithGlobalMusic.Get(i))); + if (!npc.IsValid()) + { + g_BossesWithGlobalMusic.Erase(i); + i--; + continue; + } + + char path[PLATFORM_MAX_PATH]; + float volume = 1.0, length = -1.0; + int pitch = 100; + SF2BossMusicState priority = SF2BossMusicState_Max; + GetBossMusicTrack(npc, SF2BossMusicTrackType_Global, path, sizeof(path), volume, pitch, .priority = priority, .length = length, .client = client); + + if (path[0] != '\0') + { + MusicState state; + state.Init(); + state.MusicPath = path; + state.MaxVolume = volume; + state.Pitch = pitch; + state.Length = length; + g_GlobalMusicState[client.index] = state; + g_GlobalMusicUpdateLimit[client.index] = priority; + g_CurrentTrackLength[client.index] = 0.0; + g_CurrentTheme[client.index].StopAfterFade = true; + + AddMusicToQueue(client, g_GlobalMusicState[client.index]); + g_OldGlobalMusicState[client.index].Init(); + g_OldGlobalMusicState[client.index].IsNull = true; + return; + } + } + + g_GlobalMusicState[client.index] = g_NullMusicState; +} + +static void AddMusicToQueue(SF2_BasePlayer client, MusicState state) +{ + if (!client.IsValid) + { + return; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + return; + } + + if (!client.IsEliminated && client.HasEscaped) + { + return; + } + + MusicState currentState; + currentState = g_CurrentTheme[client.index]; + if (strcmp(currentState.MusicPath, state.MusicPath, false) == 0) + { + return; + } + + if (!currentState.IsNull) + { + if (g_QueuedThemes[client.index] == null) + { + g_QueuedThemes[client.index] = new StringMap(); + } + else + { + MusicState temp; + if (g_QueuedThemes[client.index].SetArray(state.MusicPath, temp, sizeof(temp))) + { + state.CurrentVolume = temp.CurrentVolume; + } + } + if (strcmp(currentState.MusicPath, g_OldGlobalMusicState[client.index].MusicPath, false) == 0) + { + currentState.StopAfterFade = true; + } + currentState.SetFadeState(SF2MusicFadeState_FadeOut); + g_QueuedThemes[client.index].SetArray(currentState.MusicPath, currentState, sizeof(currentState)); + if (g_LayeredThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + g_LayeredThemes[client.index].GetArray(key, currentState, sizeof(currentState)); + currentState.SetFadeState(SF2MusicFadeState_FadeOut); + g_QueuedThemes[client.index].SetArray(currentState.MusicPath, currentState, sizeof(currentState)); + } + delete g_LayeredThemes[client.index]; + delete snapshot; + } + g_QueuedThemes[client.index].Remove(state.MusicPath); + } + state.SetFadeState(SF2MusicFadeState_FadeIn); + g_CurrentTheme[client.index] = state; + g_CurrentTrackLength[client.index] = 0.0; +} + +static void AddLayeredTrack(SF2_BasePlayer client, MusicState state, int index) +{ + if (!client.IsValid) + { + return; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + return; + } + + if (!client.IsEliminated && client.HasEscaped) + { + return; + } + + if (g_QueuedThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_QueuedThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState oldState; + g_QueuedThemes[client.index].GetArray(key, oldState, sizeof(oldState)); + if (strcmp(oldState.MusicPath, state.MusicPath, false) == 0) + { + g_QueuedThemes[client.index].Remove(key); + } + } + } + + char intString[16]; + IntToString(index, intString, sizeof(intString)); + if (g_LayeredThemes[client.index] == null) + { + g_LayeredThemes[client.index] = new StringMap(); + state.SetFadeState(SF2MusicFadeState_FadeIn); + g_LayeredThemes[client.index].SetArray(intString, state, sizeof(state)); + } + else + { + RemoveLayeredTracks(client); + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + bool exists = false; + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState oldState; + g_LayeredThemes[client.index].GetArray(key, oldState, sizeof(oldState)); + if (strcmp(oldState.MusicPath, state.MusicPath, false) == 0) + { + oldState.SetFadeState(SF2MusicFadeState_FadeIn); + g_LayeredThemes[client.index].SetArray(key, oldState, sizeof(oldState)); + exists = true; + continue; + } + } + delete snapshot; + if (!exists) + { + state.SetFadeState(SF2MusicFadeState_FadeIn); + g_LayeredThemes[client.index].SetArray(intString, state, sizeof(state)); + } + } +} + +static void RemoveLayeredTracks(SF2_BasePlayer client, int index = -1) +{ + if (!client.IsValid) + { + return; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + return; + } + + if (!client.IsEliminated && client.HasEscaped) + { + return; + } + + if (g_LayeredThemes[client.index] == null) + { + return; + } + + char intString[16]; + IntToString(index, intString, sizeof(intString)); + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i = 0; i < snapshot.Length; i++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i, key, sizeof(key)); + MusicState oldState; + g_LayeredThemes[client.index].GetArray(key, oldState, sizeof(oldState)); + oldState.SetFadeState(SF2MusicFadeState_FadeOut); + g_LayeredThemes[client.index].SetArray(key, oldState, sizeof(oldState)); + } + delete snapshot; +} + +static void MusicThink() +{ + float gameTime = GetGameTime(); + float delta = g_LastMusicThink < 0.0 ? 0.0 : gameTime - g_LastMusicThink; + g_LastMusicThink = gameTime; + bool globalMusic = g_BossesWithGlobalMusic.Length > 0; + + for (int i = 1; i <= MaxClients; i++) + { + SF2_BasePlayer client = SF2_BasePlayer(i); + if (!client.IsValid) + { + continue; + } + + if (client.IsEliminated && !client.IsInGhostMode && !client.IsProxy) + { + continue; + } + + if (!client.IsEliminated && client.HasEscaped) + { + continue; + } + + bool dontAdd = true; + + if (g_CurrentTheme[client.index].IsNull) + { + AddMusicToQueue(client, globalMusic ? g_GlobalMusicState[client.index] : g_DefaultMusicState); + } + + if (globalMusic) + { + if (g_GlobalMusicState[client.index].Length > 0.0 && g_CurrentTrackLength[client.index] > g_GlobalMusicState[client.index].Length) + { + UpdateBossGlobalTrackForPlayer(client); + } + } + + if (g_CurrentTheme[client.index].FadeState == SF2MusicFadeState_FadeIn) + { + g_CurrentTheme[client.index].FadeIn(client, delta); + } + + if (g_QueuedThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_QueuedThemes[client.index].Snapshot(); + for (int i2 = 0; i2 < snapshot.Length; i2++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i2, key, sizeof(key)); + MusicState state; + g_QueuedThemes[client.index].GetArray(key, state, sizeof(state)); + if (globalMusic && strcmp(state.MusicPath, g_GlobalMusicState[client.index].MusicPath, false) == 0 && state.FadeState == SF2MusicFadeState_FadeOut) + { + // The global music track still lingers as fading out, keep adding to the delta to avoid weird offsets + dontAdd = false; + } + if (strcmp(state.MusicPath, g_CurrentTheme[client.index].MusicPath, false) == 0) + { + g_QueuedThemes[client.index].Remove(key); + g_CurrentTheme[client.index].CurrentVolume = state.CurrentVolume; + continue; + } + if (state.FadeState == SF2MusicFadeState_FadeOut) + { + state.FadeOut(client, delta); + // Seriously, why the fuck can I not get reference values out of arrays? + g_QueuedThemes[client.index].SetArray(key, state, sizeof(state)); + } + else + { + g_QueuedThemes[client.index].Remove(key); + } + } + if (snapshot.Length == 0) + { + delete g_QueuedThemes[client.index]; + } + delete snapshot; + } + else + { + if (globalMusic) + { + if (strcmp(g_CurrentTheme[client.index].MusicPath, g_GlobalMusicState[client.index].MusicPath, false) != 0) + { + // Our music is not the same as the global music, stop adding to the delta time + dontAdd = true; + } + else + { + dontAdd = false; + } + } + } + + if (!dontAdd) + { + g_CurrentTrackLength[client.index] += delta; + } + + if (g_LayeredThemes[client.index] != null) + { + StringMapSnapshot snapshot = g_LayeredThemes[client.index].Snapshot(); + for (int i2 = 0; i2 < snapshot.Length; i2++) + { + char key[PLATFORM_MAX_PATH]; + snapshot.GetKey(i2, key, sizeof(key)); + MusicState state; + g_LayeredThemes[client.index].GetArray(key, state, sizeof(state)); + if (state.FadeState == SF2MusicFadeState_FadeIn) + { + state.FadeIn(client, delta); + g_LayeredThemes[client.index].SetArray(key, state, sizeof(state)); + } + else if (state.FadeState == SF2MusicFadeState_FadeOut) + { + state.FadeOut(client, delta); + g_LayeredThemes[client.index].SetArray(key, state, sizeof(state)); + } + } + delete snapshot; + } + + if (SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsRenevantMap()) + { + continue; + } + + float clientPos[3], bossPos[3], targetPos[3]; + client.GetAbsOrigin(clientPos); + char path[PLATFORM_MAX_PATH]; + float volume = 1.0, radius = 850.0; + int pitch = 100; + g_CanSwapBackToDefault[client.index] = true; + int priorityBoss = -1, layeredBoss = -1; + int priorityState = ConvertMusicStateToBossState(g_PlayerActiveBossState[client.index]); + + for (int i2 = MAX_BOSSES; i2 >= 0; i2--) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i2); + path[0] = '\0'; + if (!controller.IsValid()) + { + continue; + } + + if (controller.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + BaseBossProfile data = SF2NPC_BaseNPC(i).GetProfileDataEx(); + if (data.IsPvEBoss) + { + continue; + } + + if (data.Type != SF2BossType_Chaser) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + + if (chaser.State == STATE_DEATH) + { + continue; + } + + chaser.GetAbsOrigin(bossPos); + + GetBossMusicTrack(controller, SF2BossMusicTrackType_Normal, path, sizeof(path), volume, pitch, radius); + if (path[0] == '\0') + { + continue; + } + + CBaseEntity target = chaser.Target; + + if (chaser.State != priorityState) + { + if ((priorityState == STATE_CHASE || priorityState == STATE_ATTACK || priorityState == STATE_STUN) + && chaser.State <= STATE_CHASE) + { + continue; + } + + if (priorityState == STATE_ALERT && chaser.State <= priorityState) + { + continue; + } + } + + if (GetVectorDistance(clientPos, bossPos, true) <= SquareFloat(radius)) + { + priorityBoss = i2; + priorityState = chaser.State; + continue; + } + + if (chaser.State == STATE_CHASE || chaser.State == STATE_ATTACK || chaser.State == STATE_STUN) + { + if (target.IsValid()) + { + target.GetAbsOrigin(targetPos); + if (target.index == client.index || GetVectorDistance(clientPos, targetPos, true) <= SquareFloat(radius)) + { + priorityBoss = i2; + priorityState = chaser.State; + continue; + } + } + } + + if (controller.Path.IsValid()) + { + controller.Path.GetEndPosition(targetPos); + if (GetVectorDistance(clientPos, targetPos, true) <= SquareFloat(radius)) + { + priorityBoss = i2; + priorityState = chaser.State; + continue; + } + } + } + + SF2NPC_BaseNPC priorityController = SF2NPC_BaseNPC(priorityBoss); + SF2BossMusicState newState = ConvertBossStateToMusicState(priorityState); + if (priorityController.IsValid() && (!globalMusic || (globalMusic && newState >= g_GlobalMusicUpdateLimit[client.index]))) + { + g_CanSwapBackToDefault[client.index] = false; + int oldBoss = g_PlayerActiveBoss[client.index]; + SF2BossMusicState oldState = g_PlayerActiveBossState[client.index]; + if (oldBoss != priorityBoss || (newState != SF2BossMusicState_Invalid && oldState != ConvertBossStateToMusicState(priorityState))) + { + GetBossMusicTrack(priorityController, SF2BossMusicTrackType_Normal, path, sizeof(path), volume, pitch, radius); + MusicState musicState; + musicState.Init(); + musicState.MaxVolume = volume; + musicState.Pitch = pitch; + musicState.MusicPath = path; + AddMusicToQueue(client, musicState); + } + g_PlayerActiveBoss[client.index] = priorityBoss; + g_PlayerActiveBossState[client.index] = newState; + } + else + { + g_PlayerActiveBoss[client.index] = -1; + g_PlayerActiveBossState[client.index] = SF2BossMusicState_Invalid; + AddMusicToQueue(client, globalMusic ? g_GlobalMusicState[client.index] : g_DefaultMusicState); + } + + for (int i2 = 0; i2 < MAX_BOSSES; i2++) + { + SF2NPC_BaseNPC controller = SF2NPC_BaseNPC(i2); + path[0] = '\0'; + if (!controller.IsValid()) + { + continue; + } + + if (controller.EntIndex == INVALID_ENT_REFERENCE) + { + continue; + } + + BaseBossProfile data = SF2NPC_BaseNPC(i).GetProfileDataEx(); + if (data.IsPvEBoss) + { + continue; + } + + if (data.Type != SF2BossType_Chaser) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + + if (chaser.State == STATE_DEATH || chaser.State == STATE_STUN) + { + continue; + } + + GetBossMusicTrack(controller, SF2BossMusicTrackType_Visible, path, sizeof(path), volume, pitch, radius); + if (path[0] == '\0') + { + continue; + } + + if (priorityState != -1) + { + if ((priorityState == STATE_CHASE || priorityState == STATE_ATTACK || priorityState == STATE_STUN) + && chaser.State < STATE_CHASE) + { + continue; + } + + if (priorityState == STATE_ALERT && chaser.State < priorityState) + { + continue; + } + } + + if (PlayerCanSeeSlender(client.index, i2, false)) + { + layeredBoss = i2; + priorityState = chaser.State; + break; + } + } + + SF2NPC_BaseNPC priorityLayer = SF2NPC_BaseNPC(layeredBoss); + newState = ConvertBossStateToMusicState(priorityState); + if (priorityLayer.IsValid() && (!globalMusic || (globalMusic && newState >= g_GlobalMusicUpdateLimit[client.index]))) + { + int oldBoss = g_PlayerActiveLayerBoss[client.index]; + SF2BossMusicState oldState = g_PlayerActiveLayerBossState[client.index]; + if (oldBoss != priorityBoss || (newState != SF2BossMusicState_Invalid && oldState != ConvertBossStateToMusicState(priorityState))) + { + MusicState musicState; + musicState.Init(); + musicState.MaxVolume = volume; + musicState.Pitch = pitch; + musicState.MusicPath = path; + AddLayeredTrack(client, musicState, layeredBoss); + } + g_PlayerActiveLayerBoss[client.index] = layeredBoss; + g_PlayerActiveLayerBossState[client.index] = newState; + } + else if (!priorityLayer.IsValid()) + { + RemoveLayeredTracks(client, g_PlayerActiveLayerBoss[client.index]); + g_PlayerActiveLayerBoss[client.index] = -1; + g_PlayerActiveLayerBossState[client.index] = SF2BossMusicState_Invalid; + } + } + + if (IsRoundInEscapeObjective() && g_RoundStopPageMusicOnEscape) + { + g_OldDefaultMusicState.Reset(); + } + + RequestFrame(MusicThink); +} + +static SF2BossMusicState ConvertBossStateToMusicState(int state) +{ + if (state == STATE_IDLE) + { + return SF2BossMusicState_Idle; + } + else if (state == STATE_ALERT) + { + return SF2BossMusicState_Alert; + } + else if (state == STATE_CHASE || state == STATE_ATTACK || state == STATE_STUN) + { + return SF2BossMusicState_Chase; + } + return SF2BossMusicState_Invalid; +} + +static int ConvertMusicStateToBossState(SF2BossMusicState state) +{ + if (state == SF2BossMusicState_Idle) + { + return STATE_IDLE; + } + else if (state == SF2BossMusicState_Alert) + { + return STATE_ALERT; + } + else if (state == SF2BossMusicState_Chase) + { + return STATE_CHASE; + } + return -1; +} + +static void GetBossMusicTrack(SF2NPC_BaseNPC controller, + SF2BossMusicTrackType trackType, + char[] buffer, + int bufferSize, + float& volume = 1.0, + int& pitch = 100, + float& radius = 850.0, + bool& instantFade = false, + float& fadeSpeed = 1.0, + SF2BossMusicState& priority = SF2BossMusicState_Max, + float& length = -1.0, + SF2BossGlobalMusicShuffleType& shuffleType = SF2BossGlobalMusicShuffleType_None, + SF2_BasePlayer client = SF2_INVALID_PLAYER) +{ + if (!controller.IsValid()) + { + return; + } + + BaseBossProfile data = controller.GetProfileDataEx(); + if (data.IsPvEBoss) + { + return; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(controller.EntIndex); + + if (chaser.IsValid() && chaser.State == STATE_DEATH) + { + return; + } + + ProfileMusic soundInfo; + ProfileGlobalTracks globalTrack; + int difficulty = controller.Difficulty; + switch (trackType) + { + case SF2BossMusicTrackType_Normal: + { + if (!chaser.IsValid()) + { + return; + } + + if (data.Type != SF2BossType_Chaser) + { + return; + } + + switch (chaser.State) + { + case STATE_IDLE: + { + soundInfo = view_as(data).GetIdleMusics(); + } + + case STATE_ALERT: + { + soundInfo = view_as(data).GetAlertMusics(); + } + + case STATE_CHASE, STATE_ATTACK, STATE_STUN: + { + soundInfo = view_as(data).GetChaseMusics(); + } + } + } + + case SF2BossMusicTrackType_Visible: + { + if (!chaser.IsValid()) + { + return; + } + + if (data.Type != SF2BossType_Chaser) + { + return; + } + + switch (chaser.State) + { + case STATE_IDLE: + { + soundInfo = view_as(data).GetVisibleIdleMusics(); + } + + case STATE_ALERT: + { + soundInfo = view_as(data).GetVisibleAlertMusics(); + } + + case STATE_CHASE, STATE_ATTACK: + { + soundInfo = view_as(data).GetVisibleChaseMusics(); + } + } + } + + case SF2BossMusicTrackType_Global: + { + globalTrack = data.GetGlobalTracks(); + if (globalTrack == null) + { + return; + } + KeyMap_Array tracks = globalTrack.GetTracks(); + if (tracks == null || tracks.Length == 0) + { + return; + } + priority = globalTrack.GetPriority(difficulty); + shuffleType = globalTrack.GetShuffleType(difficulty); + bool changed = false; + SF2BossGlobalMusicShuffleType oldType = g_CurrentTrackShuffleType; + if (g_GlobalMusicTrackMasterID != controller.UniqueID) + { + if (g_CurrentTracks != null) + { + delete g_CurrentTracks; + } + g_CurrentTrackIndex = 0; + g_GlobalMusicTrackMasterID = controller.UniqueID; + g_CurrentTrackShuffleType = SF2BossGlobalMusicShuffleType_Random; + } + + if (g_CurrentTracks == null) + { + for (int i = 0; i < tracks.Length; i++) + { + ProfileGlobalTrack track = view_as(tracks.GetSection(i)); + if (track == null) + { + continue; + } + if (!track.IsEnabled(difficulty)) + { + continue; + } + if (g_CurrentTracks == null) + { + g_CurrentTracks = new ArrayList(); + } + g_CurrentTracks.Push(track); + } + } + + if (g_CurrentTracks == null) + { + // Wait that's illegal, fallback to the first track + g_CurrentTracks = new ArrayList(); + g_CurrentTracks.Push(view_as(tracks.GetSection(0))); + } + ProfileGlobalTrack track = null; + g_CurrentTrackShuffleType = globalTrack.GetShuffleType(difficulty); + if (oldType != g_CurrentTrackShuffleType) + { + changed = true; + } + switch (g_CurrentTrackShuffleType) + { + case SF2BossGlobalMusicShuffleType_None: + { + track = g_CurrentTracks.Get(g_CurrentTrackIndex); + } + + case SF2BossGlobalMusicShuffleType_Random: + { + ArrayList tempTracks = g_CurrentTracks.Clone(); + if (client.IsValid) + { + for (int i = 0; i < tempTracks.Length; i++) + { + ProfileGlobalTrack tempTrack = view_as(tempTracks.Get(i)); + if (tempTrack == null) + { + continue; + } + char temp[PLATFORM_MAX_PATH]; + tempTrack.GetPath(difficulty, temp, sizeof(temp)); + if (client.IsValid && !client.IsBot && strcmp(g_GlobalMusicState[client.index].MusicPath, temp, false) == 0) + { + tempTracks.Erase(i); + i--; + } + } + } + + track = tempTracks.Get(GetRandomInt(0, tempTracks.Length - 1)); + delete tempTracks; + } + + case SF2BossGlobalMusicShuffleType_Order: + { + g_CurrentTrackIndex++; + if (g_CurrentTrackIndex >= g_CurrentTracks.Length) + { + g_CurrentTrackIndex = 0; + } + track = g_CurrentTracks.Get(g_CurrentTrackIndex); + } + + case SF2BossGlobalMusicShuffleType_RandomOrder: + { + if (!changed) + { + g_CurrentTracks.Sort(Sort_Random, Sort_Integer); + } + g_CurrentTrackIndex++; + if (g_CurrentTrackIndex >= g_CurrentTracks.Length) + { + g_CurrentTrackIndex = 0; + } + track = g_CurrentTracks.Get(g_CurrentTrackIndex); + } + } + track.GetPath(difficulty, buffer, bufferSize); + volume = track.GetVolume(difficulty); + pitch = track.GetPitch(difficulty); + length = track.GetLength(difficulty); + return; + } + } + + if (soundInfo != null) + { + if (soundInfo.Paths != null && soundInfo.Paths.Length > 0) + { + soundInfo.Paths.GetString(GetRandomInt(0, soundInfo.Paths.Length - 1), buffer, bufferSize); + volume = soundInfo.GetVolume(); + pitch = soundInfo.GetPitch(); + radius = soundInfo.GetRadius(); + instantFade = soundInfo.CanInstantFade(); + fadeSpeed = soundInfo.GetFadeSpeed(); + return; + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/client/static.sp b/addons/sourcemod/scripting/sf2/client/static.sp index b6b89084..1bc02aac 100644 --- a/addons/sourcemod/scripting/sf2/client/static.sp +++ b/addons/sourcemod/scripting/sf2/client/static.sp @@ -153,7 +153,7 @@ void ClientProcessStaticShake(int client) newPunchAng[i] = oldPunchAng[i]; newPunchAngVel[i] = oldPunchAngVel[i]; } - + int staticMaster = NPCGetFromUniqueID(g_PlayerStaticMaster[client]); if (staticMaster != -1 && NPCGetFlags(staticMaster) & SFF_HASSTATICSHAKE) { @@ -169,7 +169,6 @@ void ClientProcessStaticShake(int client) continue; } - if (NPCGetFlags(i) & SFF_HASSTATICSHAKE) { int master = NPCGetFromUniqueID(g_SlenderCopyMaster[i]); @@ -177,8 +176,6 @@ void ClientProcessStaticShake(int client) { master = i; } - - } } diff --git a/addons/sourcemod/scripting/sf2/client/weapons.sp b/addons/sourcemod/scripting/sf2/client/weapons.sp new file mode 100644 index 00000000..e2070e50 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/client/weapons.sp @@ -0,0 +1,818 @@ +#pragma newdecls required +#pragma semicolon 1 + +static KeyMap g_RestrictedWeapons = null; + +// Weapon Handles +static Handle g_WeaponHandles[10][10]; + +void SetupPlayerWeapons() +{ + ReloadRestrictedWeapons(); + SetupClassDefaultWeapons(); +} + +void SF2_RefreshRestrictions() +{ + for(int client = 1; client <= MaxClients; client++) + { + if (IsValidClient(client) && (!g_PlayerEliminated[client] || (g_PlayerEliminated[client] && !IsClientInPvP(client) && !IsClientInPvE(client)))) + { + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + g_PlayerPostWeaponsTimer[client] = CreateTimer(1.0, Timer_ClientPostWeapons, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + } + } +} + +static void SetupClassDefaultWeapons() +{ + // Scout + g_WeaponHandles[view_as(TFClass_Scout)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_scattergun", 13, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Scout)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_pistol", 23, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Scout)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_bat", 0, 0, 0, "", true); + + // Sniper + g_WeaponHandles[view_as(TFClass_Sniper)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_sniperrifle", 14, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Sniper)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_smg", 16, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Sniper)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_club", 3, 0, 0, "", true); + + // Soldier + g_WeaponHandles[view_as(TFClass_Soldier)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_rocketlauncher", 18, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Soldier)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_shotgun", 10, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Soldier)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_shovel", 6, 0, 0, "", true); + + // Demoman + g_WeaponHandles[view_as(TFClass_DemoMan)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_grenadelauncher", 19, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_DemoMan)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_pipebomblauncher", 20, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_DemoMan)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_bottle", 1, 0, 0, "", true); + + // Medic + g_WeaponHandles[view_as(TFClass_Medic)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_syringegun_medic", 17, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Medic)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_medigun", 29, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Medic)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_bonesaw", 8, 0, 0, "", true); + + // Heavy + g_WeaponHandles[view_as(TFClass_Heavy)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_minigun", 15, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Heavy)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_shotgun", 11, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Heavy)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_fists", 5, 0, 0, "", true); + + // Pyro + g_WeaponHandles[view_as(TFClass_Pyro)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_flamethrower", 21, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Pyro)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_shotgun", 12, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Pyro)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_fireaxe", 2, 0, 0, "", true); + + // Spy + g_WeaponHandles[view_as(TFClass_Spy)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_revolver", 24, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Spy)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_knife", 4, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Spy)][TFWeaponSlot_Building] = PrepareItemHandle("tf_weapon_invis", 297, 0, 0, "", true); + + // Engineer + g_WeaponHandles[view_as(TFClass_Engineer)][TFWeaponSlot_Primary] = PrepareItemHandle("tf_weapon_shotgun", 9, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Engineer)][TFWeaponSlot_Secondary] = PrepareItemHandle("tf_weapon_pistol", 22, 0, 0, "", true); + g_WeaponHandles[view_as(TFClass_Engineer)][TFWeaponSlot_Melee] = PrepareItemHandle("tf_weapon_wrench", 7, 0, 0, "", true); +} + +Handle GetClassStockWeaponHandle(TFClassType tfClass, int slot) +{ + return g_WeaponHandles[tfClass][slot]; +} + +void ReloadRestrictedWeapons() +{ + if (g_RestrictedWeapons != null) + { + CleanupKeyMap(g_RestrictedWeapons); + } + + char buffer[PLATFORM_MAX_PATH]; + if (!g_UseAlternateConfigDirectoryConVar.BoolValue) + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_RESTRICTEDWEAPONS); + } + else + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_RESTRICTEDWEAPONS_DATA); + } + KeyValues kv = new KeyValues("root"); + if (!FileToKeyValues(kv, buffer)) + { + delete kv; + LogError("Failed to load restricted weapons list! File not found!"); + return; + } + + g_RestrictedWeapons = KeyValuesToKeyMap(kv); + delete kv; +} + +Action Timer_ClientPostWeapons(Handle timer, any userid) +{ + SF2_BasePlayer client = SF2_BasePlayer(GetClientOfUserId(userid)); + if (!client.IsValid || !client.IsAlive) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPostWeaponsTimer[client.index]) + { + return Plugin_Stop; + } + + g_PlayerHasRegenerationItem[client.index] = false; + + #if defined DEBUG + if (g_DebugDetailConVar.IntValue > 0) + { + DebugMessage("START Timer_ClientPostWeapons(%d)", client.index); + } + + int oldWeaponItemIndexes[6] = { -1, ... }; + int newWeaponItemIndexes[6] = { -1, ... }; + + for (int i = 0; i <= 5; i++) + { + int weaponEnt = client.GetWeaponSlot(i); + if (!IsValidEdict(weaponEnt)) + { + continue; + } + + oldWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + } + #endif + + bool removeWeapons = true; + bool keepUtilityItems = false; + bool restrictWeapons = true; + bool useStock = false; + bool removeWearables = false; + bool preventAttack = false; + + if (IsRoundEnding()) + { + if (!client.IsEliminated) + { + removeWeapons = true; + restrictWeapons = true; + } + } + + if (client.IsEliminated) + { + removeWeapons = !g_PlayerKeepWeaponsConVar.BoolValue; + restrictWeapons = true; + preventAttack = true; + } + + // pvp + if (client.IsInPvP || (SF_IsRaidMap() && !client.IsEliminated)) + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = false; + preventAttack = false; + } + + if (IsRoundInWarmup()) + { + removeWeapons = false; + restrictWeapons = false; + keepUtilityItems = false; + preventAttack = false; + } + + if (client.IsInPvE) + { + if (IsPvEBoxing()) + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = true; + preventAttack = false; + } + else + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = false; + preventAttack = false; + } + } + + if (client.IsProxy) + { + restrictWeapons = true; + removeWeapons = true; + useStock = true; + removeWearables = true; + keepUtilityItems = false; + preventAttack = false; + } + + if (client.IsInGhostMode) + { + removeWeapons = true; + } + + if (SF_IsBoxingMap() && !client.IsEliminated && !IsRoundEnding()) + { + removeWeapons = false; + restrictWeapons = true; + keepUtilityItems = true; + preventAttack = false; + } + + if (removeWeapons && !keepUtilityItems) + { + for (int i = 0; i <= 5; i++) + { + if (i == TFWeaponSlot_Melee && !client.IsInGhostMode) + { + continue; + } + TF2_RemoveWeaponSlotAndWearables(client.index, i); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_demoshield")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + client.SwitchToWeaponSlot(TFWeaponSlot_Melee); + } + + if (keepUtilityItems) + { + for (int i = 0; i <= 5; i++) + { + if ((i == TFWeaponSlot_Melee || i == TFWeaponSlot_Secondary) && !client.IsInGhostMode) + { + continue; + } + TF2_RemoveWeaponSlotAndWearables(client.index, i); + } + + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_weapon_builder")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + int weaponEnt = INVALID_ENT_REFERENCE; + weaponEnt = client.GetWeaponSlot(TFWeaponSlot_Secondary); + + if (IsValidEdict(weaponEnt)) + { + int itemIndex = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemIndex) + { + case 163, 129, 226, 354, 1001, 131, 406, 1099, 42, 159, 311, 433, 863, 1002, 1190: + { + //Do nothing + } + default: + { + TF2_RemoveWeaponSlotAndWearables(client.index, TFWeaponSlot_Secondary); + } + } + } + } + + if (removeWearables) + { + TF2_StripWearables(client.index); + } + + TFClassType playerClass = client.Class; + int classToInt = view_as(playerClass); + + if (restrictWeapons) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsWeaponRestricted(client, itemIndex) && GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_razorback")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsWeaponRestricted(client, itemIndex) && GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_demoshield")) != -1) + { + int itemIndex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsWeaponRestricted(client, itemIndex) && GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + if (g_RestrictedWeapons != null) + { + int weaponEnt = INVALID_ENT_REFERENCE; + for (int slot = TFWeaponSlot_Primary; slot <= 6; slot++) + { + weaponEnt = client.GetWeaponSlot(slot); + if (!IsValidEntity(weaponEnt)) + { + continue; + } + + if (useStock || IsWeaponRestricted(client, GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"))) + { + TF2_RemoveWeaponSlotAndWearables(client.index, slot); + + if (GetClassStockWeaponHandle(playerClass, slot) != null) + { + int newWeapon = TF2Items_GiveNamedItem(client.index, GetClassStockWeaponHandle(playerClass, slot)); + if (IsValidEntity(newWeapon)) + { + EquipPlayerWeapon(client.index, newWeapon); + } + } + } + else + { + if (!client.HasRegenItem) + { + client.HasRegenItem = IsRegenWeapon(weaponEnt); + } + } + } + } + } + + // Change stats on some weapons. + if (!client.IsEliminated) + { + int weaponEnt = INVALID_ENT_REFERENCE; + Handle weaponHandle = null; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = client.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + bool resetHealth = false; + switch (itemDef) + { + case 214: // Powerjack + { + weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 214, 0, 0, "180 ; 12.0 ; 412 ; 1.2"); + } + case 310: //The Warrior's Spirit + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 310, 0, 0, "2 ; 1.3 ; 412 ; 1.3"); + } + case 326: // The Back Scratcher + { + weaponHandle = PrepareItemHandle("tf_weapon_fireaxe", 326, 0, 0, "2 ; 1.25 ; 412 ; 1.25 ; 69 ; 0.25 ; 108 ; 1.25"); + } + case 304: // Amputator + { + if (!SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "200 ; 0.0 ; 57 ; 2 ; 1 ; 0.8"); + } + else + { + weaponHandle = PrepareItemHandle("tf_weapon_bonesaw", 304, 0, 0, "1 ; 0.8"); + } + } + case 239: //GRU + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 239, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); + resetHealth = true; + } + case 1100: //Bread Bite + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 1100, 0, 0, "107 ; 1.3 ; 772 ; 1.5 ; 129 ; 0.0 ; 414 ; 1.0 ; 1 ; 0.75"); + resetHealth = true; + } + case 426: //Eviction Notice + { + weaponHandle = PrepareItemHandle("tf_weapon_fists", 426, 0, 0, "6 ; 0.6 ; 107 ; 1.15 ; 737 ; 4.0 ; 1 ; 0.4 ; 412 ; 1.2"); + } + case 775: //The Escape Plan (Its like, real buggy on wearer) + { + weaponHandle = PrepareItemHandle("tf_weapon_shovel", 775, 0, 0, "414 ; 1 ; 734 ; 0.1"); + } + case 452: //Three Rune Blade + { + weaponHandle = PrepareItemHandle("tf_weapon_bat", 452, 0, 0, ""); + } + case 325: //Boston Basher + { + weaponHandle = PrepareItemHandle("tf_weapon_bat", 325, 0, 0, ""); + } + case 450: //Atomizer + { + weaponHandle = PrepareItemHandle("tf_weapon_bat", 450, 0, 0, ""); + } + case 225: //Your Eternal Reward + { + weaponHandle = PrepareItemHandle("tf_weapon_knife", 225, 0, 0, ""); + } + case 649: //Spy-cicle + { + weaponHandle = PrepareItemHandle("tf_weapon_knife", 649, 0, 0, ""); + } + case 574: //Spy-cicle + { + weaponHandle = PrepareItemHandle("tf_weapon_knife", 574, 0, 0, ""); + } + } + if (weaponHandle != null) + { + client.RemoveWeaponSlot(slot); + int entity = TF2Items_GiveNamedItem(client.index, weaponHandle); + EquipPlayerWeapon(client.index, entity); + if (resetHealth && !IsRoundPlaying()) + { + SetEntityHealth(client.index, client.MaxHealth); + } + delete weaponHandle; + } + } + } + + if (preventAttack) + { + int weaponEnt = INVALID_ENT_REFERENCE; + while ((weaponEnt = FindEntityByClassname(weaponEnt, "tf_wearable_demoshield")) != INVALID_ENT_REFERENCE) + { + if (GetEntPropEnt(weaponEnt, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(weaponEnt); + } + } + + for (int slot = 0; slot <= 5; slot++) + { + if (slot == TFWeaponSlot_Melee) + { + continue; + } + + weaponEnt = client.GetWeaponSlot(slot); + if (!IsValidEntity(weaponEnt)) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDef) + { + case 30, 212, 59, 60, 297, 947, 1101: // Invis Watch, Base Jumper + { + TF2_RemoveWeaponSlotAndWearables(client.index, slot); + } + + default: + { + SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextPrimaryAttack", 99999999.9); + SetEntPropFloat(weaponEnt, Prop_Send, "m_flNextSecondaryAttack", 99999999.9); + } + } + } + } + + //Remove the teleport ability + if (client.IsInPvP || client.IsInPvE || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !client.IsEliminated)) + { + int weaponEnt = INVALID_ENT_REFERENCE; + Handle weaponHandle = null; + for (int slot = 0; slot <= 5; slot++) + { + weaponEnt = client.GetWeaponSlot(slot); + if (!weaponEnt || weaponEnt == INVALID_ENT_REFERENCE) + { + continue; + } + + int itemDef = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + switch (itemDef) + { + case 589: // Eureka Effect + { + client.RemoveWeaponSlot(slot); + + weaponHandle = PrepareItemHandle("tf_weapon_wrench", 589, 0, 0, "93 ; 0.5 ; 732 ; 0.5"); + int entity = TF2Items_GiveNamedItem(client.index, weaponHandle); + EquipPlayerWeapon(client.index, entity); + } + } + if (weaponHandle != null) + { + delete weaponHandle; + } + } + } + //Force them to take their melee wep, it prevents the civilian bug. + client.SwitchToWeaponSlot(TFWeaponSlot_Melee); + + // Remove all hats. + if (client.IsInGhostMode) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable_campaign_item")) != -1) + { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client.index) + { + RemoveEntity(ent); + } + } + } + + float healthFromPack = 1.0; + if (!IsClassConfigsValid()) + { + if (!client.IsEliminated && !SF_IsBoxingMap()) + { + if (client.HasRegenItem) + { + healthFromPack = 0.40; + } + if (client.Class == TFClass_Medic) + { + healthFromPack = 0.0; + } + } + } + else + { + if (!client.IsEliminated && !SF_IsBoxingMap()) + { + healthFromPack = g_ClassHealthPickupMultiplier[classToInt]; + if (client.HasRegenItem) + { + healthFromPack -= 0.6; + } + if (healthFromPack <= 0.0) + { + healthFromPack = 0.0; + } + } + } + + TF2Attrib_SetByDefIndex(client.index, 109, healthFromPack); + + #if defined DEBUG + int weaponEnt = INVALID_ENT_REFERENCE; + + for (int i = 0; i <= 5; i++) + { + weaponEnt = client.GetWeaponSlot(i); + if (!IsValidEdict(weaponEnt)) + { + continue; + } + + newWeaponItemIndexes[i] = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + } + + if (g_DebugDetailConVar.IntValue > 0) + { + for (int i = 0; i <= 5; i++) + { + DebugMessage("-> slot %d: %d (old: %d)", i, newWeaponItemIndexes[i], oldWeaponItemIndexes[i]); + } + + DebugMessage("END Timer_ClientPostWeapons(%d) -> remove = %d, restrict = %d", client.index, removeWeapons, restrictWeapons); + } + #endif + + Call_StartForward(g_OnPlayerPostWeaponsPFwd); + Call_PushCell(client); + Call_Finish(); + + return Plugin_Stop; +} + +public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int itemDefinitionIndex, Handle &itemHandle) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + switch (itemDefinitionIndex) + { + case 642: + { + Handle itemOverride = PrepareItemHandle("tf_wearable", 642, 0, 0, "376 ; 1.0 ; 377 ; 0.2 ; 57 ; 2 ; 412 ; 1.10"); + + if (itemOverride != null) + { + itemHandle = itemOverride; + + return Plugin_Changed; + } + delete itemOverride; + itemOverride = null; + } + } + + return Plugin_Continue; +} + +bool IsRegenWeapon(int weaponEnt) +{ + Address attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 1003); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 490); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 190); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 130); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 57); + if (attribRegen != Address_Null) + { + return true; + } + attribRegen = TF2Attrib_GetByDefIndex(weaponEnt, 220); + if (attribRegen != Address_Null) + { + return true; + } + return false; +} + +bool IsWeaponRestricted(SF2_BasePlayer client, int itemDefInt) +{ + if (g_RestrictedWeapons == null) + { + return false; + } + + bool returnBool = false; + + char itemDef[32]; + FormatEx(itemDef, sizeof(itemDef), "%d", itemDefInt); + + bool proxyBoss = SF_IsProxyMap(); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC Npc = SF2NPC_BaseNPC(i); + if (!Npc.IsValid()) + { + continue; + } + if (Npc.GetProfileDataEx().GetProxies().IsEnabled(Npc.Difficulty)) + { + proxyBoss = true; + break; + } + } + + char className[32]; + TF2_GetClassName(client.Class, className, sizeof(className)); + KeyMap section = g_RestrictedWeapons.GetSection(className); + if (section != null) + { + char restriction[32]; + section.GetString(itemDef, restriction, sizeof(restriction), "0"); + if (StrContains(restriction, "1") != -1 && !client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "2") != -1 && !client.IsEliminated && proxyBoss) + { + returnBool = true; + } + + if (StrContains(restriction, "3") != -1 && client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "4") != -1 && client.IsInPvP) + { + returnBool = true; + } + + if (StrContains(restriction, "5") != -1) + { + returnBool = true; + } + + if (StrContains(restriction, "6") != -1 && client.IsInPvE) + { + returnBool = true; + } + + if (StrContains(restriction, "7") != -1 && !client.IsEliminated && SF_IsBoxingMap()) + { + returnBool = true; + } + + if (StrContains(restriction, "8") != -1 && !client.IsEliminated && SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + returnBool = true; + } + } + + section = g_RestrictedWeapons.GetSection("all"); + if (section != null) + { + char restriction[32]; + section.GetString(itemDef, restriction, sizeof(restriction), "0"); + if (StrContains(restriction, "1") != -1 && !client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "2") != -1 && !client.IsEliminated && proxyBoss) + { + returnBool = true; + } + + if (StrContains(restriction, "3") != -1 && client.IsEliminated) + { + returnBool = true; + } + + if (StrContains(restriction, "4") != -1 && client.IsInPvP) + { + returnBool = true; + } + + if (StrContains(restriction, "5") != -1) + { + returnBool = true; + } + + if (StrContains(restriction, "6") != -1 && client.IsInPvE) + { + returnBool = true; + } + + if (StrContains(restriction, "7") != -1 && !client.IsEliminated && SF_IsBoxingMap()) + { + returnBool = true; + } + + if (StrContains(restriction, "8") != -1 && !client.IsEliminated && SF_SpecialRound(SPECIALROUND_THANATOPHOBIA)) + { + returnBool = true; + } + } + + return returnBool; +} diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index a2b6fd36..952821e9 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -326,23 +326,6 @@ void SDK_Init() LogError("Failed to setup Studio_SeqVelocity call from gamedata"); } - // From nosoop's TF2 Utils plugin - StartPrepSDKCall(SDKCall_Entity); - PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CTFWeaponBase::GetWeaponID"); - PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); - if ((g_SDKGetWeaponID = EndPrepSDKCall()) == null) - { - SetFailState("Failed to setup CTFWeaponBase::GetWeaponID call from gamedata"); - } - - StartPrepSDKCall(SDKCall_Entity); - PrepSDKCall_SetFromConf(gameData, SDKConf_Virtual, "CBaseEntity::IsBaseCombatWeapon"); - PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); - if ((g_SDKIsWeapon = EndPrepSDKCall()) == null) - { - SetFailState("Failed to setup CBaseEntity::IsBaseCombatWeapon call from gamedata"); - } - //Hook_ClientWantsLagCompensationOnEntity int offset = gameData.GetOffset("CTFPlayer::WantsLagCompensationOnEntity"); g_DHookWantsLagCompensationOnEntity = new DynamicHook(offset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp index 5d6b5e6a..f4b96d1c 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/actions/playsequenceandwait.sp @@ -12,6 +12,7 @@ methodmap SF2_PlaySequenceAndWait < NextBotAction g_Factory.SetCallback(NextBotActionCallbackType_OnStart, OnStart); g_Factory.SetCallback(NextBotActionCallbackType_Update, Update); g_Factory.SetCallback(NextBotActionCallbackType_OnSuspend, OnSuspend); + g_Factory.SetCallback(NextBotActionCallbackType_OnEnd, OnEnd); g_Factory.BeginDataMapDesc() .DefineIntField("m_Sequence") .DefineFloatField("m_Duration") @@ -119,10 +120,12 @@ static int OnStart(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAc duration = actor.SequenceDuration(action.Sequence) / action.Rate; duration *= (1.0 - action.Cycle); } + if (SF2_ChaserEntity(actor.index).IsValid()) { SF2_ChaserEntity(actor.index).GroundSpeedOverride = true; } + action.EndTime = GetGameTime() + duration; return action.Continue(); @@ -132,10 +135,6 @@ static int Update(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, float inte { if (GetGameTime() > action.EndTime) { - if (SF2_ChaserEntity(actor.index).IsValid()) - { - SF2_ChaserEntity(actor.index).GroundSpeedOverride = false; - } return action.Done(); } @@ -143,12 +142,16 @@ static int Update(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, float inte } static int OnSuspend(SF2_PlaySequenceAndWait action, SF2_BaseBoss actor, NextBotAction interruptingAction) +{ + return action.Done(); +} + +static void OnEnd(SF2_PlaySequenceAndWait action, SF2_ChaserEntity actor) { if (SF2_ChaserEntity(actor.index).IsValid()) { SF2_ChaserEntity(actor.index).GroundSpeedOverride = false; } - return action.Done(); } static any Native_Create(Handle plugin, int numParams) diff --git a/addons/sourcemod/scripting/sf2/old_pvp.sp b/addons/sourcemod/scripting/sf2/old_pvp.sp new file mode 100644 index 00000000..ce8a5dd5 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/old_pvp.sp @@ -0,0 +1,1230 @@ +#if defined _sf2_pvp_included + #endinput +#endif +#define _sf2_pvp_included + +#pragma semicolon 1 + +#define SF2_PVP_SPAWN_SOUND "items/pumpkin_drop.wav" +#define FLAME_HIT_DELAY 0.05 + +static ConVar g_PvPArenaLeaveTimeConVar = null; +static ConVar g_PvPArenaProjectileZapConVar = null; + +static const char g_PvPProjectileClasses[][] = +{ + "tf_projectile_rocket", + "tf_projectile_sentryrocket", + "tf_projectile_stun_ball", + "tf_projectile_ball_ornament", + "tf_projectile_cleaver", + "tf_projectile_energy_ball", + "tf_projectile_flare", + "tf_projectile_balloffire", + "tf_projectile_jar", + "tf_projectile_jar_milk", + "tf_projectile_pipe", + "tf_projectile_pipe_remote", + "tf_projectile_stun_ball", + "tf_projectile_throwable_breadmonster", + "tf_projectile_throwable_brick", + "tf_projectile_throwable", + //Don't change + "tf_projectile_arrow", + "tf_projectile_healing_bolt", + "tf_projectile_energy_ring", + "tf_projectile_syringe" +}; + +static const char g_PvPProjectileClassesNoTouch[][] = +{ + "tf_projectile_flare" +}; + +static bool g_PlayerInPvP[MAXTF2PLAYERS]; +static bool g_PlayerIsLeavingPvP[MAXTF2PLAYERS]; +Handle g_PlayerPvPTimer[MAXTF2PLAYERS]; +Handle g_PlayerPvPRespawnTimer[MAXTF2PLAYERS]; +static int g_PlayerPvPTimerCount[MAXTF2PLAYERS]; +static ArrayList g_PlayerEnteredPvPTriggers[MAXTF2PLAYERS] = { null, ... }; + +//Blood +static int g_PvPUserIdLastTrace; +static float g_TimeLastTrace; + +static ArrayList g_PvPBallsOfFire; + +static GlobalForward g_OnPlayerEnterPvP; +static GlobalForward g_OnPlayerExitPvP; + +enum struct PvPProjectile_BallOfFire +{ + int EntIndex; + ArrayList TouchedEntities; + + void Init(int entIndex) + { + this.EntIndex = entIndex; + this.TouchedEntities = new ArrayList(); + } + + void OnTouchPost(int otherEntity) + { + if (IsRoundEnding()) + { + return; + } + + int ent = this.EntIndex; + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + if (!IsValidClient(ownerEntity) || otherEntity == ownerEntity || !IsClientInPvP(ownerEntity)) + { + return; + } + + if (IsValidEntity(otherEntity)) + { + if (this.TouchedEntities.FindValue(otherEntity) == -1) + { + this.TouchedEntities.Push(otherEntity); + + if (IsValidClient(otherEntity) && IsClientInPvP(otherEntity) && GetEntProp(otherEntity, Prop_Send, "m_iTeamNum") == GetEntProp(ownerEntity, Prop_Send, "m_iTeamNum")) + { + float damage = GetEntDataFloat(ent, FindSendPropInfo("CTFProjectile_BallOfFire", "m_iDeflected") + 4); + float damageBonus = TF2_IsPlayerInCondition(otherEntity, TFCond_OnFire) ? g_DragonsFuryBurningBonusConVar.FloatValue : 1.0; + float damagePos[3]; + GetEntPropVector(ent, Prop_Data, "m_vecOrigin", damagePos); + // int damageCustom = 79; + + if (TF2_GetPlayerClass(otherEntity) == TFClass_Pyro) + { + TF2_AddCondition(otherEntity, TFCond_BurningPyro, g_DragonsFuryBurnDurationConVar.FloatValue * 0.5, ownerEntity); + } + + TF2_IgnitePlayer(otherEntity, ownerEntity); + SDKHooks_TakeDamage(otherEntity, ownerEntity, ownerEntity, damage * damageBonus, 0x1220000, GetEntPropEnt(ownerEntity, Prop_Send, "m_hActiveWeapon"), NULL_VECTOR, damagePos); + } + } + } + } + + void Cleanup() + { + delete this.TouchedEntities; + } +} + +void PvP_Initialize() +{ + g_PvPArenaLeaveTimeConVar = CreateConVar("sf2_player_pvparena_leavetime", "5"); + g_PvPArenaProjectileZapConVar = CreateConVar("sf2_pvp_projectile_removal", "0", "This is an experimental code! It could make your server crash, if you get any crash disable this cvar"); + + g_PvPBallsOfFire = new ArrayList(sizeof(PvPProjectile_BallOfFire)); + + g_OnMapStartPFwd.AddFunction(null, MapStart); + g_OnGameFramePFwd.AddFunction(null, GameFrame); + g_OnRoundStartPFwd.AddFunction(null, RoundStart); + g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); + g_OnEntityDestroyedPFwd.AddFunction(null, EntityDestroyed); + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); + + AddTempEntHook("TFBlood", TempEntHook_PvPBlood); + AddTempEntHook("World Decal", TempEntHook_PvPDecal); + AddTempEntHook("Entity Decal", TempEntHook_PvPDecal); +} + +void PvP_SetupMenus() +{ + g_MenuSettingsPvP = new Menu(Menu_SettingsPvP); + g_MenuSettingsPvP.SetTitle("%t%t\n \n", "SF2 Prefix", "SF2 Settings PvP Menu Title"); + g_MenuSettingsPvP.AddItem("0", "Toggle automatic spawning"); + g_MenuSettingsPvP.AddItem("1", "Toggle spawn protection"); + g_MenuSettingsPvP.ExitBackButton = true; +} + +static void MapStart() +{ + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + if (IsValidEntity(ent)) + { + char strName[50]; + GetEntPropString(ent, Prop_Data, "m_iName", strName, sizeof(strName)); + if (strcmp(strName, "sf2_pvp_trigger") == 0) + { + //StartTouch seems to be unreliable if a player is teleported/spawned in the trigger + //SDKHook(ent, SDKHook_StartTouch, PvP_OnTriggerStartTouch); + //But end touch works fine. + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch); + } + } + } + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_respawnroom")) != -1) + { + if (IsValidEntity(ent) && SF_IsBoxingMap()) + { + SDKHook(ent, SDKHook_StartTouch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_Touch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerStartTouchBoxing); + } + } +} + +static void RoundStart() +{ + int ent = -1; + while ((ent = FindEntityByClassname(ent, "trigger_multiple")) != -1) + { + if (IsValidEntity(ent)) + { + char strName[50]; + GetEntPropString(ent, Prop_Data, "m_iName", strName, sizeof(strName)); + if (strcmp(strName, "sf2_pvp_trigger") == 0) + { + //Add physics object flag, so we can zap projectiles! + int flags = GetEntProp(ent, Prop_Data, "m_spawnflags"); + flags |= TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS; + //flags |= TRIGGER_PHYSICS_OBJECTS; + flags |= TRIGGER_PHYSICS_DEBRIS; + SetEntProp(ent, Prop_Data, "m_spawnflags", flags); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerEndTouch); + } + } + } + ent = -1; + while ((ent = FindEntityByClassname(ent, "func_respawnroom")) != -1) + { + if (IsValidEntity(ent) && SF_IsBoxingMap()) + { + //Add physics object flag, so we can zap projectiles! + int flags = GetEntProp(ent, Prop_Data, "m_spawnflags"); + flags |= TRIGGER_EVERYTHING_BUT_PHYSICS_DEBRIS; + //flags |= TRIGGER_PHYSICS_OBJECTS; + flags |= TRIGGER_PHYSICS_DEBRIS; + SetEntProp(ent, Prop_Data, "m_spawnflags", flags); + SDKHook(ent, SDKHook_StartTouch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_Touch, PvP_OnTriggerStartTouchBoxing); + SDKHook(ent, SDKHook_EndTouch, PvP_OnTriggerStartTouchBoxing); + } + } +} + +void PvP_Precache() +{ + PrecacheSound2(SF2_PVP_SPAWN_SOUND, true); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + g_PlayerEnteredPvPTriggers[client.index] = new ArrayList(); + + PvP_ForceResetPlayerPvPData(client.index); + + SDKHook(client.index, SDKHook_TraceAttack, ClientTraceAttack); + SDKHook(client.index, SDKHook_WeaponEquipPost, ClientWeaponEquipPost); +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + PvP_SetPlayerPvPState(client.index, false, false, false); + + if (g_PlayerEnteredPvPTriggers[client.index] != null) + { + delete g_PlayerEnteredPvPTriggers[client.index]; + g_PlayerEnteredPvPTriggers[client.index] = null; + } +} + +static void GameFrame() +{ + // Process through PvP projectiles. + for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, g_PvPProjectileClasses[i])) != -1) + { + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + bool changeProjectileTeam = false; + + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + if (IsValidClient(ownerEntity) && IsClientInPvP(ownerEntity) && GetClientTeam(ownerEntity) != TFTeam_Red) + { + changeProjectileTeam = true; + } + else if (throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + if (IsValidClient(ownerEntity) && IsClientInPvP(ownerEntity) && GetClientTeam(ownerEntity) != TFTeam_Red) + { + changeProjectileTeam = true; + } + } + + if (changeProjectileTeam) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + } + } + + // Process through PvP flame entities. + { + static float mins[3] = { -6.0, ... }; + static float maxs[3] = { 6.0, ... }; + + float origin[3]; + + Handle trace = null; + int ent = -1; + int ownerEntity = INVALID_ENT_REFERENCE; + int hitEntity = INVALID_ENT_REFERENCE; + + while ((ent = FindEntityByClassname(ent, "tf_flame_manager")) != -1) + { + ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (IsValidEdict(ownerEntity)) + { + // tf_flame's initial owner SHOULD be the flamethrower that it originates from. + // If not, then something's completely bogus. + + ownerEntity = GetEntPropEnt(ownerEntity, Prop_Data, "m_hOwnerEntity"); + } + + if (IsValidClient(ownerEntity) && (IsRoundInWarmup() || IsClientInPvP(ownerEntity))) + { + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", origin); + + trace = TR_TraceHullFilterEx(origin, origin, mins, maxs, MASK_PLAYERSOLID, TraceRayDontHitEntity, ownerEntity); + hitEntity = TR_GetEntityIndex(trace); + delete trace; + + if (IsValidEntity(hitEntity)) + { + PvP_OnFlameEntityStartTouchPost(ent, hitEntity); + } + } + } + delete trace; + } +} + +static void EntityCreated(CBaseEntity ent, const char[] classname) +{ + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x083EFF3EFF+ %i(%s)", ent.index, classname); + #endif + for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + SDKHook(ent.index, SDKHook_Spawn, Hook_PvPProjectileSpawn); + SDKHook(ent.index, SDKHook_SpawnPost, Hook_PvPProjectileSpawnPost); + break; + } + } + + for (int i = 0; i < sizeof(g_PvPProjectileClassesNoTouch); i++) + { + if (strcmp(classname, g_PvPProjectileClassesNoTouch[i], false) == 0) + { + SDKHook(ent.index, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + break; + } + } +} + +static void EntityDestroyed(CBaseEntity ent, const char[] classname) +{ + #if defined DEBUG + SendDebugMessageToPlayers(DEBUG_ENTITIES,0,"\x08FF4040FF- %i(%s)", ent.index, classname); + #endif + + if (strcmp(classname, "tf_projectile_balloffire", false) == 0) + { + int index = g_PvPBallsOfFire.FindValue(ent.index); + if (index != -1) + { + PvPProjectile_BallOfFire projectileData; + g_PvPBallsOfFire.GetArray(index, projectileData, sizeof(projectileData)); + projectileData.Cleanup(); + g_PvPBallsOfFire.Erase(index); + } + } +} + +static void ClientWeaponEquipPost(int client, int weapon) +{ + if (!IsValidClient(client) || !IsValidEdict(weapon)) + { + return; + } + g_DHookWeaponGetCustomDamageType.HookEntity(Hook_Pre, weapon, WeaponGetCustomDamageType); +} + +static Action Hook_PvPProjectile_OnTouch(int projectile, int client) +{ + // Check if the projectile hit a player outside of pvp area + // Without that, cannon balls can bounce players which should not happen because they are outside of pvp. + bool remove = false; + if (IsValidClient(client) && !IsClientInPvP(client)) + { + remove = true; + } + + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (owner == client) + { + remove = false; + } + + if (remove) + { + float vel[3]; + CBaseEntity(client).GetAbsVelocity(vel); + TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vel); + RemoveEntity(projectile); + return Plugin_Handled; + } + + return Plugin_Continue; +} + +static Action Hook_PvPProjectileSpawn(int ent) +{ + char class[64]; + GetEntityClassname(ent, class, sizeof(class)); + + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (ownerEntity == -1 && throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + } + + if (IsValidClient(ownerEntity)) + { + if (IsClientInPvP(ownerEntity)) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + } + + return Plugin_Continue; +} + +static void Hook_PvPProjectileSpawnPost(int ent) +{ + if (!IsValidEntity(ent)) + { + return; + } + + char class[64]; + GetEntityClassname(ent, class, sizeof(class)); + + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + + if (ownerEntity == -1 && throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + } + + if (IsValidClient(ownerEntity)) + { + if (IsRoundInWarmup() || IsClientInPvP(ownerEntity)) + { + static const char fixWeaponNotCollidingWithTeammates[][] = + { + "tf_projectile_rocket", + "tf_projectile_sentryrocket", + "tf_projectile_flare" + }; + + for (int i = 0; i < sizeof(fixWeaponNotCollidingWithTeammates); i++) + { + if (IsValidEntity(ent) && strcmp(class, fixWeaponNotCollidingWithTeammates[i], false) == 0) + { + DHookEntity(g_DHookProjectileCanCollideWithTeammates, false, ent, _, Hook_PvPProjectileCanCollideWithTeammates); + break; + } + } + + if (strcmp(class, "tf_projectile_pipe", false) == 0 && GetEntProp(ent, Prop_Send, "m_iType") == 3) + { + /* + Loose Cannon's projectiles + KR: I'm assuming that stopping non-PvP players from getting bounced is the reason why this is implemented. + Despite hooking onto Touch, the knockback still happens half of the time. StartTouch yields the same result + so this is really the best that can be done. + */ + SDKHook(ent, SDKHook_Touch, Hook_PvPProjectile_OnTouch); + } + else if (strcmp(class, "tf_projectile_balloffire", false) == 0) + { + /* + Replicate projectile logic for Dragon's Fury projectiles. + KR: The projectile checks the team of its owner entity (the player), not itself. CBaseEntity::InSameTeam() + could be hooked to change this, but I think that would be too game-changing and not really worth doing for + a single case, so the enemy player logic is sort of replicated. + */ + + PvPProjectile_BallOfFire projectileData; + projectileData.Init(ent); + g_PvPBallsOfFire.PushArray(projectileData, sizeof(projectileData)); + + SDKHook(ent, SDKHook_TouchPost, Hook_PvPProjectileBallOfFireTouchPost); + } + + if (g_PlayerPvPTimer[ownerEntity] == null) + { + SetEntProp(ent, Prop_Data, "m_iInitialTeamNum", 0); + SetEntProp(ent, Prop_Send, "m_iTeamNum", 0); + } + else + { + //Client is not in pvp, remove the projectile + if (g_PvPArenaProjectileZapConVar.BoolValue) + { + PvP_ZapProjectile(ent, false); + } + } + } + } +} + +static void Hook_PvPProjectileBallOfFireTouchPost(int projectile, int otherEntity) +{ + int index = g_PvPBallsOfFire.FindValue(projectile); + if (index != -1) + { + PvPProjectile_BallOfFire projectileData; + g_PvPBallsOfFire.GetArray(index, projectileData, sizeof(projectileData)); + projectileData.OnTouchPost(otherEntity); + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + PvP_SetPlayerPvPState(client.index, false, false, false); + + g_PlayerIsLeavingPvP[client.index] = false; + + if (client.IsAlive && client.IsParticipating) + { + if (!client.IsInGhostMode && !client.IsProxy) + { + if (client.IsEliminated || client.HasEscaped) + { + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + + if (autoSpawn) + { + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.12, Timer_TeleportPlayerToPvP, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + else + { + g_PlayerPvPRespawnTimer[client.index] = null; + } + } + } +} + +void PvP_ZapProjectile(int projectile, bool effects=true) +{ + if (!IsValidEntity(projectile)) + { + return; + } + + //Add zap effects + if (effects) + { + float pos[3]; + GetEntPropVector(projectile, Prop_Send, "m_vecOrigin", pos); + //Spawn the particle. + TE_Particle(g_Particles[ZapParticle], pos, pos); + TE_SendToAll(); + //Play zap sound. + EmitSoundToAll(ZAP_SOUND, projectile, SNDCHAN_AUTO, SNDLEVEL_CAR); + SetEntityRenderMode(projectile, RENDER_TRANSCOLOR); + SetEntityRenderColor(projectile, 0, 0, 0, 1); + } + RemoveEntity(projectile); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + if (client.IsInPvP) + { + g_PlayerIsLeavingPvP[client.index] = false; + } + + if (!client.IsInGhostMode && !client.IsProxy) + { + bool autoSpawn = g_PlayerPreferences[client.index].PlayerPreference_PvPAutoSpawn; + + if (autoSpawn) + { + if (client.IsEliminated || client.HasEscaped) + { + if (!IsRoundEnding()) + { + g_PlayerPvPRespawnTimer[client.index] = CreateTimer(0.3, Timer_RespawnPlayer, client.UserID, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + + PvP_SetPlayerPvPState(client.index, false, false, false); + } +} + +void PvP_OnClientGhostModeEnable(int client) +{ + g_PlayerPvPRespawnTimer[client] = null; +} + +void PvP_OnTriggerStartTouch(int trigger,int other) +{ + char name[64]; + GetEntPropString(trigger, Prop_Data, "m_iName", name, sizeof(name)); + + if (StrContains(name, "sf2_pvp_trigger", false) == 0 || SF2TriggerPvPEntity(trigger).IsValid()) + { + if (IsValidClient(other) && IsPlayerAlive(other) && !IsClientInGhostMode(other)) + { + if (!g_PlayerEliminated[other] && !DidClientEscape(other)) + { + return; + } + //Use valve's kill code if the player is stuck. + if (GetEntPropFloat(other, Prop_Send, "m_flModelScale") != 1.0) + { + TF2_AddCondition(other, TFCond_HalloweenTiny, 0.1); + } + //Resize the player. + SetEntPropFloat(other, Prop_Send, "m_flModelScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHeadScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flTorsoScale", 1.0); + SetEntPropFloat(other, Prop_Send, "m_flHandScale", 1.0); + + int entRef = EnsureEntRef(trigger); + if (g_PlayerEnteredPvPTriggers[other].FindValue(entRef) == -1) + { + g_PlayerEnteredPvPTriggers[other].Push(entRef); + } + + if (IsClientInPvP(other)) + { + if (g_PlayerIsLeavingPvP[other]) + { + // Player left and came back again, but is still in PvP mode. + g_PlayerPvPTimerCount[other] = 0; + g_PlayerPvPTimer[other] = null; + g_PlayerIsLeavingPvP[other] = false; + } + } + else + { + PvP_SetPlayerPvPState(other, true); + } + } + } +} + +void PvP_OnTriggerEndTouch(int trigger, int other) +{ + if (IsValidClient(other)) + { + if (g_PlayerEnteredPvPTriggers[other] != null) + { + int triggerEntRef = EnsureEntRef(trigger); + for (int i = g_PlayerEnteredPvPTriggers[other].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredPvPTriggers[other].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredPvPTriggers[other].Erase(i); + } + } + } + + if (IsClientInPvP(other)) + { + if (g_PlayerEnteredPvPTriggers[other].Length == 0) + { + g_PlayerPvPTimerCount[other] = g_PvPArenaLeaveTimeConVar.IntValue; + if (g_PlayerPvPTimerCount[other] != 0) + { + g_PlayerPvPTimer[other] = CreateTimer(1.0, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + g_PlayerIsLeavingPvP[other] = true; + } + else + { + g_PlayerPvPTimer[other] = CreateTimer(0.1, Timer_PlayerPvPLeaveCountdown, GetClientUserId(other), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + + //A projectile went off pvp area. + if (other > MaxClients && IsValidEntity(other)) + { + //Get entity's classname. + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + //Yup it's a projectile zap it! + //But we have to wait to prevent some bugs. + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static void PvP_OnTriggerStartTouchBoxing(int trigger, int other) +{ + if (other > MaxClients && IsValidEntity(other) && !IsRoundInWarmup() && !IsRoundEnding()) + { + char classname[50]; + GetEntityClassname(other, classname, sizeof(classname)); + for (int i = 0; i < (sizeof(g_PvPProjectileClasses) - 4); i++) + { + if (strcmp(classname, g_PvPProjectileClasses[i], false) == 0) + { + if (!GetEntProp(other, Prop_Send, "m_iDeflected")) + { + CreateTimer(0.1, EntityStillAlive, other, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } +} + +static Action EntityStillAlive(Handle timer, int ref) +{ + int ent = EntRefToEntIndex(ref); + if (ent && ent != INVALID_ENT_REFERENCE) + { + PvP_ZapProjectile(ent); + } + return Plugin_Stop; +} +/** + * Enables/Disables PvP mode on the player. + */ +void PvP_SetPlayerPvPState(int client, bool status, bool removeProjectiles = true, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool oldInPvP = g_PlayerInPvP[player.index]; + if (status == oldInPvP) + { + return; // no change + } + + if (status && player.IsInPvE) + { + PvE_ForceResetPlayerPvEData(player.index); + PvE_SetPlayerPvEState(player.index, false, false); + } + + g_PlayerInPvP[player.index] = status; + g_PlayerPvPTimer[player.index] = null; + g_PlayerPvPRespawnTimer[player.index] = null; + g_PlayerPvPTimerCount[player.index] = 0; + + if (removeProjectiles) + { + // Remove previous projectiles. + PvP_RemovePlayerProjectiles(player.index); + } + + if (status) + { + Call_StartForward(g_OnPlayerEnterPvP); + Call_PushCell(player.index); + Call_Finish(); + } + else + { + Call_StartForward(g_OnPlayerExitPvP); + Call_PushCell(player.index); + Call_Finish(); + } + + if (regenerate) + { + // Regenerate player but keep health the same. + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +static void PvP_OnFlameEntityStartTouchPost(int flame, int other) //Thanks Fire +{ + static float lastHit[MAXPLAYERS + 1]; + + SF2_BasePlayer client = SF2_BasePlayer(other); + if (client.IsValid) + { + float time = GetEngineTime(); + if (lastHit[client.index] < time) + { + lastHit[client.index] = time + FLAME_HIT_DELAY; + + if ((IsRoundInWarmup() || client.IsInPvP) && !IsRoundEnding()) + { + int flamethrower = GetEntPropEnt(flame, Prop_Data, "m_hOwnerEntity"); + if (IsValidEdict(flamethrower)) + { + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(flamethrower, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.index != client.index && ownerEntity.IsValid) + { + if (IsRoundInWarmup() || ownerEntity.IsInPvP) + { + if (client.Team == ownerEntity.Team && ownerEntity.Team != TFTeam_Red) + { + //TF2_MakeBleed(other, ownerEntity, 4.0); + client.Ignite(_, ownerEntity.index); + client.TakeDamage(_, ownerEntity.index, ownerEntity.index, 10.0, ownerEntity.IsCritBoosted() ? (DMG_BURN | DMG_PREVENT_PHYSICS_FORCE | DMG_ACID) : DMG_BURN | DMG_PREVENT_PHYSICS_FORCE); + } + } + } + } + } + } + } +} + +/** + * Forcibly resets global vars of the player relating to PvP. Ignores checking. + */ +void PvP_ForceResetPlayerPvPData(int client) +{ + g_PlayerInPvP[client] = false; + g_PlayerPvPTimer[client] = null; + g_PlayerPvPTimerCount[client] = 0; + g_PlayerPvPRespawnTimer[client] = null; + g_PlayerIsLeavingPvP[client] = false; + + if (g_PlayerEnteredPvPTriggers[client] != null) + { + g_PlayerEnteredPvPTriggers[client].Clear(); + } +} + +static void PvP_RemovePlayerProjectiles(int client) +{ + for (int i = 0; i < sizeof(g_PvPProjectileClasses); i++) + { + int ent = -1; + while ((ent = FindEntityByClassname(ent, g_PvPProjectileClasses[i])) != -1) + { + int throwerOffset = FindDataMapInfo(ent, "m_hThrower"); + bool mine = false; + + int ownerEntity = GetEntPropEnt(ent, Prop_Data, "m_hOwnerEntity"); + if (ownerEntity == client) + { + mine = true; + } + else if (throwerOffset != -1) + { + ownerEntity = GetEntDataEnt2(ent, throwerOffset); + if (ownerEntity == client) + { + mine = true; + } + } + + if (mine) + { + RemoveEntity(ent); + } + } + } +} + +static Action Timer_TeleportPlayerToPvP(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (g_PlayerProxy[client]) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvPRespawnTimer[client]) + { + return Plugin_Stop; + } + g_PlayerPvPRespawnTimer[client] = null; + + ArrayList spawnPointList = new ArrayList(); + ArrayList clearSpawnPointList = new ArrayList(); + + int ent = -1; + + { + float myPos[3]; + float mins[3], maxs[3]; + GetEntPropVector(client, Prop_Send, "m_vecMins", mins); + GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); + + char name[32]; + + ent = -1; + while ((ent = FindEntityByClassname(ent, "info_target")) != -1) + { + GetEntPropString(ent, Prop_Data, "m_iName", name, sizeof(name)); + + if (!StrContains(name, "sf2_pvp_spawnpoint", false)) + { + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", myPos); + + spawnPointList.Push(ent); + if (!IsSpaceOccupiedPlayer(myPos, mins, maxs, client)) + { + clearSpawnPointList.Push(ent); + } + } + } + + ent = -1; + while ((ent = FindEntityByClassname(ent, "sf2_info_player_pvpspawn")) != -1) + { + SF2PlayerPvPSpawnEntity spawnPoint = SF2PlayerPvPSpawnEntity(ent); + if (!spawnPoint.IsValid() || !spawnPoint.Enabled) + { + continue; + } + + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", myPos); + + spawnPointList.Push(ent); + if (!IsSpaceOccupiedPlayer(myPos, mins, maxs, client)) + { + clearSpawnPointList.Push(ent); + } + } + } + + int num; + if ((num = clearSpawnPointList.Length) > 0) + { + ent = clearSpawnPointList.Get(GetRandomInt(0, num - 1)); + } + else if ((num = spawnPointList.Length) > 0) + { + ent = spawnPointList.Get(GetRandomInt(0, num - 1)); + } + + delete spawnPointList; + delete clearSpawnPointList; + + if (num > 0) + { + float pos[3], ang[3]; + GetEntPropVector(ent, Prop_Data, "m_vecAbsOrigin", pos); + GetEntPropVector(ent, Prop_Data, "m_angAbsRotation", ang); + TeleportEntity(client, pos, ang, { 0.0, 0.0, 0.0 }); + + EmitAmbientSound(SF2_PVP_SPAWN_SOUND, pos, _, SNDLEVEL_NORMAL, _, 1.0); + if (g_PlayerPreferences[client].PlayerPreference_PvPSpawnProtection) + { + TF2_AddCondition(client, TFCond_UberchargedCanteen, 1.5); + } + + SF2PlayerPvPSpawnEntity spawnPoint = SF2PlayerPvPSpawnEntity(ent); + if (spawnPoint.IsValid()) + { + spawnPoint.FireOutput("OnSpawn", client); + } + } + return Plugin_Stop; +} + +static Action Timer_PlayerPvPLeaveCountdown(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); + if (client <= 0) + { + return Plugin_Stop; + } + + if (timer != g_PlayerPvPTimer[client]) + { + return Plugin_Stop; + } + + if (!IsClientInPvP(client)) + { + return Plugin_Stop; + } + + if (g_PlayerPvPTimerCount[client] <= 0) + { + PvP_SetPlayerPvPState(client, false); + g_PlayerIsLeavingPvP[client] = false; + + // Force them to their melee weapon and stop taunting, to prevent tposing and what not. + ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); + TF2_RemoveCondition(client, TFCond_Taunting); + return Plugin_Stop; + } + + g_PlayerPvPTimerCount[client]--; + + //if (!g_PlayerProxyAvailableInForce[client]) + { + SetHudTextParams(-1.0, 0.75, + 1.0, + 255, 255, 255, 255, + _, + _, + 0.25, 1.25); + + ShowSyncHudText(client, g_HudSync, "%T", "SF2 Exiting PvP Arena", client, g_PlayerPvPTimerCount[client]); + } + + return Plugin_Continue; +} +bool IsClientInPvP(int client) +{ + return g_PlayerInPvP[client]; +} + +bool IsClientLeavingPvP(int client) +{ + return g_PlayerIsLeavingPvP[client]; +} +//////////////// +// Blood effects +//////////////// + +//Actually we are blocking the damages if the player is injured and not in pvp. So this hook is useless +static Action TempEntHook_PvPBlood(const char[] te_name, int[] players, int numPlayers, float delay) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + int client = TE_ReadNum("entindex"); + if (IsValidClient(client) && g_PlayerEliminated[client] && !IsClientInPvP(client)) + { + //Block effect + return Plugin_Stop; + } + return Plugin_Continue; +} + +//Credits to STT creators for this decal trace +static Action ClientTraceAttack(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &ammotype, int hitbox, int hitgroup) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + if (IsValidClient(victim)) + { + g_PvPUserIdLastTrace = GetClientUserId(victim); + g_TimeLastTrace = GetEngineTime(); + } + + return Plugin_Continue; +} + +static Action TempEntHook_PvPDecal(const char[] te_name, int[] players, int numPlayers, float delay) +{ + if (!g_Enabled) + { + return Plugin_Continue; + } + + // Blocks the blood decals on the walls / nearby entities + + int client = GetClientOfUserId(g_PvPUserIdLastTrace); + if (IsValidClient(client) && g_PlayerEliminated[client] && !IsClientInPvP(client)) + { + if (g_TimeLastTrace != 0.0 && GetEngineTime() - g_TimeLastTrace < 0.1) + { + //PrintToChatAll("Blocked blood stain for %N (%f)", client, GetEngineTime() - g_TimeLastTrace); + return Plugin_Stop; + } + } + + return Plugin_Continue; +} + +static MRESReturn WeaponGetCustomDamageType(int weapon, DHookReturn returnHandle, DHookParam params) +{ + if (!g_Enabled) + { + return MRES_Ignored; + } + + SF2_BasePlayer ownerEntity = SF2_BasePlayer(GetEntPropEnt(weapon, Prop_Data, "m_hOwnerEntity")); + if (ownerEntity.IsValid && ownerEntity.IsInPvP && IsValidEntity(weapon) && ownerEntity) + { + int customDamageType = returnHandle.Value; + if (customDamageType != -1) + { + MRESReturn hookResult = GetWeaponCustomDamageType(weapon, ownerEntity.index, customDamageType); + if (hookResult != MRES_Ignored) + { + returnHandle.Value = customDamageType; + return hookResult; + } + } + else + { + return MRES_Ignored; + } + } + else + { + return MRES_Ignored; + } + + return MRES_Ignored; +} + +static MRESReturn GetWeaponCustomDamageType(int weapon, int client, int &customDamageType) +{ + if (IsValidClient(client) && IsClientInPvP(client) && IsValidEntity(weapon) && IsValidEdict(weapon) && IsValidEntity(client)) + { + static const char fixWeaponPenetrationClasses[][] = + { + "tf_weapon_sniperrifle", + "tf_weapon_sniperrifle_decap", + "tf_weapon_sniperrifle_classic" + }; + + char weaponName[256]; + GetEntityClassname(weapon, weaponName, sizeof(weaponName)); + + /* + * Fixes the sniper rifle not damaging teammates. + * + * WHY? For every other hitscan weapon in the game, simply enforcing lag compensation in CTFPlayer::WantsLagCompensationOnEntity() + * works. However, when it comes to weapons that penetrate teammates, the bullet trace will not iterate through teammates. This is + * the case with all sniper rifles, and is the reason why damage is never normally dealt to teammates despite having friendly fire + * on and lag compensation. + * + * In this case, the type of penetration is determined by CTFWeaponBase::GetCustomDamageType(). For Snipers, default value is + * TF_DMG_CUSTOM_PENETRATE_MY_TEAM (11) (piss rifle is TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE (14)). This value specifies + * penetration of the bullet through teammates without damaging them. The damage type is switched to 0, and for the Machina at + * full charge, TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS (12). + * + */ + for (int i = 0; i < sizeof(fixWeaponPenetrationClasses); i++) + { + if (strcmp(weaponName, fixWeaponPenetrationClasses[i], false) == 0) + { + customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS + int itemDefIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); + + if ((itemDefIndex == 526 || itemDefIndex == 30665) && GetEntPropFloat(weapon, Prop_Send, "m_flChargedDamage") >= 150.0) // The Machina, Shooting Star + { + customDamageType = 12; // TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS + } + else + { + customDamageType = 0; // no penetration behavior. + } + + return MRES_Supercede; + } + } + } + + return MRES_Ignored; +} + +static MRESReturn Hook_PvPProjectileCanCollideWithTeammates(int projectile, DHookReturn returnHandle, DHookParam params) +{ + if (!g_Enabled) + { + return MRES_Ignored; + } + + if (!IsValidEntity(projectile)) + { + return MRES_Ignored; + } + + int owner = GetEntPropEnt(projectile, Prop_Data, "m_hOwnerEntity"); + if (IsValidClient(owner) && (IsRoundInWarmup() || IsClientInPvP(owner))) + { + returnHandle.Value = true; + return MRES_Supercede; + } + + return MRES_Ignored; +} + +// API + +void PvP_InitializeAPI() +{ + CreateNative("SF2_IsClientInPvP", Native_IsClientInPvP); + g_OnPlayerEnterPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); + g_OnPlayerExitPvP = new GlobalForward("SF2_OnClientEnterPvP", ET_Ignore, Param_Cell); +} + +static int Native_IsClientInPvP(Handle plugin,int numParams) +{ + return IsClientInPvP(GetNativeCell(1)); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 9b6ffddf..a5538756 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -301,12 +301,12 @@ Action Command_Pack(int client,int args) { if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) { - CPrintToChat(client,"{red}Boss pack vote is disabled on this server."); + CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Disabled Boss Pack"); return Plugin_Handled; } char bossPackName[64]; GetCurrentBossPack(bossPackName, sizeof(bossPackName)); - CPrintToChat(client, "{dodgerblue}Pack: {lightblue}%s", bossPackName); + CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Current Boss Pack", bossPackName); return Plugin_Handled; } @@ -314,7 +314,7 @@ Action Command_NextPack(int client,int args) { if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) { - CPrintToChat(client,"{red}Boss pack vote is disabled on this server."); + CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Disabled Boss Pack"); return Plugin_Handled; } @@ -323,7 +323,7 @@ Action Command_NextPack(int client,int args) if (strcmp(nextpack, "") == 0) { - CPrintToChat(client,"{dodgerblue}%t{lightblue}%t.","SF2 Prefix","Pending Vote"); + CPrintToChat(client,"{royalblue}%t {lightblue}%t.","SF2 Prefix","Pending Vote"); return Plugin_Handled; } @@ -342,7 +342,7 @@ Action Command_NextPack(int client,int args) { FormatEx(bossPackName, sizeof(bossPackName), "Core Pack"); } - CPrintToChat(client, "{dodgerblue}Next pack: {lightblue}%s", bossPackName); + CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Boss Pack Next", bossPackName); return Plugin_Handled; } diff --git a/addons/sourcemod/scripting/sf2/profiles/keymap.sp b/addons/sourcemod/scripting/sf2/profiles/keymap.sp new file mode 100644 index 00000000..58a54259 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/profiles/keymap.sp @@ -0,0 +1,901 @@ +#pragma semicolon 1 +#pragma newdecls required + +enum KeyMapKeyType +{ + Key_Type_Invalid = -1, + Key_Type_Value = 0, + Key_Type_Section +}; + +methodmap KeyMap < StringMap +{ + public KeyMap() + { + return view_as(new StringMap()); + } + + property StringMap Super + { + public get() + { + return view_as(this); + } + } + + property KeyMap Parent + { + public get() + { + KeyMap obj = null; + this.GetValue("__parent", obj); + return obj; + } + + public set(KeyMap val) + { + this.SetValue("__parent", val); + } + } + + property bool IsArray + { + public get() + { + bool value = false; + this.GetValue("__is_array", value); + return value; + } + + public set(bool value) + { + this.SetValue("__is_array", value); + } + } + + property int KeyLength + { + public get() + { + int value = 0; + this.GetValue("__key_length", value); + return value; + } + + public set(int value) + { + this.SetValue("__key_length", value); + } + } + + property int SectionLength + { + public get() + { + int value = 0; + this.GetValue("__section_length", value); + return value; + } + + public set(int value) + { + this.SetValue("__section_length", value); + } + } + + public KeyMapKeyType GetType(const char[] key) + { + int size = strlen(key) + 11 + 1; + char type[16]; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keytype", key); + if (!this.Super.GetString(formatter, type, sizeof(type))) + { + return Key_Type_Invalid; + } + if (strcmp(type, "value") == 0) + { + return Key_Type_Value; + } + else if (strcmp(type, "section") == 0) + { + return Key_Type_Section; + } + return Key_Type_Invalid; + } + + public void SetType(const char[] key, KeyMapKeyType value) + { + int size = strlen(key) + 11 + 1; + char type[16]; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keytype", key); + switch (value) + { + case Key_Type_Value: + { + strcopy(type, sizeof(type), "value"); + } + + case Key_Type_Section: + { + strcopy(type, sizeof(type), "section"); + } + + case Key_Type_Invalid: + { + strcopy(type, sizeof(type), "invalid"); + } + } + this.Super.SetString(formatter, type); + } + + public int GetInt(const char[] key, int def = -1) + { + char string[32]; + bool state = this.Super.GetString(key, string, sizeof(string)); + if (!state) + { + return def; + } + return StringToInt(string); + } + + public float GetFloat(const char[] key, float def = -1.0) + { + char string[32]; + bool state = this.Super.GetString(key, string, sizeof(string)); + if (!state) + { + return def; + } + return StringToFloat(string); + } + + public bool GetBool(const char[] key, bool def = false) + { + char string[32]; + bool state = this.Super.GetString(key, string, sizeof(string)); + if (!state) + { + return def; + } + return StringToBool(string); + } + + public void GetString(const char[] key, char[] buffer, int bufferSize, const char[] def = "") + { + bool status = this.Super.GetString(key, buffer, bufferSize); + if (!status) + { + strcopy(buffer, bufferSize, def); + } + } + + public KeyMap GetSection(const char[] key, KeyMap def = null) + { + if (this.GetType(key) != Key_Type_Section) + { + return def; + } + + KeyMap obj = def; + this.GetValue(key, obj); + return obj; + } + + public void SetKeyIndex(const char[] key, int value) + { + int size = strlen(key) + 16 + 1; + char[] formatter = new char[size]; + if (this.GetType(key) == Key_Type_Section) + { + FormatEx(formatter, size, "%i||__sectionindex", value); + } + else + { + FormatEx(formatter, size, "%i||__keyindex", value); + } + this.Super.SetString(formatter, key); + } + + public bool GetKeyNameFromIndex(int key, char[] buffer, int bufferSize) + { + char formatter[128]; + FormatEx(formatter, sizeof(formatter), "%i||__keyindex", key); + return this.Super.GetString(formatter, buffer, bufferSize); + } + + public bool GetSectionNameFromIndex(int key, char[] buffer, int bufferSize) + { + char formatter[128]; + FormatEx(formatter, sizeof(formatter), "%i||__sectionindex", key); + return this.Super.GetString(formatter, buffer, bufferSize); + } + + public void SetIndexKey(const char[] key, int value) + { + int size = strlen(key) + 21 + 1; + char[] formatter = new char[size]; + if (this.GetType(key) == Key_Type_Section) + { + FormatEx(formatter, size, "%s||__sectionindexvalue", key); + } + else + { + FormatEx(formatter, size, "%s||__keyindexvalue", key); + } + this.Super.SetValue(formatter, value); + } + + public int GetIndexFromKey(const char[] key) + { + int size = strlen(key) + 17 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keyindexvalue", key); + int value = -1; + this.Super.GetValue(formatter, value); + return value; + } + + public int GetIndexFromSection(const char[] key) + { + int size = strlen(key) + 21 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__sectionindexvalue", key); + int value = -1; + this.Super.GetValue(formatter, value); + return value; + } + + public void SetKeyValueLength(const char[] key, int value) + { + if (this.GetType(key) == Key_Type_Section) + { + return; + } + int size = strlen(key) + 12 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keyvaluesize", key); + this.Super.SetValue(formatter, value); + } + + public int GetKeyValueLength(const char[] key) + { + if (this.GetType(key) == Key_Type_Section) + { + return -1; + } + int size = strlen(key) + 12 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keyvaluesize", key); + this.Super.GetValue(formatter, size); + return size; + } + + public void SetKeyValue(const char[] key, const char[] value) + { + this.SetType(key, Key_Type_Value); + this.Super.SetString(key, value); + this.SetKeyIndex(key, this.KeyLength); + this.SetIndexKey(key, this.KeyLength); + this.KeyLength++; + this.SetKeyValueLength(key, strlen(value) + 1); + } + + public KeyMap SetKeySection(const char[] key) + { + KeyMap newValue = new KeyMap(); + this.SetSection(key, newValue); + this.SetType(key, Key_Type_Section); + newValue.SetSectionName(key); + newValue.Parent = this; + this.SetKeyIndex(key, this.SectionLength); + this.SetIndexKey(key, this.SectionLength); + this.SectionLength++; + + /* + KeyMap child = obj.GetSection(key); + if (child == null) + { + child = new KeyMap(); + obj.SetSection(key, child); + child.SetSectionName(key); + child.Parent = obj; + obj.SetKeyIndex(key, obj.SectionLength); + obj.SectionLength++; + } + + if (kv.GotoFirstSubKey(false)) + { + IterateKeyValues(kv, child); + kv.GoBack(); + } + } + else + { + obj.SetType(key, Key_Type_Value); + kv.GetString(NULL_STRING, value, sizeof(value)); + obj.SetString(key, value); + obj.SetKeyIndex(key, obj.KeyLength); + obj.KeyLength++; + } + */ + return newValue; + } + + public void RemoveKey(const char[] key) + { + int index = -1, currentIndex = -1, size; + if (this.GetType(key) == Key_Type_Section) + { + KeyMap section = this.GetSection(key); + index = this.GetIndexFromSection(key); + currentIndex = index + 1; + size = strlen(key) + 21 + 1; + char[] formatter3 = new char[size]; + FormatEx(formatter3, size, "%s||__sectionindexvalue", key); + this.Super.Remove(formatter3); + + while (index >= 0 && currentIndex < this.SectionLength) + { + char hiddenKey[512]; + this.GetSectionNameFromIndex(currentIndex, hiddenKey, sizeof(hiddenKey)); + this.SetKeyIndex(hiddenKey, currentIndex - 1); + this.SetIndexKey(hiddenKey, currentIndex - 1); + currentIndex++; + } + + CleanupKeyMap(section); + char formatter4[128]; + FormatEx(formatter4, sizeof(formatter4), "%i||__keyindex", currentIndex - 1); + this.Super.Remove(formatter4); + this.SectionLength--; + } + else + { + index = this.GetIndexFromKey(key); + currentIndex = index + 1; + size = strlen(key) + 17 + 1; + char[] formatter2 = new char[size]; + FormatEx(formatter2, size, "%s||__keyindexvalue", key); + this.Super.Remove(formatter2); + + char[] formatter3 = new char[size]; + FormatEx(formatter3, size, "%s||__keyvaluesize", key); + this.Super.Remove(formatter3); + while (index >= 0 && currentIndex < this.KeyLength) + { + char hiddenKey[512]; + this.GetKeyNameFromIndex(currentIndex, hiddenKey, sizeof(hiddenKey)); + this.SetKeyIndex(hiddenKey, currentIndex - 1); + this.SetIndexKey(hiddenKey, currentIndex - 1); + currentIndex++; + } + + char formatter4[128]; + FormatEx(formatter4, sizeof(formatter4), "%i||__keyindex", currentIndex - 1); + this.Super.Remove(formatter4); + this.KeyLength--; + } + + this.Super.Remove(key); + + size = strlen(key) + 11 + 1; + char[] formatter = new char[size]; + FormatEx(formatter, size, "%s||__keytype", key); + this.Super.Remove(formatter); + } + + public void RenameKey(const char[] key, const char[] newKey) + { + KeyMapKeyType type = this.GetType(key); + int index = 0; + if (type == Key_Type_Section) + { + KeyMap section = this.GetSection(key); + this.SetSection(newKey, section); + section.SetSectionName(newKey); + index = this.GetIndexFromSection(key); + } + else + { + this.SetType(newKey, Key_Type_Value); + int valueSize = this.GetKeyValueLength(key); + char[] value = new char[valueSize]; + this.Super.GetString(key, value, valueSize); + this.Super.SetString(newKey, value); + index = this.GetIndexFromKey(key); + } + this.RemoveKey(key); + + this.SetKeyIndex(newKey, index); + this.SetIndexKey(newKey, index); + } + + public void SetSection(const char[] key, KeyMap value) + { + this.Super.SetValue(key, value); + this.SetType(key, Key_Type_Section); + } + + public KeyMap Clone() + { + KeyMap clone = CloneKeyMap(this); + clone.Parent = null; + KeyMap section = clone.GetSection("execution"); + if (section != null) + { + section.GetSection("paths"); + } + return clone; + } + + public void GetSectionName(char[] buffer, int bufferSize) + { + this.GetString("__section_name", buffer, bufferSize); + } + + public bool SetSectionName(const char[] buffer) + { + return this.Super.SetString("__section_name", buffer); + } + + public bool SetInt(const char[] key, int value) + { + char stringValue[128]; + IntToString(value, stringValue, sizeof(stringValue)); + this.SetString(key, stringValue); + return false; + } + + public bool SetFloat(const char[] key, float value) + { + char stringValue[128]; + FloatToString(value, stringValue, sizeof(stringValue)); + this.SetString(key, stringValue); + return false; + } + + public bool SetBool(const char[] key, bool value) + { + char stringValue[6]; + BoolToString(value, stringValue, sizeof(stringValue)); + this.SetString(key, stringValue); + return false; + } + + public int IndexOfSection(KeyMap value) + { + int length = this.Size; + for (int i = 0; i < length; i++) + { + char keyName[512]; + this.GetKeyNameFromIndex(i, keyName, sizeof(keyName)); + if (this.GetType(keyName) != Key_Type_Section) + { + continue; + } + + KeyMap current = this.GetSection(keyName); + if (value == current) + { + return i; + } + } + + return -1; + } + + public int IndexOfString(const char[] value) + { + int length = this.Size; + for (int i = 0; i < length; i++) + { + char keyName[512]; + this.GetKeyNameFromIndex(i, keyName, sizeof(keyName)); + if (this.GetType(keyName) != Key_Type_Value) + { + continue; + } + + char current[512]; + this.GetString(keyName, current, sizeof(current)); + if (strcmp(value, current) == 0) + { + return i; + } + } + + return -1; + } + + public bool ContainsString(const char[] value) + { + return this.IndexOfString(value) != -1; + } + + public void ConvertValuesSectionToArray(const char[] name) + { + KeyMap obj = this.GetSection(name); + if (obj == null) + { + return; + } + + KeyMap_Array array = new KeyMap_Array(Key_Type_Value); + + for (int i = 0; i < obj.KeyLength; i++) + { + char key[512]; + obj.GetKeyNameFromIndex(i, key, sizeof(key)); + + KeyMapKeyType type = obj.GetType(key); + if (!IsHiddenKey(key) && type == Key_Type_Value) + { + int size = obj.GetKeyValueLength(key); + char[] value = new char[size]; + obj.GetString(key, value, size); + array.PushString(value); + } + } + + delete obj; + + this.Remove(name); + this.SetSection(name, array); + array.SetSectionName(name); + array.Parent = this; + } + + public void ConvertSectionsSectionToArray(const char[] name) + { + KeyMap obj = this.GetSection(name); + if (obj == null) + { + return; + } + + KeyMap_Array array = new KeyMap_Array(Key_Type_Section); + + for (int i = 0; i < obj.SectionLength; i++) + { + char key[512]; + obj.GetSectionNameFromIndex(i, key, sizeof(key)); + + KeyMap section = obj.GetSection(key); + if (!IsHiddenKey(key) && section != null) + { + array.PushSection(section); + } + } + + delete obj; + + this.Remove(name); + this.SetSection(name, array); + array.SetSectionName(name); + array.Parent = this; + } +} + +methodmap KeyMap_Array < KeyMap +{ + public KeyMap_Array(KeyMapKeyType type = Key_Type_Invalid) + { + KeyMap_Array self = view_as(new KeyMap()); + self.IsArray = true; + self.SetType(type); + + return self; + } + + property KeyMap Super + { + public get() + { + return view_as(this); + } + } + + property int Length + { + public get() + { + return this.Super.GetInt("__length", 0); + } + + public set(int value) + { + this.Super.SetInt("__length", value); + } + } + + public bool CanUseType(KeyMapKeyType type) + { + return this.GetType() == Key_Type_Invalid || this.GetType() == type; + } + + public KeyMapKeyType GetType() + { + char key[64], type[16], formatter[128]; + this.GetSectionName(key, sizeof(key)); + FormatEx(formatter, sizeof(formatter), "%s||__arraytype", key); + if (this.Super.Super.GetString(formatter, type, sizeof(type))) + { + return Key_Type_Invalid; + } + if (strcmp(type, "value") == 0) + { + return Key_Type_Value; + } + else if (strcmp(type, "section") == 0) + { + return Key_Type_Section; + } + return Key_Type_Invalid; + } + + public void SetType(KeyMapKeyType value) + { + char key[64], type[16], formatter[128]; + this.GetSectionName(key, sizeof(key)); + FormatEx(formatter, sizeof(formatter), "%s||__arraytype", key); + switch (value) + { + case Key_Type_Value: + { + strcopy(type, sizeof(type), "value"); + } + + case Key_Type_Section: + { + strcopy(type, sizeof(type), "section"); + } + + case Key_Type_Invalid: + { + strcopy(type, sizeof(type), "invalid"); + } + } + this.Super.Super.SetString(formatter, type); + } + + public void SetKeyType(int key, KeyMapKeyType value) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + this.Super.SetType(keyName, value); + } + + public void GetString(int key, char[] buffer, int bufferSize, const char[] def = "") + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + this.Super.GetString(keyName, buffer, bufferSize, def); + } + + public int GetInt(int key, int def = -1) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetInt(keyName, def); + } + + public float GetFloat(int key, float def = -1.0) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetFloat(keyName, def); + } + + public bool GetBool(int key, bool def = false) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetBool(keyName, def); + } + + public KeyMap GetSection(int key, KeyMap def = null) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetSection(keyName, def); + } + + public KeyMapKeyType GetTypeFromKey(int key) + { + char keyName[16]; + IntToString(key, keyName, sizeof(keyName)); + + return this.Super.GetType(keyName); + } + + public bool SetString(int index, const char[] value) + { + if (!this.CanUseType(Key_Type_Value)) + { + return false; + } + + char key[16]; + IntToString(index, key, sizeof(key)); + + return this.Super.SetString(key, value); + } + + public bool SetSection(int index, KeyMap value) + { + if (!this.CanUseType(Key_Type_Section)) + { + return false; + } + + char key[16]; + IntToString(index, key, sizeof(key)); + + return this.Super.SetValue(key, value); + } + + public int PushString(const char[] value) + { + int index = this.Length; + this.SetKeyType(index, Key_Type_Value); + if (!this.SetString(index, value)) + { + return -1; + } + this.Length++; + return index; + } + + public int PushSection(KeyMap value) + { + int index = this.Length; + this.SetKeyType(index, Key_Type_Section); + if (!this.SetSection(index, value)) + { + return -1; + } + this.Length++; + return index; + } + + public int IndexOf(const char[] value) + { + for (int i = 0; i < this.Length; i++) + { + if (this.GetTypeFromKey(i) != Key_Type_Value) + { + continue; + } + + char current[128]; + this.GetString(i, current, sizeof(current)); + if (strcmp(value, current) == 0) + { + return i; + } + } + + return -1; + } +} + +KeyMap KeyValuesToKeyMap(KeyValues kv, KeyMap obj = null) +{ + if (obj == null) + { + obj = new KeyMap(); + } + + if (kv.GotoFirstSubKey(false)) + { + IterateKeyValues(kv, obj); + kv.GoBack(); + } + + return obj; +} + +static void IterateKeyValues(KeyValues kv, KeyMap obj) +{ + char key[512], value[2048]; + + do + { + kv.GetSectionName(key, sizeof(key)); + + int keyLen = strlen(key); + for (int i = 0; i < keyLen; i++) + { + key[i] = CharToLower(key[i]); + } + + bool isSection = ((kv.NodesInStack() == 0) || (kv.GetDataType(NULL_STRING) == KvData_None && kv.NodesInStack() > 0)); + + if (isSection) + { + KeyMap child = obj.GetSection(key); + if (child == null) + { + child = obj.SetKeySection(key); + } + + if (kv.GotoFirstSubKey(false)) + { + IterateKeyValues(kv, child); + kv.GoBack(); + } + } + else + { + kv.GetString(NULL_STRING, value, sizeof(value)); + obj.SetKeyValue(key, value); + } + } + while (kv.GotoNextKey(false)); +} + +static KeyMap CloneKeyMap(KeyMap obj) +{ + if (obj == null) + { + return null; + } + + StringMap temp = view_as(obj).Clone(); + KeyMap clone = view_as(temp); + + for (int i = 0; i < clone.SectionLength; i++) + { + char key[512]; + clone.GetSectionNameFromIndex(i, key, sizeof(key)); + + KeyMap value = clone.GetSection(key); + if (value != null) + { + value = CloneKeyMap(value); + clone.SetSection(key, value); + value.Parent = clone; + } + } + + return clone; +} + +bool IsHiddenKey(const char[] key) +{ + return !strncmp(key, "__", 2); +} + +void CleanupKeyMap(KeyMap obj) +{ + if (obj == null) + { + return; + } + + StringMapSnapshot snapshot = obj.Snapshot(); + for (int i = 0; i < snapshot.Length; i += 1) + { + char key[512]; + snapshot.GetKey(i, key, sizeof(key)); + + KeyMap value = obj.GetSection(key); + if (value != null) + { + CleanupKeyMap(value); + } + } + + delete snapshot; + + delete obj; +} diff --git a/addons/sourcemod/scripting/sf2/profiles/objects.sp b/addons/sourcemod/scripting/sf2/profiles/objects.sp new file mode 100644 index 00000000..07d8a873 --- /dev/null +++ b/addons/sourcemod/scripting/sf2/profiles/objects.sp @@ -0,0 +1,2964 @@ +#pragma semicolon 1 +#pragma newdecls required + +methodmap ProfileObject < KeyMap +{ + property KeyMap Super + { + public get() + { + return view_as(this); + } + } + + public bool ContainsDifficultyKey(const char[] key, int difficulty) + { + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + return this.ContainsKey(diffKey); + } + + public bool ContainsAnyKey(ArrayList keys) + { + if (keys == null) + { + return false; + } + for (int i = 0; i < keys.Length; i++) + { + char key[256]; + keys.GetString(i, key, sizeof(key)); + if (this.ContainsKey(key)) + { + return true; + } + } + return false; + } + + public bool ContainsAnyDifficultyKey(ArrayList keys, int specified = -1) + { + if (keys == null) + { + return false; + } + if (specified == -1) + { + for (int i = 0; i < keys.Length; i++) + { + char key[256]; + keys.GetString(i, key, sizeof(key)); + for (int i2 = 0; i2 < Difficulty_Max; i2++) + { + if (this.ContainsDifficultyKey(key, i2)) + { + return true; + } + } + } + } + else + { + for (int i = 0; i < keys.Length; i++) + { + char key[256]; + keys.GetString(i, key, sizeof(key)); + if (this.ContainsDifficultyKey(key, specified)) + { + return true; + } + } + } + return false; + } + + public void RemoveDifficultyKey(const char[] key, int difficulty) + { + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.RemoveKey(diffKey); + } + + public ProfileObject GetSection(const char[] key, ProfileObject def = null) + { + if (this == null) + { + return def; + } + + ProfileObject obj = view_as(this.Super.GetSection(key)); + if (obj == null) + { + return def; + } + + return obj; + } + + public bool GetRandomValues(const char[] key, float buffer[2]) + { + if (!this.ContainsKey(key)) + { + return false; + } + + char string[64]; + if (!view_as(this).GetString(key, string, sizeof(string))) + { + return false; + } + + int contains = StrContains(string, "(val)) != 0) == def) + { + val = this.Super.GetBool(key, def); + } + return val; + } + + public void SetBool(const char[] key, bool value, bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + this.Super.SetBool(key, value); + } + + public void GetString(const char[] key, char[] buffer, int bufferSize, const char[] def = "") + { + if (this == null) + { + strcopy(buffer, bufferSize, def); + return; + } + + this.Super.GetString(key, buffer, bufferSize, def); + } + + public bool SetString(const char[] key, const char[] value, bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + this.Super.SetString(key, value); + } + + public void GetVector(const char[] key, float buffer[3], const float def[3] = { 0.0, 0.0, 0.0 }) + { + if (this == null) + { + buffer = def; + return; + } + + if (!this.ContainsKey(key)) + { + buffer = def; + return; + } + + char vecString[64]; + this.GetString(key, vecString, sizeof(vecString)); + StringToVector(vecString, buffer); + } + + public void SetVector(const char[] key, float value[3], bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + char vecString[64]; + VectorToString(value, vecString, sizeof(vecString)); + this.SetString(key, vecString); + } + + public void GetColor(const char[] key, int buffer[4], const int def[4] = { 255, 255, 255, 255 }) + { + if (this == null) + { + buffer = def; + return; + } + + if (!this.ContainsKey(key)) + { + buffer = def; + return; + } + + char vecString[64]; + this.GetString(key, vecString, sizeof(vecString)); + StringToColor(vecString, buffer); + } + + public void SetColor(const char[] key, int value[4], bool check = false) + { + if (check && this.ContainsString(key)) + { + return; + } + char vecString[64]; + ColorToString(value, vecString, sizeof(vecString)); + this.SetString(key, vecString); + } + + public KeyMap_Array GetArray(const char[] key, KeyMap_Array def = view_as(INVALID_HANDLE)) + { + if (this.GetType(key) != Key_Type_Section) + { + return def; + } + + KeyMap_Array value = view_as(this.Super.GetSection(key)); + if (value == null || !value.IsArray) + { + return def; + } + + return value; + } + + public ProfileObject GetDifficultySection(const char[] key, int difficulty, ProfileObject def = null) + { + if (this == null) + { + return def; + } + ProfileObject value = def; + + if (this.ContainsKey(key)) + { + value = this.GetSection(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetSection(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetSection(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetSection(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultySection(const char[] key, int difficulty, ProfileObject value) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetSection(diffKey, value); + } + + public int GetDifficultyInt(const char[] key, int difficulty, int def = 0) + { + if (this == null) + { + return def; + } + int value = def; + + if (this.ContainsKey(key)) + { + value = this.GetInt(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetInt(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetInt(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetInt(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultyInt(const char[] key, int difficulty, int value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetInt(diffKey, value, check); + } + + public float GetDifficultyFloat(const char[] key, int difficulty, float def = 0.0) + { + if (this == null) + { + return def; + } + float value = def; + + if (this.ContainsKey(key)) + { + value = this.GetFloat(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetFloat(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetFloat(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetFloat(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultyFloat(const char[] key, int difficulty, float value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetFloat(diffKey, value, check); + } + + public bool GetDifficultyBool(const char[] key, int difficulty, bool def = false) + { + if (this == null) + { + return def; + } + bool value = def; + + if (this.ContainsKey(key)) + { + value = this.GetBool(key, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetBool(diffKey, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetBool(diffKey, def); + return value; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + value = this.GetBool(diffKey, def); + return value; + } + } + } + } + + return value; + } + + public void SetDifficultyBool(const char[] key, int difficulty, bool value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetBool(diffKey, value, check); + } + + public void GetDifficultyVector(const char[] key, int difficulty, float buffer[3], const float def[3] = { 0.0, 0.0, 0.0 }) + { + buffer = def; + + if (this.ContainsKey(key)) + { + this.GetVector(key, buffer, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetVector(diffKey, buffer, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetVector(diffKey, buffer, def); + return; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetVector(diffKey, buffer, def); + return; + } + } + } + } + } + + public void SetDifficultyVector(const char[] key, int difficulty, float value[3], bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetVector(diffKey, value, check); + } + + public void GetDifficultyColor(const char[] key, int difficulty, int buffer[4], const int def[4] = { 255, 255, 255, 255 }) + { + buffer = def; + + if (this.ContainsKey(key)) + { + this.GetColor(key, buffer, def); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetColor(diffKey, buffer, def); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetColor(diffKey, buffer, def); + return; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetColor(diffKey, buffer, def); + return; + } + } + } + } + } + + public void SetDifficultyColor(const char[] key, int difficulty, int value[4], bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetColor(diffKey, value, check); + } + + public void GetDifficultyString(const char[] key, int difficulty, char[] buffer, int bufferSize, const char[] def = "") + { + strcopy(buffer, bufferSize, def); + + if (this.ContainsKey(key)) + { + this.GetString(key, buffer, bufferSize); + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetString(diffKey, buffer, bufferSize, buffer); + } + else + { + if (difficulty > 0) + { + for (int i2 = difficulty; i2 >= 0; i2--) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetString(diffKey, buffer, bufferSize, buffer); + return; + } + } + } + else + { + for (int i2 = 0; i2 < difficulty; i2++) + { + GetProfileKeyWithDifficultySuffix(key, i2, diffKey, diffKeySize); + if (this.ContainsKey(diffKey)) + { + this.GetString(diffKey, buffer, bufferSize, buffer); + return; + } + } + } + } + } + + public void SetDifficultyString(const char[] key, int difficulty, const char[] value, bool check = false) + { + if (difficulty < Difficulty_Easy || difficulty >= Difficulty_Max) + { + return; + } + + int diffKeySize = strlen(key) + GetMaxProfileDifficultySuffixSize(); + char[] diffKey = new char[diffKeySize]; + GetProfileKeyWithDifficultySuffix(key, difficulty, diffKey, diffKeySize); + this.SetString(diffKey, value, check); + } + + public ProfileObject InsertNewSection(const char[] name) + { + if (this.GetSection(name) != null) + { + return this.GetSection(name); + } + ProfileObject newObj = view_as(new KeyMap()); + this.SetType(name, Key_Type_Section); + this.SetSection(name, newObj); + newObj.SetSectionName(name); + newObj.Parent = this; + this.SetKeyIndex(name, this.SectionLength); + this.SectionLength++; + return newObj; + } + + public void AddExistingSection(ProfileObject newObj) + { + if (newObj == null) + { + return; + } + char sectionName[512]; + newObj.GetSectionName(sectionName, sizeof(sectionName)); + this.SetSection(sectionName, newObj); + this.SetType(sectionName, Key_Type_Section); + newObj.Parent = this; + this.SetKeyIndex(sectionName, this.SectionLength); + this.SetIndexKey(sectionName, this.SectionLength); + this.SectionLength++; + } + + public void TransferKey(ProfileObject origin, const char[] oldKey, const char[] newKey) + { + char keyValue[2048]; + if (!origin.Super.Super.GetString(oldKey, keyValue, sizeof(keyValue))) + { + return; + } + this.SetKeyValue(newKey, keyValue); + } + + public void TransferDifficultyKey(ProfileObject target, const char[] oldKey, const char[] newKey, int difficulty) + { + int oldDiffKeySize = strlen(oldKey) + GetMaxProfileDifficultySuffixSize(); + char[] oldDiffKey = new char[oldDiffKeySize]; + GetProfileKeyWithDifficultySuffix(oldKey, difficulty, oldDiffKey, oldDiffKeySize); + + int newDiffKeySize = strlen(newKey) + GetMaxProfileDifficultySuffixSize(); + char[] newDiffKey = new char[newDiffKeySize]; + GetProfileKeyWithDifficultySuffix(newKey, difficulty, newDiffKey, newDiffKeySize); + + char keyValue[2048]; + if (!this.Super.GetString(oldDiffKey, keyValue, sizeof(keyValue))) + { + return; + } + target.SetKeyValue(newDiffKey, keyValue); + } +} + +methodmap ProfileSound < ProfileObject +{ + property KeyMap_Array Paths + { + public get() + { + return this.GetArray("paths"); + } + } + + public int GetChannel(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("channel", difficulty, this.Super.GetInt("__channel", SNDCHAN_AUTO)); + } + + public float GetVolume(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("volume", difficulty, SNDVOL_NORMAL); + } + + public int GetFlags(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("flags", difficulty, this.Super.GetInt("__flags", SND_NOFLAGS)); + } + + public int GetLevel(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("level", difficulty, SNDLEVEL_SCREAMING); + } + + public int GetPitch(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("pitch", difficulty, SNDPITCH_NORMAL); + } + + public float GetCooldownMin(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("cooldown_min", difficulty, this.Super.GetFloat("__cooldown_min", 1.5)); + } + + public float GetCooldownMax(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("cooldown_max", difficulty, this.Super.GetFloat("__cooldown_max", 1.5)); + } + + public float GetHurtCooldownMin(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("hurt_cooldown_min", difficulty, this.Super.GetFloat("__cooldown_min", 1.5)); + } + + public float GetHurtCooldownMax(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("hurt_cooldown_max", difficulty, this.Super.GetFloat("__cooldown_max", 1.5)); + } + + public int GetPitchRandomMin(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("pitch_random_min", difficulty, this.GetPitch(difficulty)); + } + + public int GetPitchRandomMax(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyInt("pitch_random_max", difficulty, this.GetPitch(difficulty)); + } + + public float GetChance(int difficulty = Difficulty_Normal) + { + return this.GetDifficultyFloat("chance", difficulty, 1.0); + } + + public void SetDefaultChannel(int channel) + { + this.SetInt("__channel", channel); + } + + public void SetDefaultFlags(int flags) + { + this.SetInt("__flags", flags); + } + + public void SetDefaultCooldownMin(float cooldown) + { + this.SetFloat("__cooldown_min", cooldown); + } + + public void SetDefaultCooldownMax(float cooldown) + { + this.SetFloat("__cooldown_max", cooldown); + } + + public void SetDefaultLevel(int level) + { + this.SetInt("__level", level); + } + + public void Precache() + { + KeyMap obj = this.GetSection("paths"); + if (obj == null) + { + obj = this; + } + bool doDelete = this.GetSection("paths") != null; + + KeyMap_Array paths = new KeyMap_Array(Key_Type_Value); + char path[PLATFORM_MAX_PATH]; + for (int i2 = 1;; i2++) + { + char num[16]; + IntToString(i2, num, sizeof(num)); + obj.GetString(num, path, sizeof(path)); + if (path[0] == '\0') + { + break; + } + + TryPrecacheBossProfileSoundPath(path, g_FileCheckConVar.BoolValue); + paths.PushString(path); + obj.RemoveKey(num); + } + if (doDelete) + { + this.RemoveKey("paths"); + } + this.SetType("paths", Key_Type_Section); + this.SetSection("paths", paths); + paths.SetSectionName("paths"); + paths.Parent = this; + + ProfileObject parent = view_as(this.Parent); + char section[64], formatter[90]; + this.GetSectionName(section, sizeof(section)); + for (int i = 0; i < Difficulty_Max; i++) + { + FormatEx(formatter, sizeof(formatter), "%s_channel", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("channel", i, parent.GetDifficultyInt(formatter, i, this.Super.GetInt("__channel", SNDCHAN_AUTO))); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_volume", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyFloat("volume", i, parent.GetDifficultyFloat(formatter, i, SNDVOL_NORMAL)); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_flags", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("flags", i, parent.GetDifficultyInt(formatter, i, this.Super.GetInt("__flags", SND_NOFLAGS))); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_level", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("level", i, parent.GetDifficultyInt(formatter, i, SNDLEVEL_SCREAMING)); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_pitch", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyInt("pitch", i, parent.GetDifficultyInt(formatter, i, SNDPITCH_NORMAL)); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_cooldown_min", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyFloat("cooldown_min", i, parent.GetDifficultyFloat(formatter, i, this.Super.GetFloat("__cooldown_min", 1.5))); + parent.RemoveDifficultyKey(formatter, i); + } + + FormatEx(formatter, sizeof(formatter), "%s_cooldown_max", section); + if (parent.ContainsDifficultyKey(formatter, i)) + { + this.SetDifficultyFloat("cooldown_max", i, parent.GetDifficultyFloat(formatter, i, this.Super.GetFloat("__cooldown_max", 1.5))); + parent.RemoveDifficultyKey(formatter, i); + } + } + } + + public void StopAllSounds(int entity, int difficulty = Difficulty_Normal) + { + if (this == null) + { + return; + } + + KeyMap_Array paths = this.Paths; + if (paths == null) + { + return; + } + + char path[PLATFORM_MAX_PATH]; + for (int i = 0; i < paths.Length; i++) + { + paths.GetString(i, path, sizeof(path)); + if (path[0] == '\0') + { + continue; + } + + if (StrContains(path, ".mp3", true) == -1 && StrContains(path, ".wav", true) == -1) + { + continue; + } + + StopSound(entity, this.GetChannel(difficulty), path); + } + } + + public bool EmitSound(bool toClient = false, int entity = -1, int emitter = SOUND_FROM_PLAYER, float origin[3] = NULL_VECTOR, int addPitch = 0, char returnSound[PLATFORM_MAX_PATH] = "", int definedIndex = -1, int difficulty = Difficulty_Normal, float fixedChance = -1.0) + { + if (this == null) + { + return false; + } + + KeyMap_Array paths = this.Paths; + if (paths == null) + { + return false; + } + + char path[PLATFORM_MAX_PATH]; + if (definedIndex != -1) + { + paths.GetString(definedIndex, path, sizeof(path)); + } + else + { + paths.GetString(GetRandomInt(0, paths.Length - 1), path, sizeof(path)); + } + + returnSound = path; + + if (path[0] == '\0') + { + return false; + } + + float threshold = GetRandomFloat(0.0, 1.0); + if (fixedChance >= 0.0) + { + threshold = fixedChance; + } + if (threshold > this.GetChance(difficulty)) + { + return false; + } + + if (StrContains(path, ".mp3", true) == -1 && StrContains(path, ".wav", true) == -1) + { + if (!toClient) + { + EmitGameSoundToAll(path, entity, this.GetFlags(difficulty)); + } + else + { + EmitGameSoundToClient(entity, path, emitter, this.GetFlags(difficulty)); + } + return true; + } + + int pitch = GetRandomInt(this.GetPitchRandomMin(difficulty), this.GetPitchRandomMax(difficulty)); + int volumeCount = RoundToCeil(this.GetVolume(difficulty)); + for (int i = 0; i < volumeCount; i++) + { + float finalVolume = this.GetVolume(difficulty); + if (i + 1 != volumeCount) + { + if (!toClient) + { + EmitSoundToAll(path, entity, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), 1.0, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + else + { + EmitSoundToClient(entity, path, emitter, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), 1.0, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + } + else + { + if (finalVolume > 1.0) + { + while (finalVolume > 1.0) + { + finalVolume -= 1.0; + } + if (!toClient) + { + EmitSoundToAll(path, entity, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), finalVolume, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + else + { + EmitSoundToClient(entity, path, emitter, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), finalVolume, + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + } + else + { + if (!toClient) + { + EmitSoundToAll(path, entity, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), this.GetVolume(difficulty), + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + else + { + EmitSoundToClient(entity, path, emitter, this.GetChannel(difficulty), this.GetLevel(difficulty), this.GetFlags(difficulty), this.GetVolume(difficulty), + ((this.GetPitchRandomMin(difficulty) == this.GetPitch(difficulty) && this.GetPitchRandomMax(difficulty) == this.GetPitch(difficulty)) ? this.GetPitch(difficulty) : pitch) + addPitch, + _, origin); + } + } + } + } + return true; + } + + public bool EmitToAllPlayers() + { + float threshold = GetRandomFloat(0.0, 1.0); + bool ret = true; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsValidClient(i)) + { + continue; + } + bool value = this.EmitSound(true, i, .fixedChance = threshold); + if (!value) + { + ret = false; + } + } + return ret; + } +} + +methodmap ProfileAnimation < ProfileObject // This covers each animation index in each animation section +{ + public void GetAnimationName(int difficulty, char[] buffer, int bufferSize, bool legacy = false, const char[] legacyName = "") + { + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s", legacyName); + this.GetDifficultyString(formatter, difficulty, buffer, bufferSize); + if (!legacy) + { + this.GetDifficultyString("name", difficulty, buffer, bufferSize); + } + } + + public void GetGestureName(int difficulty, char[] buffer, int bufferSize, bool legacy = false) + { + if (legacy) + { + return; + } + this.GetDifficultyString("gesture_name", difficulty, buffer, bufferSize); + } + + public float GetAnimationPlaybackRate(int difficulty, bool legacy = false, const char[] legacyName = "") + { + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s_playbackrate", legacyName); + float val = this.GetDifficultyFloat(formatter, difficulty, 1.0); + if (!legacy) + { + val = this.GetDifficultyFloat("playbackrate", difficulty, val); + } + if (val < -12.0) + { + val = -12.0; + } + else if (val > 12.0) + { + val = 12.0; + } + + return val; + } + + public float GetGesturePlaybackRate(int difficulty, bool legacy = false) + { + if (legacy) + { + return 1.0; + } + float val = this.GetDifficultyFloat("gesture_playbackrate", difficulty, 1.0); + if (val < -12.0) + { + val = -12.0; + } + else if (val > 12.0) + { + val = 12.0; + } + + return val; + } + + public float GetDuration(int difficulty, bool legacy = false) + { + if (legacy) + { + return 0.0; + } + return this.GetDifficultyFloat("duration", difficulty); + } + + public float GetAnimationCycle(int difficulty, bool legacy = false) + { + if (legacy) + { + return 0.0; + } + return this.GetDifficultyFloat("cycle", difficulty); + } + + public float GetGestureCycle(int difficulty, bool legacy = false) + { + if (legacy) + { + return 0.0; + } + return this.GetDifficultyFloat("gesture_cycle", difficulty); + } + + public float GetFootstepInterval(int difficulty, bool legacy = false, const char[] legacyName = "") + { + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s_footstepinterval", legacyName); + float val = this.GetDifficultyFloat(formatter, difficulty, 0.0); + if (!legacy) + { + val = this.GetDifficultyFloat("footstepinterval", difficulty, val); + } + return val; + } + + public bool CanOverrideLoop(int difficulty, bool legacy = false) + { + if (legacy) + { + return false; + } + return this.GetDifficultyBool("override_loop", difficulty); + } + + public bool GetLoopState(int difficulty, bool legacy = false) + { + if (legacy) + { + return false; + } + return this.GetDifficultyBool("loop", difficulty); + } + + public bool ShouldSyncWithGround(int difficulty, bool legacy = false) + { + if (legacy) + { + return false; + } + return this.GetDifficultyBool("ground_sync", difficulty); + } + + public bool PlayAnimation(CBaseAnimating actor, int difficulty, bool loops = false) + { + int sequence; + char animation[64]; + this.GetAnimationName(difficulty, animation, sizeof(animation)); + + sequence = LookupProfileAnimation(actor.index, animation); + + if (sequence == -1) + { + return false; + } + + if (sequence != actor.GetProp(Prop_Send, "m_nSequence")) + { + actor.ResetSequence(sequence); + } + actor.SetPropFloat(Prop_Send, "m_flCycle", this.GetAnimationCycle(difficulty)); + actor.SetPropFloat(Prop_Send, "m_flPlaybackRate", this.GetAnimationPlaybackRate(difficulty)); + actor.SetProp(Prop_Data, "m_bSequenceLoops", loops); + + return true; + } + + public bool PlayGesture(CBaseAnimatingOverlay actor, int difficulty, bool loops = false, int& layer = -1) + { + int sequence; + char animation[64]; + this.GetGestureName(difficulty, animation, sizeof(animation)); + + sequence = LookupProfileAnimation(actor.index, animation); + + if (sequence == -1) + { + return false; + } + + layer = actor.AddLayeredSequence(sequence, 1); + if (layer == -1) + { + return false; + } + + actor.SetLayerCycle(layer, this.GetGestureCycle(difficulty)); + actor.SetLayerPlaybackRate(layer, this.GetGesturePlaybackRate(difficulty)); + actor.SetLayerLooping(layer, loops); + actor.SetLayerAutokill(layer, true); + + return true; + } +} + +methodmap ProfileMasterAnimations < ProfileObject // This covers the whole "animations" section +{ + property bool IsLegacy + { + public get() + { + char section[64]; + this.GetSectionName(section, sizeof(section)); + return strcmp(section, "animations") != 0; + } + } + + public bool HasAnimationSection(const char[] animType, bool checkLegacy = false) + { + if (this.IsLegacy) + { + if (checkLegacy) + { + if (strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Smell]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Rage]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_ChaseInitial]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Spawn]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_FleeInitial]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Heal]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_DeathCam]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Death]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_TauntKill]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_AttackBegin]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_AttackEnd]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_ProjectileShoot]) == 0 || + strcmp(animType, g_SlenderAnimationsList[SF2BossAnimation_Despawn]) == 0) + { + return false; + } + } + char formatter[64]; + FormatEx(formatter, sizeof(formatter), "animation_%s", animType); + return this.ContainsKey(formatter); + } + return this.GetSection(animType) != null; + } + + public ProfileAnimation GetAnimation(const char[] animType, int preDefinedIndex = -1, const char[] preDefinedName = "", int& index = -1) + { + if (this.IsLegacy) + { + if (!this.HasAnimationSection(animType)) + { + return null; + } + ProfileAnimation animObj = view_as(this); + return animObj; + } + + ProfileObject obj = this.GetSection(animType); + if (obj == null || obj.Size <= 0) + { + return null; + } + + ArrayList valids = new ArrayList(ByteCountToCells(64)); + for (int i = 0; i < obj.SectionLength; i++) + { + char key[64]; + obj.GetSectionNameFromIndex(i, key, sizeof(key)); + ProfileAnimation animObj = view_as(obj.GetSection(key)); + if (animObj != null) + { + valids.PushString(key); + } + } + + if (preDefinedIndex > -1) + { + if (preDefinedIndex >= valids.Length) + { + preDefinedIndex = valids.Length - 1; + } + index = preDefinedIndex; + } + else + { + index = GetRandomInt(0, valids.Length - 1); + } + + ProfileAnimation animObj; + if (preDefinedName[0] != '\0') + { + animObj = view_as(obj.GetSection(preDefinedName)); + } + else + { + char animName[64]; + valids.GetString(index, animName, sizeof(animName)); + animObj = view_as(obj.GetSection(animName)); + } + + delete valids; + + if (animObj == null) + { + return null; + } + + return animObj; + } +} + +methodmap ProfileEntityInputObject < ProfileObject +{ + public void GetInput(char[] buffer, int bufferSize) + { + this.GetString("input", buffer, bufferSize); + } + + public void GetParameter(char[] buffer, int bufferSize) + { + this.GetString("parameter", buffer, bufferSize); + } + + public int GetParameterSize() + { + return this.GetKeyValueLength("parameter"); + } + + public bool AcceptInput(int entity, int activator = -1, int caller = -1) + { + char input[64]; + this.GetInput(input, sizeof(input)); + + bool result = false; + + int size = this.GetParameterSize(); + char[] parameter = new char[size]; + this.GetParameter(parameter, size); + + SetVariantString(parameter); + result = AcceptEntityInput(entity, input, activator, caller); + + return result; + } +} + +methodmap ProfileEntityInputsArray < ProfileObject +{ + public void AcceptInputs(int entity, int activator = -1, int caller = -1) + { + for (int i = 0; i < this.SectionLength; i++) + { + char section[64]; + this.GetSectionNameFromIndex(i, section, sizeof(section)); + ProfileEntityInputObject output = view_as(this.GetSection(section)); + if (output == null) + { + continue; + } + + output.AcceptInput(entity, activator, caller); + } + } +} + +methodmap ProfileEntityOutputObject < ProfileObject +{ + public void GetTarget(char[] buffer, int bufferSize) + { + this.GetString("target", buffer, bufferSize); + } + + public int GetTargetSize() + { + return this.GetKeyValueLength("target"); + } + + public void GetInput(char[] buffer, int bufferSize) + { + this.GetString("input", buffer, bufferSize); + } + + public int GetInputSize() + { + return this.GetKeyValueLength("input"); + } + + public void GetParameter(char[] buffer, int bufferSize) + { + this.GetString("parameter", buffer, bufferSize); + } + + public int GetParameterSize() + { + return this.GetKeyValueLength("parameter"); + } + + public float GetDelay() + { + return this.GetFloat("delay"); + } + + public int GetTimesToFire() + { + return this.GetInt("times_to_fire", -1); + } + + public void AddOutput(int entity, const char[] output) + { + int size = this.GetTargetSize(); + char[] target = new char[size]; + this.GetTarget(target, size); + + size = this.GetInputSize(); + char[] input = new char[size]; + this.GetInput(input, size); + + size = this.GetParameterSize(); + char[] parameter = new char[size]; + this.GetParameter(parameter, size); + + size = 98 + strlen(output) + strlen(target) + strlen(input) + strlen(parameter); + char[] addOutput = new char[size]; + FormatEx(addOutput, size, "EntityOutputs.AddOutput(self, `%s`, `%s`, `%s`, `%s`, %0.3f, %d)", output, target, input, parameter, this.GetDelay(), this.GetTimesToFire()); + + PrintToChatAll("%s", addOutput); + + SetVariantString(addOutput); + AcceptEntityInput(entity, "RunScriptCode"); + } +} + +methodmap ProfileEntityOutputsArray < ProfileObject +{ + public void AddOutputs(int entity) + { + for (int i = 0; i < this.SectionLength; i++) + { + char section[64]; + this.GetSectionNameFromIndex(i, section, sizeof(section)); + ProfileObject obj = this.GetSection(section); + if (obj == null) + { + continue; + } + + for (int i2 = 0; i2 < obj.SectionLength; i2++) + { + char subSection[64]; + obj.GetSectionNameFromIndex(i2, subSection, sizeof(subSection)); + ProfileEntityOutputObject output = view_as(obj.GetSection(subSection)); + if (output == null) + { + continue; + } + + output.AddOutput(entity, section); + } + } + } +} + +int ColorToString(int buffer[4], char[] string, int maxlength) +{ + char value[32], num[4]; + for (int i = 0; i < 4; i++) + { + IntToString(buffer[i], num, sizeof(num)); + StrCat(value, sizeof(value), num); + if (i < 3) + { + StrCat(value, sizeof(value), " "); + } + } + return strcopy(string, maxlength, value); +} + +int VectorToString(float buffer[3], char[] string, int maxlength) +{ + char value[64], num[16]; + for (int i = 0; i < 3; i++) + { + FloatToString(buffer[i], num, sizeof(num)); + StrCat(value, sizeof(value), num); + if (i < 2) + { + StrCat(value, sizeof(value), " "); + } + } + return strcopy(string, maxlength, value); +} + +void StringToVector(const char[] string, float buffer[3]) +{ + buffer[0] = 0.0; + buffer[1] = 0.0; + buffer[2] = 0.0; + + char num[3][16]; + int numStrings = ExplodeString(string, " ", num, sizeof(num), sizeof(num[])); + + for (int i = 0; i < numStrings; i++) + { + buffer[i] = StringToFloat(num[i]); + } +} + +void StringToColor(const char[] string, int buffer[4]) +{ + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + + char num[4][16]; + int numStrings = ExplodeString(string, " ", num, sizeof(num), sizeof(num[])); + + for (int i = 0; i < numStrings; i++) + { + buffer[i] = StringToInt(num[i]); + } +} + +bool StringToBool(const char[] string) +{ + return strcmp(string, "1", false) == 0; +} + +int BoolToString(bool value, char[] buffer, int bufferSize) +{ + return FormatEx(buffer, bufferSize, "%b", value); +} + +void RunScriptCode(int entity, int activator, int caller, const char[] format, any ...) +{ + if (!IsValidEntity(entity)) + { + return; + } + + static char buffer[1024]; + VFormat(buffer, sizeof(buffer), format, 5); + + SetVariantString(buffer); + AcceptEntityInput(entity, "RunScriptCode", activator, caller); +} + +void SetupProfileObjectNatives() +{ + CreateNative("SF2_ProfileObject.Parent.get", Native_GetObjectParent); + CreateNative("SF2_ProfileObject.KeyLength.get", Native_GetObjectKeyLength); + CreateNative("SF2_ProfileObject.SectionLength.get", Native_GetObjectSectionLength); + CreateNative("SF2_ProfileObject.GetInt", Native_GetObjectInt); + CreateNative("SF2_ProfileObject.SetInt", Native_SetObjectInt); + CreateNative("SF2_ProfileObject.GetBool", Native_GetObjectBool); + CreateNative("SF2_ProfileObject.SetBool", Native_SetObjectBool); + CreateNative("SF2_ProfileObject.GetFloat", Native_GetObjectFloat); + CreateNative("SF2_ProfileObject.SetFloat", Native_SetObjectFloat); + CreateNative("SF2_ProfileObject.GetSection", Native_GetObjectSection); + CreateNative("SF2_ProfileObject.GetArray", Native_GetObjectArray); + CreateNative("SF2_ProfileObject.GetString", Native_GetObjectString); + CreateNative("SF2_ProfileObject.SetString", Native_SetObjectString); + CreateNative("SF2_ProfileObject.GetVector", Native_GetObjectVector); + CreateNative("SF2_ProfileObject.SetVector", Native_SetObjectVector); + CreateNative("SF2_ProfileObject.GetColor", Native_GetObjectColor); + CreateNative("SF2_ProfileObject.SetColor", Native_SetObjectColor); + CreateNative("SF2_ProfileObject.GetDifficultyInt", Native_GetObjectDifficultyInt); + CreateNative("SF2_ProfileObject.SetDifficultyInt", Native_SetObjectDifficultyInt); + CreateNative("SF2_ProfileObject.GetDifficultyBool", Native_GetObjectDifficultyBool); + CreateNative("SF2_ProfileObject.SetDifficultyBool", Native_SetObjectDifficultyBool); + CreateNative("SF2_ProfileObject.GetDifficultyFloat", Native_GetObjectDifficultyFloat); + CreateNative("SF2_ProfileObject.SetDifficultyFloat", Native_SetObjectDifficultyFloat); + CreateNative("SF2_ProfileObject.GetDifficultySection", Native_GetObjectDifficultySection); + CreateNative("SF2_ProfileObject.GetDifficultyString", Native_GetObjectDifficultyString); + CreateNative("SF2_ProfileObject.SetDifficultyString", Native_SetObjectDifficultyString); + CreateNative("SF2_ProfileObject.GetDifficultyVector", Native_GetObjectDifficultyVector); + CreateNative("SF2_ProfileObject.SetDifficultyVector", Native_SetObjectDifficultyVector); + CreateNative("SF2_ProfileObject.GetDifficultyColor", Native_GetObjectDifficultyColor); + CreateNative("SF2_ProfileObject.SetDifficultyColor", Native_SetObjectDifficultyColor); + CreateNative("SF2_ProfileObject.ConvertValuesSectionToArray", Native_ConvertValuesSectionToArray); + CreateNative("SF2_ProfileObject.ConvertSectionsSectionToArray", Native_ConvertSectionsSectionToArray); + + CreateNative("SF2_ProfileArray.Length.get", Native_GetArrayLength); + CreateNative("SF2_ProfileArray.GetInt", Native_GetArrayInt); + CreateNative("SF2_ProfileArray.GetBool", Native_GetArrayBool); + CreateNative("SF2_ProfileArray.GetFloat", Native_GetArrayFloat); + CreateNative("SF2_ProfileArray.GetSection", Native_GetArraySection); + CreateNative("SF2_ProfileArray.GetString", Native_GetArrayString); + + CreateNative("SF2_ProfileSound.Paths.get", Native_GetSoundPaths); + CreateNative("SF2_ProfileSound.SetDefaultChannel", Native_SetSoundDefaultChannel); + CreateNative("SF2_ProfileSound.SetDefaultLevel", Native_SetSoundDefaultLevel); + CreateNative("SF2_ProfileSound.SetDefaultFlags", Native_SetSoundDefaultFlags); + CreateNative("SF2_ProfileSound.SetDefaultCooldownMin", Native_SetSoundDefaultCooldownMin); + CreateNative("SF2_ProfileSound.SetDefaultCooldownMax", Native_SetSoundDefaultCooldownMax); + CreateNative("SF2_ProfileSound.GetChannel", Native_GetSoundChannel); + CreateNative("SF2_ProfileSound.GetVolume", Native_GetSoundVolume); + CreateNative("SF2_ProfileSound.GetFlags", Native_GetSoundFlags); + CreateNative("SF2_ProfileSound.GetLevel", Native_GetSoundLevel); + CreateNative("SF2_ProfileSound.GetPitch", Native_GetSoundPitch); + CreateNative("SF2_ProfileSound.GetCooldownMin", Native_GetSoundCooldownMin); + CreateNative("SF2_ProfileSound.GetCooldownMax", Native_GetSoundCooldownMax); + CreateNative("SF2_ProfileSound.Precache", Native_SoundPrecache); + CreateNative("SF2_ProfileSound.EmitToAll", Native_SoundEmitToAll); + CreateNative("SF2_ProfileSound.EmitToClient", Native_SoundEmitToClient); + CreateNative("SF2_ProfileSound.StopAllSounds", Native_SoundStopAllSounds); + + CreateNative("SF2_ProfileAnimation.GetAnimationName", Native_AnimationGetAnimationName); + CreateNative("SF2_ProfileAnimation.GetGestureName", Native_AnimationGetGestureName); + CreateNative("SF2_ProfileAnimation.GetAnimationPlaybackRate", Native_AnimationGetAnimationRate); + CreateNative("SF2_ProfileAnimation.GetGesturePlaybackRate", Native_AnimationGetGestureRate); + CreateNative("SF2_ProfileAnimation.GetDuration", Native_AnimationGetDuration); + CreateNative("SF2_ProfileAnimation.GetAnimationCycle", Native_AnimationGetAnimationCycle); + CreateNative("SF2_ProfileAnimation.GetGestureCycle", Native_AnimationGetGestureCycle); + CreateNative("SF2_ProfileAnimation.PlayAnimation", Native_AnimationPlayAnimation); + CreateNative("SF2_ProfileAnimation.PlayGesture", Native_AnimationPlayGesture); + + CreateNative("SF2_ProfileMasterAnimation.HasAnimationSection", Native_MasterAnimationHasSection); + CreateNative("SF2_ProfileMasterAnimation.GetAnimation", Native_MasterAnimationGetAnimation); +} + +static any Native_GetObjectParent(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.Parent; +} + +static any Native_GetObjectKeyLength(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.KeyLength; +} + +static any Native_GetObjectSectionLength(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.SectionLength; +} + +static any Native_GetObjectInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int def = GetNativeCell(3); + + return obj.GetInt(key, def); +} + +static any Native_SetObjectInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int def = GetNativeCell(3); + + obj.SetInt(key, def); + + return 0; +} + +static any Native_GetObjectBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + bool def = GetNativeCell(3); + + return obj.GetBool(key, def); +} + +static any Native_SetObjectBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + bool def = GetNativeCell(3); + + obj.SetBool(key, def); + + return 0; +} + +static any Native_GetObjectFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float def = GetNativeCell(3); + + return obj.GetFloat(key, def); +} + +static any Native_SetObjectFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float def = GetNativeCell(3); + + obj.SetFloat(key, def); + + return 0; +} + +static any Native_GetObjectSection(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + ProfileObject def = GetNativeCell(3); + + return obj.GetSection(key, def); +} + +static any Native_GetObjectArray(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + KeyMap_Array def = GetNativeCell(3); + + return obj.GetArray(key, def); +} + +static any Native_GetObjectString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + int defSize; + GetNativeStringLength(5, defSize); + defSize++; + char[] def = new char[defSize]; + GetNativeString(5, def, defSize); + + obj.GetString(key, buffer, bufferSize, def); + + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_SetObjectString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int valueSize; + GetNativeStringLength(3, valueSize); + valueSize++; + char[] value = new char[valueSize]; + GetNativeString(3, value, valueSize); + + obj.SetString(key, value); + + return 0; +} + +static any Native_GetObjectVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float def[3]; + GetNativeArray(4, def, 3); + float vec[3]; + + obj.GetVector(key, vec, def); + + SetNativeArray(3, vec, 3); + return 0; +} + +static any Native_SetObjectVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + float value[3]; + GetNativeArray(3, value, 3); + + obj.SetVector(key, value); + return 0; +} + +static any Native_GetObjectColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int def[4]; + GetNativeArray(4, def, 4); + int vec[4]; + + obj.GetColor(key, vec, def); + + SetNativeArray(3, vec, 4); + return 0; +} + +static any Native_SetObjectColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int value[4]; + GetNativeArray(3, value, 4); + + obj.SetColor(key, value); + return 0; +} + +static any Native_GetObjectDifficultyInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + int def = GetNativeCell(4); + + return obj.GetDifficultyInt(key, difficulty, def); +} + +static any Native_SetObjectDifficultyInt(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + int def = GetNativeCell(4); + + obj.SetDifficultyInt(key, difficulty, def); + return 0; +} + +static any Native_GetObjectDifficultyBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + bool def = GetNativeCell(4); + + return obj.GetDifficultyBool(key, difficulty, def); +} + +static any Native_SetObjectDifficultyBool(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + bool def = GetNativeCell(4); + + obj.SetDifficultyBool(key, difficulty, def); + return 0; +} + +static any Native_GetObjectDifficultyFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + float def = GetNativeCell(4); + + return obj.GetDifficultyFloat(key, difficulty, def); +} + +static any Native_SetObjectDifficultyFloat(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + float def = GetNativeCell(4); + + obj.SetDifficultyFloat(key, difficulty, def); + return 0; +} + +static any Native_GetObjectDifficultySection(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + ProfileObject def = GetNativeCell(4); + + return obj.GetDifficultySection(key, difficulty, def); +} + +static any Native_GetObjectDifficultyString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int bufferSize = GetNativeCell(5); + char[] buffer = new char[bufferSize]; + + int defSize; + GetNativeStringLength(6, defSize); + defSize++; + char[] def = new char[defSize]; + GetNativeString(6, def, defSize); + + obj.GetDifficultyString(key, difficulty, buffer, bufferSize, def); + SetNativeString(4, buffer, bufferSize); + return 0; +} + +static any Native_SetObjectDifficultyString(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int valueSize; + GetNativeStringLength(4, valueSize); + valueSize++; + char[] value = new char[valueSize]; + GetNativeString(4, value, valueSize); + + obj.SetDifficultyString(key, difficulty, value); + return 0; +} + +static any Native_GetObjectDifficultyVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + float buffer[3]; + + float def[3]; + GetNativeArray(5, def, 3); + + obj.GetDifficultyVector(key, difficulty, buffer, def); + SetNativeArray(4, buffer, 3); + return 0; +} + +static any Native_SetObjectDifficultyVector(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + float value[3]; + GetNativeArray(4, value, 3); + + obj.SetDifficultyVector(key, difficulty, value); + return 0; +} + +static any Native_GetObjectDifficultyColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int buffer[4]; + + int def[4]; + GetNativeArray(5, def, 4); + + obj.GetDifficultyColor(key, difficulty, buffer, def); + SetNativeArray(4, buffer, 4); + return 0; +} + +static any Native_SetObjectDifficultyColor(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + int difficulty = GetNativeCell(3); + + int value[4]; + GetNativeArray(4, value, 4); + + obj.SetDifficultyColor(key, difficulty, value); + return 0; +} + +static any Native_ConvertValuesSectionToArray(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + obj.ConvertValuesSectionToArray(key); + return 0; +} + +static any Native_ConvertSectionsSectionToArray(Handle plugin, int numParams) +{ + ProfileObject obj = view_as(GetNativeCell(1)); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + obj.ConvertSectionsSectionToArray(key); + return 0; +} + +static any Native_GetArrayLength(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.Length; +} + +static any Native_GetArrayInt(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetInt(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArrayBool(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetBool(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArrayFloat(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetFloat(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArraySection(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + return obj.GetSection(GetNativeCell(2), GetNativeCell(3)); +} + +static any Native_GetArrayString(Handle plugin, int numParams) +{ + KeyMap_Array obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile array handle %x", obj); + } + + int index = GetNativeCell(2); + + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + int defaultValueSize; + GetNativeStringLength(5, defaultValueSize); + defaultValueSize++; + char[] defaultValue = new char[defaultValueSize]; + GetNativeString(5, defaultValue, defaultValueSize); + + obj.GetString(index, buffer, bufferSize, defaultValue); + + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_GetSoundPaths(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.Paths; +} + +static any Native_SetSoundDefaultChannel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int channel = GetNativeCell(2); + + obj.SetDefaultChannel(channel); + return 0; +} + +static any Native_SetSoundDefaultLevel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int level = GetNativeCell(2); + + obj.SetDefaultLevel(level); + return 0; +} + +static any Native_SetSoundDefaultFlags(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int flags = GetNativeCell(2); + + obj.SetDefaultFlags(flags); + return 0; +} + +static any Native_SetSoundDefaultCooldownMin(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + float cooldown = GetNativeCell(2); + + obj.SetDefaultCooldownMin(cooldown); + return 0; +} + +static any Native_SetSoundDefaultCooldownMax(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + float cooldown = GetNativeCell(2); + + obj.SetDefaultCooldownMax(cooldown); + return 0; +} + +static any Native_GetSoundChannel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetChannel(GetNativeCell(2)); +} + +static any Native_GetSoundVolume(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetVolume(GetNativeCell(2)); +} + +static any Native_GetSoundFlags(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetFlags(GetNativeCell(2)); +} + +static any Native_GetSoundLevel(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetLevel(GetNativeCell(2)); +} + +static any Native_GetSoundPitch(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetPitch(GetNativeCell(2)); +} + +static any Native_GetSoundCooldownMin(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetCooldownMin(GetNativeCell(2)); +} + +static any Native_GetSoundCooldownMax(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetCooldownMax(GetNativeCell(2)); +} + +static any Native_SoundPrecache(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.Precache(); + return 0; +} + +static any Native_SoundEmitToAll(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.EmitSound(false, GetNativeCell(2), .difficulty = GetNativeCell(3)); + return 0; +} + +static any Native_SoundEmitToClient(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.EmitSound(true, GetNativeCell(2), GetNativeCell(3), .difficulty = GetNativeCell(4)); + return 0; +} + +static any Native_SoundStopAllSounds(Handle plugin, int numParams) +{ + ProfileSound obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + obj.StopAllSounds(.entity = GetNativeCell(2)); + return 0; +} + +static any Native_AnimationGetAnimationName(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int difficulty = GetNativeCell(2); + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + obj.GetAnimationName(difficulty, buffer, bufferSize); + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_AnimationGetGestureName(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int difficulty = GetNativeCell(2); + int bufferSize = GetNativeCell(4); + char[] buffer = new char[bufferSize]; + + obj.GetGestureName(difficulty, buffer, bufferSize); + SetNativeString(3, buffer, bufferSize); + return 0; +} + +static any Native_AnimationGetAnimationRate(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetAnimationPlaybackRate(GetNativeCell(2)); +} + +static any Native_AnimationGetGestureRate(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetGesturePlaybackRate(GetNativeCell(2)); +} + +static any Native_AnimationGetDuration(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetDuration(GetNativeCell(2)); +} + +static any Native_AnimationGetAnimationCycle(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetAnimationCycle(GetNativeCell(2)); +} + +static any Native_AnimationGetGestureCycle(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + return obj.GetGestureCycle(GetNativeCell(2)); +} + +static any Native_AnimationPlayAnimation(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + CBaseAnimating entity = CBaseAnimating(GetNativeCell(2)); + int difficulty = GetNativeCell(3); + + char name[64]; + obj.GetAnimationName(difficulty, name, sizeof(name)); + int sequence = LookupProfileAnimation(entity.index, name); + if (sequence == -1) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid animation name %s", name); + } + + float rate = obj.GetAnimationPlaybackRate(difficulty); + float cycle = obj.GetAnimationCycle(difficulty); + + entity.ResetSequence(sequence); + entity.SetPropFloat(Prop_Send, "m_flCycle", cycle); + entity.SetPropFloat(Prop_Send, "m_flPlaybackRate", rate); + entity.SetProp(Prop_Data, "m_bSequenceLoops", GetNativeCell(4)); + + return 0; +} + +static any Native_AnimationPlayGesture(Handle plugin, int numParams) +{ + ProfileAnimation obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + CBaseAnimatingOverlay entity = CBaseAnimatingOverlay(GetNativeCell(2)); + int difficulty = GetNativeCell(3); + bool loop = GetNativeCell(4); + + char name[64]; + obj.GetGestureName(difficulty, name, sizeof(name)); + int sequence = LookupProfileAnimation(entity.index, name); + if (sequence == -1) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid gesture name %s", name); + } + + float rate = obj.GetGesturePlaybackRate(difficulty); + float cycle = obj.GetGestureCycle(difficulty); + float duration = entity.SequenceDuration(sequence); + int layer = entity.AddLayeredSequence(sequence, 1); + entity.SetLayerDuration(layer, duration); + entity.SetLayerPlaybackRate(layer, rate); + entity.SetLayerCycle(layer, cycle); + entity.SetLayerLooping(layer, loop); + entity.SetLayerAutokill(layer, true); + SetNativeCellRef(5, layer); + return 0; +} + +static any Native_MasterAnimationHasSection(Handle plugin, int numParams) +{ + ProfileMasterAnimations obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize; + GetNativeStringLength(2, keySize); + keySize++; + char[] key = new char[keySize]; + GetNativeString(2, key, keySize); + + return obj.HasAnimationSection(key); +} + +static any Native_MasterAnimationGetAnimation(Handle plugin, int numParams) +{ + ProfileMasterAnimations obj = GetNativeCell(1); + if (obj == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid profile object handle %x", obj); + } + + int keySize, nameSize; + GetNativeStringLength(2, keySize); + GetNativeStringLength(4, nameSize); + keySize++; + nameSize++; + char[] key = new char[keySize]; + char[] name = new char[nameSize]; + GetNativeString(2, key, keySize); + GetNativeString(4, name, nameSize); + int index = GetNativeCell(5); + + ProfileAnimation animation = obj.GetAnimation(key, GetNativeCell(3), name, index); + SetNativeCellRef(5, index); + return animation; +} diff --git a/addons/sourcemod/scripting/sf2/stocks.sp b/addons/sourcemod/scripting/sf2/stocks.sp index 0314a30f..71180d4d 100644 --- a/addons/sourcemod/scripting/sf2/stocks.sp +++ b/addons/sourcemod/scripting/sf2/stocks.sp @@ -523,16 +523,6 @@ int SDK_SwitchWeapon(int client, int weapon) return -1; } -int GetWeaponID(int entity) -{ - return SDKCall(g_SDKGetWeaponID, entity); -} - -bool IsEntityWeapon(int entity) -{ - return SDKCall(g_SDKIsWeapon, entity); -} - bool IsClientInKart(int client) { if (TF2_IsPlayerInCondition(client, TFCond_HalloweenKart) || @@ -802,37 +792,6 @@ void ShowHealthRegen(int client, int amount, int weaponIndex = -1) } } -bool IsObjectFriendly(int obj, int entity) -{ - if (IsValidEntity(entity)) - { - if (IsValidClient(entity)) - { - if (GetEntPropEnt(obj, Prop_Send, "m_hBuilder") == GetEntPropEnt(entity, Prop_Send, "m_hDisguiseTarget")) - { - return true; - } - else if (GetEntPropEnt(obj, Prop_Send, "m_hBuilder") == entity) // obj_dispenser - { - return true; - } - else if (GetEntPropEnt(obj, Prop_Data, "m_hParent") == entity) // pd_dispenser - { - return true; - } - } - else - { - if (GetEntPropEnt(obj, Prop_Send, "m_hBuilder") == GetEntPropEnt(entity, Prop_Send, "m_hBuilder")) - { - return true; - } - } - } - - return false; -} - // ========================================================== // TF2-SPECIFIC FUNCTIONS // ========================================================== @@ -939,30 +898,6 @@ void SpecialRoundGameText(const char[] message, const char[] icon = "") CreateTimer(2.0, Timer_KillEntity, EntIndexToEntRef(entity), TIMER_FLAG_NO_MAPCHANGE); } -TFTeam TF2_GetEntityTeam(int entity) -{ - return view_as(GetEntProp(entity, Prop_Data, "m_iTeamNum")); -} - -TFTeam GetEnemyTeam(TFTeam team) -{ - switch (team) - { - case view_as(TFTeam_Red): - { - return view_as(TFTeam_Blue); - } - case view_as(TFTeam_Blue): - { - return view_as(TFTeam_Red); - } - default: - { - return team; - } - } -} - // Removes wearables such as botkillers from weapons. void TF2_RemoveWeaponSlotAndWearables(int client, int slot) { From df7fcc0a9639e4ab9adae1255229a8226eed0508 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sat, 21 Dec 2024 20:02:40 -0700 Subject: [PATCH 59/86] Update sf2.sp --- addons/sourcemod/scripting/sf2.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index fe27e1cb..c16ff837 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -746,7 +746,7 @@ int g_FlashlightHaloModel = -1; #include "sf2/entities/initialize.sp" #include "sf2/menus.sp" #include "sf2/npc.sp" -#include "sf2/pvp.sp" +#include "sf2/old_pvp.sp" #include "sf2/pve.sp" #include "sf2/client.sp" #include "sf2/specialround.sp" From 0034581b43fd3c9bd384dd131246fb2aec609f65 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Tue, 24 Dec 2024 10:39:37 -0700 Subject: [PATCH 60/86] Update sf2.phrases.txt --- addons/sourcemod/translations/sf2.phrases.txt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/addons/sourcemod/translations/sf2.phrases.txt b/addons/sourcemod/translations/sf2.phrases.txt index 3c0617d3..3224f6ec 100644 --- a/addons/sourcemod/translations/sf2.phrases.txt +++ b/addons/sourcemod/translations/sf2.phrases.txt @@ -706,6 +706,16 @@ "ko" "HUD 버전 설정" } + "SF2 Settings Hud New" + { + "en" "New" + } + + "SF2 Settings Hud Legacy" + { + "en" "Legacy" + } + "SF2 Settings Music Volume Title" { "en" "Set music volume" @@ -732,6 +742,11 @@ "ko" "{lightblue}음악 볼륨이 {1}%% 으로 설정 되었습니다." } + "SF2 Settings Projected Flashlight Title" + { + "en" "Projected flashlight" + } + "SF2 Settings Flashlight Temperature Title" { "en" "Set flashlight temperature" @@ -1161,6 +1176,23 @@ "ko" "현재 보스 재정의 : {1}" } + "SF2 Disabled Boss Pack" + { + "en" "{red}Boss Pack vote is disabled on this server." + } + + "SF2 Boss Pack Next" + { + "#format" "{1:s}" + "en" "{dodgerblue}Next boss pack: {lightblue}{1}{default}." + } + + "SF2 Current Boss Pack" + { + "#format" "{1:s}" + "en" "{dodgerblue}Current boss pack: {lightblue}{1}{default}." + } + "SF2 Admin Menu Player Set Play State" { "en" "Force player in/out of game" From a90ba114e4bb410a051e3ab7a5b744b21a4efc47 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Thu, 16 Jan 2025 14:08:09 -0700 Subject: [PATCH 61/86] Backports --- .../scripting/include/sf2/defines.inc | 1 + addons/sourcemod/scripting/sf2.sp | 18 +++ .../scripting/sf2/extras/commands.sp | 114 ++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/addons/sourcemod/scripting/include/sf2/defines.inc b/addons/sourcemod/scripting/include/sf2/defines.inc index 801baafa..f0d011f0 100644 --- a/addons/sourcemod/scripting/include/sf2/defines.inc +++ b/addons/sourcemod/scripting/include/sf2/defines.inc @@ -17,6 +17,7 @@ #define FIREWORKSRED_PARTICLENAME "utaunt_firework_teamcolor_red" #define TELEPORTEDINBLU_PARTICLENAME "teleported_red" #define SOUND_THUNDER ")ambient/explosions/explode_9.wav" +#define DEBUG_PAGEREVEALSOUND "#ui/cyoa_node_activate.wav" #define SPECIAL1UPSOUND "mvm/mvm_revive.wav" diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index c16ff837..c6b0320a 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -227,6 +227,8 @@ enum struct SF2PageEntityData int EntRef; char CollectSound[PLATFORM_MAX_PATH]; int CollectSoundPitch; + float Pos[3]; + float Ang[3]; } ArrayList g_Pages; @@ -550,6 +552,7 @@ ConVar g_FileCheckConVar; ConVar g_LoadOutsideMapsConVar; ConVar g_DefaultBossTeamConVar; ConVar g_EngineerBuildInBLUConVar; +ConVar g_DisableTauntLoopsConVar; ConVar g_RestartSessionConVar; bool g_RestartSessionEnabled; @@ -731,6 +734,10 @@ stock ArrayList g_FuncNavPrefer; int g_FlashlightHaloModel = -1; +bool g_PagesRevealed = false; +ArrayList g_PageLocations; +ArrayList g_PageLocationsGlow; + #if defined DEBUG #include "sf2/debug.sp" #endif @@ -800,8 +807,12 @@ public void OnLibraryRemoved(const char[] name) public void OnMapStart() { g_TimerFail = null; + g_PagesRevealed = false; + g_PageLocations.Clear(); + g_PageLocationsGlow.Clear(); FindHealthBar(); PrecacheSound(SOUND_THUNDER, true); + PrecacheSound(DEBUG_PAGEREVEALSOUND); PrecacheSound("weapons/teleporter_send.wav"); g_ShockwaveBeam = PrecacheModel("sprites/laser.vmt"); g_ShockwaveHalo = PrecacheModel("sprites/halo01.vmt"); @@ -2661,6 +2672,11 @@ Action Hook_TauntUserMessage(UserMsg msg_id, BfRead msg, const int[] players, in return Plugin_Handled; //Don't allow proxies to play a taunt sound } + if (g_DisableTauntLoopsConVar.BoolValue) + { + return Plugin_Continue; + } + char tauntSound[PLATFORM_MAX_PATH]; msg.ReadString(tauntSound, PLATFORM_MAX_PATH); @@ -7565,6 +7581,8 @@ static void SpawnPages() SF2PageEntityData pageData; pageData.EntRef = EnsureEntRef(page); + pageData.Pos = pos; + pageData.Ang = angle; if (spawnPoint.IsValid()) { diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 8c0792a0..8e624538 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -73,6 +73,8 @@ public void OnPluginStart() g_Pages = new ArrayList(sizeof(SF2PageEntityData)); g_PageMusicRanges = new ArrayList(3); g_EmptySpawnPagePoints = new ArrayList(); + g_PageLocations = new ArrayList(); + g_PageLocationsGlow = new ArrayList(); char valueToString[32]; @@ -231,6 +233,7 @@ public void OnPluginStart() g_DefaultBossTeamConVar = CreateConVar("sf2_default_boss_team", "1", "If bosses are loaded outside of SF2, determine what default team bosses should be with.", _, true, 1.0, true, 5.0); g_EngineerBuildInBLUConVar = CreateConVar("sf2_engineer_build_in_blue", "0", "Allows BLU engineers to build outside of the PvP and PvE arena.", _, true, 0.0, true, 1.0); + g_DisableTauntLoopsConVar = CreateConVar("sf2_engineer_build_in_blue", "0", "Enables/Disables the ability for proxies and REDs to hear taunt sounds.", _, true, 0.0, true, 1.0); g_MaxRoundsConVar = FindConVar("mp_maxrounds"); @@ -347,6 +350,7 @@ public void OnPluginStart() RegAdminCmd("sm_sf2_player_infinite_blink_override", Command_InfiniteBlink, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_wall_hax", Command_WallHax, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_keep_weapons", Command_KeepWeapons, ADMFLAG_SLAY); + RegAdminCmd("sm_sf2_reveal_page_locations", Command_RevealPageLocations, ADMFLAG_CHEATS); RegAdminCmd("+alltalk", Command_AllTalkOn, ADMFLAG_SLAY); RegAdminCmd("-alltalk", Command_AllTalkOff, ADMFLAG_SLAY); RegAdminCmd("+slalltalk", Command_AllTalkOn, ADMFLAG_SLAY, _, _, FCVAR_HIDDEN); @@ -2879,3 +2883,113 @@ static Action Command_KeepWeapons(int client, int args) CPrintToChat(client, "{royalblue}%t {default}Lobby players can %s keep their weapons.", "SF2 Prefix", g_PlayerKeepWeaponsConVar.BoolValue ? "now" : "no longer"); return Plugin_Handled; } + +static Action Command_RevealPageLocations(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + + if (!g_PagesRevealed) + { + int count = 0; + EmitSoundToClient(client, DEBUG_PAGEREVEALSOUND); + for (int i = 0; i < g_EmptySpawnPagePoints.Length; i++) + { + float pos[3], ang[3]; + CBaseEntity location = CBaseEntity(g_EmptySpawnPagePoints.Get(i)); + location.GetAbsOrigin(pos); + location.GetAbsAngles(ang); + CBaseEntity page = CBaseEntity(CreateEntityByName("prop_dynamic_override")); + page.KeyValue("solid", "0"); + page.SetModel(PAGE_MODEL); + page.Teleport(pos, ang); + page.Spawn(); + page.Activate(); + page.AcceptInput("DisableCollision"); + page.SetPropFloat(Prop_Send, "m_flModelScale", PAGE_MODELSCALE); + SetEntityTransmitState(page.index, FL_EDICT_ALWAYS); + + g_PageLocations.Push(EntIndexToEntRef(page.index)); + CBaseEntity glow = CBaseEntity(CreateGlowEntityDataless(page.index)); + glow.SetPropEnt(Prop_Data, "m_hOwnerEntity", page.index); + g_PageLocationsGlow.Push(EntIndexToEntRef(glow.index)); + count++; + } + + for (int i = 0; i < g_Pages.Length; i++) + { + SF2PageEntityData pageData; + g_Pages.GetArray(i, pageData, sizeof(pageData)); + + CBaseEntity page = CBaseEntity(EntRefToEntIndex(pageData.EntRef)); + if (!page.IsValid()) + { + page = CBaseEntity(CreateEntityByName("prop_dynamic_override")); + page.KeyValue("solid", "0"); + page.SetModel(PAGE_MODEL); + page.Teleport(pageData.Pos, pageData.Ang); + page.Spawn(); + page.Activate(); + page.AcceptInput("DisableCollision"); + page.SetPropFloat(Prop_Send, "m_flModelScale", PAGE_MODELSCALE); + SetEntityTransmitState(page.index, FL_EDICT_ALWAYS); + g_PageLocations.Push(EntIndexToEntRef(page.index)); + } + else + { + page = CBaseEntity(page.GetPropEnt(Prop_Send, "m_hOwnerEntity")); + SetEntityTransmitState(page.index, FL_EDICT_ALWAYS); + } + + CBaseEntity glow = CBaseEntity(CreateGlowEntityDataless(page.index, { 255, 0, 0, 255 })); + glow.SetPropEnt(Prop_Data, "m_hOwnerEntity", page.index); + g_PageLocationsGlow.Push(EntIndexToEntRef(glow.index)); + count++; + } + CPrintToChat(client, "{royalblue}%t {default}Revealed %i page locations.", "SF2 Prefix", count); + g_PagesRevealed = true; + } + else + { + for (int i = 0; i < g_PageLocations.Length; i++) + { + int entity = EntRefToEntIndex(g_PageLocations.Get(i)); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + RemoveEntity(entity); + } + + for (int i = 0; i < g_PageLocationsGlow.Length; i++) + { + int entity = EntRefToEntIndex(g_PageLocationsGlow.Get(i)); + if (!entity || entity == INVALID_ENT_REFERENCE) + { + continue; + } + + RemoveEntity(entity); + } + + for (int i = 0; i < g_Pages.Length; i++) + { + SF2PageEntityData pageData; + g_Pages.GetArray(i, pageData, sizeof(pageData)); + + CBaseEntity page = CBaseEntity(EntRefToEntIndex(pageData.EntRef)); + if (page.IsValid()) + { + page = CBaseEntity(page.GetPropEnt(Prop_Send, "m_hOwnerEntity")); + page.DispatchUpdateTransmitState(); + } + } + g_PageLocations.Clear(); + g_PageLocationsGlow.Clear(); + g_PagesRevealed = false; + } + return Plugin_Handled; +} \ No newline at end of file From 9aaf657788fa567ba4fc64d08b924aa3484066be Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Thu, 16 Jan 2025 14:43:53 -0700 Subject: [PATCH 62/86] Update commands.sp --- addons/sourcemod/scripting/sf2/extras/commands.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 8e624538..30834fb8 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -233,7 +233,7 @@ public void OnPluginStart() g_DefaultBossTeamConVar = CreateConVar("sf2_default_boss_team", "1", "If bosses are loaded outside of SF2, determine what default team bosses should be with.", _, true, 1.0, true, 5.0); g_EngineerBuildInBLUConVar = CreateConVar("sf2_engineer_build_in_blue", "0", "Allows BLU engineers to build outside of the PvP and PvE arena.", _, true, 0.0, true, 1.0); - g_DisableTauntLoopsConVar = CreateConVar("sf2_engineer_build_in_blue", "0", "Enables/Disables the ability for proxies and REDs to hear taunt sounds.", _, true, 0.0, true, 1.0); + g_DisableTauntLoopsConVar = CreateConVar("sf2_disable_taunt_loop_sounds", "0", "Enables/Disables the ability for proxies and REDs to hear taunt sounds.", _, true, 0.0, true, 1.0); g_MaxRoundsConVar = FindConVar("mp_maxrounds"); From 416c8ad4b1de14ea74f24124d698b782a20e53df Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:28:29 -0700 Subject: [PATCH 63/86] Update sf2.txt --- addons/sourcemod/gamedata/sf2.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/gamedata/sf2.txt b/addons/sourcemod/gamedata/sf2.txt index 756c37f5..cbfa4406 100644 --- a/addons/sourcemod/gamedata/sf2.txt +++ b/addons/sourcemod/gamedata/sf2.txt @@ -113,7 +113,7 @@ "library" "server" "linux" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" "linux64" "@_ZN9CTFPlayer20PlaySpecificSequenceEPKc" - "windows" "\x55\x8B\xEC\x53\x56\x8B\x75\x08\x57\x56\x8B\xF9\xE8\x2A\x2A\x2A\x2A\x8B\xD8" + "windows" "\x55\x8B\xEC\x56\xFF\x75\x08\x8B\xF1\xE8\x2A\x2A\x2A\x2A\x8B\xCE\x83\xF8\xFF" "windows64" "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x48\x8B\xFA\x48\x8B\xD9\xE8\x2A\x2A\x2A\x2A\x48\x8B\xCB\x83\xF8\xFF\x74\x2A" } @@ -328,4 +328,4 @@ } } } -} \ No newline at end of file +} From ce3286db3c712d6bd2a23436c91f45d5caaefb4c Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Mon, 7 Apr 2025 20:54:27 -0700 Subject: [PATCH 64/86] 2.73b --- addons/sourcemod/scripting/include/sf2.inc | 5 +- .../include/sf2/profiles/profiles.inc | 86 ++++++++++--------- .../include/sf2/profiles/profiles_chaser.inc | 4 +- addons/sourcemod/scripting/sf2.sp | 2 +- .../sourcemod/scripting/sf2/client/think.sp | 2 +- 5 files changed, 53 insertions(+), 46 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 64621c15..b149c255 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -7,8 +7,8 @@ #include #include -#define PLUGIN_VERSION "1.8.0 M Alpha 2.7" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.7" +#define PLUGIN_VERSION "1.8.0 M Alpha 2.73b" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.73b" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 @@ -541,7 +541,6 @@ forward Action SF2_OnBossPreTakeDamage(SF2_ChaserBossEntity chaser, CBaseEntity */ forward Action SF2_OnBossPreFlashlightDamage(SF2_ChaserBossEntity chaser, SF2_Player player); - /** * Called whenever a chaser boss gets a command string "suspend for action" using INextBotEventResponder.OnCommandString() * diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index 7a09cc3a..f4c99762 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -1708,10 +1708,7 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima void Load(KeyValues kv, bool newStuff = false) { SF2BossProfileAnimationSectionNameData animData; - this.Animations = new StringMap(); - ArrayList temp = new ArrayList(sizeof(animData)); - temp.PushArray(animData); - this.Animations.SetValue("deathcorridor", temp); + this.Animations = null; if (kv.JumpToKey("animations")) { this.Gestures = new StringMap(); @@ -1720,6 +1717,10 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima if (kv.GotoFirstSubKey()) // Find the animation name { + if (this.Animations == null) + { + this.Animations = new StringMap(); + } do { kv.GetSectionName(animType, sizeof(animType)); @@ -1729,6 +1730,10 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima if (kv.GotoFirstSubKey()) { + if (this.Gestures == null) + { + this.Gestures = new StringMap(); + } do { char animType2[64]; @@ -1782,6 +1787,10 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima { continue; } + if (this.Animations == null) + { + this.Animations = new StringMap(); + } ArrayList animDataArray = new ArrayList(sizeof(animData)); strcopy(section, sizeof(section), g_SlenderAnimationsList[i]); animData.Init(section); @@ -1795,54 +1804,53 @@ enum struct SF2BossProfileMasterAnimationsData // This one DOES cover the "anima void Destroy() { - if (this.Animations == null || this.Gestures == null) - { - return; - } - StringMapSnapshot snapshot = this.Animations.Snapshot(); - StringMapSnapshot gestureSnapshot = this.Gestures.Snapshot(); - char animType[64]; ArrayList animDataArray; SF2BossProfileAnimationSectionNameData animData; SF2BossProfileAttackGestureData gestureData; - - for (int i = 0; i < snapshot.Length; i++) + if (this.Animations != null) { - snapshot.GetKey(i, animType, sizeof(animType)); - this.Animations.GetValue(animType, animDataArray); - - for (int j = 0; j < animDataArray.Length; j++) - { - animDataArray.GetArray(j, animData, sizeof(animData)); - animData.Destroy(); - } - if (animDataArray != null) + StringMapSnapshot snapshot = this.Animations.Snapshot(); + for (int i = 0; i < snapshot.Length; i++) { - delete animDataArray; + snapshot.GetKey(i, animType, sizeof(animType)); + this.Animations.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, animData, sizeof(animData)); + animData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } } + delete snapshot; + delete this.Animations; } - for (int i = 0; i < gestureSnapshot.Length; i++) + if (this.Gestures != null) { - gestureSnapshot.GetKey(i, animType, sizeof(animType)); - this.Gestures.GetValue(animType, animDataArray); - - for (int j = 0; j < animDataArray.Length; j++) + StringMapSnapshot gestureSnapshot = this.Gestures.Snapshot(); + for (int i = 0; i < gestureSnapshot.Length; i++) { - animDataArray.GetArray(j, gestureData, sizeof(gestureData)); - gestureData.Destroy(); - } - if (animDataArray != null) - { - delete animDataArray; + gestureSnapshot.GetKey(i, animType, sizeof(animType)); + this.Gestures.GetValue(animType, animDataArray); + + for (int j = 0; j < animDataArray.Length; j++) + { + animDataArray.GetArray(j, gestureData, sizeof(gestureData)); + gestureData.Destroy(); + } + if (animDataArray != null) + { + delete animDataArray; + } } + delete gestureSnapshot; + delete this.Gestures; } - - delete snapshot; - delete gestureSnapshot; - delete this.Animations; - delete this.Gestures; } bool HasAnimationSection(const char[] animType) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 8c301a9b..db3319b8 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -607,7 +607,7 @@ enum struct SF2ChaserBossProfileShockwaveData return; } - if (!player.IsValid()) + if (!SF2_Player(player.index).IsValid) { return; } @@ -1068,7 +1068,7 @@ enum struct SF2ChaserBossProfileAttackData return; } - if (!player.IsValid()) + if (!SF2_Player(player.index).IsValid) { return; } diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index c6b0320a..90fd13cc 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -1398,8 +1398,8 @@ public void OnMapEnd() g_RenevantBossesChaseEndlessly = false; g_RenevantWallHax = false; - BossProfilesOnMapEnd(); NPCRemoveAll(); + BossProfilesOnMapEnd(); Call_StartForward(g_OnMapEndPFwd); Call_Finish(); diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index e4cad356..535e5a05 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -369,7 +369,7 @@ Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float } } - if (!SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) + if (IsValidEntity(inflictor) && !SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) { int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); if (npcIndex != -1) From c949a6f658b25a27925415441e7c01cd3222466f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Mon, 7 Apr 2025 20:55:26 -0700 Subject: [PATCH 65/86] Update sf2.inc --- addons/sourcemod/scripting/include/sf2.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index b149c255..7806fe02 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -7,8 +7,8 @@ #include #include -#define PLUGIN_VERSION "1.8.0 M Alpha 2.73b" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.73b" +#define PLUGIN_VERSION "1.8.0 M Alpha 2.73c" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.73c" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 From a911d534ce1e717ef80b6cda576410801d8ed0ee Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Mon, 7 Apr 2025 21:01:20 -0700 Subject: [PATCH 66/86] Update think.sp --- addons/sourcemod/scripting/sf2/client/think.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index 535e5a05..bfe9ee0e 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -375,7 +375,7 @@ Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float if (npcIndex != -1) { bool attackEliminated = (NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS) != 0; - if (!attackEliminated && (GetClientTeam(victim) == TFTeam_Blue) && IsValidClient(victim) ) + if (!attackEliminated && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Blue) { damage = 0.0; return Plugin_Changed; From 965a35d6148b0f58e7a00207e5df7cd298d09158 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Mon, 7 Apr 2025 21:35:20 -0700 Subject: [PATCH 67/86] Update sf2.inc --- addons/sourcemod/scripting/include/sf2.inc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 7806fe02..78e7dca4 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #define PLUGIN_VERSION "1.8.0 M Alpha 2.73c" #define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.73c" @@ -1025,6 +1028,7 @@ native bool SF2_IsClientUbercharged(int client); */ native bool SF2_IsClientInKart(int client); +#if defined _tf2_included /** * Returns whether or not the client is in a specific condition for SF2. * @@ -1033,6 +1037,7 @@ native bool SF2_IsClientInKart(int client); * @return True if the player is in a condition, false if not. */ native bool SF2_IsClientInCondition(int client, TFCond condition); +#endif /** * Returns a bool about the client's elimination state. @@ -2374,6 +2379,7 @@ methodmap SF2_Player < CBaseCombatCharacter */ public native int GetDataEnt(int offset); + #if defined _tf2_stocks_included /** * Alias of TF2_GetPlayerClass() */ @@ -2381,6 +2387,7 @@ methodmap SF2_Player < CBaseCombatCharacter { public native get(); } + #endif /** * Alias of GetClientTeam() @@ -2400,6 +2407,7 @@ methodmap SF2_Player < CBaseCombatCharacter public native set(bool state); } + #if defined _tf2_included /** * Alias of TF2_IsPlayerInCondition() * Use this over TF2_IsPlayerInCondition() though as it has extra optimizations @@ -2417,6 +2425,7 @@ methodmap SF2_Player < CBaseCombatCharacter * @param inflictor Inflictor of the condition */ public native bool ChangeCondition(TFCond condition, bool remove = false, float duration = -1.0, int inflictor = 0); + #endif /** * Whether or not the client is crit boosted From 0ed1c65f848d4e45fb5c10597ec2160aa7d8718f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Wed, 9 Apr 2025 13:04:03 -0700 Subject: [PATCH 68/86] 2.73d --- addons/sourcemod/scripting/include/sf2.inc | 4 +-- addons/sourcemod/scripting/sf2.sp | 26 +++++++------------ addons/sourcemod/scripting/sf2/effects.sp | 5 ---- .../sf2/npc/entities/chaser/entity.sp | 1 + addons/sourcemod/scripting/sf2/profiles.sp | 1 + 5 files changed, 13 insertions(+), 24 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 78e7dca4..89a8ab27 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -10,8 +10,8 @@ #include #include -#define PLUGIN_VERSION "1.8.0 M Alpha 2.73c" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.73c" +#define PLUGIN_VERSION "1.8.0 M Alpha 2.73d" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.73d" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 90fd13cc..9fa543eb 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -2360,8 +2360,8 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { if (g_RestartSessionConVar.BoolValue) { - ArrayList selectableBossesAdmin = GetSelectableAdminBossProfileList().Clone(); - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); + ArrayList selectableBossesAdmin = GetSelectableAdminBossProfileList(); + ArrayList selectableBosses = GetSelectableBossProfileList(); PlayNightmareSound(); SpecialRoundGameText("Its Restart Session time!", "leaderboard_streak"); CPrintToChatAll("{royalblue}%t {default}Your thirst for blood continues? Very well, let the blood spill. Let the demons feed off your unfortunate soul... Difficulty set to {mediumslateblue}%t!", "SF2 Prefix", "SF2 Calamity Difficulty"); @@ -2455,8 +2455,6 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) } delete spawnPoint; } - delete selectableBosses; - delete selectableBossesAdmin; } else { @@ -3079,7 +3077,7 @@ void CollectPage(int page, int activator) { if (g_DifficultyConVar.IntValue < 4 || GetSelectableAdminBossProfileList().Length <= 0) { - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -3089,11 +3087,10 @@ void CollectPage(int page, int activator) SpecialRoundGameText(bossName, "d_purgatory"); CPrintToChatAll("{royalblue}%t {default}Next on the roulette: {valve}%s", "SF2 Prefix", bossName); //Minimized HUD } - delete selectableBosses; } else { - ArrayList selectableBosses = GetSelectableAdminBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableAdminBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -3103,7 +3100,6 @@ void CollectPage(int page, int activator) SpecialRoundGameText(bossName, "d_purgatory"); CPrintToChatAll("{royalblue}%t {default}Next on the roulette: {valve}%s", "SF2 Prefix", bossName); } - delete selectableBosses; } } else @@ -5225,13 +5221,12 @@ void SetPageCount(int num) if (SF_SpecialRound(SPECIALROUND_LASTRESORT)) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); AddProfile(buffer); } - delete selectableBosses; } } else @@ -7819,7 +7814,7 @@ static void HandleNewBossRoundState() // Check if we have enough bosses. if (g_NewBossRound) { - ArrayList bossList = GetNewBossRoundProfileList().Clone(); + ArrayList bossList = GetNewBossRoundProfileList(); if (bossList.Length < 1) { @@ -7894,7 +7889,7 @@ static void SelectStartingBossesForRound() } #endif - ArrayList selectableBossList = GetSelectableBossProfileQueueList().Clone(); + ArrayList selectableBossList = GetSelectableBossProfileQueueList(); // Select which boss profile to use. char profileOverride[SF2_MAX_PROFILE_NAME_LENGTH]; @@ -8004,7 +7999,6 @@ static void SelectStartingBossesForRound() } #endif } - delete selectableBossList; } static void GetRoundIntroParameters() @@ -8288,13 +8282,12 @@ void InitializeNewGame() else if (SF_IsBoxingMap()) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBoxingBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBoxingBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); AddProfile(buffer); } - delete selectableBosses; } } } @@ -8529,13 +8522,12 @@ static Action Timer_ActivateRoundFromIntro(Handle timer) else if (SF_IsBoxingMap()) { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBoxingBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBoxingBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); AddProfile(buffer); } - delete selectableBosses; } } return Plugin_Stop; diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index 4da83b6f..192dcb6a 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -669,11 +669,6 @@ void SlenderRemoveEffects(int bossIndex, bool kill = false) return; } - if (g_NpcEffectsArray[bossIndex] != null && g_NpcEffectsArray[bossIndex].Length <= 0) - { - return; - } - for (int effect = 0; effect < g_NpcEffectsArray[bossIndex].Length; effect++) { int ent = g_NpcEffectsArray[bossIndex].Get(effect); diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 6bc4c68b..51bf7c2d 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -1611,6 +1611,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss } attackName = attackData.Name; + delete arrayAttacks; return SF2_ChaserAttackAction(attackName, attackData.Index, attackData.Duration[difficulty] + gameTime); } diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index a5538756..79db3e75 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -956,6 +956,7 @@ void InitiateBossPackVote(int initiator) { return; } + Handle voteMenu = NativeVotes_Create(Menu_BossPackVote, NativeVotesType_Custom_Mult); NativeVotes_SetInitiator(voteMenu, initiator); char title[255]; From 90dd90e8ce0b5abf08b918812929fcc39d578acb Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Wed, 9 Apr 2025 13:19:26 -0700 Subject: [PATCH 69/86] Update entity.sp --- .../sf2/npc/entities/chaser/entity.sp | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index 51bf7c2d..c90297ac 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -1489,7 +1489,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss SF2ChaserBossProfileData data; data = controller.GetProfileData(); int difficulty = controller.Difficulty; - ArrayList arrayAttacks = new ArrayList(); + ArrayList arrayAttacks = null; SF2ChaserBossProfileAttackData attackData; for (int index = 0; index < data.Attacks.Length; index++) { @@ -1536,6 +1536,10 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss if (result != Plugin_Continue) { + if (arrayAttacks == null) + { + arrayAttacks = new ArrayList(); + } arrayAttacks.Push(index); } continue; @@ -1567,20 +1571,27 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss attackName = attackData.Name; + if (arrayAttacks == null) + { + arrayAttacks = new ArrayList(); + } arrayAttacks.Push(index); } + if (arrayAttacks == null || arrayAttacks.Length == 0) + { + if (arrayAttacks != null) + { + delete arrayAttacks; + } + return NULL_ACTION; + } + Call_StartForward(g_OnBossPreAttackFwd); Call_PushCell(controller.Index); Call_PushCell(arrayAttacks); Call_Finish(); - if (arrayAttacks.Length == 0) - { - delete arrayAttacks; - return NULL_ACTION; - } - float eyePos[3], targetPos[3], direction[3], eyeAng[3]; this.GetAbsAngles(eyeAng); controller.GetEyePosition(eyePos); From fd44fcd46a8822863c2b1b690a9ccd4199a44fbc Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:27:04 -0700 Subject: [PATCH 70/86] Update effects.sp --- addons/sourcemod/scripting/sf2/effects.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/effects.sp b/addons/sourcemod/scripting/sf2/effects.sp index 192dcb6a..1c036a0f 100644 --- a/addons/sourcemod/scripting/sf2/effects.sp +++ b/addons/sourcemod/scripting/sf2/effects.sp @@ -669,7 +669,7 @@ void SlenderRemoveEffects(int bossIndex, bool kill = false) return; } - for (int effect = 0; effect < g_NpcEffectsArray[bossIndex].Length; effect++) + for (int effect = 0; effect < g_NpcEffectsArray[bossIndex].Length;) { int ent = g_NpcEffectsArray[bossIndex].Get(effect); if (!IsValidEntity(ent)) From 646636d7947cf7b0fef7b51f44267bb83a576418 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:32:35 -0700 Subject: [PATCH 71/86] Update specialround.sp --- .../sourcemod/scripting/sf2/specialround.sp | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index e45ad534..800b7826 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -1066,8 +1066,8 @@ void SpecialRoundStart() case SPECIALROUND_DOUBLETROUBLE: { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); + ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList(); if (!SF_IsBoxingMap()) { @@ -1085,8 +1085,6 @@ void SpecialRoundStart() AddProfile(buffer); } } - delete selectableBosses; - delete selectableBoxingBosses; SF_AddSpecialRound(SPECIALROUND_DOUBLETROUBLE); } case SPECIALROUND_SILENTSLENDER: @@ -1102,8 +1100,8 @@ void SpecialRoundStart() } char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); + ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList(); if (selectableBosses.Length > 0) { @@ -1119,8 +1117,6 @@ void SpecialRoundStart() selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); AddProfile(buffer, _, _, _, false); } - delete selectableBosses; - delete selectableBoxingBosses; SF_AddSpecialRound(SPECIALROUND_SILENTSLENDER); } case SPECIALROUND_THANATOPHOBIA: @@ -1288,8 +1284,8 @@ void SpecialRoundStart() g_OverrideDifficulty = 3; } char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - ArrayList selectableBosses = GetSelectableBossProfileList().Clone(); - ArrayList selectableBoxingBosses = GetSelectableBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableBossProfileList(); + ArrayList selectableBoxingBosses = GetSelectableBossProfileList(); if (!SF_IsBoxingMap()) { if (selectableBosses.Length > 0) @@ -1306,8 +1302,6 @@ void SpecialRoundStart() AddProfile(buffer); } } - delete selectableBosses; - delete selectableBoxingBosses; SF_AddSpecialRound(SPECIALROUND_2DOUBLE); } case SPECIALROUND_SUPRISE: @@ -1348,7 +1342,7 @@ void SpecialRoundStart() NPCStopMusic(); NPCRemoveAll(); } - ArrayList selectableBosses = GetSelectableAdminBossProfileList().Clone(); + ArrayList selectableBosses = GetSelectableAdminBossProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -1525,7 +1519,6 @@ void SpecialRoundStart() g_OverrideDifficulty = randomDifficulty; } } - delete selectableBosses; SF_AddSpecialRound(SPECIALROUND_MODBOSSES); } case SPECIALROUND_TRIPLEBOSSES: @@ -2083,4 +2076,4 @@ static any Native_IsSpecialRoundRunning(Handle plugin,int numParams) static any Native_GetSpecialRoundType(Handle plugin,int numParams) { return g_SpecialRoundType; -} \ No newline at end of file +} From b83df836b96de013cc4a7355ee496d35b559c03b Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:34:06 -0700 Subject: [PATCH 72/86] Update renevant.sp --- addons/sourcemod/scripting/sf2/gamemodes/renevant.sp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp index feb01335..96c0b335 100644 --- a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp +++ b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp @@ -376,7 +376,7 @@ static void Renevant_DoWaveAction(RenevantWave action) } case RenevantWave_AdminBoss: { - ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList().Clone(); + ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList(); if (selectableBosses.Length > 0) { selectableBosses.GetString(GetRandomInt(0, selectableBosses.Length - 1), buffer, sizeof(buffer)); @@ -405,7 +405,6 @@ static void Renevant_DoWaveAction(RenevantWave action) { g_RenevantWaveList.Erase(eraseWave); } - delete selectableBosses; } case RenevantWave_WallHax: { @@ -473,7 +472,7 @@ void Renevant_SetWave(int wave, bool resetTimer = false) g_RenevantWaveList.Erase(eraseWave); } } - ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList().Clone(); + ArrayList selectableBosses = GetSelectableRenevantBossAdminProfileList(); if (selectableBosses.Length <= 0) { int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_AdminBoss); @@ -482,7 +481,6 @@ void Renevant_SetWave(int wave, bool resetTimer = false) g_RenevantWaveList.Erase(eraseWave); } } - delete selectableBosses; } case 0: { From 535034f4c62485c3b3b7d6821ad38e1006f6b1b5 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:41:49 -0700 Subject: [PATCH 73/86] Update specialround.sp --- addons/sourcemod/scripting/sf2/specialround.sp | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index 800b7826..f9a3dde5 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -1101,7 +1101,6 @@ void SpecialRoundStart() char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; ArrayList selectableBosses = GetSelectableBossProfileList(); - ArrayList selectableBoxingBosses = GetSelectableBoxingBossProfileList(); if (selectableBosses.Length > 0) { From 5b9c5addee973ce9688aa44b136ad753803b997f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 26 Apr 2025 13:05:55 -0700 Subject: [PATCH 74/86] Add map entity graphics --- materials/editor/sf2/sf2_boss_maker.vmt | 7 +++++++ materials/editor/sf2/sf2_boss_maker.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_game_text.vmt | 7 +++++++ materials/editor/sf2/sf2_game_text.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_gamerules.vmt | 7 +++++++ materials/editor/sf2/sf2_gamerules.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_info_jumprestrict.vmt | 7 +++++++ materials/editor/sf2/sf2_info_jumprestrict.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_info_page_music.vmt | 7 +++++++ materials/editor/sf2/sf2_info_page_music.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_logic_arena.vmt | 7 +++++++ materials/editor/sf2/sf2_logic_arena.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_logic_boxing.vmt | 7 +++++++ materials/editor/sf2/sf2_logic_boxing.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_logic_elimination.vmt | 7 +++++++ materials/editor/sf2/sf2_logic_elimination.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_logic_proxy.vmt | 7 +++++++ materials/editor/sf2/sf2_logic_proxy.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_logic_raid.vmt | 7 +++++++ materials/editor/sf2/sf2_logic_raid.vtf | Bin 0 -> 22052 bytes materials/editor/sf2/sf2_logic_slaughter.vmt | 7 +++++++ materials/editor/sf2/sf2_logic_slaughter.vtf | Bin 0 -> 22052 bytes .../models/editor/sf2/dev_engy_body_blu.vmt | 4 ++++ .../models/editor/sf2/dev_engy_body_blu.vtf | Bin 0 -> 699232 bytes .../models/editor/sf2/dev_engy_body_red.vmt | 4 ++++ .../models/editor/sf2/dev_engy_body_red.vtf | Bin 0 -> 699232 bytes materials/models/editor/sf2/dev_engy_head.vmt | 4 ++++ materials/models/editor/sf2/dev_engy_head.vtf | Bin 0 -> 349760 bytes materials/models/editor/sf2/masky.vmt | 4 ++++ materials/models/editor/sf2/masky.vtf | Bin 0 -> 87616 bytes models/editor/sf2/escapespawn.dx80.vtx | Bin 0 -> 95112 bytes models/editor/sf2/escapespawn.dx90.vtx | Bin 0 -> 95112 bytes models/editor/sf2/escapespawn.mdl | Bin 0 -> 1956 bytes models/editor/sf2/escapespawn.sw.vtx | Bin 0 -> 95112 bytes models/editor/sf2/escapespawn.vvd | Bin 0 -> 357568 bytes models/editor/sf2/proxyspawn.dx80.vtx | Bin 0 -> 95595 bytes models/editor/sf2/proxyspawn.dx90.vtx | Bin 0 -> 95595 bytes models/editor/sf2/proxyspawn.mdl | Bin 0 -> 2144 bytes models/editor/sf2/proxyspawn.sw.vtx | Bin 0 -> 95595 bytes models/editor/sf2/proxyspawn.vvd | Bin 0 -> 361792 bytes models/editor/sf2/pvpspawn.dx80.vtx | Bin 0 -> 128418 bytes models/editor/sf2/pvpspawn.dx90.vtx | Bin 0 -> 128418 bytes models/editor/sf2/pvpspawn.mdl | Bin 0 -> 2184 bytes models/editor/sf2/pvpspawn.sw.vtx | Bin 0 -> 128418 bytes models/editor/sf2/pvpspawn.vvd | Bin 0 -> 504960 bytes 45 files changed, 93 insertions(+) create mode 100644 materials/editor/sf2/sf2_boss_maker.vmt create mode 100644 materials/editor/sf2/sf2_boss_maker.vtf create mode 100644 materials/editor/sf2/sf2_game_text.vmt create mode 100644 materials/editor/sf2/sf2_game_text.vtf create mode 100644 materials/editor/sf2/sf2_gamerules.vmt create mode 100644 materials/editor/sf2/sf2_gamerules.vtf create mode 100644 materials/editor/sf2/sf2_info_jumprestrict.vmt create mode 100644 materials/editor/sf2/sf2_info_jumprestrict.vtf create mode 100644 materials/editor/sf2/sf2_info_page_music.vmt create mode 100644 materials/editor/sf2/sf2_info_page_music.vtf create mode 100644 materials/editor/sf2/sf2_logic_arena.vmt create mode 100644 materials/editor/sf2/sf2_logic_arena.vtf create mode 100644 materials/editor/sf2/sf2_logic_boxing.vmt create mode 100644 materials/editor/sf2/sf2_logic_boxing.vtf create mode 100644 materials/editor/sf2/sf2_logic_elimination.vmt create mode 100644 materials/editor/sf2/sf2_logic_elimination.vtf create mode 100644 materials/editor/sf2/sf2_logic_proxy.vmt create mode 100644 materials/editor/sf2/sf2_logic_proxy.vtf create mode 100644 materials/editor/sf2/sf2_logic_raid.vmt create mode 100644 materials/editor/sf2/sf2_logic_raid.vtf create mode 100644 materials/editor/sf2/sf2_logic_slaughter.vmt create mode 100644 materials/editor/sf2/sf2_logic_slaughter.vtf create mode 100644 materials/models/editor/sf2/dev_engy_body_blu.vmt create mode 100644 materials/models/editor/sf2/dev_engy_body_blu.vtf create mode 100644 materials/models/editor/sf2/dev_engy_body_red.vmt create mode 100644 materials/models/editor/sf2/dev_engy_body_red.vtf create mode 100644 materials/models/editor/sf2/dev_engy_head.vmt create mode 100644 materials/models/editor/sf2/dev_engy_head.vtf create mode 100644 materials/models/editor/sf2/masky.vmt create mode 100644 materials/models/editor/sf2/masky.vtf create mode 100644 models/editor/sf2/escapespawn.dx80.vtx create mode 100644 models/editor/sf2/escapespawn.dx90.vtx create mode 100644 models/editor/sf2/escapespawn.mdl create mode 100644 models/editor/sf2/escapespawn.sw.vtx create mode 100644 models/editor/sf2/escapespawn.vvd create mode 100644 models/editor/sf2/proxyspawn.dx80.vtx create mode 100644 models/editor/sf2/proxyspawn.dx90.vtx create mode 100644 models/editor/sf2/proxyspawn.mdl create mode 100644 models/editor/sf2/proxyspawn.sw.vtx create mode 100644 models/editor/sf2/proxyspawn.vvd create mode 100644 models/editor/sf2/pvpspawn.dx80.vtx create mode 100644 models/editor/sf2/pvpspawn.dx90.vtx create mode 100644 models/editor/sf2/pvpspawn.mdl create mode 100644 models/editor/sf2/pvpspawn.sw.vtx create mode 100644 models/editor/sf2/pvpspawn.vvd diff --git a/materials/editor/sf2/sf2_boss_maker.vmt b/materials/editor/sf2/sf2_boss_maker.vmt new file mode 100644 index 00000000..4c82a259 --- /dev/null +++ b/materials/editor/sf2/sf2_boss_maker.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_boss_maker" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_boss_maker.vtf b/materials/editor/sf2/sf2_boss_maker.vtf new file mode 100644 index 0000000000000000000000000000000000000000..c47a9036ef685dca5e96ed3ed36ac14cd087779b GIT binary patch literal 22052 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 literal 0 HcmV?d00001 diff --git a/materials/editor/sf2/sf2_game_text.vmt b/materials/editor/sf2/sf2_game_text.vmt new file mode 100644 index 00000000..2e561aad --- /dev/null +++ b/materials/editor/sf2/sf2_game_text.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_game_text" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_game_text.vtf b/materials/editor/sf2/sf2_game_text.vtf new file mode 100644 index 0000000000000000000000000000000000000000..522665f05e1376414f09ab5d7164e2c01645a6e0 GIT binary patch literal 22052 zcmeHPdvJ~E89z%zs7u^7E|stbGc3sBqR?46q8_8l_Vi)VO zwU$uTA)RF<8x5*)DT7hgs*Tv1U@b~*?Z$KrI&Xi!H{Wx-zI^B8JDd|In#cU|yqD*F zp6B z8aGaE-@g5?1`HVR0kxHA08@bRv2n3eVq;^+ zBqSvK8c6HiyLTGPY@;ouLx&DCBO@bM?%cVvv~lCcs%6WTS^fL>-(A+L|2E{`yu7@< z$m@*N&MBufD^ng%K|#T(1`Qf~gZdC)DbN-8KA;8u^Xt{CcL$ZRR+CrNs8QqR*&DJ| zLVUc6jI1-;+krw%^O)x!s>j4c!H=h9tCmU8(b3QM>Dy;*Vq)Tg{sa2IpV%ico9NM_ z$J}1Mdu7M9iDMo$V`5_FLpL$~*vd^To0_(ER%@};-qlhhnBVuYGBX%hQa133zU99cgHyfb0K1cmM8bNs4Rs}wk= z!&VE&w`kFV_IiHn*RP*W8>`!{xcz!iheU|gbm{}DLcwo>gB_(AS$3v^w*w!WJ+Wer~Z>b@V^OPT_Fs{as zAt~55V_ELvcjRMqVxLa!(6K`%j;A%2oBlY?7ez%yWuPq+=bv$}zw!DSXiv9p-PQn^ z@d@#nu$_Rh8R?nnYX0K+>h$T;3a8AI1$zqA?K`(s*1K7%=!+sXaL_FLwxI!QFMW3#vO`A4q)TmKh ztJbaul{ainAM{ne%YRGf&YjiGn>Y2of#oIOKfryLpe@H}AM}NCprY3~7=IA>k>YRP zzFmj6#>-r2&5p~LFRLwEwmj5s+R|q0)~(VXFn{{yxgG;21MLh4uh93^t5?;cMT^8g zwFqq|Pn38gZy<-n==- zM%#=QvXAk3eMO(ZG>hg~3VoR!F3YQ6$Lq{`2m80_*A~Cn!H#3;gT8_(PVqkrW`GKO zImPF>x3a`3{?`cJ7n5a6+E^b1ECSq!Q~dv8U^Z|TI05_x_z2iwVHbcn)wQPVt@g_)0ii0IUE$@!^>9i9oJT6~(77`eb~e4$Or)F*oKID&c+Up8y^kw}Jh@_kbrQjLjUF3v*&_f!;e~h-=}!bY5m=rYbEh zWjun8 zI-tmz{HZw?~Jlf1b^m0Sb33O0gw$W2Y631-qIgJ z^IgPsS$k?f{K=hT=tKO_Cw()9o=b@z2K<>1@4x(AdzzwuuS@<%Ve3m6gR!nZDgKD9 z=FOYuJ#V9)5BTGbZhc>tf9uvQMU<43=)Lb_|8L{~Kf+k~a@}Aqs1w_Ox;EiJm!-BW`B;*$5@Pck8@Dj{QLIpt7GonyH_=B+LWJda{apl{)G4H zr(N=|=shuGn>w6 zhf3tn&kdFVe5WY*0%cz3;sJl+mj5?!GRL1I^f4A=GPdMF&D?_h*#=yCr3Ol!fCPYi zdI52O8*$73CY()k;}ABC%^c{PIWafp7>*JM|Fm!<@R&JO^LKatx-yB}Ze7!nk5%l}(M<1-$4 zGe_oS&$Y7Z2pu#Dg#Ru?75==>Ujn%0EbA!R*SL!2HE6^wM}hF?d)$h|A1}@s`MD7% zKW@eaYU7rFDI9bC{Ygb@OHOXZ&fo30Kd%jL}Kf}v)nd8fK2C=~{f1dw- z=OeC}tFN+;p^nX_4@ZL_HoE1{{m*xQb}@4`%jS8^J|>*%Zu#^6=RI5ECP$OIS+)%c zyQ*9MUm|l}??m1~!Q|;w7Ta*vZuysh#7c_@*S(@^e>ihCb92l80tT%BxX$FyYq`^! zOB=eYgt+C;{r>?#?iH6ZT$*z^0c!k^D!veu@y+*TRI-%>fg|4m9tQs@(0 zLz&ON!k^~?ma8&%r0;139CCX9Z{z<@*va~nGuMALAaj6Xm6Qg^+Rf)*(;Fb{P~@A z?o-V{>j;!hn0@8T9Lx9LHyzM%>Gf&! zJGO~WYA0K^^Eb`9&$x#ZTXVejai?rPQ1%LJS@!y8Sz!8+vS|AlX%{U%EITN#c;^u< z<;v1VExtrcpII0FY}4(S2Puo)9q<-dKD_G>8#Fx}%RA24S7w`29%etw0?W70coJLe z#g9`Ahc*xS%l*3^Yw;Ib?f=2Ld)Lhw*ImgM4NuSevCcAe(@>VY%|L;UbOT{UB-)c(=RsEwlb<(e9kj^ apXNS`zJG3^~ZPDkxHJNBvZ+XvMZ<>6e6TLB1PVBfIxWWqPb> zQ!%^z=n4}zD7|&(rp_sxH>k36724M8ROw;TcdlGxG3ghzyu2CTm7kqjcH)M^>UU`H zNJZynbU(b-z$(sF%WBplJA3^6)61`K(J;G8epc2I*#~A*FDv_*Y~TL8<=L%Uw;p!s zrI+5>xpU{&a&mG;HEY&vq)L<;G-%M=2OoTJPRo`p`|Y>iezlz7vBw^B7hG_`Elry? zy?xWBO(eNJ_uRA1F~=O!^PF?e8KJhO9dN(_omAgLn>KBR-hTV-Q?9-C+OcihwjHXr z`tQB>-VH4O^Upu`ZOoZ7$E{wy+Q$z$l1rW>f9%+? z?#3H$bT7Q{g41}{kq3+%InvQ?mo8mguU@?<>drdrto6#OUAlDX{d|Ica&vRt%9ShK z#*G{4r=PHN)v8rnXdE4mI_jvdTAEo8J@nAJVZ(-zU%q0+igivr@x;N09d=mXNq2OM4@Apunl8o|-IY^E!9#Tt9bbo_VIvQ^iLLRnE$4 zo~)>d_{+6u(ZZd6`sv9vf8~`|tbWyK{SF;E%pN^@v>)$`88h5*#~sHQUD>i_oz^MY zH1tL5^8L;`@3_k@yUdppk6S4?z+oebkRlDW_GlGwQAJ{Sbh2)K76>VSFfJy-o3k*weP9$mL?ojdQm^V}`B+~WCq+_-VRKHn<-n($d6qR9i94H`6X)2C1O z<(T*P-h0mt9z588GbZ-c{`>FGz8I9KW|1I@?&R*&lxRf%*kNd#H1jDpguZ?zFOuZMNB_uHrR? zHwya;#|f7RsozN0UD$@Y?Dq#Ac)%@Qyx2(|O6o))OFPwk= z`N(v?A9vhwN5Zoz^D{u^EN#TQ>V=@|cArc486{K<$Odx*Ijp=n>=~4|IE+Nci(*Tjg?{lr32-V z1%UQ9_Jof^Gi_OW==AsBf8Txm_1D-KzKs2_=J(xqpS$LoYkbo6*Iy6)vHCMIsK(Y# zg$flMdtmeC&3+zi-N}FS(MP>ZC{?PI9|LLd3Vktd_{Bi-vm|RjgL>#619%Y~4$wa9 ze&B%z(q@te$eT55R#JBDy6dhac~H4eA~eQ+HgCQ~9kbT-3C+kq;Ow){_I*D5@Wbxy zx8L^sg&akP0F;AI0CWhp#Qgd5sgsn`u^n&8sJVRkaxa_n^76cVrmru*{L*aL z?$~3G_4|Q+M;eHI$C%NB-+lL8a?B;%cU}D8^|Q+^ySV!G>nHUQ?Vv}eO`GQX+itt< zd?@|p!{?rR&R2y$O=d9ea^=dApBBiT^lc#fB!6uvK2!r+s_){t+N+njFyCgKH4DzR;*ZI z{6zB}`t<4J;7tH~(QHQAP~YdL*?(2D6+imuBi|PL2;RN?^2@zVO(J%!Y_{Zllcx6F zC!c&m)Xy1v571eQylKLJ3cnJ5C%jph3VKope%WWAeTcekx82suvM3KuojNtyKm4J_ zyo~0|f~@-S#~*$D#QyB7d~Du4VNv_QS3*|loaSvE>{q~KpU!xGK1icafU)Up>yTAu zGqYakC6!qwd`S3;@MB>tAk%?m%a%FDZRaJ{9Gf0cWiNA)DZ~x1mno-nw731S**Vjw z%y{9G!b!q?g>g`y{SLhR^2^D#(YNpbfNWD`FVB%FhaY~pPe;B3$R6?y7=yl_5;BG} zgdv#yZ)twtQJ1-U{xNmd}SkP_SsR93xu78U4@Sb z7YMyv!xqFxf}G)8`qo=-C2cU@VdRIkQQZ)rxx6q+dswEV@hYmo`QjYmqrw-2pUE!z zRpCd%B0*V0kN81OJMFZj zoRsg!pXt@8%;9|ElQ@5}rXXib`HcK5sti6K4?2gp#P`&`^e0AuExbk0u1*>jAOxb4NT9PNUP2OuP$HklU#+T@!&%G(@M zPHoo{BAa{t&WJqz3;us*ps`4qD*RCRY66j8U4-~e>IjR{eaikrLzmrm-+cytjz*0d zc{JI?nVmg%#1TiZ-_{G)3fBl12|pE1NZ@MyI*LdGdXZM^|GjhlrhGh>c*f z6TgA(8j|zH*%kTCT!H)UyD#(glkmp?7b(u1fm4L=T5bZ{DumAr75!a+H5!F)2>LUH z%pvoJ56#f#jCpI^xUoMMbG8B#{AKfnkBRs~;oJmb_cK?ShSu^&j!u_ucmTbKe>+tj z{0HQ5eq;_(l?l^AUZWm!##(^rXzX!NM6VWd2FNsw)w+1z562aabGL)t#=2d7_0_R- zajrY}+;hFZv`LdDvE`!e7xm4&h-mIjaxzW4+WUv*`Cmo#PaeF>84Wu-Ze3foYUORV zi!Z*|^R{GaY}w-a3-ggV8s`^6Zg(>Y?EO4^B;3znUtzO_eZ$Al&Yd0pq%bdx!?(yi z_BQFv5q>4c9+o4H?kM9cE6F)k905@s6O$;SSGKJYceulUAo42+Tejz0$a z(0SyMM;54yjoP(q*JPTtOBz0J`z|V^PZ168a}LfljJfBYdzM=Ny6blEFyHih)m2w{ z+nc>_W#U5ab+}uweC7^YA{CwZ!nt2aN9Vv-BKmt_q5J0laxx0(J;sX91R8R3ayCg-szB==(@09jS;>7Os0uf zdwsR9{oc1RGgjy}9q z?%5fpo#xsJCxm^KG|pZ@-(%x&f6d(mK4jKsDeb?o zO^Y)J;hw?Yt8qsC@0pm3q~4=Hc#k)SHVzA!AMAAdh99wK_u6YOe@~BX4}JKJxhp^p zz*C$R-~;^qdV}cn4%7SmIbT>?>4YK_z1J-h1V`E^p!ZnQQ2wHex$_QfFvg24MyKfw ztA9%ZZSX&GpZ6W`1oH!b@LtgP19^xoSrYouLmKlY;nvW5<3gJR9^lTHG-MF>@ytQE zM(_guE#y4!xTwP#LmTT1{k-XdX71Rqp_nVmFc+LF!uE=bvv)-M;>fyX;`1^!GAZmI zTCm%>FGl9@9p)2_uVJTQqgom0gD21h?0rJLHTG%7U+fupf%hC% zFD_&c;y1#s5Yg=YhM6W_?M)8#9`s=E@m>=-7N-;Nd7XUn$&NL*@k1kg!`idv%mID^ z?vLuvM&z@;O?@d6C)6dYML%l2`4$t#;ITi1d4;|bG5I1Dp zHEY)N@*Noujm)RbA)(D1bIv#O9Q7}ehTO5fu^lwd&oWKC+UqQw$M~_qcvphIi*Neo zo+Dgeqm%dj(21RHX@t;({=?>{E;gajKtA(~EJZI^J?4;mZ@wvue+AhP*5eLA;~cUT z=+EU`Pv6*|+*8}QSQ|TkTAGEud-wLToO@zRqdfD0tqbD&L0^(?W#A8F2EKhuqYUqW zD2I-L{*d;B&C7Z3s0`E3d2gD@ZW~WJA^eFRvwcGy^aN*JbRF-Q@d-!U=Di_vgHOf! zp$zr|bHf=BU21uR%(2F~GQ+gfTp_&|_8S*xo#0RWA>rDxcDzmHy}=o0oZ;yS*OB(1 z6&vyX`|nTENE^KC;H(7w(S8$p@A<7jKW{tc(;xmD-r7a`4!?On#W!ycv2RQ^TN(Ts ztUEHF{3z{|Lq2d04DCqhN0!v7QzvEr(j)6M&hA@*{-oZ6wy5vY=pr5Y%-eL*&}E$W z@KstF))g9{pSQ!vxR6FGhmV_mW8W6CpFsPz5Fd)@{#M9d%_Pv?blbnkzEnH_?f7-s z+w4maK4-rg?-P=S?a!X)jL$hY)%v3onJ1&KxG?K42eX14QX!EjceB=c9S4zL`IrBe?H=TgW}vXyF*)i^8!&-lYu|UMu9^ zJky0jdJnmTtjR7nTIjdlN$b#J=X7#@kx7?_`yV$TyG!EZsoXdqHB4~3lROu%OO9~xTAwTR7yB9w#Z*=*; zHEb~CSXd@i92*^f2WK2;XZ`Vip|ivC?0FUUkkotDFZ|D$oj1bx>Z9ixD;I|F5B?3#vc?yrA>*TGd*;RdZ-F+@ouU4; zc9|3AhJB_zwNayGuR|*c-&=T>;>iAzLIJ&J{X#$d4jt%kWR>N`h1mDldz=MK*R#H? zzpZmv7yHQe9QyzsvwY4OoQd%_pz{-ZAG-(n_BUZ}fcGiBm+-eh0ljDaLqBqx^+eZM zdR)kQqUYEn+)-LN&b#=w&t(N-Ekb$O!#|6-d?9i!#D4ldRQOwg zw!F<7j*D{%@(CVD=l&hv1a>U@pS8DjML)v-Q5_NXMV$HLZ5Q{$%zcdy=L%*KQ*#9F4xvoaPUx8Qs3BEqvObHTpFR}sfU ztS!DZe4*Hp)@B$c^xiU!HKd*Y9VjD=186Uf{z7^$>^I#wJPr-`Wlio;7oCOdj`{_x zU1(>ov*yfm9Q`^U^R{7u(CF{2cqL&Rq(eJ(X9^#&@%)w$8qnkHefCkvzr^7U&c0DT z!nelWV1DSAcHp1r7^o-z2)>>?A+~;BVO3!qlu`y*+}anid82)&^Ua+F^uePc?bJo) zaYun)6#ByUu&4Q7)-a88MEFgdy)Rn0-y9@-KzNKW4xpJmQ5@~mZ!26C?XRTY*d_2c z`XXAFG<148wgYwH|0tj6yusQp5k4ry*B?h4=_NV$E>ii;+{=NSL6KG1z350?c~(?{k-*O#*J1NJi*t()pQ)SKkfrt}_f8>R>^7Q)wY0PX3nJ#Dau@FPq};5>yZ zguK~D*KoIjy$KR$9BY^-5PJs1ZZltXDw;@(?u)o`(RAdA&Jy&CPrj%-PV%5Xoqexj zC4xFC-9(6w0iPrD1TIP-@Af&9+I$fQS7XayKVieeQy?+{y#Q~4srbg`LG~cDqxbOj z!~ts#{ab_fxIR;*BTqr}GWeSCAHr2a;~(g;`C;DpUM*aUe~>d0ci`C1iM3y-Jp3DR zVDhfC3dG5UxVrz#bmj@3VxFMS@O{P6^Xx(79{ccVVH`lSVO$wYM~|e7Rwph@x1Ha+ z9OI4l>C^201PLhR8Pt{IXHxXA* z5Yz?X#9h%~7RO1UbQ=c5P+ zp-2ry5LBco2q;{dh!iCOQBe^PX=0MI-|xRUbKE(c6B5kv`M#T#@1L1jYt33SYs#Ko zPMg+E%tgkSf}kZx;IDX`F$Ktr=2m*W{*3lx>tBC(RQ?q40p*$)0%s?^ojB_t$V+Ofk!Q@ z*6Ulh9xx|5y8ZzxKX&YxY0#j-ofRrnX!i2UFBX@L`t@tM;+UA2_%t29vGIdsOD%Z+31i*|b7|M0n$;Z&?x@frL&pMU=OnO?nmWv!2` zitXY*>^W(~%%!vEhpb<+WJ$AN!Gb&1uV3%B;n7DQ{mno1Gmjs3%gf%KLj6KZf{oCA zL;qPlHCxC)t5&U+kpyg@Prq;9U`!9vk}n6>XIxd^S-5cFq2tGoJ1bVKnAf{^?;^k1 z*hiIC)UEsP;JPX^7YuEmKYsl9t*ckBJ~V6Ato^VxPQzs*IeB=LF?Ue^NnqkZ7GiFh zS6+E#_=piB>N4;CF?#f9XW6o4(j9j5%{ToeCZ@_$DJjby8#iuTJEARq7OGsivfEdf zo94`!!@iQ#X3(HPO=rxQA-%MX8`ryXTx6+d3+w}~yho27Vx`urS5N6awSCK$Ef>w6 zJ==}Ni!Z(?{y9XO-TKmJD~jvizyEbo!k#-9Po6yK%DZ;$nx!uN?#|p9Qm$ONaehHh z(w>z27c5w?hMydN8uV*I$4Akhr+G?t=#pc3yhvC1?Ns{Z6e~wbm=$v15m$ z_)VEI#d+X?2NtsTIQVzEckk}J{r1}q{c*N$-|lqi&_Qv$2_#$~R5zdUCIU4v(NaR(8`V}TUh1GKyK)(Uw{2o{+yjVce-uSx?@kNnq3=S{^y|Tre`I` zB(lFXj6Gr|*L5DM)6Ct7a^YOpcgN@R9aHKhY;2|+J_cKN$&F)PZ{(N_{}qT?E__S1 zn)ONk=>rE2?C_KqW#?bA;T?P3Z6?=t%oF9DJ}tY%iFW}s+4{WUh8vde*|TRi<t zUiQuh^qh<0E|UpYH@E(;x#k*GSTd8G-;CNfZHc{VcU_I(8z50_}>9}ZvC_G zyLC5j-mI!`SpP~XPs`4?WJizg5BNS?#k^}wJL=ZY(O`@jGbT`KEB!Wd zl+2ttQ+se3a9BQz6e&`c^N{YVoKHXf)Oq~z$IZ%>uE|pv9FvxomZc5R(Q?xizc+(h zz%;d?wzxSVjX$o;(xpq~QgwBv(%N@P`Q+r}ihf%T=cpJ2fi}ROXv_~zV&bLq6B93M zZ_L}+C@woWqN!7-N-veOs>8WaK2=`BSmq9(Rf5%9)gu6frFMX#*Jn_U6j_&s$ z-An#dwQ5ycZ^Oe6w`+rnEpup1lK(zZJ|A%=b?b4yl`KJEHDdi1Dx9YMTpmrW(e4SD^)`|i88&y%nbXoKIL zg8NVFN2%KAEi0c}vT^p>5f9QB5AfJ5CZ3IfSKph@t$&_X82M8i1dXZU(5_v(g7W8| zm!@yuzNTf%mb&j|-Hiz7+j>eR`!f@LWyn@J+FSJOK&ke4+U(w&(Psx87pwt||2aD9 zpI47Da`k18J;5P%Zfff)_1j!UjI?H4u+|)&V=#2?q~Si@D1HuO;etKa5u`6&xs{Gc zWyT;NV^4NWBVn!+K3sqe`>e-pg5?lYJ;G>bfhbi4tuT}H6}YEjCM{^5 zO)HKdbBeA1yHj{zy>#;K4O2Gy7cAmvrMkMo)dQLwKO0uP+yL9J1E=Wn_8CzV?opqF z=D{}LZ7=Xt9Vcr3{l2IL?Gz`TxnvtF#J`KCZ`!o!*he3I^b=OJM!S03MEMHA%e#;~ zHZHitQkhG?izN$IhHNlc@mk?8C?V)_pi!&O76S!meGrzT;W-5Afdi_UA(K z*cb#dO|ROS#n%4aSFlAD#X$2;vB@Yr_AaN`x~}T`A+QgKA4wX3@wBhM{@Q&Xq8M<0 zc7m9Eb@=e%6T~BvcNV^%fBsp|1lNGaE~MYyI@s83np~wZ_7xo7P<#8=U_s%h8>F-5 zog$N(D;}vJyZsSyp3roj{T*MkX3bH}ef3{7Y}l}G&*pyn&vSdV^KCd6yarSk8Vb@2 zjc=A&Y*i=q-<|74-+xH!Li0g;zq>DzMv|`OzI_YBtLLA`HPhT93d~j5$WR{K` zIih`7b0Ew9h&)&EpuXnxx4>8Iqb};duRzPnM>QYJU9p;Hr>sr#k)&yd4jnqKKJvWb zC!QtkakYaqSI$T_okJTq1NgBtT{+#~1gUmj+7iTGahp?0Dq4+f8EJ=s&XwFQVHf;wwzia%l=*A8^ z8HL8(_!e5HOcQ%_956f4|JF^V>{~7YU z>i;lo`7Yl2sA8bbYAw{oRbXyWjwF@F_^Z4NS^E=Y`c5Iz&o?8>NoF2GKfj) zn{U3k4cTaK8-Gt8TR)jzwS49&-Yx6JmZ$H^{{f`$JEn1cPi20e)I7(p_C23svSGu9 z&%{4*;>0g}>$m^QFTd0|U~X_bFSLxUYTzU|63PbrKZ5k}+)VXVao}42Gi^}}GL>iV zV@hOX@IK(zef#zuKv(C(?xEV7TOM|bgBl<gGV&rH+-7X{1u4^~PhW#93n?c4Yv70I{O)@q zj0g6*PjSEmNPpL0}4G@*U^~Ar1LcAB%p2fEy*^gg#a_Z||Nbg%U zt}lQ~z50~poAYoo{eBC)q3fgepMfCH$m=sy9!Du!_sHDqg64zv1Nb@%$A_wOJ|6Y7 z@AGnb7Msr^&|UVluh?@x_r2ZeZ#VdhLANu=m;Yv7S3Nd)*6uiBlE`T=&q@dGX^F#MUu^`V$ zZG-NwQl-k9e7`#vTF1TbVO{S9?t6~#z1%n6bE5jMGXZ<#r_}8h+`pWZsh0|LzsH&X zdHSk%r33Y07JVH}zvpPo_kigjV&w1t=%GW0F4dWrvtBy9b4Yy_Spnbl@BjGYk9X0q zR`7B){#Kv5y2sZ#qQCm4OW)HBBp!=^Qu8s0D5)*bAdq7FezvDs|JyTw?LSlGIPaUs z*}hz8ir?~}BVg=brq3&A(0Jy9`)rIPvSizhmlXDO_r71L%0XB|pU-|kOC{d7A8y~i zy}loF#~$56K6c#V~XN|9^bbEcnz z^SRpd7SO{;n_*z$;L7tOs7lya+8>@7l{n>jypTGZAkuBvnLIEav|J#X$E0c~0^N zHJUha;z6Eu1o{qN0)3+|`3L-lZ8yJZ{D$X)dMw~R6V-meev!#@1GQDx@kyXO(73Jx zs@xx(vku@D2OD{p8-upe9HI-+}5t*N8$Mo6e!)v@W!UAM*PtMn13>Y@Of~9ZOpG^+1$muZxQz$y{zY2Q>XUy2==vxV z2AboVx0^v}f13j11}_@C38b?MoU?EG>qxIGcn2s3?mF=L3zbJN;uOZOo$p0yz+#}m zmc#u`l5ND z{wsec_#WuKNcw^L!9KE+d3Di?IC=er%hL$;Z!FODU=eW7^rYh3Yr66e0-a-bfxm^b zms37AB2Kwz%jGmi&jhp%0`LFGYYqt7FBOC5fcCgqIkg>54;vAuaK3~2G(>%?0uF$0 zz_nI~yzRSmv>pb6yFl1AVC!a=Qa|F9o&QiW8uU2OUhoER&jY0Dw-7i3kq=xaL)pm* z4;z0*(p>KVy8Rg&m*1xR2>2tQi*!yADrh;a>(vWoM?9}ir1P#n_Ll(3zn4OlRmnrH zI^{q$<-KJILY3i#qAd1M$WjirW#z6C|8i57uGP2sP-QE#zA1ajbqynIDp@GG*N7({ z){gamp=oeCyk*xZ|HSiBs4_e+x#}=Nrq)&{xz~t4AMhVaU$(@zux0Bbxa`%lI@Ye$ zwK|sXO|2}Hu9bQ9tzK|yb;I$)$%FN6Ih=ktesDeMd3kQZ|1Vf(%T^~`+44iB;#vOr p?3_<-ZCQKX)XHST%eOYYe49s46qr;0VR^Un`Tz1G@Shri{{@vYg=GK$ literal 0 HcmV?d00001 diff --git a/materials/editor/sf2/sf2_info_page_music.vmt b/materials/editor/sf2/sf2_info_page_music.vmt new file mode 100644 index 00000000..0c24a0a9 --- /dev/null +++ b/materials/editor/sf2/sf2_info_page_music.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_info_page_music" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_info_page_music.vtf b/materials/editor/sf2/sf2_info_page_music.vtf new file mode 100644 index 0000000000000000000000000000000000000000..abaea1be3828c2d3d9ecac74c60402cddd0e07b1 GIT binary patch literal 22052 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 literal 0 HcmV?d00001 diff --git a/materials/editor/sf2/sf2_logic_elimination.vmt b/materials/editor/sf2/sf2_logic_elimination.vmt new file mode 100644 index 00000000..97dcf7b9 --- /dev/null +++ b/materials/editor/sf2/sf2_logic_elimination.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_elimination" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_elimination.vtf b/materials/editor/sf2/sf2_logic_elimination.vtf new file mode 100644 index 0000000000000000000000000000000000000000..b1059ce0a87bdfd207f2a24fe10c211ffe5b78c8 GIT binary patch literal 22052 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 R?#t0wE9ez1@VjY&{{ftU17ZLG literal 0 HcmV?d00001 diff --git a/materials/editor/sf2/sf2_logic_raid.vmt b/materials/editor/sf2/sf2_logic_raid.vmt new file mode 100644 index 00000000..df6550e0 --- /dev/null +++ b/materials/editor/sf2/sf2_logic_raid.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_raid" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_raid.vtf b/materials/editor/sf2/sf2_logic_raid.vtf new file mode 100644 index 0000000000000000000000000000000000000000..669c28f378e75842786eb8e10aae764edbf992cf GIT binary patch literal 22052 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 literal 0 HcmV?d00001 diff --git a/materials/editor/sf2/sf2_logic_slaughter.vmt b/materials/editor/sf2/sf2_logic_slaughter.vmt new file mode 100644 index 00000000..d3e0d0eb --- /dev/null +++ b/materials/editor/sf2/sf2_logic_slaughter.vmt @@ -0,0 +1,7 @@ +"Sprite" +{ + "$spriteorientation" "vp_parallel" + "$spriteorigin" "[ 0.50 0.50 ]" + "$basetexture" "editor/sf2/sf2_logic_slaughter" + "$no_fullbright" "1" +} \ No newline at end of file diff --git a/materials/editor/sf2/sf2_logic_slaughter.vtf b/materials/editor/sf2/sf2_logic_slaughter.vtf new file mode 100644 index 0000000000000000000000000000000000000000..316b88bc1c9236d339379ea1facd82c202b6b0d8 GIT binary patch literal 22052 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 literal 0 HcmV?d00001 diff --git a/materials/models/editor/sf2/dev_engy_body_blu.vmt b/materials/models/editor/sf2/dev_engy_body_blu.vmt new file mode 100644 index 00000000..0fcba12c --- /dev/null +++ b/materials/models/editor/sf2/dev_engy_body_blu.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$basetexture" "models/editor/sf2/dev_engy_body_blu" +} \ No newline at end of file diff --git a/materials/models/editor/sf2/dev_engy_body_blu.vtf b/materials/models/editor/sf2/dev_engy_body_blu.vtf new file mode 100644 index 0000000000000000000000000000000000000000..b9cdb1bb49f14b737667c18d2381cae19c2a6f44 GIT binary patch literal 699232 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�XpNrwvSxo^qSj+_jhO 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 z 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 hoG7pJ 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 F{{z6czbF6z literal 0 HcmV?d00001 diff --git a/materials/models/editor/sf2/dev_engy_body_red.vmt b/materials/models/editor/sf2/dev_engy_body_red.vmt new file mode 100644 index 00000000..5f02bca2 --- /dev/null +++ b/materials/models/editor/sf2/dev_engy_body_red.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$basetexture" "models/editor/sf2/dev_engy_body_red" +} \ No newline at end of file diff --git a/materials/models/editor/sf2/dev_engy_body_red.vtf b/materials/models/editor/sf2/dev_engy_body_red.vtf new file mode 100644 index 0000000000000000000000000000000000000000..6c02f7cfa9d6b138ce1421008cfdd64db592272f GIT binary patch literal 699232 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

r+Ctv}N`_9DaqynfN-UG;(LeGLDKV8!0D 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 F{{z=vhmHUM literal 0 HcmV?d00001 diff --git a/materials/models/editor/sf2/masky.vmt b/materials/models/editor/sf2/masky.vmt new file mode 100644 index 00000000..3aad48e6 --- /dev/null +++ b/materials/models/editor/sf2/masky.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$basetexture" "models/editor/sf2/masky" +} \ No newline at end of file diff --git a/materials/models/editor/sf2/masky.vtf b/materials/models/editor/sf2/masky.vtf new file mode 100644 index 0000000000000000000000000000000000000000..7265d2b9ab83a67f399fc15036c956fe401c0f3b GIT binary patch literal 87616 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

;}uhUT_2)24}!|a2A{c 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 z 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 ad%f8^=*`GY=VmA89lF~V|H$lrzy1MOQQ;&2 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/escapespawn.dx90.vtx b/models/editor/sf2/escapespawn.dx90.vtx new file mode 100644 index 0000000000000000000000000000000000000000..88ff2897980d4a44cbd99049f7092a385bdc38be GIT binary patch literal 95112 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

;}uhUT_2)24}!|a2A{c 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 z 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 ad%f8^=*`GY=VmA89lF~V|H$lrzy1LX`{79d literal 0 HcmV?d00001 diff --git a/models/editor/sf2/escapespawn.mdl b/models/editor/sf2/escapespawn.mdl new file mode 100644 index 0000000000000000000000000000000000000000..e1a6f7526f54dfe0a5ab4885ed9abae2e0347966 GIT binary patch literal 1956 zcmeZt2@Ww}U|`s=@@jW#N@huZk$!QSk$!4%a$-ShaY15vo?dQB4p0FRU~q8~40ov2kp1*zLHwZmtum4g^mS$YccK8B7qd0%iyw zMjwIlO;`|mKuYJZF))B=8K7H0G{_7UAR9yrumTAn0GR;-AU;fN3sjC6+68Ey29jSP zdK&COc7OoL4Ilth1LGfn+6$3IkT5$zA~Rry0NF78FqIRbZiMlNGrbiUumAA?+H0W#421Iz0mcin(01hGLF7R69u6zc35A3G365yZm**$u)l0J7^2jDyBT5{5D% zaUTWr6U=P_K$+CM^vw98)D*qq+!Rbb#EXD}Y6=iv62-2k254!;JSU+WTit}Pt3Vaa z0S4ShUQ{t$ydHKHagbVk7?)m{I4Deh{QLj^A+X%TW-mw{8G!5rsl|tp)nj8L$0bPq z$(jHna|Fb+WoU*@SxcJ!3Gi|r=sP@>V_trIN@{Xq4nuKCVo7Fld_hru0Yge^T4HHV z34=pXY8nFwgDX>@a9KPsO;yGx<)>7F5*9)vBQ-IFAvZrIHK#ZRVp|NbDvn`b001yg B=->bV literal 0 HcmV?d00001 diff --git a/models/editor/sf2/escapespawn.sw.vtx b/models/editor/sf2/escapespawn.sw.vtx new file mode 100644 index 0000000000000000000000000000000000000000..3bc1766cc6495c16503ad3a3a3e482946f1cc67c GIT binary patch literal 95112 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

E>>S`tn_k``XXbM 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 Gzy1bOwm-T6 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/escapespawn.vvd b/models/editor/sf2/escapespawn.vvd new file mode 100644 index 0000000000000000000000000000000000000000..07d41be58fc6bbb8476d7a0cdca0ea020e0bfbda GIT binary patch literal 357568 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

    {ziI`vA2;^1j7aLyKOGG~EQ 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 N-}Q7Hzs(Hr{{ZF2W1|27 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/proxyspawn.dx80.vtx b/models/editor/sf2/proxyspawn.dx80.vtx new file mode 100644 index 0000000000000000000000000000000000000000..62533f318215c4ff4423a5dc37052c9e4c288af7 GIT binary patch literal 95595 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 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/proxyspawn.dx90.vtx b/models/editor/sf2/proxyspawn.dx90.vtx new file mode 100644 index 0000000000000000000000000000000000000000..da58a8bcf7dad416a72242c32ee046eef79f9797 GIT binary patch literal 95595 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 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/proxyspawn.mdl b/models/editor/sf2/proxyspawn.mdl new file mode 100644 index 0000000000000000000000000000000000000000..e1d841c2dcabf27204a2a4eed9c67ddcc78472be GIT binary patch literal 2144 zcmeZt2@Ww}U|_h~*ZDs+C9@>INWVDENWY*czoN3ZAhA48FE=FzC{GMX;6PO+5xvI# zK#JG?(+(0&D(?givOGV2kRf5?o>we3jyoK7J3jQD;e@IK!4m{B8G(2P6GW_l8N!Fr zN1%KY7K9$45Q6|a0|Q9Q1t1OLgUq-AWP@k{Rv-ZcATvM!#D|G(fyxm>{|1`(0El4# zqNl+gWCsX<+yDYFH8B1GsJ#$b1PQYfBr*eL2#^ia4^!#M24f9>ItLgZ|M39aA<$rt zBFETZ%>c!@f}Z9u%)kIw*8o%x!a#r`1`&dYH9$EC3YuPE0?9x=Qq;kOG3W)r^a@hP zgCqmX8=di}c_RfWZ6hhiVA}lo|33t*_Jboxk{EY@)DfGnL26;(Kag?e_y7MdfoXXS zG(Cf&2_}C9$bk6=J+Fh@bpxsp#0Ftl6hnnksIzB$>_8Yr5Dy1rHweQ3$gVpu4jLOt z7|Mjiy&%v}Ft-T+Wm5CfGvmRfpIa=Zm-=R;t5 zYRreJ8d^2LB|vt9)Z)W%#W)z^XdX#fpbbssI5eOM-$mn#EF?|;dNln#V08DZ92LJ#7 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/proxyspawn.sw.vtx b/models/editor/sf2/proxyspawn.sw.vtx new file mode 100644 index 0000000000000000000000000000000000000000..e28eadecccd6e251b266319484339e29eb6290fe GIT binary patch literal 95595 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 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/proxyspawn.vvd b/models/editor/sf2/proxyspawn.vvd new file mode 100644 index 0000000000000000000000000000000000000000..b488e2295d8f173240b5ad776da76a85774e0e77 GIT binary patch literal 361792 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

    cU&^%7_YhmBN#t=df4@6r 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 literal 0 HcmV?d00001 diff --git a/models/editor/sf2/pvpspawn.dx80.vtx b/models/editor/sf2/pvpspawn.dx80.vtx new file mode 100644 index 0000000000000000000000000000000000000000..c07bb5a938573186b8eb57b61d95c16e1fa785c3 GIT binary patch literal 128418 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 H-+%rO)SPEv literal 0 HcmV?d00001 diff --git a/models/editor/sf2/pvpspawn.dx90.vtx b/models/editor/sf2/pvpspawn.dx90.vtx new file mode 100644 index 0000000000000000000000000000000000000000..4949fa06b6fc746e44a97c9cd1b4fcaad17b5600 GIT binary patch literal 128418 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 MX(e58s77P#AG{t7^#A|> literal 0 HcmV?d00001 diff --git a/models/editor/sf2/pvpspawn.sw.vtx b/models/editor/sf2/pvpspawn.sw.vtx new file mode 100644 index 0000000000000000000000000000000000000000..7193f4957d5d407c42360799a4a41be18212b9f7 GIT binary patch literal 128418 zcmaIf1+*2_|2XU;(t>~}0xAO1prVw7NJ=RnA&7K{V1P<@mo!Lshje!%T_PZzA|@)> z@t=FYzh|#^E!KMPU9%$#%2IWs#ZNi6zF5H}@@-5+Js|9D`{eNjH??tQVi zJX%7mCN>rm0FI{f_^e@oL9gF2WG^t7cDaeM!}xAqZYUN z2$7Mn!>sWl6JQ*~9hvymr^95}Y4u&O8@9qE3r~cpFb(FzEEo*~VE_z*UeFtQKp%*n zyyz#|_rA~#X2W)yx(&9#5X%gPVK5Z>Lq8Y}AHyfm6UM?ExG(-Wk2dvBk$pB}FC2vZ z@EI(z)-qTQD`5q!hEHKB%!PTd5Ej5 zbbPwOMhmZp4ba^(FGCNg2Oq$j&=TsyThIU+LL+z^8bfPn0xh5^G=t{wF1!ctLtAJA z?V%5R2tA=Ubbxm75p;qy@DQYiza79h;RpB;euAIjJGfuT2_PYqhl)@cs=zDoD!dLgpeDQq zRiP48fa*{i>cAUN7ivK@cofn>DtHjC+V{sro`AfN37&-LBQ^SoUJ?&OMtBTzK|aV0 zc_1fbho>M1q=yWU7BWLt$Og~Av+x2GgW^yWO2CUy5=y~KP!7sMX($6(;A!XzTOIk` zunqRYKKKldz%e)qhhPuvf}OAf4!~hJ2>W3>Y=%v+4$i?@I1d-#B3yzia2amGH}Eyw zg0J8PT!(8=(BUltg`hALfal>k$PaB5_z`>v?V%lXgbvUJIzdu82Z9M=naEl2z(5~ zU^t9`Q7{_D!5HWQJ)jqKhn~<4xP#+pX11JPVpfKcxjj#!}!e)35o`+nJ8*;+akPnK&3-B7$f|^hp zYCv_U1~0>_P#!8lId}!?LKA2U9pMA$0Pn&3@D8+wccB%ufVZI;G>5m~b*KUrp)yo} zs!$A`f&5SannFu>7K%ezC<7&+G`t9L_cmf`SjF16RK^jO6Y2hJw z7#@Li@F=8*ryw(AgDj90ns_^H1dX9NG=rw_dx|(k@4YwSH~1R9fm`q`d=KBjkMINB zhM(YP_yz93UHBE!+0=*OQFsI%g4B=}(!hfdUvS(mrxJ-jgmIDNA`d`PNCrtDF(iV7 zkO1QIxRUP|iT?^27dh{VI|mowB3y<`a22k=b+`s!!40?xU&A+W3%-MI;YYX)Kfw2J z#nW^HuEHTW2&dsJoPjUl9Gr&>a1k!UCAbb>!8JGupTjBm0uI9wI10z$IGliOuoHH{ zI@kldVK3}}jj#pw!4g;stKc)(4+mfkY=*6{0k*?hSPeNKBRmf2;VDQ98Q>vE1F0bu ztb`S?43@)s*aQ#5Bk;e={n3BZzw!UE5&na};ZOJjeupH=NeYP}5hR5BApzV6*&rum zhrEy#o`x)t2Qote$PLd!F31o0;7NEEGQl&D5|Tl3cmf`Sf=~#agB0)pJP1YL1tp(CX};4MCwSSyrJy_%hl)@P zsz6D21xmn+@Dh}UvQQW*K?QgfD#If96c)l_m=6nJ9?XR~FdH6)bkGhyg14b1w1tt- z7@9yI=mfo?HM|2&p*b{z7Vtj22W{X3_z*fkd*}%7LMwO+M#C`p78wcU+515U?2>FAut$*!f+S?Q(z)YfN?Mu8p3p#1v6kKOoOQ~879Ga_yoqlC};%L zpgL5A22cZP!fQ|qUWeLH2kOEbP!HaO`monmqdl+>K7;*m01m<-I1ESNC>(YfV1!=oQ5;-cKjFL==1bhcnexWYiI@Uz`O81ya#Qe4RnAHpc8xuA3;ZG51pYM zG=@gd1e!w&s0W3h02F~2pePiBSD-jlfD-U3ya?r?B)kNrpbS)o(ohcaKvl>K6(Jv# zg{R>ecp37;^H3iOLSd)`RUkJsgr?98o`W}`0lWcELT1PU86guq29LuNkQyF@H1JjY ziCpvw{~E5rP52&ufje*)?o;5eA_+wjK@vy|$sj2thezQ7NDnFC5l9JXAr(9G1co_!Jhy zB3K9uU_Q))xiAN2!%Ub3GhjMQgQ+kXrof**BL9HD;V<|H{)PV_rkwjA0VIU`ArT~o zB#;!6L2`Hi9)uK-62A0mISc3D3pfR5;5?j$>RvY0;5DcLHK8`tg1S%#UWX+fVihcd zJ+J`|z)DyTn_w+$gw3!T*1$R#0Yl(p7ztZpJM4q~a0qt7Za4zFU@v?I2Vo0LgyAq0 zCc`)w3lm^2jE8wJ2j;^Rm<==GQxY=a#z8WzD~ zSO^Q?DX+S$kPWg!PRIp0pu2LqKsV?L?V%l9f(vjFuEPJ?T}B^mmqjW<6?he1f!ClC zybP70D%61LP!p;_1trw1O1^N^nxDH8+yWr&=x*|51=Ew4{e|mbcPPl8s3Ih@GiUu@4#Eo66!-k zXaJ3&5j2A)&>WgV3&;o0z|)Wt9)<@X9Xtx@Ars_<+>i%yK~Bg4Ss@!d2^k;-q=iS| zAxHzM;Xz0R$sjXihsWRvxEo&|6aBy6f!pv4{0QH`5AZ#F3*W&txC$5H0-T3a@CBTN z&*3;6gA;HRj=(`U42R$VoQ8977QTctuphR=CfEq;U_H!(*)SL8z$};nGhsUHg@54+ zT!u^VGi-pL;5z&R_j@JW5J@ibIAnpO@D%)Ktpp-T;4ibjg0JBw+=9R1SNIM7gg@YS zxX)uHhKvw{gpdg4TYW8ThJCOTK7(Db8}`69*aE9yH7tWAum~2y0$2lEVF!E)OJOCf zfaS0l>e=_Y@Fu(gwV@W&f!85FJPQTkd3X*Az$lwB68ggc7y(0JI1GVdFd3%8M3@Al zVFHYSu`mukfhjNz#=~G32!r5b=mWi>AM}MD&>ea~Pk0wvLtA(c-iJ2u4m5{m@HVuB zx1a^If^N_SIzw0J1RbFRw1f8W5qtn2LQSX*wcvHA3w59#yaA1&5j2G+&;Z_qhEN}> z!mCgns=;ee14=_#C!7Sg~&@CZB%k3u>~4;djbB!VQ66p}%5cmPsBYDfhSLQ0tH>7E1gVIC}m znJ^uu!Bm(6lVCC|hh?x0w!$vh4SQh^d~A_ zVI6FOjj$P-#J7iw-l!WvL#POELOG}o4WK@}0(GGlREF1}4pf4A&=i_O3wR3}Lk*}2 z)u1ZWhF75qRDhQ7HZ+6s@G`s(Z@{zg3_K0_ATQ*Bm!K4shB8nVO2Ug!0*XO#C<-q? z5hx6Wpdb{0=ixcX52+vpJOIhzLAVbR!2OUAzKd^s8NCi~!ME@W{0x7=ACOo%i699i zg=7$e|E!);BsHXghaeq1439usNDq&~V{pe~{Rw}=zwi(I4tL=<_!Yi^ui-ZQ1V6x! z@I7R-VHw~vv-ZJW*bfKbARLB6a1@TfF*pvN!wEPEU%;1e22R5%SPg4nEv$p}umLv0 zCfE#HU>j_O?XUy(z)si&yCJ8;n*(w~F31YmAsajenIQ{237OytcpT2#jI(eKF2V)4 z1ef6oT!X9d6qZx^nt!G117^%m;%#a z5=?{%Fb>AUO!x#A!CY7fb6_^if(0-S=EHOt0^Okp^n~8f3mU__&O z-iP;~DKvoQ&;nY*ThI)eKy|1AuR$ev1u8=oconKbHK+jP;bnLc3PJ%W3dNxayaXlT z1t3V0BbLsCcv z3E&a9AAXDPyApj_N+nzLIM~J*WkB;dQ7Db>Izn6IOeOm9Prd zz*<-b8(=+bg3YiI7Q!M}3Fzz-$-;F`N2de1E^_ zz3x7dgpdHbS*t5_fzHqgIzk6%5AEOs_z2pAD059&ZdxEtT6G!}&swQO zQo@7q0HlCqa6dc+cNCacpI{0=|CvlcE4*&#pV zfYk6fq=EGC5Tu2N;SqQg(!mpu5uSv{AQOCVY7S6#XxBwSn7%YdyFdt^XbeIcY!dMszBVjZQhXZgBK7+k* z96pEhZ~~6PVK@Y*;20c%FW@wMZd8HeZ~{)k7jO!`gfnm&s`y4!2`WQXcoklOUbd$n z^o0S?9|pld7z`i75Xj*nazb{<22VjIcmi@kZpZ_9As;*oPs20t9OQ=r@H`ZR!cYi` zzza|mibF9d0WZQ!P!dW(X($I}pe$5?m!Tq*hbJL3WQ8p7ztJ(GuMq#mkA)HUUBayU zAR*iji6IdrfuxWOl0yo303L*tkP1>m8h8lO!o!db9)U+8Jv;W5YnJspHz&>ea} zH|Ppopfmglf55-+5Bv>(L0{+%eV`ucW_%zieKPo_z8Z5AK-qgCxj%B2oggy0(bNi{lCZHKKS1V*3qngmHZF< z4S&L4@H_kgzrn9?7w*6b_#BSGaX1P`;Hpi%0@vXhY=%v+1-8O=*akabC+vn@uow2g zXRr_U!vQ!12jK`DhNEx{j>8G~98SV1_ySJD88{1H!g)9c7vM5ngiBCWJCGVs4PJwW z&=(pTbgD0*fKL-FX}ygB*|zazZZ1 z4SC>c$P4-4S$GDXgZ%J36o7(I2ns_HC<-q?F(?i%LJ24dFF|Q21!bTtybR@`96SM; z;7P~~PeE450@u~!-+*iI6?_dh;TyOG-@&)=J^TQ-;YauxeuBGj2Y!WL;38arD{vXE z!X=pQxMb6_^ig!Kws2YX;G?1eS36Slw(*bbXu6|9EM@HuRSlduhr!%;W@ zyI?o$gU?_C9EOc>5Kh4tZ~~6O5jX_F=0ZD9?nlrO+CvBE1aDgX4X6k8p#e06M$j0VLKA2P&7lRn1udZ!w1#)! zU3eR6K}~oa>OyU(1J$7hRD;)`D!d9GI8bfiLud={!8`CiybH~t8MJ`6pcT9gt)V3} zfkx028bcMR1g}74C=X?!9J~w_p#r=J#i0b0gi=rjO2bR=io;t4Dnlix02QG;ybR@_ zER=!LPzqjxlJFvwfZ|XLioy#}1fI7$&p`nw2!){#6oFz;6kdS4uK)ZFzrt^D2Y!K{ z;U~BaKSCZ4ksI>y3v$2{@EAM}nIJPf30dJO$Oc&;17w8s@F=8% zN8n*d3uzz~JOrsBIV6Q-kOES|gYW=+>k`2?@Ev>)x8Q5I317htxDMCgDqMlfa1k!S zdAI=Q;4FLzXW%rPf-m4CY=Ob@ zX2Beo2XkQ&EQAFxAEv-0m<&^4I?RBXFbzh+2p9#UVGN9gPhdPugmEwd)_HSS18ZSD zY=Dih88*Qd*all+2W*F(unTs>XRsIc!5)|k^I;w=gaxn&7Q<5b6qdkpSOzO$6|9C8 zFwnsq0E1vK41uBWF${-c&=tBscjyK^p$GJaUeFKvK!4~96P&G2gg(#@`a*vg00UtV z42B^v6h4MwFdRn02p9#UVGN9gaqtO@hj%Q{*LK}D=+QJ9$5qt>kpgnYi4$uiYLl@`@ z-Jm=4fL_oWdO|`-1ouN?xDOJ*f6nz{BL9l~1AoI`@F)BMzr$~kQGt&_ddL9j;1PHj z(!xWK8q&ankP1>l3U~mLLo!GTNuaHl#0Ss@K7{w-J?IYupda*wKF}L_K~LxoJ)kRe zgD%h+IzdNh4;`Q#d<5q_P3Pe(d6ZwH|PrO;3Mb&?csfR58A>9@FBE; zi_SRC!Fjj>X( zI10O96KsYpuobq!cGv;CVJ8fMfiMUL!^bcbhQlxz2_s-MjDj&R7RJFRFagHHWS9t( zUIzdP13|*ibbcG(!9eP49=nZ|K zFZ6=}&>wO^PRIkfAur^Er{P(62J*x6@EjC`0#FDF!$@yvBVafTgO6b-41vKg2nNDT zm<6+84ors`Fb$@{6qpQ?U?Pl%2`~;mfw3?KM#Ct0%3*l|GQpFO8L~iD$ObteJ3Iub zAT^|chaoLI0_h+^C>(?1Z~{Julkf$cf-`U$&cc^) z9?roo5?qBVa1E}*4fqPahMVvW+=B1n2ly7ggTLWl_zV7lKj9BZ;z>yai6JQ@ zhh&ff9)OhaAl%iK_*eK1LT|Xx2`+St3w`K9$Gy-^?+>eo?sdP5gf4iYt6J#67dq~R z&V8ZdUg-B1dijNpgrT!x=q(s}6o&4Hp|jy_n{fyJgwX5o-adVy-(TpJ8TwZi&;zRw z6oevB7+!#)5c;1M7YQ9&Ls!{*d%uQ0p`iz8=+qhdV1}Ngq334k?-@FEhMt?DKV#^p z`Ip1;3;Ya!!#@!EpN7t%p^s|lB^tV~hCZtI_BIV2Hh)s`e-QephJLJ}XKU!;_^*Yt zY1W$^a=>mQ+UA2+ULTDbpdxF=~*%Y-|@h5N^a+m(h}tj1pwjw>hrx=S45-)!O#-x4_v z|NDvXKSqc@35c5&f8^p2|Kg29{9r$Eh#$N>4tZ=(xM%CVw{y*9nQ*VVaQC`!7rby& z+Hj-T!qy76mrXAc?zNgly#TVWflg3vwlDX-@zAqV7zJdhP~!9$P{E;$vt0oUOh zxCP%r=p`C@FK##T z!WB3R=iqZV0f*o?9EPKC1dhSI9d)ld;E6q7S41vD8nd!MW(Xa1L)YB{UQtKk5*&lj z%`^1wylhtJ{TjN9hW@9auWRUv9(vk_F72WJd+4_w1_20N+Qaw&p+|fe79jM04%oX5o3T5|+UVm=9B-ymBf-6{rBOz*qGAVGdNCz9%4f;ZN=nTD}H}r!(Fj2|< zp^I6apd&1@%m9)8klvH>1njo(7T5rrVIvGw&L9{JLtrF~hlwx&#=$2r95UFjVIo7} zV;BXaVGN9bui+M4gPX7%mcde30-wTSc-Wg>T6hAo!=tdnS{q7JB1hJ*w0m3j%D@DSvQDN+?O=g8LrcNvQB;10#a1&<0SQrQ6VJ1w6uc1GD0ezq! z^o20YR~Ta|3^Eo5A`63zEwf)?46aoot6>d<@y9lZgmJpU*jZt~uI-lD2w@zmFf!Fm zZ}>Oh5FCc9a0xEM0K5F1NEqEKj0qM-uL^@!rLk}r<|_<-6~?>@!-a)G#=?+dVSKGH zn$~7J7=|VbgK^#RSl_@8@ICwp-@-Mx4qwAMySx&XzzSFeVQjG>B7|rf*kOtlJCN= z@CV#y;ZzXr78mX)cgMneVH<3Rt*{hk!Wb9}pTK%p3+rGFtb*0B0#?E@SPnB_I*fyf zFdnAB1egr7VHPZa`LGb?!4}vAdtf&lhXZg34#Pp%38P^L?1Bxj5f;NDm+&rXa%j|9e5Akg*Na$d;o2s z9efBML3`)`9ibC+hOW>Bxa9)rKQp1~$VM>t);Q>evNg){|fy9sq62kqE z0Pcer{AW}Dg@2&FEo=-8pdtKa;YK2Vi-cjLraPsY2Vsz?DI$v@jD~dYh)EMIGXcVQ zR$;8FFpSlB%S?tDFbS?hC8!8tFs0CUGW4drYMIc{@|ws=2t#;LM(1)Jdz?1jUy2M)qcSO@E&I#hvI zpaQ%K_YQuw&#W*&Rv0@gjHWflGAkgAHnv1$5iErHFc;>)G^hnNp$5DLo#5W?v|Y^# zLyC11=?T4{2ZS-o`ik^{{?HEwzz`S&gJB?i451V5J0hVEZ)=fe&R+>re;kLREMb>OnPVrQ}*7HQ_a=4j);#hREBl4Ag_VP!(E3 zaVQGK;01UG-i2217PN%sP#c=T>(CV1!H3WRK7uar0lW{L;XP;p?V%%df;P|=nm}V{ z2#uf?)PV-@2GoZ)p&FEc!cYWqLw?8u1>kvj87e^qs0>x0BD@IYpeEFS*PuGQ3a`LR zPzVY_87K?op%j#clJG3#gXbUzPxA zJLmx)!bk7{w1wAPtE>*y;8my!Rp1qP6BE zU3d@L!29q4w1bbJEi{Dsj@X;f1R6nOXbKIW9yEiupanFC*P%MpgjUcU-i7y|C$xdi z@IKUm*PyXIZ3Ml{>JR;(3v`8UPy^a4=R>G#)*H|gUbakmC0UFp5| zHVwCqU1i}lFcbE{UYG{^;UFA>I z8K%G_m<;`401SjdFc^lw$FKu-!YF@42PjG3`W677y+5Rv_j*P&@LsLSy><}JO$Yy2jqdAkPC9d(~uYP z!84E_o`vTiG^z>hNkW^G(9|R}M+vP^?rpLXTC;=}FQK7JX!;Ub#e{Y;q2)|yW)m9K zgl0CO;mymoIW*3xAW{h`LKUbCp=nTPJ``FOg?2t=eY20k1=7_f=2iO{fbE zp*}Q#M$j0VKyzpTO`#ct)|eaPKj5QJnJo|+G;S5y2BD?pc9GB;Gqk|3XgE4XWE6~q`7jR_z(SY{b6_^if|)P_ zro%Lt3X@?9OoGrHccMsWvKw0St}|;jtbw(#0oKDRSP5UafO8T~!znlcp@G5YBB4>l z5s^c16b{1y2n}XJTp#f@Wo7&YfouM0ahc3_wIzoH+2--m( z=nZ|L2lRse&<_T{AQ%Wk;bRyAgP|vU0BxZSw1N)sA&hdIN5E(p3Bw_@vl=Ec6o$Y+ z7!0A|R%p}J*R1}~4|+pS=mWi=J9LE}&<(mkN9YWlpgnv99iSb22yNg4XbbN{Xm$3U z$h*)QLaVA)BB2pfXnEDltQOE5nnELJ0*#>oya^4VKGcIc@CMX{*P$lVhFVYqs=;ee z9bSbhP!(Q*3Q!p;K}C2O%0oFQ3uT}*l!BL_B)kZj;4yd{GQ$(_B)kC6!Sj#@azj4I z3(vr_@HFIyf=~!bKrtu^MW8TTh`%%teR;bG=b^Y|3P5&v3PP)x(DWrVhRJ4`9FP-o zK~~5DOXIH%McWf<@s^1!hm{a&16PUMTZg#DtpDJCkCh(&G3#GQYF0u>1c@ODB!lFT z0v><|Atj`O)bJ3bfwb@lq=Sbc13U^DAqMwB0$5~ELxpUp8(nPHe3%QNB6Xg~Y?uji zU>1aW&rk;xDnLW!RH#}VXRV1a148w5sD7PlR;Y&!HDaOqHB^m-s;HsX3N^E#9yZk8 z_P0!^H4Amw)65DL=+i}pL8v7U_2&0hmxo&NP;VY;$wMt(sB3GebfEC=3;0 zp$an8wS_9SPzf07BJZt&4E2?vUN2OnhC0?z0U9b>o7>c8&=i_LV`v1Sk~GxkhC0Mh z8yKqdLQQC>ObpeXp@J_|0*1Q3P;DA&T|*^cs0j_VfmLl-s9vlra&NuxD`th-(om-v zYMeuj>Pr?b1|^^*+*=_VDnLWMXQ&B%(OShJ)YVoK3Dw*+MXEyv$OwtC^tUpA4hri)I%ls+w5BvppAc=*OLkdU=i6AljWc8on z7x)eC!mkiUzzCyB+%~I_l8eF%Pz0Wb!cY*Ng#z##i^Nf*gJ={CKL>PV{49F0MXb7W8grOC}APHfNh2NAMMy2>qj5V+p*1>w%02^Tw zY=$kc4YtA#*bc*NMnC8eBVhoHfs##7!D&~DolehFd4?e zMEC@zz(SY-3t%QJhefag=D{pj2Ag0hY=-%;0cOKo*a&lA9rT7(@DcQacF+UbLwD!^ z9ib}>gds2#K7>zUF)V@AuoBk78dwioU_6Y22`~w!Lr>@fePOAW@oHEG%V7nqgn2L@ z=D=K-4YOb-%z)`I4JzBPits8_f-3L|jIsJ?7z>kODolZiFb>AUComF5!33BD6>LU1 zcp1t=Sttc%pftP$C7>j{2*u$AC;0`X)pyQz$Y*s#=$5U10!G< z42O|03+BK=mhM(XL`~r93SNIKnhri%Y_#6I!b+7?8!e-b6 zJ75cJh3&8nR>CgW4Le~E?1iIn9FD;WI0>J_7jO#Bz-c%OU&1*!4;SDfT!PDR1+Kz1 zxDGeqE4T@_;A>b7pTb&L0_$NZEQ95+0v5v&SOsg~GuQ|F;Q$|z+m_o2EtGn z1OuQy^n*Uo7kWc4=m|X_+@-5_lDJTGVId44UrQw1OeEZ12Im4#nU_C;>&FFcgIspa47%g`gm0his4o zazbv%1$iJZNg2)?!OcW?{7fv@2! zxCuAlI$VQ&uopgq{cr#d!Vx$Mhv5*Eh4N4a%0Ve84KG1SC;=}*aVQ2w;RV6p#`ggw&7<(n1<|7#@NIa6f$IT=)ij4L9Kyd;{OXx9|ge4?n_f_!)kJ zU*Hb>3U}dm_znJqKj3fp3;uzBA!e8V6G*L8_s;Agl4 zzrt_u2mB6y!JqIq`~&~NzYwz-_d#Mv1PS4O_}=Q@!4L2g+=d_FUGL#--~(t2?cgJ5 z4+cnK=QD^Ln5L20M}WuPpShcL2hN|6U46{Lp8Arm|S z$>9M=0R^E56o*IPQAh_>pd!2s&qFbI5wgNlkPWiKa&NF}U@feNb+8dOz$VxX+h7Z9 zg;oCb*1$?w4eMYnY=HH!5jMdV*bLiXD{P0|unYFUPS^oU;8R!%%U}g8hg>bFI047ub2tfKz$rKlXW&aX3+LcGT!ag72`#g>mF5~;h0AaeF2Myj59i=4dL141@tN7zV)*_!x%5Fc<;DVHAvn(J%%+fw3?S#=``d z2$NtkOo6E|9j3udm;tk47R-UUFb@{Md{_vJU@h1IYUR=_Ig2mN7=uRXJ2 zKFo!AumBdqB3KNc!V*{t%V8O;fR(TsR>4|W1M6TtY=lj)88*NV{)v8qAK^B958uJJ z@C|$ox8N(d2{+(6RM$ACDpZ5lpa#^0+E5GX!0S*C-hjFgRoq8EaVaE#`ynwTgd~s% z-hubwb*KY%;SHzO5trtS<%)jOM#>e-9p#y# za`QxKo><;yX<6gbe6gov&qRO4rO(Fa`D4#Tf8~qikJii|<$0nh zZ?r{OV$VnE^RWU^S|Hlq0`dD$AXYHSb3{|Y=(kX$g3&UCqN!lCMB!MGX#Zb`6^#{( z(qggVu@X@pmllt`7%Le~#iR74c$bRuQnAueS~|+hL}{5=+32@iq_VN{QT}o?m5+Wa zM5++07@ro2@*>e+m7=s#^jMW*m7}ymv{czBEgSt^CYt`2SB@{cT$GlJ9`VH}EfJMb zBFam~szi_ZO6=8G)hMkRzoo^Zym&Ol%_|w@Ridd{v_~0Y)uZR2dX#63y%yy;qBLWa z)`;>NQC=uY3&mrQ(hs7vU6i+v zrgpIo@oFFI7^zbSR^9BpZXDD4uZUE=qpORQ^@ca8S8TQqfz(zv70 zEjlW-qBQPEb&t|+QCdC9dqipXC@mc2J)^Wo^lTQ6R3w^u#d=41T-qzvC;IIhsZXq5 zl=qAEk530g`GDBK_;gT|500imu^~|&mwp`OL!)#^ln#sXp|RmnJ}gQ{MCpjw$S55d z8x^IaqPZVO>F6jO9j!GwHYQ33N9n+5+v`UA6SuETqO?hDY;>&0#y<-!qr7G8llZh% zl)n>At)jGTly``xwo%$PI(~6SZful}jbekM?Hv*u7fl1AbU?Hx{i1yu7yb5$_HtY_ zjgQiy@ykqz@(Iyj3#_(X=2+7esmE zC~X{D7)|q{bY5&xd^$JE=f)Ptr*oowPVCe8bV-!YjxCA)o*i2n<%^@gm&KQ~JSt~J zEG}IctcK~ zt&e^uMp_@+5akMI_C+7i>8Co<93PmU~QCkj?&K2e^L7=k4q0l`MPL26s3n^hokgx zv@P4C^hlH*iMD1(ly~)iH1E8{4*R+qP}ne&*iE z|2uwfp0UPf&Q<5ssoGV2`t(K8E97m+yO8%GAK**;5`TmrdHpBy5kA6?@Mrj$vp;}e z;1{{VFCky?S5{md*5p04E#w>g7VAH%*-+Kki0Ojl26G`^1{4I0i~c)NGS|MaVRbV zizr2vVx$Nxj!VE|up}-;O2X2(I4lFpuuoZ128QBLTn?7wnB_@1SOHhS6=6lC5~&O; z!78wdQkAO0eX2^TagEh*Dy6#Yv*Q{hJIu~?*TjN3x$c^zmQtJKfH}DOI#@6lSD#y{ zOP8DLu1o4k=EM1vFvWZ= z)s$;##&uNSK8kB>j$4rCN>eP@TxrRkO<^_MlB!DAlE5#{=am z1P3aE;2`BcWia{UA<9sC!6C{pWjGcbfxE-ua3ua0c88d zXL9VNc!shJ3(ioMbJpc}y0QXKgVW(kJOxgJtMEiP1+K;uxt`V3WMvIpqpT(C;CiwS zu2VKB8?oR9Ws|ZQ3vN=jC|j}M7G;~V9Sd$#c2L`top7hJOW93!!aaB|+zodt`;`4y zaG!F3I!JdwIfM^!KLiisd2p_BgdFBxiS8&qq8x)qmE&0OsB%I%i3Lw6rnr@DS^}js*{}>V3)$x_w;B z4RTX*C*DSO!Yz1C0qeF z;#F`ZT#r}7RZx7jj*O(YjqWk8yGE``UdET% zdIq0y&o9E~@Ht)l_O~HI14U zrd89C^pY8HMv?($P&28S)hudOm{rZDW><5lIblvUmzta8gn4jYm>cF+^Qrl-uGXNct2JRwwH8%Vtqp6db*S1LuMVlpk?Z1mvJ1my z)G$(?D#ID;lLl%-&RGdolE-gE*NCnLtf4lhYH-}fqzP4v<2E5psX83BDQU)Wo8jha z3$>*huC`KJlQysgYz14XZF!~zY=_&b?Xh56o(adT)DCJ#wG-^5cBZ<}by2%Y{)@YC zoPS|!+zobt;kY~O#_PlB+LIoz2gmM7{4vH^@DMl@&cs91S$GInG>goZoP+1EH3$w;=Td{X2Xo0h(iiqs z=SwcY3)F>@i|`_KFPoU4u9E#~yjoo&xfZWe*Rs7%U60pr zti^D#x`Eejz$;|8g1t9Tt9WiB*EkJMQ#X;#lGAZt?%e{azq$o(VLeO87HS#Y7IiD$ z!n0e|ZJcd2T+N!ck?mwT+(veAq@C(6xQioig1g~v&bM3L12@8{>Rz&k`!2eDSWtXb z_QQQ}5S=Nk zp20`y#9k-Vv+6l`3ZBP%So2wo9bP7SG}j+CwJjp^#OI4 z{U4Br9OEIrs6N7v@CA55eN0{83LcXu-0dg$Ci~sxEVtCBob!g{Q}r3gzo$Nj&(#;| zOZAod8ot4A@LTv+eW$)BZ{Y|05x$3?@MriDe!*YxSU8sNxv%Os^*i|?`4j(AfAhZ+ zei42Qk`Sn24S%Kms_NuB)V1%bp&8I5mL~WE+nS~QP;KI9uI6Bu{8T-*1b?c&=EHCJ zn;K{lv;apSU*T6Zq83Suh*c~Y8S9)UiWU_|VI?}9M*h*Fu~G#pT68T2QJ`3>rp2Vw zv{*PMj16OJakRK3HjIbk!?-X3PN*f)62rt=5-PEl6eiV@X~{`am;%Ru$zgIWr97Kp zN-Y&krKN_cSw}RO2By){Qoqz_FdhEQeNCtRCh1{%Ed$J;Wz;fhnYAo1E6xTp!R$C2 zN5~Fy;50BN%qfqVQ_F>Oany7$H_Xj3)5AP44@b@b^TNDZKF*LqGM|=TE5Mdueyt!~ z0j-c$SPRvPz@j)57K6pK;#vt(3>MQ$Ql;ohYNc@*QW}=#y%2&0%i`Z^S-Rh9A+|Dd z+}~IbzmK>E>%aX2kU8J zR6VUetgkiD8j||3zSc-x27n6@qW@Mt(34%fy|W9i0d`eVo6T9pk>}v4+I&{JfIHqDE|hn^2kuE0 zald;?E@FkfbQ0ip43jkQ`%{GT05hiC8xD>vOA}p z$LF*Q_#(LgFL1_-IrZ?0b?JaA4i*IZ1@H>1H-qhYxH@Uj^bjwuM2EU=={6R$bO$=R3tipQed0n_5A+CFFwi6Fk+5K7 z90f*#k@TqgKUgrT9!-yq1!LeC_^TF!e9>a+vGmw_OcIBRt;dCN^>|cVJwA-DC!pf% z31LD#5tUF+3=``~NK(mUIGLVYG6hbdr<6>EQGbq^20bH{NzY85 zQ8Kfhg+7a(6=v15>Dl!hdQLqT7R;&V*7IP&+G`RAdI7zlUPv#jhw4T2 zq9jx=28-#%VR5~LUXm1N578CIf+h7*dTG53kC!IJ*e=diDJ)nUD4^tE{8fB}Mgm zxG1Tohmra)3^t(Z>kairq&{pc`zE-F-c+&~Zcds}E$Eu*EpbadT(T8zO-(U2qrNh^qg0ORT+t z-j(|k2E%Y=t}6^y#iFmu6<6i@1;yQIuK&vwwAZ_F#x}4$Y=v9t-6dP%W~3!-f*X-0 zum|peJHyVbu_NrnE5xHcI94|v=|ESBuUAjlQ}4xBsFGway*E{j?cRDHTn*Ns@5Nrd zNnh?#KfOQf&pm4m2fzW`%Wyal4&)9G(*M&3!@>FxYOp>O4%LT|;c$dLQXd6J>7(Ij zeGD9GR=yeF0p+qk^;X0$x2EF2sxU#rhI`slH5KPF6^+#4Fia1y|{-$r{Nec!|DN zaxq@ZYuD-P$zr$xZzLPwa=d~phnw(PvPs{pZ_&5Ht@<{&P2Uc;>pS$F`YwGp+^z4? z_v-uf{cyj2KtHG-(htMK`Vsx8eoQ|OkLxG&llm$BG(4@JfoJry@T`7LKd)cVFT#uZ zC3s1{3@__f^sD+c{knbw3trc6!khXncuT)c-O}&CJNjMy9=QYW==b4$UU!c?;FS;X zL;aEdSbw5F)t~9l$y5CWeyYEOFW@Wu3O|C6^w;eF8o!a<8~rVQ%ho&iPJgd|An)Ku z{E7VWXZkPtXO8e0zar0xINy67c}t1s-jdg>r~*2E7W8%7@}kMdfZJr6Lqsh@lQpEI%LQ|> zn#kP400!*kkpSkz4)kCy>=?P_zPWH}k_YBt)s~T$=Ny=ub4D`qQTgezll(BhQGiDZ z;t->d!7o)X3S$L^@NOwg7Yg~$^1MSrDa|OtJERCMDm$I`L{Z)iMX`8a6eYzZv*Y3< zJIu~mijxwYrv%P#l*9#Lepm{ZGD=I9!DWoHupB81%Npfjd7}cXU{s_k7?ofpqcT;= zs6thwt725e#ruSuP`n|s`IMqxC*I3RiYrx{TI4%N< z$ZINwLrG0olYK%-HKP{qi43r|e6OU#X-F!g4yg@O;1swntjqhM9;wSaLUf5pLL-dS zgY{+K0EZb3jRsUhqY>56Xbc-0O^l|bF>HpLL(w%iT9B5K;W!HkhvB>{!{s|QoYiG8 zTETEw8@FOTVr{LA*043}X-HbbFkFv>@mx5^ZAF^Graar2G=&Xu16I_Av$rtX8trgf z(#~iP+q1Stq&;kJbfDVMbuc>OcI?;5=xlT`y27r;zeYEsyU_#oFnSujjNV2c*vIH= z^do&>AEUo9z!+!@GX5h2;Xq@sF$4<^Hip8X#xOEmas(d1)?i~K797kgM&W@RXO!e< zJir)Zj3#3x$Kmm092{p%fD?>~ynduHiA;v0py)=i{}e1ZnpaFUrWw=WbYliJjBW^= zVazmU8MEPRV~#P`m}ks~^Nj_@LSvD!7%n!J7)!}wxY$@`Ea$N$#tLJlu?ns-R+BYw zEm=dq4z4#g7#raxV>8@{HyT?cx8iMNE8LEEz-@4wu@mlMYqzn-*lX+~d*B{pKiqE| zp!ORF;X&h&ao9Lw9EC@XW5#jggmDs{G)@_(jWfnsc-A;a&P!gv7mZ8CW#bCD1TPs^ z;Z@@rb=9~IuNyavo5n5UHoR@zG42}ojQjAu@c=$B9>RylBjd60#CU2v!-7wZ=f(>x z_}qADyuyMnjo0wC@dmyz-coOjckrF@o_c3|fFF#H)Cc1e{A7GKzK~DGSJ{2TUyN_$ zJN$0^AV1*`_yj&NezAgI__ypr%-?K>n2%YDVuqN4&sd9UDyHBI)}flOSb=K3HTaLP z@I8Jn{xuGM#2>{EPWTyrHcay?`OMjW7?x?7KRAzN{xWRNWaBTa)iJ+gLC18VYyLDm z(>DXtBN1Q(Ga`&=Mxr9qMIn)4WHT!Dj~NX{#?eV+_K6N-z(_DAjs;`D*f@p4 zW;`4h#)t9E1Uwd3GJ%Bg zQ&XwTG-g^e9eX9k$;|XHDNJf+V4nGB(<3t3#K-+kTft0%xY#M*Yv#j(>2ZFT4;H`$Sesx$oED}t3z34b zFb;)JziP1$Zn8ko&-176h}=H$+1gW1d$W=q_HyPA`(tQpR=l!mQfE3UC5EDDQq?Je1_ zHD|8Rnd*@?tf-!38?!Adtpl5z?Kn>f?pJ%NnAw3U#+~dy3UU8CNEYJGcVsI+?10S+$9>&cl0lRlEY@ep&UIgA=^j-VS(H`E-7N5P?Rs5zP%%6_BC z7;~&S&Kz$}fMd;xaDq7r3r@yU;3PQ7oXYX0NKQ4UanuoTI-bT+hS3cnGdSK5I1>ww z#xu=Xcot_CoQ>zeS#Xv)*PMq1=bH0b#XPtG&u1;;>Bf>7=0ZHfT!aND;6 zH7G|46Gzl29;kVS9;$607DWkoa0Wq1ZhUnV&p&*u@rxp*%72+qP2 z&E?#cac~7(!Meu7dFD!UmAM+OHrJSI$!fUTTnE>g>*0EH1KB9K32!ntOK!ni$riZ9 z+-7daf;;d|xE=1oyYOzf+uTF;O76q^$v${M_6PBP^N@MaJZv5@kDABKVxEA9 z&68O0uz89)%s!{cY4ePE);tH#ndi+5 z`~&`Eet}I-{cqM_fuGk^;Vs=*q#Caex?;o7h!tb^;q+OQt3YlY#u zR(;$6)`fMghCJU8*R~qrny|Lj7}jLn#kuA}P+V&)M>>#>lC5wntCM7B9By@Hy9*45 z;Z|4cUo6PB^^>t^-9-L0PVy+}{k1$TnIU}xOh>SOhV{cu04zvKWskPLu>@E{%) z{15-f-hzYi5NoJ4%o=WuutvgBc(^qhk0GP2u~=}lH4cum##y}$9td(%3wF<7XR#R)NwQw!l>&PFku-3EZ zI=If-U~R;L8>~&%X1s~oLbn-igoK)^5pl zcpc~4&1<(>d#t_IK5IWbfDe)b@PKv5I*bJmSx2m+oN6&?y>b0KO;}!Q|r0) z0t-I3Uc#5wEBMNKZM`9X{Fd&m^^SVW8sB5F@{4pI$VK>3_D}FL^2GWiyJsBf6MKKc z@5pPq_i|tH=o^mynb&?{|F7f=d?fn^_^b6zUdaP|pL}QK53L{6L$2%xxdZQT4}OrJ z)-Nph7(d2O$#3`+@|(VF#pV}?5e;g#4t1MZke_PE!(#5b3Q@ac5Ii} z(8C_~p>GG2Z%2R;?1*+GJF*=GMzNz(|IkIXqv7aKJQ9O0CW!%KaF&?FgeGT?%B%i7 zAI*+s$F}3ZxHuk+1>@rcFdmF&C#2%>ctVnheG}n0c4C~wPHHEE$?W8I3M`o1PHCsY zf~j$8oCc<`)7t4s8aut6!Om!BvNPLRU=}+o$p*8NY%mATNpip(b}l zotNX}{p);m`Rx2KKhNcXnP4XN&m@mkz%FPPvJ2RSaVRVRi{K)-C@g9hvx{TFVs;6; zBo-`Tm$FM^!7{iEE(^=rkmeYmwR=L6b-SLt?&7ex9Yz)B=wU=0zYbk3jwxJ+qRURgsg`yt z*vf8gw}CBTTe}@>kK3@_o@&Xf+LPuy)?TtXXDP|yt`dyyWnhuz!mWB0ZD+5PPS_CPoY|3?PFLH1y32;ERP7!Jcj;BYwH9zhMa zN5YZzD0?&(9A%GzW9+f^I5Gx~$K&k@k`wVndy?d2JcUe#eQ{qr6;5S6)5ugf9ZzSC zf-~?;GQ*yQ1!v>g_8iH%c#b{So(JdI^Wl7ZfxVE-hl}tc+#mMmN*CjST<2oRCHOyk ziM;luc!<4}?IB#zQm$p`UoT_T%h;Y^FUJ#D@p4M6dpTLb%2xdKN>;RzZQ(g^4rgD5 z1;siR<3(h#Ji=nmxSTBGOv~^D&M}r}mvg)oylR!b+FoO?g=_6~aGkxLT4!&78|;nt zCb-VtY;Uo*+S|w$xE=3++u%;TgZ*~G^>`QD0axQ)_HMiyuCe!!-Ec47YwwfiT!Yt= z{nR?T{q_O8i5##GV!;FUAs#=3_uGf@VZ0CSvyYIY@EGUW0e7&TEqEJ`9=A`xlXxFI z3QysEocR)R@M1u@J0JF zzCtd;tN1Fu2Cv!IscZHPc*DL)-LP-LTlQ`H4!LFDmEApj$G&IZ$M@_9_<{XU@)3Sy zKbCxgAKOptr}i@}_|$%Gzrcde?U&Rmx)<=Z{f4}PZ)N`upW&*`v8wC#d+IvtxM+W1 z&39PGNBa~0$l34EeWLr!D^73^-;&oH^EG}!zHl$U;FH|PFZNe*3ZCL#p0>ZyowmQ@ z@AePLpZFK~Y5&H8AoZpO1&TF2Bj4aRdB?tS{>S7Y^~2VjhvYtYS95f%J9oM3 zx^tWRaU0(x25Y*>|3VJIf(C0g91Z^BRX^x;woJ!zY{zk2I@9s!OtwA8CpNU5z==R? zXtQqw5)nppB2f{Y$S|@K#fj?t<3w|!W5H-n3@RpF3?~+j31d5PNGuo^$AfX4_&AP} z0LG^h(j|b2sD!K}5lKvvNP0La@t|0r?w zFcPaxg9W2FX{jh&L0Xc|Nlzlf3^)VM2s1jFoXjMnlSOt}ab_p0lMQFX*%jxvqR%WqOVt5k_?KQxUnB`AGrpc>!!X1!eEzLd1nG*I$SvYdK2Ns60U@UpJL+N60=R>Jzd^JLeI9@?qfaK@A`Eh!Zk*`xmoLP3!aa6umS><&_ z=c|>KWG6*nc9@g%7UqbBNI@Qt?G%MYc_sm0rDCv{QydoODvLWMJE}4Q%7Ib=r|Ou#M9mws$%>9i2{2XV}^40=qa};lEBd*v;tP(}ia!u37bgpPR z?&!>r{Y>1^ndQuMW;=77xp1yC&zVo=!nw`@xWHKm7dnfa#bhB|=qz!TV!8!>poi%ukvsQ8)UQgD+bg;fK zI=h_Ra1Y+&?3LVy_mh2apL4)D=p1qm!^6%I=O{T04?D-4AZ%ooj1-~=biH&zIQ%2ADvImXZYFq0>3z4;aBII^WFL3{DeQ9U(`?MH~j5{xQZL% z{&p0?Ur2Q|wpCZhhHJVOw6X0vk}h^!*Y%+1`XrEyfFrmOB_rX8ZX`D{jO<3CBD+yx zRQDeeO)@%;=0i#&Tm*vD`Q?jvJT6lZ=lOxCz}vFfmR-62ru9Qa72K z+)V*fxGCLKZfZ9TOyj1d(zxkhIyXJZAej+obTdh2#+lsAZWfru%}TOKX2;pw>~0R2 z!_DdDA~|4goCoHDd2t>$AI{_E$NAj?k_B-gQV$?p&V`a$(ZbP>bX#g9z zjol_#u(8_|Hg%hk=CB!TLBe1Q*pdo!!`)U`Fq{?Dz=ExD4Yv($OWMF@xCs-KZ|!~X67s=qrB4s-{(|B-=k zpgY(df&~Y=L)~HSaCZb8;f{nO-BDz;%Zx|!~5JPXc&bKJSq9Csd^=gz0*xeMR|ccHt8EPxB##qJU; zxY%7vEu&ikm&4`m3TnB#60UStQ7gHoRb(|+v>Fd{*T{Y?9_Frd*TPZmdbr-*05`ZB z-A(RhcMIH#w~?)ItGgX;cXz-Y?oP5xayQ=P?soUUJ?>s=kGl`0b=|!QZ@Raro9=CR+r2~GcJI3P-23hW_aVFo@41iM$5`-@ z`^0^U1)sRj*!QXAGxs^Ky$fI9=k81R0^Y)};7fP|zlN{iHT(v?W^LE#-eSQw?mPEA ze&>F`@7#~@1H6YH!H@8x`-yWulKkX;=KN12Kf7Nz^E1gW?pMzL9Nyzf-ct`a>IaVa zjeM7UgWs@%@9q!xr~8ZimJIQJ(}j2nRy=;q0i6alPbY??iA~Rvw6W#co&z1vC7z^@ zJ5eYdXY$E$tXCA_rn#8ihsCKy`SzsUNkSd_Ya9d#iWbj#lo??*phK@ zTrZv%A11)@U_zMCOGG8~62rt^5-PEm6ejhOQAxezFu9k)OG%Q$R)56p+ z9ZnC^!VEYgOb;{Rj9zA((aVCfk}NQr?6c#nUJfri$tjr&=l1e=d0{@B&&w}a02lBI zN*2O}Ng)`Di@?ILC@zYN!D3!O zie4pH$*WANNLIzwyy{*JSi`FcYkIX{Ew46J%c}$Hcy*~dUOiaP3-jueda$0?z-x#F z8+eVp##peC*Tidz1)F%yU^A~dZ0@z7ntLr_OE27OMOwm^UTd!n7HsXc_1byuy$-M= z?nFAmj$UW4i`Uir7yj#Y^SXOIyq>V9*URhe_3`?`zFt47uh$>;_Xbe?y@7C`H^}?X z8|)2%L%gBhFmJdw0*>%TdZWD2-WWK>8wP^Ff zQ@!cl3~#143(oRp!`a>(ILDhy&GF{JdER_#p0@xl@D_TD$O5=n_Dk?0Z>hJ0ER$T0 zmwPKDSK^i4D#_J&4OtCWduzRQ-g<8X+~94bHh7!hCT}y@BDocB^|ncF$2+{8-Y&Qs z@Amdc?#27aUbxrW5BGZq-~sQTcgQ>J9f3!@qtp@a7(C`3r;d3i;0f=fcgj2Moq=b( zv)(!HymtX!@Gg3nu;4}SvUddwUd31OHF(Xt?%nWidbi+hd>h|^cf7mQ9q%5z=iT=n zcn`ft@R9e}d*VIyp226{bMFNfeD1yUUg4MCYwwNs7QXe~k@u1x@CWase`z-kd zfAPLbe#76q?~*_8ckhSy6aMsmQ9r%k@V6J@|MnDLQ1umGR9&;NfR5s>08k9 zZT=&R>wD1ieLwIc_z_`5KN1zuj|?OGQT(X>KYlbSI$bnB298N$z!-ikKQr@?}0aax=Xrt{PL8T^cXCMq*s zCO-?#O0vK#el|Zl7R=`7@N;6p9DXi8x1Yz)3-kK<{QOujpI^W)hy@Gyg+#lhOgro2%f3)NnJeG`s z<77V`kM$?`-_a(E!==N!u4>yzlqvRx5?jvxA7c{}4GWc?2KykNQW*G0Efjn19?q0Z;fRsT2Mwc*;Lbo$}AXGyYj} zPVziH?_ZF-h%fq=BroGj{$>9Pyy9P_uK3sBHUB!fA$b#D^Kbb#$!*Cy_>O;9@*ckL zKky&IhyEk-Sn>&eN}j+c{xknM7JTNv@LyuV7yc{w%6|=C`){b%{#*Fge@DIb-^2I* z2kO255q|VP`Jc&0_|gC3f5n2|@OStXe)WI&KmA|+Zx|B%@|7SY_z8ddYM=#rU_c|_ z50gU+TKtcCCvXD~dVwDVL4+V8j2J|sA_kFRPI3Yx$sLDQfaX#$%B&4U(LuzAoDwhY2yc+e_n zO~PS#&?ab$1=|Geg7#RjUC;q`2s*-!L8qW|&?V>!y9WP~Zj#+`57HfW4|)c@uwc)i zH`Rx(chDF2f&E~=pg$QPIS>yD{tE`f!NCw}a4-}O4Te!egW+&^Fd`U9hQr~(C^#w@ z4MzuKsL{b#I5rqZjSa@b@xcT#QF0QV6ik+!f~N#iC8y!(WEz|y`Lvf!D6^LSOS*>OX1RB8C(`Dhs%Q%!OCD&uo|uo*1$EvTDUe? z7py01;o4vW+z@Pp8-q>3X0j1(47LPYvEY_qTd+OY5$uFJgI(0lU^m!yN{+xI!7+F&I1Y~oCxVmYI6NMlf~SJh@N{q{I2)V` z&cpM;1?qfo5nc=~Q5S>D@N#e^xJoX=Yxp|63UA<>@H)I6+zM`E!8`bFa4)zIAK-`Z zF1#B&3LXbff~W9l@GN+a1)l{kf|pqE6@D4K#xH|6I7H50(3ossFoD5fwt)#P$RA{) zEdJ-GP56(r(1ZK~Cgm})!6yL|7x-|Lvlk*VDM3tOh{)^(;YiG3h(Mys*$WY%%f4a; zLsV$vXz(9s;^;6M)NoW@6$8fL*?(Y67?WqC!B{XB`^9GEahMzt2RpKJn3WKR6~|@U zk`oyWXmLI9u?`LRXF5ZCEG9K1q7u_33K7#862qi0X-Kk=Q7_=307FZWtHmg?V5c92@4P^3mmG(t((ake?X|`I(3yW-%1N zg>gYrI3$#$f}t=COvChqA}}3H$7+jW!Hlf77#7UTYKvpRtgN{(t4>7ZRal9As!z;UFeCv}Be;I22tt$7@Mi@w!&Hwd`v1yZT#G4VmiDnzUk`Lo3`)PJw98 z1cHuoszWo_j0pwpNhf}5dl!EHd>3XD2zJBW`0ez9op2}i=}vk`Hpe|lbJ(2Yir=L# z*pAhR-=#0ul5>jRr7zCj2lj<+Sw&yTez*-Q?8lsgez-qrKnR1 za3iXgoa`_N|3_*tjp0Ao7}sEu!eF|_+_fQ0Y8di&7RFE}Ck*8d4kHzr<}gfBOzWt? zgoNSDpcp|{g_#c{;3zo9^mJw_ZVv55+ImuxlQyj!y zn86%}0n|+HOn)fuTtD2GOrd5n-(d>rNzLMG(gXer|K%&y70!aQnEfyt&ZMT%&E%-l z$PmftcnIel%-ILy|72H%_xUL1MvTI9NHtiE*%9;PEQz^Hb(kk7NX%#Y!veO(z=d#> zd~^YxCttsi=N55?7BlBzG8xaLhw*ae!#LS50Oo3QOtzv5(lOa}-6_Oj}F;;|Z$(kSPjd z=qA7e%u<*DC&Gi&A-YNA5NDbM#Y~SKa0hEyjJHzT>9%uU1P{ymvji_CN4PUfC66%8 zVL3U*9EW50I8zpmk>gy?PAqs5uO%nt#E7-bUJw&APQz1h9X`!8265Nc;SF+T#72CU z`*oHH4dOoh12REVo`M#NRNuKl0M5ZC@rhPWZ8LTvs! zA!0LIo0$=@h1`L6m;620oJY*0xQ_)N z;FI_iTc_|PW<8u@jSuNAFv;N|xxf`XB4^=QuJRE%MTtoikMKi|@ql>|H?ZJiCPCc7 zf=`$!A!b`VrJm87Fo$;yHYbUy{f072d&D_ceUYyXZADD0Y$E@GX49 zJ7_oE9wOd9d*ytJeUk6lKFExUcklo{z_#Fi{F*)9kk?GIcq^w_9Av)_?D>JS9fGfU zcN}J(#StRj6-V)LvW zd+{s8#J~7USfaO?hVhGI@`v7)?;#HJFLYk%G7E!W%q1sc_{_o(eZUk9ov6&hP~}7n zMfQ<6Uqm?_Bcc)oM`0dDWDKoN^JDRb*lIMO+exc^GkUHaWo~JIu!Ph!{9K zb0T68G3z4-QzXPnW8wrP7b{I5nM=-%NCXq|SROe$BOx<0;&1JjwijuRcHL z%`exJpGgv#Nk*nKWW-{=M?tC(U0PCzDomFahLR#M6c$yAks`3T?Eg&OD9P-NQgZ%A zXQsrjnd57C`0lRG0~$elQ!~^{E`K5dCpyq^A?~h&uj{@1~G|4%&92M+={}u zB9kWy%V``HIddhtBBU58DOm-VCRI3oX~`-~tfG*Y=v9HFc`*l)WL$aaT}$rocYm~u081hJ1{3h zO#S%d&h%ZF)zKMtWsXMIzwRa{e~9@XJ(&K{6LyEaaBtXC&iv@bypKL~eVOsmhv^yp z=myAn9|PDL$dru%k^`B(G5CM8JqFPam6JV&;9+Dqb2^4e4rfZoNI9otq@3k3hJG}Y zIz}_0V;mM7%Z!Z)Sa3X(Ehb^XiA=W;^Ew14%ZVLR<)n^j^wXJ^F-^|Wm=0&LHCs;F zn1$ysb7Ib4&to>nJhtaEokR2s@glO2T1>YHE+I?d61a>khs)#?hvm#{SRv;&2#UEI zYvkkxF>^yq+*r%>2QkHA9krhM5bK%luz}jh+=xw3%-h(^Jc%uEtDG(&xRqHE+y6Ih zW4oNUv4d$8yXf}F*&Bc6ZR}%fFS9E4$|)QB;6Y|s>?a4AYjFr3W@5x4cvQ}jI0ldY zov(3%IRwX<9&ubw)i@z}l9?Ja+RfzCW(=o*BYSi9vF* z!~oonchog_jrUt0cpYBn{dj|1hduC3cms-=HMihR-qYRWe2cD9EVmJqWm ze#*%eVur2W-w~kq2G=07o-EqkPE! z)>NDF`4vE)xfQwiTnm^kA%0HgQX;6iNkladTM^|Pi#$pswG2NsBQeLK44>wa)lwu1 zETu$IOYrk7ikxjxoGvO8E{gN{UtIZz2^Wba#Y`=x!E?RQn0OIF#Jz~l#EV}_3}#;l z#=^1X9E{lXahQA&TQUwaFXA!pA}&mTm+--#NPDeLRP>k~44<1Mcj?8-!#-F$-ot1eQysSA|uq?_Px^j5ca+bkH%fn| zYE0*+=KysoKT!v$lld7dCdv$?#FUvqax%?-yvvHHFPUvKSRJekQHSzL`9K-U%#53S z4yWY1Vz@e!pTHwH+6ay}5)M!&V!`ovl$?+vW_;9OT8EgBGKPM%`h+8mWtPArjy9Hg z8{>GljprR!6;6N?c!#CpdvPM1$oE=WI0;VTdo4Yj3@7uQHib-vS#S!zkEg<^>^l)o zgVWe|5}XdFv*!#l9ZtbB`4qI|gbzWBPfIc3L(t??bPh8;`tr{@eU-T!JqO>pS$I_r zl1p+1p22sk;5$t-C`C<6e4C4ZDaaJR3bi|qv6e1a3dZ_HnOUL za5-Gbr}IEMF{4PVcq40AjRnOVlg+$x8?}XQ8)wbtzoRoRO1o8C=F(l4X1qPs!OOE1;O-vWf{Nr{NmBhWRCeyYN|fj_W`7*XOzV z^K1+MnQ5|zyKs?yKNQ`5CY0>wE?lBqe%@aqmnnzufXn0x-wRi;m~=7|&gA>x8Wx64@BwZHTfio`2{Tay8_C%y5Ah=+e#$(iYrs#O z$K;7*9sGo!OEqClK3l3YALR+0fS-{GZ~~t_&#~b6|F5sNj&Ca6-nX0VqLsZuFO)BNrFTeDc?B<{mw8pM$x?ZZzM<}MrEiq?^tIgM)kjg0 z+@DDFCa+lb_tBf&z58524A=9N6qXoDuIVlHj`fV^e@DI0losI&p7${-e8qDVdROMqa@j4cY_&rzlM9qh77^LzRvugj1&^Ti&^`poN% z#iKs!qg~c1j$PJC?u3)CN+QiSUega={Wq@vE4!bVXTDNDx&GMJFKKm#ztP_`0eX>F zBd^7V+DNMr+R^xw9m;EPpcdB{pPkc6fI4}m1e#?N3&tWB$8gq!FgE&y=P^*8H6h1w zVO-89-yxK`K{Fl7b`IZ7D5(dn&68C4c@Lg^sdICeA2(8ZvPt7bgu&>&eGS z$C=ZUXOqmzfF_5@U`8|rl=EjoGodMAO3Rm(lC$}0+D>LPwUt@V<%g!_IJ1=nX5rD9 zU{;uwGh~F>U^dR20cMBUdB)6^pOpjV;IUa?PMDL&W`((6E*_f==7zcXjy&Yg_dpgYt z3MI8Htrcqb<7fh$V4r65v!?NB#}r!*u5=2Yfyg!HuqN4ZQWN;RB&Q|J#aG9|@qCh! zo9i88&&}}|yJYwL$#uK)d$N<RN|;CyRudVN^<?ERrXS{|0?xdUK1SdQl|0|Q|o&tAc*XdUIVkqXL693SVikxJwbRkFhP zs_d$eIaH0R4y(Z$tm@~kk4akTl@KhWvYI zNRo}#!qt5KB)LYbp(MxFC)sGFvY-C#q5&yK1Nqy5upvoDr)>l6!ba99sxdreYs{-| zLN(@f%kFQq33sKkCLvXT6-Ys<$m^|$HYFEHUVAgLky=pANEVV^6SM`%LM_>~Mw@UY zt=Kh2h5uwOHRe&x_*KbTYR1|AhJSGexxaPw4%g*=*5$5As#7gg(wSOwminAelAKzx z>hV1FxF0Q9!6Y&TbC=q%s*uQ3g`ZekmgGdWLxnYX2FXKe&SToKYs1&uS=abfsy(Sq zSNX)My>-Rb!MaRIf^0`}lm^>7ay;1HNmHE$^S7u@))0GV(wv6y_o&X+P9G&eK_jN?j)bl^|ek?;rtD( zF9}aW?cp|IKh1#Z%2y-{O3v4h?~rt;zC24$5{NpJKO_lIk_a_WueBo@!Y+hYF^CF* z?d26&*ZKVI8n1OQt3An2gQ)g!C_2pQYd>HcW*x8%r}n{pwh<&CN%HRq(xCR(Mq0Zl zNd+E7LedT>X~CmOhuQ{r*v62QB)P(4;3l{QhSD=#HodzgsH{o z5)z>nlLEC=cgx6bTBa#ZQ|-&qmDW_da0-8KT?JQKPi#}zJ>!!@;VROfUhvtWa5c$K zulNK}xW;kU3-y$!F!>-_D0uyqF=XP2F%iH+lL{X@v7+Qfb+9LoJ#j0z>aYAIX{m!r!` zvf9i&+QM$CeJe*(`8)nrYpi`6`Br1?(Y9??6cugTP7YO+EfS65bKC7!B;29-SNHiu zcZWspGIbZ;we2Dc>MmEbi)-0U{?a)69ul&4YbMraGNShIJbQWGEj-6wGMe_H`#gDC z`!w%rpPqR)U)`+j*rKZiL6+33M&o$WQ(RyERrAx*E!EH7Q|X)JbY3JW8#ED>>Uro<+DqpZfrMobL?d z5yzEB(Nk79sadBq{VE)ur6OR2rgKe$5&XV6k51!v(Ru0uoC2pp$?Up_PKJ|tA6%p^ zQK2xDBrf4NbR79u!VvovYlH0yDtTTT__vU&@EZAD>-hJPYw$X0TWk1tlk4yXSzW8( zD*jF7CK+DB75rPvGFxFXyKeF)r(0A(cnjX9^20n-ekfU9cd0w@4mnr%H2vzH^#FZf zMJl7vDC?mz8jZFdDIcpovSPTJ|H~MYl(8$;6Y4a)touvo6YD9j^dj|C?}zZI^^E&; zLHUfkcaGh2_?&wiPCbXu`Oa|aG`nYf$8+iluSKr=uJwZ5U9R#4^->v$Mq00w57CEQ z-z)01G6FqAx%Ca#{f6Uf@DzNDUWc-~&b_?B8DGMi=uMtUj$ZNIFR2^UTjd+{ou-4m zvp$gR^}!l%7k)ys@@d^CD=YOGezyGhwEHvpS$_5}mM@=)`=Vd1j7muZ`>Kgx-!v0U zlEMDT1N*6oV81mFOp?KDWP{lpiAf1dY}p-&DAG2`1xsZ4ph5=;VLsL%8_{5nLHrA& z!!giC5C`m!_P2RE`cnP*H%b@d=lk;SlrBean;|`{w=EXQVMY_g;*c5^i;ANuV)0;n zO%RJma#(y;0?iLgh$ix+he?uH63q~MYV%?Dlz+wXaXhyr*oRMf({rEXzI@>xexp)4K2e{!zaP*K+$G_A z^ape&roHDLz2~R$jbHo0UH#7aB(LndW~`-R|I~GQbRuOuVX$%1pe{Oq!;aiOe&(A4yRk zDlyB~kr`&@er9&~!T6LP^y7YKq5L?r>@uLjEF`35MuqO&G(R*0N50UPXOe8TY>w<` zHYz)>K0DVU%&6CtokX>aRC==2(xW*wt1T_eNzz(cRIJ#1KVP`(m&+F(+<%9Vg-TC+N?r1)e+j_FkukW_rXe_=` zfcIG}Sb!WitjiGl2~0q$TLR5?ON18Y9aab?L1S!5*v0U#hDkW5(g{c^>wg}%Nd2xS|4JVm#C17##;UqCm^5RO76jxGNisZ8Z z&5bLA`jf~OKqj1|%LVf5#g&1ip80bmEJs3I8MHhpaTUmjs|YK>N}3^8QCXR+xN4dl zSB+hD662~VYodRVL{9 zZfmkS_gtr5G%B(yameNFP^BvGyjSL{w`YsxN&q_zrF z5V>;AVGG!TT)7tHs|j19tu$S(CfRGPxzaXVc`ePEs{j3MaEjWKgSdn|v8WmRIp43FEaTR}a7u)eY9bgBt*4j~>VP}1|wrCrw3)j;IHseaW z!mgynHPhs}W@NoJWpyX@ttr)n)q^}ZNvdna>dC!n2z$X^WYsmK8t{l-e60sbb-g%C zA9mfS?j+Ln(;PWTpc_CQTz?pX4$zFa5KWB>Av11>ro;^=A8rJ>aKp%g8=!7xd~kL1kImY%vn}&#^rD!Iv=j!Ya_Wk%h6%nvE^_j z912&UtKdpFADs(VX&&Bcvg&4#LN^VrA){`Zax%J(`?MBLL_^^^vgbnKIJllit=2TV zHOjRdZ_PzrK#+>D__L6wFhuuDM@Ai;+x1Zes^6&O*{@p=%m>j$#|7PLI z{ur5fN65oFrkQxcFvkg2*uTkmr!*Vy6xn!Zs0igbR8sQJLCMKGPe$GacnQ7yZ(iO- z((qt^BZl?Z@dQ3`JY_v`JcG|jwR`4}483QLm*@-l3chl@rrs#uqHi=YPm=T8iFrcF zqLb{rPw*3&c%R7m6W&C>IKGmACn74rY?^2$ zbdY6cK__%-&Yd^vq71odE>F^(i=?&K9LK?g9-9QVxa{Il@mUGjNw!)7&AAgw(p@4= zx|75^A5YeukLKM;(p@rkUmVG)Z!m=>-K8Ko&Yg58{DY*sG*A-n(rVgWdSwP@dR7K! zMwro=iSkuuMtz-`osuEv=gdN7Rc1r8IkPKspgEj5mATMd&fLm8Xl`d7vg~qerd>Ww zwacfOcJ5TW0wmiNVihFGtq@g&N}-gzK1r(k6Bb8{lQdV1M7d%fEyb=BiEn?R!qQHE zX8Q5G_Xq!t!W&GzbQ1hFf{1e6B#`^WDw) z{+61y)&l*TCEwrL*~ZzHWVN&&`!?IunUx37tLOibhU0It#ySx zG%_abqvrzWoT_T;YhCVQ<9xohqOw*H#H*58xD*58xG)}QpXLFBOw(OkBn zn#eYUOtxXva5zG<*@kN_+el4j8%f&QsQ*c48}~o?Y~wXkEtHJ5iMpGFPS8v>Nn@L$ zd2Caa(=>~1x^jl5v9*IUxf3%;Uu(y1rslBC;b=CgYqQCGYlR9W{jC+%fHj({Yd|%m z=E8=s5ov9W&?dTT!qI&8^Z98kpytDR=t8&v)<+k?g_`A7pWR}z-0D;EK3PJxTQDW> zoTVhZ)zy?Y$rD`8&wV+mZmm$^3Xc2%B*JZlo8dNe8>w)@9hwfeQ@M*&xSgcLNjltaQsMT%y`FTq{pdbT zh&w4>;Y$SKUVX;Zq56=J@08r8$1tN!@jUDzY7LY zePLhH0;luSZ$l#8bfx^-1!^+y&dKNy-c7QlOBAomZ&S@CrOi9an~-r+NKhJo*Y>xytXM z5VG+W@V>f6%_A*u3qPaln(cR<-d`RZsI-WP0dc>n2^E>r1|1B8He*?yA4&VehiK7^nBr`sNF?#MN@QFU=G1`uI zO$YwV*8%NBb)ifjOd z1y}om%)y7Omn8lDH+}FmSO13fNK*(O@oL^u@3@=qJo=vfJ>i=Q*XT>?HGF}7 z<5_>h-#pKE_!hq9+1|k4PA{?%?U=DG)Qc2E{?Q*gy}e00G^mT6lVQ#-z8Z_2gN(yi zEq;Z%Ic?%xlO>Wm6_Vvr`FR z0$#bDti$ZkhT6y>6k2Fbn2U14+>{f_UCx8b{|vi4(4BRd56w^dVLr`9%&&=wl9^bD zDhvy2x?f>U`76p%F?PksO3cZbi?if83h=lRq$B!q%_XUYGQj)$H2m{f4Fdun-`FPH9WC4~#%Tpy-LF`J978u0U zRA49hkCGi2MB-q1suD}SrxNEVM^)n7l}IKm%c{(CR^l<0xf@lu6ICg<79@wUAjhKe z>IOlXn+V6+0`tXjWoZPhw(Mk`g6Bu_q~sJv1w^C+x$Kq$Kv%l)^sb6ZR#S zu%BiV4kVGVzh)5*Ad7II<{u6si*N`vh=fDg4MT-P$TS>*3Wt+tI0_YxB*Abr90SLY zPdE-0N`_%5DjZLS;Y3t8LDK~%DyMKfm2|(!a2hHphSNx3lvKk8)C^5!oIze-J@&KU zOc;#LhO;y+Q?d=~Xu{!KR5AqTkPSEwF3@B{Nez@dK*=za(HLN*Y=Nf7)H4Bc`bi&cxrFA3{u0uCap(GS;fSXWZD0gWJDx8KkC$+E{+=4dO zm$s5xxE0-|@2%FFU)Wl63|sS#+Ch%t4s@r!!*-$V$uexudulf-?8tjck`0BO zd585tg?rInum{|S_JO_NezYHychmv&06G8;Ap7tjJj6Td5E`Nhh=(=n@-XkHBdozB zAr7SmYaZeWQV_%bO+ySL>+qCjA%??n(h1K{;V_(p!m}h2o`L7k2u&o6&`iV&@S-Oh z@e;esB>Y__+3yOx3MCcsDtU-P$wjBppVf(YlM)+{1?)ALkW3gpc7d_y~=Gk6|<_34)S-C|QV4$u4}N*@e%@ zD11gn;R|vLpJ~3~3;3EN$uxWo-=c5eJNS+q!}pqD_}-IK_=)U7cUs|R@(Mp`V&NC^ z3cqpmm6XD7npY_NNmk(x_{)=7=tW-PFK;hTZlN9ZBB#(!Ih0P+L0X|VDTUss%N4`_ z(Ld$tW%ro9(e%PNp7g?4nq3&5ltQDKg+}uVV{w#_$0a1OFfJ-gq}hdWVO-5Fj7xT* zj5_rpy)cd@7{*4Eao%K_VHg`qMq&z9Ja#EPS%#_Dr6IpCm1Y{I)l5UlGIS>zrgLR* zW#mYh3H5~;VP@11`ob(|R_F(_q1jJ z&#O%hQ^I(fxS&_^=`%c13|Bru69 zh)N8DU#Q(Rwh6bkew)ukH+UJt8fjHDVYYX3agSM>Ce?v<=F#C zSIo^_tIqi=Xu@I;EYDF*Rt>TW3vkzJk+E0+7J{`|wMbV?Mb2VsR$cBvY0X?LP1<5f zRxo*sC8^@9dSoURr;4%alf77sD#B_&qGAzB(kC18Gm$jPhENh3bD_dU{7iFbMq>`L z7_+e&lhv4w%0gOW7W8l48Cghe%*?JS@0iS_H)dkjjCWEd-eKw4{XwN-HRBrV!ltk( z*BA`{hJSO7^uRY)`d= z?KI!9y|RPmJ9bod(uBv(p0r2Fc^OvHn93SPD&sH`Bd4)OXx8I&vK?nq z)5&j?-E1g{jj+@*IUz z(e-cxH4ScnGvExadLt^F%{6aAg>$*;&8Sdb!(vpp1zidkbA4Obt3% zNxtMt?$j=hc4;D|q*IRMXb;@OdqEO6_d-dj+{e#k4&09}p!UQ4npC+!xrpOK{A>=w zCFm0Lux4u>_9RpuC2#T=Ns=ctp)yQ)k`&4?GAbpV@|5ONhNGuRqzosS@(jDPnooHS zo+Y31EP0d{G^_H0CR1M0q)N%8yg;hsWzD6$>Pe%#%2y31&vMxlc{4aonaOm#^WeH z^UC7sw6b{2ElbSQvP4WCOQ>_nlE7p-hb#q}+%tD9HJVB%aHVB}SQ?$ql}2ZYrD0B2 z8fJ;PCyAx;^7YE>|iv zhRF=B0y=$5W``AG@>U^KriSHb&Q>vIY{{&x;#3J(5*B01R!LouEw3>Ih|WRu|@4bzw@DusiyH6SR6V7pn)8vwG_E ztX?`fs~0nu`sn1Peo$s;4PY`;2n=CL)F5U<4S+HYYbZ5D=Q#~yzSB^qI}N8sFokKj za)f6R(&O_(0=ChllGoltSGittPa+FMsTFibi6Ok5ko+W(u5_BmOk(TQ$ zq*csAlKDj|nO3x#S_}U>*-7R)t<$+q>zOUHQFoh|;Ix^eEi9Stw2etbJDAn94eo+F znH98+-5$7`IX2sLQqned``Pbf-qAs3>KtIE&Ovwt-2o5ibe$b=2NQIT!Q-qQOw2g} z!>H|WJF{@Ys8cXpC+LJT5$6<>b53(CQ=9_!?y$2zm+jZ2=^d5`dH7^Hra&(QoH&cOJls7bJ zEa-x<(Ks-c&H#$d^q;uw;-hhNk`HkVwuJ25^L~7Eu8&Oj@nO18QkWd|(aAVMA7=Ta zLQ^tHClyQsQ#0)*6+4-IBU6CVGA~DF;-sU}+A`qU&wysMWkP*nMwro-+2)64W@TZQ zm6CZsGWADh{kUhgWM@8%O#jKjY?YiaH!8DMax*4c&x@>vW!KOvR~6$yA)G${IRlN9OL- zVls}*$f>PUa_XYNItwOPr@%C1{!4x4ywuleFO8V`(vbRRmQ7=uOtSeKHiOM<%~{QC zEno{jm+-(~a3vXUC`$o*vD990~h+Cdl+>8pl5qWCk*&V-UwOJ0=9m92uD)BOHbfheP2| zrpJu1jbt~%HVPdHN5j!f1#!=i8O^+qu{t?sf@f09M2;r0#^aC@mz4OXWNOSbCX$HT zTqecLVrGdr+UGFGL?*+CJADD(`bD_xm%t@B@0aP+mt{KrWhF;4_hqF{ei4Vw8fq=I z4z8!x!42p}odB~D$HN9(JDWJ(j1yuLPM$67wleX>J^w{!ziijJFT3yoi5F-O+=DY{ zAG#OE&_1O&Fb?1#+NX0~_R*bxnEgI@gmsu{FGt}qd`w4`$8eB@adZMNiTIgL!IP}r z>`uXO>a_9!8?=?s@k_(!g@zs|MD zoPm8@%?+K>avN8`UDh3)wQ>)=Pd#AWp@aVc6~&6A^D>HxW|g7Al~?E+>MiRv&WN|vd#>j_dV~4^KX7#)sSof2 z^Bg`fWkvW7{S05j*UVgbNxkN|o-lpoD|`eW>D-k^Tt+5)QatlXU~+p3T=J<<_e7IF>}m1M%N&#R_6&3$%M_DLc3U7O>0gk;15Z_DC@tpGBBjP~0bq{+>=Ctbc#r27=#NqNT-KXR*L&iJi5YaxB|sFEEKn(_@{;9Bdmk3Fc=0i z0ir%ClvxlBab!34%m-l}xG)CL98nSB# z+rsu}2PpGGI->tf3+bSfLOSCwZ^z@hQf=^^cgKg`#NLys9=(~{(Nl?EhJ8PKU)UY( zuCq6~^SJ)h05||`raI6b!nBRSP`qP9sljjF5k~sn1}V#dP+w&>1?*Vh;P+I`d){ zl-Uz=bQ;A1_Ve-c&&S6vPW}a~Ma;HX49CK;_9d*bT*DY98!Vy5Fx6lgT+SN9b*-?k zWOl&{YPC))SO?eX#EFeM$6*sw3AWg`>MRHMl!=`>2|}hy?9gcvJ9Ku0%yy7T5Bqew z!akkbaDWL02ia|h+x0Amm|?J$+D09LNBFAnD0-Af36G)2IE(N&dYrQgPoO7wPGJ~& zk_v-6b$<#y#gz%e(bMphy4k~VvY%lWfu6>_9s$pzr*W;HhZoV)c;+uEPxDW*V)}+F2G%QAALaGhxeHV5QPdK zqR}u4MyayH|uWnPvx?O2W z<01bZxPdWIm1eEyLW8+ef<8iM0 zSV?iNC$+>go&<_ZJQYlZBRq{IF7q^0I+pm&(^08lDl0uJ6^~6%W#B96t&BV}oid}9 z$?~-_B8)ba#ReLc5&9992ckjaAy~XB~d?o*(KPOhNW;c2cRWjajT4_ZfRJ~ zDv#f}0xJk-b47J7SB4evz>0ra9L$yR)rw=eCai|jR$R5>tF4K%wl2QeU|1V3ZG9ZJ z;;{|JsoMz0ZDZI(UAm3br7LWtj@@RsXj@Rt@X@xi#Ov#J)3(A>D@W~MJN28kM>|j* z@uqfAc1Fdi*-1UKo$+ppdsbYX;+yU1ak%zYS8PAn52xY)v_CaK-I{}N8-}Rka0nc# zZo^?X8;9bF9Hzd=5pbmXA4lPf9DyHlG+xOO>OvK_)c({P$j#a}vA9jDWAm(Ea!=?rz4&cI(fh_4Ld`a<+^A$;dlu5dEP zLpbAPC=SgbT&X<&cytUkLfxqIaG%b_bvjo)r=klv64&W`b)L@0Ke*Y1595P8;_*Tr#=CfgI%XaJk5};&M<>;( zct+iGr`4kv4#WACQ|NKLbrI~&QQ}rSkN57Jy73~=vv~6^oc$dte8~C4>m_G?55)@>13#jl)Q|TG z*Vsq(k9~q))kpS~-8bA|;w<~2uCVXuFOTEy2hK1r$1iAecsax$=7s0Zjw8$qKbXbN zffvkz-l&W6#s%h5#zMt4XP{B%Tx?|=yi9KQTmm$ndc55Jx%fDl65{?64_y*Ud~-=D zAN9%!lXIK`CWFZw&$v@5lqorWg~#m$_bHVlC62FD9H*vUbEi@}UUQ$HS;-tvC?Dv9 zdn+;JqYgrGcm2VuOH2LWKBUDf=feLbKEZT&!kiTUmjN@t47_$P_{&Pb)n{~MqT<1L z>ecdP7l+sD%QD;tUn(>ACo}rf@>A!XAFj3URu;!s>N9sf3y!tV{4}!SA^U7)bA05d zB#yO@R(9Nb@8LTuDekf)tQ|)U{O{hm`oeN~*idA1{>K;pMNct+FssJzPQfjVj_ts)SaiD#1$X z*{ZCpg2OA2sshEMRGq2UxTWtey-a1pZ>zbCC;aMc(dw4@n$u`chvwk zQTJ6dw5i8q)e3Ei_o)s3rgmss^*(jP_0%4>RA)R+U13-CHg#5tGpVQgo5Zms9wu=! z^``pZ;_0pIqrN4#d+Gn2O9OH3^jFW)5cTT}#d$Lr56&<+oHZEN(g-+`8U;tG186j! zrIG4e8j0^`9D8vsjif^11WLR&;$RZ*k9cv!1vCXt#$7Xk-848AXUzol@JwJgll=@F zOS5s-%teLs(0RCiX5pw=h_7a`x@(r;6+tuiP?yhgbUlw;iw9^qyA9Mvd_NoDX7*dCwQwtL9k+u=d_BwAZ-+Zs%X#chY8PMG zseYcF?DulycJv(ZxOooZ-`R(&$L-}g>Nw^&u6~{4cy`3ebA+#)M1{v3rySv^@RZ}U zI(dYraq^t~A1}`ZTsz|8xuhPRa2Sqr=Q2D4&*0;Upw2+?^juRX&kYf{j@Pf~V1>fT8TlXILL|4RxeKA4pFc2Ys{ z@1$1uP8#$NkB8?E_3xx}rgy$^h?nP$BLn-_@Qov*GXuMfR3>L8_zHdH@TD?4h40XJ z4nL>x1Ny;{#VP!ResW}W3csLV9NBRCh^r`@^P3|(&Yy4Gz3k2(4)Oko7b*u1px=(1 zlo#HgoK7!XKyD{c9^5~<{&fN6apqGm5Ff#?%MbI*|Ba|8$l)x&JuOIyFDMoo3ynjG zBdQ<{A-R|F&;;rhN`w~XJ{IOqivOqxD<{uZgeuAs*HKZ558|O+{Zqxbx5c>INpbXu zyC^C5_fM)gRYI8xO@-4)mD5{BvsD|?|97MJ8_|$=Q z@q^UEA0oaHw|A&94xdIiOvE$Pj3rK?7S5K=R;-pd9NMVEr=9vA#N*S6)$w0fPd8iu z-RZRNiC>}@?2QXSTsz|3`Ny#nf@5b8z5hdS35ZW;7*2r^>J%7(w_&t;aK^%M^x22f zUq1m(q`!VL{q^FynM$|*bb9s0X)}{v`q_9g#8Wd@y)g^ay|7UIFH7K395BnMrEsZw zT$U-9tFL8+awT2(Yt-K&?iTU5h^u9dx?9}dmi6jx*@y#S1G~-WCXc&ii~3u(DYvV? zWruPn9v5-72zR4<@U-mnxLFRUPvL;})E`n$%OUNjKjJ*NJP6$$mUH+tE>P!il3ak7)ZuXnuf=6`UI?$_xwwvJ zLwFOtiTgr$TU{7;ly~u9h&w=dAH4@3zz6sxB4HGaqT@fB?)%47jPeN@gY)4jd`hqW zQ@ZwrFVL6jT9E$ySL|QWt1sR7(v$xleUE;CAGGiPGk$?j_y;~ypXlTNs_uawxB`CR z2l(}$9)3Ih^S<8FNAFAjxgSfq(EX@v ztgQ5#OZRyW`pczXJ*W1R=hdEf>3)}f_qQfEqCzh^q=~$MY z@}}O>OWw@8xpxcimh_BE4|!{E=^JkcrC(h7#5;L9#kzKZghk9@brWC(q8bs zbm{h?`qP;^ke=KD)FAEimTvE%p8js>dmc$|_h{|tmj2{1?8l>{=|&!}J>3&LUEP!D zrk+AKw{&k$qozRV-Zs;xA7rk9Op|@%u^maO) zr5k#O_CD{>uIJrwFUNc7YTgH>yLms|%+lF>5FVtL`7m`59@HM@qwJ2*>3fu(-=pYp z>I6JNFY|HgBr8n&n5BzZdYPq*S-P2}pZTnIF-Lehna|R_d`|nAh3-D)Wu9*4W!#xd z+!yI)zN+2K*Z%EazC{P~ZMv7Q!|QY|OaJn1cn|$=_j086Do4_h98D*3q%vANlOHJ` zYd3O?^2xvb$j|9Me!+T0AMy)I`jMp@`86HLZ?z-&J(N!5_u7U0p8n&HbRd7!KIBi@ zf&7imW9dWwLFe&T}Q=MYNar zPxvSOuO(1naqW*SrTxUxKU|8=S%2tHH*Nqd1Iy5rTNVbwK>C5p!SdP{EFHQP>GG{W zRn{KgO0W`rywy-)b+kHK1J?MrgSRGKytOFl+x?5q-P%-LWw7?d2GjXkm#WXI=jnTG z=;?ZOcfdB%&f7-Xaoa@uW}9dyZBy8st?rX8|9U{5+_d(s#C-~L$XjqOce zY;Wz0?W^pk{ju&Y*#X+Q8bY6}yH|DyeX@h-TpjeEZrNelCp%nwSclOQIf^b;;YjTy z9j(2yV>~^xq4dm#(jPmHnn;K2IPE2szR}5aib{v4cq2&4csk82pZm1;b3eVF2iZx-r}TdwL61^LwBJ*BM7uqYDUWN%=Lzljl>SfY`#h;# zpW)i~DczrE`C0@#1EuRTf^N@qp3cvU=y}&g>Jpuv7nPUj_55$&=Qa90uhR8-1Kpz? zpnK^5l>Kh>7PXt+&s)mfys|qS38jbj4t1CNa2J)n(5;@n&7}*o*hJm&(Xv1Abf!yg@?Jf`+3w}Dh9^5Uby7Izbp>AGZoETn2UV!IZUIe|-NB}KpF%Hc#PNg^MG~UJqUB;MLlnchzeH=8d ziD%-&1ZX^%5GFK8E|b^fLxp)wep3Jy<~IdRAyin<6gEXrVPR7g7B$6SG4rP>P8Ea2ObJsG z6_zliOleeD%J@Tn695BD8CHNP3(J~7Q;sSN%W~CzXckl61epq^A}kNfn@X(mrn0GG zs+wxBnyGGTP}N{HQ@(4cf-ERklOhns%l=Y;QVH9hIHX&QvGZMfY9N z&Ze8`YPy>qrYG!adYRr-PuSD+F?~^CAJfnDM}_^&05cF3hM*znAUMbjHbbaEa40$q z4uM0=a5DlG4mTssC{#Goj5cFX;b=1!jy2=pI5VC#&V<5HGl3OqCc=qkl9^0RgcHpa zIK@nbQ_VCpotkQ9=x!#QVP;aZ%xqLR2b~LN!+GdjGasF67N85vLggZKky#A8Q;XqZ zvxNK79WFJ?%yPKgtS~F7<#4%K#ahj7m05$XF>96U&~;|Las#^FY%m+)Mze|9tlWZb zHe1YAxYcZ9Z8h8BcC&-psoaI`G`q}hxZCVu?KXSiUbD~aHwVl?cnCd&9)^d_5p$F} z43DA5;Zb-34THzwN%Ry9gJCAzoHl37S$Nh&m~*Hw!kmZa%>{VDTx4A^m*6FH*<7J6 z!As^U>l(Z3=7zZmZ<<@Io8~sWZSJsco4fF?xo7U12PV=)p~6V>&_tuchvpG{WFEuE zCdNFW9>b^TQ}h{pW}cfD)HC?Zyo4{!EBMO1HgBj`@RfOM-l4+x=m+=?zB3;a^zL_8BPwEH!VSX7R&;Nh__x}Ku#p_G} literal 0 HcmV?d00001 diff --git a/models/editor/sf2/pvpspawn.vvd b/models/editor/sf2/pvpspawn.vvd new file mode 100644 index 0000000000000000000000000000000000000000..df1143342d6ab904e602641aae1db34dc4d9a12d GIT binary patch literal 504960 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

    +wIuU913B<^-Q+eqtkhYe>SyP%U08y$`(b&P{P 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�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 ZWIKB5HR^jSb}OIy+tETe^Hn>a{{v)bYp4JK literal 0 HcmV?d00001 From b8e57882a4b0b5b7ff869db6b8e0b7bdc87dc07f Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Thu, 1 May 2025 19:09:43 -0700 Subject: [PATCH 75/86] Update sf2.txt --- addons/sourcemod/gamedata/sf2.txt | 135 +++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 11 deletions(-) diff --git a/addons/sourcemod/gamedata/sf2.txt b/addons/sourcemod/gamedata/sf2.txt index cbfa4406..6babec27 100644 --- a/addons/sourcemod/gamedata/sf2.txt +++ b/addons/sourcemod/gamedata/sf2.txt @@ -6,103 +6,159 @@ { "CTFPlayer::WantsLagCompensationOnEntity" { - "linux" "335" - "windows" "334" + "linux" "336" + "linux64" "336" + "windows" "335" + "windows64" "335" } "CTFWeaponBase::GetCustomDamageType" { - "linux" "387" - "windows" "381" + "linux" "388" + "linux64" "388" + "windows" "382" + "windows64" "382" } "CBaseEntity::ShouldTransmit" { "linux" "21" + "linux64" "21" "windows" "20" + "windows64" "20" } "CBaseEntity::UpdateTransmitState" { "linux" "22" + "linux64" "22" "windows" "21" + "windows64" "21" } "CBaseProjectile::CanCollideWithTeammates" { - "linux" "230" - "windows" "229" + "linux" "231" + "linux64" "231" + "windows" "230" + "windows64" "230" } "CTFPlayer::EquipWearable" { - "linux" "439" - "windows" "438" + "linux" "440" + "linux64" "440" + "windows" "439" + "windows64" "439" } "CBaseTrigger::PassesTriggerFilters" { - "linux" "208" - "windows" "207" + "linux" "209" + "linux64" "209" + "windows" "208" + "windows64" "208" } "CTFWeaponBase::GetWeaponID" { "windows" "380" "linux" "386" + "linux64" "386" + "windows64" "380" } "CBaseEntity::IsBaseCombatWeapon" { "windows" "91" "linux" "92" + "linux64" "92" + "windows64" "91" } "CBaseEntity::ChangeTeam" { "library" "server" "linux" "97" + "linux64" "97" "windows" "96" + "windows64" "96" } "CTeam::AddPlayer" { "linux" "209" + "linux64" "209" "windows" "208" + "windows64" "208" } "CTeam::RemovePlayer" { "linux" "210" + "linux64" "210" "windows" "209" + "windows64" "209" } "CBaseGrenade::Explode" { "library" "server" "linux" "236" + "linux64" "236" "windows" "235" + "windows64" "235" } "CTFBaseRocket::Explode" { "library" "server" "linux" "240" + "linux64" "240" "windows" "239" + "windows64" "239" } "CBaseEntity::VPhysicsUpdate" { "library" "server" "linux" "164" + "linux64" "164" "windows" "163" + "windows64" "163" } "CTFSniperRifle::GetPenetrateType" { "library" "server" "linux" "407" + "linux64" "407" "windows" "400" + "windows64" "400" + } + + "CBaseEntity::Deflected" + { + "linux" "160" + "linux64" "160" + "windows" "159" + "windows64" "159" + } + + "CBasePlayer::Event_Killed" + { + "linux" "69" + "linux64" "69" + "windows" "68" + "windows64" "68" + } + + "CBaseObject::OnTakeDamage" + { + "linux" "65" + "linux64" "65" + "windows" "65" + "windows64" "65" } } @@ -216,6 +272,14 @@ "windows" "\x55\x8B\xEC\x53\x8B\xD9\x56\x57\x8B\x93\x10\x02\x00\x00\x85\xD2\x0F\x84\x2A\x2A\x2A\x2A\xB9\xFF\x1F\x00\x00\x83\xFA\xFF\x74\x2A\x0F\xB7\xCA\xA1\x2A\x2A\x2A\x2A\xC1\xE1\x04\x8D\x78\x04\x03\xF9\x0F\x84\x2A\x2A\x2A\x2A\xC1\xEA\x10\x39\x57\x04\x0F\x85\x2A\x2A\x2A\x2A\x8B\x3F\x85\xFF\x0F\x84\x2A\x2A\x2A\x2A\x8B\x07\x8B\xCF\x8B\x80\x48\x01\x00\x00\xFF\xD0\x84\xC0\x0F\x84\x2A\x2A\x2A\x2A\x8B\x75\x08\x85\xF6\x74\x2A" "windows64" "\x48\x89\x5C\x24\x10\x48\x89\x6C\x24\x18\x48\x89\x74\x24\x20\x57\x48\x83\xEC\x30\x48\x8B\xDA" } + + "CTFPlayer::TeamFortress_SetSpeed" + { + "linux" "@_ZN9CTFPlayer21TeamFortress_SetSpeedEv" + "windows" "\x55\x8B\xEC\x83\xEC\x24\xA1\x2A\x2A\x2A\x2A\x56\x8B\xF1\x89\x75\xF0" + "linux64" "@_ZN9CTFPlayer21TeamFortress_SetSpeedEv" + "windows64" "\x40\x55\x48\x83\xEC\x60\x48\x8B\x05\x03\x3D\x7A\x00" + } } "Functions" @@ -326,6 +390,55 @@ } } } + + "CBaseEntity::Deflected" + { + "offset" "CBaseEntity::Deflected" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "pDeflectedBy" + { + "type" "cbaseentity" + } + "vecDir" + { + "type" "vectorptr" + } + } + } + + "CBasePlayer::Event_Killed" + { + "offset" "CBasePlayer::Event_Killed" + "hooktype" "entity" + "return" "void" + "this" "entity" + "arguments" + { + "info" + { + "type" "objectptr" + } + } + } + + "CBaseObject::OnTakeDamage" + { + "offset" "CBaseObject::OnTakeDamage" + "hooktype" "entity" + "return" "int" + "this" "entity" + "arguments" + { + "info" + { + "type" "objectptr" + } + } + } } } -} +} \ No newline at end of file From 44b9da40f5a602f576e680f7f16f39290d456b90 Mon Sep 17 00:00:00 2001 From: Mentrillum Date: Sat, 7 Jun 2025 09:28:26 -0700 Subject: [PATCH 76/86] Delete TERMSOFSERVICE.md --- TERMSOFSERVICE.md | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 TERMSOFSERVICE.md diff --git a/TERMSOFSERVICE.md b/TERMSOFSERVICE.md deleted file mode 100644 index 640f400e..00000000 --- a/TERMSOFSERVICE.md +++ /dev/null @@ -1,35 +0,0 @@ -# Terms of Service - -Welcome to Slender Fortress Modified. These are the terms and service for use of the gamemode which you may use in many differnt ways, including but not limited to content creation, server hosting, and beta testing. By hosting Slender Fortress Modified, you are agreeing to be bound by these Terms. If you disagree with any part of the terms then you may not use Slender Fortress Modified. - -## 1. Server Hosting - -Slender Fortress Modified is a Sourcemod plugin offered by Mentrillum for free. If you plan to only use the gamemode for a private server that no one else can join, you can skip this section of the Terms of Service. You may host a server publicly for full or part time and have any compatible plugin you'd like to add to your server. You may not host your server publicly or promote it in any way which includes announcements and video showcases if one of these conditions are met. - -### A. Stolen Content - -As of April 24th, 2024, any content taken from other servers is now dependent on where the content came from. Server owners and or boss/map creators which port assets from other games or similar medias do not have the right to request a content removal for other servers or platforms like Garry's Mod. This doesn't apply to assets which were created by the map/boss creators as the creators do get to decide on how their content is handled. The latter of which has specific rules. Attempting to take self-made assets without the creator's intentions falls under content stealing. Removing the stolen content will no longer violate this rule. Consider this your warning. - -### B. Explicit Content - -Any content found in your server that contains Not Safe For Work (NSFW) elements or is prone to violate server rules specifically against NSFW content is prohibited to add and keep. Slender Fortress' main audience consists of users under the age of 18. Showing off the content in video form will result in you having to take that video down, not privating the video or unlisting it, but deleting it. Very few exceptions can be made where you can put age restrictions on your videos. Any other content that is prone to violate any server rules outside of your server such as explicit slurs, enforcing political and religious beliefs, fetishes, signs of sexual elements, nudity or revealing elements, discrimination, and propaganda are prohibited. We allow bosses like The Twins if any of these elements are censored out enough. - -### C. Server Handling - -Hosing a server and promoting it with the sole purpose of luring children in to show any explicit content, trying to enforce beliefs on users with specific content, or traumetizing children is prohibited. This includes failing to enforce your server rules properly, exposing the alformentioned factors publicly, creating content to specifically enforce beliefs, or not creating any server rule that stops any of the alformentioned factors from happening. We do not take this part lightly, consider fixing your server if this rule is violated. - -## 2. Content Creation - -Creating any form of content follows the same rules as hosting a server with extra considerations to take into account. If you do not plan to show the content off to anyone which includes servers even after the content is fully finished, you may skip this part of the Terms of Service. You may release any content publicly or privately. By allowing any server to take your boss, you agree to let server owners or developers edit your boss without your permission or consent. Unless the content is self-made in which you do have the right to enforce takedowns, but if you've done only ports, you cannot enforce takedowns. So these rules will only apply to self-made content. - -### A. Content Submissions + Remakes - -You may skip this part of the Terms of Service if you don't plan to submit any content to be displayed on the Github page for everyone to easily find and download or to any officially supported servers. Submitting any content to Mentrillum where it can be added on the Github wiki means you agree to follow the content creating guidelines before submitting your content. Any small detail that may hint at any of these violations will result in a request for removing that small detail. Failing to do so will not get your content added on the Github. If these details are noticed months after the content was added, it will be taken down immediately after the discovery and will only be readded if you remove those details. Content will also be reviewed based on the quality of the boss, we do not want carbon copy bosses sitting on the Github page. This does not mean your boss or map has to be balanced out perfectly. The same logic will apply to submitting content to servers that are officially supported (Disc-FF, Demon Hamster's server, etc). Fully remaking every asset a creation uses is allowed and doesn't need permission from the original self-made asset creator(s), which also includes restorations, sub plugin recreations, etc. - -## 3. Consequences - -After you are caught violating any of these rules, you will be approached by one of the higher ups at the SF2M team and will be requested to take action to fix any of these violations which may or may not include deleting parts or all of a certain piece of content, adjusting your server rules, deleting parts or a whole video, etc. Going forth with these requests and proving you put in effort to fix these violations without question will not make anything major happen depending on if these violations were intentional or unintentional. Intentionally violating these terms and fixing the violations will result in a warning meaning you can still create content and host a server. Depending on if you just create content for servers or host a server and make videos about it, the second warning will be different. For just creating and submitting content boss or map related, you will be given a second warning if these violations intentionally happened which most of the time is the case. The third warning for creating and submitting content boss or map related or the second warning for hosting a server and making videos on that server will depend on which of the following actions you do. Creating and submitting content will result in a blacklist that every server owner can see which they can quickly make the choice to either avoid your content or carelessly add it either way, the blacklist will have an associated reason as to why this blacklist happened. Getting a second warning for hosting a server or making videos about a server showing content that violates these terms will also result in a blacklist but for average players. Neglecting to fix these actions in any way which includes ignoring any team member's request, being defiant about these requests, never responding to these team members, or continuing to violate these already violated terms to a greater degree will result in a immediate blacklist. - -## 4. Appeals - -If you are already blacklisted in any way but you believe you have changed enough to where you don't violate these terms anymore, you can negociate with Mentrillum proving these terms are no longer violated and you show you will not violate these terms any longer. These appeals can only be given after a one month grace period since the blacklist addition. If these appeals are accepted, your blacklist will be removed and you'll be back to square one in terms of the warning system. Rejected appeals typically mean either you haven't changed or you aren't trusted enough to create content, host a server, or make videos about both that may violate the terms, if this is the case you'll have to wait for an undetermined long period of time. Checking back in on if an appeal can be accepted in an annoying way will lessen the likelihood of the current and following appeals to be accepted. From c762fb1756c90639c6ed64ef74a3fe7aaacf09dd Mon Sep 17 00:00:00 2001 From: CookieCat <81494827+CookieCat45@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:38:41 -0400 Subject: [PATCH 77/86] Update entity.sp (#158) --- .../scripting/sf2/npc/entities/base/entity.sp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp index 21f1e8a5..2d4dc6df 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/base/entity.sp @@ -184,32 +184,32 @@ methodmap SF2_BaseBoss < CBaseCombatCharacter public bool GetIsVisible(CBaseEntity entity) { - return this.GetProp(Prop_Data, "m_IsEntityVisible", entity.index) != 0; + return this.GetProp(Prop_Data, "m_IsEntityVisible", _, entity.index) != 0; } public void SetIsVisible(CBaseEntity entity, bool value) { - this.SetProp(Prop_Data, "m_IsEntityVisible", value, entity.index); + this.SetProp(Prop_Data, "m_IsEntityVisible", value, _, entity.index); } public bool GetInFOV(CBaseEntity entity) { - return this.GetProp(Prop_Data, "m_IsEntityInFOV", entity.index) != 0; + return this.GetProp(Prop_Data, "m_IsEntityInFOV", _, entity.index) != 0; } public void SetInFOV(CBaseEntity entity, bool value) { - this.SetProp(Prop_Data, "m_IsEntityInFOV", value, entity.index); + this.SetProp(Prop_Data, "m_IsEntityInFOV", value, _, entity.index); } public bool GetIsNear(CBaseEntity entity) { - return this.GetProp(Prop_Data, "m_IsEntityNear", entity.index) != 0; + return this.GetProp(Prop_Data, "m_IsEntityNear", _, entity.index) != 0; } public void SetIsNear(CBaseEntity entity, bool value) { - this.SetProp(Prop_Data, "m_IsEntityNear", value, entity.index); + this.SetProp(Prop_Data, "m_IsEntityNear", value, _, entity.index); } property float CurrentChaseDuration From dd191b4bfd55d4d1d9ae363532bcea3e11de5660 Mon Sep 17 00:00:00 2001 From: JuegosPablo <40556796+JuegosPablo@users.noreply.github.com> Date: Sun, 19 Oct 2025 18:39:45 -0300 Subject: [PATCH 78/86] Translation Update For Renevant (#152) * Translation Update Renevant * Update renevant.sp --- .../scripting/sf2/gamemodes/renevant.sp | 49 ++++++++++++------- addons/sourcemod/translations/sf2.phrases.txt | 37 +++++++++++++- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp index 96c0b335..d258e473 100644 --- a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp +++ b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp @@ -139,7 +139,7 @@ static void Renevant_DoWaveAction(RenevantWave action) if (addedBossCount == 1 && action != RenevantWave_DoubleTrouble && action != RenevantWave_DoomBox && action != RenevantWave_SingleBoss && action != RenevantWave_AdminBoss) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s","SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } @@ -157,21 +157,21 @@ static void Renevant_DoWaveAction(RenevantWave action) { g_DifficultyConVar.IntValue = Difficulty_Hard; - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nDifficulty set to: %t", "SF2 Hard Difficulty"); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Hard Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Hard: { g_DifficultyConVar.IntValue = Difficulty_Insane; - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nDifficulty set to: %t", "SF2 Insane Difficulty"); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Insane Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Insane: { g_DifficultyConVar.IntValue = Difficulty_Nightmare; - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nDifficulty set to: %t", "SF2 Nightmare Difficulty"); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Nightmare Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Nightmare: @@ -255,7 +255,8 @@ static void Renevant_DoWaveAction(RenevantWave action) case RenevantWave_MultiEffect: { g_RenevantMultiEffect = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nBosses can now inflict Multieffect."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Multieffects"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_MultiEffect); if (eraseWave != -1) { @@ -265,7 +266,8 @@ static void Renevant_DoWaveAction(RenevantWave action) case RenevantWave_BaconSpray: { g_RenevantBeaconEffect = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nBosses are now alerted on spawn."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Bacon"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_BaconSpray); if (eraseWave != -1) { @@ -281,12 +283,12 @@ static void Renevant_DoWaveAction(RenevantWave action) if (addedBossCount == 1) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s","SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } else if (addedBossCount == 2) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBosses: %s and %s", name, name2); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s and %s","SF2 Bosses Added", name, name2); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } } @@ -303,17 +305,17 @@ static void Renevant_DoWaveAction(RenevantWave action) if (addedBossCount == 1) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s","SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } else if (addedBossCount == 2) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBosses: %s and %s", name, name2); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s and %s","SF2 Bosses Added", name, name2); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } else if (addedBossCount == 3) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBosses: %s, %s, and %s", name, name2, name3); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s, %s, and %s","SF2 Bosses Added", name, name2, name3); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_DoomBox); @@ -325,7 +327,8 @@ static void Renevant_DoWaveAction(RenevantWave action) case RenevantWave_90s: { g_Renevant90sEffect = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nYou feel very nervous."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant 90s"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_90s); if (eraseWave != -1) { @@ -351,7 +354,8 @@ static void Renevant_DoWaveAction(RenevantWave action) } TF2_AddCondition(client, TFCond_MarkedForDeathSilent, -1.0); } - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nEveryone is marked for death permanently."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Marked"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_MarkForDeath); if (eraseWave != -1) { @@ -388,15 +392,25 @@ static void Renevant_DoWaveAction(RenevantWave action) { strcopy(name, sizeof(name), buffer); } - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s", "SF2 Admin Boss", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); + char nightmareDisplay[256]; + + for (int i = 0; i < sizeof(g_SoundNightmareMode)-1; i++) + { + EmitSoundToAll(g_SoundNightmareMode[i]); + } + + FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%s", name); + + SpecialRoundGameText(nightmareDisplay, "d_purgatory"); } } else { if (Renevant_TryAddBossProfile(buffer, sizeof(buffer), name, sizeof(name), false)) { - FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\nBoss: %s", name); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %s", "SF2 Boss Added", name); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } } @@ -407,9 +421,10 @@ static void Renevant_DoWaveAction(RenevantWave action) } } case RenevantWave_WallHax: - { + { g_RenevantWallHax = true; - StrCat(broadcastMessage, sizeof(broadcastMessage), "\nYou can now see players and bosses through walls."); + FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Wall Hax"); + StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); int eraseWave = g_RenevantWaveList.FindValue(RenevantWave_WallHax); if (eraseWave != -1) { diff --git a/addons/sourcemod/translations/sf2.phrases.txt b/addons/sourcemod/translations/sf2.phrases.txt index 3224f6ec..0fe4a9b2 100644 --- a/addons/sourcemod/translations/sf2.phrases.txt +++ b/addons/sourcemod/translations/sf2.phrases.txt @@ -9,7 +9,42 @@ "pt" "Selecione uma dificuldade:" "ko" "난이도 선택:" } - + "SF2 Renevant Wall Hax" + { + "en" "You can now see everything through walls" + } + "SF2 Renevant Marked" + { + "en" "Everyone is marked for death permanently" + } + "SF2 Renevant 90s" + { + "en" "Everyone run more faster" + } + "SF2 Renevant Bacon" + { + "en" "Now Bosses are now alerted on spawn" + } + "SF2 Renevant Multieffects" + { + "en" "Now Bosses can now inflict Multieffect on damage" + } + "SF2 Renevant Difficulty" + { + "en" "Now difficulty got changed to" + } + "SF2 Boss Added" + { + "en" "New Boss" + } + "SF2 Bosses Added" + { + "en" "New Bosses Added" + } + "SF2 Admin Boss" + { + "en" "Special Boss Added" + } "SF2 Difficulty Vote Finished" { "en" "Vote successful! The difficulty has been changed to:" From 56b4d5b20953672e0b00371e7c6acb2e54c87b66 Mon Sep 17 00:00:00 2001 From: NNinja1255 <50771176+NNinja1255@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:40:08 -0400 Subject: [PATCH 79/86] fix static shake implementation (#151) * fix static shake implementation fixes a stupid typo that broke the static shake. this took so long to track down since it was easy to miss and it didn't produce any errors. * Update profiles_boss_functions.sp --- .../sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index 01261cc4..ae908928 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -567,7 +567,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu { bossFlags |= SFF_WEAPONKILLSONRADIUS; } - if (kv.GetNum("static_shake"), 0) + if (kv.GetNum("static_shake")) { bossFlags |= SFF_HASSTATICSHAKE; } From c34c8c8c4787ae4795954be6e2bcef28c704770f Mon Sep 17 00:00:00 2001 From: NNinja1255 <50771176+NNinja1255@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:42:52 -0400 Subject: [PATCH 80/86] fix deathcams from static (#150) * fix deathcams from static this makes it so that bosses with non-public deathcams still play their deathcam when killing you with static. * I almost missed this * I have no idea how I missed this as well, wtf * dammit * I accidentally made this an array, whoops --- addons/sourcemod/scripting/include/sf2.inc | 6 ++++-- addons/sourcemod/scripting/sf2/client.sp | 2 +- addons/sourcemod/scripting/sf2/client/deathcam.sp | 6 +++--- addons/sourcemod/scripting/sf2/extras/natives.sp | 2 +- addons/sourcemod/scripting/sf2/methodmaps.sp | 6 +++--- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index 89a8ab27..d4269270 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -1268,8 +1268,9 @@ native bool SF2_IsClientInDeathCam(int client); * @param bossIndex Boss index. * @param vecLookPos The position the player should look at. * @param antiCamp Whether the player was camping or not. + * @param staticDeath Whether or not the player's death was caused by static. */ -native void SF2_ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false); +native void SF2_ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false); /** * Returns whether or not a proxy can be spawned, returns a teleport position or spawn point if defined and the result is true, works well with SF2_ClientForceProxy. @@ -2699,8 +2700,9 @@ methodmap SF2_Player < CBaseCombatCharacter * @param bossIndex Boss index to use * @param lookPos What position the player should look at * @param antiCamp Whether or not this was an anticamping death, only useful for public death cams + * @param staticDeath Whether or not the player's death was caused by static. */ - public native void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false); + public native void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false); /** * Whether or not the player has escaped diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index fd42a215..63b08d67 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -530,7 +530,7 @@ void ClientProcessVisibility(int client) // Process death cam sequence with static if (g_PlayerStaticAmount[client] >= 1.0) { - ClientStartDeathCam(client, NPCGetFromUniqueID(g_PlayerStaticMaster[client]), slenderPos, true); + ClientStartDeathCam(client, NPCGetFromUniqueID(g_PlayerStaticMaster[client]), slenderPos, false, true); } } } diff --git a/addons/sourcemod/scripting/sf2/client/deathcam.sp b/addons/sourcemod/scripting/sf2/client/deathcam.sp index c169291c..7d933138 100644 --- a/addons/sourcemod/scripting/sf2/client/deathcam.sp +++ b/addons/sourcemod/scripting/sf2/client/deathcam.sp @@ -196,7 +196,7 @@ static Action Timer_DeleteRagdoll(Handle timer, int client) return Plugin_Continue; } -void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false) +void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false) { if (IsClientInDeathCam(client)) { @@ -248,9 +248,10 @@ void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool Call_PushCell(bossIndex); Call_Finish(); + bool publicDeathcam = data.PublicDeathCam || data.DeathCamData.Enabled; if ((NPCGetFlags(bossIndex) & SFF_FAKE) == 0) { - if ((!NPCHasDeathCamEnabled(bossIndex) || antiCamp)) + if ((!NPCHasDeathCamEnabled(bossIndex) || antiCamp || (staticDeath && publicDeathcam))) { SetEntProp(client, Prop_Data, "m_takedamage", 2); // We do this because the point_viewcontrol changes our lifestate. @@ -289,7 +290,6 @@ void ClientStartDeathCam(int client, int bossIndex, const float lookPos[3], bool // Create fake model. int slender = -1; - bool publicDeathcam = data.PublicDeathCam || data.DeathCamData.Enabled; if (!publicDeathcam) { slender = SpawnSlenderModel(bossIndex, lookPos, true); diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 952821e9..890c5221 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -617,7 +617,7 @@ static any Native_ClientStartDeathCam(Handle plugin, int numParams) { float pos[3]; GetNativeArray(3, pos, sizeof(pos)); - ClientStartDeathCam(GetNativeCell(1), GetNativeCell(2), pos, GetNativeCell(4)); + ClientStartDeathCam(GetNativeCell(1), GetNativeCell(2), pos, GetNativeCell(4), GetNativeCell(5)); return 0; } diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index 68cb4d7c..9f9b93dc 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -862,9 +862,9 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } - public void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false) + public void StartDeathCam(int bossIndex, const float lookPos[3], bool antiCamp = false, bool staticDeath = false) { - ClientStartDeathCam(this.index, bossIndex, lookPos, antiCamp); + ClientStartDeathCam(this.index, bossIndex, lookPos, antiCamp, staticDeath); } property bool HasEscaped @@ -2090,7 +2090,7 @@ static any Native_ClientStartDeathCam(Handle plugin, int numParams) SF2_BasePlayer player = SF2_BasePlayer(client); float buffer[3]; GetNativeArray(3, buffer, 3); - player.StartDeathCam(GetNativeCell(2), buffer, GetNativeCell(4)); + player.StartDeathCam(GetNativeCell(2), buffer, GetNativeCell(4), GetNativeCell(5)); return 0; } From a1ceeae34b6e6898cf1c08dd1ebeff0c540272df Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Fri, 26 Dec 2025 22:16:43 -0700 Subject: [PATCH 81/86] RC1 --- addons/sourcemod/scripting/include/sf2.inc | 4 +- .../include/sf2/profiles/profiles.inc | 12 + .../include/sf2/profiles/profiles_chaser.inc | 5 +- addons/sourcemod/scripting/sf2.sp | 39 +- addons/sourcemod/scripting/sf2/adminmenu.sp | 8 +- addons/sourcemod/scripting/sf2/client.sp | 484 +++++++++++++++++ .../sourcemod/scripting/sf2/client/proxy.sp | 4 +- .../sourcemod/scripting/sf2/client/think.sp | 434 --------------- .../projectiles/sf2_baseball_projectile.sp | 2 +- .../scripting/sf2/extras/bosspreview.sp | 504 ++++++++++++++++++ .../scripting/sf2/extras/commands.sp | 34 +- .../scripting/sf2/extras/game_events.sp | 3 + .../sourcemod/scripting/sf2/extras/natives.sp | 2 + .../scripting/sf2/gamemodes/renevant.sp | 12 +- addons/sourcemod/scripting/sf2/mapentities.sp | 4 + .../sf2/mapentities/sf2_gamerules.sp | 2 +- .../sf2/mapentities/sf2_trigger_weapons.sp | 189 +++++++ addons/sourcemod/scripting/sf2/menus.sp | 54 +- addons/sourcemod/scripting/sf2/methodmaps.sp | 16 + .../chaser/actions/attacks/laser_beam.sp | 2 + .../entities/chaser/actions/attacks/melee.sp | 12 +- .../sf2/npc/entities/chaser/entity.sp | 47 +- addons/sourcemod/scripting/sf2/profiles.sp | 257 +++++++-- .../sf2/profiles/profiles_boss_functions.sp | 44 +- .../sourcemod/scripting/sf2/specialround.sp | 36 +- 25 files changed, 1659 insertions(+), 551 deletions(-) create mode 100644 addons/sourcemod/scripting/sf2/extras/bosspreview.sp create mode 100644 addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_weapons.sp diff --git a/addons/sourcemod/scripting/include/sf2.inc b/addons/sourcemod/scripting/include/sf2.inc index d4269270..ce4880fa 100644 --- a/addons/sourcemod/scripting/include/sf2.inc +++ b/addons/sourcemod/scripting/include/sf2.inc @@ -10,8 +10,8 @@ #include #include -#define PLUGIN_VERSION "1.8.0 M Alpha 2.73d" -#define PLUGIN_VERSION_DISPLAY "1.8.0 M Alpha 2.73d" +#define PLUGIN_VERSION "1.8.0 M RC1" +#define PLUGIN_VERSION_DISPLAY "1.8.0 M RC1" // Some defines. #define SF2_MAX_PROFILE_NAME_LENGTH 64 diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc index f4c99762..8cb7c864 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles.inc @@ -2413,6 +2413,12 @@ enum struct SF2BossProfileData SF2BossProfileSlaughterRunData SlaughterRunData; + bool EnableRandomSelection; + bool AdminOnly; + bool EnableRandomSelectionBoxing; + bool EnableRandomSelectionRenevant; + bool EnableRandomSelectionRenevantAdmin; + void Init() { this.Models = null; @@ -2643,6 +2649,12 @@ enum struct SF2BossProfileData this.SlaughterRunData.Init(); + this.EnableRandomSelection = false; + this.AdminOnly = false; + this.EnableRandomSelectionBoxing = false; + this.EnableRandomSelectionRenevant = false; + this.EnableRandomSelectionRenevantAdmin = false; + for (int difficulty = 0; difficulty < Difficulty_Max; difficulty++) { this.Skin[difficulty] = 0; diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index db3319b8..08a0b50c 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -409,7 +409,10 @@ enum struct SF2ChaserBossProfileDamageEffectData // Can be any data } case SF2DamageType_Bleed: { - TF2_MakeBleed(player.index, player.index, this.Duration[difficulty]); + if (!TF2_IsPlayerInCondition(player.index, TFCond_Bleeding)) + { + TF2_MakeBleed(player.index, player.index, this.Duration[difficulty]); + } } case SF2DamageType_Milk: { diff --git a/addons/sourcemod/scripting/sf2.sp b/addons/sourcemod/scripting/sf2.sp index 9fa543eb..86c84ce4 100644 --- a/addons/sourcemod/scripting/sf2.sp +++ b/addons/sourcemod/scripting/sf2.sp @@ -553,6 +553,7 @@ ConVar g_LoadOutsideMapsConVar; ConVar g_DefaultBossTeamConVar; ConVar g_EngineerBuildInBLUConVar; ConVar g_DisableTauntLoopsConVar; +ConVar g_BossPreviewWikiConVar; ConVar g_RestartSessionConVar; bool g_RestartSessionEnabled; @@ -642,6 +643,8 @@ GlobalForward g_OnDifficultyVoteFinishedFwd; GlobalForward g_OnIsBossCustomAttackPossibleFwd; GlobalForward g_OnBossGetCustomAttackActionFwd; GlobalForward g_OnProjectileTouchFwd; +GlobalForward g_OnPreSelectStartingBosses; +GlobalForward g_OnPostSelectStartingBosses; // Private forwards PrivateForward g_OnGamemodeStartPFwd; @@ -658,6 +661,7 @@ PrivateForward g_OnAdminMenuCreateOptionsPFwd; PrivateForward g_OnPlayerJumpPFwd; PrivateForward g_OnPlayerSpawnPFwd; PrivateForward g_OnPlayerTakeDamagePFwd; +PrivateForward g_OnPlayerTakeDamagePostPFwd; PrivateForward g_OnPlayerDeathPrePFwd; PrivateForward g_OnPlayerDeathPFwd; PrivateForward g_OnPlayerPutInServerPFwd; @@ -765,6 +769,7 @@ ArrayList g_PageLocationsGlow; #include "sf2/extras/commands.sp" #include "sf2/extras/game_events.sp" #include "sf2/extras/afk_mode.sp" +#include "sf2/extras/bosspreview.sp" SF2LogicRenevantEntity g_RenevantLogicEntity = view_as(-1); @@ -920,7 +925,6 @@ public void OnConfigsExecuted() continue; } g_ClientInGame[i] = true; - SDKHook(i, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); Call_StartForward(g_OnPlayerPutInServerPFwd); Call_PushCell(SF2_BasePlayer(i)); Call_Finish(); @@ -1075,6 +1079,7 @@ static void StartPlugin() ReloadClassConfigs(); NPCOnConfigsExecuted(); + BossPreview_ReloadPack(); InitializeBossPackVotes(); SetupTimeLimitTimerForBossPackVote(); @@ -2366,7 +2371,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) SpecialRoundGameText("Its Restart Session time!", "leaderboard_streak"); CPrintToChatAll("{royalblue}%t {default}Your thirst for blood continues? Very well, let the blood spill. Let the demons feed off your unfortunate soul... Difficulty set to {mediumslateblue}%t!", "SF2 Prefix", "SF2 Calamity Difficulty"); g_RestartSessionEnabled = true; - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + SetDifficulty(Difficulty_Apollyon); g_IgnoreRoundWinConditionsConVar.SetBool(true); g_IgnoreRedPlayerDeathSwapConVar.SetBool(true); g_BossChaseEndlesslyConVar.SetBool(true); @@ -2460,7 +2465,7 @@ void OnConVarChanged(Handle cvar, const char[] oldValue, const char[] intValue) { CPrintToChatAll("{royalblue}%t {default}You're done? Ok. Difficulty set to {darkgray}Apollyon.", "SF2 Prefix"); g_RestartSessionEnabled = false; - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + SetDifficulty(Difficulty_Apollyon); g_IgnoreRoundWinConditionsConVar.SetBool(false); g_IgnoreRedPlayerDeathSwapConVar.SetBool(false); g_BossChaseEndlesslyConVar.SetBool(false); @@ -3611,7 +3616,6 @@ public void OnClientPutInServer(int client) { if (g_LoadOutsideMapsConVar.BoolValue) { - SDKHook(client, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); Call_StartForward(g_OnPlayerPutInServerPFwd); Call_PushCell(SF2_BasePlayer(client)); Call_Finish(); @@ -3655,7 +3659,6 @@ public void OnClientPutInServer(int client) SDKHook(client, SDKHook_PreThink, Hook_ClientPreThink); SDKHook(client, SDKHook_PreThinkPost, Hook_OnFlashlightThink); SDKHook(client, SDKHook_SetTransmit, Hook_ClientSetTransmit); - SDKHook(client, SDKHook_OnTakeDamage, Hook_ClientOnTakeDamage); g_DHookWantsLagCompensationOnEntity.HookEntity(Hook_Pre, client, Hook_ClientWantsLagCompensationOnEntity); @@ -3811,7 +3814,7 @@ public void OnClientDisconnect(int client) CreateTimer(0.2, Timer_CheckAlivePlayers, _, TIMER_FLAG_NO_MAPCHANGE); } - if (!IsRoundInWarmup()) + if (!IsRoundInWarmup() && GameRules_GetRoundState() != RoundState_TeamWin) { if (g_PlayerPlaying[client] && !g_PlayerEliminated[client]) { @@ -3836,6 +3839,7 @@ public void OnClientDisconnect(int client) g_PlayerQueuePoints[client] = 0; AFK_SetTime(client, false); + BossPreview_Remove(client); Call_StartForward(g_OnPlayerDisconnectedPFwd); Call_PushCell(SF2_BasePlayer(client)); @@ -4038,7 +4042,7 @@ void SetRoundState(SF2RoundState roundState) g_Renevant90sEffect = false; g_RenevantMarkForDeath = false; g_RenevantBossesChaseEndlessly = false; - g_DifficultyConVar.SetInt(Difficulty_Normal); + SetDifficulty(Difficulty_Normal); if (g_RestartSessionConVar.BoolValue) { g_RestartSessionEnabled = false; @@ -7889,6 +7893,9 @@ static void SelectStartingBossesForRound() } #endif + Call_StartForward(g_OnPreSelectStartingBosses); + Call_Finish(); + ArrayList selectableBossList = GetSelectableBossProfileQueueList(); // Select which boss profile to use. @@ -8211,6 +8218,10 @@ void InitializeNewGame() ForceInNextPlayersInQueue(GetMaxPlayersForRound()); + Call_StartForward(g_OnPostSelectStartingBosses); + Call_PushStringEx(g_RoundBossProfile, sizeof(g_RoundBossProfile), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + Call_Finish(); + // Respawn all players, if needed. for (int i = 1; i <= MaxClients; i++) { @@ -8597,3 +8608,17 @@ void CheckRoundWinConditions() } } } + +// Fixes setting the same difficulty not calling our forwards +void SetDifficulty(int difficulty) +{ + char last[16], newDiff[16]; + g_DifficultyConVar.GetString(last, sizeof(last)); + IntToString(difficulty, newDiff, sizeof(newDiff)); + if (strcmp(last, newDiff, false) == 0) + { + StrCat(newDiff, sizeof(newDiff), " "); + } + + g_DifficultyConVar.SetString(newDiff); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/adminmenu.sp b/addons/sourcemod/scripting/sf2/adminmenu.sp index 90211f03..81e458e0 100644 --- a/addons/sourcemod/scripting/sf2/adminmenu.sp +++ b/addons/sourcemod/scripting/sf2/adminmenu.sp @@ -291,7 +291,7 @@ static int AdminMenu_PlayerSetPlayStateConfirm(Menu menu, MenuAction action, int return 0; } -static void DisplayBossMainAdminMenu(int client) +void DisplayBossMainAdminMenu(int client) { Menu menuHandle = new Menu(AdminMenu_BossMain); menuHandle.SetTitle("%t %T\n \n", "SF2 Prefix", "SF2 Admin Menu Boss Main", client); @@ -317,6 +317,8 @@ static void DisplayBossMainAdminMenu(int client) menuHandle.AddItem("force_alert", buffer); FormatEx(buffer, sizeof(buffer), "Make a boss use a attack"); menuHandle.AddItem("force_attack", buffer); + FormatEx(buffer, sizeof(buffer), "Force a mod boss to be loaded next pack"); + menuHandle.AddItem("force_bossload", buffer); menuHandle.ExitBackButton = true; menuHandle.Display(client, MENU_TIME_FOREVER); @@ -379,6 +381,10 @@ static int AdminMenu_BossMain(Menu menu, MenuAction action, int param1, int para { DisplayBossAttackAdminMenu(param1); } + else if (strcmp(info, "force_bossload") == 0) + { + Command_BossOverride(param1, 0); + } } return 0; } diff --git a/addons/sourcemod/scripting/sf2/client.sp b/addons/sourcemod/scripting/sf2/client.sp index 63b08d67..23d2072e 100644 --- a/addons/sourcemod/scripting/sf2/client.sp +++ b/addons/sourcemod/scripting/sf2/client.sp @@ -8,6 +8,8 @@ #define SF2_OVERLAY_DEFAULT "overlays/slender/newcamerahud_3" #define SF2_OVERLAY_DEFAULT_NO_FILMGRAIN "overlays/slender/nofilmgrain" +static int g_ClientModifiedMaxHealth[MAXTF2PLAYERS]; + //Client Special Round Timer static Handle g_ClientSpecialRoundTimer[MAXTF2PLAYERS]; @@ -40,11 +42,486 @@ int g_ClientFrame[MAXTF2PLAYERS]; #include "client/music.sp" #include "client/proxy.sp" +void SetupClients() +{ + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); +} + void Client_SetupAPI() { Sprint_SetupAPI(); } +static void OnPutInServer(SF2_BasePlayer client) +{ + SDKHook(client.index, SDKHook_OnTakeDamage, OnTakeDamage); + SDKHook(client.index, SDKHook_OnTakeDamageAlivePost, OnTakeDamageAlivePost); +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + CreateTimer(0.1, Timer_CheckHealth, client.UserID, TIMER_FLAG_NO_MAPCHANGE); +} + +static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) +{ + SF2_BasePlayer victimPlayer = SF2_BasePlayer(victim); + if (!g_Enabled) + { + if (NPCGetFromEntIndex(attacker) != -1 && GetEntProp(attacker, Prop_Data, "m_iTeamNum") == victimPlayer.Team) + { + damage = 0.0; + return Plugin_Changed; + } + return Plugin_Continue; + } + + Action action = Plugin_Continue; + + float damage2 = damage; + Call_StartForward(g_OnClientTakeDamageFwd); + Call_PushCell(victimPlayer.index); + Call_PushCellRef(attacker); + Call_PushCellRef(inflictor); + Call_PushFloatRef(damage2); + Call_Finish(action); + + if (action == Plugin_Changed) + { + damage = damage2; + return Plugin_Changed; + } + + Call_StartForward(g_OnPlayerTakeDamagePFwd); + Call_PushCell(victimPlayer); + Call_PushCellRef(attacker); + Call_PushCellRef(inflictor); + Call_PushFloatRef(damage2); + Call_PushCellRef(damagetype); + Call_Finish(action); + + if (action == Plugin_Changed) + { + damage = damage2; + return Plugin_Changed; + } + + TFClassType class = victimPlayer.Class; + int classToInt = view_as(class); + + if (IsRoundInWarmup() && IsValidClient(attacker)) + { + float modelScale = GetEntPropFloat(attacker, Prop_Send, "m_flModelScale"); + float headScale = GetEntPropFloat(attacker, Prop_Send, "m_flHeadScale"); + float torsoScale = GetEntPropFloat(attacker, Prop_Send, "m_flTorsoScale"); + float handScale = GetEntPropFloat(attacker, Prop_Send, "m_flHandScale"); + if (modelScale < 1.0 || modelScale > 1.0 || headScale < 1.0 || headScale > 1.0 || torsoScale < 1.0 || torsoScale > 1.0 || handScale < 1.0 || handScale > 1.0) + { + damage = 0.0; //So how does it feel? + return Plugin_Changed; + } + } + + if (IsClientInKart(victim) && (attacker == -1 || inflictor == -1)) + { + damage = 0.0; + return Plugin_Changed; + } + + char inflictorClass[32]; + if (inflictor >= 0) + { + GetEdictClassname(inflictor, inflictorClass, sizeof(inflictorClass)); + } + + if (IsValidClient(attacker) && IsValidClient(victim) && g_PlayerProxy[attacker] && GetClientTeam(victim) == TFTeam_Red && TF2_IsPlayerInCondition(victim, TFCond_Gas)) + { + TF2_IgnitePlayer(victim, victim); + TF2_RemoveCondition(victim, TFCond_Gas); + } + + if (TF2_IsPlayerInCondition(victim, TFCond_Gas) && SF2_ChaserEntity(attacker).IsValid()) + { + TF2_IgnitePlayer(victim, victim); + TF2_RemoveCondition(victim, TFCond_Gas); + } + + if (IsValidClient(attacker) && IsValidClient(victim) && (IsClientInPvP(victim) || IsClientInPvE(victim)) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) + { + damage = 0.0; + return Plugin_Changed; + } + + if (IsValidClient(attacker) && victimPlayer.IsValid && victimPlayer.Team == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && (victimPlayer.IsTrapped || victimPlayer.IsLatched)) + { + if (!g_PlayerEliminated[attacker] && !victimPlayer.IsEliminated && (damagetype & 0x80) != 0) + { + victimPlayer.IsTrapped = false; + if (victimPlayer.IsLatched) + { + victimPlayer.ChangeCondition(TFCond_Dazed, true); + for (int i = 0; i < MAX_BOSSES; i++) + { + SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); + if (!npc.IsValid()) + { + continue; + } + + if (victimPlayer.Latcher != npc.Index) + { + continue; + } + + SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); + if (!chaser.IsValid()) + { + continue; + } + + chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString("break tongue"); + } + } + victimPlayer.IsLatched = false; + victimPlayer.LatchCount = 0; + TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); + TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); + } + } + + if (IsValidClient(attacker) && !g_PlayerEliminated[attacker] && !DidClientEscape(attacker) && class == TFClass_Soldier && !(GetEntityFlags(attacker) & FL_ONGROUND)) + { + int weaponEnt = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); + if (IsValidEntity(weaponEnt)) + { + int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); + float zVelocity[3]; + GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); + if (itemDefInt == 416 && zVelocity[2] < 0.0 && weaponEnt == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) //A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. + { + damagetype |= DMG_ACID; + } + } + } + + if (IsValidEntity(inflictor) && !SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) + { + int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); + if (npcIndex != -1) + { + bool attackEliminated = (NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS) != 0; + if (!attackEliminated && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Blue) + { + damage = 0.0; + return Plugin_Changed; + } + } + } + + bool canDamage = false; + if (attacker != victim && IsValidClient(attacker)) + { + if (IsClientInPvP(victim) && IsClientInPvP(attacker)) + { + canDamage = true; + } + if (IsClientLeavingPvP(victim) && !IsClientInPvP(attacker)) + { + canDamage = true; + } + if (!IsRoundEnding()) + { + if (canDamage) + { + if (attacker == inflictor) + { + if (IsValidEdict(weapon)) + { + char weaponClass[64]; + GetEdictClassname(weapon, weaponClass, sizeof(weaponClass)); + + // Backstab check! + if ((strcmp(weaponClass, "tf_weapon_knife") == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy") == 0)) && + (damagecustom != TF_CUSTOM_TAUNT_FENCING)) + { + float myPos[3], hisPos[3], myDirection[3]; + GetClientAbsOrigin(victim, myPos); + GetClientAbsOrigin(attacker, hisPos); + GetClientEyeAngles(victim, myDirection); + GetAngleVectors(myDirection, myDirection, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(myDirection, myDirection); + ScaleVector(myDirection, 32.0); + AddVectors(myDirection, myPos, myDirection); + + float p[3], s[3]; + MakeVectorFromPoints(myPos, hisPos, p); + MakeVectorFromPoints(myPos, myDirection, s); + if (GetVectorDotProduct(p, s) <= 0.0)//We can backstab him m8 + { + if (GetClientTeam(victim) == GetClientTeam(attacker) && class == TFClass_Sniper) + { + //look if the player has a razorback + int wearableEnt = INVALID_ENT_REFERENCE; + while ((wearableEnt = FindEntityByClassname(wearableEnt, "tf_wearable")) != -1) + { + if (GetEntPropEnt(wearableEnt, Prop_Send, "m_hOwnerEntity") == victim && GetEntProp(wearableEnt, Prop_Send, "m_iItemDefinitionIndex") == 57) + { + RemoveEntity(wearableEnt); + damage = 0.0; + EmitSoundToClient(victim, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); + + SetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); + SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); + SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); + int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); + if (vm > MaxClients) + { + int meleeIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); + int anim = 41; + switch (meleeIndex) + { + case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: + { + anim = 15; + } + case 638: + { + anim = 31; + } + } + SetEntProp(vm, Prop_Send, "m_nSequence", anim); + } + return Plugin_Changed; + } + } + } + if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. + { + damage = 120.0; + if (damagetype & DMG_ACID) + { + damage = 120.0; + } + } + + if (g_WeaponCriticalsConVar != null && g_WeaponCriticalsConVar.BoolValue) + { + damagetype |= DMG_ACID; + } + + if (!IsClientCritUbercharged(victim)) + { + if (GetClientTeam(victim) == GetClientTeam(attacker)) + { + int pistol = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); + if (pistol > MaxClients && GetEntProp(pistol, Prop_Send, "m_iItemDefinitionIndex") == 525) //Give one crit fort the backstab + { + int crits = GetEntProp(attacker, Prop_Send, "m_iRevengeCrits"); + crits++; + SetEntProp(attacker, Prop_Send, "m_iRevengeCrits", crits); + } + } + if (GetEntProp(victim, Prop_Send, "m_iHealth") <= 120) + { + g_PlayerBackStabbed[victim] = true; + } + else + { + g_PlayerBackStabbed[victim] = false; + } + } + return Plugin_Changed; + } + } + } + } + } + else if (g_PlayerProxy[victim] || g_PlayerProxy[attacker]) + { + if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) + { + damage = 0.0; + return Plugin_Changed; + } + + if (attacker == victim)//Don't allow proxy to self regenerate control. + { + return Plugin_Continue; + } + + if (g_PlayerProxy[attacker]) + { + int maxHealth = SDKCall(g_SDKGetMaxHealth, victim); + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[attacker]); + if (master != -1) + { + int difficulty = GetLocalGlobalDifficulty(master); + if (damagecustom == TF_CUSTOM_TAUNT_GRAND_SLAM || + damagecustom == TF_CUSTOM_TAUNT_FENCING || + damagecustom == TF_CUSTOM_TAUNT_ARROW_STAB || + damagecustom == TF_CUSTOM_TAUNT_GRENADE || + damagecustom == TF_CUSTOM_TAUNT_BARBARIAN_SWING || + damagecustom == TF_CUSTOM_TAUNT_ENGINEER_ARM || + damagecustom == TF_CUSTOM_TAUNT_ARMAGEDDON) + { + if (damage >= float(maxHealth)) + { + damage = float(maxHealth) * 0.5; + } + else + { + damage = 0.0; + } + } + else if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. + { + damage = float(maxHealth) * g_SlenderProxyDamageVsBackstab[master][difficulty]; + if (damagetype & DMG_ACID) + { + damage /= 2.0; + } + } + + g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitEnemy[master][difficulty]; + if (g_PlayerProxyControl[attacker] > 100) + { + g_PlayerProxyControl[attacker] = 100; + } + + float originalPercentage = g_SlenderProxyDamageVsEnemy[master][difficulty]; + float additionPercentage = 0.15; + if (!IsClassConfigsValid()) + { + if (class == TFClass_Medic) + { + damage *= (originalPercentage + additionPercentage); + } + else + { + damage *= originalPercentage; + } + } + else + { + damage *= originalPercentage + g_ClassProxyDamageVulnerability[classToInt]; + } + } + return Plugin_Changed; + } + else if (g_PlayerProxy[victim]) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + int master = NPCGetFromUniqueID(g_PlayerProxyMaster[victim]); + if (master != -1) + { + NPCGetProfile(master, profile, sizeof(profile)); + int difficulty = GetLocalGlobalDifficulty(master); + g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitByEnemy[master][difficulty]; + if (g_PlayerProxyControl[attacker] > 100) + { + g_PlayerProxyControl[attacker] = 100; + } + + damage *= g_SlenderProxyDamageVsSelf[master][difficulty]; + } + if (TF2_IsPlayerInCondition(victim, view_as(87))) + { + damage = 0.0; + } + if (damage * (damagetype & DMG_CRIT ? 3.0 : 1.0) >= float(GetClientHealth(victim)) && !TF2_IsPlayerInCondition(victim, view_as(87)))//The proxy is about to die + { + char buffer[PLATFORM_MAX_PATH]; + int classIndex = view_as(TF2_GetPlayerClass(victim)); + ArrayList deathAnims = GetBossProfileProxyDeathAnimations(profile); + if (deathAnims != null) + { + deathAnims.GetString(classIndex, buffer, sizeof(buffer)); + if (buffer[0] == '\0') + { + deathAnims.GetString(0, buffer, sizeof(buffer)); + } + if (buffer[0] != '\0') + { + g_ClientMaxFrameDeathAnim[victim]=GetBossProfileProxyDeathAnimFrames(profile, classIndex); + if (g_ClientMaxFrameDeathAnim[victim] == 0) + { + g_ClientMaxFrameDeathAnim[victim] = GetBossProfileProxyDeathAnimFrames(profile, 0); + } + if (g_ClientMaxFrameDeathAnim[victim] > 0) + { + // Cancel out any other taunts. + if (TF2_IsPlayerInCondition(victim, TFCond_Taunting)) + { + TF2_RemoveCondition(victim, TFCond_Taunting); + } + //The model has a death anim play it. + SDK_PlaySpecificSequence(victim,buffer); + g_ClientFrame[victim] = 0; + RequestFrame(ProxyDeathAnimation,victim); + TF2_AddCondition(victim, view_as(87), 5.0); + //Prevent death, and show the damage to the attacker. + TF2_AddCondition(victim, view_as(70), 0.5); + return Plugin_Changed; + } + } + } + + //the player has no death anim leave him die. + } + return Plugin_Changed; + } + } + else + { + damage = 0.0; + return Plugin_Changed; + } + } + else + { + if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) + { + damage = 0.0; + return Plugin_Changed; + } + } + + if (IsClientInGhostMode(victim)) + { + damage = 0.0; + return Plugin_Changed; + } + } + + return Plugin_Continue; +} + +static void OnTakeDamageAlivePost(int victim, int attacker, int inflictor, float damage, int damageType, int weaponEntIndex, const float vecDamageForce[3], const float vecDamagePosition[3]) +{ + Call_StartForward(g_OnPlayerTakeDamagePostPFwd); + Call_PushCell(SF2_BasePlayer(victim)); + Call_PushCell(attacker); + Call_PushCell(inflictor); + Call_PushFloat(damage); + Call_PushCell(damageType); + Call_Finish(); +} + +static Action Timer_CheckHealth(Handle timer, any id) +{ + int client = GetClientOfUserId(id); + if (!IsValidClient(client)) + { + return Plugin_Stop; + } + + g_ClientModifiedMaxHealth[client] = SF2_BasePlayer(client).Health; + + return Plugin_Stop; +} + // ========================================================== // GENERAL CLIENT HOOK FUNCTIONS // ========================================================== @@ -102,6 +579,11 @@ void ClientSetScareBoostEndTime(int client, float time) g_PlayerScareBoostEndTime[client] = time; } +int ClientGetModifiedMaxHealth(int client) +{ + return g_ClientModifiedMaxHealth[client]; +} + Action Hook_HealthKitOnTouch(int healthKit, int client) { if (IsValidClient(client)) @@ -191,6 +673,8 @@ Action Hook_ClientSetTransmit(int client, int other) public Action TF2_CalcIsAttackCritical(int client, int weapon, char[] weaponName, bool &result) { + BossPreview_OnClientAttack(client); + if (!g_Enabled || g_RestartSessionEnabled || (g_PlayerEliminated[client] && !g_PlayerProxy[client])) { return Plugin_Continue; diff --git a/addons/sourcemod/scripting/sf2/client/proxy.sp b/addons/sourcemod/scripting/sf2/client/proxy.sp index 495ed120..da894e96 100644 --- a/addons/sourcemod/scripting/sf2/client/proxy.sp +++ b/addons/sourcemod/scripting/sf2/client/proxy.sp @@ -1118,7 +1118,7 @@ void SF2_RefreshRestrictions() { for(int client = 1; client <= MaxClients; client++) { - if (IsValidClient(client) && (!g_PlayerEliminated[client] || (g_PlayerEliminated[client] && !IsClientInPvP(client) && !IsClientInPvE(client)))) + if (IsValidClient(client) && (!g_PlayerEliminated[client] || (g_PlayerEliminated[client] && !IsClientInPvP(client) && !IsClientInPvE(client) && !IsClientInWeaponsTrigger(client)))) { ClientSwitchToWeaponSlot(client, TFWeaponSlot_Melee); g_PlayerPostWeaponsTimer[client] = CreateTimer(1.0, Timer_ClientPostWeapons, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); @@ -1199,7 +1199,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) } // pvp - if (IsClientInPvP(client)) + if (IsClientInPvP(client) || IsClientInWeaponsTrigger(client)) { removeWeapons = false; restrictWeapons = false; diff --git a/addons/sourcemod/scripting/sf2/client/think.sp b/addons/sourcemod/scripting/sf2/client/think.sp index bfe9ee0e..93b6647d 100644 --- a/addons/sourcemod/scripting/sf2/client/think.sp +++ b/addons/sourcemod/scripting/sf2/client/think.sp @@ -228,440 +228,6 @@ void Hook_ClientPreThink(int client) ClientProcessVisibility(client); } -Action Hook_ClientOnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) -{ - SF2_BasePlayer victimPlayer = SF2_BasePlayer(victim); - if (!g_Enabled) - { - if (NPCGetFromEntIndex(attacker) != -1 && GetEntProp(attacker, Prop_Data, "m_iTeamNum") == victimPlayer.Team) - { - damage = 0.0; - return Plugin_Changed; - } - return Plugin_Continue; - } - - Action action = Plugin_Continue; - - float damage2 = damage; - Call_StartForward(g_OnClientTakeDamageFwd); - Call_PushCell(victimPlayer.index); - Call_PushCellRef(attacker); - Call_PushCellRef(inflictor); - Call_PushFloatRef(damage2); - Call_Finish(action); - - if (action == Plugin_Changed) - { - damage = damage2; - return Plugin_Changed; - } - - Call_StartForward(g_OnPlayerTakeDamagePFwd); - Call_PushCell(victimPlayer); - Call_PushCellRef(attacker); - Call_PushCellRef(inflictor); - Call_PushFloatRef(damage2); - Call_PushCellRef(damagetype); - Call_Finish(action); - - if (action == Plugin_Changed) - { - damage = damage2; - return Plugin_Changed; - } - - TFClassType class = victimPlayer.Class; - int classToInt = view_as(class); - - if (IsRoundInWarmup() && IsValidClient(attacker)) - { - float modelScale = GetEntPropFloat(attacker, Prop_Send, "m_flModelScale"); - float headScale = GetEntPropFloat(attacker, Prop_Send, "m_flHeadScale"); - float torsoScale = GetEntPropFloat(attacker, Prop_Send, "m_flTorsoScale"); - float handScale = GetEntPropFloat(attacker, Prop_Send, "m_flHandScale"); - if (modelScale < 1.0 || modelScale > 1.0 || headScale < 1.0 || headScale > 1.0 || torsoScale < 1.0 || torsoScale > 1.0 || handScale < 1.0 || handScale > 1.0) - { - damage = 0.0; //So how does it feel? - return Plugin_Changed; - } - } - - if (IsClientInKart(victim) && (attacker == -1 || inflictor == -1)) - { - damage = 0.0; - return Plugin_Changed; - } - - char inflictorClass[32]; - if (inflictor >= 0) - { - GetEdictClassname(inflictor, inflictorClass, sizeof(inflictorClass)); - } - - if (IsValidClient(attacker) && IsValidClient(victim) && g_PlayerProxy[attacker] && GetClientTeam(victim) == TFTeam_Red && TF2_IsPlayerInCondition(victim, TFCond_Gas)) - { - TF2_IgnitePlayer(victim, victim); - TF2_RemoveCondition(victim, TFCond_Gas); - } - - if (TF2_IsPlayerInCondition(victim, TFCond_Gas) && SF2_ChaserEntity(attacker).IsValid()) - { - TF2_IgnitePlayer(victim, victim); - TF2_RemoveCondition(victim, TFCond_Gas); - } - - if (IsValidClient(attacker) && IsValidClient(victim) && (IsClientInPvP(victim) || IsClientInPvE(victim)) && GetClientTeam(victim) == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && victim != attacker) - { - damage = 0.0; - return Plugin_Changed; - } - - if (IsValidClient(attacker) && victimPlayer.IsValid && victimPlayer.Team == TFTeam_Red && GetClientTeam(attacker) == TFTeam_Red && (victimPlayer.IsTrapped || victimPlayer.IsLatched)) - { - if (!g_PlayerEliminated[attacker] && !victimPlayer.IsEliminated && (damagetype & 0x80) != 0) - { - victimPlayer.IsTrapped = false; - if (victimPlayer.IsLatched) - { - victimPlayer.ChangeCondition(TFCond_Dazed, true); - for (int i = 0; i < MAX_BOSSES; i++) - { - SF2NPC_BaseNPC npc = SF2NPC_BaseNPC(i); - if (!npc.IsValid()) - { - continue; - } - - if (victimPlayer.Latcher != npc.Index) - { - continue; - } - - SF2_ChaserEntity chaser = SF2_ChaserEntity(npc.EntIndex); - if (!chaser.IsValid()) - { - continue; - } - - chaser.MyNextBotPointer().GetIntentionInterface().OnCommandString("break tongue"); - } - } - victimPlayer.IsLatched = false; - victimPlayer.LatchCount = 0; - TF2_AddCondition(attacker, TFCond_SpeedBuffAlly, 4.0); - TF2_AddCondition(victim, TFCond_SpeedBuffAlly, 4.0); - } - } - - if (IsValidClient(attacker) && !g_PlayerEliminated[attacker] && !DidClientEscape(attacker) && class == TFClass_Soldier && !(GetEntityFlags(attacker) & FL_ONGROUND)) - { - int weaponEnt = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Melee); - if (IsValidEntity(weaponEnt)) - { - int itemDefInt = GetEntProp(weaponEnt, Prop_Send, "m_iItemDefinitionIndex"); - float zVelocity[3]; - GetEntPropVector(attacker, Prop_Data, "m_vecVelocity", zVelocity); - if (itemDefInt == 416 && zVelocity[2] < 0.0 && weaponEnt == GetEntPropEnt(attacker, Prop_Send, "m_hActiveWeapon")) //A soldier has the market gardener and is currently falling down, like Minecraft with it's critical hits. - { - damagetype |= DMG_ACID; - } - } - } - - if (IsValidEntity(inflictor) && !SF2_ChaserEntity(inflictor).IsValid() && !SF2_StatueEntity(inflictor).IsValid() && !IsValidClient(inflictor)) - { - int npcIndex = NPCGetFromEntIndex(GetEntPropEnt(inflictor, Prop_Send, "m_hOwnerEntity")); - if (npcIndex != -1) - { - bool attackEliminated = (NPCGetFlags(npcIndex) & SFF_ATTACKWAITERS) != 0; - if (!attackEliminated && IsValidClient(victim) && GetClientTeam(victim) == TFTeam_Blue) - { - damage = 0.0; - return Plugin_Changed; - } - } - } - - bool canDamage = false; - if (attacker != victim && IsValidClient(attacker)) - { - if (IsClientInPvP(victim) && IsClientInPvP(attacker)) - { - canDamage = true; - } - if (IsClientLeavingPvP(victim) && !IsClientInPvP(attacker)) - { - canDamage = true; - } - if (!IsRoundEnding()) - { - if (canDamage) - { - if (attacker == inflictor) - { - if (IsValidEdict(weapon)) - { - char weaponClass[64]; - GetEdictClassname(weapon, weaponClass, sizeof(weaponClass)); - - // Backstab check! - if ((strcmp(weaponClass, "tf_weapon_knife") == 0 || (TF2_GetPlayerClass(attacker) == TFClass_Spy && strcmp(weaponClass, "saxxy") == 0)) && - (damagecustom != TF_CUSTOM_TAUNT_FENCING)) - { - float myPos[3], hisPos[3], myDirection[3]; - GetClientAbsOrigin(victim, myPos); - GetClientAbsOrigin(attacker, hisPos); - GetClientEyeAngles(victim, myDirection); - GetAngleVectors(myDirection, myDirection, NULL_VECTOR, NULL_VECTOR); - NormalizeVector(myDirection, myDirection); - ScaleVector(myDirection, 32.0); - AddVectors(myDirection, myPos, myDirection); - - float p[3], s[3]; - MakeVectorFromPoints(myPos, hisPos, p); - MakeVectorFromPoints(myPos, myDirection, s); - if (GetVectorDotProduct(p, s) <= 0.0)//We can backstab him m8 - { - if (GetClientTeam(victim) == GetClientTeam(attacker) && class == TFClass_Sniper) - { - //look if the player has a razorback - int wearableEnt = INVALID_ENT_REFERENCE; - while ((wearableEnt = FindEntityByClassname(wearableEnt, "tf_wearable")) != -1) - { - if (GetEntPropEnt(wearableEnt, Prop_Send, "m_hOwnerEntity") == victim && GetEntProp(wearableEnt, Prop_Send, "m_iItemDefinitionIndex") == 57) - { - RemoveEntity(wearableEnt); - damage = 0.0; - EmitSoundToClient(victim, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - EmitSoundToClient(attacker, "player/spy_shield_break.wav", _, _, SNDLEVEL_TRAFFIC, SND_NOFLAGS, 0.7, 100); - - SetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flNextAttack", GetGameTime() + 2.0); - SetEntPropFloat(attacker, Prop_Send, "m_flStealthNextChangeTime", GetGameTime() + 2.0); - int vm = GetEntPropEnt(attacker, Prop_Send, "m_hViewModel"); - if (vm > MaxClients) - { - int meleeIndex = GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"); - int anim = 41; - switch (meleeIndex) - { - case 4, 194, 225, 356, 461, 574, 649, 665, 794, 803, 883, 892, 901, 910, 959, 968: - { - anim = 15; - } - case 638: - { - anim = 31; - } - } - SetEntProp(vm, Prop_Send, "m_nSequence", anim); - } - return Plugin_Changed; - } - } - } - if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. - { - damage = 120.0; - if (damagetype & DMG_ACID) - { - damage = 120.0; - } - } - - if (g_WeaponCriticalsConVar != null && g_WeaponCriticalsConVar.BoolValue) - { - damagetype |= DMG_ACID; - } - - if (!IsClientCritUbercharged(victim)) - { - if (GetClientTeam(victim) == GetClientTeam(attacker)) - { - int pistol = GetPlayerWeaponSlot(attacker, TFWeaponSlot_Primary); - if (pistol > MaxClients && GetEntProp(pistol, Prop_Send, "m_iItemDefinitionIndex") == 525) //Give one crit fort the backstab - { - int crits = GetEntProp(attacker, Prop_Send, "m_iRevengeCrits"); - crits++; - SetEntProp(attacker, Prop_Send, "m_iRevengeCrits", crits); - } - } - if (GetEntProp(victim, Prop_Send, "m_iHealth") <= 120) - { - g_PlayerBackStabbed[victim] = true; - } - else - { - g_PlayerBackStabbed[victim] = false; - } - } - return Plugin_Changed; - } - } - } - } - } - else if (g_PlayerProxy[victim] || g_PlayerProxy[attacker]) - { - if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) - { - damage = 0.0; - return Plugin_Changed; - } - - if (attacker == victim)//Don't allow proxy to self regenerate control. - { - return Plugin_Continue; - } - - if (g_PlayerProxy[attacker]) - { - int maxHealth = SDKCall(g_SDKGetMaxHealth, victim); - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[attacker]); - if (master != -1) - { - int difficulty = GetLocalGlobalDifficulty(master); - if (damagecustom == TF_CUSTOM_TAUNT_GRAND_SLAM || - damagecustom == TF_CUSTOM_TAUNT_FENCING || - damagecustom == TF_CUSTOM_TAUNT_ARROW_STAB || - damagecustom == TF_CUSTOM_TAUNT_GRENADE || - damagecustom == TF_CUSTOM_TAUNT_BARBARIAN_SWING || - damagecustom == TF_CUSTOM_TAUNT_ENGINEER_ARM || - damagecustom == TF_CUSTOM_TAUNT_ARMAGEDDON) - { - if (damage >= float(maxHealth)) - { - damage = float(maxHealth) * 0.5; - } - else - { - damage = 0.0; - } - } - else if (damagecustom == TF_CUSTOM_BACKSTAB) // Modify backstab damage. - { - damage = float(maxHealth) * g_SlenderProxyDamageVsBackstab[master][difficulty]; - if (damagetype & DMG_ACID) - { - damage /= 2.0; - } - } - - g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitEnemy[master][difficulty]; - if (g_PlayerProxyControl[attacker] > 100) - { - g_PlayerProxyControl[attacker] = 100; - } - - float originalPercentage = g_SlenderProxyDamageVsEnemy[master][difficulty]; - float additionPercentage = 0.15; - if (!IsClassConfigsValid()) - { - if (class == TFClass_Medic) - { - damage *= (originalPercentage + additionPercentage); - } - else - { - damage *= originalPercentage; - } - } - else - { - damage *= originalPercentage + g_ClassProxyDamageVulnerability[classToInt]; - } - } - return Plugin_Changed; - } - else if (g_PlayerProxy[victim]) - { - char profile[SF2_MAX_PROFILE_NAME_LENGTH]; - int master = NPCGetFromUniqueID(g_PlayerProxyMaster[victim]); - if (master != -1) - { - NPCGetProfile(master, profile, sizeof(profile)); - int difficulty = GetLocalGlobalDifficulty(master); - g_PlayerProxyControl[attacker] += g_SlenderProxyControlGainHitByEnemy[master][difficulty]; - if (g_PlayerProxyControl[attacker] > 100) - { - g_PlayerProxyControl[attacker] = 100; - } - - damage *= g_SlenderProxyDamageVsSelf[master][difficulty]; - } - if (TF2_IsPlayerInCondition(victim, view_as(87))) - { - damage = 0.0; - } - if (damage * (damagetype & DMG_CRIT ? 3.0 : 1.0) >= float(GetClientHealth(victim)) && !TF2_IsPlayerInCondition(victim, view_as(87)))//The proxy is about to die - { - char buffer[PLATFORM_MAX_PATH]; - int classIndex = view_as(TF2_GetPlayerClass(victim)); - ArrayList deathAnims = GetBossProfileProxyDeathAnimations(profile); - if (deathAnims != null) - { - deathAnims.GetString(classIndex, buffer, sizeof(buffer)); - if (buffer[0] == '\0') - { - deathAnims.GetString(0, buffer, sizeof(buffer)); - } - if (buffer[0] != '\0') - { - g_ClientMaxFrameDeathAnim[victim]=GetBossProfileProxyDeathAnimFrames(profile, classIndex); - if (g_ClientMaxFrameDeathAnim[victim] == 0) - { - g_ClientMaxFrameDeathAnim[victim] = GetBossProfileProxyDeathAnimFrames(profile, 0); - } - if (g_ClientMaxFrameDeathAnim[victim] > 0) - { - // Cancel out any other taunts. - if (TF2_IsPlayerInCondition(victim, TFCond_Taunting)) - { - TF2_RemoveCondition(victim, TFCond_Taunting); - } - //The model has a death anim play it. - SDK_PlaySpecificSequence(victim,buffer); - g_ClientFrame[victim] = 0; - RequestFrame(ProxyDeathAnimation,victim); - TF2_AddCondition(victim, view_as(87), 5.0); - //Prevent death, and show the damage to the attacker. - TF2_AddCondition(victim, view_as(70), 0.5); - return Plugin_Changed; - } - } - } - - //the player has no death anim leave him die. - } - return Plugin_Changed; - } - } - else - { - damage = 0.0; - return Plugin_Changed; - } - } - else - { - if (g_PlayerEliminated[attacker] == g_PlayerEliminated[victim]) - { - damage = 0.0; - return Plugin_Changed; - } - } - - if (IsClientInGhostMode(victim)) - { - damage = 0.0; - return Plugin_Changed; - } - } - - return Plugin_Continue; -} - void ClientOnButtonPress(SF2_BasePlayer client, int button) { switch (button) diff --git a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp index a9f6f33d..932580ad 100644 --- a/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp +++ b/addons/sourcemod/scripting/sf2/entities/projectiles/sf2_baseball_projectile.sp @@ -81,7 +81,7 @@ methodmap SF2_ProjectileBaseball < SF2_ProjectileGrenade ball.SetProp(Prop_Send, "m_usSolidFlags", 12); ball.KeyValue("solid", "2"); ball.KeyValue("spawnflags", "4"); - SetEntityCollisionGroup(ball.index, COLLISION_GROUP_DEBRIS_TRIGGER); + SetEntityCollisionGroup(ball.index, 13); ball.SetProp(Prop_Send, "m_usSolidFlags", 0); ball.Spawn(); diff --git a/addons/sourcemod/scripting/sf2/extras/bosspreview.sp b/addons/sourcemod/scripting/sf2/extras/bosspreview.sp new file mode 100644 index 00000000..fdefe4eb --- /dev/null +++ b/addons/sourcemod/scripting/sf2/extras/bosspreview.sp @@ -0,0 +1,504 @@ +enum struct BossData +{ + char Name[64]; + char Profile[64]; + char Pack[64]; + char Filepath[PLATFORM_MAX_PATH]; +} + +enum struct PackData +{ + char Name[64]; + char Path[64]; + bool AutoLoad; +} + +enum struct EquipData +{ + char Profile[64]; + int Flags; +} + +static ArrayList BossPacks; +static ArrayList BossList; + +static int MainMenuPage[MAXPLAYERS+1]; +static int PackMenuPage[MAXPLAYERS+1]; +static char ViewingPack[MAXPLAYERS+1][64]; +static char ViewingBoss[MAXPLAYERS+1][64]; + +static int PetRef[MAXPLAYERS+1] = {INVALID_ENT_REFERENCE, ...}; + +void BossPreview_ReloadPack() +{ + if(BossPacks) + delete BossPacks; + + if(BossList) + delete BossList; + + BossPacks = new ArrayList(sizeof(PackData)); + BossList = new ArrayList(sizeof(BossData)); + + BossData data; + char pack[64], buffer[PLATFORM_MAX_PATH]; + + BuildPath(Path_SM, buffer, sizeof(buffer), "data/sf2/profiles"); + strcopy(data.Pack, sizeof(data.Pack), "Core Pack"); + AddSlenderPack(data, "", buffer, true); + + KeyValues kv = new KeyValues("root"); + + BuildPath(Path_SM, buffer, sizeof(buffer), "data/sf2/profiles_packs.cfg"); + if(kv.ImportFromFile(buffer)) + { + kv.JumpToKey("packs"); + kv.GotoFirstSubKey(); + + do + { + kv.GetString("file", pack, sizeof(pack)); + BuildPath(Path_SM, buffer, sizeof(buffer), "data/sf2/profiles/packs/%s", pack); + kv.GetString("name", data.Pack, sizeof(data.Pack)); + + AddSlenderPack(data, pack, buffer, kv.GetNum("autoload", false) != 0); + } + while(kv.GotoNextKey()); + } + + delete kv; +} + +Action BossPreview_MainMenu(int client, int args) +{ + if(client) + { + if(args) + { + char buffer[64]; + GetCmdArgString(buffer, sizeof(buffer)); + StripQuotes(buffer); + + BossData boss; + int i; + int length = BossList.Length; + for(; i(SF2_GetCurrentBossPack(buffer, length)); +} + +static void CreatePet(int client, const char[] profile) +{ + BossPreview_Remove(client); + + if (!SF2_IsValidClient(client) || !IsPlayerAlive(client) || !IsProfileValid(profile)) + return; + + if (SF2_IsClientInGhostMode(client) || SF2_IsClientProxy(client)) + return; + + ArrayList modelsArray = GetBossProfileModels(profile); + + char model[PLATFORM_MAX_PATH]; + modelsArray.GetString(1, model, sizeof(model)); + + CBaseAnimating entity = CBaseAnimating(CreateEntityByName("prop_dynamic")); + if (entity.IsValid()) + { + entity.SetModel(model); + entity.KeyValue("solid", "0"); + entity.SetProp(Prop_Send, "m_nBody", GetBossProfileBodyGroups(profile)); + entity.SetProp(Prop_Send, "m_nSkin", GetBossProfileSkin(profile)); + entity.SetPropFloat(Prop_Send, "m_flModelScale", GetBossProfileModelScale(profile)); + + entity.SetPropEnt(Prop_Data, "m_hEffectEntity", client); + SDKHook(entity.index, SDKHook_SetTransmit, PetTransmit); + + PetRef[client] = EntIndexToEntRef(entity.index); + strcopy(ViewingBoss[client], sizeof(ViewingBoss[]), profile); + SetDefaultAnimation(entity.index, profile); + + static float pos[3], ang[3]; + GetClientAbsOrigin(client, pos); + GetClientEyeAngles(client, ang); + ang[0] = 0.0; + entity.Teleport(pos, ang, NULL_VECTOR); + + entity.Spawn(); + + int move = entity.LookupPoseParameter("move_x"); + if (move > 0) + { + entity.SetPoseParameter(move, 1.0); + } + move = entity.LookupPoseParameter("move_scale"); + if (move > 0) + { + entity.SetPoseParameter(move, 1.0); + } + } +} + +void BossPreview_Remove(int client) +{ + if (IsValidEntity(PetRef[client])) + { + RemoveEntity(PetRef[client]); + PetRef[client] = INVALID_ENT_REFERENCE; + } +} + +static Action PetTransmit(int entity, int client) +{ + SetEdictFlags(entity, GetEdictFlags(entity) &~ FL_EDICT_ALWAYS); + + int owner = GetEntPropEnt(entity, Prop_Data, "m_hEffectEntity"); + if(owner == -1) + { + RemoveEntity(entity); + return Plugin_Continue; + } + + if(owner == client) + { + if(GetClientMenu(client) == MenuSource_None) + RemoveEntity(entity); + + return Plugin_Continue; + } + + return Plugin_Stop; +} + +static void SetDefaultAnimation(int entity, const char[] profile) +{ + SF2BossProfileMasterAnimationsData masterData; + GetBossProfileAnimationsData(profile, masterData); + char animation[64]; + masterData.GetAnimation("idle", Difficulty_Normal, animation, sizeof(animation)); + + ArrayList animations, validAnimations; + validAnimations = new ArrayList(); + masterData.Animations.GetValue("idle", animations); + for (int i = 0; i < animations.Length; i++) + { + validAnimations.Push(i); + } + if (validAnimations.Length <= 0) + return; + + float playback; + int randomIndex = validAnimations.Get(GetRandomInt(0, validAnimations.Length - 1)); + masterData.GetAnimation("idle", Difficulty_Normal, animation, sizeof(animation), playback, _, _, _, _, randomIndex); + + CBaseAnimating animator = CBaseAnimating(entity); + + SetVariantString(animation); + animator.AcceptInput("SetDefaultAnimation"); + + SetVariantString(animation); + animator.AcceptInput("SetAnimation"); + + animator.SetPropFloat(Prop_Data, "m_flCycle", 0.0); + animator.SetPropFloat(Prop_Send, "m_flPlaybackRate", playback); + + delete validAnimations; +} + +void BossPreview_OnClientAttack(int client) +{ + if(!IsValidEntity(PetRef[client])) + return; + + int entity = EntRefToEntIndex(PetRef[client]); + + SF2BossProfileMasterAnimationsData masterData; + GetBossProfileAnimationsData(ViewingBoss[client], masterData); + char animation[64]; + masterData.GetAnimation("attack", Difficulty_Normal, animation, sizeof(animation)); + + ArrayList animations, validAnimations; + validAnimations = new ArrayList(); + masterData.Animations.GetValue("attack", animations); + for (int i = 0; i < animations.Length; i++) + { + validAnimations.Push(i); + } + if (validAnimations.Length <= 0) + return; + + float playback; + int randomIndex = validAnimations.Get(GetRandomInt(0, validAnimations.Length - 1)); + masterData.GetAnimation("attack", Difficulty_Normal, animation, sizeof(animation), playback, _, _, _, _, randomIndex); + + CBaseAnimating animator = CBaseAnimating(entity); + + SetVariantString(animation); + animator.AcceptInput("SetAnimation"); + + animator.SetPropFloat(Prop_Data, "m_flCycle", 0.0); + animator.SetPropFloat(Prop_Send, "m_flPlaybackRate", playback); + + delete validAnimations; +} diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 30834fb8..82d69296 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -235,6 +235,8 @@ public void OnPluginStart() g_EngineerBuildInBLUConVar = CreateConVar("sf2_engineer_build_in_blue", "0", "Allows BLU engineers to build outside of the PvP and PvE arena.", _, true, 0.0, true, 1.0); g_DisableTauntLoopsConVar = CreateConVar("sf2_disable_taunt_loop_sounds", "0", "Enables/Disables the ability for proxies and REDs to hear taunt sounds.", _, true, 0.0, true, 1.0); + g_BossPreviewWikiConVar = CreateConVar("sf2_bosspreview_wikiurl", "", "The url format to show the wiki (%s for the boss name)"); + g_MaxRoundsConVar = FindConVar("mp_maxrounds"); g_HudSync = CreateHudSynchronizer(); @@ -287,8 +289,8 @@ public void OnPluginStart() RegConsoleCmd("sm_sltuto", Command_Tutorial); RegConsoleCmd("sm_sf2tutorial", Command_Tutorial); RegConsoleCmd("sm_sf2tuto", Command_Tutorial); - RegConsoleCmd("sm_slpack", Command_Pack); - RegConsoleCmd("sm_sf2pack", Command_Pack); + RegConsoleCmd("sm_slpack", BossPreview_MainMenu); + RegConsoleCmd("sm_sf2pack", BossPreview_MainMenu); RegConsoleCmd("sm_slnextpack", Command_NextPack); RegConsoleCmd("sm_sf2nextpack", Command_NextPack); RegConsoleCmd("sm_slnext", Command_Next); @@ -297,8 +299,15 @@ public void OnPluginStart() RegConsoleCmd("sm_slhelp", Command_Help); RegConsoleCmd("sm_slsettings", Command_Settings); RegConsoleCmd("sm_slcredits", Command_Credits); - RegConsoleCmd("sm_slviewbosslist", Command_BossList); - RegConsoleCmd("sm_slbosslist", Command_BossList); + RegConsoleCmd("sm_slviewbosslist", BossPreview_MainMenu); + RegConsoleCmd("sm_slbosslist", BossPreview_MainMenu); + RegConsoleCmd("sm_slpacks", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_sf2packs", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_sf2viewbosslist", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_sf2bosslist", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_bosslist", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_listboss", BossPreview_MainMenu, "", FCVAR_HIDDEN); + RegConsoleCmd("sm_bosspack", BossPreview_MainMenu, "", FCVAR_HIDDEN); RegConsoleCmd("sm_slafk", Command_NoPoints); RegConsoleCmd("sm_flashlight", Command_ToggleFlashlight); RegConsoleCmd("sm_slhud", Command_MenuSwitchHud); @@ -351,6 +360,7 @@ public void OnPluginStart() RegAdminCmd("sm_sf2_wall_hax", Command_WallHax, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_keep_weapons", Command_KeepWeapons, ADMFLAG_SLAY); RegAdminCmd("sm_sf2_reveal_page_locations", Command_RevealPageLocations, ADMFLAG_CHEATS); + RegAdminCmd("sm_sf2_set_modboss", Command_BossOverride, ADMFLAG_SLAY); RegAdminCmd("+alltalk", Command_AllTalkOn, ADMFLAG_SLAY); RegAdminCmd("-alltalk", Command_AllTalkOff, ADMFLAG_SLAY); RegAdminCmd("+slalltalk", Command_AllTalkOn, ADMFLAG_SLAY, _, _, FCVAR_HIDDEN); @@ -417,6 +427,7 @@ public void OnPluginStart() g_OnPlayerJumpPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerSpawnPFwd = new PrivateForward(ET_Ignore, Param_Cell); g_OnPlayerTakeDamagePFwd = new PrivateForward(ET_Hook, Param_Cell, Param_CellByRef, Param_CellByRef, Param_FloatByRef, Param_CellByRef); + g_OnPlayerTakeDamagePostPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Cell); g_OnPlayerDeathPrePFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); g_OnPlayerDeathPFwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); g_OnPlayerPutInServerPFwd = new PrivateForward(ET_Ignore, Param_Cell); @@ -466,6 +477,8 @@ public void OnPluginStart() InitializeEffects(); + SetupClients(); + SetupAntiCamping(); SetupBlink(); SetupBreathing(); @@ -629,17 +642,6 @@ static Action Command_Credits(int client, int args) return Plugin_Handled; } -static Action Command_BossList(int client, int args) -{ - if (!g_Enabled) - { - return Plugin_Continue; - } - - DisplayBossList(client); - return Plugin_Handled; -} - static Action Command_NoPoints(int client, int args) { if (!g_Enabled) @@ -2027,7 +2029,7 @@ static Action Command_ForceDifficulty(int client, int args) } else if (newDifficulty > Difficulty_Easy && newDifficulty < Difficulty_Max) { - g_DifficultyConVar.SetInt(newDifficulty); + SetDifficulty(newDifficulty); } switch (newDifficulty) diff --git a/addons/sourcemod/scripting/sf2/extras/game_events.sp b/addons/sourcemod/scripting/sf2/extras/game_events.sp index e2e4d491..d5e54ce2 100644 --- a/addons/sourcemod/scripting/sf2/extras/game_events.sp +++ b/addons/sourcemod/scripting/sf2/extras/game_events.sp @@ -555,6 +555,8 @@ Action Event_PlayerSpawn(Handle event, const char[] name, bool dB) } } + BossPreview_Remove(client); + Call_StartForward(g_OnPlayerSpawnPFwd); Call_PushCell(SF2_BasePlayer(client)); Call_Finish(); @@ -920,6 +922,7 @@ Action Event_PlayerDeath(Event event, const char[] name, bool dB) ClientResetSlenderStats(client); ClientResetOverlay(client); ClientResetJumpScare(client); + BossPreview_Remove(client); for (int npcIndex = 0; npcIndex < MAX_BOSSES; npcIndex++) { diff --git a/addons/sourcemod/scripting/sf2/extras/natives.sp b/addons/sourcemod/scripting/sf2/extras/natives.sp index 890c5221..78c61389 100644 --- a/addons/sourcemod/scripting/sf2/extras/natives.sp +++ b/addons/sourcemod/scripting/sf2/extras/natives.sp @@ -60,6 +60,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max g_OnIsBossCustomAttackPossibleFwd = new GlobalForward("SF2_OnIsBossCustomAttackPossible", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell); g_OnBossGetCustomAttackActionFwd = new GlobalForward("SF2_OnBossGetCustomAttackAction", ET_Hook, Param_Cell, Param_String, Param_Array, Param_Cell, Param_CellByRef); g_OnProjectileTouchFwd = new GlobalForward("SF2_OnProjectileTouch", ET_Ignore, Param_Cell, Param_Cell); + g_OnPreSelectStartingBosses = new GlobalForward("SF2_OnSelectStartingBosses", ET_Ignore); + g_OnPostSelectStartingBosses = new GlobalForward("SF2_OnFinishStartingBosses", ET_Ignore, Param_String); CreateNative("SF2_GetConfig", Native_GetConfig); CreateNative("SF2_IsRunning", Native_IsRunning); diff --git a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp index d258e473..e0571c45 100644 --- a/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp +++ b/addons/sourcemod/scripting/sf2/gamemodes/renevant.sp @@ -155,28 +155,28 @@ static void Renevant_DoWaveAction(RenevantWave action) { case Difficulty_Normal: { - g_DifficultyConVar.IntValue = Difficulty_Hard; + SetDifficulty(Difficulty_Hard); FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Hard Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Hard: { - g_DifficultyConVar.IntValue = Difficulty_Insane; + SetDifficulty(Difficulty_Insane); FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Insane Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Insane: { - g_DifficultyConVar.IntValue = Difficulty_Nightmare; + SetDifficulty(Difficulty_Nightmare); FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t: %t", "SF2 Renevant Difficulty", "SF2 Nightmare Difficulty"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); } case Difficulty_Nightmare: { - g_DifficultyConVar.IntValue = Difficulty_Apollyon; + SetDifficulty(Difficulty_Apollyon); g_RenevantBossesChaseEndlessly = true; g_IsRoundInfiniteSprint = true; @@ -421,7 +421,7 @@ static void Renevant_DoWaveAction(RenevantWave action) } } case RenevantWave_WallHax: - { + { g_RenevantWallHax = true; FormatEx(broadcastBuffer, sizeof(broadcastBuffer), "\n%t.", "SF2 Renevant Wall Hax"); StrCat(broadcastMessage, sizeof(broadcastMessage), broadcastBuffer); @@ -467,7 +467,7 @@ void Renevant_SetWave(int wave, bool resetTimer = false) { case 1: //Wave 1 { - g_DifficultyConVar.IntValue = Difficulty_Normal; + SetDifficulty(Difficulty_Normal); Renevant_DoWaveAction(RenevantWave_Normal); diff --git a/addons/sourcemod/scripting/sf2/mapentities.sp b/addons/sourcemod/scripting/sf2/mapentities.sp index 0c58b292..dd6edeee 100644 --- a/addons/sourcemod/scripting/sf2/mapentities.sp +++ b/addons/sourcemod/scripting/sf2/mapentities.sp @@ -99,6 +99,7 @@ void SF2MapEntity_AddHook(SF2MapEntityHook hookType, Function hookFunc) #include "mapentities/sf2_info_player_escapespawn.sp" #include "mapentities/sf2_trigger_pvp.sp" #include "mapentities/sf2_trigger_pve.sp" +#include "mapentities/sf2_trigger_weapons.sp" #include "mapentities/sf2_info_player_pvpspawn.sp" #include "mapentities/sf2_info_player_proxyspawn.sp" #include "mapentities/sf2_info_boss_spawn.sp" @@ -151,6 +152,9 @@ void SetupCustomMapEntities() // sf2_trigger_pve SF2TriggerPvEEntity.Initialize(); + // sf2_trigger_weapons + SF2TriggerWeaponsEntity.Initialize(); + // sf2_info_player_pvpspawn SF2PlayerPvPSpawnEntity.Initialize(); diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp index 715596be..6501631b 100644 --- a/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_gamerules.sp @@ -820,7 +820,7 @@ static void InputSetDifficulty(int entity, int activator, int caller, int value) value = Difficulty_Max - 1; } - g_DifficultyConVar.SetInt(value); + SetDifficulty(value); } static void InputEndGracePeriod(int entity, int activator, int caller) diff --git a/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_weapons.sp b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_weapons.sp new file mode 100644 index 00000000..ec81c29c --- /dev/null +++ b/addons/sourcemod/scripting/sf2/mapentities/sf2_trigger_weapons.sp @@ -0,0 +1,189 @@ +// sf2_trigger_weapons + +#pragma semicolon 1 +#pragma newdecls required + +static CEntityFactory g_EntityFactory; + +static bool g_PlayerInWeaponsTrigger[MAXTF2PLAYERS]; +static ArrayList g_PlayerEnteredWeaponTriggers[MAXTF2PLAYERS] = { null, ... }; + +/** + * Interface that exposes public methods for interacting with the entity. + */ +methodmap SF2TriggerWeaponsEntity < SF2TriggerMapEntity +{ + public SF2TriggerWeaponsEntity(int entIndex) + { + return view_as(SF2TriggerMapEntity(entIndex)); + } + + public bool IsValid() + { + if (!CBaseEntity(this.index).IsValid()) + { + return false; + } + + return CEntityFactory.GetFactoryOfEntity(this.index) == g_EntityFactory; + } + + public static void Initialize() + { + Initialize(); + } +} + +static void Initialize() +{ + g_EntityFactory = new CEntityFactory("sf2_trigger_weapons", OnCreate); + g_EntityFactory.DeriveFromClass("trigger_multiple"); + + g_EntityFactory.Install(); + + g_OnPlayerPutInServerPFwd.AddFunction(null, OnPutInServer); + g_OnPlayerDisconnectedPFwd.AddFunction(null, OnDisconnected); + g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); + g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); +} + +static void OnPutInServer(SF2_BasePlayer client) +{ + if (!g_Enabled) + { + return; + } + + g_PlayerEnteredWeaponTriggers[client.index] = new ArrayList(); + g_PlayerInWeaponsTrigger[client.index] = false; +} + +static void OnDisconnected(SF2_BasePlayer client) +{ + SetWeaponsState(client.index, false, false); + + if (g_PlayerEnteredWeaponTriggers[client.index] != null) + { + delete g_PlayerEnteredWeaponTriggers[client.index]; + g_PlayerEnteredWeaponTriggers[client.index] = null; + } +} + +static void OnPlayerSpawn(SF2_BasePlayer client) +{ + if (IsRoundInWarmup() || GameRules_GetProp("m_bInWaitingForPlayers")) + { + return; + } + + SetWeaponsState(client.index, false, false); +} + +static void OnPlayerDeath(SF2_BasePlayer client, int attacker, int inflictor, bool fake) +{ + if (!g_Enabled) + { + return; + } + + if (!fake) + { + SetWeaponsState(client.index, false, false); + } +} + +static void OnCreate(int entity) +{ + SDKHook(entity, SDKHook_SpawnPost, OnSpawn); + SDKHook(entity, SDKHook_StartTouchPost, OnStartTouchPost); + SDKHook(entity, SDKHook_EndTouchPost, OnEndTouchPost); +} + +static void OnSpawn(int entity) +{ + int spawnFlags = GetEntProp(entity, Prop_Data, "m_spawnflags"); + SetEntProp(entity, Prop_Data, "m_spawnflags", spawnFlags | TRIGGER_CLIENTS); +} + +static void OnStartTouchPost(int entity, int toucher) +{ + if (!IsValidClient(toucher) || !IsPlayerAlive(toucher) || IsClientInGhostMode(toucher)) + { + return; + } + + if (!g_PlayerEliminated[toucher] && !DidClientEscape(toucher)) + { + return; + } + + int entRef = EnsureEntRef(entity); + if (g_PlayerEnteredWeaponTriggers[toucher].FindValue(entRef) == -1) + { + g_PlayerEnteredWeaponTriggers[toucher].Push(entRef); + } + + SetWeaponsState(toucher, true); +} + +static void OnEndTouchPost(int entity, int toucher) +{ + if (!IsValidClient(toucher)) + { + return; + } + + if (g_PlayerEnteredWeaponTriggers[toucher] != null) + { + int triggerEntRef = EnsureEntRef(entity); + for (int i = g_PlayerEnteredWeaponTriggers[toucher].Length - 1; i >= 0; i--) + { + int entRef = g_PlayerEnteredWeaponTriggers[toucher].Get(i); + if (entRef == triggerEntRef) + { + g_PlayerEnteredWeaponTriggers[toucher].Erase(i); + } + else if (EntRefToEntIndex(entRef) == INVALID_ENT_REFERENCE) + { + g_PlayerEnteredWeaponTriggers[toucher].Erase(i); + } + } + } + + if (IsClientInWeaponsTrigger(toucher) && g_PlayerEnteredWeaponTriggers[toucher].Length == 0) + { + SetWeaponsState(toucher, false); + } +} + +static void SetWeaponsState(int client, bool status, bool regenerate = true) +{ + SF2_BasePlayer player = SF2_BasePlayer(client); + if (!player.IsValid) + { + return; + } + + bool old = g_PlayerInWeaponsTrigger[player.index]; + if (status == old) + { + return; + } + + g_PlayerInWeaponsTrigger[player.index] = status; + + if (regenerate) + { + int health = player.GetProp(Prop_Send, "m_iHealth"); + player.RemoveWeaponSlot(TFWeaponSlot_Primary); + player.RemoveWeaponSlot(TFWeaponSlot_Secondary); + player.Regenerate(); + player.SetProp(Prop_Data, "m_iHealth", health); + player.SetProp(Prop_Send, "m_iHealth", health); + } +} + +bool IsClientInWeaponsTrigger(int client) +{ + return g_PlayerInWeaponsTrigger[client]; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/sf2/menus.sp b/addons/sourcemod/scripting/sf2/menus.sp index 04b80690..9fd02d5f 100644 --- a/addons/sourcemod/scripting/sf2/menus.sp +++ b/addons/sourcemod/scripting/sf2/menus.sp @@ -480,7 +480,7 @@ static int Menu_Main(Menu menu, MenuAction action, int param1, int param2) } case 8: { - DisplayBossList(param1); + BossPreview_MainMenu(param1, 0); } } } @@ -496,10 +496,6 @@ static void Menu_VoteRunoffDifficulty(Menu oldmenu, int votes, int clients, cons { if (float(itemInfo[0][VOTEINFO_ITEM_VOTES]) <= (votes * runoff)) { - g_IsRunOff = true; - Menu newmenu = new Menu(Menu_VoteDifficulty); - newmenu.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); - ArrayList list = new ArrayList(); for(int i = 0; i < items; i++) { @@ -509,7 +505,46 @@ static void Menu_VoteRunoffDifficulty(Menu oldmenu, int votes, int clients, cons } } - char data[64], display[64]; + char data[64]; + if (g_HighDifficultyPercentConVar.FloatValue > 0.0) + { + bool hard, easy; + int length = list.Length; + for (int i = 0; i < length; i++) + { + int index = list.Get(i); + oldmenu.GetItem(index, data, sizeof(data)); + + if ((strcmp(data, "4") == 0 || strcmp(data, "5") == 0)) + { + hard = true; + } + else + { + easy = true; + } + + if(easy && hard) + { + delete list; + + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(false); + Call_Finish(); + + CPrintToChatAll("%t", "SF2 Difficulty Vote Finished Unsuccessful Runoff", RoundToFloor(g_HighDifficultyPercentConVar.FloatValue * 100.0)); + g_VoteTimer = CreateTimer(1.0, Timer_HighVoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + return; + } + } + } + + g_IsRunOff = true; + Menu newmenu = new Menu(Menu_VoteDifficulty); + newmenu.SetTitle("%t %t\n \n", "SF2 Prefix", "SF2 Difficulty Vote Menu Title"); + + char display[64]; int length = list.Length; for (int i = 0; i < length; i++) { @@ -701,6 +736,11 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par { if (values > 0.5) { + Call_StartForward(g_OnDifficultyVoteFinishedFwd); + Call_PushCell(g_Voters); + Call_PushCell(g_IsRunOff); + Call_Finish(); + CPrintToChatAll("%t", "SF2 Difficulty Vote Finished Unsuccessful Runoff", RoundToFloor(g_HighDifficultyPercentConVar.FloatValue * 100.0)); g_VoteTimer = CreateTimer(1.0, Timer_HighVoteDifficulty, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); return 0; @@ -938,7 +978,7 @@ static int Menu_VoteDifficulty(Menu menu, MenuAction action, int param1, int par } } - g_DifficultyConVar.SetInt(difficulty); + SetDifficulty(difficulty); if (!change) { diff --git a/addons/sourcemod/scripting/sf2/methodmaps.sp b/addons/sourcemod/scripting/sf2/methodmaps.sp index 9f9b93dc..9aeb0a15 100644 --- a/addons/sourcemod/scripting/sf2/methodmaps.sp +++ b/addons/sourcemod/scripting/sf2/methodmaps.sp @@ -565,6 +565,14 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + property int ModifiedMaxHealth + { + public get() + { + return ClientGetModifiedMaxHealth(this.index); + } + } + property bool Ducking { public get() @@ -854,6 +862,14 @@ methodmap SF2_BasePlayer < CBaseCombatCharacter } } + property bool IsInWeaponsTriggers + { + public get() + { + return IsClientInWeaponsTrigger(this.index); + } + } + property bool IsInDeathCam { public get() diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp index 82504be0..8706c0cb 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/laser_beam.sp @@ -257,6 +257,8 @@ static void FireLaser(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity acto attackData.ApplyDamageEffects(SF2_BasePlayer(hitTarget), difficulty, SF2_ChaserBossEntity(actor.index)); } + + delete trace; } static void OnAnimationEvent(SF2_ChaserAttackAction_Laser action, SF2_ChaserEntity actor, int event) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp index a5da6338..9c263f90 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/actions/attacks/melee.sp @@ -305,8 +305,16 @@ static void DoMeleeAttack(SF2_ChaserAttackAction_Melee action, SF2_ChaserEntity float realDamage = damage; if (attackData.DamagePercent[difficulty] > 0.0) { - realDamage = strcmp(class, "tank_boss", false) != 0 ? float(prop.GetProp(Prop_Send, "m_iMaxHealth")) : float(prop.GetProp(Prop_Data, "m_iMaxHealth")); - realDamage *= attackData.DamagePercent[difficulty]; + if (SF2_BasePlayer(targets.Get(i)).IsValid) + { + realDamage = float(SF2_BasePlayer(targets.Get(i)).ModifiedMaxHealth); + realDamage *= attackData.DamagePercent[difficulty]; + } + else + { + realDamage = (strcmp(class, "tank_boss", false) != 0 && strcmp(class, "func_breakable", false) != 0) ? float(prop.GetProp(Prop_Send, "m_iMaxHealth")) : float(prop.GetProp(Prop_Data, "m_iMaxHealth")); + realDamage *= attackData.DamagePercent[difficulty]; + } } SDKHooks_TakeDamage(prop.index, actor.index, actor.index, realDamage, 64, _, _, myEyePos, false); } diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index c90297ac..fd7284fc 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -119,7 +119,7 @@ methodmap SF2_ChaserEntity < SF2_BaseBoss g_OnEntityCreatedPFwd.AddFunction(null, EntityCreated); g_OnPlayerSpawnPFwd.AddFunction(null, OnPlayerSpawn); - g_OnPlayerTakeDamagePFwd.AddFunction(null, OnPlayerTakeDamage); + g_OnPlayerTakeDamagePostPFwd.AddFunction(null, OnPlayerTakeDamagePost); g_OnPlayerDeathPrePFwd.AddFunction(null, OnPlayerDeathPre); g_OnPlayerDeathPFwd.AddFunction(null, OnPlayerDeath); @@ -3209,18 +3209,18 @@ static void OnPlayerSpawn(SF2_BasePlayer client) } } -static Action OnPlayerTakeDamage(SF2_BasePlayer client, int &attacker, int &inflictor, float &damage, int &damageType) +static void OnPlayerTakeDamagePost(SF2_BasePlayer client, int attacker, int inflictor, float damage, int damageType) { SF2_ChaserEntity boss = SF2_ChaserEntity(inflictor); if (!boss.IsValid()) { - return Plugin_Continue; + return; } SF2NPC_Chaser controller = boss.Controller; if (!controller.IsValid()) { - return Plugin_Continue; + return; } Call_StartForward(g_OnClientDamagedByBossFwd); @@ -3246,8 +3246,6 @@ static Action OnPlayerTakeDamage(SF2_BasePlayer client, int &attacker, int &infl SlenderSpawnEffects(attackData.HitEffects, controller.Index, false, _, _, _, client.index); } } - - return Plugin_Continue; } static void OnPlayerDeathPre(SF2_BasePlayer client, int attacker, int inflictor, bool fake) @@ -3497,16 +3495,26 @@ static void SpawnPost(int entIndex) { continue; } - if (originalData.IsPvEBoss) + if (g_Enabled) { - if (!player.IsEliminated) + if (originalData.IsPvEBoss) { - continue; + if (!player.IsEliminated) + { + continue; + } + } + else + { + if (player.IsEliminated || !player.IsAlive || player.HasEscaped) + { + continue; + } } } else { - if (player.IsEliminated || !player.IsAlive || player.HasEscaped) + if (player.Team != TFTeam_Red && player.Team != TFTeam_Blue) { continue; } @@ -3581,8 +3589,21 @@ static void SpawnPost(int entIndex) if (originalData.Healthbar) { controller.Flags |= SFF_NOTELEPORT; - UpdateHealthBar(controller.Index); + CreateTimer(0.1, Timer_UpdateHealthBar, controller.UniqueID, TIMER_FLAG_NO_MAPCHANGE); + } +} + +static Action Timer_UpdateHealthBar(Handle timer, any id) +{ + int bossIndex = NPCGetFromUniqueID(id); + if (bossIndex == -1) + { + return Plugin_Continue; } + + UpdateHealthBar(bossIndex); + + return Plugin_Stop; } static Action OnTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damageType, int &weapon, float damageForce[3], float damagePosition[3], int damageCustom) @@ -4518,9 +4539,9 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } } - delete valids; + CNavArea myArea = chaser.GetLastKnownArea(); - if (!originalData.IsPvEBoss && (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly || g_RenevantBossesChaseEndlessly)) + if (!originalData.IsPvEBoss && myArea != NULL_AREA && (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly || g_RenevantBossesChaseEndlessly)) { if (!IsTargetValidForSlender(chaser, CBaseEntity(bestNewTarget), attackEliminated)) { diff --git a/addons/sourcemod/scripting/sf2/profiles.sp b/addons/sourcemod/scripting/sf2/profiles.sp index 79db3e75..f4dc6782 100644 --- a/addons/sourcemod/scripting/sf2/profiles.sp +++ b/addons/sourcemod/scripting/sf2/profiles.sp @@ -20,6 +20,7 @@ static ArrayList g_SelectableBoxingBossProfileList = null; static ArrayList g_SelectableRenevantBossProfileList = null; static ArrayList g_SelectableRenevantBossAdminProfileList = null; static ArrayList g_SelectableBossProfileQueueList = null; +ArrayList g_BossProfileRequested = null; StringMap g_BossProfileData = null; @@ -43,6 +44,9 @@ static char mapBossPack[64]; GlobalForward g_OnBossProfileLoadedFwd; static GlobalForward g_OnBossProfileUnloadedFwd; +static ArrayList g_AllBosses; +static StringMap g_AllBossesName; + #include "profiles/profiles_boss_functions.sp" #include "profiles/profile_chaser.sp" #include "profiles/profile_statue.sp" @@ -297,20 +301,7 @@ void GetCurrentBossPack(char[] bossPackName, int length) /* Command */ -Action Command_Pack(int client,int args) -{ - if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) - { - CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Disabled Boss Pack"); - return Plugin_Handled; - } - char bossPackName[64]; - GetCurrentBossPack(bossPackName, sizeof(bossPackName)); - CPrintToChat(client, "{royalblue}%t {default}%t", "SF2 Prefix", "SF2 Current Boss Pack", bossPackName); - return Plugin_Handled; -} - -Action Command_NextPack(int client,int args) +Action Command_NextPack(int client, int args) { if (!g_BossPackEndOfMapVoteConVar.BoolValue || !g_BossPackVoteEnabled) { @@ -346,6 +337,101 @@ Action Command_NextPack(int client,int args) return Plugin_Handled; } +Action Command_BossOverride(int client, int args) +{ + if (args) + { + ArrayList bossList = GetBossProfileList(); + + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + GetCmdArgString(profile, sizeof(profile)); + + if(bossList.FindString(profile) == -1) + { + CReplyToCommand(client, "{royalblue}%t {default}Added profile \"%s\" to be forced loaded next pack", "SF2 Prefix", profile); + } + else + { + char displayName[SF2_MAX_NAME_LENGTH]; + NPCGetBossName(_, displayName, sizeof(displayName), profile); + CReplyToCommand(client, "{royalblue}%t {default}Added boss \"%s\" (%s) to be forced loaded next pack", "SF2 Prefix", displayName, profile); + } + + if(!g_BossProfileRequested) + { + g_BossProfileRequested = new ArrayList(sizeof(profile)); + } + + g_BossProfileRequested.PushString(profile); + } + else if (client) + { + Menu menu = new Menu(Command_BossOverrideHandle); + + ArrayList bossList = g_AllBosses; + + if (bossList != null) + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + char displayName[SF2_MAX_NAME_LENGTH]; + for (int i = 0; i < bossList.Length; i++) + { + bossList.GetString(i, profile, sizeof(profile)); + displayName[0] = 0; + //NPCGetBossName(_, displayName, sizeof(displayName), profile); + g_AllBossesName.GetString(profile, displayName, sizeof(displayName)); + if (displayName[0] == '\0') + { + strcopy(displayName, sizeof(displayName), profile); + } + SF2BossProfileData data; + g_BossProfileData.GetArray(profile, data, sizeof(data)); + /*if (data.Description.Hidden || data.IsPvEBoss) + { + continue; + }*/ + menu.AddItem(profile, displayName); + } + } + + menu.SetTitle("Force Load Next Pack\nUsage: sm_sf2_set_modboss \n "); + menu.ExitBackButton = true; + menu.Display(client, MENU_TIME_FOREVER); + } + else + { + PrintToServer("[SM] Usage: sm_sf2_set_modboss "); + } + return Plugin_Handled; +} + +static int Command_BossOverrideHandle(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + delete menu; + } + case MenuAction_Cancel: + { + if(param2 == MenuCancel_ExitBack) + { + DisplayBossMainAdminMenu(param1); + } + } + case MenuAction_Select: + { + char profile[SF2_MAX_PROFILE_NAME_LENGTH]; + menu.GetItem(param2, profile, sizeof(profile)); + + FakeClientCommand(param1, "sm_sf2_set_modboss %s", profile); + } + } + + return 0; +} + void BossProfilesOnMapEnd() { ClearBossProfiles(); @@ -553,6 +639,12 @@ void ReloadBossProfiles() delete g_SelectableBossProfileQueueList; } + delete g_AllBosses; + g_AllBosses = new ArrayList(PLATFORM_MAX_PATH); + + delete g_AllBossesName; + g_AllBossesName = new StringMap(); + char configPath[PLATFORM_MAX_PATH]; // Only load profiles individually from configs/sf2/profiles or data/sf2/profiles directory. @@ -620,6 +712,7 @@ void ReloadBossProfiles() if (g_BossPackConfig.JumpToKey("shuffler")) { maxLoadedBosses = g_BossPackConfig.GetNum("max", maxLoadedBosses); + g_BossPackConfig.GoBack(); } LoadProfilesFromDirectory(packConfigFilePath, maxLoadedBosses); } @@ -671,6 +764,7 @@ void ReloadBossProfiles() if (g_BossPackConfig.JumpToKey("shuffler")) { maxLoadedBosses = g_BossPackConfig.GetNum("max", maxLoadedBosses); + g_BossPackConfig.GoBack(); } LoadProfilesFromDirectory(packConfigFilePath, maxLoadedBosses); } @@ -700,6 +794,8 @@ void ReloadBossProfiles() LogSF2Message("Time to take to load all boss configs: %f", profiler.Time); delete profiler; + + delete g_BossProfileRequested; } /** @@ -752,11 +848,11 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss int count = 0; - char filePath[PLATFORM_MAX_PATH]; - char fileName[PLATFORM_MAX_PATH]; + char filePath[PLATFORM_MAX_PATH], filePathEx[PLATFORM_MAX_PATH]; + char fileName[PLATFORM_MAX_PATH], fileNameEx[PLATFORM_MAX_PATH]; char profileName[SF2_MAX_PROFILE_NAME_LENGTH]; char errorReason[512]; - FileType fileType; + FileType fileType, fileTypeEx; ArrayList directories = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); @@ -773,37 +869,111 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss directories.PushString(filePath); } - delete directory; - ArrayList alwaysLoad; + ArrayList prioLoad; - if (maxLoadedBosses > 0) + //if (maxLoadedBosses > 0 || g_BossProfileRequested) { alwaysLoad = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + prioLoad = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); for (int i = 0; i < directories.Length; i++) { directories.GetString(i, filePath, sizeof(filePath)); - if (FileExists(filePath)) + if (!FileExists(filePath)) + { + continue; + } + KeyValues kv = new KeyValues("root"); + if (!FileToKeyValues(kv, filePath)) + { + delete kv; + continue; + } + kv.GetSectionName(profileName, sizeof(profileName)); + if(kv.GetNum("admin_only")) + { + g_AllBosses.PushString(profileName); + kv.GetString("name", fileName, sizeof(fileName)); + g_AllBossesName.SetString(profileName, fileName); + } + + if (kv.GetNum("always_load", false) != 0) { - KeyValues kv = new KeyValues("root"); - if (FileToKeyValues(kv, filePath) && kv.GetNum("always_load", false) != 0) + int index = directories.FindString(filePath); + if (index != -1) { - int index = directories.FindString(filePath); - if (index != -1) + directories.Erase(index); + } + alwaysLoad.PushString(filePath); + i--; + } + else if (g_BossProfileRequested && g_BossProfileRequested.FindString(profileName) != -1) + { + int index = directories.FindString(filePath); + if (index != -1) + { + directories.Erase(index); + } + prioLoad.PushString(filePath); + i--; + } + + if (kv.JumpToKey("always_load_with") && kv.GotoFirstSubKey(false)) + { + char key[64], boss[SF2_MAX_PROFILE_NAME_LENGTH]; + do + { + kv.GetSectionName(key, sizeof(key)); + kv.GetString(NULL_STRING, boss, sizeof(boss)); + if (boss[0] == '\0') { - directories.Erase(index); + continue; } - alwaysLoad.PushString(filePath); - i--; - } - delete kv; + delete directory; + directory = OpenDirectory(dirPath); + while (directory.GetNext(fileNameEx, sizeof(fileNameEx), fileTypeEx)) + { + if (fileTypeEx == FileType_Directory) + { + continue; + } + + FormatEx(filePathEx, sizeof(filePathEx), "%s/%s", relDirPath, fileNameEx); + BuildPath(Path_SM, filePathEx, sizeof(filePathEx), filePathEx); + + KeyValues kvEx = new KeyValues("root"); + if (!FileToKeyValues(kvEx, filePathEx)) + { + delete kvEx; + continue; + } + + char section[SF2_MAX_PROFILE_NAME_LENGTH]; + kvEx.GetSectionName(section, sizeof(section)); + + if (strcmp(section, boss, false) != 0) + { + delete kvEx; + continue; + } + + alwaysLoad.PushString(filePathEx); + delete kvEx; + break; + } + } + while (kv.GotoNextKey(false)); } + + delete kv; } } + delete directory; + if (alwaysLoad != null) { for (int i = 0; i < alwaysLoad.Length; i++) @@ -823,6 +993,31 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss delete alwaysLoad; } + if (prioLoad != null) + { + for (int i = 0; i < prioLoad.Length; i++) + { + if (maxLoadedBosses > 0 && count >= maxLoadedBosses) + { + break; + } + + prioLoad.GetString(i, filePath, sizeof(filePath)); + + if (!LoadProfileFile(filePath, profileName, sizeof(profileName), errorReason, sizeof(errorReason), maxLoadedBosses > 0, dirPath)) + { + LogSF2Message("(ADMIN LOAD) %s...FAILED (reason: %s)", filePath, errorReason); + } + else + { + LogSF2Message("(ADMIN LOAD) %s...", profileName, filePath); + count++; + } + } + + delete prioLoad; + } + if (maxLoadedBosses > 0) { directories.Sort(Sort_Random, Sort_String); @@ -830,7 +1025,7 @@ static void LoadProfilesFromDirectory(const char[] relDirPath, int maxLoadedBoss for (int i = 0; i < directories.Length; i++) { - if (maxLoadedBosses > 0 && count == maxLoadedBosses) + if (maxLoadedBosses > 0 && count >= maxLoadedBosses) { break; } diff --git a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp index ae908928..83acedb4 100644 --- a/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp +++ b/addons/sourcemod/scripting/sf2/profiles/profiles_boss_functions.sp @@ -10,6 +10,12 @@ */ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBuffer, int loadFailReasonBufferLen, bool lookIntoLoads = false, const char[] originalDir = "") { + if (IsProfileValid(profile)) + { + FormatEx(loadFailReasonBuffer, loadFailReasonBufferLen, "is already loaded or matches another boss's profile name!"); + return false; + } + SF2BossProfileData profileData; profileData.Init(); @@ -869,7 +875,13 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu g_BossProfileList.PushString(profile); } - if (kv.GetNum("enable_random_selection", true) != 0) + profileData.EnableRandomSelection = kv.GetNum("enable_random_selection", true) != 0; + profileData.AdminOnly = kv.GetNum("admin_only", false) != 0; + profileData.EnableRandomSelectionBoxing = kv.GetNum("enable_random_selection_boxing", false) != 0; + profileData.EnableRandomSelectionRenevant = kv.GetNum("enable_random_selection_renevant", false) != 0; + profileData.EnableRandomSelectionRenevantAdmin = kv.GetNum("enable_random_selection_renevant_admin", false) != 0; + + if (profileData.EnableRandomSelection) { if (GetSelectableBossProfileList().FindString(profile) == -1) { @@ -886,7 +898,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (kv.GetNum("admin_only", false) != 0) + if (profileData.AdminOnly) { if (GetSelectableAdminBossProfileList().FindString(profile) == -1) { @@ -903,7 +915,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (kv.GetNum("enable_random_selection_boxing", false) != 0) + if (profileData.EnableRandomSelectionBoxing) { if (GetSelectableBoxingBossProfileList().FindString(profile) == -1) { @@ -920,7 +932,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (kv.GetNum("enable_random_selection_renevant", false) != 0) + if (profileData.EnableRandomSelectionRenevant) { if (GetSelectableRenevantBossProfileList().FindString(profile) == -1) { @@ -937,7 +949,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu } } - if (kv.GetNum("enable_random_selection_renevant_admin", false) != 0) + if (profileData.EnableRandomSelectionRenevantAdmin) { if (GetSelectableRenevantBossAdminProfileList().FindString(profile) == -1) { @@ -1294,6 +1306,7 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu profileData.CompanionsArray = new ArrayList(sizeof(SF2BossProfileCompanionsInfo)); kv.GetString("type", profileData.CompanionSpawnType, sizeof(profileData.CompanionSpawnType)); + ArrayList validProfiles = null; if (kv.GotoFirstSubKey()) { do @@ -1331,25 +1344,38 @@ bool LoadBossProfile(KeyValues kv, const char[] profile, char[] loadFailReasonBu if (strcmp(compProfile, otherProfile) == 0) { - if (!LoadBossProfile(otherKeys, otherProfile, loadFailReasonBuffer, loadFailReasonBufferLen)) + if (validProfiles == null) { - LogSF2Message("(COMPANION) %s...FAILED (reason: %s)", dir, loadFailReasonBuffer); + validProfiles = new ArrayList(ByteCountToCells(SF2_MAX_PROFILE_NAME_LENGTH)); } - else + if (validProfiles.FindString(otherProfile) == -1) { - LogSF2Message("(COMPANION) %s...", otherProfile); + if (!LoadBossProfile(otherKeys, otherProfile, loadFailReasonBuffer, loadFailReasonBufferLen)) + { + LogSF2Message("(COMPANION) %s...FAILED (reason: %s)", dir, loadFailReasonBuffer); + } + else + { + LogSF2Message("(COMPANION) %s...", otherProfile); + } + validProfiles.PushString(otherProfile); } } delete otherKeys; } } + delete directory; } } while (kv.GotoNextKey()); kv.GoBack(); } kv.GoBack(); + if (validProfiles != null) + { + delete validProfiles; + } } if (kv.JumpToKey("attributes")) diff --git a/addons/sourcemod/scripting/sf2/specialround.sp b/addons/sourcemod/scripting/sf2/specialround.sp index f9a3dde5..67b1695d 100644 --- a/addons/sourcemod/scripting/sf2/specialround.sp +++ b/addons/sourcemod/scripting/sf2/specialround.sp @@ -1092,7 +1092,7 @@ void SpecialRoundStart() ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); if (g_DifficultyConVar.IntValue < 2) { - g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. + SetDifficulty(Difficulty_Hard); // Override difficulty to Hardcore. } if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 2) { @@ -1231,7 +1231,7 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. } if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) { @@ -1243,7 +1243,7 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 2) { - g_DifficultyConVar.SetString("2"); // Override difficulty to Hardcore. + SetDifficulty(Difficulty_Hard); // Override difficulty to Insane. } if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 2) { @@ -1259,7 +1259,7 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. } if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) { @@ -1276,7 +1276,7 @@ void SpecialRoundStart() ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); if (g_DifficultyConVar.IntValue < 3 && !SF_IsBoxingMap()) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. } if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) { @@ -1313,7 +1313,7 @@ void SpecialRoundStart() ForceInNextPlayersInQueue(g_MaxPlayersConVar.IntValue); if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. } if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) { @@ -1353,17 +1353,17 @@ void SpecialRoundStart() { case 1: { - g_DifficultyConVar.SetInt(Difficulty_Normal); + SetDifficulty(Difficulty_Normal); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {yellow}%t{default}.", "SF2 Prefix", "SF2 Normal Difficulty"); } case 2: { - g_DifficultyConVar.SetInt(Difficulty_Hard); + SetDifficulty(Difficulty_Hard); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {orange}%t{default}.", "SF2 Prefix", "SF2 Hard Difficulty"); } case 3: { - g_DifficultyConVar.SetInt(Difficulty_Insane); + SetDifficulty(Difficulty_Insane); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {red}%t{default}.", "SF2 Prefix", "SF2 Insane Difficulty"); } case 4: @@ -1371,7 +1371,7 @@ void SpecialRoundStart() PlayNightmareSound(); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + SetDifficulty(Difficulty_Nightmare); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {valve}%t!", "SF2 Prefix", "SF2 Nightmare Difficulty"); } case 5: @@ -1379,7 +1379,7 @@ void SpecialRoundStart() PlayNightmareSound(); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + SetDifficulty(Difficulty_Apollyon); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {darkgray}%t!", "SF2 Prefix", "SF2 Apollyon Difficulty"); int randomQuote = GetRandomInt(1, 8); switch (randomQuote) @@ -1436,17 +1436,17 @@ void SpecialRoundStart() { case 1: { - g_DifficultyConVar.SetInt(Difficulty_Normal); + SetDifficulty(Difficulty_Normal); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {yellow}%t{default}.", "SF2 Prefix", "SF2 Normal Difficulty"); } case 2: { - g_DifficultyConVar.SetInt(Difficulty_Hard); + SetDifficulty(Difficulty_Hard); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {orange}%t{default}.", "SF2 Prefix", "SF2 Hard Difficulty"); } case 3: { - g_DifficultyConVar.SetInt(Difficulty_Insane); + SetDifficulty(Difficulty_Insane); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {red}%t{default}.", "SF2 Prefix", "SF2 Insane Difficulty"); } case 4: @@ -1454,7 +1454,7 @@ void SpecialRoundStart() PlayNightmareSound(); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Nightmare Difficulty"); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Nightmare); + SetDifficulty(Difficulty_Nightmare); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {valve}%t!", "SF2 Prefix", "SF2 Nightmare Difficulty"); } case 5: @@ -1462,7 +1462,7 @@ void SpecialRoundStart() PlayNightmareSound(); FormatEx(nightmareDisplay, sizeof(nightmareDisplay), "%t mode!", "SF2 Apollyon Difficulty"); SpecialRoundGameText(nightmareDisplay, "leaderboard_streak"); - g_DifficultyConVar.SetInt(Difficulty_Apollyon); + SetDifficulty(Difficulty_Apollyon); CPrintToChatAll("{royalblue}%t {default}The difficulty has been set to {darkgray}%t!", "SF2 Prefix", "SF2 Apollyon Difficulty"); int randomQuote = GetRandomInt(1, 8); switch (randomQuote) @@ -1523,7 +1523,7 @@ void SpecialRoundStart() case SPECIALROUND_TRIPLEBOSSES: { char buffer[SF2_MAX_PROFILE_NAME_LENGTH]; - int tripleBosses=0; + int tripleBosses = 0; for (int i = 0; i < MAX_BOSSES; i++) { NPCStopMusic(); @@ -1576,7 +1576,7 @@ void SpecialRoundStart() { if (g_DifficultyConVar.IntValue < 3) { - g_DifficultyConVar.SetString("3"); // Override difficulty to Insane. + SetDifficulty(Difficulty_Insane); // Override difficulty to Insane. } if (g_OverrideDifficulty == -1 || g_OverrideDifficulty < 3) { From a43fae5bbf283215b527daa5b953a5ada697130e Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Fri, 26 Dec 2025 23:16:42 -0700 Subject: [PATCH 82/86] Alright let's finish this, Merasmus is, getting a little bored watching you. --- .../sourcemod/scripting/sf2/client/weapons.sp | 4 +- .../scripting/sf2/extras/bosspreview.sp | 348 +++++++++++++----- 2 files changed, 249 insertions(+), 103 deletions(-) diff --git a/addons/sourcemod/scripting/sf2/client/weapons.sp b/addons/sourcemod/scripting/sf2/client/weapons.sp index e2070e50..6709c876 100644 --- a/addons/sourcemod/scripting/sf2/client/weapons.sp +++ b/addons/sourcemod/scripting/sf2/client/weapons.sp @@ -165,7 +165,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) } // pvp - if (client.IsInPvP || (SF_IsRaidMap() && !client.IsEliminated)) + if (client.IsInPvP || (SF_IsRaidMap() && !client.IsEliminated) || IsClientInWeaponsTrigger(client.index)) { removeWeapons = false; restrictWeapons = true; @@ -511,7 +511,7 @@ Action Timer_ClientPostWeapons(Handle timer, any userid) } //Remove the teleport ability - if (client.IsInPvP || client.IsInPvE || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !client.IsEliminated)) + if (client.IsInPvP || client.IsInPvE || ((SF_IsRaidMap() || SF_IsBoxingMap()) && !client.IsEliminated) || IsClientInWeaponsTrigger(client.index)) { int weaponEnt = INVALID_ENT_REFERENCE; Handle weaponHandle = null; diff --git a/addons/sourcemod/scripting/sf2/extras/bosspreview.sp b/addons/sourcemod/scripting/sf2/extras/bosspreview.sp index fdefe4eb..106deb41 100644 --- a/addons/sourcemod/scripting/sf2/extras/bosspreview.sp +++ b/addons/sourcemod/scripting/sf2/extras/bosspreview.sp @@ -3,6 +3,10 @@ enum struct BossData char Name[64]; char Profile[64]; char Pack[64]; + char Type[32]; + char Description[128]; + char WalkSpeed[32]; + char RunSpeed[32]; char Filepath[PLATFORM_MAX_PATH]; } @@ -19,38 +23,56 @@ enum struct EquipData int Flags; } -static ArrayList BossPacks; -static ArrayList BossList; +static ArrayList g_BossPacks; +static ArrayList g_BossList; -static int MainMenuPage[MAXPLAYERS+1]; -static int PackMenuPage[MAXPLAYERS+1]; -static char ViewingPack[MAXPLAYERS+1][64]; -static char ViewingBoss[MAXPLAYERS+1][64]; +static int g_MainMenuPage[MAXPLAYERS + 1]; +static int g_PackMenuPage[MAXPLAYERS + 1]; +static char g_ViewingPack[MAXPLAYERS + 1][64]; +static char g_ViewingBoss[MAXPLAYERS + 1][64]; -static int PetRef[MAXPLAYERS+1] = {INVALID_ENT_REFERENCE, ...}; +static int g_PreviewRef[MAXPLAYERS + 1] = {INVALID_ENT_REFERENCE, ...}; void BossPreview_ReloadPack() { - if(BossPacks) - delete BossPacks; + if (g_BossPacks) + { + delete g_BossPacks; + } - if(BossList) - delete BossList; + if (g_BossList) + { + delete g_BossList; + } - BossPacks = new ArrayList(sizeof(PackData)); - BossList = new ArrayList(sizeof(BossData)); + g_BossPacks = new ArrayList(sizeof(PackData)); + g_BossList = new ArrayList(sizeof(BossData)); BossData data; char pack[64], buffer[PLATFORM_MAX_PATH]; - - BuildPath(Path_SM, buffer, sizeof(buffer), "data/sf2/profiles"); + + if (!g_UseAlternateConfigDirectoryConVar.BoolValue) + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_DIR); + } + else + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_DIR_DATA); + } strcopy(data.Pack, sizeof(data.Pack), "Core Pack"); AddSlenderPack(data, "", buffer, true); KeyValues kv = new KeyValues("root"); - BuildPath(Path_SM, buffer, sizeof(buffer), "data/sf2/profiles_packs.cfg"); - if(kv.ImportFromFile(buffer)) + if (!g_UseAlternateConfigDirectoryConVar.BoolValue) + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_PACKS); + } + else + { + BuildPath(Path_SM, buffer, sizeof(buffer), FILE_PROFILES_PACKS_DATA); + } + if (kv.ImportFromFile(buffer)) { kv.JumpToKey("packs"); kv.GotoFirstSubKey(); @@ -58,12 +80,12 @@ void BossPreview_ReloadPack() do { kv.GetString("file", pack, sizeof(pack)); - BuildPath(Path_SM, buffer, sizeof(buffer), "data/sf2/profiles/packs/%s", pack); + BuildPath(Path_SM, buffer, sizeof(buffer), "%s/%s", !g_UseAlternateConfigDirectoryConVar.BoolValue ? FILE_PROFILES_PACKS_DIR : FILE_PROFILES_PACKS_DIR_DATA, pack); kv.GetString("name", data.Pack, sizeof(data.Pack)); AddSlenderPack(data, pack, buffer, kv.GetNum("autoload", false) != 0); } - while(kv.GotoNextKey()); + while (kv.GotoNextKey()); } delete kv; @@ -71,9 +93,9 @@ void BossPreview_ReloadPack() Action BossPreview_MainMenu(int client, int args) { - if(client) + if (client) { - if(args) + if (args) { char buffer[64]; GetCmdArgString(buffer, sizeof(buffer)); @@ -81,20 +103,22 @@ Action BossPreview_MainMenu(int client, int args) BossData boss; int i; - int length = BossList.Length; - for(; i(SF2_GetCurrentBossPack(buffer, length)); -} - -static void CreatePet(int client, const char[] profile) +static void CreatePreview(int client, const char[] profile) { BossPreview_Remove(client); - if (!SF2_IsValidClient(client) || !IsPlayerAlive(client) || !IsProfileValid(profile)) + if (!IsValidClient(client) || !IsPlayerAlive(client) || !IsProfileValid(profile)) + { return; - - if (SF2_IsClientInGhostMode(client) || SF2_IsClientProxy(client)) + } + + if (IsClientInGhostMode(client) || g_PlayerProxy[client]) + { return; + } ArrayList modelsArray = GetBossProfileModels(profile); @@ -371,12 +445,12 @@ static void CreatePet(int client, const char[] profile) entity.SetProp(Prop_Send, "m_nBody", GetBossProfileBodyGroups(profile)); entity.SetProp(Prop_Send, "m_nSkin", GetBossProfileSkin(profile)); entity.SetPropFloat(Prop_Send, "m_flModelScale", GetBossProfileModelScale(profile)); - + entity.SetPropEnt(Prop_Data, "m_hEffectEntity", client); - SDKHook(entity.index, SDKHook_SetTransmit, PetTransmit); + SDKHook(entity.index, SDKHook_SetTransmit, PreviewTransmit); - PetRef[client] = EntIndexToEntRef(entity.index); - strcopy(ViewingBoss[client], sizeof(ViewingBoss[]), profile); + g_PreviewRef[client] = EntIndexToEntRef(entity.index); + strcopy(g_ViewingBoss[client], sizeof(g_ViewingBoss[]), profile); SetDefaultAnimation(entity.index, profile); static float pos[3], ang[3]; @@ -402,19 +476,19 @@ static void CreatePet(int client, const char[] profile) void BossPreview_Remove(int client) { - if (IsValidEntity(PetRef[client])) + if (IsValidEntity(g_PreviewRef[client])) { - RemoveEntity(PetRef[client]); - PetRef[client] = INVALID_ENT_REFERENCE; + RemoveEntity(g_PreviewRef[client]); + g_PreviewRef[client] = INVALID_ENT_REFERENCE; } } -static Action PetTransmit(int entity, int client) +static Action PreviewTransmit(int entity, int client) { SetEdictFlags(entity, GetEdictFlags(entity) &~ FL_EDICT_ALWAYS); - + int owner = GetEntPropEnt(entity, Prop_Data, "m_hEffectEntity"); - if(owner == -1) + if (owner == -1) { RemoveEntity(entity); return Plugin_Continue; @@ -422,12 +496,14 @@ static Action PetTransmit(int entity, int client) if(owner == client) { - if(GetClientMenu(client) == MenuSource_None) + if (GetClientMenu(client) == MenuSource_None) + { RemoveEntity(entity); - + } + return Plugin_Continue; } - + return Plugin_Stop; } @@ -437,7 +513,7 @@ static void SetDefaultAnimation(int entity, const char[] profile) GetBossProfileAnimationsData(profile, masterData); char animation[64]; masterData.GetAnimation("idle", Difficulty_Normal, animation, sizeof(animation)); - + ArrayList animations, validAnimations; validAnimations = new ArrayList(); masterData.Animations.GetValue("idle", animations); @@ -446,8 +522,10 @@ static void SetDefaultAnimation(int entity, const char[] profile) validAnimations.Push(i); } if (validAnimations.Length <= 0) + { return; - + } + float playback; int randomIndex = validAnimations.Get(GetRandomInt(0, validAnimations.Length - 1)); masterData.GetAnimation("idle", Difficulty_Normal, animation, sizeof(animation), playback, _, _, _, _, randomIndex); @@ -462,22 +540,24 @@ static void SetDefaultAnimation(int entity, const char[] profile) animator.SetPropFloat(Prop_Data, "m_flCycle", 0.0); animator.SetPropFloat(Prop_Send, "m_flPlaybackRate", playback); - + delete validAnimations; } void BossPreview_OnClientAttack(int client) { - if(!IsValidEntity(PetRef[client])) + if (!IsValidEntity(g_PreviewRef[client])) + { return; - - int entity = EntRefToEntIndex(PetRef[client]); + } + + int entity = EntRefToEntIndex(g_PreviewRef[client]); SF2BossProfileMasterAnimationsData masterData; - GetBossProfileAnimationsData(ViewingBoss[client], masterData); + GetBossProfileAnimationsData(g_ViewingBoss[client], masterData); char animation[64]; masterData.GetAnimation("attack", Difficulty_Normal, animation, sizeof(animation)); - + ArrayList animations, validAnimations; validAnimations = new ArrayList(); masterData.Animations.GetValue("attack", animations); @@ -486,8 +566,10 @@ void BossPreview_OnClientAttack(int client) validAnimations.Push(i); } if (validAnimations.Length <= 0) + { return; - + } + float playback; int randomIndex = validAnimations.Get(GetRandomInt(0, validAnimations.Length - 1)); masterData.GetAnimation("attack", Difficulty_Normal, animation, sizeof(animation), playback, _, _, _, _, randomIndex); @@ -499,6 +581,70 @@ void BossPreview_OnClientAttack(int client) animator.SetPropFloat(Prop_Data, "m_flCycle", 0.0); animator.SetPropFloat(Prop_Send, "m_flPlaybackRate", playback); - + delete validAnimations; } + +static void ConvertWalkSpeedToDescription(float speed, char[] buffer, int bufferLen) +{ + char val[128]; + val = "None"; + if (speed > 0.0 && speed <= 45.0) + { + val = "Very slow"; + } + else if (speed > 45.0 && speed <= 65.0) + { + val = "Slow"; + } + else if (speed > 65.0 && speed <= 85.0) + { + val = "Moderate"; + } + else if (speed > 85.0 && speed <= 120.0) + { + val = "Average"; + } + else if (speed > 120.0 && speed <= 150.0) + { + val = "Fast"; + } + else + { + val = "Very fast"; + } + + strcopy(buffer, bufferLen, val); +} + +static void ConvertRunSpeedToDescription(float speed, char[] buffer, int bufferLen) +{ + char val[128]; + val = "None"; + if (speed > 0.0 && speed <= 75.0) + { + val = "Very slow"; + } + else if (speed > 75.0 && speed <= 150.0) + { + val = "Slow"; + } + else if (speed > 150.0 && speed <= 275.0) + { + val = "Moderate"; + } + else if (speed > 275.0 && speed <= 325.0) + { + val = "Average"; + } + else if (speed > 325.0 && speed <= 375.0) + { + val = "Fast"; + } + else + { + val = "Very fast"; + } + + strcopy(buffer, bufferLen, val); +} From 49ba36ddf94630de16943b2e155986e383c72ee5 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:01:31 -0700 Subject: [PATCH 83/86] Wait nonononono --- addons/sourcemod/scripting/sf2/extras/commands.sp | 1 + addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp | 1 + 2 files changed, 2 insertions(+) diff --git a/addons/sourcemod/scripting/sf2/extras/commands.sp b/addons/sourcemod/scripting/sf2/extras/commands.sp index 82d69296..8eb2fc5b 100644 --- a/addons/sourcemod/scripting/sf2/extras/commands.sp +++ b/addons/sourcemod/scripting/sf2/extras/commands.sp @@ -1774,6 +1774,7 @@ static Action Command_ReloadProfiles(int client, int args) ReloadRestrictedWeapons(); ReloadSpecialRounds(); ReloadClassConfigs(); + BossPreview_ReloadPack(); } for (int i = 0; i < MAX_BOSSES; i++) diff --git a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp index fd7284fc..927ddd4b 100644 --- a/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp +++ b/addons/sourcemod/scripting/sf2/npc/entities/chaser/entity.sp @@ -4539,6 +4539,7 @@ static CBaseEntity ProcessVision(SF2_ChaserEntity chaser, int &interruptConditio } } + delete valids; CNavArea myArea = chaser.GetLastKnownArea(); if (!originalData.IsPvEBoss && myArea != NULL_AREA && (SF_IsRaidMap() || SF_BossesChaseEndlessly() || SF_IsProxyMap() || SF_IsBoxingMap() || SF_IsSlaughterRunMap() || data.ChasesEndlessly || g_RenevantBossesChaseEndlessly)) From 547922e7efcfb52f593bce2cbc1160dfc48e56c4 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:20:38 -0700 Subject: [PATCH 84/86] Update sf2.phrases.txt aga --- addons/sourcemod/translations/sf2.phrases.txt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/addons/sourcemod/translations/sf2.phrases.txt b/addons/sourcemod/translations/sf2.phrases.txt index 0fe4a9b2..02340da5 100644 --- a/addons/sourcemod/translations/sf2.phrases.txt +++ b/addons/sourcemod/translations/sf2.phrases.txt @@ -11,39 +11,39 @@ } "SF2 Renevant Wall Hax" { - "en" "You can now see everything through walls" + "en" "You can now see bosses and players through walls." } "SF2 Renevant Marked" { - "en" "Everyone is marked for death permanently" + "en" "Everyone is marked for death permanently." } "SF2 Renevant 90s" { - "en" "Everyone run more faster" + "en" "You feel really nervous, now run faster." } "SF2 Renevant Bacon" { - "en" "Now Bosses are now alerted on spawn" + "en" "Bosses are now alerted on spawn." } "SF2 Renevant Multieffects" { - "en" "Now Bosses can now inflict Multieffect on damage" + "en" "Bosses can now inflict Multieffect." } "SF2 Renevant Difficulty" { - "en" "Now difficulty got changed to" + "en" "Difficulty has been set to" } "SF2 Boss Added" { - "en" "New Boss" + "en" "Next Boss" } "SF2 Bosses Added" { - "en" "New Bosses Added" + "en" "Next Bosses" } "SF2 Admin Boss" { - "en" "Special Boss Added" + "en" "Next Exclusive Boss" } "SF2 Difficulty Vote Finished" { @@ -2339,3 +2339,4 @@ } } + From 6ed312effd84e4cdd4e5b2d02ff197bf551e9680 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:07:17 -0700 Subject: [PATCH 85/86] Update sf2.fgd --- sf2.fgd | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sf2.fgd b/sf2.fgd index 1eaeeb04..dd96573b 100644 --- a/sf2.fgd +++ b/sf2.fgd @@ -405,4 +405,8 @@ 0 : "Disabled" 1 : "Enabled" ] -] \ No newline at end of file +] + +@SolidClass base(trigger_multiple) = sf2_trigger_weapons : "(Modified only) A trigger brush that when touched by a player will automatically give them all weapons but cannot damage other players. The substitude for sf2_trigger_pve" +[ +] From bbcbcca9f4171e80f377afbebe943a094c87cab6 Mon Sep 17 00:00:00 2001 From: Mentrillum <42941613+Mentrillum@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:58:57 -0700 Subject: [PATCH 86/86] :agabusiness: --- .../scripting/include/sf2/profiles/profiles_chaser.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc index 08a0b50c..77f7e686 100644 --- a/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc +++ b/addons/sourcemod/scripting/include/sf2/profiles/profiles_chaser.inc @@ -374,7 +374,7 @@ enum struct SF2ChaserBossProfileDamageEffectData // Can be any data this.Sounds.Destroy(); } - void Apply(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = SF2_ChaserBossEntity(-1)) + void Apply(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = view_as(-1)) { if (!this.Enabled[difficulty]) { @@ -603,7 +603,7 @@ enum struct SF2ChaserBossProfileShockwaveData } } - void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = SF2_ChaserBossEntity(-1)) + void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = view_as(-1)) { if (this.DamageEffects == null) { @@ -1064,7 +1064,7 @@ enum struct SF2ChaserBossProfileAttackData return strcmp(subType, this.SubType) == 0; } - void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = SF2_ChaserBossEntity(-1)) + void ApplyDamageEffects(CBaseCombatCharacter player, int difficulty, SF2_ChaserBossEntity chaser = view_as(-1)) { if (this.DamageEffects == null) {