Last active
September 6, 2021 02:40
-
-
Save nosoop/538169d7cb9e60ef8ea9126ca16e8270 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* [TF2] Jungle Inferno Equipment | |
*/ | |
#pragma semicolon 1 | |
#include <sourcemod> | |
#include <tf2_stocks> | |
#include <clientprefs> | |
#include <sdkhooks> | |
#pragma newdecls required | |
#define PLUGIN_VERSION "1.0.2" | |
public Plugin myinfo = { | |
name = "[TF2] Jungle Inferno Equipment", | |
author = "nosoop", | |
description = "Allows players to equip new weapons for Pyro and Heavy. " | |
... "Because Valve is giving normal community servers fewer reasons to exist.", | |
version = PLUGIN_VERSION, | |
url = "https://csrd.science/" | |
} | |
enum UpdateWeapon { | |
Weapon_DragonsFury, | |
Weapon_ThermalThruster, | |
Weapon_GasPasser, | |
Weapon_HotHand, | |
Weapon_SecondBanana | |
}; | |
char INFERNO_WEAPON_LOADOUT_NAMES[UpdateWeapon][] = { | |
"The Dragon's Fury (Pyro Primary)", | |
"The Thermal Thruster (Pyro Secondary)", | |
"The Gas Passer (Pyro Secondary)", | |
"The Hot Hand (Pyro Melee)", | |
"The Second Banana (Heavy Secondary)" | |
}; | |
Handle g_PrefJungleWeapons; | |
int g_EquippedWeapons[MAXPLAYERS + 1]; | |
bool g_bUpdateWeaponNotify[MAXPLAYERS + 1]; | |
float g_flGasPasserCharge[MAXPLAYERS + 1]; | |
public void OnPluginStart() { | |
HookEvent("post_inventory_application", OnInventoryApplied); | |
HookEvent("player_spawn", OnPlayerSpawn); | |
HookEvent("player_disconnect", OnPlayerDisconnect); | |
RegConsoleCmd("sm_loadout", OpenLoadoutOverrideMenu); | |
g_PrefJungleWeapons = RegClientCookie("loadout_jungle_inferno", | |
"Preferences for Jungle Inferno weapons.", CookieAccess_Private); | |
} | |
public void OnMapStart() { | |
for (int i = 1; i <= MaxClients; i++) { | |
if (IsClientConnected(i)) { | |
SDKHook(i, SDKHook_PostThinkPost, OnPlayerChargeMeterUpdate); | |
if (AreClientCookiesCached(i)) { | |
OnClientCookiesCached(i); | |
} | |
} | |
} | |
} | |
public void OnClientCookiesCached(int client) { | |
char weaponBits[16]; | |
GetClientCookie(client, g_PrefJungleWeapons, weaponBits, sizeof(weaponBits)); | |
g_EquippedWeapons[client] = StringToInt(weaponBits); | |
} | |
public void OnPlayerDisconnect(Event event, const char[] name, bool dontBroadcast) { | |
int client = GetClientOfUserId(event.GetInt("userid")); | |
g_EquippedWeapons[client] = 0; | |
g_bUpdateWeaponNotify[client] = false; | |
} | |
public Action OpenLoadoutOverrideMenu(int client, int argc) { | |
Menu loadoutMenu = new Menu(LoadoutUpdateCallback, MENU_ACTIONS_ALL); | |
loadoutMenu.SetTitle("Select an item to toggle."); | |
for (int i = 0; i < view_as<int>(UpdateWeapon); i++) { | |
char menuInfo[8]; | |
IntToString(i, menuInfo, sizeof(menuInfo)); | |
// display will be overridden in the menu callback | |
loadoutMenu.AddItem(menuInfo, INFERNO_WEAPON_LOADOUT_NAMES[i]); | |
} | |
loadoutMenu.Display(client, 10); | |
return Plugin_Handled; | |
} | |
public int LoadoutUpdateCallback(Menu menu, MenuAction action, int param1, int param2) { | |
switch (action) { | |
case MenuAction_Select: { | |
int client = param1, selection = param2; | |
char menuInfo[8]; | |
menu.GetItem(selection, menuInfo, sizeof(menuInfo)); | |
UpdateWeapon menuItem = view_as<UpdateWeapon>(StringToInt(menuInfo)); | |
ToggleUpdateWeaponOverride(client, menuItem); | |
menu.Display(client, 10); | |
} | |
case MenuAction_DisplayItem: { | |
int client = param1, item = param2; | |
char menuInfo[8]; | |
menu.GetItem(item, menuInfo, sizeof(menuInfo)); | |
UpdateWeapon menuItem = view_as<UpdateWeapon>(StringToInt(menuInfo)); | |
char menuDisplay[128]; | |
Format(menuDisplay, sizeof(menuDisplay), "[%s] %s", | |
IsUpdateWeaponOverride(client, menuItem) ? "x" : " ", | |
INFERNO_WEAPON_LOADOUT_NAMES[menuItem]); | |
return RedrawMenuItem(menuDisplay); | |
} | |
case MenuAction_End: { | |
int endReason = param1; | |
if (endReason == MenuEnd_Cancelled || endReason == MenuEnd_Exit) { | |
delete menu; | |
} | |
} | |
} | |
return 0; | |
} | |
public void OnInventoryApplied(Event event, const char[] name, bool dontBroadcast) { | |
int client = GetClientOfUserId(event.GetInt("userid")); | |
if (!client) { | |
return; | |
} | |
if (!g_bUpdateWeaponNotify[client]) { | |
// todo notify: | |
// * when both conditions are met: | |
// * player hasn't connected in the last week, and | |
// * player doesn't have all of the weapons (possible to check?) | |
// * or player has any plugin-granted weapons (reminder to erase) | |
PrintToChat(client, "This server is allowing players to equip the new Jungle Inferno " | |
... "update weapons. Type /loadout to toggle them."); | |
g_bUpdateWeaponNotify[client] = true; | |
} | |
switch(TF2_GetPlayerClass(client)) { | |
case TFClass_Pyro: { | |
if (IsUpdateWeaponOverride(client, Weapon_DragonsFury)) { | |
TF2_RemoveWeaponSlot(client, 0); | |
TF2_CreateAndEquipWeapon(client, 1178, "tf_weapon_rocketlauncher_fireball"); | |
} | |
if (IsUpdateWeaponOverride(client, Weapon_ThermalThruster)) { | |
TF2_RemoveWeaponSlot(client, 1); | |
TF2_CreateAndEquipWeapon(client, 1179, "tf_weapon_rocketpack"); | |
} | |
if (IsUpdateWeaponOverride(client, Weapon_GasPasser)) { | |
TF2_RemoveWeaponSlot(client, 1); | |
int weapon = TF2_CreateAndEquipWeapon(client, 1180, "tf_weapon_jar_gas"); | |
TF2_EmptyWeaponAmmo(weapon); | |
// Match game behavior by persisting recharge meter | |
// BUG: Item is unusuable if it spawns at full charge | |
float flItemChargeMeter = g_flGasPasserCharge[client] > 99.5? | |
99.5 : g_flGasPasserCharge[client]; | |
SetEntPropFloat(client, Prop_Send, "m_flItemChargeMeter", | |
flItemChargeMeter, 1); | |
} | |
if (IsUpdateWeaponOverride(client, Weapon_HotHand)) { | |
TF2_RemoveWeaponSlot(client, 2); | |
TF2_CreateAndEquipWeapon(client, 1181, "tf_weapon_slap"); | |
} | |
} | |
case TFClass_Heavy: { | |
if (IsUpdateWeaponOverride(client, Weapon_SecondBanana)) { | |
TF2_RemoveWeaponSlot(client, 1); | |
TF2_CreateAndEquipWeapon(client, 1190, "tf_weapon_lunchbox"); | |
} | |
} | |
} | |
} | |
public void OnPlayerSpawn(Event event, const char[] name, bool dontBroadcast) { | |
int client = GetClientOfUserId(event.GetInt("userid")); | |
// Gas Passer starts empty on spawn | |
g_flGasPasserCharge[client] = 0.0; | |
} | |
public void OnPlayerChargeMeterUpdate(int client) { | |
if (IsUpdateWeaponOverride(client, Weapon_GasPasser)) { | |
int item = GetPlayerWeaponSlot(client, 1); | |
if (GetEntProp(item, Prop_Send, "m_iItemDefinitionIndex") == 1180) { | |
g_flGasPasserCharge[client] = GetEntPropFloat(client, Prop_Send, | |
"m_flItemChargeMeter", 1); | |
} | |
} | |
} | |
bool IsUpdateWeaponOverride(int client, UpdateWeapon weapon) { | |
return !!((g_EquippedWeapons[client] >> view_as<int>(weapon)) & 1); | |
} | |
void ToggleUpdateWeaponOverride(int client, UpdateWeapon weapon) { | |
// disable the other secondary weapon | |
if (weapon == Weapon_ThermalThruster) { | |
g_EquippedWeapons[client] &= ~(1 << view_as<int>(Weapon_GasPasser)); | |
} else if (weapon == Weapon_GasPasser) { | |
g_EquippedWeapons[client] &= ~(1 << view_as<int>(Weapon_ThermalThruster)); | |
} | |
g_EquippedWeapons[client] ^= (1 << view_as<int>(weapon)); | |
char weaponBits[16]; | |
IntToString(g_EquippedWeapons[client], weaponBits, sizeof(weaponBits)); | |
SetClientCookie(client, g_PrefJungleWeapons, weaponBits); | |
} | |
int TF2_CreateAndEquipWeapon(int client, int defindex, const char[] weaponClass) { | |
int weapon = TF2_CreateWeapon(defindex, weaponClass); | |
TF2_EquipPlayerWeapon(client, weapon); | |
return weapon; | |
} | |
/** | |
* Creates a weapon for the specified player. | |
*/ | |
stock int TF2_CreateWeapon(int defindex, const char[] weaponClass) { | |
int weapon = CreateEntityByName(weaponClass); | |
if (IsValidEntity(weapon)) { | |
SetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex", defindex); | |
SetEntProp(weapon, Prop_Send, "m_bInitialized", 1); | |
// Allow quality / level override by updating through the offset. | |
char netClass[64]; | |
GetEntityNetClass(weapon, netClass, sizeof(netClass)); | |
SetEntData(weapon, FindSendPropInfo(netClass, "m_iEntityQuality"), 6); | |
SetEntData(weapon, FindSendPropInfo(netClass, "m_iEntityLevel"), 1); | |
SetEntProp(weapon, Prop_Send, "m_iEntityQuality", 6); | |
SetEntProp(weapon, Prop_Send, "m_iEntityLevel", 1); | |
DispatchSpawn(weapon); | |
} | |
return weapon; | |
} | |
stock void TF2_EquipPlayerWeapon(int client, int weapon) { | |
char weaponClass[64]; | |
GetEntityClassname(weapon, weaponClass, sizeof(weaponClass)); | |
if (StrContains(weaponClass, "tf_wearable", false) == 0) { | |
// CTFPlayer_EquipWearable(client, weapon); | |
} else { | |
EquipPlayerWeapon(client, weapon); | |
/** | |
* Ensure that weapon spawns with the correct amount of ammo. | |
*/ | |
TF2_EmptyWeaponAmmo(weapon); | |
TF2_GiveWeaponAmmo(weapon, 500); | |
} | |
} | |
/** | |
* Gives a weapon ammo. Rather, it gives ammo to the player holding the weapon. | |
* This stock uses GivePlayerAmmo, so it will not give any more ammo than what the player can | |
* carry. It does not check if the existing amount exceeds what the player can carry. | |
* | |
* @return Amount of ammo actually given. | |
*/ | |
stock int TF2_GiveWeaponAmmo(int weapon, int amount, bool supressSound = true) { | |
int ammoType = GetEntProp(weapon, Prop_Send, "m_iPrimaryAmmoType"); | |
int client = GetEntPropEnt(weapon, Prop_Send, "m_hOwner"); | |
if (client > 0 && client <= MaxClients) { | |
return GivePlayerAmmo(client, amount, ammoType, supressSound); | |
} | |
return 0; | |
} | |
/** | |
* Directly sets the amount of ammo a client can carry for a specific weapon. | |
*/ | |
stock void TF2_SetWeaponAmmo(int weapon, int amount) { | |
int ammoType = GetEntProp(weapon, Prop_Send, "m_iPrimaryAmmoType"); | |
int client = GetEntPropEnt(weapon, Prop_Send, "m_hOwner"); | |
if (ammoType != -1) { | |
SetEntProp(client, Prop_Send, "m_iAmmo", amount, 4, ammoType); | |
} | |
} | |
/** | |
* Reduces a client's currently carried amount of ammo for a specified weapon to zero. | |
*/ | |
stock void TF2_EmptyWeaponAmmo(int weapon) { | |
TF2_SetWeaponAmmo(weapon, 0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment