From 74c96e4b2b9338478367a453ac964cfe14f17d2d Mon Sep 17 00:00:00 2001 From: punteroo Date: Tue, 2 Feb 2021 16:03:54 -0300 Subject: [PATCH] Plugins Source Code --- configs/vip-system.cfg | 37 ++ scripting/custom-includes/paintcosmetics.inc | 58 +++ scripting/include/killstreak-class.inc | 151 ++++++ scripting/include/unusual-class.inc | 127 +++++ scripting/include/vip-unusual-glow.inc | 51 ++ scripting/vip-australium.sp | 370 +++++++++++++++ scripting/vip-killstreak.sp | 469 +++++++++++++++++++ scripting/vip-paints.sp | 343 ++++++++++++++ scripting/vip-system.sp | 160 +++++++ scripting/vip-unusual-glow.sp | 94 ++++ scripting/vip-unusuals.sp | 358 ++++++++++++++ 11 files changed, 2218 insertions(+) create mode 100644 configs/vip-system.cfg create mode 100644 scripting/custom-includes/paintcosmetics.inc create mode 100644 scripting/include/killstreak-class.inc create mode 100644 scripting/include/unusual-class.inc create mode 100644 scripting/include/vip-unusual-glow.inc create mode 100644 scripting/vip-australium.sp create mode 100644 scripting/vip-killstreak.sp create mode 100644 scripting/vip-paints.sp create mode 100644 scripting/vip-system.sp create mode 100644 scripting/vip-unusual-glow.sp create mode 100644 scripting/vip-unusuals.sp diff --git a/configs/vip-system.cfg b/configs/vip-system.cfg new file mode 100644 index 0000000..a49bc3a --- /dev/null +++ b/configs/vip-system.cfg @@ -0,0 +1,37 @@ +// V I P M A N A G E M E N T C O N F I G +// +// This configuration file is utilized to manage the plugins' behaivour. All commands utilized by VIP members of your community +// will be here. I'll spit some information about what the plugin does and how to use this CFG file correctly as well. +// +// This plugin is a fast and easy menu utilized to concise all VIP / Donator commands into one. It is highly customizable and +// stable, it is optimized to not lag any server on its usage. You can customize the menu's title, options, descriptions, etc. +// I've made an example configuration below (you must modify it at your liking) for you to check out how everything works and +// how to change what. Keep in mind, the limit of commands by default is 30. This can be changed in the plugins' code. +// Here are all the effect attributes cause on the plugin (i planned more but they're unnecesary for now :3): +// +// command - The command the user executes once he selects that option. If it's a command whose case is similar to the noclip one +// explained above, then leave it blank. If it's left blank no command will be executed on the user. +// +// level - Determines the VIP "level" to use that command (or have it even listed). It is currently disabled as it will not be +// used. +// + +"VIP-System" +{ + "Menu - Inusuales" + { + "command" "sm_unu" + } + "Menu - Killstreaks" + { + "command" "sm_killstreak" + } + "Toggle - Australiums" + { + "command" "sm_aussie" + } + "Noclip" + { + "command" "sm_noclip @me" + } +} \ No newline at end of file diff --git a/scripting/custom-includes/paintcosmetics.inc b/scripting/custom-includes/paintcosmetics.inc new file mode 100644 index 0000000..7d8715b --- /dev/null +++ b/scripting/custom-includes/paintcosmetics.inc @@ -0,0 +1,58 @@ +#include +#include + +enum struct PaintedHat { + // This enum controls a player's painted hats status. + // A maximum of 3 painted hats can be used at a time. + float values[3]; + int hatIndex; + + void WriteValues(float[3] buffer) { + for (int i = 0; i < 3; i++) + buffer[i] = this.values[i]; + } +} + +// Returns wether or not an Item Definition Index is paintable or not. +bool IsHatPaintable(int iItemDefinitionIndex) +{ + char strItemDefinitionIndex[32]; + IntToString(iItemDefinitionIndex, strItemDefinitionIndex, 32); + + ArrayList arg = CreateArray(sizeof(strItemDefinitionIndex)); + arg.PushString(strItemDefinitionIndex); + + DBStatement result = TF2IDB_CustomQuery("SELECT capability FROM tf2idb_capabilities WHERE id = ?", arg, sizeof(strItemDefinitionIndex)); + if (result != INVALID_HANDLE) { + while (SQL_FetchRow(result)) { + char capability[64]; + SQL_FetchString(result, 0, capability, sizeof(capability)); + + if (StrEqual(capability, "paintable")) + return true; + } + } + return false; +} + +// Writes an array of all paint values present on a CTFWearable entity index, if any. +// If there was no present paint, 0.0 is returned. Else just returns the paint value present. +void GetPaint(float[] val, int[] ids, int amount, float paints[3]) +{ + for (int i = 0; i < amount; i++) { + switch (ids[i]) { + case 142: + paints[0] = val[i]; + case 261: + paints[1] = val[i]; + case 1004: + paints[2] = val[i]; + } + } +} + + +#if defined _INCLUDE_included + #endinput +#endif +#define _INCLUDE_included \ No newline at end of file diff --git a/scripting/include/killstreak-class.inc b/scripting/include/killstreak-class.inc new file mode 100644 index 0000000..0868c8b --- /dev/null +++ b/scripting/include/killstreak-class.inc @@ -0,0 +1,151 @@ +methodmap KsClient < StringMap { + public KsClient() { + return view_as(new StringMap()); + } + + // Initializes all values to 0 + public void Initialize() { + this.SetValue("type", 0.0); + this.SetValue("sheen", 0.0); + this.SetValue("streaker", 0.0); + this.SetValue("all", false); + } + + // Temporary slot selection. Stores the selected slot on the menu to work with. + public void SetSlot(int slot) { + this.SetValue("slot", slot); + } + + // Temporary slot selection. Gets the selected slot on the menu to work with. + public int GetSlot() { + int slot; + this.GetValue("slot", slot); + + return slot; + } + + // Sets the type of Killstreak the client chose. + public void SetType(float val, int slot) { + bool all; + this.GetValue("all", all); + + if (!all) { + float type[3]; + this.GetArray("type", type, sizeof(type)); + + type[slot] = val; + this.SetArray("type", type, sizeof(type)); + } + else + this.SetValue("type", val); + } + + // Sets the sheen of a Specialized Killstreak the client chose. + public void SetSheen(float val, int slot) { + bool all; + this.GetValue("all", all); + + if (!all) { + float sheen[3]; + this.GetArray("sheen", sheen, sizeof(sheen)); + + sheen[slot] = val; + this.SetArray("sheen", sheen, sizeof(sheen)); + } + else + this.SetValue("sheen", val); + } + + // Sets the killstreaker of a Professional Killstreak the client chose. + public void SetStreaker(float val, int slot) { + bool all; + this.GetValue("all", all); + + if (!all) { + float streaker[3]; + this.GetArray("streaker", streaker, sizeof(streaker)); + + streaker[slot] = val; + this.SetArray("streaker", streaker, sizeof(streaker)); + } + else + this.SetValue("streaker", val); + } + + // Changes if the mode is for all weapons or not. + public void AllWeapons(bool mode) { + this.SetValue("all", mode); + } + + // Gets the current type array (index is slot). + public void GetType(float array[3]) { + bool all; + this.GetValue("all", all); + + if (!all) { + float t[3]; + this.GetArray("type", t, sizeof(t)); + array = t; + } + else { + float t1[3], t2; + this.GetValue("type", t2); + + for (int i = 0; i < sizeof(t1); i++) + t1[i] = t2; + + array = t1; + } + } + + // Gets the current sheen array (index is slot). + public void GetSheen(float array[3]) { + bool all; + this.GetValue("all", all); + + if (!all) { + float s[3]; + this.GetArray("sheen", s, sizeof(s)); + + array = s; + } + else { + float s1[3], s2; + this.GetValue("sheen", s2); + + for (int i = 0; i < sizeof(s1); i++) + s1[i] = s2; + + array = s1; + } + } + + // Gets the current killstreaker array (index is slot). + public void GetStreaker(float array[3]) { + bool all; + this.GetValue("all", all); + + if (!all) { + float st[3]; + this.GetArray("streaker", st, sizeof(st)); + array = st; + } + else { + float st1[3], st2; + this.GetValue("streaker", st2); + + for (int i = 0; i < sizeof(st1); i++) + st1[i] = st2; + + array = st1; + } + } + + // Checks if the client has All Weapons Mode. + public bool IsAllMode() { + bool all; + this.GetValue("all", all); + + return all; + } +} \ No newline at end of file diff --git a/scripting/include/unusual-class.inc b/scripting/include/unusual-class.inc new file mode 100644 index 0000000..5e95b08 --- /dev/null +++ b/scripting/include/unusual-class.inc @@ -0,0 +1,127 @@ +// Unusual Class +methodmap UnusualClient < StringMap { + public UnusualClient() { + return view_as(new StringMap()); + } + + public void Initialize() { + this.SetValue("slot", 0); + this.SetArray("id", { 0, 0, 0 }, 3); + this.SetArray("unusual", { 0.0, 0.0, 0.0 }, 3); + } + + // SetSlot() - Temporary function, used only to keep track of the slot selected by the user. + public void SetSlot(int slot) { + this.SetValue("slot", slot); + } + + // SetId() - Temporary function, used only to keep track of the item ID selected by the user. + public void SetId(int id) { + int slot; + this.GetValue("slot", slot); + + int arr[3]; + if (!this.GetArray("id", arr, sizeof(arr))) + arr = { 0, 0, 0 }; + arr[slot] = id; + this.SetArray("id", arr, sizeof(arr)); + } + + // SetUnusual() - Sets the Unusual effect the user chose. + // Keep in mind, a max of 3 different effects and hats can be chosen. + public void SetUnusual(float eff) { + int slot; + this.GetValue("slot", slot); + + float unu[3]; + if (!this.GetArray("unusual", unu, sizeof(unu))) + unu = { 0.0, 0.0, 0.0 }; + + unu[slot] = eff; + this.SetArray("unusual", unu, sizeof(unu)); + } + + public int GetSlot() { + int slot; + this.GetValue("slot", slot); + + return slot; + } + + // GetId() - Gets the ID at a certain slot. + // Empty slots (no unusual) is identified with 0.0 content. + public int GetId(int slot) { + int id[3]; + this.GetArray("id", id, sizeof(id)); + + return id[slot]; + } + + // GetUnusual() - Gets the unusual array the client has currently on n slot. + // Empty slots (no unusual) is identified with 0.0 content. + public float GetUnusual(int slot) { + float unusual[3]; + this.GetArray("unusual", unusual, sizeof(unusual)); + + return unusual[slot]; + } +} + +// Unusual Particle Effects List +int unusualIds[138] = { + 0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 43, 44, 45, 46, 47, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, + 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, + 137, 138, 139, 141, 142, 143, 144, 145, 147, 148, 149, + 150, 151, 152, 153, 154, 155, + 156, 157, 158, 159, 160, 161, 162, 163 +}; + +char unusualNames[138][64] = { + "Ninguno", "Green Confetti", "Purple Confetti", "Haunted Ghosts", "Green Energy", + "Purple Energy", "Circling TF Logo", "Massed Flies", "Burning Flames", + "Scorching Flames", "Searing Plasma", "Vivid Plasma", "Sunbeams", + "Circling Peace Sign", "Circling Heart", "Stormy Storm", "Blizzardy Storm", + "Nuts n' Bolts", "Orbiting Planets", "Orbiting Fire", "Bubbling", "Smoking", + "Steaming", "Flaming Lantern", "Cloudy Moon", "Cauldron Bubbles", "Eerie Orbiting Fire", + "Knifestorm", "Misty Skull", "Harvest Moon", "It's A Secret To Everybody", + "Stormy 13th Hour", "Kill-a-Watt", "Terror-Watt", "Cloud 9", "Aces High", + "Dead Presidents", "Miami Nights", "Disco Beat Down", "Phosphorous", "Sulphurous", + "Memory Leak", "Overclocked", "Electrostatic", "Power Surge", "Anti-Freeze", + "Time Warp", "Green Black Hole", "Roboactive", "Arcana", "Spellbound", + "Chiroptera Venenata", "Poisoned Shadow", "Something Burning This Way Comes", + "Hellfire", "Darkblaze", "Demonflame", "Bonzo The All-Gnawing", "Amaranthine", + "Stare From Beyond", "The Ooze", "Ghastly Ghosts Jr", "Haunted Phantasm Jr", + "Frostbite", "Molten Mallard", "Morning Glory", "Death at Dusk", "Abduction", + "Atomic", "Subatomic", "Electric Hat Protector", "Magnetic Hat Protector", + "Voltaic Hat Protector", "Galactic Codex", "Ancient Codex", "Nebula", "Death by Disco", + "It's a mystery to everyone", "It's a puzzle to me", "Ether Trail", "Nether Trail", + "Ancient Eldritch", "Eldritch Flame", "Neutron Star", "Tesla Coil", "Starstorm Insomnia", + "Starstorm Slumber", "Brain Drain", "Open Mind", "Head of Steam", "Galactic Gateway", + "The Eldritch Opening", "The Dark Doorway", "Ring of Fire", "Vicious Circle", "White Lightning", + "Omniscient Orb", "Clairvoyance", "Fifth Dimension", "Vicious Vortex", "Menacing Miasma", "Abyssal Aura", + "Wicked Wood", "Ghastly Grove", "Mystical Medley", "Ethereal Essence", "Twisted Radiance", + "Violet Vortex", "Verdant Vortex", "Valiant Vortex", "Sparkling Lights", "Frozen Icefall", + "Fragmented Gluons", "Fragmented Quarks", "Fragmented Photons", "Defragmenting Reality", + "Fragmenting Reality", "Refragmenting Reality", "Snowfallen", "Snowblinded", "Pyroland Daydream", + "Verdatica", "Aromatica", "Chromatica", "Prismatica", "Bee Swarm", "Frisky Fireflies", "Smoldering Spirits", + "Wandering Wisps", "Kaleidoscope", + "Green Giggler", "Laugh-O-Lantern", "Plum Prankster", "Pyroland Nightmare", "Gravelly Ghoul", "Vexed Volcanics", + "Gourdian Angels", "Pumpkin Party" +}; + +// AddUnusuals(Menu menu) - Adds all Unusual Effects to a menu. +void AddUnusuals(Menu menu) +{ + for (int i = 0; i < sizeof(unusualIds); i++) { + char id[5]; + IntToString(unusualIds[i], id, sizeof(id)); + + AddMenuItem(menu, id, unusualNames[i]); + } +} \ No newline at end of file diff --git a/scripting/include/vip-unusual-glow.inc b/scripting/include/vip-unusual-glow.inc new file mode 100644 index 0000000..897c11c --- /dev/null +++ b/scripting/include/vip-unusual-glow.inc @@ -0,0 +1,51 @@ +#include + +char glowNames[6][64] = { + "None", "Emerald Allurement", "Pyrophoric Personality", "Spellbound Aspect", + "Static Shock", "Veno Shock" +}; + +float glowIds[6] = { + 0.0, 3041.0, 3042.0, 3043.0, + 3044.0, 3045.0 +} + +/* CreateGlowMenu() + * Creates a menu with all the glow options. + * ---------------------------------- + * return - Menu Handle with all data needed. Handler name is "glowHdlr" + */ +Menu CreateGlowMenu() +{ + Menu menu = CreateMenu(glowHdlr); + + menu.SetTitle("Glow Effects"); + + for (int i = 0; i < sizeof(glowNames); i++) { + char idStr[12]; + FloatToString(glowIds[i], idStr, sizeof(idStr)); + + menu.AddItem(idStr, glowNames[i]); + } + + menu.ExitButton = true; + + return menu; +} + +/* + * SetGlow(int client, float eff) + * Sets the glow effect on the specified client. + * A value of eff = 0.0 will remove the particle effect. + * ---------------------------------------------- + * client - Client index of the player to set the effect on. + * eff - Effect ID to set on the client. + * return - True if the effect was applied successfully, false if there was an error (client has no m_Item net table) + */ +bool SetGlow(int client, float eff) +{ + TF2Attrib_RemoveByName(client, "attach particle effect"); + + if (IsClientInGame(client) && IsPlayerAlive(client) && eff > 0.0) + return TF2Attrib_SetByName(client, "attach particle effect", eff); +} \ No newline at end of file diff --git a/scripting/vip-australium.sp b/scripting/vip-australium.sp new file mode 100644 index 0000000..1927888 --- /dev/null +++ b/scripting/vip-australium.sp @@ -0,0 +1,370 @@ +#include +#include +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0.0" + +///////////////////// +// GLOBAL DECLARES // +///////////////////// + +// Declare the weapons slots amount +#define WEAPON_SLOTS 3 + +// Attribute IDs required for an australium weapon. +//#define AUS_ATTRS "2027 ; 1 ; 2022 ; 1 ; 542 ; 1" old define i used +int AttribId[3] = { 2027, 2022, 542 }; + +// Per-client Boolean for Australium Mode +bool Australium[MAXPLAYERS + 1] = false; +bool AustraliumSE[MAXPLAYERS + 1] = false; + +// New Golden Weapons +int clientGold[MAXPLAYERS + 1] = 0; + +int goldWeps[3] = { 264, 423, 169 }; +char goldNames[3][32] = { "Golden Frying Pan", "Saxxy", "Golden Wrench" }; + +///////////////////// +///////////////////// +///////////////////// + +public Plugin myinfo = { + name = "[VIP Module] Australium Weapons", + author = "Lucas 'puntero' Maza", + description = "Australium weapons plugin for VIP members.", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/member.php?u=213425" +}; + +public void OnPluginStart() +{ + RegAdminCmd("sm_australium", CMD_Aussie, ADMFLAG_RESERVATION, "Toggles the australium weapon mode."); + RegAdminCmd("sm_aussie", CMD_Aussie, ADMFLAG_RESERVATION, "Toggles the australium weapon mode."); + // Just some other detection for a short and long command. + + HookEvent("post_inventory_application", OnItems); + HookEvent("player_spawn", OnItems); + // Hook to the items given / resupply event, this manages when to give the user their weapons. + // Also hook into the player_spawn event just in case post_inventory_application isn't called (happens sometimes) +} + +public void OnMapStart() +{ + for (int i = 0; i < sizeof(Australium); i++) { + Australium[i] = false; + AustraliumSE[i] = false; + clientGold[i] = -1; + } +} + +public void OnClientConnected(int client) +{ + // Disable all australiums on connection. + Australium[client] = false; + AustraliumSE[client] = false; + clientGold[client] = 0; +} + +public void OnClientDisconnect(int client) +{ + // Disable all australiums on disconnection. + Australium[client] = false; + AustraliumSE[client] = false; + clientGold[client] = -1; +} + +public Action CMD_Aussie(int client, int args) +{ + OpenAustraliumMenu(client); + + // We're done with the command. + return Plugin_Handled; +} + +public Action OnItems(Event event, char[] name, bool dontBroadcast) +{ + // Get the client that requested an item "refresh" + int client = GetClientOfUserId(GetEventInt(event, "userid")); + + // Did he enable australium mode on himself? + if (Australium[client]) { + // If the client has australium mode and touches a resupply locker, he'll drop the weapon infinitely. + // So we create a timer to remove those dropped weapons just as they are spawned in. + CreateTimer(0.1, RemoveDropped, client); + + // While that is happening, we can give this person their weapon. + // There's a little problem though, not all weapons are australium, only a few can be aussie. + // So we need to check first if the weapon CAN be australium before being given. + for (int i = 0; i < WEAPON_SLOTS; i++) + { + // Get the weapon the player has on i slot. + int ent = GetPlayerWeaponSlot(client, i); + + // If it is a valid entity reference index, proceed. + if (ent != INVALID_ENT_REFERENCE) { + int id = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (AustraliumSE[client] && i == 2) { + char tClass[64]; + + TFClassType iClass = TF2_GetPlayerClass(client); + switch (iClass) { + case TFClass_Scout: + Format(tClass, sizeof(tClass), "tf_weapon_bat"); + case TFClass_Soldier: + Format(tClass, sizeof(tClass), "tf_weapon_shovel"); + case TFClass_Pyro: + Format(tClass, sizeof(tClass), "tf_weapon_fireaxe"); + case TFClass_DemoMan: + Format(tClass, sizeof(tClass), "tf_weapon_bottle"); + case TFClass_Engineer: + Format(tClass, sizeof(tClass), "tf_weapon_wrench"); + case TFClass_Heavy: + Format(tClass, sizeof(tClass), "tf_weapon_fists"); + case TFClass_Sniper: + Format(tClass, sizeof(tClass), "tf_weapon_club"); + case TFClass_Medic: + Format(tClass, sizeof(tClass), "tf_weapon_bonesaw"); + case TFClass_Spy: + Format(tClass, sizeof(tClass), "tf_weapon_knife"); + } + + GiveAustralium(client, tClass, goldWeps[clientGold[client]], 15, 11, i); + continue; + } + + // If the weapon is australizable, continue. + if (IsAustralizable(id)) { + char classname[32]; + GetEntityClassname(ent, classname, sizeof(classname)); + + // Get the weapon level as well. + int level = GetEntProp(ent, Prop_Send, "m_iEntityLevel"); + + // Give them the australium weapon. + GiveAustralium(client, classname, id, level, 11, i); + } + } + } + // We've done what we had to do. + return Plugin_Continue; + } + // Just another user without Australium mode. Let's go somewhere else. + return Plugin_Continue; +} + +public Action RemoveDropped(Handle timer, any client) +{ + // Start with the first entity in the server. + // Loop through all of the entities and only perform on "tf_dropped_weapon" (dropped weapons) + int ent = FindEntityByClassname(-1, "tf_dropped_weapon"); + while (ent != -1) { + RemoveEdict(ent); + ent = FindEntityByClassname(-1, "tf_dropped_weapon"); + } + return Plugin_Handled; +} + +public int AussieHdlr(Menu menu, MenuAction action, int client, int p2) +{ + switch (action) + { + case MenuAction_Select: + { + char sel[64]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + if (StrEqual(sel, "sw")) { + // Invert the value of the boolean. + Australium[client] = !Australium[client]; + + // Check its state and reply to the user accordingly. + Australium[client] ? CPrintToChat(client, "{green}[VIP] {white}Se habilitó el modo {gold}Australium.") : CPrintToChat(client, "{red}[VIP] {white}Se deshabilitó el modo {gold}Australium."); + return 0; + } + + if (StrEqual(sel, "ase")) { + AustraliumSE[client] = !AustraliumSE[client]; + + OpenAustraliumMenu(client); + return 0; + } + + if (StrEqual(sel, "gold")) { + clientGold[client]++; + + if (clientGold[client] > 2 || clientGold[client] < 0) + clientGold[client] = 0; + + OpenAustraliumMenu(client); + return 0; + } + } + } + return 0; +} + + + +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// +// Below this section lie my beautiful (horrendous) custom functions.// +// Laughing is allowed. // +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// + + + +// GiveAustralium() - Gives an australium weapon to the player. +bool GiveAustralium(int client, char[] classname, int index, int level, int quality, int slot) +{ + // Oh boy, custom items. They're a delight to see them work but an ass to code them in. + // Let's begin with this shithole. + + // First of all these shitty flags. + int flags = OVERRIDE_ALL | FORCE_GENERATION | PRESERVE_ATTRIBUTES; + + // Create the infamous weapon handle. + Handle weapon = TF2Items_CreateItem(flags); + // If it fails, stop there! + if (weapon == INVALID_HANDLE) + return false; + + // Set the parsed info through the function, easy stuff right? + TF2Items_SetClassname(weapon, classname); + + int fId = StockToStrange(index); + if (fId < 0) + fId = index; + + PrintToChatAll("%d", fId); + + TF2Items_SetItemIndex(weapon, fId); + + if ((fId == 169 || index == 169) && (TF2_GetPlayerClass(client) != TFClass_Engineer)) + TF2Items_SetItemIndex(weapon, 1071); + + TF2Items_SetLevel(weapon, level); + TF2Items_SetQuality(weapon, quality); + + // Since aussies only need 3 attributes, we'll utilize only those. + if (!IsSpecialWeapon(fId)) + TF2Items_SetNumAttributes(weapon, 3); + else + TF2Items_SetNumAttributes(weapon, 2); + + // Time to assign these attributes to our item. + if (!IsSpecialWeapon(fId)) { + for (int i = 0; i < sizeof(AttribId); i++) + TF2Items_SetAttribute(weapon, i, AttribId[i], 1.0); + } + else { + TF2Items_SetAttribute(weapon, 0, 542, 0.0); + TF2Items_SetAttribute(weapon, 1, 150, 1.0); + } + + // The item is ready, let's remove the weapon slot to give the new weapon. + TF2_RemoveWeaponSlot(client, slot); + + // Now, let's give it to them! + int entity = TF2Items_GiveNamedItem(client, weapon); + + // shh.... don't pay attention to this it isn't important... + SetEntProp(entity, Prop_Send, "m_bValidatedAttachedEntity", 1, 1); + + // Give it to our lucky boi. + EquipPlayerWeapon(client, entity); + + // Mission success. + CloseHandle(weapon); + return true; +} + +// OpenAustraliumMenu() - Opens an Australium preference menu. +void OpenAustraliumMenu(int client) +{ + Menu menu = CreateMenu(AussieHdlr); + + SetMenuTitle(menu, "--Australium Mode--"); + + AddMenuItem(menu, "---", "Armas Especiales: Golden Pan, Saxxy y Golden Wrench.", ITEMDRAW_DISABLED); + + Australium[client] ? AddMenuItem(menu, "sw", "Modo Australium: ON") : AddMenuItem(menu, "sw", "Modo Australium: OFF"); + + AustraliumSE[client] ? AddMenuItem(menu, "ase", "¿Siempre dar armas especiales? (SI)") : AddMenuItem(menu, "ase", "¿Siempre dar armas especiales? (NO)"); + + char goldWeapon[64]; + if (clientGold[client] > -1) + Format(goldWeapon, sizeof(goldWeapon), "Arma Preferida: %s", goldNames[clientGold[client]]); + + AddMenuItem(menu, "gold", goldWeapon, (AustraliumSE[client] ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED)); + + SetMenuExitButton(menu, true); + + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +// IsAustralizable() - cool name xd. Checks if the weapon currently equipped can be australium. +bool IsAustralizable(int id) +{ + // Time to check! + switch (id) { + case 13, 200, 45, // Scout + 18, 205, 228, // Soldier + 21, 208, 38, // Pyro + 19, 206, 20, 207, 132, // Demoman + 15, 202, 424, // Heavy + 141, 7, 197, // Engineer + 36, 29, 211, // Medic + 14, 201, 16, 203, // Sniper + 61, 4, 194, // Spy + 264: // Frying Pan (Multi-Class) + { + return true; + } + } + return false; +} + +// IsSpecialWeapon() - cool other name, checks if the weapon is not in the "special" weapon list +bool IsSpecialWeapon(int id) +{ + return ((id == 1071) || (id == 169) || (id == 423)); +} + +// StockToStrange() - Changes the Stock weapon ID to its Strange variant. +int StockToStrange(int id) +{ + switch (id) { + case 13: + return 200; + case 18: + return 205; + case 21: + return 208; + case 19: + return 206; + case 20: + return 207; + case 15: + return 202; + case 7: + return 197; + case 29: + return 211; + case 14: + return 201; + case 16: + return 203; + case 4: + return 194; + case 264: + return 1071; + } + return -1; +} \ No newline at end of file diff --git a/scripting/vip-killstreak.sp b/scripting/vip-killstreak.sp new file mode 100644 index 0000000..a3a60a7 --- /dev/null +++ b/scripting/vip-killstreak.sp @@ -0,0 +1,469 @@ +#include +#include +#include + +// Custom made "class" (actually a methodmap) to manage user selections. +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0.0" + +public Plugin myinfo = +{ + name = "[VIP Module] Killstreak Manager", + author = "Lucas 'puntero' Maza", + description = "Gives the ability for users to customize their weapons' killstreak effects.", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/member.php?u=213425" +}; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + EngineVersion g_engineversion = GetEngineVersion(); + if (g_engineversion != Engine_TF2) + SetFailState("This plugin was made for use with Team Fortress 2 only."); +} + +///////////////////// +// GLOBAL DECLARES // +///////////////////// + +// Per client stringmap +KsClient Ks[MAXPLAYERS + 1]; + +// Sheen names array +char Sheens[8][32] = { + "None", "Team-Shine", "Deadly Daffodil", "Manndarin", "Mean Green", "Agonizing Emerald", "Villainous Violet", "Hot Rod" +}; + +// Killstreaker names array +char Streakers[8][32] = { + "None", "Fire Horns", "Cerebral Discharge", "Tornado", "Flames", "Singularity", "Incinerator", "Hypno-Beam" +}; + +///////////////////// +///////////////////// +///////////////////// + +public void OnPluginStart() +{ + RegAdminCmd("sm_killstreak", CMD_Ks, ADMFLAG_RESERVATION, "Opens the killstreak configuration menu."); + RegAdminCmd("sm_ks", CMD_Ks, ADMFLAG_RESERVATION, "Opens the killstreak configuration menu."); + RegAdminCmd("sm_killstreaks", CMD_Ks, ADMFLAG_RESERVATION, "Opens the killstreak configuration menu."); + // Various ways of invoking the command. For user commodity ;) + + HookEvent("post_inventory_application", OnItems); + + HookEvent("player_spawn", OnItems); + // Event for item "refresh" detection. + // Hook to the items given / resupply event, this manages when to give the user their weapons. + // Also hook into the player_spawn event just in case post_inventory_application isn't called (happens sometimes) +} + +public void OnMapStart() +{ + for (int i = 0; i < sizeof(Ks); i++) + delete Ks[i]; +} +public void OnClientPostAdminCheck(int client) +{ + // We need to create a KsClient object for clients that join. + // I planned to do this only for the ones who have CUSTOM1 flag access, but because of admin cache reloads i'll just assign it to everyone. + // Also initialize values so we avoid any errors. + Ks[client] = new KsClient(); + Ks[client].Initialize(); +} + +public void OnClientDisconnect(int client) +{ + // If he has disconnected, empty this object slot so we don't occupy any extra memory. + delete Ks[client]; +} + +public Action CMD_Ks (int client, int args) +{ + // Open up the main menu for the client. + GenerateMenu(client); +} + +public Action OnItems (Event event, char[] name, bool dontBroadcast) +{ + // Apply killstreak effects upon spawn or resupply touch. + CreateTimer(0.2, Apply, GetClientOfUserId(GetEventInt(event, "userid"))); +} + +public Action Apply (Handle timer, any client) +{ + // Callback for that timer on the event callback (duh) + + // ONLY APPLY KILLSTREAK EFFECTS IF THE PLAYER IS ALIVE + // Menus can be used even when dead, so we must make sure he doesn't fuck anything up on accident. + if (IsPlayerAlive(client) && IsClientInGame(client)) + ApplyKillstreak(client); +} + +public int MainHdlr (Menu menu, MenuAction action, int client, int p2) +{ + switch (action) + { + case MenuAction_Select: + { + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + if (StrEqual(sel, "kD", false) || + StrEqual(sel, "kE", false)) { + Ks[client].AllWeapons(!Ks[client].IsAllMode()); + + GenerateMenu(client); + } + else { + int slot = StringToInt(sel); + Ks[client].SetSlot(slot); + + GenerateWeaponMenu(client, slot); + } + } + } + return 0; +} + +public int WeaponHdlr (Menu menu, MenuAction action, int client, int p2) +{ + switch (action) + { + case MenuAction_Select: + { + // Fuck strings man, seriously. + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + if (StrEqual(sel, "type", false)) { + float type[3]; + Ks[client].GetType(type); + + int slot = Ks[client].GetSlot(); + + type[slot] += 1.0; + Ks[client].SetType((0.0 <= type[slot] <= 3.0) ? type[slot] : 0.0, slot); + + SetClientKillstreak(client, type[slot], slot); + + switch (RoundToFloor(type[slot])) { + case 0, 1, 2: { + if (IsPlayerAlive(client) && IsClientInGame(client)) + RemoveKillstreak(client, RoundToFloor(type[slot])); + } + } + + // ONLY APPLY KILLSTREAK EFFECTS IF THE PLAYER IS ALIVE + // Menus can be used even when dead, so we must make sure he doesn't fuck anything up on accident. + if (IsPlayerAlive(client)) + ApplyKillstreak(client); + + GenerateWeaponMenu(client, slot); + + return 0; + } + if (StrEqual(sel, "sheen", false)) + GenerateEffectsMenu(client); + if (StrEqual(sel, "streaker", false)) + GenerateEffectsMenu(client, true); + } + } + return 0; +} + +public int SheenHdlr (Menu menu, MenuAction action, int client, int p2) +{ + switch (action) + { + case MenuAction_Select: + { + // Fuck strings man, seriously. + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + int slot = Ks[client].GetSlot(); + + Ks[client].SetSheen(StringToFloat(sel), slot); + + // ONLY APPLY KILLSTREAK EFFECTS IF THE PLAYER IS ALIVE + // Menus can be used even when dead, so we must make sure he doesn't fuck anything up on accident. + if (IsPlayerAlive(client) && IsClientInGame(client)) + ApplyKillstreak(client); + + GenerateWeaponMenu(client, slot); + return 0; + } + } + return 0; +} + +public int StreakerHdlr (Menu menu, MenuAction action, int client, int p2) +{ + switch (action) + { + case MenuAction_Select: + { + // Fuck strings man, seriously. + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + int slot = Ks[client].GetSlot(); + + Ks[client].SetStreaker(StringToFloat(sel), slot); + + // ONLY APPLY KILLSTREAK EFFECTS IF THE PLAYER IS ALIVE + // Menus can be used even when dead, so we must make sure he doesn't fuck anything up on accident. + if (IsPlayerAlive(client) && IsClientInGame(client)) + ApplyKillstreak(client); + + GenerateWeaponMenu(client, slot); + return 0; + } + } + return 0; +} + + + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + // Below this section lie my beautiful (horrendous) custom functions.// + // Laughing is allowed. // + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + + +// SetClientKillstreak() - Sets the current clients' killstreak preferences according to the type. +// @arg int client - Represents the client integer. +// @arg float type - The type of Killstreak the client chose. +// @noreturn +void SetClientKillstreak(int client, float type, int slot = 0) +{ + // This function all it does is reset Sheen values and Killstreaker values whenever the client switches types of killstreaks. + // Since we support All Weapons Mode, there is a different method for either. + // Makes this block kind of repetitive but it doesn't matter. + + // First check if the client has All Weapons Mode enabled. + if (Ks[client].IsAllMode()) { + // If he does, we'll loop through all the slots and set them to 0. + for (int i = 0; i < 3; i++) { + // Use as an Integer to avoid 0.0 or 1.0 + switch (RoundToFloor(type)) { + case 0, 1: { + // If he has no Killstreak enabled or a Normal Killstreak, empty Sheen and Killstreaker values. + Ks[client].SetSheen(0.0, i); + Ks[client].SetStreaker(0.0, i); + } + case 2: + // If he has a Specialized Killstreak, empty only the Sheen value. + Ks[client].SetStreaker(0.0, i); + } + } + } + // Same goes here, except it's just for one specific slot. + else { + switch (RoundToFloor(type)) { + case 0, 1: { + Ks[client].SetSheen(0.0, slot); + Ks[client].SetStreaker(0.0, slot); + } + case 2: + Ks[client].SetStreaker(0.0, slot); + } + } +} +// GenerateMenu() - Generates a menu with the client's weapons, and displays it to them. This allows them to choose individual weapon effects. +void GenerateMenu(int client) +{ + Menu menu = CreateMenu(MainHdlr); + + SetMenuTitle(menu, "Killstreak Manager"); + + bool isAll = Ks[client].IsAllMode(); + + if (!isAll) { + AddMenuItem(menu, "0", "Slot primario"); + AddMenuItem(menu, "1", "Slot secundario"); + AddMenuItem(menu, "2", "Slot melee"); + } + else + AddMenuItem(menu, "0", "Todas las Armas"); + + AddMenuItem(menu, "-", "-----------------", ITEMDRAW_DISABLED); + + isAll ? AddMenuItem(menu, "kD", "Aplicar en todo: Sí") : AddMenuItem(menu, "kE", "Aplicar en todo: No"); + + AddMenuItem(menu, "---", "Created by puntero @ 2020.", ITEMDRAW_DISABLED); + + SetMenuExitButton(menu, true); + + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +// GenerateWeaponMenu() - Generates a menu that lets you see the details of the selected weapon, and set them accordingly. +void GenerateWeaponMenu(int client, int slot) +{ + Menu menu = CreateMenu(WeaponHdlr); + + char title[20]; + bool isAll = Ks[client].IsAllMode(); + + if (!isAll) { + switch (slot) { + case 0: + Format(title, sizeof(title), "Arma Primaria"); + case 1: + Format(title, sizeof(title), "Arma Secundaria"); + case 2: + Format(title, sizeof(title), "Arma Melee"); + } + } + else + Format(title, sizeof(title), "Todas las Armas"); + + SetMenuTitle(menu, "Configurando: %s", title); + + float type[3], sheen[3], streaker[3]; + char typeStr[64], sheenStr[64], streakerStr[64]; + Ks[client].GetType(type); + Ks[client].GetSheen(sheen); + Ks[client].GetStreaker(streaker); + + switch (RoundToFloor(type[slot])) { + case 0: + Format(typeStr, sizeof(typeStr), "Killstreak: Ninguno"); + case 1: + Format(typeStr, sizeof(typeStr), "Killstreak: Normal"); + case 2: + Format(typeStr, sizeof(typeStr), "Killstreak: Specialized"); + case 3: + Format(typeStr, sizeof(typeStr), "Killstreak: Professional"); + } + AddMenuItem(menu, "type", typeStr); + + /*if () { + Format(sh, sizeof(sh), "Sheen: %s", All ? Sheens[RoundToFloor(KsAllVals[client][1])] : Sheens[RoundToFloor(KsFx[client][slot][0])]); + AddMenuItem(menu, "sheen", sh); + + if ((All ? RoundToFloor(KsAllVals[client][0]) : KsSlots[client][slot]) > 2) { + Format(st, sizeof(st), "Killstreaker: %s", All ? Streakers[(KsAllVals[client][2] > 2000) ? (RoundToFloor(KsAllVals[client][2]) - 2001) : RoundToFloor(KsAllVals[client][2])] : Streakers[(KsFx[client][slot][1] > 2000) ? (RoundToFloor(KsFx[client][slot][1]) - 2001) : RoundToFloor(KsFx[client][slot][1])]); + AddMenuItem(menu, "streaker", st); + } + } old shitty code, ignore this shithole */ + + if (type[slot] > 1.0) { + Format(sheenStr, sizeof(sheenStr), "Sheen: %s", Sheens[RoundToFloor(sheen[slot])]); + AddMenuItem(menu, "sheen", sheenStr); + + if (type[slot] > 2.0) { + Format(streakerStr, sizeof(streakerStr), "Killstreaker: %s", Streakers[RoundToFloor((streaker[slot] > 2000) ? (streaker[slot] - 2001) : streaker[slot])]); + AddMenuItem(menu, "streaker", streakerStr); + } + } + + AddMenuItem(menu, "-", "----------------------------------", ITEMDRAW_DISABLED); + + SetMenuExitButton(menu, true); + + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +// GenerateEffectsMenu() - This generates the menus utilized to select effects on weapons. +void GenerateEffectsMenu(int client, bool showStr = false) +{ + ///////////////////////////////////////////////// + // SHEENS MENU + Menu m1 = CreateMenu(SheenHdlr); + + SetMenuTitle(m1, "Seleccionar Sheen"); + + for (int i = 0; i < sizeof(Sheens); i++) { + // SourcePawn strings are a pain in the ass to manage + char info[32]; + Format(info, sizeof(info), "%d", i); + + AddMenuItem(m1, info, Sheens[i]); + } + + SetMenuExitButton(m1, true); + + if (!showStr) + DisplayMenu(m1, client, MENU_TIME_FOREVER); + ///////////////////////////////////////////////// + // KILLSTREAKER MENU + Menu m2 = CreateMenu(StreakerHdlr); + + SetMenuTitle(m2, "Selecionar Killstreaker"); + + for (int i = 2001; i < (sizeof(Streakers) + 2001); i++) { + char info[32]; + Format(info, sizeof(info), "%d", i); + + AddMenuItem(m2, info, Streakers[i - 2001]); + } + + SetMenuExitButton(m2, true); + + if (showStr) + DisplayMenu(m2, client, MENU_TIME_FOREVER); + ///////////////////////////////////////////////// +} + +// ApplyKillstreak() - Applies corresponding killstreak effects on each weapon indicated. +void ApplyKillstreak(int client) +{ + int entity[3] = INVALID_ENT_REFERENCE; + + float types[3], sheens[3], streakers[3]; + Ks[client].GetType(types); + Ks[client].GetSheen(sheens); + Ks[client].GetStreaker(streakers); + + for (int i = 0; i < sizeof(entity); i++) { + entity[i] = GetPlayerWeaponSlot(client, i); + + if (entity[i] != INVALID_ENT_REFERENCE) { + if (types[i] > 0.0) { + TF2Attrib_SetByDefIndex(entity[i], 2025, types[i]); + if (types[i] > 1.0) { + TF2Attrib_SetByDefIndex(entity[i], 2014, sheens[i]); + if (types[i] > 2.0) + TF2Attrib_SetByDefIndex(entity[i], 2013, streakers[i]); + } + } + } + } +} + +// RemoveKilltreak() - Removes Killstreak effects off clients depending on their type. +void RemoveKillstreak(int client, int type) +{ + int entity[3] = INVALID_ENT_REFERENCE; + + for (int i = 0; i < sizeof(entity); i++) { + entity[i] = GetPlayerWeaponSlot(client, i); + + if (entity[i] != INVALID_ENT_REFERENCE) { + int ids[3] = { 2014, 2013, 2025 }, len = 0; + switch (type) { + case 0: + len = 3; + case 1: + len = 2; + case 2: + len = 1; + } + + for (int x = 0; x < len; x++) { + TF2Attrib_SetByDefIndex(entity[i], ids[x], 0.0); + TF2Attrib_RemoveByDefIndex(entity[i], ids[x]); + } + } + } +} \ No newline at end of file diff --git a/scripting/vip-paints.sp b/scripting/vip-paints.sp new file mode 100644 index 0000000..7d3abc1 --- /dev/null +++ b/scripting/vip-paints.sp @@ -0,0 +1,343 @@ +#include +#include +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "0x01" + +public Plugin myinfo = +{ + name = "[VIP Module] Hat Painter", + author = "Lucas 'puntero' Maza", + description = "Lets users paint their hats respectively.", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/member.php?u=213425" +}; + +enum struct PaintPlayer { + // This enum controls a player's painted hats status. + // A maximum of 3 painted hats can be used at a time. + + float values[3]; + int hats[3]; + // Temporary slot variable to keep track of selections during apply. + int tSlot; + + bool hasTeamPaint; + int teamIndex; + + float GetPaintForSlot(int slot) { + return this.values[slot]; + } + + int GetHatForSlot(int slot) { + return this.hats[slot]; + } + + void SetPaintForSlot(int slot, float paint) { + this.values[slot] = paint; + } + + void SetHatForSlot(int slot, int iItemDefinitionIndex) { + this.hats[slot] = iItemDefinitionIndex; + } + + int GetSlot() { + return this.tSlot; + } + + void SetSlot(int slot) { + this.tSlot = slot; + } +} + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + EngineVersion g_engineversion = GetEngineVersion(); + if (g_engineversion != Engine_TF2) + { + SetFailState("This plugin was made for use with Team Fortress 2 only."); + } +} + +// GLOBALS ///////////////////////////////////////////// + +// Per-Player PaintPlayer object, this allows a "smoother" management of the paints applied. +PaintPlayer PPlayer[MAXPLAYERS + 1]; + +// Global Regeneration SDKCall +Handle hRegen = INVALID_HANDLE; + +// Networkable Server Offsets (used for regen) +int clipOff; +int ammoOff; + +// Paints menu. Generated ONCE. +Menu PaintsMenu; + +// Paint names. +char paintNames[30][64] = { + "No Paint", "Indubitably Green", "Zepheniah's Greed", "Noble Hatter's Violet", "Color No. 216-190-216", "A Deep Commitment to Purple", + "Mann Co. Orange", "Muskelmannbraun", "Peculiarly Drab Tincture", "Radigan Conagher Brown", "Ye Olde Rustic Colour", + "Australium Gold", "Aged Moustache Grey", "An Extraordinary Abundance of Tinge", "A Distinctive Lack of Hue", "Pink as Hell", + "A Color Similar to Slate", "Drably Olive", "The Bitter Taste of Defeat and Lime", "The Color of a Gentlemann's Business Pants", + "Dark Salmon Injustice", "A Mann's Mint", "After Eight", + "Team Spirit", "Operator's Overalls", "Waterlogged Lab Coat", "Balaclavas Are Forever", "An Air of Debonair", "The Value of Teamwork", + "Cream Spirit" +}; + +// Paint Values (individual colors) +int paintColors[23] = { + 0, 7511618, 4345659, 5322826, 14204632, 8208497, 13595446, 10843461, 12955537, 6901050, 8154199, 15185211, 8289918, 15132390, + 1315860, 16738740, 3100495, 8421376, 3329330, 15787660, 15308410, 12377523, 2960676 +}; + +// Paint Values (team colors) +int teamColors[7][2] = { + {12073019, 5801378}, {4732984, 3686984}, {11049612, 8626083}, {3874595, 1581885}, {6637376, 2636109}, {8400928, 2452877}, {12807213, 12091445} +}; + +//////////////////////////////////////////////////////// + +public void OnPluginStart() +{ + RegAdminCmd("sm_paint", CMD_Paint, ADMFLAG_RESERVATION, "Opens the Paint Menu."); + + Handle hGameConf = LoadGameConfigFile("sm-tf2.games"); + + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "Regenerate"); + PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain); + + hRegen = EndPrepSDKCall(); + // This piece of code makes an SDKCall for the Regenerate function inside the game's gamedata. + // Refreshes the entire player to ensure Unusuals take effect instantly. +} + +// Generates the paints menu once in a map lifetime. +public void OnMapStart() +{ + GeneratePaintsMenu(); +} + +// Clean that menu handle after it ended, prevents memory leaks? +public void OnMapEnd() +{ + delete PaintsMenu; +} + +public Action CMD_Paint(int client, int args) +{ + GenerateHatsMenu(client); + + return Plugin_Handled; +} + +public int MainHdlr(Menu menu, MenuAction action, int client, int p2) +{ + switch (action) { + case MenuAction_Select: { + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + int slot = p2, id = StringToInt(sel); + + PPlayer[client].SetSlot(slot); + PPlayer[client].SetHatForSlot(slot, id); + + DisplayMenu(PaintsMenu, client, MENU_TIME_FOREVER); + } + } + return 0; +} + +public int PaintMgr(Menu menu, MenuAction action, int client, int p2) +{ + switch (action) { + case MenuAction_Select: { + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + int slot = PPlayer[client].GetSlot(); + + bool isTeam = (StrContains(sel, "t", false) != -1); + + PPlayer[client].SetPaintForSlot(slot, isTeam ? -1.0 : StringToFloat(sel)); + + PPlayer[client].hasTeamPaint = isTeam; + PPlayer[client].teamIndex = (isTeam ? (p2 - 23) : 0); + + AdministerPaint(client); + } + } +} + +public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int iItemDefinitionIndex, Handle& hItem) +{ + if (StrEqual(classname, "tf_wearable", false) && !IsWearableWeapon(iItemDefinitionIndex)) { + for (int i = 0; i < 3; i++) { + int hatId = PPlayer[client].GetHatForSlot(i); + + if (hatId > 0 && iItemDefinitionIndex == hatId) { + float paint = PPlayer[client].GetPaintForSlot(i); + + int flags = OVERRIDE_ALL | FORCE_GENERATION; + + hItem = TF2Items_CreateItem(flags); + + TF2Items_SetClassname(hItem, classname); + TF2Items_SetItemIndex(hItem, iItemDefinitionIndex); + TF2Items_SetLevel(hItem, GetRandomInt(0, 126)); + + TF2Items_SetQuality(hItem, 6); + + int numAttribs = (paint > 0.0) ? 1 : (paint == -1.0) ? 2 : 0; + + if (numAttribs) { + TF2Items_SetNumAttributes(hItem, numAttribs); + + int attribs[2] = { 142, 261 }, paintIndex = PPlayer[client].teamIndex; + for (int x = 0; x < numAttribs; x++) + TF2Items_SetAttribute(hItem, x, attribs[x], (paint == -1.0) ? float(teamColors[paintIndex][x]) : paint); + + return Plugin_Changed; + } + } + continue; + } + } + return Plugin_Continue; +} + + + +// AdministerPaint() - Regenerates the player with the new paint applied on their selected wearables. +void AdministerPaint(int client) +{ + int ent = -1; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != INVALID_ENT_REFERENCE) { + if (GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity") == client) { + AcceptEntityInput(ent, "Kill"); + } + } + CreateTimer(0.06, PaintTimer, client); +} + +public Action PaintTimer(Handle timer, any client) +{ + int hp = GetClientHealth(client), clip[2], ammo[2]; + + clipOff = FindSendPropInfo("CTFWeaponBase", "m_iClip1"); + ammoOff = FindSendPropInfo("CTFPlayer", "m_iAmmo"); + + float uber = -1.0; + + if (TF2_GetPlayerClass(client) == TFClass_Medic) + uber = GetEntPropFloat(GetPlayerWeaponSlot(client, 1), Prop_Send, "m_flChargeLevel"); + + for (int i = 0; i < sizeof(clip); i++) { + int wep = GetPlayerWeaponSlot(client, i); + if (wep != INVALID_ENT_REFERENCE) { + int ammoOff2 = GetEntProp(wep, Prop_Send, "m_iPrimaryAmmoType", 1) * 4 + ammoOff; + + clip[i] = GetEntData(wep, clipOff); + ammo[i] = GetEntData(wep, ammoOff2); + } + } + + // Regenerate the player (SDK Call) + SDKCall(hRegen, client, 0); + + SetEntityHealth(client, hp); + if (uber > -1.0) + SetEntPropFloat(GetPlayerWeaponSlot(client, 1), Prop_Send, "m_flChargeLevel", uber); + + for (int i = 0; i < sizeof(clip); i++) { + int wep = GetPlayerWeaponSlot(client, i); + if (wep != INVALID_ENT_REFERENCE) { + int ammoOff2 = GetEntProp(wep, Prop_Send, "m_iPrimaryAmmoType", 1) * 4 + ammoOff; + + SetEntData(wep, clipOff, clip[i]); + SetEntData(wep, ammoOff2, ammo[i]); + } + } +} + +// GenerateHatsMenu() - Generates the first menu where the player's hats are listed +void GenerateHatsMenu(int client) +{ + Menu menu = CreateMenu(MainHdlr); + + SetMenuTitle(menu, "Painted Hats Manager"); + + int hat = -1, found = 0; + while ((hat = FindEntityByClassname(hat, "tf_wearable")) != -1) { + if ((hat != INVALID_ENT_REFERENCE) && (GetEntPropEnt(hat, Prop_Send, "m_hOwnerEntity") == client)) { + int id = GetEntProp(hat, Prop_Send, "m_iItemDefinitionIndex"); + + char query[64], idStr[16]; + Format(query, sizeof(query), "SELECT capability FROM tf2idb_capabilities WHERE id = ?"); + IntToString(id, idStr, sizeof(idStr)); + + Handle arguments = CreateArray(sizeof(idStr)); + PushArrayString(arguments, idStr); + + DBStatement result = TF2IDB_CustomQuery(query, arguments, 16); + + if (result != INVALID_HANDLE) { + while (SQL_FetchRow(result)) { + char capability[32]; + SQL_FetchString(result, 0, capability, sizeof(capability)); + + if (StrEqual(capability, "paintable")) { + char hatName[42]; + TF2IDB_GetItemName(id, hatName, sizeof(hatName)); + + AddMenuItem(menu, idStr, hatName); + } + } + found++; + } + } + } + + if (!found) + AddMenuItem(menu, "-", "No se encontraron hats compatibles.", ITEMDRAW_DISABLED); + + AddMenuItem(menu, "-", "- - - - - - - - - - - - - - - - - -", ITEMDRAW_DISABLED); + + AddMenuItem(menu, "-", "Uso: Selecciona tu hat equipado y la pintura a utilizar.", ITEMDRAW_DISABLED); + + SetMenuExitButton(menu, true); + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +// GeneratePaintsMenu() - Fills the Menu handle for paint values. +void GeneratePaintsMenu() +{ + PaintsMenu = CreateMenu(PaintMgr); + + PaintsMenu.SetTitle("Select a paint..."); + + for (int i = 0; i < sizeof(paintNames); i++) { + char valStr[42]; + (i < sizeof(paintColors)) ? IntToString(paintColors[i], valStr, sizeof(valStr)) : Format(valStr, sizeof(valStr), "t%d", i - 23); + + PaintsMenu.AddItem(valStr, paintNames[i]); + } + + PaintsMenu.ExitButton = true; +} + +// IsWearableWeapon() - Checks if the wearable is in a weapon slot (so we don't count it as a hat) +bool IsWearableWeapon(int id) +{ + switch (id) { + case 133, 444, 405, 608, 231, 642: + return true; + } + return false; +} \ No newline at end of file diff --git a/scripting/vip-system.sp b/scripting/vip-system.sp new file mode 100644 index 0000000..0a4fe9a --- /dev/null +++ b/scripting/vip-system.sp @@ -0,0 +1,160 @@ +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0.0" + +public Plugin myinfo = +{ + name = "VIP Management System", + author = "Lucas 'puntero' Maza", + description = "Private VIP System made for Prophet's server.", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/member.php?u=213425" +}; + +///////////////////// +// GLOBAL DECLARES // +///////////////////// + +// Maximum commands allowed +// You can modify this if you reaaaally need more commands. +// Default limit is 30 +#define MAX_VIP_COMMANDS 30 + +// Commands array +char Commands[MAX_VIP_COMMANDS][64]; + +// Config Path n' Handle +char CfgPath[PLATFORM_MAX_PATH]; +Handle CfgKv = INVALID_HANDLE; + +// Menus after loading config +// Main Menu +Menu VIPMenu; + +///////////////////// +///////////////////// +///////////////////// + +public void OnPluginStart() +{ + RegAdminCmd("sm_vipmenu", CMD_VIP, ADMFLAG_RESERVATION, "Opens up the VIP menu."); + RegAdminCmd("sm_vip", CMD_VIP, ADMFLAG_RESERVATION, "Opens up the VIP menu."); + RegAdminCmd("sm_donor", CMD_VIP, ADMFLAG_RESERVATION, "Opens up the VIP menu."); + RegAdminCmd("sm_donator", CMD_VIP, ADMFLAG_RESERVATION, "Opens up the VIP menu."); + // Just registering many ways of invoking the command, for convinience. + + BuildPath(Path_SM, CfgPath, sizeof(CfgPath), "configs/vip-system.cfg"); + // Used to get the full path to the config file so we can load it. + + LoadVIP(); + // Load dat config boi. +} + +public void OnMapStart() +{ + LoadVIP(); + // Reload the config on each map change, to prevent the menu from corrupting. +} + +public Action CMD_VIP (int client, int args) +{ + // If the menu is fine, we just show it to the player and we're done. + if (VIPMenu != INVALID_HANDLE) { + DisplayMenu(VIPMenu, client, MENU_TIME_FOREVER); + return Plugin_Handled; + } + + // If the menu is indeed wrong (which is impossible), we log an error message. + LogMessage("[VIP] ERROR: Config was loaded correctly, yet menu is invalid."); + return Plugin_Handled; +} + +public int MainVIPh (Menu menu, MenuAction action, int p1, int p2) +{ + // no yanderedev code here, we do this prestigiously + switch (action) + { + // Whenever the client selects something from the menu. + case MenuAction_Select: + { + char sel[3]; + + GetMenuItem(menu, p2, sel, sizeof(sel)); + int id = StringToInt(sel); + // Get the selected item ID (what i told you in the loading function we'll use later :3) + + // Now, since we have an array of commands each corresponding to each ID, we just execute the one the player selected! + ClientCommand(p1, Commands[id]); + // We're done here. + return 1; + } + } + return 0; +} + + + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + // Below this section lie my beautiful (horrendous) custom functions.// + // Laughing is allowed. // + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + + +// LoadVIP() - Loads the VIP config file. +void LoadVIP() +{ + // Check if the config file is where it should be. + if (!FileExists(CfgPath)) + SetFailState("[VIP] Config file wasn't found at %s. Plugin will not run.", CfgPath); + + // Validate the handle beforehand. + CfgKv = CreateKeyValues("VIP-System"); + + // Check if the config file is valid on parse. + if (!FileToKeyValues(CfgKv, CfgPath)) + SetFailState("[VIP] Invalid configuration file structure. Plugin will not run."); + + // Jump inside the structure. + if (KvGotoFirstSubKey(CfgKv)) { + // Just an easy int that tracks how many commands are there. If it exceeds MAX_VIP_COMMANDS, the plugin stops loading commands. + int foundCmds = 0; + + // Create the main menu (we only fill it ONCE) + VIPMenu = CreateMenu(MainVIPh); + SetMenuTitle(VIPMenu, "--VIP Menu--"); + + // Now load the menu! + do + { + char CmdName[64], ID[3]; + + // Section name is the sub-key value (what will be displayed as item in the menu) + KvGetSectionName(CfgKv, CmdName, sizeof(CmdName)); + // Get all attributes (there aren't many now but we'll work on it) + KvGetString(CfgKv, "command", Commands[foundCmds], sizeof(Commands[])); + + // We convert foundCmds to a string for internal usage. You'll see later why! + IntToString(foundCmds, ID, sizeof(ID)); + + // Add dat shit in the menu. + AddMenuItem(VIPMenu, ID, CmdName); + + // Increase it by one each loop, until it breaks. + foundCmds++; + } while (KvGotoNextKey(CfgKv) && (foundCmds < MAX_VIP_COMMANDS)); + // Loop through all the commands in the config, until there aren't any more or the limit is reached. + + // We're done loading it all in. + LogMessage("[VIP] Configuration file loaded correctly! %d commands are available.", foundCmds); + return; + } + // If we couldn't jump into the structure some medicine must be applied to the config file. + SetFailState("[VIP] Could not read the config's content. Make sure no bracket is left unclosed, plugin will not run."); +} \ No newline at end of file diff --git a/scripting/vip-unusual-glow.sp b/scripting/vip-unusual-glow.sp new file mode 100644 index 0000000..fc27174 --- /dev/null +++ b/scripting/vip-unusual-glow.sp @@ -0,0 +1,94 @@ +#include +#include +#include + +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0.0" + +public Plugin myinfo = +{ + name = "[VIP Module] Unusual Glow", + author = "Lucas 'puntero' Maza", + description = "Makes your player glow constantly.", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/member.php?u=213425" +}; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + EngineVersion g_engineversion = GetEngineVersion(); + if (g_engineversion != Engine_TF2) + { + SetFailState("This plugin was made for use with Team Fortress 2 only."); + } +} + +// Global Menu handle for effects. +Menu glowMenu; + +// Per-Player effect variable to hold which effect they've selected. +float clientEffect[MAXPLAYERS + 1] = 0.0; + +public void OnPluginStart() +{ + RegAdminCmd("sm_unuglow", CMD_UnuGlow, ADMFLAG_RESERVATION, "Opens the Unusual Glowing menu."); + RegAdminCmd("sm_glowme", CMD_UnuGlow, ADMFLAG_RESERVATION, "Opens the Unusual Glowing menu."); + + HookEvent("player_spawn", OnPlayerSpawn); +} + +public void OnMapStart() +{ + for (int i = 0; i < sizeof(clientEffect); i++) { + clientEffect[i] = 0.0; + } + + delete glowMenu; + glowMenu = CreateGlowMenu(); +} + +public void OnClientConnected(int client) +{ + clientEffect[client] = 0.0; +} + +public void OnClientDisconnect(int client) +{ + clientEffect[client] = 0.0; +} + +public Action OnPlayerSpawn(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(GetEventInt(event, "userid")); + + if (clientEffect[client] > 0.0) + SetGlow(client, clientEffect[client]); + + return Plugin_Continue; +} + +public Action CMD_UnuGlow(int client, int args) +{ + DisplayMenu(glowMenu, client, MENU_TIME_FOREVER); + return Plugin_Handled; +} + +public int glowHdlr(Menu menu, MenuAction action, int client, int p2) +{ + switch (action) { + case MenuAction_Select: { + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + float eff = StringToFloat(sel); + + SetGlow(client, eff); + CPrintToChat(client, "{unusual}[UnuGlow] {white}Tus efectos se aplicarán cuando respawnees."); + } + } + return 0; +} \ No newline at end of file diff --git a/scripting/vip-unusuals.sp b/scripting/vip-unusuals.sp new file mode 100644 index 0000000..1cdf9f6 --- /dev/null +++ b/scripting/vip-unusuals.sp @@ -0,0 +1,358 @@ +#include +#include +#include + +// Custom made "class" for unusual clients +#include +#include "custom-includes/paintcosmetics.inc" + +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "2.0.0" + +public Plugin myinfo = +{ + name = "[VIP Module] Unusual Manager", + author = "Lucas 'puntero' Maza", + description = "Gives the ability for users to customize unusual effects on their hats!", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/member.php?u=213425" +}; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + EngineVersion g_engineversion = GetEngineVersion(); + if (g_engineversion != Engine_TF2) + { + SetFailState("This plugin was made for use with Team Fortress 2 only."); + } +} + +///////////////////// +// GLOBAL DECLARES // +///////////////////// + +// Global Regeneration SDKCall Handle (Used to update items the player has) +Handle hRegen = INVALID_HANDLE; + +// Valid Equip Regions for Unusual items (to prevent weird unusual items such as an unusual flapjack) +char validRegions[8][24] = { "pyro_head_replacement", "hat", "face", "glasses", "beard", "whole_head", "lenses", "head_skin" }; + +// Per Client unusual effect chosen and ID +UnusualClient Unu[MAXPLAYERS + 1]; + +// Per Client painted hat information, first dimension is Client Index, second dimension is Hat Slot. +PaintedHat PPlayer[MAXPLAYERS + 1][3]; + +// Global effects Menu +// it's always static, so generate it only once. +Menu effMenu; + +// Networkable Server Offsets (used for regen) +int clipOff; +int ammoOff; + +///////////////////// +///////////////////// +///////////////////// + +public void OnPluginStart() +{ + RegAdminCmd("sm_unusual", CMD_Unusual, ADMFLAG_RESERVATION, "Opens the Unusual menu."); + RegAdminCmd("sm_unu", CMD_Unusual, ADMFLAG_RESERVATION, "Opens the Unusual menu."); + RegAdminCmd("sm_inusual", CMD_Unusual, ADMFLAG_RESERVATION, "Opens the Unusual menu."); + // Various ways of invoking the command. For user commodity ;) + + Handle hGameConf = LoadGameConfigFile("sm-tf2.games"); + + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "Regenerate"); + PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain); + + hRegen = EndPrepSDKCall(); + // This piece of code makes an SDKCall for the Regenerate function inside the game's gamedata. + // Refreshes the entire player to ensure Unusuals take effect instantly. + + EffectsMenu(); + // Generates the effects menu (with all the unusual effects available) +} + +public void OnMapStart() +{ + for (int i = 0; i < sizeof(Unu); i++) { + delete Unu[i]; + CloseHandle(Unu[i]); + } + + delete effMenu; + + EffectsMenu(); +} + +public void OnClientConnected(int client) +{ + // Generate a new UnusualClient handle for the player. + Unu[client] = new UnusualClient(); + Unu[client].Initialize(); +} + +public void OnClientDisconnect(int client) +{ + // Empty the old Unusual slot for new players joining. + delete Unu[client]; + CloseHandle(Unu[client]); +} + +public Action CMD_Unusual(int client, int args) +{ + GenerateHatsMenu(client); + return Plugin_Handled; +} + +public int MainHdlr(Menu menu, MenuAction action, int client, int p2) +{ + switch (action) { + case MenuAction_Select: { + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + int id = StringToInt(sel); + if (id > 0) { + Unu[client].SetSlot(p2); + Unu[client].SetId(id); + + DisplayMenu(effMenu, client, MENU_TIME_FOREVER); + } + } + } + return 0; +} + +public int EffectHdlr(Menu menu, MenuAction action, int client, int p2) +{ + switch (action) { + case MenuAction_Select: { + char sel[32]; + GetMenuItem(menu, p2, sel, sizeof(sel)); + + Unu[client].SetUnusual(StringToFloat(sel)); + + ForceChange(client); + } + } + return 0; +} + +public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int iItemDefinitionIndex, Handle& hItem) +{ + // This is where the magic happens :) + // First check if it's a hat and not any weapon or item. + if ((StrEqual(classname, "tf_wearable")) && !IsWearableWeapon(iItemDefinitionIndex) && IsHatUnusual(iItemDefinitionIndex)) { + for (int i = 0; i < 3; i++) { + float unusual = Unu[client].GetUnusual(i); + + int id = Unu[client].GetId(i); + if ((iItemDefinitionIndex == id) && (unusual > 0.0)) { + int flags = OVERRIDE_ALL | FORCE_GENERATION; + + hItem = TF2Items_CreateItem(flags); + + TF2Items_SetClassname(hItem, classname); + TF2Items_SetItemIndex(hItem, iItemDefinitionIndex); + TF2Items_SetLevel(hItem, 69); + + TF2Items_SetQuality(hItem, 5); + + TF2Items_SetNumAttributes(hItem, 4); + + TF2Items_SetAttribute(hItem, 0, 134, unusual); + + int attribs[3] = { 142, 261, 1004 }; + for (int j = 0; j < sizeof(attribs); j++) { + float paints[3]; + PPlayer[client][j].WriteValues(paints); + + if (PPlayer[client][j].hatIndex == iItemDefinitionIndex) { + for (int k = 0; k < sizeof(paints); k++) { + if (paints[k] > 0.0) + TF2Items_SetAttribute(hItem, k + 1, attribs[k], paints[k]); + } + } + } + + return Plugin_Changed; + } + continue; + } + } + return Plugin_Continue; +} + + + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + // Below this section lie my beautiful (horrendous) custom functions.// + // Laughing is allowed. // + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + + +// GenerateHatsMenu() - Generates the first menu where the player's hats are listed +void GenerateHatsMenu(int client) +{ + Menu menu = CreateMenu(MainHdlr); + + SetMenuTitle(menu, "Unusual Hat Manager"); + + int hat = -1, found = 0; + while ((hat = FindEntityByClassname(hat, "tf_wearable")) != -1) { + if ((hat != INVALID_ENT_REFERENCE) && (GetEntPropEnt(hat, Prop_Send, "m_hOwnerEntity") == client)) { + int id = GetEntProp(hat, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsHatUnusual(id)) { + char idStr[12], hatName[42]; + IntToString(id, idStr, sizeof(idStr)); + TF2IDB_GetItemName(id, hatName, sizeof(hatName)); + + AddMenuItem(menu, idStr, hatName); + found++; + } + } + } + if (!found) { + AddMenuItem(menu, "-", "No se encontraron hats compatibles.", ITEMDRAW_DISABLED); + } + + AddMenuItem(menu, "-", "Uso: Selecciona tu hat equipado y el efecto deseado.", ITEMDRAW_DISABLED); + + AddMenuItem(menu, "-", "- - - - - - - - - - - - - - - - - -", ITEMDRAW_DISABLED); + + SetMenuExitButton(menu, true); + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +// EffectsMenu() - Generates the effects menu. This is done always on start. +void EffectsMenu() +{ + effMenu = CreateMenu(EffectHdlr); + + SetMenuTitle(effMenu, "Seleccionar Efecto"); + + AddUnusuals(effMenu); + + SetMenuExitButton(effMenu, true); +} + +// ForceChange() - Forces an SDKCall on the player to get the Unusual effects to be applied instantly. +void ForceChange(int client) +{ + int ent = -1, hatsC = 0; + while ((ent = FindEntityByClassname(ent, "tf_wearable")) != INVALID_ENT_REFERENCE) { + if (client == GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity")) { + int itemDef = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex"); + + if (IsHatPaintable(itemDef)) { + float val[16]; + int ids[16]; + int amount = TF2Attrib_GetSOCAttribs(ent, ids, val); + + float paints[3]; + GetPaint(val, ids, amount, paints); + + for (int i = 0; i < 3; i++) + PPlayer[client][hatsC].values[i] = paints[i]; + PPlayer[client][hatsC].hatIndex = itemDef; + + hatsC++; + } + + AcceptEntityInput(ent, "Kill"); + } + } + + CreateTimer(0.06, ForceTimer, client); +} + +// ForceChange's timer callback +public Action ForceTimer(Handle timer, any client) +{ + int hp = GetClientHealth(client), clip[2], ammo[2]; + + clipOff = FindSendPropInfo("CTFWeaponBase", "m_iClip1"); + ammoOff = FindSendPropInfo("CTFPlayer", "m_iAmmo"); + + float uber = -1.0; + + if (TF2_GetPlayerClass(client) == TFClass_Medic) + uber = GetEntPropFloat(GetPlayerWeaponSlot(client, 1), Prop_Send, "m_flChargeLevel"); + + for (int i = 0; i < sizeof(clip); i++) { + int wep = GetPlayerWeaponSlot(client, i); + if (wep != INVALID_ENT_REFERENCE) { + int ammoOff2 = GetEntProp(wep, Prop_Send, "m_iPrimaryAmmoType", 1) * 4 + ammoOff; + + clip[i] = GetEntData(wep, clipOff); + ammo[i] = GetEntData(wep, ammoOff2); + } + } + + // Regenerate the player (SDK Call) + SDKCall(hRegen, client, 0); + + SetEntityHealth(client, hp); + if (uber > -1.0) + SetEntPropFloat(GetPlayerWeaponSlot(client, 1), Prop_Send, "m_flChargeLevel", uber); + + for (int i = 0; i < sizeof(clip); i++) { + int wep = GetPlayerWeaponSlot(client, i); + if (wep != INVALID_ENT_REFERENCE) { + int ammoOff2 = GetEntProp(wep, Prop_Send, "m_iPrimaryAmmoType", 1) * 4 + ammoOff; + + SetEntData(wep, clipOff, clip[i]); + SetEntData(wep, ammoOff2, ammo[i]); + } + } + + GenerateHatsMenu(client); +} + +// IsHatUnusual() - Retrieves wether the current worn hat CAN have an Unusual effect. +// @ int iItemDefinitionIndex - The Item Index for the hat being tested. +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// @return bool - True if the hat can be Unusual, false if not. +bool IsHatUnusual(int iItemDefinitionIndex) +{ + Handle region = TF2IDB_GetItemEquipRegions(iItemDefinitionIndex); + + if (region != INVALID_HANDLE) { + for (int i = 0; i < GetArraySize(region); i++) { + char regionStr[32]; + GetArrayString(region, i, regionStr, sizeof(regionStr)); + + // VALID EQUIP REGIONS FOR UNUSUALS: pyro_head_replacement, hat, face, glasses, beard, whole_head, lenses, head_skin + // Contained in the string array validRegions + for (int x = 0; x < sizeof(validRegions); x++) { + if (StrEqual(regionStr, validRegions[x], false)) { + delete region; + return true; + } + continue; + } + } + } + delete region; + return false; +} + +// IsWearableWeapon() - Checks if the wearable is in a weapon slot (so we don't count it as a hat) +bool IsWearableWeapon(int id) +{ + switch (id) { + case 133, 444, 405, 608, 231, 642: + return true; + } + return false; +} \ No newline at end of file