diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b21bdbcf..13591b75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Ansible Lint # feel free to pick your own name +name: Ansible Lint # feel free to pick your own name on: [push, pull_request] @@ -7,10 +7,10 @@ jobs: name: Ansible Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run ansible-lint uses: ansible/ansible-lint@main -# # optional (see below): + # # optional (see below): with: args: "--exclude sm_plugins watcher" setup_python: "true" @@ -21,12 +21,12 @@ jobs: name: Build Plugins runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup SourcePawn Compiler uses: rumblefrog/setup-sp@master with: - version: "1.12.x" + version: "1.13.x" - name: Ensure plugin builds working-directory: ./roles/sourcemod/files/addons/sourcemod/scripting @@ -36,7 +36,7 @@ jobs: echo -e "\nCompiling $file... to ../plugins/${f}.smx" spcomp -w234 -O2 -v2 -i include $file -o ../plugins/$f.smx done - + echo "===OUT FILES===" ls - echo version = ${{ steps.setup_sp.outputs.plugin-version }} \ No newline at end of file + echo version = ${{ steps.setup_sp.outputs.plugin-version }} diff --git a/.gitmodules b/.gitmodules index abc8dab2..ea703b85 100644 --- a/.gitmodules +++ b/.gitmodules @@ -47,3 +47,6 @@ [submodule "sm_plugins/HalloweenCosmeticEnabler"] path = sm_plugins/HalloweenCosmeticEnabler url = https://github.com/Mikusch/HalloweenCosmeticEnabler +[submodule "sm_plugins/TF2-Dodgeball-Modified"] + path = sm_plugins/TF2-Dodgeball-Modified + url = https://github.com/Silorak/TF2-Dodgeball-Modified.git diff --git a/Makefile b/Makefile deleted file mode 100644 index 58ceedd7..00000000 --- a/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -.PHONY: all pre system deploy - -PLAYBOOK_PATH := ./ -HOSTS := hosts.yml -USER := tf2server -FORKS := 20 -HOSTS_DEV := dev.hosts -HOSTS_PROD := prod.hosts - -all: site - -#format: -# @find ./roles/sourcemod/files/addons/sourcemod/scripting -regex '.*\.\(sp\)' -exec clang-format -style=file -i {} \; - -lint: - @ansible-lint --exclude sm_plugins watcher - -deps: - @ansible-galaxy collection install -r collections/requirements.yml - -adduser: - @ansible-playbook -u root -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/adduser.yml - -srcds: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/srcds.yml - -web: - ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/web.yml --limit metrics - -vpn: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/vpn.yml - -system: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/system.yml - -site: - ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) site.yml - -ping: - @ansible tf2 -m ping $(ARGS) - -update: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/update.yml --limit sao-1.br.uncletopia.com - -game_engine: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) --tags game_engine $(PLAYBOOK_PATH)/srcds.yml - -game_config: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) --tags game_config $(PLAYBOOK_PATH)/srcds.yml - -uncledane: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/uncledane.yml - -bdapi: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/bdapi.yml - -demostats: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/demostats.yml - -tf2bdd: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/tf2bdd.yml - -sentry: - @ansible-playbook -u $(USER) -i $(HOSTS_PROD) --forks $(FORKS) $(PLAYBOOK_PATH)/sentry.yml - -srcds-test: - @ansible-playbook -u $(USER) -i $(HOSTS_DEV) --forks $(FORKS) $(PLAYBOOK_PATH)/srcds.yml - diff --git a/justfile b/justfile new file mode 100644 index 00000000..e66322c3 --- /dev/null +++ b/justfile @@ -0,0 +1,64 @@ +PLAYBOOK_PATH := "./" +HOSTS := "hosts.yml" +USER := "tf2server" +FORKS := "20" +inventory := "prod.hosts" + +all: site + +#format: +# @find ./roles/sourcemod/files/addons/sourcemod/scripting -regex '.*\.\(sp\)' -exec clang-format -style=file -i {} \; + +lint: + @ansible-lint --exclude sm_plugins watcher + +deps: + @ansible-galaxy collection install -r collections/requirements.yml + +adduser: + @ansible-playbook -u root -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/adduser.yml + +srcds: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/srcds.yml + +web: + ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/web.yml --limit metrics + +vpn: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/vpn.yml + +system: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/system.yml + +site: + ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) site.yml + +ping: + @ansible tf2 -m ping $(ARGS) + +update: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/update.yml + +game_engine: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) --tags game_engine $(PLAYBOOK_PATH)/srcds.yml + +game_config: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) --tags game_config $(PLAYBOOK_PATH)/srcds.yml + +uncledane: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/uncledane.yml + +bdapi: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/bdapi.yml + +demostats: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/demostats.yml + +tf2bdd: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/tf2bdd.yml + +sentry: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/sentry.yml + +srcds-test: + @ansible-playbook -u $(USER) -i $(inventory) --forks $(FORKS) $(PLAYBOOK_PATH)/srcds.yml diff --git a/roles/sourcemod/files/addons/sourcemod/gamedata/fov.games.txt b/roles/sourcemod/files/addons/sourcemod/gamedata/fov.games.txt new file mode 100644 index 00000000..5839dd1c --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/gamedata/fov.games.txt @@ -0,0 +1,65 @@ +"Games" +{ + "tf" + { + "Functions" + { + "CBasePlayer::SetFOV" + { + "signature" "CBasePlayer::SetFOV" + "callconv" "thiscall" + "return" "bool" + "this" "entity" + + "arguments" + { + "pRequester" + { + "type" "cbaseentity" + } + "FOV" + { + "type" "int" + } + "zoomRate" + { + "type" "float" + } + "iZoomStart" + { + "type" "int" + } + } + } + "CBasePlayer::SetDefaultFOV" + { + "signature" "CBasePlayer::SetDefaultFOV" + "callconv" "thiscall" + "return" "void" + "this" "entity" + + "arguments" + { + "fov" + { + "type" "int" + } + } + } + } + + "Signatures" + { + "CBasePlayer::SetFOV" + { + "library" "server" + "linux" "@_ZN11CBasePlayer6SetFOVEP11CBaseEntityifi" + } + "CBasePlayer::SetDefaultFOV" + { + "library" "server" + "linux" "@_ZN11CBasePlayer13SetDefaultFOVEi" + } + } + } +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/dodgeball.sp b/roles/sourcemod/files/addons/sourcemod/scripting/dodgeball.sp new file mode 100644 index 00000000..50414d73 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/dodgeball.sp @@ -0,0 +1,563 @@ +// ********************************************************************************* +// PREPROCESSOR +// ********************************************************************************* +#pragma semicolon 1 +#pragma newdecls required + +// ********************************************************************************* +// INCLUDES +// ********************************************************************************* +#include +#include +#include +#include + +#include + +// ********************************************************************************* +// CONSTANTS +// ********************************************************************************* +#define PLUGIN_NAME "[TF2] Dodgeball" +#define PLUGIN_AUTHOR "Damizean, x07x08 continued by Silorak" +#define PLUGIN_VERSION "2.0.2" +#define PLUGIN_CONTACT "https://github.com/Silorak/TF2-Dodgeball-Modified" + +enum Musics +{ + Music_RoundStart, + Music_RoundWin, + Music_RoundLose, + Music_Gameplay, + SizeOfMusicsArray +} + +// ********************************************************************************* +// VARIABLES +// ********************************************************************************* + +// -----<<< Cvars >>>----- +ConVar CvarEnabled; +ConVar CvarEnableCfgFile; +ConVar CvarDisableCfgFile; +ConVar CvarStealPreventionNumber; +ConVar CvarStealPreventionDamage; +ConVar CvarStealDistance; +ConVar CvarDelayPrevention; +ConVar CvarDelayPreventionTime; +ConVar CvarDelayPreventionSpeedup; +ConVar CvarNoTargetRedirectDamage; +ConVar CvarStealMessage; +ConVar CvarDelayMessage; +// New CVar for bounce mechanic +ConVar CvarBounceForceAngle; +ConVar CvarBounceForceScale; + + +// -----<<< Gameplay >>>----- +bool Enabled; +bool RoundStarted; +int RoundCount; +int RocketsFired; +Handle LogicTimer; +float NextSpawnTime; +int LastDeadTeam; +int LastDeadClient; +int PlayerCount; +float TickModifier; +int LastStealer; + +eRocketSteal StealInfo[MAXPLAYERS + 1]; + +// -----<<< Configuration >>>----- +bool MusicEnabled; +bool Music[view_as(SizeOfMusicsArray)]; +char MusicPath[view_as(SizeOfMusicsArray)][PLATFORM_MAX_PATH]; +bool UseWebPlayer; +char WebPlayerUrl[256]; + +// -----<<< Structures >>>----- +// Rockets +bool RocketIsValid[MAX_ROCKETS]; +int RocketEntity[MAX_ROCKETS]; +int RocketTarget[MAX_ROCKETS]; +int RocketClass[MAX_ROCKETS]; +RocketFlags RocketInstanceFlags[MAX_ROCKETS]; +RocketState RocketInstanceState[MAX_ROCKETS]; +float RocketSpeed[MAX_ROCKETS]; +float RocketMphSpeed[MAX_ROCKETS]; +float RocketDirection[MAX_ROCKETS][3]; +int RocketDeflections[MAX_ROCKETS]; +int RocketEventDeflections[MAX_ROCKETS]; +float RocketLastDeflectionTime[MAX_ROCKETS]; +float RocketLastBeepTime[MAX_ROCKETS]; +float LastSpawnTime[MAX_ROCKETS]; +int RocketBounces[MAX_ROCKETS]; +int RocketCount; + +// Classes +char RocketClassName[MAX_ROCKET_CLASSES][16]; +char RocketClassLongName[MAX_ROCKET_CLASSES][32]; +BehaviourTypes RocketClassBehaviour[MAX_ROCKET_CLASSES]; +char RocketClassModel[MAX_ROCKET_CLASSES][PLATFORM_MAX_PATH]; +RocketFlags RocketClassFlags[MAX_ROCKET_CLASSES]; +float RocketClassBeepInterval[MAX_ROCKET_CLASSES]; +char RocketClassSpawnSound[MAX_ROCKET_CLASSES][PLATFORM_MAX_PATH]; +char RocketClassBeepSound[MAX_ROCKET_CLASSES][PLATFORM_MAX_PATH]; +char RocketClassAlertSound[MAX_ROCKET_CLASSES][PLATFORM_MAX_PATH]; +float RocketClassCritChance[MAX_ROCKET_CLASSES]; +float RocketClassDamage[MAX_ROCKET_CLASSES]; +float RocketClassDamageIncrement[MAX_ROCKET_CLASSES]; +float RocketClassSpeed[MAX_ROCKET_CLASSES]; +float RocketClassSpeedIncrement[MAX_ROCKET_CLASSES]; +float RocketClassSpeedLimit[MAX_ROCKET_CLASSES]; +float RocketClassTurnRate[MAX_ROCKET_CLASSES]; +float RocketClassTurnRateIncrement[MAX_ROCKET_CLASSES]; +float RocketClassTurnRateLimit[MAX_ROCKET_CLASSES]; +float RocketClassElevationRate[MAX_ROCKET_CLASSES]; +float RocketClassElevationLimit[MAX_ROCKET_CLASSES]; +float RocketClassRocketsModifier[MAX_ROCKET_CLASSES]; +float RocketClassPlayerModifier[MAX_ROCKET_CLASSES]; +float RocketClassControlDelay[MAX_ROCKET_CLASSES]; +float RocketClassDragTimeMin[MAX_ROCKET_CLASSES]; +float RocketClassDragTimeMax[MAX_ROCKET_CLASSES]; +float RocketClassTargetWeight[MAX_ROCKET_CLASSES]; +DataPack RocketClassCmdsOnSpawn[MAX_ROCKET_CLASSES]; +DataPack RocketClassCmdsOnDeflect[MAX_ROCKET_CLASSES]; +DataPack RocketClassCmdsOnKill[MAX_ROCKET_CLASSES]; +DataPack RocketClassCmdsOnExplode[MAX_ROCKET_CLASSES]; +DataPack RocketClassCmdsOnNoTarget[MAX_ROCKET_CLASSES]; +int RocketClassMaxBounces[MAX_ROCKET_CLASSES]; +float RocketClassBounceScale[MAX_ROCKET_CLASSES]; +float RocketClassCrawlBounceScale[MAX_ROCKET_CLASSES]; +float RocketClassCrawlBounceMaxUp[MAX_ROCKET_CLASSES]; +float RocketClassBounceForceAngle[MAX_ROCKET_CLASSES]; +float RocketClassBounceForceScale[MAX_ROCKET_CLASSES]; +int RocketClassCount; + +// Spawner classes +char SpawnersName[MAX_SPAWNER_CLASSES][32]; +int SpawnersMaxRockets[MAX_SPAWNER_CLASSES]; +float SpawnersInterval[MAX_SPAWNER_CLASSES]; +ArrayList SpawnersChancesTable[MAX_SPAWNER_CLASSES]; +StringMap SpawnersTrie; +int SpawnersCount; + +int CurrentRedSpawn; +int SpawnPointsRedCount; +int SpawnPointsRedClass[MAX_SPAWN_POINTS]; +int SpawnPointsRedEntity[MAX_SPAWN_POINTS]; + +int CurrentBluSpawn; +int SpawnPointsBluCount; +int SpawnPointsBluClass[MAX_SPAWN_POINTS]; +int SpawnPointsBluEntity[MAX_SPAWN_POINTS]; + +int DefaultRedSpawner; +int DefaultBluSpawner; + +// -----<<< Forward handles >>>----- +Handle ForwardOnRocketCreated; +Handle ForwardOnRocketCreatedPre; +Handle ForwardOnRocketDeflect; +Handle ForwardOnRocketDeflectPre; +Handle ForwardOnRocketSteal; +Handle ForwardOnRocketNoTarget; +Handle ForwardOnRocketDelay; +Handle ForwardOnRocketBounce; +Handle ForwardOnRocketBouncePre; +Handle ForwardOnRocketsConfigExecuted; +Handle ForwardOnRocketStateChanged; + +// ********************************************************************************* +// PLUGIN LOGIC (INCLUDES) +// ********************************************************************************* +#include +#include +#include +#include +#include +#include + +// ********************************************************************************* +// PLUGIN INFO & LIFECYCLE +// ********************************************************************************* +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = PLUGIN_NAME, + version = PLUGIN_VERSION, + url = PLUGIN_CONTACT +}; + +public void OnPluginStart() +{ + char modName[32]; GetGameFolderName(modName, sizeof(modName)); + if (!StrEqual(modName, "tf")) SetFailState("This plugin is only for Team Fortress 2."); + + LoadTranslations("tfdb.phrases.txt"); + + CreateConVar("tf_dodgeball_version", PLUGIN_VERSION, PLUGIN_NAME, FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_SPONLY|FCVAR_DONTRECORD); + CvarEnabled = CreateConVar("tf_dodgeball_enabled", "1", "Enable Dodgeball on TFDB maps?", _, true, 0.0, true, 1.0); + CvarEnableCfgFile = CreateConVar("tf_dodgeball_enablecfg", "sourcemod/dodgeball_enable.cfg", "Config file to execute when enabling the Dodgeball game mode."); + CvarDisableCfgFile = CreateConVar("tf_dodgeball_disablecfg", "sourcemod/dodgeball_disable.cfg", "Config file to execute when disabling the Dodgeball game mode."); + CvarStealPreventionNumber = CreateConVar("tf_dodgeball_sp_number", "3", "How many steals before you get slayed?", _, true, 0.0, false); + CvarStealPreventionDamage = CreateConVar("tf_dodgeball_sp_damage", "0", "Reduce all damage on stolen rockets?", _, true, 0.0, true, 1.0); + CvarStealDistance = CreateConVar("tf_dodgeball_sp_distance", "48.0", "The distance between players for a steal to register.", _, true, 0.0, false); + CvarDelayPrevention = CreateConVar("tf_dodgeball_delay_prevention", "1", "Enable delay prevention?", _, true, 0.0, true, 1.0); + CvarDelayPreventionTime = CreateConVar("tf_dodgeball_dp_time", "5", "How much time (in seconds) before delay prevention activates?", _, true, 0.0, false); + CvarDelayPreventionSpeedup = CreateConVar("tf_dodgeball_dp_speedup", "100", "How much speed (in hammer units per second) should the rocket gain when delayed?", _, true, 0.0, false); + CvarNoTargetRedirectDamage = CreateConVar("tf_dodgeball_redirect_damage", "1", "Reduce all damage when a rocket has an invalid target?", _, true, 0.0, true, 1.0); + CvarStealMessage = CreateConVar("tf_dodgeball_sp_message", "1", "Display the steal message(s)?", _, true, 0.0, true, 1.0); + CvarDelayMessage = CreateConVar("tf_dodgeball_dp_message", "1", "Display the delay message(s)?", _, true, 0.0, true, 1.0); + CvarBounceForceAngle = CreateConVar("tf_dodgeball_bounce_force_angle", "45.0", "Minimum downward angle (pitch) for a player to trigger a forced bounce.", _, true, 0.0, true, 90.0); + CvarBounceForceScale = CreateConVar("tf_dodgeball_bounce_force_scale", "1.5", "How much stronger a player-forced bounce is. (Multiplier)", _, true, 1.0); + + + SpawnersTrie = new StringMap(); + TickModifier = 0.1 / GetTickInterval(); + + AddTempEntHook("TFExplosion", OnTFExplosion); + + RegisterCommands(); +} + +public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] strError, int iErrMax) +{ + CreateNative("TFDB_IsValidRocket", Native_IsValidRocket); + CreateNative("TFDB_FindRocketByEntity", Native_FindRocketByEntity); + CreateNative("TFDB_IsDodgeballEnabled", Native_IsDodgeballEnabled); + CreateNative("TFDB_GetRocketEntity", Native_GetRocketEntity); + CreateNative("TFDB_GetRocketFlags", Native_GetRocketFlags); + CreateNative("TFDB_SetRocketFlags", Native_SetRocketFlags); + CreateNative("TFDB_GetRocketTarget", Native_GetRocketTarget); + CreateNative("TFDB_SetRocketTarget", Native_SetRocketTarget); + CreateNative("TFDB_GetRocketEventDeflections", Native_GetRocketEventDeflections); + CreateNative("TFDB_SetRocketEventDeflections", Native_SetRocketEventDeflections); + CreateNative("TFDB_GetRocketDeflections", Native_GetRocketDeflections); + CreateNative("TFDB_SetRocketDeflections", Native_SetRocketDeflections); + CreateNative("TFDB_GetRocketClass", Native_GetRocketClass); + CreateNative("TFDB_SetRocketClass", Native_SetRocketClass); + CreateNative("TFDB_GetRocketClassCount", Native_GetRocketClassCount); + CreateNative("TFDB_GetRocketClassBehaviour", Native_GetRocketClassBehaviour); + CreateNative("TFDB_SetRocketClassBehaviour", Native_SetRocketClassBehaviour); + CreateNative("TFDB_GetRocketClassFlags", Native_GetRocketClassFlags); + CreateNative("TFDB_SetRocketClassFlags", Native_SetRocketClassFlags); + CreateNative("TFDB_GetRocketClassDamage", Native_GetRocketClassDamage); + CreateNative("TFDB_SetRocketClassDamage", Native_SetRocketClassDamage); + CreateNative("TFDB_GetRocketClassDamageIncrement", Native_GetRocketClassDamageIncrement); + CreateNative("TFDB_SetRocketClassDamageIncrement", Native_SetRocketClassDamageIncrement); + CreateNative("TFDB_GetRocketClassSpeed", Native_GetRocketClassSpeed); + CreateNative("TFDB_SetRocketClassSpeed", Native_SetRocketClassSpeed); + CreateNative("TFDB_GetRocketClassSpeedIncrement", Native_GetRocketClassSpeedIncrement); + CreateNative("TFDB_SetRocketClassSpeedIncrement", Native_SetRocketClassSpeedIncrement); + CreateNative("TFDB_GetRocketClassSpeedLimit", Native_GetRocketClassSpeedLimit); + CreateNative("TFDB_SetRocketClassSpeedLimit", Native_SetRocketClassSpeedLimit); + CreateNative("TFDB_GetRocketClassTurnRate", Native_GetRocketClassTurnRate); + CreateNative("TFDB_SetRocketClassTurnRate", Native_SetRocketClassTurnRate); + CreateNative("TFDB_GetRocketClassTurnRateIncrement", Native_GetRocketClassTurnRateIncrement); + CreateNative("TFDB_SetRocketClassTurnRateIncrement", Native_SetRocketClassTurnRateIncrement); + CreateNative("TFDB_GetRocketClassTurnRateLimit", Native_GetRocketClassTurnRateLimit); + CreateNative("TFDB_SetRocketClassTurnRateLimit", Native_SetRocketClassTurnRateLimit); + CreateNative("TFDB_GetRocketClassElevationRate", Native_GetRocketClassElevationRate); + CreateNative("TFDB_SetRocketClassElevationRate", Native_SetRocketClassElevationRate); + CreateNative("TFDB_GetRocketClassElevationLimit", Native_GetRocketClassElevationLimit); + CreateNative("TFDB_SetRocketClassElevationLimit", Native_SetRocketClassElevationLimit); + CreateNative("TFDB_GetRocketClassRocketsModifier", Native_GetRocketClassRocketsModifier); + CreateNative("TFDB_SetRocketClassRocketsModifier", Native_SetRocketClassRocketsModifier); + CreateNative("TFDB_GetRocketClassPlayerModifier", Native_GetRocketClassPlayerModifier); + CreateNative("TFDB_SetRocketClassPlayerModifier", Native_SetRocketClassPlayerModifier); + CreateNative("TFDB_GetRocketClassControlDelay", Native_GetRocketClassControlDelay); + CreateNative("TFDB_SetRocketClassControlDelay", Native_SetRocketClassControlDelay); + CreateNative("TFDB_GetRocketClassDragTimeMin", Native_GetRocketClassDragTimeMin); + CreateNative("TFDB_SetRocketClassDragTimeMin", Native_SetRocketClassDragTimeMin); + CreateNative("TFDB_GetRocketClassDragTimeMax", Native_GetRocketClassDragTimeMax); + CreateNative("TFDB_SetRocketClassDragTimeMax", Native_SetRocketClassDragTimeMax); + CreateNative("TFDB_SetRocketClassCount", Native_SetRocketClassCount); + CreateNative("TFDB_SetRocketEntity", Native_SetRocketEntity); + CreateNative("TFDB_GetRocketClassMaxBounces", Native_GetRocketClassMaxBounces); + CreateNative("TFDB_SetRocketClassMaxBounces", Native_SetRocketClassMaxBounces); + CreateNative("TFDB_GetSpawnersName", Native_GetSpawnersName); + CreateNative("TFDB_SetSpawnersName", Native_SetSpawnersName); + CreateNative("TFDB_GetSpawnersMaxRockets", Native_GetSpawnersMaxRockets); + CreateNative("TFDB_SetSpawnersMaxRockets", Native_SetSpawnersMaxRockets); + CreateNative("TFDB_GetSpawnersInterval", Native_GetSpawnersInterval); + CreateNative("TFDB_SetSpawnersInterval", Native_SetSpawnersInterval); + CreateNative("TFDB_GetSpawnersChancesTable", Native_GetSpawnersChancesTable); + CreateNative("TFDB_SetSpawnersChancesTable", Native_SetSpawnersChancesTable); + CreateNative("TFDB_GetSpawnersCount", Native_GetSpawnersCount); + CreateNative("TFDB_SetSpawnersCount", Native_SetSpawnersCount); + CreateNative("TFDB_GetCurrentRedSpawn", Native_GetCurrentRedSpawn); + CreateNative("TFDB_SetCurrentRedSpawn", Native_SetCurrentRedSpawn); + CreateNative("TFDB_GetSpawnPointsRedCount", Native_GetSpawnPointsRedCount); + CreateNative("TFDB_SetSpawnPointsRedCount", Native_SetSpawnPointsRedCount); + CreateNative("TFDB_GetSpawnPointsRedClass", Native_GetSpawnPointsRedClass); + CreateNative("TFDB_SetSpawnPointsRedClass", Native_SetSpawnPointsRedClass); + CreateNative("TFDB_GetSpawnPointsRedEntity", Native_GetSpawnPointsRedEntity); + CreateNative("TFDB_SetSpawnPointsRedEntity", Native_SetSpawnPointsRedEntity); + CreateNative("TFDB_GetCurrentBluSpawn", Native_GetCurrentBluSpawn); + CreateNative("TFDB_SetCurrentBluSpawn", Native_SetCurrentBluSpawn); + CreateNative("TFDB_GetSpawnPointsBluCount", Native_GetSpawnPointsBluCount); + CreateNative("TFDB_SetSpawnPointsBluCount", Native_SetSpawnPointsBluCount); + CreateNative("TFDB_GetSpawnPointsBluClass", Native_GetSpawnPointsBluClass); + CreateNative("TFDB_SetSpawnPointsBluClass", Native_SetSpawnPointsBluClass); + CreateNative("TFDB_GetSpawnPointsBluEntity", Native_GetSpawnPointsBluEntity); + CreateNative("TFDB_SetSpawnPointsBluEntity", Native_SetSpawnPointsBluEntity); + CreateNative("TFDB_GetRoundStarted", Native_GetRoundStarted); + CreateNative("TFDB_GetRoundCount", Native_GetRoundCount); + CreateNative("TFDB_GetRocketsFired", Native_GetRocketsFired); + CreateNative("TFDB_GetNextSpawnTime", Native_GetNextSpawnTime); + CreateNative("TFDB_SetNextSpawnTime", Native_SetNextSpawnTime); + CreateNative("TFDB_GetLastDeadTeam", Native_GetLastDeadTeam); + CreateNative("TFDB_GetLastDeadClient", Native_GetLastDeadClient); + CreateNative("TFDB_GetLastStealer", Native_GetLastStealer); + CreateNative("TFDB_GetRocketSpeed", Native_GetRocketSpeed); + CreateNative("TFDB_SetRocketSpeed", Native_SetRocketSpeed); + CreateNative("TFDB_GetRocketMphSpeed", Native_GetRocketMphSpeed); + CreateNative("TFDB_SetRocketMphSpeed", Native_SetRocketMphSpeed); + CreateNative("TFDB_GetRocketDirection", Native_GetRocketDirection); + CreateNative("TFDB_SetRocketDirection", Native_SetRocketDirection); + CreateNative("TFDB_GetRocketLastDeflectionTime", Native_GetRocketLastDeflectionTime); + CreateNative("TFDB_SetRocketLastDeflectionTime", Native_SetRocketLastDeflectionTime); + CreateNative("TFDB_GetRocketLastBeepTime", Native_GetRocketLastBeepTime); + CreateNative("TFDB_SetRocketLastBeepTime", Native_SetRocketLastBeepTime); + CreateNative("TFDB_GetRocketCount", Native_GetRocketCount); + CreateNative("TFDB_GetLastSpawnTime", Native_GetLastSpawnTime); + CreateNative("TFDB_GetRocketBounces", Native_GetRocketBounces); + CreateNative("TFDB_SetRocketBounces", Native_SetRocketBounces); + CreateNative("TFDB_GetRocketClassName", Native_GetRocketClassName); + CreateNative("TFDB_SetRocketClassName", Native_SetRocketClassName); + CreateNative("TFDB_GetRocketClassLongName", Native_GetRocketClassLongName); + CreateNative("TFDB_SetRocketClassLongName", Native_SetRocketClassLongName); + CreateNative("TFDB_GetRocketClassModel", Native_GetRocketClassModel); + CreateNative("TFDB_SetRocketClassModel", Native_SetRocketClassModel); + CreateNative("TFDB_GetRocketClassBeepInterval", Native_GetRocketClassBeepInterval); + CreateNative("TFDB_SetRocketClassBeepInterval", Native_SetRocketClassBeepInterval); + CreateNative("TFDB_GetRocketClassSpawnSound", Native_GetRocketClassSpawnSound); + CreateNative("TFDB_SetRocketClassSpawnSound", Native_SetRocketClassSpawnSound); + CreateNative("TFDB_GetRocketClassBeepSound", Native_GetRocketClassBeepSound); + CreateNative("TFDB_SetRocketClassBeepSound", Native_SetRocketClassBeepSound); + CreateNative("TFDB_GetRocketClassAlertSound", Native_GetRocketClassAlertSound); + CreateNative("TFDB_SetRocketClassAlertSound", Native_SetRocketClassAlertSound); + CreateNative("TFDB_GetRocketClassCritChance", Native_GetRocketClassCritChance); + CreateNative("TFDB_SetRocketClassCritChance", Native_SetRocketClassCritChance); + CreateNative("TFDB_GetRocketClassTargetWeight", Native_GetRocketClassTargetWeight); + CreateNative("TFDB_SetRocketClassTargetWeight", Native_SetRocketClassTargetWeight); + CreateNative("TFDB_GetRocketClassCmdsOnSpawn", Native_GetRocketClassCmdsOnSpawn); + CreateNative("TFDB_SetRocketClassCmdsOnSpawn", Native_SetRocketClassCmdsOnSpawn); + CreateNative("TFDB_GetRocketClassCmdsOnDeflect", Native_GetRocketClassCmdsOnDeflect); + CreateNative("TFDB_SetRocketClassCmdsOnDeflect", Native_SetRocketClassCmdsOnDeflect); + CreateNative("TFDB_GetRocketClassCmdsOnKill", Native_GetRocketClassCmdsOnKill); + CreateNative("TFDB_SetRocketClassCmdsOnKill", Native_SetRocketClassCmdsOnKill); + CreateNative("TFDB_GetRocketClassCmdsOnExplode", Native_GetRocketClassCmdsOnExplode); + CreateNative("TFDB_SetRocketClassCmdsOnExplode", Native_SetRocketClassCmdsOnExplode); + CreateNative("TFDB_GetRocketClassCmdsOnNoTarget", Native_GetRocketClassCmdsOnNoTarget); + CreateNative("TFDB_SetRocketClassCmdsOnNoTarget", Native_SetRocketClassCmdsOnNoTarget); + CreateNative("TFDB_GetRocketClassBounceScale", Native_GetRocketClassBounceScale); + CreateNative("TFDB_SetRocketClassBounceScale", Native_SetRocketClassBounceScale); + CreateNative("TFDB_CreateRocket", Native_CreateRocket); + CreateNative("TFDB_DestroyRocket", Native_DestroyRocket); + CreateNative("TFDB_DestroyRockets", Native_DestroyRockets); + CreateNative("TFDB_DestroyRocketClasses", Native_DestroyRocketClasses); + CreateNative("TFDB_DestroySpawners", Native_DestroySpawners); + CreateNative("TFDB_ParseConfigurations", Native_ParseConfigurations); + CreateNative("TFDB_PopulateSpawnPoints", Native_PopulateSpawnPoints); + CreateNative("TFDB_HomingRocketThink", Native_HomingRocketThink); + CreateNative("TFDB_RocketLegacyThink", Native_RocketLegacyThink); + CreateNative("TFDB_GetRocketState", Native_GetRocketState); + CreateNative("TFDB_SetRocketState", Native_SetRocketState); + CreateNative("TFDB_GetStealInfo", Native_GetStealInfo); + CreateNative("TFDB_SetStealInfo", Native_SetStealInfo); + + SetupForwards(); + + RegPluginLibrary("tfdb"); + + return APLRes_Success; +} + +void SetupForwards() +{ + ForwardOnRocketCreated = CreateGlobalForward("TFDB_OnRocketCreated", ET_Ignore, Param_Cell, Param_Cell); + ForwardOnRocketCreatedPre = CreateGlobalForward("TFDB_OnRocketCreatedPre", ET_Event, Param_Cell, Param_CellByRef, Param_CellByRef); + ForwardOnRocketDeflect = CreateGlobalForward("TFDB_OnRocketDeflect", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); + ForwardOnRocketDeflectPre = CreateGlobalForward("TFDB_OnRocketDeflectPre", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_CellByRef); + ForwardOnRocketSteal = CreateGlobalForward("TFDB_OnRocketSteal", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + ForwardOnRocketNoTarget = CreateGlobalForward("TFDB_OnRocketNoTarget", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); + ForwardOnRocketDelay = CreateGlobalForward("TFDB_OnRocketDelay", ET_Ignore, Param_Cell, Param_Cell); + ForwardOnRocketBounce = CreateGlobalForward("TFDB_OnRocketBounce", ET_Ignore, Param_Cell, Param_Cell); + ForwardOnRocketBouncePre = CreateGlobalForward("TFDB_OnRocketBouncePre", ET_Event, Param_Cell, Param_Cell, Param_Array, Param_Array); + ForwardOnRocketsConfigExecuted = CreateGlobalForward("TFDB_OnRocketsConfigExecuted", ET_Ignore, Param_String); + ForwardOnRocketStateChanged = CreateGlobalForward("TFDB_OnRocketStateChanged", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); +} + +public void OnConfigsExecuted() +{ + if (!(CvarEnabled.BoolValue && IsDodgeBallMap())) return; + + EnableDodgeBall(); +} + +public void OnMapEnd() +{ + DisableDodgeBall(); +} + +void Forward_OnRocketCreated(int index, int entity) +{ + Call_StartForward(ForwardOnRocketCreated); + Call_PushCell(index); + Call_PushCell(entity); + Call_Finish(); +} + +Action Forward_OnRocketCreatedPre(int index, int &rocketClass, RocketFlags &flags) +{ + Action result; + + Call_StartForward(ForwardOnRocketCreatedPre); + Call_PushCell(index); + Call_PushCellRef(rocketClass); + Call_PushCellRef(flags); + Call_Finish(result); + + return result; +} + +void Forward_OnRocketDeflect(int index, int entity, int owner) +{ + Call_StartForward(ForwardOnRocketDeflect); + Call_PushCell(index); + Call_PushCell(entity); + Call_PushCell(owner); + Call_Finish(); +} + +Action Forward_OnRocketDeflectPre(int index, int entity, int owner, int &target) +{ + Action result; + + Call_StartForward(ForwardOnRocketDeflectPre); + Call_PushCell(index); + Call_PushCell(entity); + Call_PushCell(owner); + Call_PushCellRef(target); + Call_Finish(result); + + return result; +} + +void Forward_OnRocketSteal(int index, int owner, int target, int stealCount) +{ + Call_StartForward(ForwardOnRocketSteal); + Call_PushCell(index); + Call_PushCell(owner); + Call_PushCell(target); + Call_PushCell(stealCount); + Call_Finish(); +} + +void Forward_OnRocketNoTarget(int index, int target, int owner) +{ + Call_StartForward(ForwardOnRocketNoTarget); + Call_PushCell(index); + Call_PushCell(target); + Call_PushCell(owner); + Call_Finish(); +} + +void Forward_OnRocketDelay(int index, int target) +{ + Call_StartForward(ForwardOnRocketDelay); + Call_PushCell(index); + Call_PushCell(target); + Call_Finish(); +} + +void Forward_OnRocketBounce(int index, int entity) +{ + Call_StartForward(ForwardOnRocketBounce); + Call_PushCell(index); + Call_PushCell(entity); + Call_Finish(); +} + +Action Forward_OnRocketBouncePre(int index, int entity, float angles[3], float velocity[3]) +{ + Action result; + + Call_StartForward(ForwardOnRocketBouncePre); + Call_PushCell(index); + Call_PushCell(entity); + Call_PushArrayEx(angles, sizeof(angles), SM_PARAM_COPYBACK); + Call_PushArrayEx(velocity, sizeof(velocity), SM_PARAM_COPYBACK); + Call_Finish(result); + + return result; +} + +void Forward_OnRocketsConfigExecuted(const char[] configFile) +{ + Call_StartForward(ForwardOnRocketsConfigExecuted); + Call_PushString(configFile); + Call_Finish(); +} + +void Forward_OnRocketStateChanged(int index, RocketState state, RocketState newState) +{ + Call_StartForward(ForwardOnRocketStateChanged); + Call_PushCell(index); + Call_PushCell(state); + Call_PushCell(newState); + Call_Finish(); +} + +void Internal_SetRocketState(int index, RocketState newState) +{ + RocketState state = RocketInstanceState[index]; + if (state == newState) + { + return; + } + + RocketInstanceState[index] = newState; + Forward_OnRocketStateChanged(index, state, newState); +} + +public bool MLTargetFilterStealer(const char[] pattern, ArrayList clients) +{ + bool reverse = (StrContains(pattern, "!", false) == 1); + for (int client = 1; client <= MaxClients; client++) + { + if (!(IsClientInGame(client) && (clients.FindValue(client) == -1))) continue; + + if (client == LastStealer) + { + if (!reverse) + { + clients.Push(client); + } + } + else if (reverse) + { + clients.Push(client); + } + } + + return !!clients.Length; +} + +#if defined _multicolors_included && defined _more_colors_included && defined _colors_included +stock void CSkipNextClient(int client) +{ + if (!IsSource2009()) + { + C_SkipNextClient(client); + } + else + { + MC_SkipNextClient(client); + } +} + +#endif diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/fov.sp b/roles/sourcemod/files/addons/sourcemod/scripting/fov.sp new file mode 100644 index 00000000..9f3ef2ad --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/fov.sp @@ -0,0 +1,237 @@ +/** + * Copyright Andrew Betson. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +ConVar sv_fov_min; +ConVar sv_fov_max; +int g_nFOVOverride[ MAXPLAYERS + 1 ] = { -1, ... }; +Handle g_hCookie_FOV; + +DHookSetup g_hDetour_BasePlayer_SetFOV; +DHookSetup g_hDetour_BasePlayer_SetDefaultFOV; + +public Plugin myinfo = +{ + name = "[TF2] FOV", + author = "Andrew \"andrewb\" Betson", + description = "Allow players to set their FOV beyond the arbitrary limit of the fov_desired cvar.", + version = "1.0.2", + url = "https://github.com/AndrewBetson/TF-FOV" +}; + +public void OnPluginStart() +{ + LoadTranslations( "common.phrases" ); + LoadTranslations( "fov.phrases" ); + + sv_fov_min = CreateConVar( "sv_fov_min", "75.0", "Minimum FOV", FCVAR_NOTIFY, true, 10.0, true, 998.0 ); + sv_fov_min.AddChangeHook( ConVar_FOV_MinMax ); + + sv_fov_max = CreateConVar( "sv_fov_max", "120.0", "Maximum FOV", FCVAR_NOTIFY, true, 11.0, true, 999.0 ); + sv_fov_max.AddChangeHook( ConVar_FOV_MinMax ); + + RegConsoleCmd( "sm_fov", Cmd_FOV, "Set calling players FOV" ); + RegConsoleCmd( "sm_fov_clear", Cmd_FOV_Clear, "Clear calling players FOV preference" ); + + AutoExecConfig( true, "fov" ); + + g_hCookie_FOV = RegClientCookie( "fov_override", "FOV", CookieAccess_Public ); + + Handle hGameData = LoadGameConfigFile( "fov.games" ); + if ( !hGameData ) + { + SetFailState( "Failed to load fov gamedata." ); + } + + g_hDetour_BasePlayer_SetFOV = DHookCreateFromConf( hGameData, "CBasePlayer::SetFOV" ); + g_hDetour_BasePlayer_SetDefaultFOV = DHookCreateFromConf( hGameData, "CBasePlayer::SetDefaultFOV" ); + + delete hGameData; + + if ( !DHookEnableDetour( g_hDetour_BasePlayer_SetFOV, false, Detour_BasePlayer_SetFOV ) ) + { + SetFailState( "Failed to detour CBasePlayer::SetFOV, tell Andrew to update the signatures." ); + } + + if ( !DHookEnableDetour( g_hDetour_BasePlayer_SetDefaultFOV, false, Detour_BasePlayer_SetDefaultFOV ) ) + { + SetFailState( "Failed to detour CBasePlayer::SetDefaultFOV, tell Andrew to update the signatures." ); + } + + // Late-load/reload support. + for ( int i = 1; i <= MaxClients; i++ ) + { + if ( IsClientInGame( i ) ) + { + if ( AreClientCookiesCached( i ) ) + { + OnClientCookiesCached( i ); + } + } + } +} + +public void OnClientCookiesCached( int nClientIdx ) +{ + if ( nClientIdx <= 0 ) + { + return; + } + + char szFOV[ 4 ]; + GetClientCookie( nClientIdx, g_hCookie_FOV, szFOV, 4 ); + + if ( szFOV[ 0 ] == EOS ) + { + g_nFOVOverride[ nClientIdx ] = -1; + return; + } + + g_nFOVOverride[ nClientIdx ] = StringToInt( szFOV ); +} + +public void OnClientDisconnect( int nClientIdx ) +{ + g_nFOVOverride[ nClientIdx ] = -1; +} + +public Action Cmd_FOV( int nClientIdx, int nNumArgs ) +{ + if ( nNumArgs < 1 ) + { + CReplyToCommand( nClientIdx, "%t", "FOV_Usage" ); + return Plugin_Continue; + } + + int nNewFOV; + +#if SOURCEMOD_V_MINOR >= 11 + if ( !GetCmdArgIntEx( 1, nNewFOV ) ) + { + CReplyToCommand( nClientIdx, "%t", "FOV_MustBeANumber" ); + return Plugin_Continue; + } +#else + char szSM110Hack[ 4 ]; + GetCmdArg( 1, szSM110Hack, 4 ); + + nNewFOV = StringToInt( szSM110Hack ); +#endif // SOURCEMOD_V_MINOR == 11 + + if ( nNewFOV > sv_fov_max.IntValue || nNewFOV < sv_fov_min.IntValue ) + { + CReplyToCommand( nClientIdx, "%t", "FOV_MustBeWithinRange", sv_fov_min.IntValue, sv_fov_max.IntValue ); + return Plugin_Continue; + } + + g_nFOVOverride[ nClientIdx ] = nNewFOV; + + char szNewFOV[ 4 ]; + IntToString( nNewFOV, szNewFOV, 4 ); + + SetClientCookie( nClientIdx, g_hCookie_FOV, szNewFOV ); + SetClientFOV( nClientIdx ); + + return Plugin_Continue; +} + +public Action Cmd_FOV_Clear( int nClientIdx, int nNumArgs ) +{ + g_nFOVOverride[ nClientIdx ] = -1; + + char szNullFOV[ 4 ]; + IntToString( -1, szNullFOV, 4 ); + + SetClientCookie( nClientIdx, g_hCookie_FOV, szNullFOV ); + + return Plugin_Continue; +} + +public void ConVar_FOV_MinMax( ConVar hConVar, const char[] szOldValue, const char[] szNewValue ) +{ + int nNewValue = StringToInt( szNewValue ); + + for ( int i = 1; i <= MaxClients; i++ ) + { + if ( IsClientInGame( i ) ) + { + if ( ( g_nFOVOverride[ i ] > nNewValue && hConVar == sv_fov_max ) || ( g_nFOVOverride[ i ] < nNewValue && hConVar == sv_fov_min ) ) + { + g_nFOVOverride[ i ] = nNewValue; + SetClientFOV( i ); + } + } + } +} + +public MRESReturn Detour_BasePlayer_SetFOV( int pBasePlayer, DHookReturn hReturn, DHookParam hParams ) +{ + if ( g_nFOVOverride[ pBasePlayer ] == -1 ) + { + return MRES_Ignored; + } + + RequestFrame( Frame_SetFOV, pBasePlayer ); + + return MRES_Ignored; +} + +public MRESReturn Detour_BasePlayer_SetDefaultFOV( int pBasePlayer, DHookParam hParams ) +{ + if ( g_nFOVOverride[ pBasePlayer ] == -1 ) + { + return MRES_Ignored; + } + + RequestFrame( Frame_SetFOV, pBasePlayer ); + + return MRES_Ignored; +} + +void Frame_SetFOV( any aData ) +{ + int pBasePlayer = view_as< int >( aData ); + + if ( TF2_IsPlayerInCondition( pBasePlayer, TFCond_Zoomed ) ) + { + return; + } + + SetClientFOV( pBasePlayer ); +} + +void SetClientFOV( int nClientIdx ) +{ + int nTargetFOV = g_nFOVOverride[ nClientIdx ]; + + // Clamp the target FOV to the min-max range. + if ( nTargetFOV > sv_fov_max.IntValue ) nTargetFOV = sv_fov_max.IntValue; + if ( nTargetFOV < sv_fov_min.IntValue ) nTargetFOV = sv_fov_min.IntValue; + + SetEntProp( nClientIdx, Prop_Send, "m_iFOV", nTargetFOV ); + SetEntProp( nClientIdx, Prop_Send, "m_iDefaultFOV", nTargetFOV ); +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/colors.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/colors.inc index 45fd1ef2..170ec97f 100644 --- a/roles/sourcemod/files/addons/sourcemod/scripting/include/colors.inc +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/colors.inc @@ -1,10 +1,11 @@ /************************************************************************** * * * Colored Chat Functions * - * Author: exvel * - * Version: 1.0.5 * + * Author: exvel, Editor: Popoklopsi, Powerlord, Bara * + * Version: 2.0.0-MC * * * **************************************************************************/ + #if defined _colors_included #endinput @@ -12,33 +13,47 @@ #define _colors_included #define MAX_MESSAGE_LENGTH 250 -#define MAX_COLORS 6 +#define MAX_COLORS 18 #define SERVER_INDEX 0 #define NO_INDEX -1 #define NO_PLAYER -2 -enum Colors -{ +enum C_Colors { Color_Default = 0, + Color_Darkred, Color_Green, Color_Lightgreen, Color_Red, Color_Blue, - Color_Olive + Color_Olive, + Color_Lime, + Color_Lightred, + Color_Purple, + Color_Grey, + Color_Yellow, + Color_Orange, + Color_Bluegrey, + Color_Lightblue, + Color_Darkblue, + Color_Grey2, + Color_Orchid, + Color_Lightred2 } -/* Colors' properties */ -new String:CTag[][] = {"{default}", "{green}", "{lightgreen}", "{red}", "{blue}", "{olive}"}; -new String:CTagCode[][] = {"\x01", "\x04", "\x03", "\x03", "\x03", "\x05"}; -new bool:CTagReqSayText2[] = {false, false, true, true, true, false}; -new bool:CEventIsHooked = false; -new bool:CSkipList[MAXPLAYERS+1] = {false,...}; +/* C_Colors' properties */ +char C_Tag[][] = {"{default}", "{darkred}", "{green}", "{lightgreen}", "{red}", "{blue}", "{olive}", "{lime}", "{lightred}", "{purple}", "{grey}", "{yellow}", "{orange}", "{bluegrey}", "{lightblue}", "{darkblue}", "{grey2}", "{orchid}", "{lightred2}"}; +char C_TagCode[][] = {"\x01", "\x02", "\x04", "\x03", "\x03", "\x03", "\x05", "\x06", "\x07", "\x03", "\x08", "\x09", "\x10", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F"}; +bool C_TagReqSayText2[] = {false, false, false, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool C_EventIsHooked; +bool C_SkipList[MAXPLAYERS+1]; /* Game default profile */ -new bool:CProfile_Colors[] = {true, true, false, false, false, false}; -new CProfile_TeamIndex[] = {NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX}; -new bool:CProfile_SayText2 = false; +bool C_Profile_Colors[] = {true, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +int C_Profile_TeamIndex[] = {NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX}; +bool C_Profile_SayText2; + +static ConVar sm_show_activity; /** * Prints a message to a specific client in the chat area. @@ -50,28 +65,91 @@ new bool:CProfile_SayText2 = false; * * On error/Errors: If the client is not connected an error will be thrown. */ -stock CPrintToChat(client, const String:szMessage[], any:...) -{ - if (client <= 0 || client > MaxClients) +stock void C_PrintToChat(int client, const char[] szMessage, any ...) { + if (client <= 0 || client > MaxClients) { ThrowError("Invalid client index %d", client); - - if (!IsClientInGame(client)) + } + + if (!IsClientInGame(client)) { ThrowError("Client %d is not in game", client); - - decl String:szBuffer[MAX_MESSAGE_LENGTH]; - decl String:szCMessage[MAX_MESSAGE_LENGTH]; + } + + char szBuffer[MAX_MESSAGE_LENGTH]; + char szCMessage[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage); VFormat(szCMessage, sizeof(szCMessage), szBuffer, 3); - - new index = CFormat(szCMessage, sizeof(szCMessage)); - if (index == NO_INDEX) - { - PrintToChat(client, szCMessage); + + int index = C_Format(szCMessage, sizeof(szCMessage)); + + if (index == NO_INDEX) { + PrintToChat(client, "%s", szCMessage); + } + else { + C_SayText2(client, index, szCMessage); + } +} + +/** + * Reples to a message in a command. A client index of 0 will use PrintToServer(). + * If the command was from the console, PrintToConsole() is used. If the command was from chat, C_PrintToChat() is used. + * Supports color tags. + * + * @param client Client index, or 0 for server. + * @param szMessage Formatting rules. + * @param ... Variable number of format parameters. + * @return No return + * + * On error/Errors: If the client is not connected or invalid. + */ +stock void C_ReplyToCommand(int client, const char[] szMessage, any ...) { + char szCMessage[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(szCMessage, sizeof(szCMessage), szMessage, 3); + + if (client == 0) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToServer("%s", szCMessage); + } + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToConsole(client, "%s", szCMessage); + } + else { + C_PrintToChat(client, "%s", szCMessage); + } +} + +/** + * Reples to a message in a command. A client index of 0 will use PrintToServer(). + * If the command was from the console, PrintToConsole() is used. If the command was from chat, C_PrintToChat() is used. + * Supports color tags. + * + * @param client Client index, or 0 for server. + * @param author Author index whose color will be used for teamcolor tag. + * @param szMessage Formatting rules. + * @param ... Variable number of format parameters. + * @return No return + * + * On error/Errors: If the client is not connected or invalid. + */ +stock void C_ReplyToCommandEx(int client, int author, const char[] szMessage, any ...) { + char szCMessage[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(szCMessage, sizeof(szCMessage), szMessage, 4); + + if (client == 0) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToServer("%s", szCMessage); + } + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + C_RemoveTags(szCMessage, sizeof(szCMessage)); + PrintToConsole(client, "%s", szCMessage); } - else - { - CSayText2(client, index, szCMessage); + else { + C_PrintToChatEx(client, author, "%s", szCMessage); } } @@ -83,20 +161,18 @@ stock CPrintToChat(client, const String:szMessage[], any:...) * @param szMessage Message (formatting rules) * @return No return */ -stock CPrintToChatAll(const String:szMessage[], any:...) -{ - decl String:szBuffer[MAX_MESSAGE_LENGTH]; - - for (new i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && !IsFakeClient(i) && !CSkipList[i]) - { +stock void C_PrintToChatAll(const char[] szMessage, any ...) { + char szBuffer[MAX_MESSAGE_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && !IsFakeClient(i) && !C_SkipList[i]) { SetGlobalTransTarget(i); VFormat(szBuffer, sizeof(szBuffer), szMessage, 2); - CPrintToChat(i, szBuffer); + + C_PrintToChat(i, "%s", szBuffer); } - - CSkipList[i] = false; + + C_SkipList[i] = false; } } @@ -111,31 +187,34 @@ stock CPrintToChatAll(const String:szMessage[], any:...) * * On error/Errors: If the client or author are not connected an error will be thrown. */ -stock CPrintToChatEx(client, author, const String:szMessage[], any:...) -{ - if (client <= 0 || client > MaxClients) +stock void C_PrintToChatEx(int client, int author, const char[] szMessage, any ...) { + if (client <= 0 || client > MaxClients) { ThrowError("Invalid client index %d", client); - - if (!IsClientInGame(client)) + } + + if (!IsClientInGame(client)) { ThrowError("Client %d is not in game", client); - - if (author < 0 || author > MaxClients) + } + + if (author < 0 || author > MaxClients) { ThrowError("Invalid client index %d", author); - - decl String:szBuffer[MAX_MESSAGE_LENGTH]; - decl String:szCMessage[MAX_MESSAGE_LENGTH]; + } + + char szBuffer[MAX_MESSAGE_LENGTH]; + char szCMessage[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage); VFormat(szCMessage, sizeof(szCMessage), szBuffer, 4); - - new index = CFormat(szCMessage, sizeof(szCMessage), author); - if (index == NO_INDEX) - { - PrintToChat(client, szCMessage); + + int index = C_Format(szCMessage, sizeof(szCMessage), author); + + if (index == NO_INDEX) { + PrintToChat(client, "%s", szCMessage); } - else - { - CSayText2(client, author, szCMessage); + else { + C_SayText2(client, author, szCMessage); } } @@ -149,26 +228,26 @@ stock CPrintToChatEx(client, author, const String:szMessage[], any:...) * * On error/Errors: If the author is not connected an error will be thrown. */ -stock CPrintToChatAllEx(author, const String:szMessage[], any:...) -{ - if (author < 0 || author > MaxClients) +stock void C_PrintToChatAllEx(int author, const char[] szMessage, any ...) { + if (author < 0 || author > MaxClients) { ThrowError("Invalid client index %d", author); - - if (!IsClientInGame(author)) + } + + if (!IsClientInGame(author)) { ThrowError("Client %d is not in game", author); - - decl String:szBuffer[MAX_MESSAGE_LENGTH]; - - for (new i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && !IsFakeClient(i) && !CSkipList[i]) - { + } + + char szBuffer[MAX_MESSAGE_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && !IsFakeClient(i) && !C_SkipList[i]) { SetGlobalTransTarget(i); VFormat(szBuffer, sizeof(szBuffer), szMessage, 3); - CPrintToChatEx(i, author, szBuffer); + + C_PrintToChatEx(i, author, "%s", szBuffer); } - - CSkipList[i] = false; + + C_SkipList[i] = false; } } @@ -178,19 +257,55 @@ stock CPrintToChatAllEx(author, const String:szMessage[], any:...) * @param szMessage String. * @return No return */ -stock CRemoveTags(String:szMessage[], maxlength) -{ - for (new i = 0; i < MAX_COLORS; i++) - { - ReplaceString(szMessage, maxlength, CTag[i], ""); +stock void C_RemoveTags(char[] szMessage, int maxlength) { + for (int i = 0; i < MAX_COLORS; i++) { + ReplaceString(szMessage, maxlength, C_Tag[i], "", false); } - - ReplaceString(szMessage, maxlength, "{teamcolor}", ""); + + ReplaceString(szMessage, maxlength, "{teamcolor}", "", false); +} + +/** + * Checks whether a color is allowed or not + * + * @param tag Color Tag. + * @return True when color is supported, otherwise false + */ +stock bool C_ColorAllowed(C_Colors color) { + if (!C_EventIsHooked) { + C_SetupProfile(); + + C_EventIsHooked = true; + } + + return C_Profile_Colors[color]; +} + +/** + * Replace the color with another color + * Handle with care! + * + * @param color color to replace. + * @param newColor color to replace with. + * @noreturn + */ +stock void C_ReplaceColor(C_Colors color, C_Colors newColor) { + if (!C_EventIsHooked) { + C_SetupProfile(); + + C_EventIsHooked = true; + } + + C_Profile_Colors[color] = C_Profile_Colors[newColor]; + C_Profile_TeamIndex[color] = C_Profile_TeamIndex[newColor]; + + C_TagReqSayText2[color] = C_TagReqSayText2[newColor]; + Format(C_TagCode[color], sizeof(C_TagCode[]), C_TagCode[newColor]); } /** * This function should only be used right in front of - * CPrintToChatAll or CPrintToChatAllEx and it tells + * C_PrintToChatAll or C_PrintToChatAllEx and it tells * to those funcions to skip specified client when printing * message to all clients. After message is printed client will * no more be skipped. @@ -198,12 +313,12 @@ stock CRemoveTags(String:szMessage[], maxlength) * @param client Client index * @return No return */ -stock CSkipNextClient(client) -{ - if (client <= 0 || client > MaxClients) +stock void C_SkipNextClient(int client) { + if (client <= 0 || client > MaxClients) { ThrowError("Invalid client index %d", client); - - CSkipList[client] = true; + } + + C_SkipList[client] = true; } /** @@ -215,119 +330,114 @@ stock CSkipNextClient(client) * * On error/Errors: If there is more then one team color is used an error will be thrown. */ -stock CFormat(String:szMessage[], maxlength, author=NO_INDEX) -{ +stock int C_Format(char[] szMessage, int maxlength, int author = NO_INDEX) { /* Hook event for auto profile setup on map start */ - if (!CEventIsHooked) - { - CSetupProfile(); - HookEvent("server_spawn", CEvent_MapStart, EventHookMode_PostNoCopy); - CEventIsHooked = true; + if (!C_EventIsHooked) { + C_SetupProfile(); + HookEvent("server_spawn", C_Event_MapStart, EventHookMode_PostNoCopy); + + C_EventIsHooked = true; } - - new iRandomPlayer = NO_INDEX; - + + int iRandomPlayer = NO_INDEX; + + // On CS:GO set invisible precolor + if (GetEngineVersion() == Engine_CSGO) { + Format(szMessage, maxlength, " %s", szMessage); + } + /* If author was specified replace {teamcolor} tag */ - if (author != NO_INDEX) - { - if (CProfile_SayText2) - { - ReplaceString(szMessage, maxlength, "{teamcolor}", "\x03"); + if (author != NO_INDEX) { + if (C_Profile_SayText2) { + ReplaceString(szMessage, maxlength, "{teamcolor}", "\x03", false); + iRandomPlayer = author; } /* If saytext2 is not supported by game replace {teamcolor} with green tag */ - else - { - ReplaceString(szMessage, maxlength, "{teamcolor}", CTagCode[Color_Green]); + else { + ReplaceString(szMessage, maxlength, "{teamcolor}", C_TagCode[Color_Green], false); } } - else - { - ReplaceString(szMessage, maxlength, "{teamcolor}", ""); + else { + ReplaceString(szMessage, maxlength, "{teamcolor}", "", false); } - + /* For other color tags we need a loop */ - for (new i = 0; i < MAX_COLORS; i++) - { + for (int i = 0; i < MAX_COLORS; i++) { /* If tag not found - skip */ - if (StrContains(szMessage, CTag[i]) == -1) - { + if (StrContains(szMessage, C_Tag[i], false) == -1) { continue; } + /* If tag is not supported by game replace it with green tag */ - else if (!CProfile_Colors[i]) - { - ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green]); + else if (!C_Profile_Colors[i]) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[Color_Green], false); } + /* If tag doesn't need saytext2 simply replace */ - else if (!CTagReqSayText2[i]) - { - ReplaceString(szMessage, maxlength, CTag[i], CTagCode[i]); + else if (!C_TagReqSayText2[i]) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[i], false); } + /* Tag needs saytext2 */ - else - { + else { /* If saytext2 is not supported by game replace tag with green tag */ - if (!CProfile_SayText2) - { - ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green]); + if (!C_Profile_SayText2) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[Color_Green], false); } + /* Game supports saytext2 */ - else - { + else { /* If random player for tag wasn't specified replace tag and find player */ - if (iRandomPlayer == NO_INDEX) - { + if (iRandomPlayer == NO_INDEX) { /* Searching for valid client for tag */ - iRandomPlayer = CFindRandomPlayerByTeam(CProfile_TeamIndex[i]); - + iRandomPlayer = C_FindRandomPlayerByTeam(C_Profile_TeamIndex[i]); + /* If player not found replace tag with green color tag */ - if (iRandomPlayer == NO_PLAYER) - { - ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green]); + if (iRandomPlayer == NO_PLAYER) { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[Color_Green], false); } + /* If player was found simply replace */ - else - { - ReplaceString(szMessage, maxlength, CTag[i], CTagCode[i]); + else { + ReplaceString(szMessage, maxlength, C_Tag[i], C_TagCode[i], false); } - + } /* If found another team color tag throw error */ - else - { - //ReplaceString(szMessage, maxlength, CTag[i], ""); + else { + //ReplaceString(szMessage, maxlength, C_Tag[i], ""); ThrowError("Using two team colors in one message is not allowed"); } } - } } - + return iRandomPlayer; } /** - * Founds a random player with specified team + * Finds a random player with specified team * * @param color_team Client team. * @return Client index or NO_PLAYER if no player found */ -stock CFindRandomPlayerByTeam(color_team) -{ - if (color_team == SERVER_INDEX) - { +stock int C_FindRandomPlayerByTeam(int color_team) { + if (color_team == SERVER_INDEX) { return 0; } - else - { - for (new i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && GetClientTeam(i) == color_team) - { - return i; - } - } + + int[] players = new int[MaxClients]; + int count; + + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && GetClientTeam(i) == color_team) { + players[count++] = i; + } + } + + if (count) { + return players[GetRandomInt(0, count-1)]; } return NO_PLAYER; @@ -341,12 +451,26 @@ stock CFindRandomPlayerByTeam(color_team) * @param szMessage Message * @return No return. */ -stock CSayText2(client, author, const String:szMessage[]) -{ - new Handle:hBuffer = StartMessageOne("SayText2", client); - BfWriteByte(hBuffer, author); - BfWriteByte(hBuffer, true); - BfWriteString(hBuffer, szMessage); +stock void C_SayText2(int client, int author, const char[] szMessage) { + Handle hBuffer = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); + + if (GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) { + Protobuf pb = UserMessageToProtobuf(hBuffer); + pb.SetInt("ent_idx", author); + pb.SetBool("chat", true); + pb.SetString("msg_name", szMessage); + pb.AddString("params", ""); + pb.AddString("params", ""); + pb.AddString("params", ""); + pb.AddString("params", ""); + } + else { + BfWrite bf = UserMessageToBfWrite(hBuffer); + bf.WriteByte(author); + bf.WriteByte(true); + bf.WriteString(szMessage); + } + EndMessage(); } @@ -356,91 +480,426 @@ stock CSayText2(client, author, const String:szMessage[]) * * @return No return. */ -stock CSetupProfile() -{ - decl String:szGameName[30]; - GetGameFolderName(szGameName, sizeof(szGameName)); - - if (StrEqual(szGameName, "cstrike", false)) - { - CProfile_Colors[Color_Lightgreen] = true; - CProfile_Colors[Color_Red] = true; - CProfile_Colors[Color_Blue] = true; - CProfile_Colors[Color_Olive] = true; - CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; - CProfile_TeamIndex[Color_Red] = 2; - CProfile_TeamIndex[Color_Blue] = 3; - CProfile_SayText2 = true; - } - else if (StrEqual(szGameName, "tf", false)) - { - CProfile_Colors[Color_Lightgreen] = true; - CProfile_Colors[Color_Red] = true; - CProfile_Colors[Color_Blue] = true; - CProfile_Colors[Color_Olive] = true; - CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; - CProfile_TeamIndex[Color_Red] = 2; - CProfile_TeamIndex[Color_Blue] = 3; - CProfile_SayText2 = true; - } - else if (StrEqual(szGameName, "left4dead", false) || StrEqual(szGameName, "left4dead2", false)) - { - CProfile_Colors[Color_Lightgreen] = true; - CProfile_Colors[Color_Red] = true; - CProfile_Colors[Color_Blue] = true; - CProfile_Colors[Color_Olive] = true; - CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; - CProfile_TeamIndex[Color_Red] = 3; - CProfile_TeamIndex[Color_Blue] = 2; - CProfile_SayText2 = true; - } - else if (StrEqual(szGameName, "hl2mp", false)) - { +stock void C_SetupProfile() { + EngineVersion engine = GetEngineVersion(); + + if (engine == Engine_CSS) { + C_Profile_Colors[Color_Lightgreen] = true; + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; + } + else if (engine == Engine_CSGO) { + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_Colors[Color_Darkred] = true; + C_Profile_Colors[Color_Lime] = true; + C_Profile_Colors[Color_Lightred] = true; + C_Profile_Colors[Color_Purple] = true; + C_Profile_Colors[Color_Grey] = true; + C_Profile_Colors[Color_Yellow] = true; + C_Profile_Colors[Color_Orange] = true; + C_Profile_Colors[Color_Bluegrey] = true; + C_Profile_Colors[Color_Lightblue] = true; + C_Profile_Colors[Color_Darkblue] = true; + C_Profile_Colors[Color_Grey2] = true; + C_Profile_Colors[Color_Orchid] = true; + C_Profile_Colors[Color_Lightred2] = true; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; + } + else if (engine == Engine_TF2) { + C_Profile_Colors[Color_Lightgreen] = true; + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; + } + else if (engine == Engine_Left4Dead || engine == Engine_Left4Dead2) { + C_Profile_Colors[Color_Lightgreen] = true; + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + C_Profile_TeamIndex[Color_Red] = 3; + C_Profile_TeamIndex[Color_Blue] = 2; + C_Profile_SayText2 = true; + } + else if (engine == Engine_HL2DM) { /* hl2mp profile is based on mp_teamplay convar */ - if (GetConVarBool(FindConVar("mp_teamplay"))) - { - CProfile_Colors[Color_Red] = true; - CProfile_Colors[Color_Blue] = true; - CProfile_Colors[Color_Olive] = true; - CProfile_TeamIndex[Color_Red] = 3; - CProfile_TeamIndex[Color_Blue] = 2; - CProfile_SayText2 = true; + if (GetConVarBool(FindConVar("mp_teamplay"))) { + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_Colors[Color_Olive] = true; + C_Profile_TeamIndex[Color_Red] = 3; + C_Profile_TeamIndex[Color_Blue] = 2; + C_Profile_SayText2 = true; } - else - { - CProfile_SayText2 = false; - CProfile_Colors[Color_Olive] = true; + else { + C_Profile_SayText2 = false; + C_Profile_Colors[Color_Olive] = true; } } - else if (StrEqual(szGameName, "dod", false)) - { - CProfile_Colors[Color_Olive] = true; - CProfile_SayText2 = false; + else if (engine == Engine_DODS) { + C_Profile_Colors[Color_Olive] = true; + C_Profile_SayText2 = false; } /* Profile for other games */ - else - { - if (GetUserMessageId("SayText2") == INVALID_MESSAGE_ID) - { - CProfile_SayText2 = false; + else { + if (GetUserMessageId("SayText2") == INVALID_MESSAGE_ID) { + C_Profile_SayText2 = false; } - else - { - CProfile_Colors[Color_Red] = true; - CProfile_Colors[Color_Blue] = true; - CProfile_TeamIndex[Color_Red] = 2; - CProfile_TeamIndex[Color_Blue] = 3; - CProfile_SayText2 = true; + else { + C_Profile_Colors[Color_Red] = true; + C_Profile_Colors[Color_Blue] = true; + C_Profile_TeamIndex[Color_Red] = 2; + C_Profile_TeamIndex[Color_Blue] = 3; + C_Profile_SayText2 = true; } } } -public Action:CEvent_MapStart(Handle:event, const String:name[], bool:dontBroadcast) -{ - CSetupProfile(); - - for (new i = 1; i <= MaxClients; i++) - { - CSkipList[i] = false; +public void C_Event_MapStart(Event event, const char[] name, bool dontBroadcast) { + C_SetupProfile(); + + for (int i = 1; i <= MaxClients; ++i) { + C_SkipList[i] = false; + } +} + +/** + * Displays usage of an admin command to users depending on the + * setting of the sm_show_activity cvar. + * + * This version does not display a message to the originating client + * if used from chat triggers or menus. If manual replies are used + * for these cases, then this function will suffice. Otherwise, + * C_ShowActivity2() is slightly more useful. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +stock int C_ShowActivity(int client, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char tag[] = "[SM] "; + + char szBuffer[MAX_MESSAGE_LENGTH]; + //char szCMessage[MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + bool display_in_chat = false; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + /* Display the message to the client? */ + if (replyto == SM_REPLY_TO_CONSOLE) { + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToConsole(client, "%s%s", tag, szBuffer); + display_in_chat = true; + } + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", tag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = GetAdminFlag(id, Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 3); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} + +/** + * Same as C_ShowActivity(), except the tag parameter is used instead of "[SM] " (note that you must supply any spacing). + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to display with. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +stock int C_ShowActivityEx(int client, const char[] tag, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char szTag[MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); + + char szBuffer[MAX_MESSAGE_LENGTH]; + //char szCMessage[MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + bool display_in_chat = false; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + /* Display the message to the client? */ + if (replyto == SM_REPLY_TO_CONSOLE) { + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToConsole(client, "%s%s", szTag, szBuffer); + display_in_chat = true; + } + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", szTag, szBuffer); + } + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = GetAdminFlag(id, Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root) || (i == client)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} + +/** + * Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar. + * All users receive a message in their chat text, except for the originating client, + * who receives the message based on the current ReplySource. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to prepend to the message. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +stock int C_ShowActivity2(int client, const char[] tag, const char[] format, any ...) { + if (sm_show_activity == null) { + sm_show_activity = FindConVar("sm_show_activity"); + } + + char szTag[MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); + + char szBuffer[MAX_MESSAGE_LENGTH]; + //char szCMessage[MAX_MESSAGE_LENGTH]; + int value = sm_show_activity.IntValue; + // ReplySource replyto = GetCmdReplySource(); + + char name[MAX_NAME_LENGTH] = "Console"; + char sign[MAX_NAME_LENGTH] = "ADMIN"; + if (client != 0) { + if (client < 0 || client > MaxClients || !IsClientConnected(client)) { + ThrowError("Client index %d is invalid", client); + } + + GetClientName(client, name, sizeof(name)); + AdminId id = GetUserAdmin(client); + if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + sign = "PLAYER"; + } + + SetGlobalTransTarget(client); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + /* We don't display directly to the console because the chat text + * simply gets added to the console, so we don't want it to print + * twice. + */ + C_PrintToChatEx(client, client, "%s%s", szTag, szBuffer); + } + else { + SetGlobalTransTarget(LANG_SERVER); + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_RemoveTags(szBuffer, sizeof(szBuffer)); + PrintToServer("%s%s", szTag, szBuffer); } -} \ No newline at end of file + + if (!value) { + return 1; + } + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || IsFakeClient(i) || i == client) { + continue; + } + + AdminId id = GetUserAdmin(i); + SetGlobalTransTarget(i); + if (id == INVALID_ADMIN_ID + || !GetAdminFlag(id, Admin_Generic, Access_Effective)) { + /* Treat this as a normal user. */ + if ((value & 1) | (value & 2)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 2)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + else { + /* Treat this as an admin user */ + bool is_root = GetAdminFlag(id, Admin_Root, Access_Effective); + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { + char newsign[MAX_NAME_LENGTH]; + + if ((value & 8) || ((value & 16) && is_root)) { + newsign = name; + } + else { + newsign = sign; + } + + VFormat(szBuffer, sizeof(szBuffer), format, 4); + + C_PrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer); + } + } + } + + return 1; +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_config.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_config.inc new file mode 100644 index 00000000..d94cf83f --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_config.inc @@ -0,0 +1,540 @@ +#if defined _dodgeball_config_included + #endinput +#endif +#define _dodgeball_config_included + +/** + * Handles parsing of configuration files and server commands. + */ + +/** Clears all rocket class definitions and frees associated command DataPacks. */ +void DestroyRocketClasses() +{ + for (int iIndex = 0; iIndex < RocketClassCount; iIndex++) + { + DataPack hCmdOnSpawn = RocketClassCmdsOnSpawn[iIndex]; + DataPack hCmdOnKill = RocketClassCmdsOnKill[iIndex]; + DataPack hCmdOnExplode = RocketClassCmdsOnExplode[iIndex]; + DataPack hCmdOnDeflect = RocketClassCmdsOnDeflect[iIndex]; + DataPack hCmdOnNoTarget = RocketClassCmdsOnNoTarget[iIndex]; + + if (hCmdOnSpawn != null) delete hCmdOnSpawn; + if (hCmdOnKill != null) delete hCmdOnKill; + if (hCmdOnExplode != null) delete hCmdOnExplode; + if (hCmdOnDeflect != null) delete hCmdOnDeflect; + if (hCmdOnNoTarget != null) delete hCmdOnNoTarget; + + RocketClassCmdsOnSpawn[iIndex] = null; + RocketClassCmdsOnKill[iIndex] = null; + RocketClassCmdsOnExplode[iIndex] = null; + RocketClassCmdsOnDeflect[iIndex] = null; + RocketClassCmdsOnNoTarget[iIndex] = null; + } + RocketClassCount = 0; +} + +/** Clears all spawner class definitions and spawn point data. */ +void DestroySpawners() +{ + for (int iIndex = 0; iIndex < SpawnersCount; iIndex++) + { + delete SpawnersChancesTable[iIndex]; + } + + SpawnersCount = 0; + SpawnPointsRedCount = 0; + SpawnPointsBluCount = 0; + DefaultRedSpawner = -1; + DefaultBluSpawner = -1; + SpawnersTrie.Clear(); +} + +/** + * Scans the map for info_target entities and populates spawn point arrays. + * Looks for entities named "rocket_spawn_red", "tf_dodgeball_red", + * "rocket_spawn_blu", or "tf_dodgeball_blu". + * Calls SetFailState if no spawn points are found. + */ +void PopulateSpawnPoints() +{ + SpawnPointsRedCount = 0; + SpawnPointsBluCount = 0; + int iEntity = -1; + while ((iEntity = FindEntityByClassname(iEntity, "info_target")) != -1) + { + char strName[32]; GetEntPropString(iEntity, Prop_Data, "m_iName", strName, sizeof(strName)); + if ((StrContains(strName, "rocket_spawn_red") != -1) || (StrContains(strName, "tf_dodgeball_red") != -1)) + { + int iIndex = FindSpawnerByName(strName); + if (iIndex == -1) iIndex = DefaultRedSpawner; + SpawnPointsRedClass [SpawnPointsRedCount] = iIndex; + SpawnPointsRedEntity[SpawnPointsRedCount] = iEntity; + SpawnPointsRedCount++; + } + + if ((StrContains(strName, "rocket_spawn_blu") != -1) || (StrContains(strName, "tf_dodgeball_blu") != -1)) + { + int iIndex = FindSpawnerByName(strName); + if (iIndex == -1) iIndex = DefaultBluSpawner; + + SpawnPointsBluClass [SpawnPointsBluCount] = iIndex; + SpawnPointsBluEntity[SpawnPointsBluCount] = iEntity; + SpawnPointsBluCount++; + } + } + + // New code block starts here + if (SpawnPointsRedCount == 0 && SpawnPointsBluCount == 0) + { + SetFailState("No 'info_target' entities found on this map."); + return; + } + // New code block ends here + + if (SpawnPointsRedCount == 0) SetFailState("No RED spawn points found on this map."); + if (SpawnPointsBluCount == 0) SetFailState("No BLU spawn points found on this map."); +} + +/** + * Finds a spawner class index by name using the spawners trie. + * + * @param strName The spawner name to search for. + * @return The spawner class index, or -1 if not found. + */ +int FindSpawnerByName(char strName[32]) +{ + int iIndex = -1; + SpawnersTrie.GetValue(strName, iIndex); + return iIndex; +} + +/** Registers server commands for explosion and shockwave effects. */ +void RegisterCommands() +{ + RegServerCmd("tf_dodgeball_explosion", CmdExplosion); + RegServerCmd("tf_dodgeball_shockwave", CmdShockwave); +} + +/** + * Server command: Creates nuke particle effects at a client's position. + * Usage: tf_dodgeball_explosion + * + * @param iArgs Number of command arguments. + * @return Plugin_Handled after processing. + */ +public Action CmdExplosion(int iArgs) +{ + if (!Enabled) + { + CPrintToServer("%t", "Command_Disabled"); + return Plugin_Handled; + } + + if (iArgs != 1) + { + CPrintToServer("%t", "Command_DBShockwave_Usage"); + return Plugin_Handled; + } + + char strBuffer[8]; + int iClient; + GetCmdArg(1, strBuffer, sizeof(strBuffer)); + iClient = StringToInt(strBuffer); + + if (!IsValidClient(iClient)) return Plugin_Handled; + + float fPosition[3]; + GetClientAbsOrigin(iClient, fPosition); + switch (GetURandomIntRange(0, 4)) + { + case 0: PlayParticle(fPosition, PARTICLE_NUKE_1_ANGLES, PARTICLE_NUKE_1); + case 1: PlayParticle(fPosition, PARTICLE_NUKE_2_ANGLES, PARTICLE_NUKE_2); + case 2: PlayParticle(fPosition, PARTICLE_NUKE_3_ANGLES, PARTICLE_NUKE_3); + case 3: PlayParticle(fPosition, PARTICLE_NUKE_4_ANGLES, PARTICLE_NUKE_4); + case 4: PlayParticle(fPosition, PARTICLE_NUKE_5_ANGLES, PARTICLE_NUKE_5); + } + PlayParticle(fPosition, PARTICLE_NUKE_COLLUMN_ANGLES, PARTICLE_NUKE_COLLUMN); + + return Plugin_Handled; +} + +/** + * Server command: Creates a shockwave that damages and pushes players. + * Usage: tf_dodgeball_shockwave + * + * @param iArgs Number of command arguments. + * @return Plugin_Handled after processing. + */ +public Action CmdShockwave(int iArgs) +{ + if (!Enabled) + { + CPrintToServer("%t", "Command_Disabled"); + return Plugin_Handled; + } + + if (iArgs != 5) + { + CPrintToServer("%t", "Command_DBShockwave_Usage"); + return Plugin_Handled; + } + + char strBuffer[32]; + int iClient, iTeam; + float fPosition[3]; + int iDamage; + float fPushStrength, fRadius, fFalloffRadius; + + GetCmdArg(1, strBuffer, sizeof(strBuffer)); iClient = StringToInt(strBuffer); + GetCmdArg(2, strBuffer, sizeof(strBuffer)); iDamage = StringToInt(strBuffer); + GetCmdArg(3, strBuffer, sizeof(strBuffer)); fPushStrength = StringToFloat(strBuffer); + GetCmdArg(4, strBuffer, sizeof(strBuffer)); fRadius = StringToFloat(strBuffer); + GetCmdArg(5, strBuffer, sizeof(strBuffer)); fFalloffRadius = StringToFloat(strBuffer); + + if (!IsValidClient(iClient)) return Plugin_Handled; + + iTeam = GetClientTeam(iClient); + GetClientAbsOrigin(iClient, fPosition); + + for (iClient = 1; iClient <= MaxClients; iClient++) + { + if (!(IsValidClientEx(iClient, true) && (GetClientTeam(iClient) == iTeam))) continue; + + float fPlayerPosition[3]; + GetClientEyePosition(iClient, fPlayerPosition); + float fDistanceToShockwave = GetVectorDistance(fPosition, fPlayerPosition); + + if (!(fDistanceToShockwave < fRadius)) continue; + + float fImpulse[3]; + float fFinalPush; + int iFinalDamage; + fImpulse[0] = fPlayerPosition[0] - fPosition[0]; + fImpulse[1] = fPlayerPosition[1] - fPosition[1]; + fImpulse[2] = fPlayerPosition[2] - fPosition[2]; + NormalizeVector(fImpulse, fImpulse); + + if (fImpulse[2] < 0.4) { fImpulse[2] = 0.4; NormalizeVector(fImpulse, fImpulse); } + + if (fDistanceToShockwave < fFalloffRadius) + { + fFinalPush = fPushStrength; + iFinalDamage = iDamage; + } + else + { + float fImpact = (1.0 - ((fDistanceToShockwave - fFalloffRadius) / (fRadius - fFalloffRadius))); + fFinalPush = fImpact * fPushStrength; + iFinalDamage = RoundToFloor(fImpact * iDamage); + } + + ScaleVector(fImpulse, fFinalPush); + SlapPlayer(iClient, iFinalDamage, true); + SetEntPropVector(iClient, Prop_Data, "m_vecAbsVelocity", fImpulse); + } + + return Plugin_Handled; +} + +/** + * Executes stored commands from a DataPack, replacing placeholders with actual values. + * Used for on-spawn, on-deflect, on-kill, on-explode, and on-no-target commands. + * + * Supported placeholders: + * @name - Rocket class long name + * @rocket - Rocket entity index + * @owner - Owner client index + * @target - Target client index + * @dead - Last dead client index + * @deflections - Number of deflections + * @speed - Current speed (Hammer units/s) + * @mphspeed - Current speed (MPH, rounded) + * @capmphspeed - Capped MPH speed + * @nocapspeed - Uncapped speed + * @2dspeed - Speed with 2 decimal places + * @2dnocapspeed - Uncapped speed with 2 decimal places + * + * @param hDataPack DataPack containing commands to execute. + * @param iClass Rocket class index. + * @param iRocket Rocket entity index. + * @param iOwner Owner client index. + * @param iTarget Target client index. + * @param iLastDead Last dead client index. + * @param fSpeed Current rocket speed (Hammer units/s). + * @param iNumDeflections Number of deflections. + * @param fMphSpeed Current rocket speed (MPH). + */ +void ExecuteCommands(DataPack hDataPack, int iClass, int iRocket, int iOwner, int iTarget, int iLastDead, float fSpeed, int iNumDeflections, float fMphSpeed) +{ + hDataPack.Reset(false); + int iNumCommands = hDataPack.ReadCell(); + + while (iNumCommands-- > 0) + { + char strCmd[256], strBuffer[32]; + + hDataPack.ReadString(strCmd, sizeof(strCmd)); + ReplaceString(strCmd, sizeof(strCmd), "@name", RocketClassLongName[iClass]); + FormatEx(strBuffer, sizeof(strBuffer), "%i", iRocket); ReplaceString(strCmd, sizeof(strCmd), "@rocket", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%i", iOwner); ReplaceString(strCmd, sizeof(strCmd), "@owner", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%i", iTarget); ReplaceString(strCmd, sizeof(strCmd), "@target", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%i", iLastDead); ReplaceString(strCmd, sizeof(strCmd), "@dead", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%i", iNumDeflections); ReplaceString(strCmd, sizeof(strCmd), "@deflections", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%f", fSpeed); ReplaceString(strCmd, sizeof(strCmd), "@speed", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%i", RoundToNearest(fMphSpeed)); ReplaceString(strCmd, sizeof(strCmd), "@mphspeed", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%i", RoundToNearest(fSpeed * HAMMER_TO_MPH)); ReplaceString(strCmd, sizeof(strCmd), "@capmphspeed", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%f", fMphSpeed / HAMMER_TO_MPH); ReplaceString(strCmd, sizeof(strCmd), "@nocapspeed", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%.2f", fSpeed); ReplaceString(strCmd, sizeof(strCmd), "@2dspeed", strBuffer); + FormatEx(strBuffer, sizeof(strBuffer), "%.2f", fMphSpeed / HAMMER_TO_MPH); ReplaceString(strCmd, sizeof(strCmd), "@2dnocapspeed", strBuffer); + + ServerCommand(strCmd); + } +} + +/** + * Parses a dodgeball configuration file (KeyValues format). + * Loads general settings, rocket classes, and spawner classes. + */ +void ParseConfigurations(char[] strConfigFile = "general.cfg") +{ + char strPath[PLATFORM_MAX_PATH]; + char strFileName[PLATFORM_MAX_PATH]; + FormatEx(strFileName, sizeof(strFileName), "configs/dodgeball/%s", strConfigFile); + BuildPath(Path_SM, strPath, sizeof(strPath), strFileName); + LogMessage("Executing configuration file %s", strPath); + + if (!FileExists(strPath, true)) return; + + KeyValues kvConfig = new KeyValues("TF2_Dodgeball"); + if (!kvConfig.ImportFromFile(strPath)) SetFailState("Error while parsing the configuration file."); + + kvConfig.GotoFirstSubKey(); + do + { + char strSection[64]; kvConfig.GetSectionName(strSection, sizeof(strSection)); + if (StrEqual(strSection, "general")) ParseGeneral(kvConfig); + else if (StrEqual(strSection, "classes")) ParseClasses(kvConfig); + else if (StrEqual(strSection, "spawners")) ParseSpawners(kvConfig); + } + while (kvConfig.GotoNextKey()); + + delete kvConfig; + Forward_OnRocketsConfigExecuted(strConfigFile); +} + +/** Parses the "general" section of the config (music settings). */ +void ParseGeneral(KeyValues kvConfig) +{ + MusicEnabled = view_as(kvConfig.GetNum("music", 0)); + if (!MusicEnabled) return; + + UseWebPlayer = view_as(kvConfig.GetNum("use web player", 0)); + kvConfig.GetString("web player url", WebPlayerUrl, sizeof(WebPlayerUrl)); + Music[Music_RoundStart] = kvConfig.GetString("round start", MusicPath[Music_RoundStart], PLATFORM_MAX_PATH) && MusicPath[Music_RoundStart][0]; + Music[Music_RoundWin] = kvConfig.GetString("round end (win)", MusicPath[Music_RoundWin], PLATFORM_MAX_PATH) && MusicPath[Music_RoundWin][0]; + Music[Music_RoundLose] = kvConfig.GetString("round end (lose)", MusicPath[Music_RoundLose], PLATFORM_MAX_PATH) && MusicPath[Music_RoundLose][0]; + Music[Music_Gameplay] = kvConfig.GetString("gameplay", MusicPath[Music_Gameplay], PLATFORM_MAX_PATH) && MusicPath[Music_Gameplay][0]; +} + +/** Parses the "classes" section of the config (rocket class definitions). */ +void ParseClasses(KeyValues kvConfig) +{ + char strName[64]; + char strBuffer[256]; + kvConfig.GotoFirstSubKey(); + do + { + int iIndex = RocketClassCount; + RocketFlags iFlags; + + kvConfig.GetSectionName(strName, sizeof(strName)); strcopy(RocketClassName[iIndex], 16, strName); + kvConfig.GetString("name", strBuffer, sizeof(strBuffer)); strcopy(RocketClassLongName[iIndex], 32, strBuffer); + + if (kvConfig.GetString("model", strBuffer, sizeof(strBuffer))) + { + strcopy(RocketClassModel[iIndex], PLATFORM_MAX_PATH, strBuffer); + if (RocketClassModel[iIndex][0]) + { + iFlags |= RocketFlag_CustomModel; + if (kvConfig.GetNum("is animated", 0)) iFlags |= RocketFlag_IsAnimated; + } + } + + kvConfig.GetString("behaviour", strBuffer, sizeof(strBuffer), "homing"); + if (StrEqual(strBuffer, "homing")) RocketClassBehaviour[iIndex] = Behaviour_Homing; + else if (StrEqual(strBuffer, "legacy homing")) RocketClassBehaviour[iIndex] = Behaviour_LegacyHoming; + else RocketClassBehaviour[iIndex] = Behaviour_Unknown; + + if (kvConfig.GetNum("play spawn sound", 0) == 1) + { + iFlags |= RocketFlag_PlaySpawnSound; + if (kvConfig.GetString("spawn sound", RocketClassSpawnSound[iIndex], PLATFORM_MAX_PATH) && RocketClassSpawnSound[iIndex][0]) + { + iFlags |= RocketFlag_CustomSpawnSound; + } + } + + if (kvConfig.GetNum("play beep sound", 0) == 1) + { + iFlags |= RocketFlag_PlayBeepSound; + RocketClassBeepInterval[iIndex] = kvConfig.GetFloat("beep interval", 0.5); + if (kvConfig.GetString("beep sound", RocketClassBeepSound[iIndex], PLATFORM_MAX_PATH) && RocketClassBeepSound[iIndex][0]) + { + iFlags |= RocketFlag_CustomBeepSound; + } + } + + if (kvConfig.GetNum("play alert sound", 0) == 1) + { + iFlags |= RocketFlag_PlayAlertSound; + if (kvConfig.GetString("alert sound", RocketClassAlertSound[iIndex], PLATFORM_MAX_PATH) && RocketClassAlertSound[iIndex][0]) + { + iFlags |= RocketFlag_CustomAlertSound; + } + } + + if (kvConfig.GetNum("elevate on deflect", 1) == 1) iFlags |= RocketFlag_ElevateOnDeflect; + if (kvConfig.GetNum("neutral rocket", 0) == 1) iFlags |= RocketFlag_IsNeutral; + if (kvConfig.GetNum("keep direction", 0) == 1) iFlags |= RocketFlag_KeepDirection; + if (kvConfig.GetNum("teamless deflects", 0) == 1) iFlags |= RocketFlag_TeamlessHits; + if (kvConfig.GetNum("reset bounces", 0) == 1) iFlags |= RocketFlag_ResetBounces; + if (kvConfig.GetNum("no bounce drags", 0) == 1) iFlags |= RocketFlag_NoBounceDrags; + if (kvConfig.GetNum("can be stolen", 0) == 1) iFlags |= RocketFlag_CanBeStolen; + if (kvConfig.GetNum("steal team check", 0) == 1) iFlags |= RocketFlag_StealTeamCheck; + if (kvConfig.GetNum("crawl bounce", 0) == 1) iFlags |= RocketFlag_CrawlBounce; + + RocketClassDamage[iIndex] = kvConfig.GetFloat("damage"); + RocketClassDamageIncrement[iIndex] = kvConfig.GetFloat("damage increment"); + RocketClassCritChance[iIndex] = kvConfig.GetFloat("critical chance"); + RocketClassSpeed[iIndex] = kvConfig.GetFloat("speed"); + RocketClassSpeedIncrement[iIndex] = kvConfig.GetFloat("speed increment"); + if ((RocketClassSpeedLimit[iIndex] = kvConfig.GetFloat("speed limit")) != 0.0) + { + iFlags |= RocketFlag_IsSpeedLimited; + } + + RocketClassTurnRate[iIndex] = kvConfig.GetFloat("turn rate"); + RocketClassTurnRateIncrement[iIndex] = kvConfig.GetFloat("turn rate increment"); + if ((RocketClassTurnRateLimit[iIndex] = kvConfig.GetFloat("turn rate limit")) != 0.0) + { + iFlags |= RocketFlag_IsTRLimited; + } + + RocketClassElevationRate[iIndex] = kvConfig.GetFloat("elevation rate"); + RocketClassElevationLimit[iIndex] = kvConfig.GetFloat("elevation limit"); + RocketClassControlDelay[iIndex] = kvConfig.GetFloat("control delay"); + RocketClassDragTimeMin[iIndex] = kvConfig.GetFloat("drag time min"); + RocketClassDragTimeMax[iIndex] = kvConfig.GetFloat("drag time max"); + RocketClassMaxBounces[iIndex] = kvConfig.GetNum("max bounces"); + RocketClassBounceScale[iIndex] = kvConfig.GetFloat("bounce scale", 1.0); + RocketClassBounceForceScale[iIndex] = kvConfig.GetFloat("bounce force scale", CvarBounceForceScale.FloatValue); + RocketClassBounceForceAngle[iIndex] = kvConfig.GetFloat("bounce force angle", CvarBounceForceAngle.FloatValue); + RocketClassCrawlBounceScale[iIndex] = kvConfig.GetFloat("crawl bounce scale", 0.4); + RocketClassCrawlBounceMaxUp[iIndex] = kvConfig.GetFloat("crawl bounce max up", 250.0); + RocketClassPlayerModifier[iIndex] = kvConfig.GetFloat("no. players modifier"); + RocketClassRocketsModifier[iIndex] = kvConfig.GetFloat("no. rockets modifier"); + RocketClassTargetWeight[iIndex] = kvConfig.GetFloat("direction to target weight"); + + DataPack hCmds = null; + kvConfig.GetString("on spawn", strBuffer, sizeof(strBuffer)); + if ((hCmds = ParseCommands(strBuffer)) != null) { iFlags |= RocketFlag_OnSpawnCmd; RocketClassCmdsOnSpawn[iIndex] = hCmds; } + + kvConfig.GetString("on deflect", strBuffer, sizeof(strBuffer)); + if ((hCmds = ParseCommands(strBuffer)) != null) { iFlags |= RocketFlag_OnDeflectCmd; RocketClassCmdsOnDeflect[iIndex] = hCmds; } + + kvConfig.GetString("on kill", strBuffer, sizeof(strBuffer)); + if ((hCmds = ParseCommands(strBuffer)) != null) { iFlags |= RocketFlag_OnKillCmd; RocketClassCmdsOnKill[iIndex] = hCmds; } + + kvConfig.GetString("on explode", strBuffer, sizeof(strBuffer)); + if ((hCmds = ParseCommands(strBuffer)) != null) { iFlags |= RocketFlag_OnExplodeCmd; RocketClassCmdsOnExplode[iIndex] = hCmds; } + + kvConfig.GetString("on no target", strBuffer, sizeof(strBuffer)); + if ((hCmds = ParseCommands(strBuffer)) != null) { iFlags |= RocketFlag_OnNoTargetCmd; RocketClassCmdsOnNoTarget[iIndex] = hCmds; } + + RocketClassFlags[iIndex] = iFlags; + RocketClassCount++; + + if (RocketClassCount >= MAX_ROCKET_CLASSES) + { + LogError("Reached maximum rocket classes (%d). Remaining classes will be ignored.", MAX_ROCKET_CLASSES); + break; + } + } + while (kvConfig.GotoNextKey()); + kvConfig.GoBack(); +} + +/** Parses the "spawners" section of the config (spawner class definitions). */ +void ParseSpawners(KeyValues kvConfig) +{ + char strBuffer[256]; + kvConfig.GotoFirstSubKey(); + do + { + int iIndex = SpawnersCount; + + kvConfig.GetSectionName(strBuffer, sizeof(strBuffer)); strcopy(SpawnersName[iIndex], 32, strBuffer); + SpawnersMaxRockets[iIndex] = kvConfig.GetNum("max rockets", 1); + SpawnersInterval[iIndex] = kvConfig.GetFloat("interval", 1.0); + + SpawnersChancesTable[iIndex] = new ArrayList(); + for (int iClassIndex = 0; iClassIndex < RocketClassCount; iClassIndex++) + { + FormatEx(strBuffer, sizeof(strBuffer), "%s%%", RocketClassName[iClassIndex]); + SpawnersChancesTable[iIndex].Push(kvConfig.GetNum(strBuffer, 0)); + } + + SpawnersTrie.SetValue(SpawnersName[iIndex], iIndex); + SpawnersCount++; + } + while (kvConfig.GotoNextKey()); + + kvConfig.GoBack(); + SpawnersTrie.GetValue("red", DefaultRedSpawner); + SpawnersTrie.GetValue("blu", DefaultBluSpawner); +} + +/** + * Parses a semicolon-separated command string into a DataPack. + * Each command between semicolons becomes a separate entry. + * + * @param strLine The command string to parse (e.g., "sm_slay @target; sm_beacon @owner"). + * @return DataPack containing the commands, or null if empty. + */ +DataPack ParseCommands(char[] strLine) +{ + TrimString(strLine); + if (!strLine[0]) return null; + + // Use static array to avoid heap allocation on each call + // This prevents "Not enough space on the heap" when parsing many rocket classes + static char strParts[32][256]; + int iNumParts = ExplodeString(strLine, ";", strParts, 32, 256); + + ArrayList hCommands = new ArrayList(ByteCountToCells(256)); + char strBuffer[256]; + + for (int i = 0; i < iNumParts; i++) + { + TrimString(strParts[i]); + if (strParts[i][0]) hCommands.PushString(strParts[i]); + } + + int iNumCommands = hCommands.Length; + if (iNumCommands == 0) + { + delete hCommands; + return null; + } + + DataPack hDataPack = new DataPack(); + hDataPack.WriteCell(iNumCommands); + for (int i = 0; i < iNumCommands; i++) + { + hCommands.GetString(i, strBuffer, sizeof(strBuffer)); + hDataPack.WriteString(strBuffer); + } + + delete hCommands; + return hDataPack; +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_core.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_core.inc new file mode 100644 index 00000000..6fa0f5d2 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_core.inc @@ -0,0 +1,208 @@ +#if defined _dodgeball_core_included + #endinput +#endif +#define _dodgeball_core_included + +/** + * Contains core gamemode logic: enabling/disabling the mod and the main game timer. + */ + +// Tracks whether game events have been hooked (to prevent double-unhook errors) +static bool EventsHooked = false; +// Tracks whether object_deflected was successfully hooked. +static bool ObjectDeflectedHooked = false; + +/** Checks if the current map is a dodgeball map (starts with "tfdb_", "db_", or "dbs_"). */ +bool IsDodgeBallMap() +{ + char map[64]; GetCurrentMap(map, sizeof(map)); + GetMapDisplayName(map, map, sizeof(map)); + return (StrContains(map, "tfdb_", false) == 0 || + StrContains(map, "db_", false) == 0 || + StrContains(map, "dbs_", false) == 0); +} + +/** + * Enables the dodgeball gamemode. Loads configuration, hooks events, + * precaches sounds/models, and starts the game timer. + */ +void EnableDodgeBall() +{ + if (Enabled) return; + + char mapName[64]; GetCurrentMap(mapName, sizeof(mapName)); + GetMapDisplayName(mapName, mapName, sizeof(mapName)); + char mapFile[PLATFORM_MAX_PATH]; FormatEx(mapFile, sizeof(mapFile), "%s.cfg", mapName); + + ParseConfigurations(); + ParseConfigurations(mapFile); + + if (RocketClassCount == 0) SetFailState("No rocket class defined."); + if (SpawnersCount == 0) SetFailState("No spawner class defined."); + if (DefaultRedSpawner == -1) SetFailState("No spawner class definition for the Red spawners exists in the config file."); + if (DefaultBluSpawner == -1) SetFailState("No spawner class definition for the Blu spawners exists in the config file."); + + // Only hook events if not already hooked + if (!EventsHooked) + { + HookEventEx("teamplay_round_start", OnRoundStart, EventHookMode_PostNoCopy); + HookEventEx("teamplay_round_active", OnSetupFinished, EventHookMode_PostNoCopy); // For non-Arena modes + HookEventEx("arena_round_start", OnSetupFinished, EventHookMode_PostNoCopy); // For Arena mode + HookEventEx("teamplay_round_win", OnRoundEnd, EventHookMode_PostNoCopy); + HookEventEx("player_spawn", OnPlayerSpawn, EventHookMode_Post); + HookEventEx("player_death", OnPlayerDeath, EventHookMode_Pre); + HookEventEx("post_inventory_application", OnPlayerInventory, EventHookMode_Post); + HookEventEx("teamplay_broadcast_audio", OnBroadcastAudio, EventHookMode_Pre); + // Keep this hook global (not tied to enable/disable) to avoid + // map-end unhook exceptions observed on some installs. + if (!ObjectDeflectedHooked) + { + ObjectDeflectedHooked = HookEventEx("object_deflected", OnObjectDeflected); + if (!ObjectDeflectedHooked) + { + LogError("Failed to hook game event \"object_deflected\". Deflect-specific rocket updates will be disabled."); + } + } + EventsHooked = true; + } + + AddMultiTargetFilter("@stealer", MLTargetFilterStealer, "last stealer", false); + AddMultiTargetFilter("@!stealer", MLTargetFilterStealer, "non last stealer", false); + + PrecacheSound(SOUND_DEFAULT_SPAWN, true); + PrecacheSound(SOUND_DEFAULT_BEEP, true); + PrecacheSound(SOUND_DEFAULT_ALERT, true); + PrecacheSound(SOUND_DEFAULT_SPEEDUP, true); + + if (MusicEnabled) + { + if (Music[Music_RoundStart]) PrecacheSoundEx(MusicPath[Music_RoundStart], true, true); + if (Music[Music_RoundWin]) PrecacheSoundEx(MusicPath[Music_RoundWin], true, true); + if (Music[Music_RoundLose]) PrecacheSoundEx(MusicPath[Music_RoundLose], true, true); + if (Music[Music_Gameplay]) PrecacheSoundEx(MusicPath[Music_Gameplay], true, true); + } + + PrecacheParticle(PARTICLE_NUKE_1); + PrecacheParticle(PARTICLE_NUKE_2); + PrecacheParticle(PARTICLE_NUKE_3); + PrecacheParticle(PARTICLE_NUKE_4); + PrecacheParticle(PARTICLE_NUKE_5); + PrecacheParticle(PARTICLE_NUKE_COLLUMN); + + for (int i = 0; i < RocketClassCount; i++) + { + RocketFlags flags = RocketClassFlags[i]; + if (TestFlags(flags, RocketFlag_CustomModel)) PrecacheModelEx(RocketClassModel[i], true, true); + if (TestFlags(flags, RocketFlag_CustomSpawnSound)) PrecacheSoundEx(RocketClassSpawnSound[i], true, true); + if (TestFlags(flags, RocketFlag_CustomBeepSound)) PrecacheSoundEx(RocketClassBeepSound[i], true, true); + if (TestFlags(flags, RocketFlag_CustomAlertSound)) PrecacheSoundEx(RocketClassAlertSound[i], true, true); + } + + char cfgFile[64]; + CvarEnableCfgFile.GetString(cfgFile, sizeof(cfgFile)); + ServerCommand("exec \"%s\"", cfgFile); + + Enabled = true; + RoundStarted = false; + RoundCount = 0; +} + +/** + * Disables the dodgeball gamemode. Cleans up rockets, unhooks events, + * and resets state. + */ +void DisableDodgeBall() +{ + if (!Enabled) return; + + DestroyRockets(); + DestroyRocketClasses(); + DestroySpawners(); + + if (LogicTimer != null) KillTimer(LogicTimer); + LogicTimer = null; + + Music[Music_RoundStart] = + Music[Music_RoundWin] = + Music[Music_RoundLose] = + Music[Music_Gameplay] = false; + + // Only unhook events if they were previously hooked + if (EventsHooked) + { + UnhookEvent("teamplay_round_start", OnRoundStart, EventHookMode_PostNoCopy); + UnhookEvent("teamplay_round_active", OnSetupFinished, EventHookMode_PostNoCopy); + UnhookEvent("arena_round_start", OnSetupFinished, EventHookMode_PostNoCopy); + UnhookEvent("teamplay_round_win", OnRoundEnd, EventHookMode_PostNoCopy); + UnhookEvent("player_spawn", OnPlayerSpawn, EventHookMode_Post); + UnhookEvent("player_death", OnPlayerDeath, EventHookMode_Pre); + UnhookEvent("post_inventory_application", OnPlayerInventory, EventHookMode_Post); + UnhookEvent("teamplay_broadcast_audio", OnBroadcastAudio, EventHookMode_Pre); + EventsHooked = false; + } + + RemoveMultiTargetFilter("@stealer", MLTargetFilterStealer); + RemoveMultiTargetFilter("@!stealer", MLTargetFilterStealer); + + char cfgFile[64]; CvarDisableCfgFile.GetString(cfgFile, sizeof(cfgFile)); + ServerCommand("exec \"%s\"", cfgFile); + + Enabled = false; + RoundStarted = false; + RoundCount = 0; +} + +/** + * This is the main 20Hz timer for the gamemode. + * It handles rocket spawning and the logic for "legacy homing" rockets. + * It also calls shared logic functions like sound updates. + */ +public Action OnDodgeBallGameFrame(Handle timer, any data) +{ + if (!BothTeamsPlaying()) return Plugin_Continue; + + // Rocket Spawning Logic + if (GetGameTime() >= NextSpawnTime) + { + if (LastDeadTeam == view_as(TFTeam_Red)) + { + int spawnerEntity = SpawnPointsRedEntity[CurrentRedSpawn]; + int spawnerClass = SpawnPointsRedClass[CurrentRedSpawn]; + + if (RocketCount < SpawnersMaxRockets[spawnerClass]) + { + CreateRocket(spawnerEntity, spawnerClass, view_as(TFTeam_Red)); + CurrentRedSpawn = (CurrentRedSpawn + 1) % SpawnPointsRedCount; + } + } + else + { + int spawnerEntity = SpawnPointsBluEntity[CurrentBluSpawn]; + int spawnerClass = SpawnPointsBluClass[CurrentBluSpawn]; + + if (RocketCount < SpawnersMaxRockets[spawnerClass]) + { + CreateRocket(spawnerEntity, spawnerClass, view_as(TFTeam_Blue)); + CurrentBluSpawn = (CurrentBluSpawn + 1) % SpawnPointsBluCount; + } + } + } + + // Rocket Think Logic + int index = -1; + while ((index = FindNextValidRocket(index)) != -1) + { + // This timer handles legacy homing rockets and shared logic for all rockets. + switch (RocketClassBehaviour[RocketClass[index]]) + { + case Behaviour_LegacyHoming: + { + RocketLegacyThink(index); + } + } + // Shared logic (sounds, delays) is called for all rockets from here. + SharedRocketThink(index); + } + + return Plugin_Continue; +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_events.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_events.inc new file mode 100644 index 00000000..df39bd6c --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_events.inc @@ -0,0 +1,469 @@ +#if defined _dodgeball_events_included + #endinput +#endif +#define _dodgeball_events_included + +/** + * Handles all game events hooked by the plugin. + */ + +/** Called when a new round starts. Resets steal info and plays round start music. */ +public void OnRoundStart(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + if (Music[Music_RoundStart]) + { + EmitSoundToAll(MusicPath[Music_RoundStart]); + } + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + StealInfo[iClient].stoleRocket = false; + StealInfo[iClient].rocketsStolen = 0; + } +} + +/** Called when setup phase ends. Initializes spawn points and starts the game timer. */ +public void OnSetupFinished(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + // Guard against being called twice (both teamplay_round_active and arena_round_start hook this) + if (RoundStarted) return; + if (!BothTeamsPlaying()) return; + + PopulateSpawnPoints(); + if (LastDeadTeam == 0) LastDeadTeam = GetURandomIntRange(view_as(TFTeam_Red), view_as(TFTeam_Blue)); + if (!IsValidClient(LastDeadClient)) LastDeadClient = 0; + + LogicTimer = CreateTimer(FPS_LOGIC_INTERVAL, OnDodgeBallGameFrame, _, TIMER_REPEAT); + PlayerCount = CountAlivePlayers(); + RocketsFired = 0; + CurrentRedSpawn = 0; + CurrentBluSpawn = 0; + NextSpawnTime = GetGameTime(); + RoundStarted = true; + RoundCount++; +} + +/** Called when round ends. Stops timer, music, and destroys all rockets. */ +public void OnRoundEnd(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + if (LogicTimer != null) + { + KillTimer(LogicTimer); + LogicTimer = null; + } + + if (MusicEnabled) + { + if (UseWebPlayer) + { + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsValidClientEx(iClient)) continue; + ShowHiddenMOTDPanel(iClient, "MusicPlayerStop", "http://0.0.0.0/"); + } + } + else if (Music[Music_Gameplay]) + { + StopSoundToAll(SNDCHAN_MUSIC, MusicPath[Music_Gameplay]); + } + } + + DestroyRockets(); + RoundStarted = false; +} + +/** Called when player spawns. Forces Pyro class for dodgeball. */ +public void OnPlayerSpawn(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + int iClient = GetClientOfUserId(hEvent.GetInt("userid")); + if (!IsValidClient(iClient)) return; + + TFClassType iClass = TF2_GetPlayerClass(iClient); + if ((iClass == TFClass_Pyro || iClass == TFClass_Unknown)) return; + + TF2_SetPlayerClass(iClient, TFClass_Pyro, false, true); + TF2_RespawnPlayer(iClient); +} + +/** Called when player dies. Updates game state and executes kill commands. */ +public void OnPlayerDeath(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + if (!RoundStarted) return; + + SetRandomSeed(view_as(GetGameTime())); + + int iAttacker = GetClientOfUserId(hEvent.GetInt("attacker")); + int iVictim = GetClientOfUserId(hEvent.GetInt("userid")); + + if (!IsValidClient(iVictim)) return; + + StealInfo[iVictim].stoleRocket = false; + StealInfo[iVictim].rocketsStolen = 0; + + LastDeadClient = iVictim; + LastDeadTeam = GetClientTeam(iVictim); + PlayerCount = CountAlivePlayers(); + + int iInflictor = hEvent.GetInt("inflictor_entindex"); + int iIndex = FindRocketByEntity(iInflictor); + + if (iIndex == -1) return; + + int iClass = RocketClass[iIndex]; + int iTarget = EntRefToEntIndex(RocketTarget[iIndex]); + float fSpeed = RocketSpeed[iIndex]; + float fMphSpeed = RocketMphSpeed[iIndex]; + int iDeflections = RocketDeflections[iIndex]; + + if ((RocketInstanceFlags[iIndex] & RocketFlag_OnExplodeCmd) && !(RocketInstanceFlags[iIndex] & RocketFlag_Exploded)) + { + ExecuteCommands(RocketClassCmdsOnExplode[iClass], iClass, iInflictor, iAttacker, iTarget, LastDeadClient, fSpeed, iDeflections, fMphSpeed); + RocketInstanceFlags[iIndex] |= RocketFlag_Exploded; + } + + if (TestFlags(RocketInstanceFlags[iIndex], RocketFlag_OnKillCmd)) + { + ExecuteCommands(RocketClassCmdsOnKill[iClass], iClass, iInflictor, iAttacker, iTarget, LastDeadClient, fSpeed, iDeflections, fMphSpeed); + } +} + +/** Called when player receives inventory. Removes non-flamethrower weapons. */ +public void OnPlayerInventory(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + int iClient = GetClientOfUserId(hEvent.GetInt("userid")); + + if (!IsValidClient(iClient)) return; + for (int iSlot = 1; iSlot < 5; iSlot++) + { + int iEntity = GetPlayerWeaponSlot(iClient, iSlot); + if (iEntity != -1) RemoveEdict(iEntity); + } +} + +/** Blocks primary attack when dodgeball is enabled. */ +public Action OnPlayerRunCmd(int iClient, int &iButtons, int &iImpulse, float fVelocity[3], float fAngles[3], int &iWeapon) +{ + if (Enabled) iButtons &= ~IN_ATTACK; + return Plugin_Continue; +} + +/** Handles custom music for round start, win, and lose events. */ +public Action OnBroadcastAudio(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + if (!MusicEnabled) return Plugin_Continue; + + char strSound[PLATFORM_MAX_PATH]; + hEvent.GetString("sound", strSound, sizeof(strSound)); + int iTeam = hEvent.GetInt("team"); + + if (StrEqual(strSound, "Announcer.AM_RoundStartRandom")) + { + if (!UseWebPlayer) + { + if (!Music[Music_Gameplay]) return Plugin_Continue; + EmitSoundToAll(MusicPath[Music_Gameplay], SOUND_FROM_PLAYER, SNDCHAN_MUSIC); + return Plugin_Handled; + } + else + { + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsValidClientEx(iClient)) continue; + ShowHiddenMOTDPanel(iClient, "MusicPlayerStart", WebPlayerUrl); + } + return Plugin_Handled; + } + } + else if (StrEqual(strSound, "Game.YourTeamWon")) + { + if (!Music[Music_RoundWin]) return Plugin_Continue; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!(IsValidClientEx(iClient) && (iTeam == GetClientTeam(iClient)))) continue; + EmitSoundToClient(iClient, MusicPath[Music_RoundWin]); + } + return Plugin_Handled; + } + else if (StrEqual(strSound, "Game.YourTeamLost")) + { + if (!Music[Music_RoundLose]) return Plugin_Continue; + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!(IsValidClientEx(iClient) && (iTeam == GetClientTeam(iClient)))) continue; + EmitSoundToClient(iClient, MusicPath[Music_RoundLose]); + } + return Plugin_Handled; + } + + return Plugin_Continue; +} + +/** Called when a projectile is deflected. Updates deflection count and drag state. */ +public Action OnObjectDeflected(Event hEvent, const char[] strEventName, bool bDontBroadcast) +{ + int iEntity = hEvent.GetInt("object_entindex"); + int iIndex = FindRocketByEntity(iEntity); + if (iIndex == -1) return Plugin_Continue; + + RocketEventDeflections[iIndex]++; + + int iLauncher = GetEntPropEnt(iEntity, Prop_Send, "m_hLauncher"); + if (iLauncher != -1) + { + SetEntPropEnt(iEntity, Prop_Send, "m_hOriginalLauncher", iLauncher); + } + + if (RocketInstanceState[iIndex] & RocketState_Delayed) + { + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] & ~RocketState_Delayed)); + RocketSpeed[iIndex] = CalculateRocketSpeed(RocketClass[iIndex], CalculateModifier(RocketClass[iIndex], RocketDeflections[iIndex])); + } + + if (RocketInstanceFlags[iIndex] & RocketFlag_ResetBounces) + { + RocketBounces[iIndex] = 0; + } + + if (RocketInstanceFlags[iIndex] & RocketFlag_IsNeutral) + { + SetEntProp(iEntity, Prop_Send, "m_iTeamNum", 1, 1); + } + + return Plugin_Continue; +} + +/** + * This hook runs every server frame. + * It's responsible for the smooth "homing" behaviour. + * "legacy homing" is handled by the 20Hz timer OnDodgeBallGameFrame. + */ +public void OnGameFrame() +{ + if (!Enabled || !RoundStarted) return; + + int iIndex = -1; + while ((iIndex = FindNextValidRocket(iIndex)) != -1) + { + if (RocketClassBehaviour[RocketClass[iIndex]] == Behaviour_Homing) + { + HomingRocketThink(iIndex); + } + } +} + +/** + * Cleans up client state when they disconnect. + * + * @param iClient Client index that disconnected. + */ +public void OnClientDisconnect(int iClient) +{ + StealInfo[iClient].stoleRocket = false; + StealInfo[iClient].rocketsStolen = 0; + + if (iClient == LastDeadClient) + { + LastDeadClient = 0; + } + + if (iClient == LastStealer) + { + LastStealer = 0; + } +} + +/** + * SDKHook callback for rocket collision detection. + * Handles initial checks before bouncing and player collision. + * + * @param iEntity The rocket entity. + * @param iOther The entity being touched. + * @return Plugin_Handled to block touch, Plugin_Continue otherwise. + */ +public Action OnStartTouch(int iEntity, int iOther) +{ + int iIndex = FindRocketByEntity(iEntity); + + if (iIndex == -1) return Plugin_Continue; + if (iOther > 0 && iOther <= MaxClients) + { + if (RocketDeflections[iIndex] == 0) + { + SetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity", 0); + SetEntPropEnt(iEntity, Prop_Send, "m_hOriginalLauncher", -1); + SetEntPropEnt(iEntity, Prop_Send, "m_hLauncher", -1); + } + return Plugin_Continue; + } + + int iClass = RocketClass[iIndex]; + if (RocketBounces[iIndex] >= RocketClassMaxBounces[iClass]) + { + if (RocketDeflections[iIndex] == 0) + { + SetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity", 0); + SetEntPropEnt(iEntity, Prop_Send, "m_hOriginalLauncher", -1); + SetEntPropEnt(iEntity, Prop_Send, "m_hLauncher", -1); + } + return Plugin_Continue; + } + + SDKHook(iEntity, SDKHook_Touch, OnTouch); + return Plugin_Handled; +} + +public Action OnTouch(int iEntity, int iOther) +{ + int iIndex = FindRocketByEntity(iEntity); + + if (iIndex == -1) + { + SDKUnhook(iEntity, SDKHook_Touch, OnTouch); + return Plugin_Continue; + } + + int iClass = RocketClass[iIndex]; + + float vOrigin[3]; + GetEntPropVector(iEntity, Prop_Data, "m_vecOrigin", vOrigin); + + float vAngles[3]; + GetEntPropVector(iEntity, Prop_Data, "m_angRotation", vAngles); + + float vVelocity[3]; + GetEntPropVector(iEntity, Prop_Data, "m_vecAbsVelocity", vVelocity); + + Handle hTrace = TR_TraceRayFilterEx(vOrigin, vAngles, MASK_SHOT, RayType_Infinite, TEF_ExcludeEntity, iEntity); + + if (!TR_DidHit(hTrace)) + { + delete hTrace; + return Plugin_Continue; + } + + float vNormal[3]; + TR_GetPlaneNormal(hTrace, vNormal); + delete hTrace; + + float dotProduct = GetVectorDotProduct(vNormal, vVelocity); + ScaleVector(vNormal, dotProduct); + ScaleVector(vNormal, 2.0); + + float vBounceVec[3]; + SubtractVectors(vVelocity, vNormal, vBounceVec); + int iOwner = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity"); + if (!(RocketInstanceFlags[iIndex] & RocketFlag_CrawlBounce)) + { + if (IsValidClient(iOwner, true)) + { + float fOwnerAngles[3]; + GetClientEyeAngles(iOwner, fOwnerAngles); + + if (fOwnerAngles[0] >= RocketClassBounceForceAngle[iClass]) + ScaleVector(vBounceVec, RocketClassBounceScale[iClass] * RocketClassBounceForceScale[iClass]); + else + ScaleVector(vBounceVec, RocketClassBounceScale[iClass]); + } + else + { + ScaleVector(vBounceVec, RocketClassBounceScale[iClass]); + } + } + else + { + float fBounceScale = RocketClassBounceScale[iClass]; + + if (IsValidClient(iOwner, true)) + { + float fOwnerAngles[3]; + GetClientEyeAngles(iOwner, fOwnerAngles); + if (fOwnerAngles[0] >= RocketClassBounceForceAngle[iClass]) + fBounceScale *= RocketClassBounceForceScale[iClass]; + } + + // Crawl mode: preserve horizontal carry, suppress/clamp upward pop. + vBounceVec[0] *= fBounceScale; + vBounceVec[1] *= fBounceScale; + vBounceVec[2] *= fBounceScale * RocketClassCrawlBounceScale[iClass]; + + if (RocketClassCrawlBounceMaxUp[iClass] > 0.0 && vBounceVec[2] > RocketClassCrawlBounceMaxUp[iClass]) + vBounceVec[2] = RocketClassCrawlBounceMaxUp[iClass]; + } + + + float vNewAngles[3]; + GetVectorAngles(vBounceVec, vNewAngles); + + float vNewAnglesRef[3]; CopyVectors(vNewAngles, vNewAnglesRef); + float vBounceVecRef[3]; + CopyVectors(vBounceVec, vBounceVecRef); + + Action aResult = Forward_OnRocketBouncePre(iIndex, iEntity, vNewAnglesRef, vBounceVecRef); + if (aResult == Plugin_Stop || aResult == Plugin_Handled) + { + SDKUnhook(iEntity, SDKHook_Touch, OnTouch); + return Plugin_Continue; + } + else if (aResult == Plugin_Changed) + { + CopyVectors(vNewAnglesRef, vNewAngles); + CopyVectors(vBounceVecRef, vBounceVec); + } + + TeleportEntity(iEntity, NULL_VECTOR, vNewAngles, vBounceVec); + RocketBounces[iIndex]++; + if (RocketInstanceFlags[iIndex] & RocketFlag_NoBounceDrags) + { + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] & ~RocketState_CanDrag)); + } + + if ((RocketInstanceState[iIndex] & RocketState_Delayed) || (RocketInstanceFlags[iIndex] & RocketFlag_KeepDirection)) + { + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] | RocketState_Bouncing)); + } + else + { + float fDirection[3]; + GetAngleVectors(vNewAngles,fDirection,NULL_VECTOR,NULL_VECTOR); + CopyVectors(fDirection, RocketDirection[iIndex]); + } + + Forward_OnRocketBounce(iIndex, iEntity); + SDKUnhook(iEntity, SDKHook_Touch, OnTouch); + + return Plugin_Handled; +} + +public Action OnTFExplosion(const char[] strTEName, const int[] iClients, int iNumClients, float fDelay) +{ + static int bIgnoreHook; + if (!Enabled) + { + return Plugin_Continue; + } + + if (bIgnoreHook) + { + bIgnoreHook = false; + return Plugin_Continue; + } + + TE_Start("TFExplosion"); + + float vecNormal[3]; TE_ReadVector("m_vecNormal", vecNormal); + TE_WriteFloat("m_vecOrigin[0]", TE_ReadFloat("m_vecOrigin[0]")); + TE_WriteFloat("m_vecOrigin[1]", TE_ReadFloat("m_vecOrigin[1]")); + TE_WriteFloat("m_vecOrigin[2]", TE_ReadFloat("m_vecOrigin[2]")); + TE_WriteVector("m_vecNormal", vecNormal); + TE_WriteNum("m_iWeaponID", TE_ReadNum("m_iWeaponID")); + TE_WriteNum("entindex", TE_ReadNum("entindex")); + TE_WriteNum("m_nDefID", -1); + TE_WriteNum("m_nSound", TE_ReadNum("m_nSound")); + TE_WriteNum("m_iCustomParticleIndex", TE_ReadNum("m_iCustomParticleIndex")); + + bIgnoreHook = true; + TE_Send(iClients, iNumClients, fDelay); + return Plugin_Stop; + +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_natives.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_natives.inc new file mode 100644 index 00000000..e9dede24 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_natives.inc @@ -0,0 +1,772 @@ +#if defined _dodgeball_natives_included + #endinput +#endif +#define _dodgeball_natives_included + +/** + * Implements the native functions exposed by the TF2 Dodgeball plugin. + * These natives allow other plugins to interact with and control the gamemode. + */ + +// ********************************************************************************* +// NATIVES - GENERAL & STATE +// ********************************************************************************* + +public any Native_IsValidRocket(Handle hPlugin, int iNumParams) { + return IsValidRocket(GetNativeCell(1)); +} + +public any Native_FindRocketByEntity(Handle hPlugin, int iNumParams) { + return FindRocketByEntity(GetNativeCell(1)); +} + +public any Native_IsDodgeballEnabled(Handle hPlugin, int iNumParams) { + return Enabled; +} + +// ********************************************************************************* +// NATIVES - ROCKET GETTERS/SETTERS +// ********************************************************************************* + +public any Native_GetRocketEntity(Handle hPlugin, int iNumParams) { + return EntRefToEntIndex(RocketEntity[GetNativeCell(1)]); +} + +public any Native_SetRocketEntity(Handle hPlugin, int iNumParams) { + RocketEntity[GetNativeCell(1)] = EntIndexToEntRef(GetNativeCell(2)); + return 0; +} + +public any Native_GetRocketFlags(Handle hPlugin, int iNumParams) { + return RocketInstanceFlags[GetNativeCell(1)]; +} + +public any Native_SetRocketFlags(Handle hPlugin, int iNumParams) { + RocketInstanceFlags[GetNativeCell(1)] = view_as(GetNativeCell(2)); + return 0; +} + +public any Native_GetRocketTarget(Handle hPlugin, int iNumParams) { + return EntRefToEntIndex(RocketTarget[GetNativeCell(1)]); +} + +public any Native_SetRocketTarget(Handle hPlugin, int iNumParams) { + RocketTarget[GetNativeCell(1)] = EntIndexToEntRef(GetNativeCell(2)); + return 0; +} + +public any Native_GetRocketEventDeflections(Handle hPlugin, int iNumParams) { + return RocketEventDeflections[GetNativeCell(1)]; +} + +public any Native_SetRocketEventDeflections(Handle hPlugin, int iNumParams) { + RocketEventDeflections[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketDeflections(Handle hPlugin, int iNumParams) { + return RocketDeflections[GetNativeCell(1)]; +} + +public any Native_SetRocketDeflections(Handle hPlugin, int iNumParams) { + RocketDeflections[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClass(Handle hPlugin, int iNumParams) { + return RocketClass[GetNativeCell(1)]; +} + +public any Native_SetRocketClass(Handle hPlugin, int iNumParams) { + RocketClass[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketSpeed(Handle hPlugin, int iNumParams) { + return view_as(RocketSpeed[GetNativeCell(1)]); +} + +public any Native_SetRocketSpeed(Handle hPlugin, int iNumParams) { + RocketSpeed[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketMphSpeed(Handle hPlugin, int iNumParams) { + return view_as(RocketMphSpeed[GetNativeCell(1)]); +} + +public any Native_SetRocketMphSpeed(Handle hPlugin, int iNumParams) { + RocketMphSpeed[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketDirection(Handle hPlugin, int iNumParams) { + SetNativeArray(2, RocketDirection[GetNativeCell(1)], 3); + return 0; +} + +public any Native_SetRocketDirection(Handle hPlugin, int iNumParams) { + float fDirection[3]; + GetNativeArray(2, fDirection, sizeof(fDirection)); + CopyVectors(fDirection, RocketDirection[GetNativeCell(1)]); + return 0; +} + +public any Native_GetRocketLastDeflectionTime(Handle hPlugin, int iNumParams) { + return view_as(RocketLastDeflectionTime[GetNativeCell(1)]); +} + +public any Native_SetRocketLastDeflectionTime(Handle hPlugin, int iNumParams) { + RocketLastDeflectionTime[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketLastBeepTime(Handle hPlugin, int iNumParams) { + return view_as(RocketLastBeepTime[GetNativeCell(1)]); +} + +public any Native_SetRocketLastBeepTime(Handle hPlugin, int iNumParams) { + RocketLastBeepTime[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketCount(Handle hPlugin, int iNumParams) { + return RocketCount; +} + +public any Native_GetLastSpawnTime(Handle hPlugin, int iNumParams) { + return view_as(LastSpawnTime[GetNativeCell(1)]); +} + +public any Native_GetRocketBounces(Handle hPlugin, int iNumParams) { + return RocketBounces[GetNativeCell(1)]; +} + +public any Native_SetRocketBounces(Handle hPlugin, int iNumParams) { + RocketBounces[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketState(Handle hPlugin, int iNumParams) { + return RocketInstanceState[GetNativeCell(1)]; +} + +public any Native_SetRocketState(Handle hPlugin, int iNumParams) { + Internal_SetRocketState(GetNativeCell(1), view_as(GetNativeCell(2))); + return 0; +} + +// ********************************************************************************* +// NATIVES - ROCKET CLASS GETTERS/SETTERS +// ********************************************************************************* + +public any Native_GetRocketClassCount(Handle hPlugin, int iNumParams) { + return RocketClassCount; +} + +public any Native_SetRocketClassCount(Handle hPlugin, int iNumParams) { + RocketClassCount = GetNativeCell(1); + return 0; +} + +public any Native_GetRocketClassBehaviour(Handle hPlugin, int iNumParams) { + return RocketClassBehaviour[GetNativeCell(1)]; +} + +public any Native_SetRocketClassBehaviour(Handle hPlugin, int iNumParams) { + RocketClassBehaviour[GetNativeCell(1)] = view_as(GetNativeCell(2)); + return 0; +} + +public any Native_GetRocketClassFlags(Handle hPlugin, int iNumParams) { + return RocketClassFlags[GetNativeCell(1)]; +} + +public any Native_SetRocketClassFlags(Handle hPlugin, int iNumParams) { + RocketClassFlags[GetNativeCell(1)] = view_as(GetNativeCell(2)); + return 0; +} + +public any Native_GetRocketClassDamage(Handle hPlugin, int iNumParams) { + return view_as(RocketClassDamage[GetNativeCell(1)]); +} + +public any Native_SetRocketClassDamage(Handle hPlugin, int iNumParams) { + RocketClassDamage[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassDamageIncrement(Handle hPlugin, int iNumParams) { + return view_as(RocketClassDamageIncrement[GetNativeCell(1)]); +} + +public any Native_SetRocketClassDamageIncrement(Handle hPlugin, int iNumParams) { + RocketClassDamageIncrement[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassSpeed(Handle hPlugin, int iNumParams) { + return view_as(RocketClassSpeed[GetNativeCell(1)]); +} + +public any Native_SetRocketClassSpeed(Handle hPlugin, int iNumParams) { + RocketClassSpeed[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassSpeedIncrement(Handle hPlugin, int iNumParams) { + return view_as(RocketClassSpeedIncrement[GetNativeCell(1)]); +} + +public any Native_SetRocketClassSpeedIncrement(Handle hPlugin, int iNumParams) { + RocketClassSpeedIncrement[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassSpeedLimit(Handle hPlugin, int iNumParams) { + return view_as(RocketClassSpeedLimit[GetNativeCell(1)]); +} + +public any Native_SetRocketClassSpeedLimit(Handle hPlugin, int iNumParams) { + RocketClassSpeedLimit[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassTurnRate(Handle hPlugin, int iNumParams) { + return view_as(RocketClassTurnRate[GetNativeCell(1)]); +} + +public any Native_SetRocketClassTurnRate(Handle hPlugin, int iNumParams) { + RocketClassTurnRate[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassTurnRateIncrement(Handle hPlugin, int iNumParams) { + return view_as(RocketClassTurnRateIncrement[GetNativeCell(1)]); +} + +public any Native_SetRocketClassTurnRateIncrement(Handle hPlugin, int iNumParams) { + RocketClassTurnRateIncrement[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassTurnRateLimit(Handle hPlugin, int iNumParams) { + return view_as(RocketClassTurnRateLimit[GetNativeCell(1)]); +} + +public any Native_SetRocketClassTurnRateLimit(Handle hPlugin, int iNumParams) { + RocketClassTurnRateLimit[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassElevationRate(Handle hPlugin, int iNumParams) { + return view_as(RocketClassElevationRate[GetNativeCell(1)]); +} + +public any Native_SetRocketClassElevationRate(Handle hPlugin, int iNumParams) { + RocketClassElevationRate[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassElevationLimit(Handle hPlugin, int iNumParams) { + return view_as(RocketClassElevationLimit[GetNativeCell(1)]); +} + +public any Native_SetRocketClassElevationLimit(Handle hPlugin, int iNumParams) { + RocketClassElevationLimit[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassRocketsModifier(Handle hPlugin, int iNumParams) { + return view_as(RocketClassRocketsModifier[GetNativeCell(1)]); +} + +public any Native_SetRocketClassRocketsModifier(Handle hPlugin, int iNumParams) { + RocketClassRocketsModifier[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassPlayerModifier(Handle hPlugin, int iNumParams) { + return view_as(RocketClassPlayerModifier[GetNativeCell(1)]); +} + +public any Native_SetRocketClassPlayerModifier(Handle hPlugin, int iNumParams) { + RocketClassPlayerModifier[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassControlDelay(Handle hPlugin, int iNumParams) { + return view_as(RocketClassControlDelay[GetNativeCell(1)]); +} + +public any Native_SetRocketClassControlDelay(Handle hPlugin, int iNumParams) { + RocketClassControlDelay[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassDragTimeMin(Handle hPlugin, int iNumParams) { + return view_as(RocketClassDragTimeMin[GetNativeCell(1)]); +} + +public any Native_SetRocketClassDragTimeMin(Handle hPlugin, int iNumParams) { + RocketClassDragTimeMin[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassDragTimeMax(Handle hPlugin, int iNumParams) { + return view_as(RocketClassDragTimeMax[GetNativeCell(1)]); +} + +public any Native_SetRocketClassDragTimeMax(Handle hPlugin, int iNumParams) { + RocketClassDragTimeMax[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassMaxBounces(Handle hPlugin, int iNumParams) { + return RocketClassMaxBounces[GetNativeCell(1)]; +} + +public any Native_SetRocketClassMaxBounces(Handle hPlugin, int iNumParams) { + RocketClassMaxBounces[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassName(Handle hPlugin, int iNumParams) { + SetNativeString(2, RocketClassName[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public any Native_SetRocketClassName(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + int iMaxLen; + GetNativeStringLength(2, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(2, strBuffer, iMaxLen + 1); + strcopy(RocketClassName[iClass], sizeof(RocketClassName[]), strBuffer); + return 0; +} + +public any Native_GetRocketClassLongName(Handle hPlugin, int iNumParams) { + SetNativeString(2, RocketClassLongName[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public any Native_SetRocketClassLongName(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + int iMaxLen; + GetNativeStringLength(2, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(2, strBuffer, iMaxLen + 1); + strcopy(RocketClassLongName[iClass], sizeof(RocketClassLongName[]), strBuffer); + return 0; +} + +public any Native_GetRocketClassModel(Handle hPlugin, int iNumParams) { + SetNativeString(2, RocketClassModel[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public any Native_SetRocketClassModel(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + int iMaxLen; + GetNativeStringLength(2, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(2, strBuffer, iMaxLen + 1); + strcopy(RocketClassModel[iClass], sizeof(RocketClassModel[]), strBuffer); + return 0; +} + +public any Native_GetRocketClassBeepInterval(Handle hPlugin, int iNumParams) { + return view_as(RocketClassBeepInterval[GetNativeCell(1)]); +} + +public any Native_SetRocketClassBeepInterval(Handle hPlugin, int iNumParams) { + RocketClassBeepInterval[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassSpawnSound(Handle hPlugin, int iNumParams) { + SetNativeString(2, RocketClassSpawnSound[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public any Native_SetRocketClassSpawnSound(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + int iMaxLen; + GetNativeStringLength(2, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(2, strBuffer, iMaxLen + 1); + strcopy(RocketClassSpawnSound[iClass], sizeof(RocketClassSpawnSound[]), strBuffer); + return 0; +} + +public any Native_GetRocketClassBeepSound(Handle hPlugin, int iNumParams) { + SetNativeString(2, RocketClassBeepSound[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public any Native_SetRocketClassBeepSound(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + int iMaxLen; + GetNativeStringLength(2, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(2, strBuffer, iMaxLen + 1); + strcopy(RocketClassBeepSound[iClass], sizeof(RocketClassBeepSound[]), strBuffer); + return 0; +} + +public any Native_GetRocketClassAlertSound(Handle hPlugin, int iNumParams) { + SetNativeString(2, RocketClassAlertSound[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public any Native_SetRocketClassAlertSound(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + int iMaxLen; + GetNativeStringLength(2, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(2, strBuffer, iMaxLen + 1); + strcopy(RocketClassAlertSound[iClass], sizeof(RocketClassAlertSound[]), strBuffer); + return 0; +} + +public any Native_GetRocketClassCritChance(Handle hPlugin, int iNumParams) { + return view_as(RocketClassCritChance[GetNativeCell(1)]); +} + +public any Native_SetRocketClassCritChance(Handle hPlugin, int iNumParams) { + RocketClassCritChance[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassTargetWeight(Handle hPlugin, int iNumParams) { + return view_as(RocketClassTargetWeight[GetNativeCell(1)]); +} + +public any Native_SetRocketClassTargetWeight(Handle hPlugin, int iNumParams) { + RocketClassTargetWeight[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetRocketClassBounceScale(Handle hPlugin, int iNumParams) { + return view_as(RocketClassBounceScale[GetNativeCell(1)]); +} + +public any Native_SetRocketClassBounceScale(Handle hPlugin, int iNumParams) { + RocketClassBounceScale[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +// ********************************************************************************* +// NATIVES - SPAWNER GETTERS/SETTERS +// ********************************************************************************* + +public any Native_GetSpawnersName(Handle hPlugin, int iNumParams) { + SetNativeString(2, SpawnersName[GetNativeCell(1)], GetNativeCell(3)); + return 0; +} + +public any Native_SetSpawnersName(Handle hPlugin, int iNumParams) { + int iSpawnerClass = GetNativeCell(1); + int iMaxLen; + GetNativeStringLength(2, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(2, strBuffer, iMaxLen + 1); + strcopy(SpawnersName[iSpawnerClass], sizeof(SpawnersName[]), strBuffer); + return 0; +} + +public any Native_GetSpawnersMaxRockets(Handle hPlugin, int iNumParams) { + return SpawnersMaxRockets[GetNativeCell(1)]; +} + +public any Native_SetSpawnersMaxRockets(Handle hPlugin, int iNumParams) { + SpawnersMaxRockets[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetSpawnersInterval(Handle hPlugin, int iNumParams) { + return view_as(SpawnersInterval[GetNativeCell(1)]); +} + +public any Native_SetSpawnersInterval(Handle hPlugin, int iNumParams) { + SpawnersInterval[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetSpawnersChancesTable(Handle hPlugin, int iNumParams) { + return CloneHandle(SpawnersChancesTable[GetNativeCell(1)], hPlugin); +} + +public any Native_SetSpawnersChancesTable(Handle hPlugin, int iNumParams) { + int iSpawnerClass = GetNativeCell(1); + ArrayList hTable = view_as(GetNativeCell(2)); + ArrayList hClonedTable = view_as(CloneHandle(hTable, GetMyHandle())); + delete SpawnersChancesTable[iSpawnerClass]; + SpawnersChancesTable[iSpawnerClass] = hClonedTable; + return 0; +} + +public any Native_GetSpawnersCount(Handle hPlugin, int iNumParams) { + return SpawnersCount; +} + +public any Native_SetSpawnersCount(Handle hPlugin, int iNumParams) { + SpawnersCount = GetNativeCell(1); + return 0; +} + +// ********************************************************************************* +// NATIVES - SPAWN POINT GETTERS/SETTERS +// ********************************************************************************* + +public any Native_GetCurrentRedSpawn(Handle hPlugin, int iNumParams) { + return CurrentRedSpawn; +} + +public any Native_SetCurrentRedSpawn(Handle hPlugin, int iNumParams) { + CurrentRedSpawn = GetNativeCell(1); + return 0; +} + +public any Native_GetSpawnPointsRedCount(Handle hPlugin, int iNumParams) { + return SpawnPointsRedCount; +} + +public any Native_SetSpawnPointsRedCount(Handle hPlugin, int iNumParams) { + SpawnPointsRedCount = GetNativeCell(1); + return 0; +} + +public any Native_GetSpawnPointsRedClass(Handle hPlugin, int iNumParams) { + return SpawnPointsRedClass[GetNativeCell(1)]; +} + +public any Native_SetSpawnPointsRedClass(Handle hPlugin, int iNumParams) { + SpawnPointsRedClass[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetSpawnPointsRedEntity(Handle hPlugin, int iNumParams) { + return SpawnPointsRedEntity[GetNativeCell(1)]; +} + +public any Native_SetSpawnPointsRedEntity(Handle hPlugin, int iNumParams) { + SpawnPointsRedEntity[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetCurrentBluSpawn(Handle hPlugin, int iNumParams) { + return CurrentBluSpawn; +} + +public any Native_SetCurrentBluSpawn(Handle hPlugin, int iNumParams) { + CurrentBluSpawn = GetNativeCell(1); + return 0; +} + +public any Native_GetSpawnPointsBluCount(Handle hPlugin, int iNumParams) { + return SpawnPointsBluCount; +} + +public any Native_SetSpawnPointsBluCount(Handle hPlugin, int iNumParams) { + SpawnPointsBluCount = GetNativeCell(1); + return 0; +} + +public any Native_GetSpawnPointsBluClass(Handle hPlugin, int iNumParams) { + return SpawnPointsBluClass[GetNativeCell(1)]; +} + +public any Native_SetSpawnPointsBluClass(Handle hPlugin, int iNumParams) { + SpawnPointsBluClass[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +public any Native_GetSpawnPointsBluEntity(Handle hPlugin, int iNumParams) { + return SpawnPointsBluEntity[GetNativeCell(1)]; +} + +public any Native_SetSpawnPointsBluEntity(Handle hPlugin, int iNumParams) { + SpawnPointsBluEntity[GetNativeCell(1)] = GetNativeCell(2); + return 0; +} + +// ********************************************************************************* +// NATIVES - GAMEPLAY GETTERS/SETTERS +// ********************************************************************************* + +public any Native_GetRoundStarted(Handle hPlugin, int iNumParams) { + return RoundStarted; +} + +public any Native_GetRoundCount(Handle hPlugin, int iNumParams) { + return RoundCount; +} + +public any Native_GetRocketsFired(Handle hPlugin, int iNumParams) { + return RocketsFired; +} + +public any Native_GetNextSpawnTime(Handle hPlugin, int iNumParams) { + return view_as(NextSpawnTime); +} + +public any Native_SetNextSpawnTime(Handle hPlugin, int iNumParams) { + NextSpawnTime = GetNativeCell(1); + return 0; +} + +public any Native_GetLastDeadTeam(Handle hPlugin, int iNumParams) { + return LastDeadTeam; +} + +public any Native_GetLastDeadClient(Handle hPlugin, int iNumParams) { + return LastDeadClient; +} + +public any Native_GetLastStealer(Handle hPlugin, int iNumParams) { + return LastStealer; +} + +public any Native_GetStealInfo(Handle hPlugin, int iNumParams) { + int iClient = GetNativeCell(1); + if (iClient < 1 || iClient > MaxClients) { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", iClient); + } + SetNativeCellRef(2, StealInfo[iClient].stoleRocket); + SetNativeCellRef(3, StealInfo[iClient].rocketsStolen); + return 0; +} + +public any Native_SetStealInfo(Handle hPlugin, int iNumParams) { + int iClient = GetNativeCell(1); + if (iClient < 1 || iClient > MaxClients) { + return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", iClient); + } + StealInfo[iClient].stoleRocket = view_as(GetNativeCell(2)); + StealInfo[iClient].rocketsStolen = GetNativeCell(3); + return 0; +} + +// ********************************************************************************* +// NATIVES - DATAPACK-BASED GETTERS/SETTERS +// ********************************************************************************* + +public any Native_GetRocketClassCmdsOnSpawn(Handle hPlugin, int iNumParams) { + return CloneHandle(RocketClassCmdsOnSpawn[GetNativeCell(1)], hPlugin); +} + +public any Native_SetRocketClassCmdsOnSpawn(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + DataPack hCmds = view_as(GetNativeCell(2)); + DataPack hClonedPack = view_as(CloneHandle(hCmds, GetMyHandle())); + delete RocketClassCmdsOnSpawn[iClass]; + RocketClassCmdsOnSpawn[iClass] = hClonedPack; + return 0; +} + +public any Native_GetRocketClassCmdsOnDeflect(Handle hPlugin, int iNumParams) { + return CloneHandle(RocketClassCmdsOnDeflect[GetNativeCell(1)], hPlugin); +} + +public any Native_SetRocketClassCmdsOnDeflect(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + DataPack hCmds = view_as(GetNativeCell(2)); + DataPack hClonedPack = view_as(CloneHandle(hCmds, GetMyHandle())); + delete RocketClassCmdsOnDeflect[iClass]; + RocketClassCmdsOnDeflect[iClass] = hClonedPack; + return 0; +} + +public any Native_GetRocketClassCmdsOnKill(Handle hPlugin, int iNumParams) { + return CloneHandle(RocketClassCmdsOnKill[GetNativeCell(1)], hPlugin); +} + +public any Native_SetRocketClassCmdsOnKill(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + DataPack hCmds = view_as(GetNativeCell(2)); + DataPack hClonedPack = view_as(CloneHandle(hCmds, GetMyHandle())); + delete RocketClassCmdsOnKill[iClass]; + RocketClassCmdsOnKill[iClass] = hClonedPack; + return 0; +} + +public any Native_GetRocketClassCmdsOnExplode(Handle hPlugin, int iNumParams) { + return CloneHandle(RocketClassCmdsOnExplode[GetNativeCell(1)], hPlugin); +} + +public any Native_SetRocketClassCmdsOnExplode(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + DataPack hCmds = view_as(GetNativeCell(2)); + DataPack hClonedPack = view_as(CloneHandle(hCmds, GetMyHandle())); + delete RocketClassCmdsOnExplode[iClass]; + RocketClassCmdsOnExplode[iClass] = hClonedPack; + return 0; +} + +public any Native_GetRocketClassCmdsOnNoTarget(Handle hPlugin, int iNumParams) { + return CloneHandle(RocketClassCmdsOnNoTarget[GetNativeCell(1)], hPlugin); +} + +public any Native_SetRocketClassCmdsOnNoTarget(Handle hPlugin, int iNumParams) { + int iClass = GetNativeCell(1); + DataPack hCmds = view_as(GetNativeCell(2)); + DataPack hClonedPack = view_as(CloneHandle(hCmds, GetMyHandle())); + delete RocketClassCmdsOnNoTarget[iClass]; + RocketClassCmdsOnNoTarget[iClass] = hClonedPack; + return 0; +} + +// ********************************************************************************* +// NATIVES - ACTION FUNCTIONS +// ********************************************************************************* + +public any Native_CreateRocket(Handle hPlugin, int iNumParams) { + CreateRocket(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3), GetNativeCell(4)); + return 0; +} + +public any Native_DestroyRocket(Handle hPlugin, int iNumParams) { + DestroyRocket(GetNativeCell(1)); + return 0; +} + +public any Native_DestroyRockets(Handle hPlugin, int iNumParams) { + DestroyRockets(); + return 0; +} + +public any Native_DestroyRocketClasses(Handle hPlugin, int iNumParams) { + DestroyRocketClasses(); + return 0; +} + +public any Native_DestroySpawners(Handle hPlugin, int iNumParams) { + DestroySpawners(); + return 0; +} + +public any Native_ParseConfigurations(Handle hPlugin, int iNumParams) { + int iMaxLen; + GetNativeStringLength(1, iMaxLen); + char[] strBuffer = new char[iMaxLen + 1]; + GetNativeString(1, strBuffer, iMaxLen + 1); + ParseConfigurations(strBuffer); + return 0; +} + +public any Native_PopulateSpawnPoints(Handle hPlugin, int iNumParams) { + PopulateSpawnPoints(); + return 0; +} + +public any Native_HomingRocketThink(Handle hPlugin, int iNumParams) { + HomingRocketThink(GetNativeCell(1)); + return 0; +} + +public any Native_RocketLegacyThink(Handle hPlugin, int iNumParams) { + RocketLegacyThink(GetNativeCell(1)); + return 0; +} \ No newline at end of file diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_rockets.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_rockets.inc new file mode 100644 index 00000000..66df8cb7 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_rockets.inc @@ -0,0 +1,713 @@ +#if defined _dodgeball_rockets_included + #endinput +#endif +#define _dodgeball_rockets_included + +/** + * All logic directly related to creating, managing, and controlling rockets. + */ + +/** + * Creates and spawns a new rocket from a spawner. + * + * @param iSpawnerEntity The spawner entity to spawn from. + * @param iSpawnerClass The spawner class index (determines spawn chances). + * @param iTeam The team the rocket belongs to (TFTeam_Red or TFTeam_Blue). + * @param iClass Optional rocket class (-1 for random from spawner chances). + */ +void CreateRocket(int iSpawnerEntity, int iSpawnerClass, int iTeam, int iClass = -1) +{ + int iIndex = FindFreeRocketSlot(); + if (iIndex == -1) return; + + iClass = iClass == -1 ? GetRandomRocketClass(iSpawnerClass) : iClass; + RocketFlags iFlags = RocketClassFlags[iClass]; + + int iClassRef = iClass; + RocketFlags iFlagsRef = iFlags; + Action aResult = Forward_OnRocketCreatedPre(iIndex, iClassRef, iFlagsRef); + + if (aResult == Plugin_Stop || aResult == Plugin_Handled) + { + return; + } + else if (aResult == Plugin_Changed) + { + iClass = iClassRef; + iFlags = iFlagsRef; + } + + int iEntity = CreateEntityByName(TestFlags(iFlags, RocketFlag_IsAnimated) ? "tf_projectile_sentryrocket" : "tf_projectile_rocket"); + if (iEntity == -1) return; + + float fPosition[3], fAngles[3], fDirection[3]; + GetEntPropVector(iSpawnerEntity, Prop_Send, "m_vecOrigin", fPosition); + GetEntPropVector(iSpawnerEntity, Prop_Send, "m_angRotation", fAngles); + GetAngleVectors(fAngles, fDirection, NULL_VECTOR, NULL_VECTOR); + + SetEntProp(iEntity, Prop_Send, "m_bCritical", (GetURandomFloatRange(0.0, 100.0) <= RocketClassCritChance[iClass]) ? 1 : 0, 1); + SetEntProp(iEntity, Prop_Send, "m_iTeamNum", (TestFlags(iFlags, RocketFlag_IsNeutral)) ? 1 : iTeam, 1); + SetEntProp(iEntity, Prop_Send, "m_iDeflected", 0); + TeleportEntity(iEntity, fPosition, fAngles, view_as({0.0, 0.0, 0.0})); + + int iTargetTeam = (TestFlags(iFlags, RocketFlag_IsNeutral)) ? 0 : GetAnalogueTeam(iTeam); + int iTarget = SelectTarget(iTargetTeam); + int iRocketOwner = SelectTarget(GetAnalogueTeam(GetClientTeam(iTarget))); + + SetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity", iRocketOwner); + int iWeapon = GetPlayerWeaponSlot(iRocketOwner, TFWeaponSlot_Primary); + SetEntPropEnt(iEntity, Prop_Send, "m_hOriginalLauncher", iWeapon == -1 ? iEntity : iWeapon); + SetEntPropEnt(iEntity, Prop_Send, "m_hLauncher", iWeapon == -1 ? iEntity : iWeapon); + + float fModifier = CalculateModifier(iClass, 0); + RocketIsValid[iIndex] = true; + RocketInstanceFlags[iIndex] = iFlags; + RocketEntity[iIndex] = EntIndexToEntRef(iEntity); + RocketTarget[iIndex] = EntIndexToEntRef(iTarget); + RocketClass[iIndex] = iClass; + RocketDeflections[iIndex] = 0; + RocketEventDeflections[iIndex] = 0; + RocketBounces[iIndex] = 0; + RocketLastDeflectionTime[iIndex] = GetGameTime(); + RocketLastBeepTime[iIndex] = GetGameTime(); + RocketSpeed[iIndex] = CalculateRocketSpeed(iClass, fModifier); + RocketMphSpeed[iIndex] = RocketSpeed[iIndex] * HAMMER_TO_MPH; + Internal_SetRocketState(iIndex, RocketState_None); + + CopyVectors(fDirection, RocketDirection[iIndex]); + SetEntDataFloat(iEntity, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, CalculateRocketDamage(iClass, fModifier), true); + DispatchSpawn(iEntity); + + if (TestFlags(iFlags, RocketFlag_CustomModel)) + { + SetEntityModel(iEntity, RocketClassModel[iClass]); + UpdateRocketSkin(iEntity, iTeam, TestFlags(iFlags, RocketFlag_IsNeutral)); + } + + if (TestFlags(iFlags, RocketFlag_OnSpawnCmd)) + { + ExecuteCommands(RocketClassCmdsOnSpawn[iClass], iClass, iEntity, 0, iTarget, LastDeadClient, RocketSpeed[iIndex], 0, RocketMphSpeed[iIndex]); + } + + EmitRocketSound(RocketSound_Spawn, iClass, iEntity, iTarget, iFlags); + EmitRocketSound(RocketSound_Alert, iClass, iEntity, iTarget, iFlags); + + RocketCount++; + RocketsFired++; + LastSpawnTime[iIndex] = GetGameTime(); + NextSpawnTime = GetGameTime() + SpawnersInterval[iSpawnerClass]; + + SDKHook(iEntity, SDKHook_StartTouch, OnStartTouch); + Forward_OnRocketCreated(iIndex, iEntity); +} + +/** + * Destroys a rocket by its internal index. + * + * @param iIndex The rocket's internal index. + */ +void DestroyRocket(int iIndex) +{ + if (!IsValidRocket(iIndex)) return; + + int iEntity = EntRefToEntIndex(RocketEntity[iIndex]); + if (iEntity != INVALID_ENT_REFERENCE) + { + RemoveEntity(iEntity); + } + RocketIsValid[iIndex] = false; + RocketCount--; +} + +/** + * Destroys all active rockets. + */ +void DestroyRockets() +{ + for (int iIndex = 0; iIndex < MAX_ROCKETS; iIndex++) + { + DestroyRocket(iIndex); + } + RocketCount = 0; +} + +/** + * Checks if a rocket index is valid and the rocket exists. + * + * @param iIndex The rocket's internal index. + * @return True if valid, false otherwise. + */ +bool IsValidRocket(int iIndex) +{ + if (!((iIndex >= 0) && RocketIsValid[iIndex])) return false; + + if (EntRefToEntIndex(RocketEntity[iIndex]) != -1) return true; + + RocketIsValid[iIndex] = false; + RocketCount--; + + return false; +} + +/** + * Finds the next valid rocket after the given index. + * + * @param iIndex Starting index to search from. + * @return Next valid rocket index, or -1 if none found. + */ +int FindNextValidRocket(int iIndex) +{ + for (int iCurrent = iIndex + 1; iCurrent < MAX_ROCKETS; iCurrent++) + { + if (IsValidRocket(iCurrent)) return iCurrent; + } + return -1; +} + +/** + * Finds a free slot in the rocket array. + * + * @return Free rocket index, or -1 if array is full. + */ +int FindFreeRocketSlot() +{ + for (int iCurrent = 0; iCurrent < MAX_ROCKETS; iCurrent++) + { + if (!IsValidRocket(iCurrent)) return iCurrent; + } + + return -1; +} + +/** + * Finds a rocket's internal index by its entity index. + * + * @param iEntity The rocket's entity index. + * @return Internal rocket index, or -1 if not found. + */ +int FindRocketByEntity(int iEntity) +{ + int iIndex = -1; + while ((iIndex = FindNextValidRocket(iIndex)) != -1) + { + if (EntRefToEntIndex(RocketEntity[iIndex]) == iEntity) return iIndex; + } + return -1; +} + +/** + * Main think function for homing rockets. Handles tracking, dragging, + * deflection, target switching, and all per-frame rocket behaviour. + * Called from the 20Hz timer in dodgeball_core.inc. + * + * @param iIndex The rocket's internal index. + */ +void HomingRocketThink(int iIndex) +{ + int iEntity = EntRefToEntIndex(RocketEntity[iIndex]); + int iClass = RocketClass[iIndex]; + RocketFlags iFlags = RocketInstanceFlags[iIndex]; + int iTarget = EntRefToEntIndex(RocketTarget[iIndex]); + int iTeam = GetEntProp(iEntity, Prop_Send, "m_iTeamNum", 1); + int iTargetTeam = (TestFlags(iFlags, RocketFlag_IsNeutral)) ? 0 : GetAnalogueTeam(iTeam); + int iDeflectionCount = RocketEventDeflections[iIndex]; + float fModifier = CalculateModifier(iClass, iDeflectionCount); + + if ((iDeflectionCount > RocketDeflections[iIndex]) && !(RocketInstanceState[iIndex] & RocketState_Dragging)) + { + int iClient = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity"); + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] | (RocketState_CanDrag | RocketState_Dragging))); + if (iClient >= 1) + { + float fViewAngles[3], fDirection[3]; + GetClientEyeAngles(iClient, fViewAngles); + GetAngleVectors(fViewAngles, fDirection, NULL_VECTOR, NULL_VECTOR); + CopyVectors(fDirection, RocketDirection[iIndex]); + UpdateRocketSkin(iEntity, iTeam, TestFlags(iFlags, RocketFlag_IsNeutral)); + } + RocketLastDeflectionTime[iIndex] = GetGameTime(); + } + else + { + if ((GetGameTime() - RocketLastDeflectionTime[iIndex]) <= RocketClassDragTimeMax[iClass] + GetTickInterval()) + { + if ((RocketClassDragTimeMin[iClass] <= (GetGameTime() - RocketLastDeflectionTime[iIndex])) && (RocketInstanceState[iIndex] & RocketState_CanDrag)) + { + int iClient = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity"); + if (iClient >= 1) + { + float fViewAngles[3], fDirection[3]; + GetClientEyeAngles(iClient, fViewAngles); + GetAngleVectors(fViewAngles, fDirection, NULL_VECTOR, NULL_VECTOR); + CopyVectors(fDirection, RocketDirection[iIndex]); + } + } + } + else + { + if (!IsValidClient(iTarget, true)) + { + int iOwner = iTarget; + iTarget = SelectTarget(iTargetTeam, iIndex); + + if (!IsValidClient(iTarget, true)) return; + RocketTarget[iIndex] = EntIndexToEntRef(iTarget); + EmitRocketSound(RocketSound_Alert, iClass, iEntity, iTarget, iFlags); + + if (TestFlags(iFlags, RocketFlag_OnNoTargetCmd)) + { + ExecuteCommands(RocketClassCmdsOnNoTarget[iClass], iClass, iEntity, iOwner, iTarget, LastDeadClient, RocketSpeed[iIndex], iDeflectionCount, RocketMphSpeed[iIndex]); + } + + if (CvarNoTargetRedirectDamage.BoolValue) + { + SetEntDataFloat(iEntity, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.0, true); + } + Forward_OnRocketNoTarget(iIndex, iTarget, iOwner); + } + else if ((iDeflectionCount > RocketDeflections[iIndex])) + { + int iClient = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity"); + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] & ~(RocketState_Dragging | RocketState_Stolen))); + + if ((iClient >= 1) && !(iFlags & RocketFlag_CanBeStolen)) + { + CheckStolenRocket(iClient, iIndex); + } + + iTarget = SelectTarget(iTargetTeam, iIndex); + RocketTarget[iIndex] = EntIndexToEntRef(iTarget); + RocketDeflections[iIndex] = iDeflectionCount; + RocketSpeed[iIndex] = CalculateRocketSpeed(iClass, fModifier); + SetEntDataFloat(iEntity, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, CalculateRocketDamage(iClass, fModifier), true); + + if (TestFlags(iFlags, RocketFlag_ElevateOnDeflect)) RocketInstanceFlags[iIndex] |= RocketFlag_Elevating; + + if ((iFlags & RocketFlag_IsSpeedLimited) && (RocketSpeed[iIndex] >= RocketClassSpeedLimit[iClass])) + { + RocketSpeed[iIndex] = RocketClassSpeedLimit[iClass]; + } + + RocketMphSpeed[iIndex] = RocketSpeed[iIndex] * HAMMER_TO_MPH; + + if (CvarStealPreventionDamage.BoolValue && (RocketInstanceState[iIndex] & RocketState_Stolen)) + { + SetEntDataFloat(iEntity, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.0, true); + } + + if (iFlags & RocketFlag_TeamlessHits) + { + SetEntProp(iEntity, Prop_Send, "m_iTeamNum", 1, 1); + } + + int iTargetRef = iTarget; + Action aResult = Forward_OnRocketDeflectPre(iIndex, iEntity, iClient, iTargetRef); + + if (aResult == Plugin_Stop || aResult == Plugin_Handled) + { + return; + } + else if (aResult == Plugin_Changed) + { + iTarget = iTargetRef; + RocketTarget[iIndex] = EntIndexToEntRef(iTarget); + } + + EmitRocketSound(RocketSound_Alert, iClass, iEntity, iTarget, iFlags); + if (TestFlags(iFlags, RocketFlag_OnDeflectCmd)) + { + ExecuteCommands(RocketClassCmdsOnDeflect[iClass], iClass, iEntity, iClient, iTarget, LastDeadClient, RocketSpeed[iIndex], iDeflectionCount, RocketMphSpeed[iIndex]); + } + Forward_OnRocketDeflect(iIndex, iEntity, iClient); + } + else + { + if ((GetGameTime() - RocketLastDeflectionTime[iIndex]) >= RocketClassControlDelay[iClass]) + { + float fTurnrate = CalculateRocketTurnRate(iClass, fModifier) / TickModifier; + float fDirectionToTarget[3]; CalculateDirectionToClient(iEntity, iTarget, fDirectionToTarget); + + if (RocketInstanceFlags[iIndex] & RocketFlag_Elevating) + { + fDirectionToTarget[2] = RocketDirection[iIndex][2]; + } + + if ((RocketInstanceFlags[iIndex] & RocketFlag_IsTRLimited) && (fTurnrate >= RocketClassTurnRateLimit[iClass] / TickModifier)) + { + fTurnrate = RocketClassTurnRateLimit[iClass] / TickModifier; + } + LerpVectors(RocketDirection[iIndex], fDirectionToTarget, RocketDirection[iIndex], fTurnrate); + } + } + } + } + + if (!(RocketInstanceState[iIndex] & RocketState_Bouncing)) + { + ApplyRocketParameters(iIndex); + } +} + +/** + * Handles logic that is shared between all rocket behaviours, like sounds and delay checks. + * This is called from the 20Hz timer in dodgeball_core.inc + */ +void SharedRocketThink(int iIndex) +{ + int iEntity = EntRefToEntIndex(RocketEntity[iIndex]); + int iClass = RocketClass[iIndex]; + RocketFlags iFlags = RocketInstanceFlags[iIndex]; + int iTarget = EntRefToEntIndex(RocketTarget[iIndex]); + int iDeflectionCount = RocketEventDeflections[iIndex]; + + if (!(iDeflectionCount > RocketDeflections[iIndex])) + { + if (((GetGameTime() - RocketLastDeflectionTime[iIndex]) >= RocketClassControlDelay[iClass]) && + (RocketInstanceFlags[iIndex] & RocketFlag_Elevating)) + { + if (RocketDirection[iIndex][2] < RocketClassElevationLimit[iClass]) + { + RocketDirection[iIndex][2] = FMin(RocketDirection[iIndex][2] + RocketClassElevationRate[iClass], RocketClassElevationLimit[iClass]); + } + else + { + RocketInstanceFlags[iIndex] &= ~RocketFlag_Elevating; + } + } + + if ((GetGameTime() - RocketLastBeepTime[iIndex]) >= RocketClassBeepInterval[iClass]) + { + EmitRocketSound(RocketSound_Beep, iClass, iEntity, iTarget, iFlags); + RocketLastBeepTime[iIndex] = GetGameTime(); + } + + if (CvarDelayPrevention.BoolValue) + { + CheckRoundDelays(iIndex); + } + } + + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] & ~RocketState_Bouncing)); +} + +/** + * Think function for legacy homing rockets. Uses instant direction changes + * instead of smooth tracking. Called from the 20Hz timer. + * + * @param iIndex The rocket's internal index. + */ +void RocketLegacyThink(int iIndex) +{ + int iEntity = EntRefToEntIndex(RocketEntity[iIndex]); + int iClass = RocketClass[iIndex]; + RocketFlags iFlags = RocketInstanceFlags[iIndex]; + int iTarget = EntRefToEntIndex(RocketTarget[iIndex]); + int iTeam = GetEntProp(iEntity, Prop_Send, "m_iTeamNum", 1); + int iTargetTeam = (TestFlags(iFlags, RocketFlag_IsNeutral)) ? 0 : GetAnalogueTeam(iTeam); + int iDeflectionCount = RocketEventDeflections[iIndex]; + float fModifier = CalculateModifier(iClass, iDeflectionCount); + + if (!IsValidClient(iTarget, true)) + { + int iOwner = iTarget; + iTarget = SelectTarget(iTargetTeam, iIndex); + + if (!IsValidClient(iTarget, true)) return; + + RocketTarget[iIndex] = EntIndexToEntRef(iTarget); + EmitRocketSound(RocketSound_Alert, iClass, iEntity, iTarget, iFlags); + + if (TestFlags(iFlags, RocketFlag_OnNoTargetCmd)) + { + ExecuteCommands(RocketClassCmdsOnNoTarget[iClass], iClass, iEntity, iOwner, iTarget, LastDeadClient, RocketSpeed[iIndex], iDeflectionCount, RocketMphSpeed[iIndex]); + } + + if (CvarNoTargetRedirectDamage.BoolValue) + { + SetEntDataFloat(iEntity, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.0, true); + } + Forward_OnRocketNoTarget(iIndex, iTarget, iOwner); + } + else if ((iDeflectionCount > RocketDeflections[iIndex])) + { + int iClient = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity"); + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] & ~RocketState_Stolen)); + + if (iClient >= 1) + { + float fViewAngles[3], fDirection[3]; + GetClientEyeAngles(iClient, fViewAngles); + GetAngleVectors(fViewAngles, fDirection, NULL_VECTOR, NULL_VECTOR); + CopyVectors(fDirection, RocketDirection[iIndex]); + UpdateRocketSkin(iEntity, iTeam, TestFlags(iFlags, RocketFlag_IsNeutral)); + + if (!(iFlags & RocketFlag_CanBeStolen)) + { + CheckStolenRocket(iClient, iIndex); + } + } + + iTarget = SelectTarget(iTargetTeam, iIndex); + RocketTarget[iIndex] = EntIndexToEntRef(iTarget); + RocketDeflections[iIndex] = iDeflectionCount; + RocketLastDeflectionTime[iIndex] = GetGameTime(); + RocketSpeed[iIndex] = CalculateRocketSpeed(iClass, fModifier); + SetEntDataFloat(iEntity, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, CalculateRocketDamage(iClass, fModifier), true); + + if (TestFlags(iFlags, RocketFlag_ElevateOnDeflect)) RocketInstanceFlags[iIndex] |= RocketFlag_Elevating; + + if ((iFlags & RocketFlag_IsSpeedLimited) && (RocketSpeed[iIndex] >= RocketClassSpeedLimit[iClass])) + { + RocketSpeed[iIndex] = RocketClassSpeedLimit[iClass]; + } + + RocketMphSpeed[iIndex] = RocketSpeed[iIndex] * HAMMER_TO_MPH; + + if (CvarStealPreventionDamage.BoolValue && (RocketInstanceState[iIndex] & RocketState_Stolen)) + { + SetEntDataFloat(iEntity, FindSendPropInfo("CTFProjectile_Rocket", "m_iDeflected") + 4, 0.0, true); + } + + if (iFlags & RocketFlag_TeamlessHits) + { + SetEntProp(iEntity, Prop_Send, "m_iTeamNum", 1, 1); + } + + int iTargetRef = iTarget; + Action aResult = Forward_OnRocketDeflectPre(iIndex, iEntity, iClient, iTargetRef); + + if (aResult == Plugin_Stop || aResult == Plugin_Handled) + { + return; + } + else if (aResult == Plugin_Changed) + { + iTarget = iTargetRef; + RocketTarget[iIndex] = EntIndexToEntRef(iTarget); + } + + EmitRocketSound(RocketSound_Alert, iClass, iEntity, iTarget, iFlags); + if (TestFlags(iFlags, RocketFlag_OnDeflectCmd)) + { + ExecuteCommands(RocketClassCmdsOnDeflect[iClass], iClass, iEntity, iClient, iTarget, LastDeadClient, RocketSpeed[iIndex], iDeflectionCount, RocketMphSpeed[iIndex]); + } + Forward_OnRocketDeflect(iIndex, iEntity, iClient); + } + else + { + if ((GetGameTime() - RocketLastDeflectionTime[iIndex]) >= RocketClassControlDelay[iClass]) + { + float fTurnrate = CalculateRocketTurnRate(iClass, fModifier); + float fDirectionToTarget[3]; + CalculateDirectionToClient(iEntity, iTarget, fDirectionToTarget); + + if (RocketInstanceFlags[iIndex] & RocketFlag_Elevating) + { + if (RocketDirection[iIndex][2] < RocketClassElevationLimit[iClass]) + { + RocketDirection[iIndex][2] = FMin(RocketDirection[iIndex][2] + RocketClassElevationRate[iClass], RocketClassElevationLimit[iClass]); + fDirectionToTarget[2] = RocketDirection[iIndex][2]; + } + else + { + RocketInstanceFlags[iIndex] &= ~RocketFlag_Elevating; + } + } + + if ((RocketInstanceFlags[iIndex] & RocketFlag_IsTRLimited) && (fTurnrate >= RocketClassTurnRateLimit[iClass])) + { + fTurnrate = RocketClassTurnRateLimit[iClass]; + } + LerpVectors(RocketDirection[iIndex], fDirectionToTarget, RocketDirection[iIndex], fTurnrate); + } + } + ApplyRocketParameters(iIndex); +} + +/** Calculates the modifier based on deflections, rockets fired, and player count. */ +float CalculateModifier(int iClass, int iDeflections) +{ + return iDeflections + + (RocketsFired * RocketClassRocketsModifier[iClass]) + + (PlayerCount * RocketClassPlayerModifier[iClass]); +} + +/** Calculates rocket damage based on class settings and modifier. */ +float CalculateRocketDamage(int iClass, float fModifier) +{ + return RocketClassDamage[iClass] + RocketClassDamageIncrement[iClass] * fModifier; +} + +/** Calculates rocket speed based on class settings and modifier. */ +float CalculateRocketSpeed(int iClass, float fModifier) +{ + return RocketClassSpeed[iClass] + RocketClassSpeedIncrement[iClass] * fModifier; +} + +/** Calculates turn rate based on class settings and modifier. */ +float CalculateRocketTurnRate(int iClass, float fModifier) +{ + return RocketClassTurnRate[iClass] + RocketClassTurnRateIncrement[iClass] * fModifier; +} + +/** Calculates the normalized direction vector from rocket to client. */ +void CalculateDirectionToClient(int iEntity, int iClient, float fOut[3]) +{ + float fRocketPosition[3]; GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", fRocketPosition); + GetClientEyePosition(iClient, fOut); + MakeVectorFromPoints(fRocketPosition, fOut, fOut); + NormalizeVector(fOut, fOut); +} + +/** Applies rocket direction and speed to the entity's velocity. */ +void ApplyRocketParameters(int iIndex) +{ + int iEntity = EntRefToEntIndex(RocketEntity[iIndex]); + float fAngles[3]; GetVectorAngles(RocketDirection[iIndex], fAngles); + float fVelocity[3]; CopyVectors(RocketDirection[iIndex], fVelocity); + + ScaleVector(fVelocity, RocketSpeed[iIndex]); + SetEntPropVector(iEntity, Prop_Data, "m_vecAbsVelocity", fVelocity); + SetEntPropVector(iEntity, Prop_Send, "m_angRotation", fAngles); +} + +/** Updates the rocket's skin based on team and neutral status. */ +void UpdateRocketSkin(int iEntity, int iTeam, bool bNeutral) +{ + if (bNeutral) SetEntProp(iEntity, Prop_Send, "m_nSkin", 2); + else SetEntProp(iEntity, Prop_Send, "m_nSkin", (iTeam == view_as(TFTeam_Blue)) ? 0 : 1); +} + +/** Gets a random rocket class based on spawner spawn chances. */ +int GetRandomRocketClass(int iSpawnerClass) +{ + int iRandom = GetURandomIntRange(1, 100); + ArrayList hTable = SpawnersChancesTable[iSpawnerClass]; + int iTableSize = hTable.Length; + int iChancesLower = 0; + int iChancesUpper = 0; + + for (int iEntry = 0; iEntry < iTableSize; iEntry++) + { + iChancesLower += iChancesUpper; + iChancesUpper = iChancesLower + hTable.Get(iEntry); + + if ((iRandom >= iChancesLower) && (iRandom <= iChancesUpper)) + { + return iEntry; + } + } + return 0; +} + +/** + * Emits a rocket sound (spawn, beep, or alert) based on class settings. + * Note: Beep timing is managed by SharedRocketThink - this function only plays sounds. + */ +void EmitRocketSound(RocketSound iSound, int iClass, int iEntity, int iTarget, RocketFlags iFlags) +{ + switch (iSound) + { + case RocketSound_Spawn: + { + if (TestFlags(iFlags, RocketFlag_PlaySpawnSound)) + { + if (TestFlags(iFlags, RocketFlag_CustomSpawnSound)) EmitSoundToAll(RocketClassSpawnSound[iClass], iEntity); + else EmitSoundToAll(SOUND_DEFAULT_SPAWN, iEntity); + } + } + case RocketSound_Beep: + { + // Beep interval timing is handled by SharedRocketThink before calling this function + if (TestFlags(iFlags, RocketFlag_PlayBeepSound)) + { + if (TestFlags(iFlags, RocketFlag_CustomBeepSound)) EmitSoundToAll(RocketClassBeepSound[iClass], iEntity); + else EmitSoundToAll(SOUND_DEFAULT_BEEP, iEntity); + } + } + case RocketSound_Alert: + { + if (TestFlags(iFlags, RocketFlag_PlayAlertSound)) + { + if (TestFlags(iFlags, RocketFlag_CustomAlertSound)) EmitSoundToClient(iTarget, RocketClassAlertSound[iClass]); + else EmitSoundToClient(iTarget, SOUND_DEFAULT_ALERT, _, _, _, _, 0.5); + } + } + } +} + +/** + * Checks if a rocket deflection was a steal and applies penalties. + * Uses guard clauses for clarity and safety. + */ +void CheckStolenRocket(int iClient, int iIndex) +{ + int iTarget = EntRefToEntIndex(RocketTarget[iIndex]); + + // Guard clauses - each condition is clear and prevents potential crashes + if (iTarget == -1) return; // Invalid target entity + if (!IsValidClient(iTarget, true)) return; // Target not valid/alive + if (iTarget == iClient) return; // Can't steal from yourself + if (StealInfo[iClient].stoleRocket) return; // Already processed a steal this round + if (RocketInstanceState[iIndex] & RocketState_Delayed) return; // Delayed rockets exempt from steal check + if (GetEntitiesDistance(iTarget, iClient) <= CvarStealDistance.FloatValue) return; // Too close to target + + // Team check: if enabled, only count as steal if on same team as target + if ((RocketInstanceFlags[iIndex] & RocketFlag_StealTeamCheck) && + (GetClientTeam(iTarget) != GetClientTeam(iClient))) return; + + // All conditions met - this is a steal + StealInfo[iClient].stoleRocket = true; + + if (StealInfo[iClient].rocketsStolen < CvarStealPreventionNumber.IntValue) + { + StealInfo[iClient].rocketsStolen++; + SlapPlayer(iClient, 0, true); + CPrintToChat(iClient, "%t", "DBSteal_Warning_Client", StealInfo[iClient].rocketsStolen, CvarStealPreventionNumber.IntValue); + + if (CvarStealMessage.BoolValue) + { + CSkipNextClient(iClient); + CPrintToChatAll("%t", "DBSteal_Announce_All", iClient, iTarget); + } + StealInfo[iClient].stoleRocket = false; + } + else + { + ForcePlayerSuicide(iClient); + CPrintToChat(iClient, "%t", "DBSteal_Slay_Client"); + if (CvarStealMessage.BoolValue) + { + CSkipNextClient(iClient); + CPrintToChatAll("%t", "DBSteal_Announce_Slay_All", iClient); + } + } + + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] | RocketState_Stolen)); + LastStealer = iClient; + Forward_OnRocketSteal(iIndex, iClient, iTarget, StealInfo[iClient].rocketsStolen); +} + +void CheckRoundDelays(int iIndex) +{ + int iEntity = EntRefToEntIndex(RocketEntity[iIndex]); + int iTarget = EntRefToEntIndex(RocketTarget[iIndex]); + float fTimeToCheck = RocketDeflections[iIndex] == 0 ? LastSpawnTime[iIndex] : RocketLastDeflectionTime[iIndex]; + + if (iTarget == -1 || (GetGameTime() - fTimeToCheck) < CvarDelayPreventionTime.FloatValue) return; + + if (!(RocketInstanceState[iIndex] & RocketState_Delayed)) + { + if (CvarDelayMessage.BoolValue) + { + CPrintToChatAll("%t", "DBDelay_Announce_All", iTarget); + } + EmitSoundToAll(SOUND_DEFAULT_SPEEDUP, iEntity, SNDCHAN_AUTO, SNDLEVEL_GUNFIRE); + Internal_SetRocketState(iIndex, (RocketInstanceState[iIndex] | RocketState_Delayed)); + Forward_OnRocketDelay(iIndex, iTarget); + } + else + { + RocketSpeed[iIndex] += CvarDelayPreventionSpeedup.FloatValue; + } +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_utilities.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_utilities.inc new file mode 100644 index 00000000..ab9d3f0e --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/dodgeball_utilities.inc @@ -0,0 +1,373 @@ +#if defined _dodgeball_utilities_included + #endinput +#endif +#define _dodgeball_utilities_included + +/** + * Contains general-purpose utility and helper functions for the + * TF2 Dodgeball plugin. + */ + + // Add this new function to the file +stock void StopSoundToAll(int iChannel, const char[] strSound) +{ + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (IsValidClientEx(iClient)) + { + StopSound(iClient, iChannel, strSound); + } + } +} + +// ********************************************************************************* +// VECTOR & MATH UTILITIES +// ********************************************************************************* + +/** + * Copies the contents from one vector to another. + */ +stock void CopyVectors(float fFrom[3], float fTo[3]) +{ + fTo[0] = fFrom[0]; + fTo[1] = fFrom[1]; + fTo[2] = fFrom[2]; +} + +/** + * Calculates the linear interpolation of two vectors. + */ +stock void LerpVectors(float fA[3], float fB[3], float fC[3], float t) +{ + t = (t < 0.0) ? 0.0 : (t > 1.0) ? 1.0 : t; + + fC[0] = fA[0] + (fB[0] - fA[0]) * t; + fC[1] = fA[1] + (fB[1] - fA[1]) * t; + fC[2] = fA[2] + (fB[2] - fA[2]) * t; +} + +/** + * Gets the distance between two entities. + */ +stock float GetEntitiesDistance(int iEnt1, int iEnt2) +{ + float fOrig1[3]; + GetEntPropVector(iEnt1, Prop_Send, "m_vecOrigin", fOrig1); + + float fOrig2[3]; + GetEntPropVector(iEnt2, Prop_Send, "m_vecOrigin", fOrig2); + + return GetVectorDistance(fOrig1, fOrig2); +} + +/** + * Returns the maximum of two float values. + */ +stock float FMax(float a, float b) +{ + return (a > b) ? a : b; +} + +/** + * Returns the minimum of two float values. + */ +stock float FMin(float a, float b) +{ + return (a < b) ? a : b; +} + +/** + * Returns a random integer within a given range (inclusive). + */ +stock int GetURandomIntRange(int iMin, int iMax) +{ + return iMin + (GetURandomInt() % (iMax - iMin + 1)); +} + +/** + * Returns a random float within a given range. + */ +stock float GetURandomFloatRange(float fMin, float fMax) +{ + return fMin + (GetURandomFloat() * (fMax - fMin)); +} + +// ********************************************************************************* +// CLIENT & GAMEPLAY UTILITIES +// ********************************************************************************* + +/** + * Checks if a client index is valid, connected, and optionally alive. + */ +stock bool IsValidClient(int iClient, bool bAlive = false) +{ + return iClient >= 1 && + iClient <= MaxClients && + IsClientInGame(iClient) && + (!bAlive || IsPlayerAlive(iClient)); +} + +/** + * IsValidClient() but without the index range check. + */ +stock bool IsValidClientEx(int iClient, bool bAlive = false) +{ + return IsClientInGame(iClient) && (!bAlive || IsPlayerAlive(iClient)); +} + +/** + * Checks if there are living players on both teams. + */ +stock bool BothTeamsPlaying() +{ + bool bRedFound, bBluFound; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsValidClientEx(iClient, true)) continue; + + int iTeam = GetClientTeam(iClient); + + if (iTeam == view_as(TFTeam_Red)) bRedFound = true; + if (iTeam == view_as(TFTeam_Blue)) bBluFound = true; + } + + return bRedFound && bBluFound; +} + +/** + * Counts the number of players currently alive. + */ +stock int CountAlivePlayers() +{ + int iCount = 0; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (IsValidClientEx(iClient, true)) iCount++; + } + + return iCount; +} + +/** + * Gets the opposing team. + */ +stock int GetAnalogueTeam(int iTeam) +{ + if (iTeam == view_as(TFTeam_Red)) return view_as(TFTeam_Blue); + + return view_as(TFTeam_Red); +} + +/** + * Selects a target for a homing rocket based on team and optional directional weighting. + * Falls back to random selection if rocket entity is invalid. + */ +stock int SelectTarget(int iTeam, int iRocket = -1) +{ + int iTarget = -1; + float fTargetWeight = 0.0; + float fRocketPosition[3]; + float fRocketDirection[3]; + float fWeight; + bool bUseRocket = false; + int iOwner = -1; + + if (iRocket != -1) + { + int iEntity = EntRefToEntIndex(RocketEntity[iRocket]); + + // Validate entity before any property access to prevent crashes + if (iEntity != -1 && IsValidEntity(iEntity)) + { + int iClass = RocketClass[iRocket]; + iOwner = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity"); + + GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", fRocketPosition); + CopyVectors(RocketDirection[iRocket], fRocketDirection); + fWeight = RocketClassTargetWeight[iClass]; + + bUseRocket = true; + } + // If entity is invalid, bUseRocket stays false and we fall back to random selection + } + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsValidClientEx(iClient, true)) continue; + if (iTeam && GetClientTeam(iClient) != iTeam) continue; + if (iClient == iOwner) continue; + + float fNewWeight = GetURandomFloatRange(0.0, 100.0); + + if (bUseRocket) + { + float fClientPosition[3]; GetClientEyePosition(iClient, fClientPosition); + float fDirectionToClient[3]; MakeVectorFromPoints(fRocketPosition, fClientPosition, fDirectionToClient); + + fNewWeight += GetVectorDotProduct(fRocketDirection, fDirectionToClient) * fWeight; + } + + if ((iTarget == -1) || fNewWeight >= fTargetWeight) + { + iTarget = iClient; + fTargetWeight = fNewWeight; + } + } + + return iTarget; +} + +// ********************************************************************************* +// EFFECT & RESOURCE UTILITIES +// ********************************************************************************* + +/** + * Shows a hidden MOTD panel, often used for streaming audio. + */ +stock void ShowHiddenMOTDPanel(int iClient, char[] strTitle, char[] strMsg, char[] strType = "2") +{ + KeyValues hPanel = new KeyValues("data"); + hPanel.SetString("title", strTitle); + hPanel.SetString("type", strType); + hPanel.SetString("msg", strMsg); + ShowVGUIPanel(iClient, "info", hPanel, false); + delete hPanel; +} + +/** + * Plays a particle system. + */ +stock void PlayParticle(float fPosition[3], float fAngles[3], char[] strParticleName, float fEffectTime = 5.0, float fLifeTime = 9.0) +{ + int iEntity = CreateEntityByName("info_particle_system"); + + if (iEntity == -1) + { + LogError("PlayParticle: could not create info_particle_system"); + return; + } + + TeleportEntity(iEntity, fPosition, fAngles, NULL_VECTOR); + DispatchKeyValue(iEntity, "effect_name", strParticleName); + ActivateEntity(iEntity); + AcceptEntityInput(iEntity, "Start"); + CreateTimer(fEffectTime, StopParticle, EntIndexToEntRef(iEntity)); + CreateTimer(fLifeTime, KillParticle, EntIndexToEntRef(iEntity)); +} + +/** + * Timer callback to stop a particle system. + */ +public Action StopParticle(Handle hTimer, any iEntityRef) +{ + if (iEntityRef == INVALID_ENT_REFERENCE) return Plugin_Continue; + + int iEntity = EntRefToEntIndex(iEntityRef); + if (iEntity != INVALID_ENT_REFERENCE) + { + AcceptEntityInput(iEntity, "Stop"); + } + + return Plugin_Continue; +} + +/** + * Timer callback to kill a particle system entity. + */ +public Action KillParticle(Handle hTimer, any iEntityRef) +{ + if (iEntityRef == INVALID_ENT_REFERENCE) return Plugin_Continue; + + int iEntity = EntRefToEntIndex(iEntityRef); + if (iEntity != INVALID_ENT_REFERENCE) + { + RemoveEntity(iEntity); + } + + return Plugin_Continue; +} + +/** + * Precaches a particle system by creating and quickly destroying it. + */ +stock void PrecacheParticle(char[] strParticleName) +{ + PlayParticle(view_as({0.0, 0.0, 0.0}), view_as({0.0, 0.0, 0.0}), strParticleName, 0.1, 0.1); +} + +/** + * Precaches a sound and adds it to the download table. + */ +stock void PrecacheSoundEx(char[] strFileName, bool bPreload = false, bool bAddToDownloadTable = false) +{ + char strFinalPath[PLATFORM_MAX_PATH]; + FormatEx(strFinalPath, sizeof(strFinalPath), "sound/%s", strFileName); + PrecacheSound(strFileName, bPreload); + + if (bAddToDownloadTable) AddFileToDownloadsTable(strFinalPath); +} + +/** + * Removes illegal characters from a string. + */ +stock void CleanString(char[] strBuffer) +{ + int iLength = strlen(strBuffer); + for (int iPos = 0; iPos < iLength; iPos++) + { + switch (strBuffer[iPos]) + { + case '\r', '\n', '\t': strBuffer[iPos] = ' '; + } + } + TrimString(strBuffer); +} + +/** + * Precaches a model and its dependencies and adds them to the download table. + */ +stock void PrecacheModelEx(char[] strFileName, bool bPreload = false, bool bAddToDownloadTable = false) +{ + PrecacheModel(strFileName, bPreload); + + if (!bAddToDownloadTable) return; + + char strDepFileName[PLATFORM_MAX_PATH]; + FormatEx(strDepFileName, sizeof(strDepFileName), "%s.res", strFileName); + + if (!FileExists(strDepFileName)) return; + + File hStream = OpenFile(strDepFileName, "r"); + if (hStream == null) + { + LogError("Error, can't read file containing model dependencies: %s", strDepFileName); + return; + } + + while(!hStream.EndOfFile()) + { + char strBuffer[PLATFORM_MAX_PATH]; + hStream.ReadLine(strBuffer, sizeof(strBuffer)); + CleanString(strBuffer); + + if (!FileExists(strBuffer, true)) continue; + + if (StrContains(strBuffer, ".vmt", false) != -1) PrecacheDecal(strBuffer, true); + else if (StrContains(strBuffer, ".mdl", false) != -1) PrecacheModel(strBuffer, true); + else if (StrContains(strBuffer, ".pcf", false) != -1) PrecacheGeneric(strBuffer, true); + + AddFileToDownloadsTable(strBuffer); + } + + delete hStream; +} + +/** + * Custom trace ray filter to exclude a specific entity. + */ +public bool TEF_ExcludeEntity(int iEntity, int iContentsMask, any aData) +{ + return (iEntity != aData); +} \ No newline at end of file diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/morecolors.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/morecolors.inc index 1662b7f0..b1a62854 100755 --- a/roles/sourcemod/files/addons/sourcemod/scripting/include/morecolors.inc +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/morecolors.inc @@ -7,9 +7,7 @@ #endif #define _more_colors_included -#pragma semicolon 1 -#pragma newdecls required - +#pragma newdecls optional #include #define MORE_COLORS_VERSION "2.0.0-MC" @@ -23,15 +21,11 @@ #define MC_GAME_DODS 0 -// hack for no sm 1.12 -// -sappho -#define TF_MAXPLAYERS 101 - -bool MC_SkipList[TF_MAXPLAYERS + 1]; +bool MC_SkipList[MAXPLAYERS+1]; StringMap MC_Trie; int MC_TeamColors[][] = {{0xCCCCCC, 0x4D7942, 0xFF4040}}; // Multi-dimensional array for games that don't support SayText2. First index is the game index (as defined by the GAME_ defines), second index is team. 0 = spectator, 1 = team1, 2 = team2 -static ConVar sm_show_activity = null; +static ConVar sm_show_activity; /** * Prints a message to a specific client in the chat area. @@ -112,11 +106,11 @@ stock void MC_PrintToChatEx(int client, int author, const char[] message, any .. ThrowError("Client %i is not in game", client); } - if (author <= 0 || author > MaxClients) { + if (author < 0 || author > MaxClients) { ThrowError("Invalid client index %i", author); } - if (!IsClientInGame(author)) { + if ((author != 0) && !IsClientInGame(author)) { ThrowError("Client %i is not in game", author); } @@ -140,11 +134,11 @@ stock void MC_PrintToChatEx(int client, int author, const char[] message, any .. stock void MC_PrintToChatAllEx(int author, const char[] message, any ...) { MC_CheckTrie(); - if (author <= 0 || author > MaxClients) { + if (author < 0 || author > MaxClients) { ThrowError("Invalid client index %i", author); } - if (!IsClientInGame(author)) { + if ((author != 0) && !IsClientInGame(author)) { ThrowError("Client %i is not in game", author); } @@ -265,6 +259,7 @@ stock void MC_ReplaceColorCodes(char[] buffer, int author = 0, bool removeTags = ReplaceString(buffer, maxlen, "{default}", "", false); ReplaceString(buffer, maxlen, "{teamcolor}", "", false); } + if (author != 0 && !removeTags) { if (author < 0 || author > MaxClients) { ThrowError("Invalid client index %i", author); @@ -286,12 +281,13 @@ stock void MC_ReplaceColorCodes(char[] buffer, int author = 0, bool removeTags = // Since the string's size is going to be changing, output will hold the replaced string and we'll search buffer Regex regex = new Regex("{[#a-zA-Z0-9]+}"); - for(int i = 0; i < 1000; i++) { // The RegEx extension is quite flaky, so we have to loop here :/. This loop is supposed to be infinite and broken by return, but conditions have been added to be safe. + for (int i = 0; i < 1000; i++) { // The RegEx extension is quite flaky, so we have to loop here :/. This loop is supposed to be infinite and broken by return, but conditions have been added to be safe. if (regex.Match(buffer[cursor]) < 1) { delete regex; strcopy(buffer, maxlen, output); return; } + regex.GetSubString(0, tag, sizeof(tag)); MC_StrToLower(tag); cursor = StrContains(buffer[cursor], tag, false) + cursor + 1; @@ -343,11 +339,12 @@ stock void MC_ReplaceColorCodes(char[] buffer, int author = 0, bool removeTags = */ stock void CSubString(const char[] input, char[] output, int maxlen, int start, int numChars = 0) { int i = 0; - for(;;) { + for (;;) { if (i == maxlen - 1 || i >= numChars || input[start + i] == '\0') { output[i] = '\0'; return; } + output[i] = input[start + i]; i++; } @@ -360,7 +357,7 @@ stock void CSubString(const char[] input, char[] output, int maxlen, int start, */ stock void MC_StrToLower(char[] buffer) { int len = strlen(buffer); - for(int i = 0; i < len; i++) { + for (int i = 0; i < len; i++) { buffer[i] = CharToLower(buffer[i]); } } @@ -414,8 +411,7 @@ stock void MC_ReplyToCommand(int client, const char[] message, any ...) { MC_RemoveTags(buffer, sizeof(buffer)); PrintToServer("%s", buffer); } - - if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { MC_RemoveTags(buffer, sizeof(buffer)); PrintToConsole(client, "%s", buffer); } @@ -440,8 +436,7 @@ stock void MC_ReplyToCommandEx(int client, int author, const char[] message, any MC_RemoveTags(buffer, sizeof(buffer)); PrintToServer("%s", buffer); } - - if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { MC_RemoveTags(buffer, sizeof(buffer)); PrintToConsole(client, "%s", buffer); } @@ -486,20 +481,17 @@ stock int MC_ShowActivity(int client, const char[] format, any ...) { GetClientName(client, name, sizeof(name)); AdminId id = GetUserAdmin(client); - if (id == INVALID_ADMIN_ID - || !id.HasFlag(Admin_Generic, Access_Effective)) - { + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { sign = "PLAYER"; } /* Display the message to the client? */ - if (replyto == SM_REPLY_TO_CONSOLE) - { + if (replyto == SM_REPLY_TO_CONSOLE) { SetGlobalTransTarget(client); VFormat(szBuffer, sizeof(szBuffer), format, 3); MC_RemoveTags(szBuffer, sizeof(szBuffer)); - PrintToConsole(client, "%s%s\n", tag, szBuffer); + PrintToConsole(client, "%s%s", tag, szBuffer); display_in_chat = true; } } @@ -508,7 +500,7 @@ stock int MC_ShowActivity(int client, const char[] format, any ...) { VFormat(szBuffer, sizeof(szBuffer), format, 3); MC_RemoveTags(szBuffer, sizeof(szBuffer)); - PrintToServer("%s%s\n", tag, szBuffer); + PrintToServer("%s%s", tag, szBuffer); } if (!value) { @@ -516,24 +508,23 @@ stock int MC_ShowActivity(int client, const char[] format, any ...) { } for (int i = 1; i <= MaxClients; ++i) { - if (!IsClientInGame(i) - || IsFakeClient(i) - || (display_in_chat && i == client)) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { continue; } AdminId id = GetUserAdmin(i); SetGlobalTransTarget(i); - if (id == INVALID_ADMIN_ID - || !id.HasFlag(Admin_Generic, Access_Effective)) { + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { /* Treat this as a normal user. */ if ((value & 1) | (value & 2)) { char newsign[MAX_NAME_LENGTH]; - newsign = sign; if ((value & 2) || (i == client)) { newsign = name; } + else { + newsign = sign; + } VFormat(szBuffer, sizeof(szBuffer), format, 3); @@ -543,15 +534,15 @@ stock int MC_ShowActivity(int client, const char[] format, any ...) { else { /* Treat this as an admin user */ bool is_root = id.HasFlag(Admin_Root, Access_Effective); - if ((value & 4) - || (value & 8) - || ((value & 16) && is_root)) { + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { char newsign[MAX_NAME_LENGTH]; - newsign = sign; if ((value & 8) || ((value & 16) && is_root) || (i == client)) { newsign = name; } + else { + newsign = sign; + } VFormat(szBuffer, sizeof(szBuffer), format, 3); @@ -574,8 +565,13 @@ stock int MC_ShowActivity(int client, const char[] format, any ...) { * @error */ stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, any ...) { - if (sm_show_activity == null) + if (sm_show_activity == null) { sm_show_activity = FindConVar("sm_show_activity"); + } + + char szTag[MC_MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); char szBuffer[MC_MAX_MESSAGE_LENGTH]; //char szCMessage[MC_MAX_MESSAGE_LENGTH]; @@ -592,8 +588,7 @@ stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, a GetClientName(client, name, sizeof(name)); AdminId id = GetUserAdmin(client); - if (id == INVALID_ADMIN_ID - || !id.HasFlag(Admin_Generic, Access_Effective)) { + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { sign = "PLAYER"; } @@ -603,7 +598,7 @@ stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, a VFormat(szBuffer, sizeof(szBuffer), format, 4); MC_RemoveTags(szBuffer, sizeof(szBuffer)); - PrintToConsole(client, "%s%s\n", tag, szBuffer); + PrintToConsole(client, "%s%s", szTag, szBuffer); display_in_chat = true; } } @@ -612,7 +607,7 @@ stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, a VFormat(szBuffer, sizeof(szBuffer), format, 4); MC_RemoveTags(szBuffer, sizeof(szBuffer)); - PrintToServer("%s%s\n", tag, szBuffer); + PrintToServer("%s%s", szTag, szBuffer); } if (!value) { @@ -620,24 +615,23 @@ stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, a } for (int i = 1; i <= MaxClients; ++i) { - if (!IsClientInGame(i) - || IsFakeClient(i) - || (display_in_chat && i == client)) { + if (!IsClientInGame(i) || IsFakeClient(i) || (display_in_chat && i == client)) { continue; } AdminId id = GetUserAdmin(i); SetGlobalTransTarget(i); - if (id == INVALID_ADMIN_ID - || !id.HasFlag(Admin_Generic, Access_Effective)) { + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { /* Treat this as a normal user. */ if ((value & 1) | (value & 2)) { char newsign[MAX_NAME_LENGTH]; - newsign = sign; if ((value & 2) || (i == client)) { newsign = name; } + else { + newsign = sign; + } VFormat(szBuffer, sizeof(szBuffer), format, 4); @@ -647,15 +641,15 @@ stock int MC_ShowActivityEx(int client, const char[] tag, const char[] format, a else { /* Treat this as an admin user */ bool is_root = id.HasFlag(Admin_Root, Access_Effective); - if ((value & 4) - || (value & 8) - || ((value & 16) && is_root)) { + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { char newsign[MAX_NAME_LENGTH]; - newsign = sign; if ((value & 8) || ((value & 16) && is_root) || (i == client)) { newsign = name; } + else { + newsign = sign; + } VFormat(szBuffer, sizeof(szBuffer), format, 4); @@ -684,6 +678,10 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an sm_show_activity = FindConVar("sm_show_activity"); } + char szTag[MC_MAX_MESSAGE_LENGTH]; + strcopy(szTag, sizeof(szTag), tag); + MC_RemoveTags(szTag, sizeof(szTag)); + char szBuffer[MC_MAX_MESSAGE_LENGTH]; //char szCMessage[MC_MAX_MESSAGE_LENGTH]; int value = sm_show_activity.IntValue; @@ -700,8 +698,7 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an GetClientName(client, name, sizeof(name)); AdminId id = GetUserAdmin(client); - if (id == INVALID_ADMIN_ID - || !id.HasFlag(Admin_Generic, Access_Effective)) { + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { sign = "PLAYER"; } @@ -712,14 +709,14 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an * simply gets added to the console, so we don't want it to print * twice. */ - MC_PrintToChatEx(client, client, "%s%s", tag, szBuffer); + MC_PrintToChatEx(client, client, "%s%s", szTag, szBuffer); } else { SetGlobalTransTarget(LANG_SERVER); VFormat(szBuffer, sizeof(szBuffer), format, 4); MC_RemoveTags(szBuffer, sizeof(szBuffer)); - PrintToServer("%s%s\n", tag, szBuffer); + PrintToServer("%s%s", szTag, szBuffer); } if (!value) { @@ -727,24 +724,23 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an } for (int i = 1; i <= MaxClients; ++i) { - if (!IsClientInGame(i) - || IsFakeClient(i) - || i == client) { + if (!IsClientInGame(i) || IsFakeClient(i) || i == client) { continue; } AdminId id = GetUserAdmin(i); SetGlobalTransTarget(i); - if (id == INVALID_ADMIN_ID - || !id.HasFlag(Admin_Generic, Access_Effective)) { + if (id == INVALID_ADMIN_ID || !id.HasFlag(Admin_Generic, Access_Effective)) { /* Treat this as a normal user. */ if ((value & 1) | (value & 2)) { char newsign[MAX_NAME_LENGTH]; - newsign = sign; if ((value & 2)) { newsign = name; } + else { + newsign = sign; + } VFormat(szBuffer, sizeof(szBuffer), format, 4); @@ -754,15 +750,16 @@ stock int MC_ShowActivity2(int client, const char[] tag, const char[] format, an else { /* Treat this as an admin user */ bool is_root = id.HasFlag(Admin_Root, Access_Effective); - if ((value & 4) - || (value & 8) - || ((value & 16) && is_root)) { + if ((value & 4) || (value & 8) || ((value & 16) && is_root)) { char newsign[MAX_NAME_LENGTH]; - newsign = sign; + if ((value & 8) || ((value & 16) && is_root)) { newsign = name; } + else { + newsign = sign; + } VFormat(szBuffer, sizeof(szBuffer), format, 4); @@ -996,6 +993,6 @@ stock StringMap MC_InitColorTrie() { hTrie.SetValue("whitesmoke", 0xF5F5F5); hTrie.SetValue("yellow", 0xFFFF00); hTrie.SetValue("yellowgreen", 0x9ACD32); - + return hTrie; } diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/multicolors.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/multicolors.inc new file mode 100644 index 00000000..ba1976bd --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/multicolors.inc @@ -0,0 +1,461 @@ +#if defined _multicolors_included + #endinput +#endif +#define _multicolors_included + +#define MuCo_VERSION "2.1.2" + +#include "morecolors" +#include "colors" + +/* +* +* Credits: +* - Popoklopsi +* - Powerlord +* - exvel +* - Dr. McKay +* +* Based on stamm-colors +* - https://github.com/popoklopsi/Stamm/blob/master/include/stamm/stamm-colors.inc +* +*/ + + + +#define PREFIX_MAX_LENGTH 64 +#define PREFIX_SEPARATOR "{default} " + +/* Global var to check whether colors are fixed or not */ +static bool g_bCFixColors; + +static char g_sCPrefix[PREFIX_MAX_LENGTH]; + +/** + * Add a chat prefix before all chat msg + * + * @param sPrefix Prefix + */ +stock void CSetPrefix(const char[] sPrefix, any ...) { + if (!sPrefix[0]) { + return; + } + + SetGlobalTransTarget(LANG_SERVER); + VFormat(g_sCPrefix, sizeof(g_sCPrefix) - strlen(PREFIX_SEPARATOR), sPrefix, 2); + + // Add ending space + Format(g_sCPrefix, sizeof(g_sCPrefix), "%s%s", g_sCPrefix, PREFIX_SEPARATOR); +} + +/** + * Add a chat prefix before all chat msg + * + * @param sPrefix Prefix + */ +stock void CClearPrefix() { + g_sCPrefix[0] = '\0'; +} + +/** + * Writes a message to a client with the correct stock for the game. + * + * @param client Client index. + * @param message Message (formatting rules). + * + * @error If the client is not connected an error will be thrown. + */ +stock void CPrintToChat(int client, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_PrintToChat(client, "%s%s", g_sCPrefix, buffer); + } + else { + MC_PrintToChat(client, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules) + */ +stock void CPrintToChatAll(const char[] message, any ...) { + if (!g_bCFixColors) { + CFixColors(); + } + + char buffer[MAX_BUFFER_LENGTH]; + if (!IsSource2009()) { + for (int i = 1; i <= MaxClients; ++i) { + if (IsClientInGame(i) && !IsFakeClient(i) && !C_SkipList[i]) { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), message, 2); + + C_PrintToChat(i, "%s", buffer); + } + + C_SkipList[i] = false; + } + } + else { + MC_CheckTrie(); + + char buffer2[MAX_BUFFER_LENGTH]; + + for (int i = 1; i <= MaxClients; ++i) { + if (!IsClientInGame(i) || MC_SkipList[i]) { + MC_SkipList[i] = false; + continue; + } + + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 2); + + MC_ReplaceColorCodes(buffer2); + MC_SendMessage(i, buffer2); + } + } +} + +/** + * Writes a message to all of a client's observers. + * + * @param target Client index. + * @param message Message (formatting rules). + */ +stock void CPrintToChatObservers(int target, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + for (int client = 1; client <= MaxClients; client++) { + if (IsClientInGame(client) && !IsPlayerAlive(client) && !IsFakeClient(client)) { + int observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); + int ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); + + if (observee == target && (ObserverMode == 4 || ObserverMode == 5)) { + CPrintToChat(client, buffer); + } + } + } +} + +/** + * Writes a message to a client with the correct stock for the game. + * + * @param client Client index. + * @param author Author index. + * @param message Message (formatting rules). + * + * @error If the client is not connected an error will be thrown. + */ +stock void CPrintToChatEx(int client, int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_PrintToChatEx(client, author, "%s%s", g_sCPrefix, buffer); + } + else { + MC_PrintToChatEx(client, author, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Writes a message to all clients with the correct stock for the game. + * + * @param author Author index. + * @param message Message (formatting rules). + */ +stock void CPrintToChatAllEx(int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_PrintToChatAllEx(author, "%s%s", g_sCPrefix, buffer); + } + else { + MC_PrintToChatAllEx(author, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Writes a message to all of a client's observers with the correct + * game stock. + * + * @param target Client index. + * @param message Message (formatting rules). + */ +stock void CPrintToChatObserversEx(int target, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + for (int client = 1; client <= MaxClients; client++) { + if (IsClientInGame(client) && !IsPlayerAlive(client) && !IsFakeClient(client)) { + int observee = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); + int ObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode"); + + if (observee == target && (ObserverMode == 4 || ObserverMode == 5)) { + CPrintToChatEx(client, target, buffer); + } + } + } +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param message Message (formatting rules) + */ +stock void CReplyToCommand(int client, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ReplyToCommand(client, "%s%s", g_sCPrefix, buffer); + } + else { + MC_ReplyToCommand(client, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param author Client to use for {teamcolor} + * @param message Message (formatting rules) + */ +stock void CReplyToCommandEx(int client, int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ReplyToCommandEx(client, author, "%s%s", g_sCPrefix, buffer); + } + else { + MC_ReplyToCommandEx(client, author, "%s%s", g_sCPrefix, buffer); + } +} + +/** + * Remove all tags and print to server + * + * @param message Message (formatting rules) + */ +stock void CPrintToServer(const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + char prefixBuffer[PREFIX_MAX_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 2); + strcopy(prefixBuffer, sizeof(prefixBuffer), g_sCPrefix); + + if (!g_bCFixColors) { + CFixColors(); + } + + CRemoveTags(buffer, sizeof(buffer)); + CRemoveTags(prefixBuffer, sizeof(prefixBuffer)); + + PrintToServer("%s%s", prefixBuffer, buffer); +} + +/** + * Displays usage of an admin command to users depending on the + * setting of the sm_show_activity cvar. + * + * This version does not display a message to the originating client + * if used from chat triggers or menus. If manual replies are used + * for these cases, then this function will suffice. Otherwise, + * CShowActivity2() is slightly more useful. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * + * @error + */ +stock void CShowActivity(int author, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 3); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ShowActivity(author, "%s", buffer); + } + else { + MC_ShowActivity(author, "%s", buffer); + } +} + +/** + * Same as C_ShowActivity(), except the tag parameter is used instead of "[SM] " (note that you must supply any spacing). + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to display with. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * + * @error + */ +stock void CShowActivityEx(int author, const char[] tag, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ShowActivityEx(author, tag, "%s", buffer); + } + else { + MC_ShowActivityEx(author, tag, "%s", buffer); + } +} + +/** + * Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar. + * All users receive a message in their chat text, except for the originating client, + * who receives the message based on the current ReplySource. + * Supports color tags. + * + * @param client Client index doing the action, or 0 for server. + * @param tags Tag to prepend to the message. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * + * @error + */ + stock void CShowActivity2(int author, const char[] tag, const char[] message, any ...) { + char buffer[MAX_MESSAGE_LENGTH]; + SetGlobalTransTarget(LANG_SERVER); + VFormat(buffer, sizeof(buffer), message, 4); + + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + C_ShowActivity2(author, tag, "%s", buffer); + } + else { + MC_ShowActivity2(author, tag, "%s", buffer); + } +} + + +/** + * Replaces color tags in a string with color codes + * + * @param message String. + * @param maxlength Maximum length of the string buffer. + */ +stock void CFormatColor(char[] message, int maxlength, int author = -1) { + if (!g_bCFixColors) { + CFixColors(); + } + + if (!IsSource2009()) { + if (author == 0) { + author = -1; + } + + C_Format(message, maxlength, author); + } + else { + if (author == -1) { + author = 0; + } + + MC_ReplaceColorCodes(message, author, false, maxlength); + } +} + +/** + * Removes color tags from a message + * + * @param message Message to remove tags from + * @param maxlen Maximum buffer length + */ +stock void CRemoveTags(char[] message, int maxlen) { + if (!IsSource2009()) { + C_RemoveTags(message, maxlen); + } + else { + MC_RemoveTags(message, maxlen); + } +} + +/** + * Fixes missing Lightgreen color. + */ +stock void CFixColors() { + g_bCFixColors = true; + + // Replace lightgreen if not exists + if (!C_ColorAllowed(Color_Lightgreen)) { + if (C_ColorAllowed(Color_Lime)) { + C_ReplaceColor(Color_Lightgreen, Color_Lime); + } + else if (C_ColorAllowed(Color_Olive)) { + C_ReplaceColor(Color_Lightgreen, Color_Olive); + } + } +} + +stock bool IsSource2009() { + return (GetEngineVersion() == Engine_CSS + || GetEngineVersion() == Engine_HL2DM + || GetEngineVersion() == Engine_DODS + || GetEngineVersion() == Engine_TF2 + || GetEngineVersion() == Engine_SDK2013); +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/include/tfdb.inc b/roles/sourcemod/files/addons/sourcemod/scripting/include/tfdb.inc new file mode 100644 index 00000000..a6717d4e --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/include/tfdb.inc @@ -0,0 +1,940 @@ +#if defined _tfdb_included + #endinput +#endif +#define _tfdb_included + +// ---- General settings ----------------------------------------------------------- +#define FPS_LOGIC_RATE 20.0 +#define FPS_LOGIC_INTERVAL 1.0 / FPS_LOGIC_RATE + +/** Conversion factor: Hammer units/s to MPH. (1 HU ≈ 0.01905m) */ +#define HAMMER_TO_MPH 0.042614 + +// ---- Maximum structure sizes ---------------------------------------------------- +#define MAX_ROCKETS 100 +#define MAX_ROCKET_CLASSES 50 +#define MAX_SPAWNER_CLASSES 50 +#define MAX_SPAWN_POINTS 100 + +// ---- Flags and types constants -------------------------------------------------- + +/** + * Rocket behaviour types that determine how rockets track targets. + */ +enum BehaviourTypes +{ + Behaviour_Unknown, /**< Unknown/undefined behaviour */ + Behaviour_Homing, /**< Modern homing - smooth tracking with turn rate */ + Behaviour_LegacyHoming /**< Legacy homing - instant direction changes */ +}; + +/** + * Rocket behaviour flags (bitfield). Combine using bitwise OR (|). + * Use TestFlags() macro to check if a flag is set. + */ +enum RocketFlags +{ + RocketFlag_None = 0, /**< No flags set */ + RocketFlag_PlaySpawnSound = 1 << 0, /**< Play sound when rocket spawns */ + RocketFlag_PlayBeepSound = 1 << 1, /**< Play periodic beep sound */ + RocketFlag_PlayAlertSound = 1 << 2, /**< Play alert sound when target changes */ + RocketFlag_ElevateOnDeflect = 1 << 3, /**< Rocket elevates after deflection */ + RocketFlag_IsNeutral = 1 << 4, /**< Rocket has no team (grey skin) */ + RocketFlag_Exploded = 1 << 5, /**< Rocket has exploded (internal) */ + RocketFlag_OnSpawnCmd = 1 << 6, /**< Execute commands on spawn */ + RocketFlag_OnDeflectCmd = 1 << 7, /**< Execute commands on deflect */ + RocketFlag_OnKillCmd = 1 << 8, /**< Execute commands on kill */ + RocketFlag_OnExplodeCmd = 1 << 9, /**< Execute commands on explode */ + RocketFlag_CustomModel = 1 << 10, /**< Uses custom model */ + RocketFlag_CustomSpawnSound = 1 << 11, /**< Uses custom spawn sound */ + RocketFlag_CustomBeepSound = 1 << 12, /**< Uses custom beep sound */ + RocketFlag_CustomAlertSound = 1 << 13, /**< Uses custom alert sound */ + RocketFlag_Elevating = 1 << 14, /**< Currently elevating (internal) */ + RocketFlag_IsAnimated = 1 << 15, /**< Model has animations */ + RocketFlag_IsTRLimited = 1 << 16, /**< Turn rate has a limit */ + RocketFlag_IsSpeedLimited = 1 << 17, /**< Speed has a limit */ + RocketFlag_KeepDirection = 1 << 18, /**< Maintain direction after deflect */ + RocketFlag_TeamlessHits = 1 << 19, /**< Can hit players of any team */ + RocketFlag_ResetBounces = 1 << 20, /**< Reset bounce count on deflect */ + RocketFlag_NoBounceDrags = 1 << 21, /**< Can't drag while bouncing */ + RocketFlag_OnNoTargetCmd = 1 << 22, /**< Execute commands when no target */ + RocketFlag_CanBeStolen = 1 << 23, /**< Rocket can be stolen by other players */ + RocketFlag_StealTeamCheck = 1 << 24, /**< Only teammates can steal */ + RocketFlag_CrawlBounce = 1 << 25 /**< Use crawl bounce scaling/clamping */ + }; + +/** + * Sound types for rocket audio. + */ +enum RocketSound +{ + RocketSound_Spawn, /**< Sound played when rocket spawns */ + RocketSound_Beep, /**< Periodic tracking beep sound */ + RocketSound_Alert /**< Sound played when target changes */ +}; + +/** + * Rocket state flags (bitfield). Indicates current rocket status. + */ +enum RocketState +{ + RocketState_None = 0, /**< Default state */ + RocketState_Dragging = 1 << 0, /**< Player is dragging the rocket */ + RocketState_Bouncing = 1 << 1, /**< Rocket is bouncing off surfaces */ + RocketState_Stolen = 1 << 2, /**< Rocket was stolen from another player */ + RocketState_Delayed = 1 << 3, /**< Rocket is in delay prevention mode */ + RocketState_CanDrag = 1 << 4 /**< Rocket can be dragged by owner */ +}; + +/** + * Steal information for a client. + */ +enum struct eRocketSteal +{ + bool stoleRocket; /**< Whether client stole a rocket this round */ + int rocketsStolen; /**< Number of rockets stolen this round */ +} + +/** Macro to test if flags are set: TestFlags(flags, RocketFlag_PlaySpawnSound) */ +#define TestFlags(%1,%2) (!!((%1) & (%2))) + +// ---- Other resources ------------------------------------------------------------ +#define SOUND_DEFAULT_SPAWN "weapons/sentry_rocket.wav" +#define SOUND_DEFAULT_BEEP "weapons/sentry_scan.wav" +#define SOUND_DEFAULT_ALERT "weapons/sentry_spot.wav" +#define SOUND_DEFAULT_SPEEDUP "misc/doomsday_lift_warning.wav" +#define SNDCHAN_MUSIC 32 +#define PARTICLE_NUKE_1 "fireSmokeExplosion" +#define PARTICLE_NUKE_2 "fireSmokeExplosion1" +#define PARTICLE_NUKE_3 "fireSmokeExplosion2" +#define PARTICLE_NUKE_4 "fireSmokeExplosion3" +#define PARTICLE_NUKE_5 "fireSmokeExplosion4" +#define PARTICLE_NUKE_COLLUMN "fireSmoke_collumnP" +#define PARTICLE_NUKE_1_ANGLES view_as({270.0, 0.0, 0.0}) +#define PARTICLE_NUKE_2_ANGLES PARTICLE_NUKE_1_ANGLES +#define PARTICLE_NUKE_3_ANGLES PARTICLE_NUKE_1_ANGLES +#define PARTICLE_NUKE_4_ANGLES PARTICLE_NUKE_1_ANGLES +#define PARTICLE_NUKE_5_ANGLES PARTICLE_NUKE_1_ANGLES +#define PARTICLE_NUKE_COLLUMN_ANGLES PARTICLE_NUKE_1_ANGLES + +// ---- Forwards -------------------------------------------------- + +/** + * Called when a rocket has been created and spawned. + * + * @param iIndex The rocket's internal index (0 to MAX_ROCKETS-1). + * @param iEntity The rocket's entity index. + */ +forward void TFDB_OnRocketCreated(int iIndex, int iEntity); + +/** + * Called before a rocket is created. Return Plugin_Handled to prevent creation. + * + * @param iIndex The rocket's internal index (0 to MAX_ROCKETS-1). + * @param iClass The rocket class index. Can be modified. + * @param iFlags The rocket flags. Can be modified. + * @return Plugin_Continue to allow, Plugin_Handled to block. + */ +forward Action TFDB_OnRocketCreatedPre(int iIndex, int &iClass, RocketFlags &iFlags); + +/** + * Called when a rocket has been deflected by a player. + * + * @param iIndex The rocket's internal index. + * @param iEntity The rocket's entity index. + * @param iOwner The client index of the new owner (deflector). + */ +forward void TFDB_OnRocketDeflect(int iIndex, int iEntity, int iOwner); + +/** + * Called before a rocket deflection. Return Plugin_Handled to prevent deflection. + * + * @param iIndex The rocket's internal index. + * @param iEntity The rocket's entity index. + * @param iOwner The client index of the new owner (deflector). + * @param iTarget The new target client. Can be modified. + * @return Plugin_Continue to allow, Plugin_Handled to block. + */ +forward Action TFDB_OnRocketDeflectPre(int iIndex, int iEntity, int iOwner, int &iTarget); + +/** + * Called when a player steals another player's rocket. + * + * @param iIndex The rocket's internal index. + * @param iOwner The client who stole the rocket. + * @param iTarget The original target who lost the rocket. + * @param iStealCount The number of times this player has stolen rockets this round. + */ +forward void TFDB_OnRocketSteal(int iIndex, int iOwner, int iTarget, int iStealCount); + +/** + * Called when a rocket has no valid target available. + * + * @param iIndex The rocket's internal index. + * @param iTarget The previous target (may be invalid). + * @param iOwner The rocket's current owner. + */ +forward void TFDB_OnRocketNoTarget(int iIndex, int iTarget, int iOwner); + +/** + * Called when a rocket enters delay prevention mode (player is delaying). + * + * @param iIndex The rocket's internal index. + * @param iTarget The target player who is delaying. + */ +forward void TFDB_OnRocketDelay(int iIndex, int iTarget); + +/** + * Called when a rocket bounces off a surface. + * + * @param iIndex The rocket's internal index. + * @param iEntity The rocket's entity index. + */ +forward void TFDB_OnRocketBounce(int iIndex, int iEntity); + +/** + * Called before a rocket bounces. Return Plugin_Handled to prevent bounce. + * + * @param iIndex The rocket's internal index. + * @param iEntity The rocket's entity index. + * @param fAngles The bounce angles. Can be modified. + * @param fVelocity The bounce velocity. Can be modified. + * @return Plugin_Continue to allow, Plugin_Handled to block. + */ +forward Action TFDB_OnRocketBouncePre(int iIndex, int iEntity, float fAngles[3], float fVelocity[3]); + +/** + * Called after a rockets configuration file has been loaded and parsed. + * + * @param strConfigFile The name of the configuration file that was loaded. + */ +forward void TFDB_OnRocketsConfigExecuted(const char[] strConfigFile); + +/** + * Called when a rocket's state changes. + * + * @param iIndex The rocket's internal index. + * @param iState The previous state. + * @param iNewState The new state. + */ +forward void TFDB_OnRocketStateChanged(int iIndex, RocketState iState, RocketState iNewState); + +// ---- Natives -------------------------------------------------- + +// ============ Rocket Instance Functions ============ + +/** + * Checks if a rocket index is valid and the rocket exists. + * + * @param iIndex The rocket's internal index (0 to MAX_ROCKETS-1). + * @return True if the rocket exists, false otherwise. + */ +native bool TFDB_IsValidRocket(int iIndex); + +/** + * Finds a rocket's internal index by its entity index. + * + * @param iEntity The rocket's entity index. + * @return The rocket's internal index, or -1 if not found. + */ +native int TFDB_FindRocketByEntity(int iEntity); + +/** + * Checks if the dodgeball gamemode is currently enabled. + * + * @return True if dodgeball is enabled, false otherwise. + */ +native bool TFDB_IsDodgeballEnabled(); + +/** + * Gets the entity index of a rocket. + * + * @param iIndex The rocket's internal index. + * @return The rocket's entity index. + */ +native int TFDB_GetRocketEntity(int iIndex); + +/** + * Gets the behaviour flags of a rocket instance. + * + * @param iIndex The rocket's internal index. + * @return The rocket's RocketFlags bitfield. + */ +native RocketFlags TFDB_GetRocketFlags(int iIndex); + +/** + * Sets the behaviour flags of a rocket instance. + * + * @param iIndex The rocket's internal index. + * @param iFlags The new RocketFlags bitfield. + */ +native void TFDB_SetRocketFlags(int iIndex, RocketFlags iFlags); + +/** + * Gets the current target of a rocket. + * + * @param iIndex The rocket's internal index. + * @return The target client index, or -1 if no target. + */ +native int TFDB_GetRocketTarget(int iIndex); + +/** + * Sets the target of a rocket. + * + * @param iIndex The rocket's internal index. + * @param iTarget The new target client index (as entity reference). + */ +native void TFDB_SetRocketTarget(int iIndex, int iTarget); + +/** + * Gets the deflection count for the current event (resets each round). + * + * @param iIndex The rocket's internal index. + * @return The number of deflections this event. + */ +native int TFDB_GetRocketEventDeflections(int iIndex); + +/** + * Sets the event deflection count. + * + * @param iIndex The rocket's internal index. + * @param iDeflections The new deflection count. + */ +native void TFDB_SetRocketEventDeflections(int iIndex, int iDeflections); + +/** + * Gets the total deflection count for this rocket's lifetime. + * + * @param iIndex The rocket's internal index. + * @return The total number of deflections. + */ +native int TFDB_GetRocketDeflections(int iIndex); + +/** + * Sets the total deflection count. + * + * @param iIndex The rocket's internal index. + * @param iDeflections The new total deflection count. + */ +native void TFDB_SetRocketDeflections(int iIndex, int iDeflections); + +/** + * Gets the class index of a rocket. + * + * @param iIndex The rocket's internal index. + * @return The rocket class index. + */ +native int TFDB_GetRocketClass(int iIndex); + +/** + * Sets the class of a rocket. + * + * @param iIndex The rocket's internal index. + * @param iClass The new rocket class index. + */ +native void TFDB_SetRocketClass(int iIndex, int iClass); + +// ============ Rocket Class Configuration ============ + +/** + * Gets the total number of rocket classes defined. + * + * @return The number of rocket classes. + */ +native int TFDB_GetRocketClassCount(); + +/** + * Gets the behaviour type of a rocket class. + * + * @param iClass The rocket class index. + * @return The BehaviourTypes value (Homing, LegacyHoming, etc). + */ +native BehaviourTypes TFDB_GetRocketClassBehaviour(int iClass); + +/** + * Sets the behaviour type of a rocket class. + * + * @param iClass The rocket class index. + * @param iBehaviour The new BehaviourTypes value. + */ +native void TFDB_SetRocketClassBehaviour(int iClass, BehaviourTypes iBehaviour); + +/** Gets the default flags of a rocket class. */ +native RocketFlags TFDB_GetRocketClassFlags(int iClass); + +/** Sets the default flags of a rocket class. */ +native void TFDB_SetRocketClassFlags(int iClass, RocketFlags iFlags); + +/** Gets the base damage of a rocket class. */ +native float TFDB_GetRocketClassDamage(int iClass); + +/** Sets the base damage of a rocket class. */ +native void TFDB_SetRocketClassDamage(int iClass, float fDamage); + +/** Gets the damage increment per deflection. */ +native float TFDB_GetRocketClassDamageIncrement(int iClass); + +/** Sets the damage increment per deflection. */ +native void TFDB_SetRocketClassDamageIncrement(int iClass, float fDamage); + +/** Gets the base speed (in Hammer units/second). */ +native float TFDB_GetRocketClassSpeed(int iClass); + +/** Sets the base speed (in Hammer units/second). */ +native void TFDB_SetRocketClassSpeed(int iClass, float fSpeed); + +/** Gets the speed increment per deflection. */ +native float TFDB_GetRocketClassSpeedIncrement(int iClass); + +/** Sets the speed increment per deflection. */ +native void TFDB_SetRocketClassSpeedIncrement(int iClass, float fSpeed); + +/** Gets the maximum speed limit (0 = no limit). */ +native float TFDB_GetRocketClassSpeedLimit(int iClass); + +/** Sets the maximum speed limit (0 = no limit). */ +native void TFDB_SetRocketClassSpeedLimit(int iClass, float fSpeed); + +/** Gets the base turn rate (degrees/second). */ +native float TFDB_GetRocketClassTurnRate(int iClass); + +/** Sets the base turn rate (degrees/second). */ +native void TFDB_SetRocketClassTurnRate(int iClass, float fTurnRate); + +/** Gets the turn rate increment per deflection. */ +native float TFDB_GetRocketClassTurnRateIncrement(int iClass); + +/** Sets the turn rate increment per deflection. */ +native void TFDB_SetRocketClassTurnRateIncrement(int iClass, float fTurnRate); + +/** Gets the maximum turn rate limit (0 = no limit). */ +native float TFDB_GetRocketClassTurnRateLimit(int iClass); + +/** Sets the maximum turn rate limit (0 = no limit). */ +native void TFDB_SetRocketClassTurnRateLimit(int iClass, float fTurnRate); + +/** Gets the elevation rate (vertical tracking speed). */ +native float TFDB_GetRocketClassElevationRate(int iClass); + +/** Sets the elevation rate (vertical tracking speed). */ +native void TFDB_SetRocketClassElevationRate(int iClass, float fElevation); + +/** Gets the maximum elevation limit. */ +native float TFDB_GetRocketClassElevationLimit(int iClass); + +/** Sets the maximum elevation limit. */ +native void TFDB_SetRocketClassElevationLimit(int iClass, float fElevation); + +/** Gets the rockets modifier (affects speed based on active rockets). */ +native float TFDB_GetRocketClassRocketsModifier(int iClass); + +/** Sets the rockets modifier (affects speed based on active rockets). */ +native void TFDB_SetRocketClassRocketsModifier(int iClass, float fModifier); + +/** Gets the player modifier (affects speed based on player count). */ +native float TFDB_GetRocketClassPlayerModifier(int iClass); + +/** Sets the player modifier (affects speed based on player count). */ +native void TFDB_SetRocketClassPlayerModifier(int iClass, float fModifier); + +/** Gets the control delay (time before player can redirect after deflect). */ +native float TFDB_GetRocketClassControlDelay(int iClass); + +/** Sets the control delay. */ +native void TFDB_SetRocketClassControlDelay(int iClass, float fDelay); + +/** Gets the minimum drag time. */ +native float TFDB_GetRocketClassDragTimeMin(int iClass); + +/** Sets the minimum drag time. */ +native void TFDB_SetRocketClassDragTimeMin(int iClass, float fMin); + +/** Gets the maximum drag time. */ +native float TFDB_GetRocketClassDragTimeMax(int iClass); + +/** Sets the maximum drag time. */ +native void TFDB_SetRocketClassDragTimeMax(int iClass, float fMax); + +/** Sets the total number of rocket classes. */ +native void TFDB_SetRocketClassCount(int iCount); + +/** Sets the entity index of a rocket. */ +native void TFDB_SetRocketEntity(int iIndex, int iEntity); + +/** Gets the maximum number of bounces allowed. */ +native int TFDB_GetRocketClassMaxBounces(int iClass); + +/** Sets the maximum number of bounces allowed. */ +native void TFDB_SetRocketClassMaxBounces(int iClass, int iBounces); + +// ============ Spawner Configuration ============ + +/** Gets the name of a spawner class. */ +native void TFDB_GetSpawnersName(int iSpawnerClass, char[] strBuffer, int iMaxLen); + +/** Sets the name of a spawner class. */ +native void TFDB_SetSpawnersName(int iSpawnerClass, const char[] strName); + +/** Gets the maximum rockets a spawner can fire at once. */ +native int TFDB_GetSpawnersMaxRockets(int iSpawnerClass); + +/** Sets the maximum rockets a spawner can fire at once. */ +native void TFDB_SetSpawnersMaxRockets(int iSpawnerClass, int iMaxRockets); + +/** Gets the spawn interval (seconds between rockets). */ +native float TFDB_GetSpawnersInterval(int iSpawnerClass); + +/** Sets the spawn interval (seconds between rockets). */ +native void TFDB_SetSpawnersInterval(int iSpawnerClass, float fInterval); + +/** Gets the rocket class spawn chances table. */ +native ArrayList TFDB_GetSpawnersChancesTable(int iSpawnerClass); + +/** Sets the rocket class spawn chances table. */ +native void TFDB_SetSpawnersChancesTable(int iSpawnerClass, ArrayList hTable); + +/** Gets the total number of spawner classes. */ +native int TFDB_GetSpawnersCount(); + +/** Sets the total number of spawner classes. */ +native void TFDB_SetSpawnersCount(int iCount); + +// ============ Spawn Points ============ + +/** Gets the current RED team spawn point index. */ +native int TFDB_GetCurrentRedSpawn(); + +/** Sets the current RED team spawn point index. */ +native void TFDB_SetCurrentRedSpawn(int iRedSpawn); + +/** Gets the number of RED team spawn points. */ +native int TFDB_GetSpawnPointsRedCount(); + +/** Sets the number of RED team spawn points. */ +native void TFDB_SetSpawnPointsRedCount(int iCount); + +/** Gets the spawner class assigned to a RED spawn point. */ +native int TFDB_GetSpawnPointsRedClass(int iSpawner); + +/** Sets the spawner class assigned to a RED spawn point. */ +native void TFDB_SetSpawnPointsRedClass(int iSpawner, int iSpawnerClass); + +/** Gets the entity index of a RED spawn point. */ +native int TFDB_GetSpawnPointsRedEntity(int iSpawner); + +/** Sets the entity index of a RED spawn point. */ +native void TFDB_SetSpawnPointsRedEntity(int iSpawner, int iEntity); + +/** Gets the current BLU team spawn point index. */ +native int TFDB_GetCurrentBluSpawn(); + +/** Sets the current BLU team spawn point index. */ +native void TFDB_SetCurrentBluSpawn(int iBluSpawn); + +/** Gets the number of BLU team spawn points. */ +native int TFDB_GetSpawnPointsBluCount(); + +/** Sets the number of BLU team spawn points. */ +native void TFDB_SetSpawnPointsBluCount(int iCount); + +/** Gets the spawner class assigned to a BLU spawn point. */ +native int TFDB_GetSpawnPointsBluClass(int iSpawner); + +/** Sets the spawner class assigned to a BLU spawn point. */ +native void TFDB_SetSpawnPointsBluClass(int iSpawner, int iSpawnerClass); + +/** Gets the entity index of a BLU spawn point. */ +native int TFDB_GetSpawnPointsBluEntity(int iSpawner); + +/** Sets the entity index of a BLU spawn point. */ +native void TFDB_SetSpawnPointsBluEntity(int iSpawner, int iEntity); + +// ============ Game State ============ + +/** Returns true if the round has started. */ +native bool TFDB_GetRoundStarted(); + +/** Gets the current round count. */ +native int TFDB_GetRoundCount(); + +/** Gets the total number of rockets fired this round. */ +native int TFDB_GetRocketsFired(); + +/** Gets the next scheduled rocket spawn time. */ +native float TFDB_GetNextSpawnTime(); + +/** Sets the next scheduled rocket spawn time. */ +native void TFDB_SetNextSpawnTime(float fSpawnTime); + +/** Gets the team of the last player to die. */ +native int TFDB_GetLastDeadTeam(); + +/** Gets the client index of the last player to die. */ +native int TFDB_GetLastDeadClient(); + +/** Gets the client index of the last player to steal a rocket. */ +native int TFDB_GetLastStealer(); + +// ============ Rocket Runtime State ============ + +/** Gets the current speed of a rocket (Hammer units/second). */ +native float TFDB_GetRocketSpeed(int iIndex); + +/** Sets the current speed of a rocket (Hammer units/second). */ +native void TFDB_SetRocketSpeed(int iIndex, float fSpeed); + +/** Gets the current speed of a rocket in MPH. */ +native float TFDB_GetRocketMphSpeed(int iIndex); + +/** Sets the current speed of a rocket in MPH. */ +native void TFDB_SetRocketMphSpeed(int iIndex, float fSpeed); + +/** Gets the current direction vector of a rocket. */ +native void TFDB_GetRocketDirection(int iIndex, float fBuffer[3]); + +/** Sets the direction vector of a rocket. */ +native void TFDB_SetRocketDirection(int iIndex, float fDirection[3]); + +/** Gets the timestamp of the last deflection. */ +native float TFDB_GetRocketLastDeflectionTime(int iIndex); + +/** Sets the timestamp of the last deflection. */ +native void TFDB_SetRocketLastDeflectionTime(int iIndex, float fTime); + +/** Gets the timestamp of the last beep sound. */ +native float TFDB_GetRocketLastBeepTime(int iIndex); + +/** Sets the timestamp of the last beep sound. */ +native void TFDB_SetRocketLastBeepTime(int iIndex, float fTime); + +/** Gets the total number of active rockets. */ +native int TFDB_GetRocketCount(); + +/** Gets the spawn timestamp of a rocket. */ +native float TFDB_GetLastSpawnTime(int iIndex); + +/** Gets the number of bounces for a rocket. */ +native int TFDB_GetRocketBounces(int iIndex); + +/** Sets the number of bounces for a rocket. */ +native void TFDB_SetRocketBounces(int iIndex, int iBounces); + +// ============ Rocket Class Strings & Sounds ============ + +/** Gets the short name of a rocket class. */ +native void TFDB_GetRocketClassName(int iClass, char[] strBuffer, int iMaxLen); + +/** Sets the short name of a rocket class. */ +native void TFDB_SetRocketClassName(int iClass, const char[] strName); + +/** Gets the display name of a rocket class. */ +native void TFDB_GetRocketClassLongName(int iClass, char[] strBuffer, int iMaxLen); + +/** Sets the display name of a rocket class. */ +native void TFDB_SetRocketClassLongName(int iClass, const char[] strName); + +/** Gets the model path of a rocket class. */ +native void TFDB_GetRocketClassModel(int iClass, char[] strBuffer, int iMaxLen); + +/** Sets the model path of a rocket class. */ +native void TFDB_SetRocketClassModel(int iClass, const char[] strPath); + +/** Gets the beep sound interval (seconds). */ +native float TFDB_GetRocketClassBeepInterval(int iClass); + +/** Sets the beep sound interval (seconds). */ +native void TFDB_SetRocketClassBeepInterval(int iClass, float fInterval); + +/** Gets the spawn sound path. */ +native void TFDB_GetRocketClassSpawnSound(int iClass, char[] strBuffer, int iMaxLen); + +/** Sets the spawn sound path. */ +native void TFDB_SetRocketClassSpawnSound(int iClass, const char[] strPath); + +/** Gets the beep sound path. */ +native void TFDB_GetRocketClassBeepSound(int iClass, char[] strBuffer, int iMaxLen); + +/** Sets the beep sound path. */ +native void TFDB_SetRocketClassBeepSound(int iClass, const char[] strPath); + +/** Gets the alert sound path. */ +native void TFDB_GetRocketClassAlertSound(int iClass, char[] strBuffer, int iMaxLen); + +/** Sets the alert sound path. */ +native void TFDB_SetRocketClassAlertSound(int iClass, const char[] strPath); + +/** Gets the critical hit chance (0.0 to 1.0). */ +native float TFDB_GetRocketClassCritChance(int iClass); + +/** Sets the critical hit chance (0.0 to 1.0). */ +native void TFDB_SetRocketClassCritChance(int iClass, float fChance); + +/** Gets the target weight for target selection. */ +native float TFDB_GetRocketClassTargetWeight(int iClass); + +/** Sets the target weight for target selection. */ +native void TFDB_SetRocketClassTargetWeight(int iClass, float fWeight); + +// ============ Command Handlers ============ + +/** Gets the commands to execute on rocket spawn. */ +native DataPack TFDB_GetRocketClassCmdsOnSpawn(int iClass); + +/** Sets the commands to execute on rocket spawn. */ +native void TFDB_SetRocketClassCmdsOnSpawn(int iClass, DataPack hCmds); + +/** Gets the commands to execute on deflection. */ +native DataPack TFDB_GetRocketClassCmdsOnDeflect(int iClass); + +/** Sets the commands to execute on deflection. */ +native void TFDB_SetRocketClassCmdsOnDeflect(int iClass, DataPack hCmds); + +/** Gets the commands to execute when rocket kills a player. */ +native DataPack TFDB_GetRocketClassCmdsOnKill(int iClass); + +/** Sets the commands to execute when rocket kills a player. */ +native void TFDB_SetRocketClassCmdsOnKill(int iClass, DataPack hCmds); + +/** Gets the commands to execute when rocket explodes. */ +native DataPack TFDB_GetRocketClassCmdsOnExplode(int iClass); + +/** Sets the commands to execute when rocket explodes. */ +native void TFDB_SetRocketClassCmdsOnExplode(int iClass, DataPack hCmds); + +/** Gets the commands to execute when rocket has no target. */ +native DataPack TFDB_GetRocketClassCmdsOnNoTarget(int iClass); + +/** Sets the commands to execute when rocket has no target. */ +native void TFDB_SetRocketClassCmdsOnNoTarget(int iClass, DataPack hCmds); + +/** Gets the bounce velocity scale factor. */ +native float TFDB_GetRocketClassBounceScale(int iClass); + +/** Sets the bounce velocity scale factor. */ +native void TFDB_SetRocketClassBounceScale(int iClass, float fScale); + +// ============ Actions ============ + +/** + * Creates and spawns a new rocket. + * + * @param iSpawnerEntity The spawner entity to spawn from. + * @param iSpawnerClass The spawner class index. + * @param iTeam The team the rocket belongs to. + * @param iClass Optional rocket class (-1 for random from spawner chances). + */ +native void TFDB_CreateRocket(int iSpawnerEntity, int iSpawnerClass, int iTeam, int iClass = -1); + +/** Destroys a single rocket by index. */ +native void TFDB_DestroyRocket(int iIndex); + +/** Destroys all active rockets. */ +native void TFDB_DestroyRockets(); + +/** Clears all rocket class definitions. */ +native void TFDB_DestroyRocketClasses(); + +/** Clears all spawner class definitions. */ +native void TFDB_DestroySpawners(); + +/** + * Parses a configuration file and loads rocket/spawner classes. + * + * @param strConfigFile Config file name in configs/dodgeball/ (default: "general.cfg") + */ +native void TFDB_ParseConfigurations(const char[] strConfigFile = "general.cfg"); + +/** Populates spawn points from the current map. */ +native void TFDB_PopulateSpawnPoints(); + +/** Runs the homing rocket think logic for a rocket. */ +native void TFDB_HomingRocketThink(int iIndex); + +/** Runs the legacy homing rocket think logic for a rocket. */ +native void TFDB_RocketLegacyThink(int iIndex); + +/** Gets the state flags of a rocket. */ +native RocketState TFDB_GetRocketState(int iIndex); + +/** Sets the state flags of a rocket. */ +native void TFDB_SetRocketState(int iIndex, RocketState iState); + +/** + * Gets steal info for a client. + * + * @param iClient Client index. + * @param stole Output: whether client stole a rocket this round. + * @param count Output: number of rockets stolen this round. + */ +native void TFDB_GetStealInfo(int iClient, bool &stole, int &count); + +/** + * Sets steal info for a client. + * + * @param iClient Client index. + * @param stole Whether client stole a rocket. + * @param count Number of rockets stolen. + */ +native void TFDB_SetStealInfo(int iClient, bool stole, int count); + +public SharedPlugin __pl_TFDB = +{ + name = "tfdb", + file = "TF2Dodgeball.smx", + + #if defined REQUIRE_PLUGIN + required=1, + #else + required=0, + #endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_TFDB_SetNTVOptional() +{ + MarkNativeAsOptional("TFDB_IsValidRocket"); + MarkNativeAsOptional("TFDB_FindRocketByEntity"); + MarkNativeAsOptional("TFDB_IsDodgeballEnabled"); + MarkNativeAsOptional("TFDB_GetRocketEntity"); + MarkNativeAsOptional("TFDB_GetRocketFlags"); + MarkNativeAsOptional("TFDB_SetRocketFlags"); + MarkNativeAsOptional("TFDB_GetRocketTarget"); + MarkNativeAsOptional("TFDB_SetRocketTarget"); + MarkNativeAsOptional("TFDB_GetRocketEventDeflections"); + MarkNativeAsOptional("TFDB_SetRocketEventDeflections"); + MarkNativeAsOptional("TFDB_GetRocketDeflections"); + MarkNativeAsOptional("TFDB_SetRocketDeflections"); + MarkNativeAsOptional("TFDB_GetRocketClass"); + MarkNativeAsOptional("TFDB_SetRocketClass"); + MarkNativeAsOptional("TFDB_GetRocketClassCount"); + MarkNativeAsOptional("TFDB_GetRocketClassBehaviour"); + MarkNativeAsOptional("TFDB_SetRocketClassBehaviour"); + MarkNativeAsOptional("TFDB_GetRocketClassFlags"); + MarkNativeAsOptional("TFDB_SetRocketClassFlags"); + MarkNativeAsOptional("TFDB_GetRocketClassDamage"); + MarkNativeAsOptional("TFDB_SetRocketClassDamage"); + MarkNativeAsOptional("TFDB_GetRocketClassDamageIncrement"); + MarkNativeAsOptional("TFDB_SetRocketClassDamageIncrement"); + MarkNativeAsOptional("TFDB_GetRocketClassSpeed"); + MarkNativeAsOptional("TFDB_SetRocketClassSpeed"); + MarkNativeAsOptional("TFDB_GetRocketClassSpeedIncrement"); + MarkNativeAsOptional("TFDB_SetRocketClassSpeedIncrement"); + MarkNativeAsOptional("TFDB_GetRocketClassSpeedLimit"); + MarkNativeAsOptional("TFDB_SetRocketClassSpeedLimit"); + MarkNativeAsOptional("TFDB_GetRocketClassTurnRate"); + MarkNativeAsOptional("TFDB_SetRocketClassTurnRate"); + MarkNativeAsOptional("TFDB_GetRocketClassTurnRateIncrement"); + MarkNativeAsOptional("TFDB_SetRocketClassTurnRateIncrement"); + MarkNativeAsOptional("TFDB_GetRocketClassTurnRateLimit"); + MarkNativeAsOptional("TFDB_SetRocketClassTurnRateLimit"); + MarkNativeAsOptional("TFDB_GetRocketClassElevationRate"); + MarkNativeAsOptional("TFDB_SetRocketClassElevationRate"); + MarkNativeAsOptional("TFDB_GetRocketClassElevationLimit"); + MarkNativeAsOptional("TFDB_SetRocketClassElevationLimit"); + MarkNativeAsOptional("TFDB_GetRocketClassRocketsModifier"); + MarkNativeAsOptional("TFDB_SetRocketClassRocketsModifier"); + MarkNativeAsOptional("TFDB_GetRocketClassPlayerModifier"); + MarkNativeAsOptional("TFDB_SetRocketClassPlayerModifier"); + MarkNativeAsOptional("TFDB_GetRocketClassControlDelay"); + MarkNativeAsOptional("TFDB_SetRocketClassControlDelay"); + MarkNativeAsOptional("TFDB_GetRocketClassDragTimeMin"); + MarkNativeAsOptional("TFDB_SetRocketClassDragTimeMin"); + MarkNativeAsOptional("TFDB_GetRocketClassDragTimeMax"); + MarkNativeAsOptional("TFDB_SetRocketClassDragTimeMax"); + MarkNativeAsOptional("TFDB_SetRocketClassCount"); + MarkNativeAsOptional("TFDB_SetRocketEntity"); + MarkNativeAsOptional("TFDB_GetRocketClassMaxBounces"); + MarkNativeAsOptional("TFDB_SetRocketClassMaxBounces"); + MarkNativeAsOptional("TFDB_GetSpawnersName"); + MarkNativeAsOptional("TFDB_SetSpawnersName"); + MarkNativeAsOptional("TFDB_GetSpawnersMaxRockets"); + MarkNativeAsOptional("TFDB_SetSpawnersMaxRockets"); + MarkNativeAsOptional("TFDB_GetSpawnersInterval"); + MarkNativeAsOptional("TFDB_SetSpawnersInterval"); + MarkNativeAsOptional("TFDB_GetSpawnersChancesTable"); + MarkNativeAsOptional("TFDB_SetSpawnersChancesTable"); + MarkNativeAsOptional("TFDB_GetSpawnersCount"); + MarkNativeAsOptional("TFDB_SetSpawnersCount"); + MarkNativeAsOptional("TFDB_GetCurrentRedSpawn"); + MarkNativeAsOptional("TFDB_SetCurrentRedSpawn"); + MarkNativeAsOptional("TFDB_GetSpawnPointsRedCount"); + MarkNativeAsOptional("TFDB_SetSpawnPointsRedCount"); + MarkNativeAsOptional("TFDB_GetSpawnPointsRedClass"); + MarkNativeAsOptional("TFDB_SetSpawnPointsRedClass"); + MarkNativeAsOptional("TFDB_GetSpawnPointsRedEntity"); + MarkNativeAsOptional("TFDB_SetSpawnPointsRedEntity"); + MarkNativeAsOptional("TFDB_GetCurrentBluSpawn"); + MarkNativeAsOptional("TFDB_SetCurrentBluSpawn"); + MarkNativeAsOptional("TFDB_GetSpawnPointsBluCount"); + MarkNativeAsOptional("TFDB_SetSpawnPointsBluCount"); + MarkNativeAsOptional("TFDB_GetSpawnPointsBluClass"); + MarkNativeAsOptional("TFDB_SetSpawnPointsBluClass"); + MarkNativeAsOptional("TFDB_GetSpawnPointsBluEntity"); + MarkNativeAsOptional("TFDB_SetSpawnPointsBluEntity"); + MarkNativeAsOptional("TFDB_GetRoundStarted"); + MarkNativeAsOptional("TFDB_GetRoundCount"); + MarkNativeAsOptional("TFDB_GetRocketsFired"); + MarkNativeAsOptional("TFDB_GetNextSpawnTime"); + MarkNativeAsOptional("TFDB_SetNextSpawnTime"); + MarkNativeAsOptional("TFDB_GetLastDeadTeam"); + MarkNativeAsOptional("TFDB_GetLastDeadClient"); + MarkNativeAsOptional("TFDB_GetLastStealer"); + MarkNativeAsOptional("TFDB_GetRocketSpeed"); + MarkNativeAsOptional("TFDB_SetRocketSpeed"); + MarkNativeAsOptional("TFDB_GetRocketMphSpeed"); + MarkNativeAsOptional("TFDB_SetRocketMphSpeed"); + MarkNativeAsOptional("TFDB_GetRocketDirection"); + MarkNativeAsOptional("TFDB_SetRocketDirection"); + MarkNativeAsOptional("TFDB_GetRocketLastDeflectionTime"); + MarkNativeAsOptional("TFDB_SetRocketLastDeflectionTime"); + MarkNativeAsOptional("TFDB_GetRocketLastBeepTime"); + MarkNativeAsOptional("TFDB_SetRocketLastBeepTime"); + MarkNativeAsOptional("TFDB_GetRocketCount"); + MarkNativeAsOptional("TFDB_GetLastSpawnTime"); + MarkNativeAsOptional("TFDB_GetRocketBounces"); + MarkNativeAsOptional("TFDB_SetRocketBounces"); + MarkNativeAsOptional("TFDB_GetRocketClassName"); + MarkNativeAsOptional("TFDB_SetRocketClassName"); + MarkNativeAsOptional("TFDB_GetRocketClassLongName"); + MarkNativeAsOptional("TFDB_SetRocketClassLongName"); + MarkNativeAsOptional("TFDB_GetRocketClassModel"); + MarkNativeAsOptional("TFDB_SetRocketClassModel"); + MarkNativeAsOptional("TFDB_GetRocketClassBeepInterval"); + MarkNativeAsOptional("TFDB_SetRocketClassBeepInterval"); + MarkNativeAsOptional("TFDB_GetRocketClassSpawnSound"); + MarkNativeAsOptional("TFDB_SetRocketClassSpawnSound"); + MarkNativeAsOptional("TFDB_GetRocketClassBeepSound"); + MarkNativeAsOptional("TFDB_SetRocketClassBeepSound"); + MarkNativeAsOptional("TFDB_GetRocketClassAlertSound"); + MarkNativeAsOptional("TFDB_SetRocketClassAlertSound"); + MarkNativeAsOptional("TFDB_GetRocketClassCritChance"); + MarkNativeAsOptional("TFDB_SetRocketClassCritChance"); + MarkNativeAsOptional("TFDB_GetRocketClassTargetWeight"); + MarkNativeAsOptional("TFDB_SetRocketClassTargetWeight"); + MarkNativeAsOptional("TFDB_GetRocketClassCmdsOnSpawn"); + MarkNativeAsOptional("TFDB_SetRocketClassCmdsOnSpawn"); + MarkNativeAsOptional("TFDB_GetRocketClassCmdsOnDeflect"); + MarkNativeAsOptional("TFDB_SetRocketClassCmdsOnDeflect"); + MarkNativeAsOptional("TFDB_GetRocketClassCmdsOnKill"); + MarkNativeAsOptional("TFDB_SetRocketClassCmdsOnKill"); + MarkNativeAsOptional("TFDB_GetRocketClassCmdsOnExplode"); + MarkNativeAsOptional("TFDB_SetRocketClassCmdsOnExplode"); + MarkNativeAsOptional("TFDB_GetRocketClassCmdsOnNoTarget"); + MarkNativeAsOptional("TFDB_SetRocketClassCmdsOnNoTarget"); + MarkNativeAsOptional("TFDB_GetRocketClassBounceScale"); + MarkNativeAsOptional("TFDB_SetRocketClassBounceScale"); + MarkNativeAsOptional("TFDB_CreateRocket"); + MarkNativeAsOptional("TFDB_DestroyRocket"); + MarkNativeAsOptional("TFDB_DestroyRockets"); + MarkNativeAsOptional("TFDB_DestroyRocketClasses"); + MarkNativeAsOptional("TFDB_DestroySpawners"); + MarkNativeAsOptional("TFDB_ParseConfigurations"); + MarkNativeAsOptional("TFDB_PopulateSpawnPoints"); + MarkNativeAsOptional("TFDB_HomingRocketThink"); + MarkNativeAsOptional("TFDB_RocketLegacyThink"); + MarkNativeAsOptional("TFDB_GetRocketState"); + MarkNativeAsOptional("TFDB_SetRocketState"); + MarkNativeAsOptional("TFDB_GetStealInfo"); + MarkNativeAsOptional("TFDB_SetStealInfo"); +} +#endif diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_airblast_prevention.sp b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_airblast_prevention.sp new file mode 100644 index 00000000..b3def29b --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_airblast_prevention.sp @@ -0,0 +1,185 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include + +#include + +#define PLUGIN_NAME "[TFDB] Airblast push prevention" +#define PLUGIN_AUTHOR "BloodyNightmare, Mitchell, edited by x07x08" +#define PLUGIN_DESCRIPTION "Prevents users from pushing each other by airblasting." +#define PLUGIN_VERSION "1.2.2" +#define PLUGIN_URL "https://github.com/x07x08/TF2-Dodgeball-Modified" + +ConVar CvarCommandEnabled; +bool ClientEnabled[MAXPLAYERS + 1] = {true, ...}; +bool Loaded; + +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = PLUGIN_DESCRIPTION, + version = PLUGIN_VERSION, + url = PLUGIN_URL +}; + +public void OnPluginStart() +{ + LoadTranslations("tfdb.phrases.txt"); + + CvarCommandEnabled = CreateConVar("tf_dodgeball_ab_command", "1", "Allow people to toggle airblast prevention?\n Turning this off also enables airblast prevention on all clients.", _, true, 0.0, true, 1.0); + + RegConsoleCmd("sm_ab", CmdAirblastPrevention, "Toggles push prevention."); + + if (!TFDB_IsDodgeballEnabled()) return; + + TFDB_OnRocketsConfigExecuted("general.cfg"); + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient)) continue; + + SDKHook(iClient, SDKHook_WeaponCanUse, WeaponCanUseCallback); + + // Enabled by default + SetEntityFlags(iClient, GetEntityFlags(iClient) | FL_NOTARGET); + } +} + +public void TFDB_OnRocketsConfigExecuted(const char[] strConfigFile) +{ + if (Loaded) return; + + HookEvent("player_spawn", OnPlayerSpawn); + HookEvent("player_death", OnPlayerDeath); + + CvarCommandEnabled.AddChangeHook(CvarCommandCallback); + + Loaded = true; +} + +public void OnMapEnd() +{ + if (!Loaded) return; + + UnhookEvent("player_spawn", OnPlayerSpawn); + UnhookEvent("player_death", OnPlayerDeath); + + CvarCommandEnabled.RemoveChangeHook(CvarCommandCallback); + + Loaded = false; +} + +public void OnPlayerSpawn(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + int iClient = GetClientOfUserId(hEvent.GetInt("userid")); + + if (ClientEnabled[iClient]) + { + // Sound issue. + SetEntityFlags(iClient, GetEntityFlags(iClient) | FL_NOTARGET); + } +} + +public void OnPlayerDeath(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + int iClient = GetClientOfUserId(hEvent.GetInt("userid")); + + // If you spectate an enemy, he will "airblast" you. + // Forcefully adding FL_NOTARGET seems to fix the problem. + SetEntityFlags(iClient, GetEntityFlags(iClient) | FL_NOTARGET); +} + +public Action CmdAirblastPrevention(int iClient, int iArgs) +{ + if (iClient == 0) + { + ReplyToCommand(iClient, "Command is in-game only."); + + return Plugin_Handled; + } + + if (!TFDB_IsDodgeballEnabled() || !CvarCommandEnabled.BoolValue) + { + CReplyToCommand(iClient, "%t", "Command_Disabled"); + + return Plugin_Handled; + } + + ClientEnabled[iClient] = !ClientEnabled[iClient]; + + if (IsPlayerAlive(iClient)) + { + ToggleAirblastPrevention(iClient); + } + + CReplyToCommand(iClient, "%t", ClientEnabled[iClient] ? "Dodgeball_AirblastPreventionCmd_Enabled" : "Dodgeball_AirblastPreventionCmd_Disabled"); + + return Plugin_Handled; +} + +public void OnClientDisconnect(int iClient) +{ + ClientEnabled[iClient] = false; +} + +public void OnClientConnected(int iClient) +{ + ClientEnabled[iClient] = true; +} + +public void OnClientPutInServer(int iClient) +{ + if (!Loaded) return; + + SDKHook(iClient, SDKHook_WeaponCanUse, WeaponCanUseCallback); +} + +// https://forums.alliedmods.net/showthread.php?t=265707 +// SDKHook_WeaponCanUse fires for any weapon change (including pickup) + +public Action WeaponCanUseCallback(int iClient, int iWeapon) +{ + int iFlags = GetEntityFlags(iClient); + + if (!(iFlags & FL_NOTARGET)) return Plugin_Continue; + + SetEntityFlags(iClient, iFlags & ~FL_NOTARGET); + + RequestFrame(ResetAirblastPrevention, GetClientUserId(iClient)); + + return Plugin_Continue; +} + +public void ResetAirblastPrevention(any iUserId) +{ + int iClient = GetClientOfUserId(iUserId); + + if (iClient == 0 || !IsClientInGame(iClient) || !ClientEnabled[iClient]) return; + + SetEntityFlags(iClient, GetEntityFlags(iClient) | FL_NOTARGET); +} + +void ToggleAirblastPrevention(int iClient) +{ + int iFlags = GetEntityFlags(iClient); + + SetEntityFlags(iClient, ClientEnabled[iClient] ? iFlags | FL_NOTARGET : iFlags & ~FL_NOTARGET); +} + +public void CvarCommandCallback(ConVar hConVar, const char[] strOldValue, const char[] strNewValue) +{ + if (hConVar.BoolValue) return; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient)) continue; + + ClientEnabled[iClient] = true; + + if (IsPlayerAlive(iClient)) SetEntityFlags(iClient, GetEntityFlags(iClient) | FL_NOTARGET); + } +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_ffa.sp b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_ffa.sp new file mode 100644 index 00000000..59cd25c9 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_ffa.sp @@ -0,0 +1,615 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include + +#include + +#define PLUGIN_NAME "[TFDB] Free-for-All" +#define PLUGIN_AUTHOR "x07x08" +#define PLUGIN_DESCRIPTION "Makes all rockets neutral" +#define PLUGIN_VERSION "1.1.3" +#define PLUGIN_URL "https://github.com/x07x08/TF2-Dodgeball-Modified" + +bool Loaded; +bool FFAEnabled; +int BotCount; +bool VoteAllowed; +float LastVoteTime; +int OldTeam[MAXPLAYERS + 1]; + +Address MyWearables; + +ConVar CvarDisableOnBot; +ConVar CvarVoteTimeout; +ConVar CvarVoteDuration; +ConVar CvarToggleMode; +ConVar CvarAllowStealing; +ConVar CvarDisableConfig; +ConVar CvarEnableConfig; +ConVar CvarSwitchTeams; +ConVar CvarFriendlyFire; + +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = PLUGIN_DESCRIPTION, + version = PLUGIN_VERSION, + url = PLUGIN_URL +}; + +public void OnPluginStart() +{ + LoadTranslations("tfdb.phrases.txt"); + + MyWearables = view_as
(FindSendPropInfo("CTFPlayer", "m_hMyWearables")); + + CvarDisableOnBot = CreateConVar("tf_dodgeball_ffa_bot", "1", "Disable FFA when a bot joins?", _, true, 0.0, true, 1.0); + CvarVoteTimeout = CreateConVar("tf_dodgeball_ffa_timeout", "150", "Vote timeout (in seconds)", _, true, 0.0); + CvarVoteDuration = CreateConVar("tf_dodgeball_ffa_duration", "20", "Vote duration (in seconds)", _, true, 0.0); + CvarToggleMode = CreateConVar("tf_dodgeball_ffa_mode", "1", "How does changing FFA affect the rockets?\n 0 - No effect, wait for the next spawn\n 1 - Destroy all active rockets\n 2 - Immediately change the rockets to be neutral", _, true, 0.0); + CvarAllowStealing = CreateConVar("tf_dodgeball_ffa_stealing", "1", "Allow stealing in FFA mode?", _, true, 0.0, true, 1.0); + CvarDisableConfig = CreateConVar("tf_dodgeball_ffa_disablecfg", "sourcemod/dodgeball_ffa_disable.cfg", "Config file to execute when disabling FFA mode"); + CvarEnableConfig = CreateConVar("tf_dodgeball_ffa_enablecfg", "sourcemod/dodgeball_ffa_enable.cfg", "Config file to execute when enabling FFA mode"); + CvarSwitchTeams = CreateConVar("tf_dodgeball_ffa_teams", "1", "Automatically swap players when a team is empty in FFA mode?", _, true, 0.0, true, 1.0); + CvarFriendlyFire = FindConVar("mp_friendlyfire"); + + RegAdminCmd("sm_ffa", CmdToggleFFA, ADMFLAG_CONFIG, "Forcefully toggle FFA"); + RegConsoleCmd("sm_voteffa", CmdVoteFFA, "Start a vote to toggle FFA"); + + if (!TFDB_IsDodgeballEnabled()) return; + + TFDB_OnRocketsConfigExecuted("general.cfg"); +} + +public void TFDB_OnRocketsConfigExecuted(const char[] strConfigFile) +{ + if (Loaded) return; + + int iTeam; + + VoteAllowed = true; + FFAEnabled = false; + BotCount = 0; + LastVoteTime = 0.0; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient)) continue; + + iTeam = GetClientTeam(iClient); + + if (!(iTeam >= 2)) continue; + + OldTeam[iClient] = iTeam; + + if (IsFakeClient(iClient)) BotCount++; + } + + CvarDisableOnBot.AddChangeHook(DisableOnBotCallback); + + HookEvent("player_team", OnPlayerTeam); + HookEvent("player_death", OnPlayerDeath); + HookEvent("teamplay_round_start", OnRoundStart); + + Loaded = true; +} + +public void OnMapEnd() +{ + if (!Loaded) return; + + UnhookEvent("player_team", OnPlayerTeam); + UnhookEvent("player_death", OnPlayerDeath); + UnhookEvent("teamplay_round_start", OnRoundStart); + + CvarDisableOnBot.RemoveChangeHook(DisableOnBotCallback); + + VoteAllowed = false; + FFAEnabled = false; + BotCount = 0; + LastVoteTime = 0.0; + + CvarFriendlyFire.RestoreDefault(); + ExecuteDisableConfig(); + + Loaded = false; +} + +public void OnClientDisconnect(int iClient) +{ + OldTeam[iClient] = 0; + + if (!FFAEnabled || + !CvarSwitchTeams.BoolValue || + (CvarDisableOnBot.BoolValue && BotCount) || + !TFDB_GetRoundStarted()) + { + return; + } + + int iTeam = GetClientTeam(iClient); + + if (iTeam <= 1) return; + + int iOtherTeam = GetAnalogueTeam(iTeam); + + if (((GetTeamAliveClientCount(iTeam) - view_as(IsPlayerAlive(iClient))) == 0) && + ((GetTeamAliveClientCount(iOtherTeam) - 1) >= 1)) + { + ChangeAliveClientTeam(GetRandomTeamAliveClient(iOtherTeam), iTeam); + } +} + +public void OnClientConnected(int iClient) +{ + OldTeam[iClient] = 0; +} + +public void OnPlayerTeam(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + int iClient = GetClientOfUserId(hEvent.GetInt("userid")); + int iTeam = hEvent.GetInt("team"); + int iOldTeam = hEvent.GetInt("oldteam"); + + if (!FFAEnabled || + !CvarSwitchTeams.BoolValue || + (CvarDisableOnBot.BoolValue && BotCount) || + !TFDB_GetRoundStarted()) + { + OldTeam[iClient] = iTeam; + } + else + { + // If you swap between RED and BLU, this event gets fired first instead of player_death. + // This makes GetClientTeam report the new team instead of the old one when used inside a player_death callback. + + if (iTeam <= 1) + { + OldTeam[iClient] = iTeam; + } + else if ((iOldTeam >= 2) && + ((GetTeamAliveClientCount(iOldTeam) - view_as(IsPlayerAlive(iClient))) == 0) && + ((GetTeamAliveClientCount(iTeam) - 1) >= 1)) + { + ChangeAliveClientTeam(GetRandomTeamAliveClient(iTeam), iOldTeam); + } + } + + if (!IsFakeClient(iClient)) return; + + if ((iOldTeam <= 1) && (iTeam >= 2)) + { + BotCount++; + + if (FFAEnabled && (BotCount == 1) && CvarDisableOnBot.BoolValue) + { + CPrintToChatAll("%t", "Dodgeball_FFABot_Joined"); + CvarFriendlyFire.RestoreDefault(); + ExecuteDisableConfig(); + } + } + else if ((iOldTeam >= 2) && (iTeam <= 1)) + { + BotCount--; + + if (FFAEnabled && (BotCount == 0) && CvarDisableOnBot.BoolValue) + { + CPrintToChatAll("%t", "Dodgeball_FFABot_Left"); + CvarFriendlyFire.SetBool(true); + ExecuteEnableConfig(); + } + } +} + +public void OnPlayerDeath(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + if (!FFAEnabled || + !CvarSwitchTeams.BoolValue || + (CvarDisableOnBot.BoolValue && BotCount) || + !TFDB_GetRoundStarted()) + { + return; + } + + int iVictim = GetClientOfUserId(hEvent.GetInt("userid")); + + int iTeam = GetClientTeam(iVictim); + + if (iTeam <= 1) return; // ... + + int iOtherTeam = GetAnalogueTeam(iTeam); + + // Checking the alive players count in here doesn't exclude the player that has just died. + // Doing this check in a SDKHook_OnTakeDamagePost callback excludes him for some reason... + + if (((GetTeamAliveClientCount(iTeam) - 1) == 0) && ((GetTeamAliveClientCount(iOtherTeam) - 1) >= 1)) + { + ChangeAliveClientTeam(GetRandomTeamAliveClient(iOtherTeam), iTeam); + } +} + +public void OnRoundStart(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + if (!FFAEnabled || + !CvarSwitchTeams.BoolValue || + (CvarDisableOnBot.BoolValue && BotCount)) + { + return; + } + + int iTeam; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient) || ((iTeam = GetClientTeam(iClient)) <= 1)) continue; + + if ((OldTeam[iClient] >= 2) && + (OldTeam[iClient] != iTeam) && + ((GetTeamAliveClientCount(iTeam) - view_as(IsPlayerAlive(iClient))) >= 1)) + { + ChangeClientTeam(iClient, OldTeam[iClient]); + } + + if (OldTeam[iClient] <= 1) OldTeam[iClient] = iTeam; + } +} + +public Action CmdToggleFFA(int iClient, int iArgs) +{ + if (!TFDB_IsDodgeballEnabled()) + { + CReplyToCommand(iClient, "%t", "Command_Disabled"); + + return Plugin_Handled; + } + + ToggleFFA(); + + return Plugin_Handled; +} + +public Action CmdVoteFFA(int iClient, int iArgs) +{ + if (iClient == 0) + { + // CReplyToCommand prints the message twice... + ReplyToCommand(iClient, "Command is in-game only."); + + return Plugin_Handled; + } + + if (!TFDB_IsDodgeballEnabled()) + { + CReplyToCommand(iClient, "%t", "Command_Disabled"); + + return Plugin_Handled; + } + + if (IsVoteInProgress()) + { + CReplyToCommand(iClient, "%t", "Dodgeball_FFAVote_Conflict"); + + return Plugin_Handled; + } + + if (VoteAllowed) + { + VoteAllowed = false; + LastVoteTime = GetGameTime(); + + StartFFAVote(); + CreateTimer(CvarVoteTimeout.FloatValue, VoteTimeoutCallback, _, TIMER_FLAG_NO_MAPCHANGE); + } + else + { + CReplyToCommand(iClient, "%t", "Dodgeball_FFAVote_Cooldown", + RoundToCeil((LastVoteTime + CvarVoteTimeout.FloatValue) - GetGameTime())); + } + + return Plugin_Handled; +} + +public void DisableOnBotCallback(ConVar hConvar, const char[] strOldValue, const char[] strNewValue) +{ + if (!FFAEnabled || !BotCount) return; + + if (hConvar.BoolValue) + { + CvarFriendlyFire.RestoreDefault(); + ExecuteDisableConfig(); + } + else + { + CvarFriendlyFire.SetBool(true); + ExecuteEnableConfig(); + } +} + +void StartFFAVote() +{ + char strMode[16]; + strMode = !FFAEnabled ? "Enable" : "Disable"; + + Menu hMenu = new Menu(VoteMenuHandler); + hMenu.VoteResultCallback = VoteResultHandler; + + hMenu.SetTitle("%s FFA mode?", strMode); + + hMenu.AddItem("0", "Yes"); + hMenu.AddItem("1", "No"); + + int iTotal; + int[] iClients = new int[MaxClients]; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient) || IsFakeClient(iClient) || GetClientTeam(iClient) <= 1) + { + continue; + } + + iClients[iTotal++] = iClient; + } + + hMenu.DisplayVote(iClients, iTotal, CvarVoteDuration.IntValue); +} + +public int VoteMenuHandler(Menu hMenu, MenuAction iMenuActions, int iParam1, int iParam2) +{ + switch (iMenuActions) + { + case MenuAction_End : + { + delete hMenu; + } + } + + return 0; +} + +public void VoteResultHandler(Menu hMenu, + int iNumVotes, + int iNumClients, + const int[][] iClientInfo, + int iNumItems, + const int[][] iItemInfo) +{ + int iWinnerIndex = 0; + + if (iNumItems > 1 && + (iItemInfo[0][VOTEINFO_ITEM_VOTES] == iItemInfo[1][VOTEINFO_ITEM_VOTES])) + { + iWinnerIndex = GetRandomInt(0, 1); + } + + char strWinner[8]; hMenu.GetItem(iItemInfo[iWinnerIndex][VOTEINFO_ITEM_INDEX], strWinner, sizeof(strWinner)); + + if (StrEqual(strWinner, "0")) + { + ToggleFFA(); + } + else + { + CPrintToChatAll("%t", "Dodgeball_FFAVote_Failed"); + } +} + +void EnableFFA() +{ + FFAEnabled = true; + + if (CvarDisableOnBot.BoolValue && BotCount) + { + CPrintToChatAll("%t", "Dodgeball_FFAVote_LateEnabled"); + } + else + { + CvarFriendlyFire.SetBool(true); + ExecuteEnableConfig(); + + switch (CvarToggleMode.IntValue) + { + case 1 : + { + TFDB_DestroyRockets(); + } + + case 2 : + { + ChangeRockets(); + } + } + + CPrintToChatAll("%t", "Dodgeball_FFAVote_Enabled"); + } +} + +void DisableFFA() +{ + FFAEnabled = false; + CvarFriendlyFire.RestoreDefault(); + ExecuteDisableConfig(); + + switch (CvarToggleMode.IntValue) + { + case 1 : + { + TFDB_DestroyRockets(); + } + + case 2 : + { + ChangeRockets(); + } + } + + CPrintToChatAll("%t", "Dodgeball_FFAVote_Disabled"); +} + +void ToggleFFA() +{ + if (!FFAEnabled) + { + EnableFFA(); + } + else + { + DisableFFA(); + } +} + +void ChangeRockets() +{ + RocketFlags iFlags, iClassFlags; + int iEntity; + + for (int iIndex = 0; iIndex < MAX_ROCKETS; iIndex++) + { + if (!TFDB_IsValidRocket(iIndex)) continue; + + iFlags = TFDB_GetRocketFlags(iIndex); + iClassFlags = TFDB_GetRocketClassFlags(TFDB_GetRocketClass(iIndex)); + iEntity = EntRefToEntIndex(TFDB_GetRocketEntity(iIndex)); + + if (FFAEnabled) + { + iFlags |= RocketFlag_IsNeutral; + + if (CvarAllowStealing.BoolValue) iFlags |= RocketFlag_CanBeStolen; + + SetEntProp(iEntity, Prop_Send, "m_iTeamNum", 1, 1); + + TFDB_SetRocketFlags(iIndex, iFlags); + } + else + { + if (!(iClassFlags & RocketFlag_IsNeutral)) iFlags &= ~RocketFlag_IsNeutral; + + if (CvarAllowStealing.BoolValue && !(iClassFlags & RocketFlag_CanBeStolen)) iFlags &= ~RocketFlag_CanBeStolen; + + int iOwner = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity"); + SetEntProp(iEntity, Prop_Send, "m_iTeamNum", GetClientTeam(iOwner), 1); + + TFDB_SetRocketFlags(iIndex, iFlags); + } + } +} + +public Action VoteTimeoutCallback(Handle hTimer) +{ + VoteAllowed = true; + + return Plugin_Continue; +} + +public Action TFDB_OnRocketCreatedPre(int iIndex, int &iClass, RocketFlags &iFlags) +{ + if (FFAEnabled && (!CvarDisableOnBot.BoolValue || !BotCount)) + { + iFlags |= RocketFlag_IsNeutral; + + if (CvarAllowStealing.BoolValue) iFlags |= RocketFlag_CanBeStolen; + + return Plugin_Changed; + } + + return Plugin_Continue; +} + +void ExecuteDisableConfig() +{ + char strConfigPath[64]; CvarDisableConfig.GetString(strConfigPath, sizeof(strConfigPath)); + ServerCommand("exec \"%s\"", strConfigPath); +} + +void ExecuteEnableConfig() +{ + char strConfigPath[64]; CvarEnableConfig.GetString(strConfigPath, sizeof(strConfigPath)); + ServerCommand("exec \"%s\"", strConfigPath); +} + +int GetTeamAliveClientCount(int iTeam) +{ + int iCount; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient)) continue; + + if ((GetClientTeam(iClient) == iTeam) && IsPlayerAlive(iClient)) iCount++; + } + + return iCount; +} + +stock int GetAnalogueTeam(int iTeam) +{ + if (iTeam == view_as(TFTeam_Red)) return view_as(TFTeam_Blue); + + return view_as(TFTeam_Red); +} + +// https://forums.alliedmods.net/showthread.php?t=286924 + +int GetRandomTeamAliveClient(int iTeam) +{ + int[] iClients = new int[MaxClients]; + int iCount; + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient)) continue; + + if ((GetClientTeam(iClient) == iTeam) && IsPlayerAlive(iClient)) iClients[iCount++] = iClient; + } + + return iCount == 0 ? -1 : iClients[GetRandomInt(0, iCount - 1)]; +} + +// https://forums.alliedmods.net/showthread.php?t=314271 + +void ChangeAliveClientTeam(int iClient, int iTeam) +{ + int iLifeState = GetEntProp(iClient, Prop_Send, "m_lifeState"); + SetEntProp(iClient, Prop_Send, "m_lifeState", 2); + + ChangeClientTeam(iClient, iTeam); + SetEntProp(iClient, Prop_Send, "m_lifeState", iLifeState); + + int iWearable; + int iWearablesCount = GetPlayerWearablesCount(iClient); + Address pData = DereferencePointer(GetEntityAddress(iClient) + MyWearables); + + for (int iIndex = 0; iIndex < iWearablesCount; iIndex++) + { + iWearable = LoadEntityHandleFromAddress(pData + view_as
(0x04 * iIndex)); + + SetEntProp(iWearable, Prop_Send, "m_nSkin", (iTeam == view_as(TFTeam_Blue)) ? 1 : 0); + SetEntProp(iWearable, Prop_Send, "m_iTeamNum", iTeam); + } +} + +/* + https://github.com/nosoop/SM-TFUtils/blob/master/scripting/tf2utils.sp + https://github.com/nosoop/stocksoup/blob/master/memory.inc +*/ + +stock int LoadEntityHandleFromAddress(Address pAddress) +{ + return EntRefToEntIndex(LoadFromAddress(pAddress, NumberType_Int32) | (1 << 31)); +} + +stock Address DereferencePointer(Address pAddress) +{ + // maybe someday we'll do 64-bit addresses + return view_as
(LoadFromAddress(pAddress, NumberType_Int32)); +} + +int GetPlayerWearablesCount(int iClient) +{ + return GetEntData(iClient, view_as(MyWearables) + 0x0C); +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_no_block.sp b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_no_block.sp new file mode 100644 index 00000000..f59c1076 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_no_block.sp @@ -0,0 +1,68 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include + +#include + +#define PLUGIN_NAME "[TFDB] No block" +#define PLUGIN_AUTHOR "x07x08" +#define PLUGIN_DESCRIPTION "Removes collision between enemies." +#define PLUGIN_VERSION "1.0.3" +#define PLUGIN_URL "https://github.com/x07x08/TF2-Dodgeball-Modified" + +#define COLLISION_GROUP_PUSHAWAY 17 + +bool Loaded; + +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = PLUGIN_DESCRIPTION, + version = PLUGIN_VERSION, + url = PLUGIN_URL +}; + +public void OnPluginStart() +{ + if (!TFDB_IsDodgeballEnabled()) return; + + TFDB_OnRocketsConfigExecuted("general.cfg"); + + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient) || !IsPlayerAlive(iClient)) continue; + + SetEntProp(iClient, Prop_Data, "m_CollisionGroup", COLLISION_GROUP_PUSHAWAY); + } +} + +public void TFDB_OnRocketsConfigExecuted(const char[] strConfigFile) +{ + if (Loaded) return; + + HookEvent("player_spawn", OnPlayerSpawn); + + Loaded = true; +} + +public void OnMapEnd() +{ + if (!Loaded) return; + + UnhookEvent("player_spawn", OnPlayerSpawn); + + Loaded = false; +} + +public void OnPlayerSpawn(Event hEvent, char[] strEventName, bool bDontBroadcast) +{ + int iClient = GetClientOfUserId(hEvent.GetInt("userid")); + + if (GetClientTeam(iClient) <= 1) return; + + // SetEntityCollisionGroup makes the server crash after a while. No idea why. + SetEntProp(iClient, Prop_Data, "m_CollisionGroup", COLLISION_GROUP_PUSHAWAY); +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_print.sp b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_print.sp new file mode 100644 index 00000000..a3cfda59 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_print.sp @@ -0,0 +1,262 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include + +#define PLUGIN_NAME "[TFDB] Print & replace client indexes" +#define PLUGIN_AUTHOR "x07x08 & Silorak" +#define PLUGIN_DESCRIPTION "Does what it says" +#define PLUGIN_VERSION "1.1.3" +#define PLUGIN_URL "https://github.com/x07x08/TF2-Dodgeball-Modified" + +char CmdBuffer[255]; +char ExplodeBuffer[32][255]; + +Regex BracketsPattern; + +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = PLUGIN_DESCRIPTION, + version = PLUGIN_VERSION, + url = PLUGIN_URL +}; + +public void OnPluginStart() +{ + LoadTranslations("tfdb.phrases.txt"); + + BracketsPattern = new Regex("(?<=\\[)(.*?)(?=\\])"); + + RegAdminCmd("tf_dodgeball_print", CmdPrintMessage, ADMFLAG_CHAT, "Prints a message to chat and replaces client indexes inside a pair of '##'"); + RegAdminCmd("tf_dodgeball_print_c", CmdPrintMessageClient, ADMFLAG_CHAT, "Prints a message to a client and replaces client indexes inside a pair of '##'"); + RegAdminCmd("tf_dodgeball_phrase", CmdPrintPhrase, ADMFLAG_CHAT, "Prints a translation phrase to chat"); + RegAdminCmd("tf_dodgeball_phrase_c", CmdPrintPhraseClient, ADMFLAG_CHAT, "Prints a translation phrase to a client"); +} + +public Action CmdPrintMessage(int iClient, int iArgs) +{ + if (!(iArgs >= 1)) + { + ReplyToCommand(iClient, "Usage : tf_dodgeball_print "); + + return Plugin_Handled; + } + + GetCmdArgString(CmdBuffer, sizeof(CmdBuffer)); + TrimString(CmdBuffer); + + int iNumStrings = ExplodeString(CmdBuffer, "##", ExplodeBuffer, sizeof(ExplodeBuffer), sizeof(ExplodeBuffer[])); + int iIndex; + + for (int iPos = 0; iPos < iNumStrings; iPos++) + { + if (!ExplodeBuffer[iPos][0]) continue; + + if ((StringToIntEx(ExplodeBuffer[iPos], iIndex) == strlen(ExplodeBuffer[iPos])) && + ((iIndex >= 1) && (iIndex <= MaxClients) && IsClientInGame(iIndex))) + { + FormatEx(ExplodeBuffer[iPos], sizeof(ExplodeBuffer[]), "%N", iIndex); + } + } + + ImplodeStrings(ExplodeBuffer, iNumStrings, "", CmdBuffer, sizeof(CmdBuffer)); + + CPrintToChatAll(CmdBuffer); + + return Plugin_Handled; +} + +public Action CmdPrintMessageClient(int iClient, int iArgs) +{ + if (!(iArgs >= 2)) + { + ReplyToCommand(iClient, "Usage : tf_dodgeball_print_c "); + + return Plugin_Handled; + } + + char strBuffer[8]; + + GetCmdArgString(CmdBuffer, sizeof(CmdBuffer)); + + int iLength = BreakString(CmdBuffer, strBuffer, sizeof(strBuffer)); + int iTarget = StringToInt(strBuffer); + + TrimString(CmdBuffer[iLength]); + + int iNumStrings = ExplodeString(CmdBuffer[iLength], "##", ExplodeBuffer, sizeof(ExplodeBuffer), sizeof(ExplodeBuffer[])); + int iIndex; + + for (int iPos = 0; iPos < iNumStrings; iPos++) + { + if (!ExplodeBuffer[iPos][0]) continue; + + if ((StringToIntEx(ExplodeBuffer[iPos], iIndex) == strlen(ExplodeBuffer[iPos])) && + ((iIndex >= 1) && (iIndex <= MaxClients) && IsClientInGame(iIndex))) + { + FormatEx(ExplodeBuffer[iPos], sizeof(ExplodeBuffer[]), "%N", iIndex); + } + } + + ImplodeStrings(ExplodeBuffer, iNumStrings, "", CmdBuffer[iLength], sizeof(CmdBuffer)); + + if ((iTarget >= 1) && (iTarget <= MaxClients) && IsClientInGame(iTarget)) + { + CPrintToChat(iTarget, CmdBuffer[iLength]); + } + + return Plugin_Handled; +} + +public Action CmdPrintPhrase(int iClient, int iArgs) +{ + char strPhrase[48]; + + GetCmdArgString(CmdBuffer, sizeof(CmdBuffer)); TrimString(CmdBuffer); + + int iMatches = BracketsPattern.MatchAll(CmdBuffer); + + if (!(iMatches >= 1)) + { + ReplyToCommand(iClient, "Usage : tf_dodgeball_phrase (phrase and args must be surrounded by []) (phrase arguments must be separated by a comma [,])"); + + return Plugin_Handled; + } + + any aArgs[32]; + + BracketsPattern.GetSubString(0, strPhrase, sizeof(strPhrase), 0); TrimString(strPhrase); + + if (iMatches == 2) + { + BracketsPattern.GetSubString(0, CmdBuffer, sizeof(CmdBuffer), 1); + + int iStrings = ExplodeString(CmdBuffer, ",", ExplodeBuffer, sizeof(ExplodeBuffer), sizeof(ExplodeBuffer[])); + + for (int iIndex = 0; iIndex < iStrings; iIndex++) + { + TrimString(ExplodeBuffer[iIndex]); + + if ((StringToIntEx(ExplodeBuffer[iIndex], aArgs[iIndex]) == strlen(ExplodeBuffer[iIndex])) || + (StringToFloatEx(ExplodeBuffer[iIndex], aArgs[iIndex]) == strlen(ExplodeBuffer[iIndex]))) + { + ExplodeBuffer[iIndex] = "\0"; + } + } + } + + PrintPhrase(strPhrase, ExplodeBuffer, aArgs, true); + + return Plugin_Handled; +} + +public Action CmdPrintPhraseClient(int iClient, int iArgs) +{ + char strPhrase[48], strTarget[8]; + + GetCmdArgString(CmdBuffer, sizeof(CmdBuffer)); TrimString(CmdBuffer); + + int iMatches = BracketsPattern.MatchAll(CmdBuffer); + + if (!(iMatches >= 2)) + { + ReplyToCommand(iClient, "Usage : tf_dodgeball_phrase_c (client, phrase and args must be surrounded by []) (phrase arguments must be separated by a comma [,])"); + + return Plugin_Handled; + } + + any aArgs[32]; + + BracketsPattern.GetSubString(0, strTarget, sizeof(strTarget), 0); TrimString(strTarget); + BracketsPattern.GetSubString(0, strPhrase, sizeof(strPhrase), 1); TrimString(strPhrase); + + int iTarget = StringToInt(strTarget); + + if (iMatches == 3) + { + BracketsPattern.GetSubString(0, CmdBuffer, sizeof(CmdBuffer), 2); + + int iStrings = ExplodeString(CmdBuffer, ",", ExplodeBuffer, sizeof(ExplodeBuffer), sizeof(ExplodeBuffer[])); + + for (int iIndex = 0; iIndex < iStrings; iIndex++) + { + TrimString(ExplodeBuffer[iIndex]); + + if ((StringToIntEx(ExplodeBuffer[iIndex], aArgs[iIndex]) == strlen(ExplodeBuffer[iIndex])) || + (StringToFloatEx(ExplodeBuffer[iIndex], aArgs[iIndex]) == strlen(ExplodeBuffer[iIndex]))) + { + ExplodeBuffer[iIndex] = "\0"; + } + } + } + + if ((iTarget >= 1) && (iTarget <= MaxClients) && IsClientInGame(iTarget)) + { + PrintPhrase(strPhrase, ExplodeBuffer, aArgs, false, iTarget); + } + + return Plugin_Handled; +} + +void PrintPhrase(const char[] strPhrase, const char strArgs[32][255], const any aArgs[32], bool bAll, int iClient = -1) +{ + // Use SetGlobalTransTarget + Format with %T to properly handle + // dynamic translation arguments without heap overflow. + // Maximum phrase args in tfdb.phrases.txt is 5, so 8 slots is plenty. + + if (bAll) + { + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i)) continue; + + char strBuffer[512]; + SetGlobalTransTarget(i); + FormatEx(strBuffer, sizeof(strBuffer), "%T", strPhrase, i, + HBC(strArgs, aArgs, 0), HBC(strArgs, aArgs, 1), + HBC(strArgs, aArgs, 2), HBC(strArgs, aArgs, 3), + HBC(strArgs, aArgs, 4), HBC(strArgs, aArgs, 5), + HBC(strArgs, aArgs, 6), HBC(strArgs, aArgs, 7)); + + CPrintToChat(i, strBuffer); + } + } + else + { + char strBuffer[512]; + SetGlobalTransTarget(iClient); + FormatEx(strBuffer, sizeof(strBuffer), "%T", strPhrase, iClient, + HBC(strArgs, aArgs, 0), HBC(strArgs, aArgs, 1), + HBC(strArgs, aArgs, 2), HBC(strArgs, aArgs, 3), + HBC(strArgs, aArgs, 4), HBC(strArgs, aArgs, 5), + HBC(strArgs, aArgs, 6), HBC(strArgs, aArgs, 7)); + + CPrintToChat(iClient, strBuffer); + } +} + +// Returns either the numeric value or the string as any[]. +// SM 1.12 requires any[] return type — cannot coerce char[] to any scalar. +// With only 8 calls instead of 29, this fits comfortably in default heap. +any[] HBC(const char[][] strArgs, const any[] aArgs, int iIndex) +{ + static any aResult[256]; + + if (!strArgs[iIndex][0]) + { + aResult[0] = aArgs[iIndex]; + return aResult; + } + + int i; + for (i = 0; i < 255 && strArgs[iIndex][i]; i++) + { + aResult[i] = view_as(strArgs[iIndex][i]); + } + aResult[i] = 0; + return aResult; +} + diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_speedhud.sp b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_speedhud.sp new file mode 100644 index 00000000..28a7b79a --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/tfdb_speedhud.sp @@ -0,0 +1,343 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include +#include // Include the Dodgeball plugin's natives +#include // Include for cookie functions +#include // Include for colored chat and translations + +#define PLUGIN_VERSION "2.0" + +public Plugin myinfo = +{ + name = "[TF2] Dodgeball Speed HUD", + author = "Silorak", + description = "Displays the speed of active rockets to all players.", + version = PLUGIN_VERSION, + url = "https://github.com/Silorak/TF2-Dodgeball-Modified" +}; + +// ==================================================================================================== +// Global Variables +// ==================================================================================================== + +ConVar CvarHudEnabled; +Handle DisplayTimer; +Handle CookieHudPref; // Handle for the client's HUD preference cookie. + +// Tracks if the HUD is currently being displayed for a client. +bool IsHudVisible[MAXPLAYERS + 1]; +// Tracks a client's personal preference for seeing the HUD. +bool HudEnabledForClient[MAXPLAYERS + 1]; + +// ==================================================================================================== +// Plugin Lifecycle +// ==================================================================================================== + +public void OnPluginStart() +{ + CreateConVar("tfdb_speedhud_version", PLUGIN_VERSION, "Dodgeball Speed HUD Version", FCVAR_NOTIFY|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_DONTRECORD); + CvarHudEnabled = CreateConVar("tfdb_speedhud_enabled", "1", "Enable the rocket speed HUD for all players.", _, true, 0.0, true, 1.0); + + // Register the command for players to toggle the HUD. + RegConsoleCmd("sm_speedhud", Command_ToggleHud, "Toggles the rocket speed HUD display."); + RegConsoleCmd("sm_shud", Command_ToggleHud, "Toggles the rocket speed HUD display."); // Alias + + // Load the translations from the main Dodgeball plugin. + LoadTranslations("tfdb.phrases.txt"); + + // Register the cookie. The second argument is the default value. + CookieHudPref = RegClientCookie("tfdb_speedhud_pref", "Toggle for the Dodgeball Speed HUD", CookieAccess_Public); + + // Hook the ConVar change to enable/disable the timer on the fly. + CvarHudEnabled.AddChangeHook(OnConVarChanged); + + // Hook client connection to load their preference from cookies. + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i)) + { + OnClientPostAdminCheck(i); + } + } + + // Start the timer if the plugin is loaded while the cvar is enabled. + if (CvarHudEnabled.BoolValue) + { + StartDisplayTimer(); + } +} + +public void OnPluginEnd() +{ + // Clean up the timer when the plugin unloads. + StopDisplayTimer(); +} + +public void OnMapStart() +{ + // Check if the Dodgeball plugin is running and if the HUD is enabled. + if (LibraryExists("tfdb") && CvarHudEnabled.BoolValue) + { + StartDisplayTimer(); + } +} + +public void OnMapEnd() +{ + // Clean up the timer at the end of the map. + StopDisplayTimer(); +} + +public void OnClientPostAdminCheck(int client) +{ + // Load the client's preference when they fully connect. + char sCookie[8]; + GetClientCookie(client, CookieHudPref, sCookie, sizeof(sCookie)); + + // Default to ON if the cookie is not set or is set to "1". + if (sCookie[0] == '0') + { + HudEnabledForClient[client] = false; + } + else + { + HudEnabledForClient[client] = true; + } +} + +// ==================================================================================================== +// ConVar & Command Management +// ==================================================================================================== + +public Action Command_ToggleHud(int client, int args) +{ + // This command is for players only. + if (client == 0) + { + PrintToServer("This command can only be used by players."); + return Plugin_Handled; + } + + // Flip the player's preference. + HudEnabledForClient[client] = !HudEnabledForClient[client]; + + if (HudEnabledForClient[client]) + { + // Set cookie to "1" for ON. + SetClientCookie(client, CookieHudPref, "1"); + CPrintToChat(client, "%t", "Hud_Enabled"); + } + else + { + // Set cookie to "0" for OFF. + SetClientCookie(client, CookieHudPref, "0"); + CPrintToChat(client, "%t", "Hud_Disabled"); + } + + return Plugin_Handled; +} + +public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if (convar == CvarHudEnabled) + { + if (StringToInt(newValue) == 1) + { + StartDisplayTimer(); + } + else + { + StopDisplayTimer(); + } + } +} + +/** + * Starts the main timer for displaying the HUD. + */ +void StartDisplayTimer() +{ + // Don't create a new timer if one already exists. + if (DisplayTimer != null) + { + return; + } + // Create a repeating timer that calls DisplayHud every 0.1 seconds. + DisplayTimer = CreateTimer(0.1, DisplayHud, _, TIMER_REPEAT); +} + +/** + * Stops the main display timer and clears the HUD for all players. + */ +void StopDisplayTimer() +{ + if (DisplayTimer != null) + { + KillTimer(DisplayTimer); + DisplayTimer = null; + } + + // Clear the HUD for any player who might still have it open. + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && IsHudVisible[i]) + { + // Set an empty message to clear the HUD. + SetHudTextParams(0.0, 0.0, 0.1, 255, 255, 255, 0, 0, 0.0, 0.0, 0.0); + ShowHudText(i, 4, " "); + IsHudVisible[i] = false; + } + } +} + +// ==================================================================================================== +// Core Logic +// ==================================================================================================== + +/** + * Timer callback that runs continuously to update the HUD for all players. + */ +public Action DisplayHud(Handle timer) +{ + // Only run if the main Dodgeball plugin is enabled and the cvar is on. + if (!CvarHudEnabled.BoolValue || !TFDB_IsDodgeballEnabled()) + { + // Ensure the HUD is cleared if the plugin is disabled globally. + StopDisplayTimer(); + return Plugin_Continue; + } + + // --- Collect and sort active rockets --- + int rocketIndices[MAX_ROCKETS]; + float rocketSpeeds[MAX_ROCKETS]; + int rocketCount = 0; + + for (int i = 0; i < MAX_ROCKETS; i++) + { + if (TFDB_IsValidRocket(i)) + { + rocketIndices[rocketCount] = i; + rocketSpeeds[rocketCount] = TFDB_GetRocketMphSpeed(i); + rocketCount++; + } + } + + // --- Display logic based on rocket count --- + if (rocketCount == 0) + { + // No rockets are active, clear the HUD for anyone who has it visible. + for (int i = 1; i <= MaxClients; i++) + { + if (IsHudVisible[i] && IsClientInGame(i)) + { + SetHudTextParams(0.0, 0.0, 0.1, 255, 255, 255, 0, 0, 0.0, 0.0, 0.0); + ShowHudText(i, 4, " "); + IsHudVisible[i] = false; + } + } + } + else if (rocketCount == 1) + { + // Single rocket: Display in the center. + int rocketIndex = rocketIndices[0]; + float mphSpeed = rocketSpeeds[0]; + float huSpeed = TFDB_GetRocketSpeed(rocketIndex); + int deflections = TFDB_GetRocketDeflections(rocketIndex); + int rocketClass = TFDB_GetRocketClass(rocketIndex); + char className[64]; + TFDB_GetRocketClassLongName(rocketClass, className, sizeof(className)); + + char hudMessage[256]; + // Format the string first to avoid issues with ShowHudText. + FormatEx(hudMessage, sizeof(hudMessage), "%t", "Hud_Speedometer", mphSpeed, huSpeed, deflections, rocketIndex + 1, className); + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i)) + { + if (HudEnabledForClient[i]) + { + SetHudTextParams(-1.0, 0.85, 0.15, 100, 255, 100, 255, 0, 0.0, 0.0, 0.15); + ShowHudText(i, 4, hudMessage); + IsHudVisible[i] = true; + } + else if (IsHudVisible[i]) + { + SetHudTextParams(0.0, 0.0, 0.1, 255, 255, 255, 0, 0, 0.0, 0.0, 0.0); + ShowHudText(i, 4, " "); + IsHudVisible[i] = false; + } + } + } + } + else // Multiple rockets + { + // Sort rockets by speed (descending) using a simple bubble sort. + for (int i = 0; i < rocketCount - 1; i++) + { + for (int j = 0; j < rocketCount - i - 1; j++) + { + if (rocketSpeeds[j] < rocketSpeeds[j + 1]) + { + // Swap speeds + float tempSpeed = rocketSpeeds[j]; + rocketSpeeds[j] = rocketSpeeds[j + 1]; + rocketSpeeds[j + 1] = tempSpeed; + // Swap indices + int tempIndex = rocketIndices[j]; + rocketIndices[j] = rocketIndices[j + 1]; + rocketIndices[j + 1] = tempIndex; + } + } + } + + // Build the multi-line HUD message. + char hudMessage[1024]; + int maxRocketsToShow = 5; // Limit to prevent screen clutter. + int numToShow = rocketCount < maxRocketsToShow ? rocketCount : maxRocketsToShow; + + for (int i = 0; i < numToShow; i++) + { + int rocketIndex = rocketIndices[i]; + float mphSpeed = rocketSpeeds[i]; + float huSpeed = TFDB_GetRocketSpeed(rocketIndex); + int deflections = TFDB_GetRocketDeflections(rocketIndex); + int rocketClass = TFDB_GetRocketClass(rocketIndex); + char className[64]; + TFDB_GetRocketClassLongName(rocketClass, className, sizeof(className)); + + char line[256]; + // Pass the floats directly, the translation file will format them. + Format(line, sizeof(line), "%t\n", "Hud_SpeedometerEx", mphSpeed, huSpeed, deflections, i + 1, className); + + if (i == 0) + strcopy(hudMessage, sizeof(hudMessage), line); + else + Format(hudMessage, sizeof(hudMessage), "%s%s", hudMessage, line); + } + + // Display the list on the left side for all players. + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i)) + { + if (HudEnabledForClient[i]) + { + SetHudTextParams(0.05, 0.4, 0.15, 100, 255, 100, 255, 0, 0.0, 0.0, 0.15); + ShowHudText(i, 4, hudMessage); + IsHudVisible[i] = true; + } + else if (IsHudVisible[i]) + { + SetHudTextParams(0.0, 0.0, 0.1, 255, 255, 255, 0, 0, 0.0, 0.0, 0.0); + ShowHudText(i, 4, " "); + IsHudVisible[i] = false; + } + } + } + } + return Plugin_Continue; +} diff --git a/roles/sourcemod/files/addons/sourcemod/scripting/thirdperson.sp b/roles/sourcemod/files/addons/sourcemod/scripting/thirdperson.sp new file mode 100644 index 00000000..2ba9f5dc --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/scripting/thirdperson.sp @@ -0,0 +1,74 @@ +#pragma semicolon 1 +#pragma tabsize 4 +#pragma newdecls required + +#include +#include + +#define PLUGIN_VERSION "2.2.0" + +bool g_bThirdPersonEnabled[MAXPLAYERS+1] = { false, ... }; + +public Plugin myinfo = +{ + name = "[TF2] Thirdperson", + author = "DarthNinja, Leigh MacDonald", + description = "Allows players to use thirdperson without having to enable client sv_cheats", + version = PLUGIN_VERSION, + url = "https://github.com/leighmacdonald/uncletopia" +}; + +public void OnPluginStart() +{ + CreateConVar("thirdperson_version", PLUGIN_VERSION, "Plugin Version", FCVAR_PLUGIN|FCVAR_NOTIFY); + RegAdminCmd("sm_thirdperson", EnableThirdperson, 0, "Usage: sm_thirdperson"); + RegAdminCmd("tp", EnableThirdperson, 0, "Usage: sm_thirdperson"); + RegAdminCmd("sm_firstperson", DisableThirdperson, 0, "Usage: sm_firstperson"); + RegAdminCmd("fp", DisableThirdperson, 0, "Usage: sm_firstperson"); + HookEvent("player_spawn", OnPlayerSpawned); + HookEvent("player_class", OnPlayerSpawned); +} + +public Action OnPlayerSpawned(Handle event, char[] name, bool dontBroadcast) { + int userid = GetEventInt(event, "userid"); + if (g_bThirdPersonEnabled[GetClientOfUserId(userid)]) { + CreateTimer(0.2, SetViewOnSpawn, userid); + } + + return Plugin_Continue; +} + +public Action SetViewOnSpawn(Handle timer, int userid) { + int client = GetClientOfUserId(userid); + if (client != 0) { + SetVariantInt(1); + AcceptEntityInput(client, "SetForcedTauntCam"); + } + + return Plugin_Continue; +} + +public Action EnableThirdperson(int client, int args) { + if (!IsPlayerAlive(client)) { + PrintToChat(client, "[SM] Thirdperson view will be enabled when you spawn."); + } + SetVariantInt(1); + AcceptEntityInput(client, "SetForcedTauntCam"); + g_bThirdPersonEnabled[client] = true; + + return Plugin_Handled; +} + +public Action DisableThirdperson(int client, int args) { + if (!IsPlayerAlive(client)) { + PrintToChat(client, "[SM] Thirdperson view disabled!"); + } + SetVariantInt(0); + AcceptEntityInput(client, "SetForcedTauntCam"); + g_bThirdPersonEnabled[client] = false; + return Plugin_Handled; +} + +public void OnClientDisconnect(int client) { + g_bThirdPersonEnabled[client] = false; +} diff --git a/roles/sourcemod/files/addons/sourcemod/translations/fov.phrases.txt b/roles/sourcemod/files/addons/sourcemod/translations/fov.phrases.txt new file mode 100644 index 00000000..ec9b7903 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/translations/fov.phrases.txt @@ -0,0 +1,17 @@ +"Phrases" +{ + "FOV_Usage" + { + "en" "{orange}[FOV]{default} Usage: sm_fov " + } + + "FOV_MustBeANumber" + { + "en" "{orange}[FOV]{default} Parameter must be a number." + } + + "FOV_MustBeWithinRange" + { + "en" "{orange}[FOV]{default} Parameter must be within range %d-%d" + } +} diff --git a/roles/sourcemod/files/addons/sourcemod/translations/tfdb.phrases.txt b/roles/sourcemod/files/addons/sourcemod/translations/tfdb.phrases.txt new file mode 100644 index 00000000..85fa3bb2 --- /dev/null +++ b/roles/sourcemod/files/addons/sourcemod/translations/tfdb.phrases.txt @@ -0,0 +1,622 @@ +"Phrases" +{ + "Command_Disabled" + { + "en" "[{olive}TFDB{default}] This command is disabled." + } + + "Command_DBExplosion_Usage" + { + "en" "Usage : tf_dodgeball_explosion " + } + + "Command_DBShockwave_Usage" + { + "en" "Usage : tf_dodgeball_shockwave " + } + + "Command_DBRefresh_Done" + { + "#format" "{1:N}" + "en" "[{olive}TFDB{default}] {darkorange}{1}{default} refreshed the {steelblue}dodgeball configs{default}." + } + + "Command_DBHideParticles_Hidden" + { + "en" "[{olive}TFDB{default}] Custom rocket {darkorange}particle{default} trails are now {steelblue}hidden{default}." + } + + "Command_DBHideParticles_Visible" + { + "en" "[{olive}TFDB{default}] Custom rocket {darkorange}particle{default} trails are now {steelblue}visible{default}." + } + + "Command_DBHideSprites_Hidden" + { + "en" "[{olive}TFDB{default}] Custom rocket {darkorange}sprite{default} trails are now {steelblue}hidden{default}." + } + + "Command_DBHideSprites_Visible" + { + "en" "[{olive}TFDB{default}] Custom rocket {darkorange}sprite{default} trails are now {steelblue}visible{default}." + } + + "DBSteal_Warning_Client" + { + "#format" "{1:i},{2:i}" + "en" "[{olive}TFDB{default}] Do not steal rockets. [Warning {darkorange}{1}{default} / {darkorange}{2}{default}]" + } + + "DBSteal_Announce_All" + { + "#format" "{1:N},{2:N}" + "en" "[{olive}TFDB{default}] {darkorange}{1}{default} stole {steelblue}{2}{default}'s rocket!" + } + + "DBSteal_Slay_Client" + { + "en" "[{olive}TFDB{default}] You have been slain for stealing rockets." + } + + "DBSteal_Announce_Slay_All" + { + "#format" "{1:N}" + "en" "[{olive}TFDB{default}] {darkorange}{1}{default} was slain for stealing rockets." + } + + "DBDelay_Announce_All" + { + "#format" "{1:N}" + "en" "[{olive}TFDB{default}] {darkorange}{1}{default} is delaying, the rocket will now speed up." + } + + // Starting from here, the translations are used by other plugins + // Add your own custom phrases here to maintain the order + + "Dodgeball_AirblastPreventionCmd_Enabled" + { + "en" "[{olive}TFDB{default}] {community}Enabled{default} airblast push prevention." + } + + "Dodgeball_AirblastPreventionCmd_Disabled" + { + "en" "[{olive}TFDB{default}] {red}Disabled{default} airblast push prevention." + } + + "Dodgeball_FFAVote_Conflict" + { + "en" "[{olive}TFDB{default}] There is another vote currently in progress." + } + + "Dodgeball_FFAVote_Cooldown" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Voting for {darkmagenta}FFA{default} is in cooldown for {darkorange}{1}{default} seconds." + } + + "Dodgeball_FFAVote_Failed" + { + "en" "[{olive}TFDB{default}] {darkmagenta}FFA{default} vote {red}failed{default}." + } + + "Dodgeball_FFAVote_Enabled" + { + "en" "[{olive}TFDB{default}] {darkmagenta}FFA{default} is now {community}enabled{default}." + } + + "Dodgeball_FFAVote_LateEnabled" + { + "en" "[{olive}TFDB{default}] {darkmagenta}FFA{default} will be {community}enabled{default} once the bot leaves." + } + + "Dodgeball_FFAVote_Disabled" + { + "en" "[{olive}TFDB{default}] {darkmagenta}FFA{default} is now {red}disabled{default}." + } + + "Dodgeball_FFABot_Joined" + { + "en" "[{olive}TFDB{default}] A bot has joined. {darkmagenta}FFA{default} is now {red}disabled{default}." + } + + "Dodgeball_FFABot_Left" + { + "en" "[{olive}TFDB{default}] The bot(s) left. {darkmagenta}FFA{default} is now {community}enabled{default}." + } + + "Dodgeball_Death_Message" + { + "#format" "{1:N},{2:i}" + "en" "[{olive}TFDB{default}] {burlywood}{1}{default} died to a rocket travelling {red}{2}{default} MpH" + } + + "Dodgeball_Disabled" + { + "en" "[{olive}TFDB{default}] Dodgeball is disabled." + } + + "Hud_Enabled" + { + "en" "[{olive}TFDB{default}] Rocket speedometer is now {darkorange}enabled{default}." + } + + "Hud_Disabled" + { + "en" "[{olive}TFDB{default}] Rocket speedometer is now {darkorange}disabled{default}." + } + + // {1} - MpH speed (Miles per Hour) + // {2} - HU speed (Hammer Units) + // {3} - Deflections + // {4} - Index + // {5} - Rocket class long name + + "Hud_Speedometer" + { + "#format" "{1:.0f},{2:.0f},{3:i},{4:i},{5:s}" + "en" "Speed : {1} MpH" + } + + // {1} - MpH speed (Miles per Hour) + // {2} - HU speed (Hammer Units) + // {3} - Deflections + // {4} - Index + // {5} - Rocket class long name + + // This one is for displaying multiple rockets + + "Hud_SpeedometerEx" + { + "#format" "{1:.0f},{2:.0f},{3:i},{4:i},{5:s}" + "en" "{4}. {5} : {1} MpH" + } + + "Menu_NoRockets" + { + "en" "[{olive}TFDB{default}] There are {red}no active{default} rockets." + } + + "Menu_InvalidRocket" + { + "en" "[{olive}TFDB{default}] This rocket is {red}not valid{default} anymore." + } + + "Menu_InvalidClient" + { + "en" "[{olive}TFDB{default}] Client is dead or has disconnected from the server." + } + + "Menu_CannotTarget" + { + "en" "[{olive}TFDB{default}] Can't change target to this client." + } + + "Menu_SameTarget" + { + "en" "[{olive}TFDB{default}] Can't change to the same target." + } + + "Menu_ChangedTarget" + { + "#format" "{1:N}" + "en" "[{olive}TFDB{default}] Changed the target of a rocket to {darkorange}{1}{default}." + } + + "Menu_SameRocketClass" + { + "en" "[{olive}TFDB{default}] Can't change to the same class." + } + + "Menu_ChangedRocketClass" + { + "#format" "{1:s},{2:s}" + "en" "[{olive}TFDB{default}] Changed the class of a rocket from {darkorange}{1}{default} to {steelblue}{2}{default}." + } + + "Menu_Reset" + { + "en" "[{olive}TFDB{default}] Type -1 to reset it." + } + + "Menu_ResetBeepInterval" + { + "en" "[{olive}TFDB{default}] Type -1 to reset it, 0 to disable beeping." + } + + "Menu_ResetSpeedLimit" + { + "en" "[{olive}TFDB{default}] Type -1 to reset it, 0 to disable the speed limit." + } + + "Menu_ResetTurnRateLimit" + { + "en" "[{olive}TFDB{default}] Type -1 to reset it, 0 to disable the turn rate limit." + } + + "Menu_SpriteColor" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new sprite trail color (HEX or RGB) within {darkorange}{1}{default} seconds." + } + + "Menu_SpriteLifetime" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new sprite trail duration within {darkorange}{1}{default} seconds." + } + + "Menu_SpriteStartWidth" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new sprite trail start width within {darkorange}{1}{default} seconds." + } + + "Menu_SpriteEndWidth" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new sprite trail end width within {darkorange}{1}{default} seconds." + } + + "Menu_BeepInterval" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new beep interval within {darkorange}{1}{default} seconds." + } + + "Menu_CritChance" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new critical rocket chance within {darkorange}{1}{default} seconds." + } + + "Menu_Damage" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new damage within {darkorange}{1}{default} seconds." + } + + "Menu_DamageIncrement" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new damage increment within {darkorange}{1}{default} seconds." + } + + "Menu_Speed" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new speed within {darkorange}{1}{default} seconds." + } + + "Menu_SpeedIncrement" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new speed increment within {darkorange}{1}{default} seconds." + } + + "Menu_SpeedLimit" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new speed limit within {darkorange}{1}{default} seconds." + } + + "Menu_TurnRate" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new turn rate within {darkorange}{1}{default} seconds." + } + + "Menu_TurnRateIncrement" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new turn rate increment within {darkorange}{1}{default} seconds." + } + + "Menu_TurnRateLimit" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new turn rate limit within {darkorange}{1}{default} seconds." + } + + "Menu_ElevationRate" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new elevation rate within {darkorange}{1}{default} seconds." + } + + "Menu_ElevationLimit" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new elevation limit within {darkorange}{1}{default} seconds." + } + + "Menu_RocketsModifier" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new fired rockets modifier within {darkorange}{1}{default} seconds." + } + + "Menu_PlayerModifier" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new player count modifier within {darkorange}{1}{default} seconds." + } + + "Menu_ControlDelay" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new control delay within {darkorange}{1}{default} seconds." + } + + "Menu_DragTimeMin" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new drag time start within {darkorange}{1}{default} seconds." + } + + "Menu_DragTimeMax" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new drag time end within {darkorange}{1}{default} seconds." + } + + "Menu_TargetWeight" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new target weight within {darkorange}{1}{default} seconds." + } + + "Menu_MaxBounces" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new maximum bounces within {darkorange}{1}{default} seconds." + } + + "Menu_BounceScale" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new bounce scale within {darkorange}{1}{default} seconds." + } + + "Menu_MaxRockets" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new maximum rockets count within {darkorange}{1}{default} seconds." + } + + "Menu_Interval" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new rocket spawn interval within {darkorange}{1}{default} seconds." + } + + "Menu_ChancesTable" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Type in chat the new rocket class spawn chances within {darkorange}{1}{default} seconds." + } + + // Escaping x07 doesn't work. Parsing it as an argument does the job. + + "Menu_ChangedSpriteColor" + { + "#format" "{1:c},{2:s},{3:s}" + "en" "[{olive}TFDB{default}] Changed rocket class sprite trail color to {1}{2}#{3}." + } + + "Menu_ResetSpriteColor" + { + "#format" "{1:c},{2:s},{3:s}" + "en" "[{olive}TFDB{default}] Reset rocket class sprite trail color to {1}{2}#{3}." + } + + "Menu_ChangedSpriteLifetime" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class sprite trail duration to {darkorange}{1}{default}." + } + + "Menu_ChangedSpriteStartWidth" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class sprite trail start width to {darkorange}{1}{default}." + } + + "Menu_ChangedSpriteEndWidth" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class sprite trail end width to {darkorange}{1}{default}." + } + + "Menu_ChangedBeepInterval" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class beep interval to {darkorange}{1}{default}." + } + + "Menu_ChangedCritChance" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class critical chance to {darkorange}{1}{default}." + } + + "Menu_ChangedDamage" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class damage to {darkorange}{1}{default}." + } + + "Menu_ChangedDamageIncrement" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class damage increment to {darkorange}{1}{default}." + } + + "Menu_ChangedSpeed" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class speed to {darkorange}{1}{default}." + } + + "Menu_ChangedSpeedIncrement" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class speed increment to {darkorange}{1}{default}." + } + + "Menu_ChangedSpeedLimit" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class speed limit to {darkorange}{1}{default}." + } + + "Menu_ChangedTurnRate" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class turn rate to {darkorange}{1}{default}." + } + + "Menu_ChangedTurnRateIncrement" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class turn rate increment to {darkorange}{1}{default}." + } + + "Menu_ChangedTurnRateLimit" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class turn rate limit to {darkorange}{1}{default}." + } + + "Menu_ChangedElevationRate" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class elevation rate to {darkorange}{1}{default}." + } + + "Menu_ChangedElevationLimit" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class elevation limit to {darkorange}{1}{default}." + } + + "Menu_ChangedRocketsModifier" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class fired rockets modifier to {darkorange}{1}{default}." + } + + "Menu_ChangedPlayerModifier" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class player count modifier to {darkorange}{1}{default}." + } + + "Menu_ChangedControlDelay" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class control delay to {darkorange}{1}{default}." + } + + "Menu_ChangedDragTimeMin" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class drag time start to {darkorange}{1}{default}." + } + + "Menu_ChangedDragTimeMax" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class drag time end to {darkorange}{1}{default}." + } + + "Menu_ChangedTargetWeight" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class target weight to {darkorange}{1}{default}." + } + + "Menu_ChangedMaxBounces" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Changed rocket class maximum bounces to {darkorange}{1}{default}." + } + + "Menu_ChangedBounceScale" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed rocket class bounce scale to {darkorange}{1}{default}." + } + + "Menu_ChangedMaxRockets" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Changed spawners maximum rockets to {darkorange}{1}{default}." + } + + "Menu_ChangedInterval" + { + "#format" "{1:.2f}" + "en" "[{olive}TFDB{default}] Changed spawners rocket spawn interval to {darkorange}{1}{default}." + } + + "Menu_ChangedChancesTable" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Changed spawners rocket class chances to {darkorange}{1}{default}." + } + + "Dodgeball_BounceVote_Cooldown" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Voting for bounce toggling is in cooldown for {darkorange}{1}{default} seconds." + } + + "Dodgeball_BounceVote_Failed" + { + "en" "[{olive}TFDB{default}] Bounce toggle vote {red}failed{default}." + } + + "Dodgeball_BounceVote_Enabled" + { + "en" "[{olive}TFDB{default}] No bounce mode is now {community}enabled{default}." + } + + "Dodgeball_BounceVote_Disabled" + { + "en" "[{olive}TFDB{default}] No bounce mode is now {red}disabled{default}." + } + + "Dodgeball_ClassVote_Cooldown" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Voting for a main rocket class is in cooldown for {darkorange}{1}{default} seconds." + } + + "Dodgeball_ClassVote_Reset" + { + "en" "[{olive}TFDB{default}] The main rocket class has been {red}reset{default}." + } + + "Dodgeball_ClassVote_Changed" + { + "#format" "{1:s}" + "en" "[{olive}TFDB{default}] The main rocket class has been changed to \"{community}{1}{default}\"." + } + + "Dodgeball_CountVote_Cooldown" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] Voting for rockets count is in cooldown for {darkorange}{1}{default} seconds." + } + + "Dodgeball_CountVote_Reset" + { + "en" "[{olive}TFDB{default}] The rockets count has been {red}reset{default}." + } + + "Dodgeball_CountVote_Changed" + { + "#format" "{1:i}" + "en" "[{olive}TFDB{default}] The rockets count has been changed to {community}{1}{default}." + } +} diff --git a/roles/sourcemod/files/cfg/sourcemod/dodgeball_disable.cfg b/roles/sourcemod/files/cfg/sourcemod/dodgeball_disable.cfg new file mode 100644 index 00000000..930f4057 --- /dev/null +++ b/roles/sourcemod/files/cfg/sourcemod/dodgeball_disable.cfg @@ -0,0 +1,14 @@ +// ---- Config file executes when the Dodgeball game mode is disabled ---- + +// Set the burst ammo to 25 (default) +sm_cvar tf_flamethrower_burstammo 25 + +// Reenable any class restriction / fast respawn plugins here +// ... + +// Enables arena queue +sm_cvar tf_arena_use_queue 1 + +sm_cvar sv_allow_point_servercommand official + +sv_alltalk 0 diff --git a/roles/sourcemod/files/cfg/sourcemod/dodgeball_enable.cfg b/roles/sourcemod/files/cfg/sourcemod/dodgeball_enable.cfg new file mode 100644 index 00000000..78e4c7e4 --- /dev/null +++ b/roles/sourcemod/files/cfg/sourcemod/dodgeball_enable.cfg @@ -0,0 +1,14 @@ +// ---- Config file executes when the Dodgeball game mode is enabled ---- + +// Set the burst ammo to 0 +sm_cvar tf_flamethrower_burstammo 0 + +// Disable any class restriction / fast respawn plugins here +// ... + +// Disables arena queue +sm_cvar tf_arena_use_queue 0 + +sm_cvar sv_allow_point_servercommand always + +sv_alltalk 1 diff --git a/roles/sourcemod/files/cfg/sourcemod/dodgeball_ffa_disable.cfg b/roles/sourcemod/files/cfg/sourcemod/dodgeball_ffa_disable.cfg new file mode 100644 index 00000000..509f698b --- /dev/null +++ b/roles/sourcemod/files/cfg/sourcemod/dodgeball_ffa_disable.cfg @@ -0,0 +1,2 @@ +sm_cvar tf_dodgeball_as_collision 1 +sm_cvar tf_dodgeball_as_damage 1 diff --git a/roles/sourcemod/files/cfg/sourcemod/dodgeball_ffa_enable.cfg b/roles/sourcemod/files/cfg/sourcemod/dodgeball_ffa_enable.cfg new file mode 100644 index 00000000..ebaf82af --- /dev/null +++ b/roles/sourcemod/files/cfg/sourcemod/dodgeball_ffa_enable.cfg @@ -0,0 +1,2 @@ +sm_cvar tf_dodgeball_as_collision 0 +sm_cvar tf_dodgeball_as_damage 0 diff --git a/roles/srcds/tasks/main.yml b/roles/srcds/tasks/main.yml index 7ab510da..131e98df 100644 --- a/roles/srcds/tasks/main.yml +++ b/roles/srcds/tasks/main.yml @@ -271,6 +271,30 @@ loop_control: index_var: loop0 +- name: Make tf/addons/sourcemod/configs/dodgeball + ansible.builtin.file: + path: ~/srcds-{{ item.server_name_short }}/tf/addons/sourcemod/configs/dodgeball + state: directory + mode: "0755" + tags: + - game_config + loop: "{{ services }}" + when: item.config == "dodgeball" + loop_control: + index_var: loop0 + +- name: Generate /tf/addons/sourcemod/configs/dodgeball/dodgeball_general.cfg + ansible.builtin.template: + src: dodgeball_general.cfg.j2 + dest: ~/srcds-{{ item.server_name_short }}/tf/addons/sourcemod/configs/dodgeball/general.cfg + mode: "0655" + tags: + - game_config + loop: "{{ services }}" + when: item.config == "dodgeball" + loop_control: + index_var: loop0 + - name: Generate /tf/cfg/sourcemod/gbans.cfg ansible.builtin.template: src: gbans.cfg.j2 diff --git a/roles/srcds/templates/dodgeball_general.cfg.j2 b/roles/srcds/templates/dodgeball_general.cfg.j2 new file mode 100644 index 00000000..181d1ceb --- /dev/null +++ b/roles/srcds/templates/dodgeball_general.cfg.j2 @@ -0,0 +1,289 @@ +// ------------------------------------------------------- +// Events +// ------------------------------------------------------- +// +// In order to further customize the rocket types, you can +// set a command to be executed when a certain event happens +// with a dodgeball rocket. +// +// The events that are right now implemented are the following : +// +// on spawn - When a rocket spawns. +// on deflect - A client has deflected a rocket. +// on kill - One of the rockets has killed the client. +// on explode - Same as on kill, only triggered once. +// on no target - When a rocket has an invalid target. +// - The target parameter "@target" will be the new target of the rocket. +// on destroyed - When a rocket explodes and does not kill anyone. +// - As a result, the rocket entity parameter "@rocket" will always be -1. +// - The target parameter "@target" will be the player who touched the rocket. +// - Requires the extra events plugin. +// +// The possible parameters passed for the commands are the following : +// +// @name - [String] : Name of the projectile type +// @rocket - [Integer] : Rocket entity index +// @owner - [Integer] : Owner client index +// @target - [Integer] : Target client index +// @dead - [Integer] : Last dead client index +// @deflections - [Integer] : Number of rocket deflections +// @speed - [Float] : Speed of the rocket with limit +// @mphspeed - [Integer] : Speed of the rocket in MpH without limit +// @capmphspeed - [Integer] : Speed of the rocket in Mph with limit +// @nocapspeed - [Float] : Speed of the rocket without limit (conversion from MpH) +// @2dspeed - [Float] : Speed of the rocket with limit that has only 2 decimal numbers +// @2dnocapspeed - [Float] : Speed of the rocket without limit that has only 2 decimal numbers +// +// ------------------------------------------------------- +// Commands +// ------------------------------------------------------- +// +// For using with the events system, the plugin has some useful +// commands if you want to make kickass explosion blasts. +// +// tf_dodgeball_explosion +// Shows a huge explosion at the location of the specified client. +// +// tf_dodgeball_shockwave +// Applies a huge shockwave at the location of the client. +// +// If you are able to write plugins, you can include your custom +// commands too! +// +// ------------------------------------------------------- +// Extra information +// ------------------------------------------------------- +// +// It is possible to have map specific configuration files by +// creating a config with the name of the map. Keep in mind that +// no previous values are deleted. +// +// If a keyvalue / parameter has a default value and you do not want +// to change it, you can leave the value empty or ignore the parameter +// altogether. +// +// Neutral rockets cannot damage teammates unless "mp_friendlyfire" +// is set to 1. +// ------------------------------------------------------- + +"tf2_dodgeball" +{ + + "general" + { + // >>> Experimental scaling modes (all disabled by default) <<< + // These override specific per-class values when enabled. + // Your existing config will work exactly the same with all of these set to 0. + + "orbit coefficient" "0" // 1 = use "orbit tightness" instead of "turn rate" + "turn rate increment" + // Turn rate is auto-derived from speed, keeping orbit radius constant. + "target speed scaling" "0" // 1 = use "max speed" + "max deflections" instead of "speed increment" + // Speed increment is auto-calculated to reach max speed evenly. + "smooth elevation" "1" // 1 = apply elevation per-frame (smooth) instead of logic timer (~10Hz) steps + // Does not override any values, only changes update rate. + "bounce vertical scale" "0" // 1 = use per-class "bounce vertical ratio" to dampen vertical bounces + + "music" "0" // Play music on Dodgeball gamemode? + + "round start" "" // Music to play on round start (before gameplay start) + "round end (win)" "" // Music to play for the winner team + "round end (lose)" "" // Music to play for the loser team + "gameplay" "" // Music to play when the gameplay starts. This one stops + // at round end. + + "use web player" "0" // If the use of web player is enabled, these will be + "web player url" "" // used instead of the gameplay music + } + + "classes" + { + "common" + { + // >>> Basic parameters <<< + "name" "Homing Rocket" // Full name of the rocket type + "behaviour" "homing" // "homing" for smooth, per-frame updates. + // "legacy homing" for classic, 20Hz timer updates. + + "model" "" // Default: Common rocket model + "is animated" "0" // Only works when using a custom model (Default : 0) + + // Trails section. Only usable if the trails plugin is installed. + // Remove the comments (//) before the keys (ex. "trail particle") if you want to use this section. + + //"trail particle" "" // Particle for particle trail (Default : no custom particle trail) + // Not all particles are going to work + + //"trail sprite" "" // Sprite path for sprite trail (Default : no custom sprite trail) + //"custom color" "" // Custom color for the sprite trail (Default : [255 255 255]) + //"sprite lifetime" "" // Custom lifetime for sprite trail (Default : 1.0 seconds) + //"sprite start width" "" // Custom start width for sprite trail (Default : 6.0 units) + //"sprite end width" "" // Custom end width for sprite trail (Default : 15.0 units) + //"texture resolution" "" // Custom texture resolution (Default : 0.05) + + // You can also insert custom keyvalue pairs for the "env_spritetrail" entity in this section + //"entity keyvalues" + //{ + // For example, this replaces the sprite set in the "trail sprite" key with "materials/sprites/laser.vmt" + // "spritename" "materials/sprites/laser.vmt" + + // Check https://developer.valvesoftware.com/wiki/Env_spritetrail for more info. + //} + + //"remove particles" "" // Remove the default rocket particles? (Default : 0) + //"replace particles" "" // Replace the default rocket particles? Works only if they have been removed (Default : 0) + // Creates a clone of the rocket and adds crit glows + + // Trails section end. + + "play spawn sound" "1" // Does the rocket emit a sound when spawning? (Default : 0) + "play beep sound" "1" // Does the rocket emit a beeping sound? (Default : 0) + "play alert sound" "1" // Does the rocket emit an alert sound to the client when being targetted? (Default : 0) + "spawn sound" "" // Default: Sentry rocket sound + "beep sound" "" // Default: Sentry searching sound + "alert sound" "" // Default: Sentry client spotted sound + "beep interval" "0" // Emit sound every x time (Default : 0.5 seconds) + + // >>> Specific behaviour modificators <<< + "elevate on deflect" "0" // Does the rocket elevate after a deflection? (Default : 0) + "neutral rocket" "0" // Does the rocket have no team based targets? (Default : 0) + "keep direction" "1" // Does the rocket keep its direction after touching a surface? (Default : 0) + "teamless deflects" "0" // Can this rocket be deflected by anyone? Same as neutral but targeting is not affected. (Default : 0) + "reset bounces" "1" // Does this rocket reset its internal bounces count on deflect? (Default : 0) + "no bounce drags" "0" // Can you drag this rocket after it touched a surface? Does not affect "legacy homing" rockets. (Default : 0) + "can be stolen" "0" // Can you steal this rocket from its target? (Default : 0) + "steal team check" "1" // Checks if the stealer and the target are on the same team. (Default : 0) + // Useful for neutral rockets. + + // >>> Movement parameters <<< + "damage" "50" // Base damage done by the rocket. + "damage increment" "25" // Increment per reflection. + // Damage is multiplied by 3 if the rocket is critical. + + "speed" "875" // Base speed for the rocket. + "speed increment" "160" // Speed increment per reflection. + "speed limit" "0" // Speed limit for the rocket (0 to disable speed limiting) + + "turn rate" "0.260" // Turn rate / tick for this rocket. + "turn rate increment" "0.0180" // Increment per reflection. + "turn rate limit" "0" // Maximum turn rate when deflected (0 to disable turn rate limiting) + // Cannot exceed 1.0 regardless of the actual limit value + + "elevation rate" "0" // Elevation rate when deflected (if enabled) + "elevation limit" "0" // Maximum elevation when deflected (if enabled) + + "control delay" "0" // Delay until the rocket starts tracking the target after a deflection. + + // "drag time min": After a deflect, this is the delay (in seconds) before the player's aim starts guiding the rocket. + // "drag time max": This is the total duration (in seconds) the player can guide the rocket after the initial min_time delay. + // Example: min 0.1, max 0.5 means dragging starts 0.1s after deflect and lasts for 0.5s, ending at 0.6s after deflect. + // This does not affect "legacy homing" rockets. + "drag time min" "0.05" + "drag time max" "0.05" + + "max bounces" "1000" // How many times can this rocket bounce? + "bounce scale" "1.0" // How hard should the rocket bounce? (Default : 1.0 multiplier) + "bounce force scale" "1.5" // Multiplier for how much stronger a player-forced bounce is. + "bounce force angle" "45.0" // Minimum downward angle a player must look to trigger a stronger bounce. + "crawl bounce" "1" // Use crawl bounce scaling/clamping. + "crawl bounce scale" "1" // Extra Z scaling in crawl mode. + "crawl bounce max up" "1500" // Max upward velocity after bounce in crawl mode (0 disables cap). + + "critical chance" "100" // Percentage of chance for a critical rocket. + + "no. players modifier" "0" // Increment based upon the number of players in the server. + "no. rockets modifier" "0" // Increment based upon the number of rockets fired since the start of the round. + "direction to target weight" "100" // Weight modifier for target selection, based upon the direction of the rocket + // to the client. + + // >>> Events <<< + "on spawn" "" // Actions to execute on rocket spawn. + "on deflect" "" // Actions to execute when a rocket is deflected. + "on kill" "" // Actions to execute when a rocket kills a client. + "on explode" "" // Actions to execute when a rocket kills a client (triggered once). + "on no target" "" // Actions to execute when a rocket has an invalid target. + "on destroyed" "" // Actions to execute when a rocket explodes (will not trigger if the player is killed by the rocket). + } + + "nuke" + { + // >>> Basic parameters <<< + "name" "Nuke!" + "behaviour" "homing" + "model" "models/custom/dodgeball/nuke/nuke.mdl" + "is animated" "1" + "trail particle" "" + "trail sprite" "" + "custom color" "" + "remove particles" "" + "replace particles" "" + "play spawn sound" "1" + "play beep sound" "1" + "play alert sound" "1" + "spawn sound" "" + "beep sound" "" + "alert sound" "" + "beep interval" "0.2" + + // >>> Specific behaviour modificators <<< + "elevate on deflect" "0" + "neutral rocket" "0" + "keep direction" "1" + "teamless deflects" "0" + "reset bounces" "0" + "no bounce drags" "0" + + // >>> Movement parameters <<< + "damage" "200" + "damage increment" "200" + "speed" "550" + "speed increment" "100" + "speed limit" "0" + "turn rate" "0.233" + "turn rate increment" "0.0275" + "turn rate limit" "0" + "elevation rate" "0.1237" + "elevation limit" "0.1237" + "control delay" "0" + "max bounces" "0" + "bounce scale" "1.0" + "critical chance" "100" + "no. players modifier" "0" + "no. rockets modifier" "0" + "direction to target weight" "25" + + // >>> Events <<< + "on spawn" "" + "on deflect" "" + "on kill" "tf_dodgeball_print [{olive}TFDB{default}] {darkmagenta}☢ NUKE{default} | {lightblue}##@owner## {default}nuked {red}##@dead## {default}| Deflects: {darkorange}@deflections {default}| Speed: {red}@capmphspeed mph" + "on explode" "tf_dodgeball_explosion @dead ; tf_dodgeball_shockwave @dead 200 1000 1000 600" + "on no target" "" + } + } + + "spawners" + { + // >>> Default RED spawner <<< + "red" + { + // >>> Basic parameters <<< + "max rockets" "1" // Max no. of rockets before the spawner can fire another. (Default : 1) + "interval" "2.0" // Minimum time between rocket fires. (Default : 2.0 seconds) + + // >>> Chances table <<< + "common%" "100" // Chance to spawn a common rocket + "nuke%" "0" // Chance to spawn a nuke rocket + } + + // >>> Default BLU spawner <<< + "blu" + { + // >>> Basic parameters <<< + "max rockets" "1" // Max no. of rockets before the spawner can fire another. + "interval" "2.0" // Minimum time between rocket fires. + + // >>> Chances table <<< + "common%" "100" // Chance to spawn a common rocket + "nuke%" "0" // Chance to spawn a nuke rocket + } + } +} diff --git a/roles/srcds/templates/dodgeball_presets.cfg.j2 b/roles/srcds/templates/dodgeball_presets.cfg.j2 new file mode 100644 index 00000000..0a04d235 --- /dev/null +++ b/roles/srcds/templates/dodgeball_presets.cfg.j2 @@ -0,0 +1,56 @@ +// TF2 Dodgeball - Presets Configuration +// +// Presets allow vote plugins to switch between gameplay modes with a single call. +// Each preset defines a rocket class to use, how many rockets spawn, and the spawn interval. +// +// The "rocket class" value must match a class short name (section key) from general.cfg. +// For example, if general.cfg has a class defined as: +// +// "classes" { +// "common" { <-- this is the short name +// "name" "Homing Rocket" +// ... +// } +// } +// +// Then use "rocket class" "common" in the preset. +// +// "spawn interval" is optional. If set to 0 or omitted, the spawner's +// default interval from general.cfg is kept. +// +// Usage from a subplugin: +// int count = TFDB_GetPresetCount(); // how many presets loaded +// TFDB_GetPresetName(0, name, sizeof(name)); // get display name +// TFDB_ApplyPreset(0); // apply preset (destroys rockets, sets spawners) + +"TF2_Dodgeball" +{ + "presets" + { + "1r" + { + "name" "1 Rocket (Competitive)" + "rocket class" "common" + "max rockets" "1" + "spawn interval" "1.5" + } + + // Example additional presets (uncomment and adjust to your classes): + // + // "2r" + // { + // "name" "2 Rockets (Standard)" + // "rocket class" "common" + // "max rockets" "2" + // "spawn interval" "1.5" + // } + // + // "3r" + // { + // "name" "3 Rockets (Casual)" + // "rocket class" "common" + // "max rockets" "3" + // "spawn interval" "2.0" + // } + } +} diff --git a/roles/srcds/templates/motd.txt.j2 b/roles/srcds/templates/motd.txt.j2 index 8eed58be..7a001c3a 100644 --- a/roles/srcds/templates/motd.txt.j2 +++ b/roles/srcds/templates/motd.txt.j2 @@ -1 +1,7 @@ -{% if item.config == 'pve' %}{{ motd_pve }}{% else %}{{ motd }}{% endif %} +{% if item.config == 'pve' %} +{{ motd_pve }} +{% elif item.config == 'dodgeball' %} +{{ motd_dodgeball }} +{% else %} +{{ motd }} +{% endif %} diff --git a/roles/srcds/templates/server.cfg.j2 b/roles/srcds/templates/server.cfg.j2 index 74326fab..c9156fd2 100644 --- a/roles/srcds/templates/server.cfg.j2 +++ b/roles/srcds/templates/server.cfg.j2 @@ -148,9 +148,12 @@ sv_disable_weapon_drop_on_death 1 {#{% endfor %}#} // Built in class limit commands + +{% if item.tf_classlimit|default(3) > 0 %} tf_classlimit {{ item.tf_classlimit|default(3) }} sv_vote_issue_classlimits_allowed 0 sv_vote_issue_classlimits_max 3 +{% endif %} // Disable stalemates mp_stalemate_enable 0 @@ -228,4 +231,4 @@ tv_snapshotrate {{ tv_snapshotrate|default(64) }} mp_timelimit 0 mp_winlimit 0 mp_maxrounds 0 -{% endif %} \ No newline at end of file +{% endif %} diff --git a/shell.nix b/shell.nix index d785482c..67945577 100644 --- a/shell.nix +++ b/shell.nix @@ -9,10 +9,13 @@ in pkgs.mkShellNoCC { packages = with pkgs; [ - gnumake ansible ansible-lint - #ansible-language-server yamllint + just + just-lsp + nix + nixd + just-formatter ]; } diff --git a/sm_plugins/TF2-Dodgeball-Modified-UDL b/sm_plugins/TF2-Dodgeball-Modified-UDL new file mode 160000 index 00000000..b3b21818 --- /dev/null +++ b/sm_plugins/TF2-Dodgeball-Modified-UDL @@ -0,0 +1 @@ +Subproject commit b3b21818dd615fde6cad12f61df7fdca243d9cf5 diff --git a/sm_plugins_update.sh b/sm_plugins_update.sh index 11f3fc14..c870366e 100755 --- a/sm_plugins_update.sh +++ b/sm_plugins_update.sh @@ -3,6 +3,9 @@ ROOT=$(pwd) SM_ROOT=$ROOT/roles/sourcemod/files/addons/sourcemod SRC_ROOT="sm_plugins" +DODGEBALL_ROOT="$SRC_ROOT/TF2-Dodgeball-Modified-UDL" +DODGEBALL_BRANCH="master" + STAC_ROOT="$SRC_ROOT/stac" STAC_BRANCH="gbans-native" @@ -45,6 +48,26 @@ HALLOWEENCOSMETICS_BRANCH="master" # git submodule update --init --recursive +pushd $DODGEBALL_ROOT || exit +git fetch --all +git checkout $DODGEBALL_BRANCH +git pull +pushd TF2Dodgeball/addons/sourcemod || exit +for d in 'scripting' 'translations'; do + cp -rv $d "$SM_ROOT" +done +popd || exit + +cp -rv Subplugins/AirblastPrevention/scripting/tfdb_airblast_prevention.sp $SM_ROOT/scripting/ +cp -rv Subplugins/NoBlock/scripting/tfdb_no_block.sp $SM_ROOT/scripting/ +#cp -rv Subplugins/Votes/scripting/tfdb_votes.sp $SM_ROOT/scripting/ +cp -rv Subplugins/Speedometer/scripting/tfdb_speedhud.sp $SM_ROOT/scripting/ +cp -rv Subplugins/FFA/scripting/tfdb_ffa.sp $SM_ROOT/scripting/ +cp -rv Subplugins/Print/scripting/tfdb_print.sp $SM_ROOT/scripting/ +#cp -rv TF2Dodgeball/cfg/sourcemod/* $ROOT/roles/sourcemod/files/cfg/sourcemod/ + +popd || exit +exit pushd $HALLOWEENCOSMETICS_ROOT || exit git fetch --all git checkout $HALLOWEENCOSMETICS_BRANCH @@ -104,12 +127,6 @@ git checkout $ECON_DATA_BRANCH cp -rv scripting gamedata "$SM_ROOT" popd || exit -pushd $ATTRIBUTES_ROOT || exit -git fetch --all -git checkout $ATTRIBUTES_BRANCH -cp -rv scripting gamedata "$SM_ROOT" -rm "$SM_ROOT/scripting/tf2attributes_example.sp" -popd || exit pushd $CENTERPROJECTILES_ROOT || exit git fetch --all @@ -138,3 +155,15 @@ git fetch --all git checkout $PVE_BRANCH cp -rv scripting gamedata "$SM_ROOT" popd || exit + + +echo "tf2attributes skipped" + +exit + +pushd $ATTRIBUTES_ROOT || exit +git fetch --all +git checkout $ATTRIBUTES_BRANCH +cp -rv scripting gamedata "$SM_ROOT" +rm "$SM_ROOT/scripting/tf2attributes_example.sp" +popd || exit