From aa73293ac15fc2b60fdf9353d29d15854212e2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nopied=E2=97=8E?= Date: Fri, 25 Aug 2023 03:44:18 +0900 Subject: [PATCH] feat(wip): shotgun's special ability --- addons/sourcemod/gamedata/potry.txt | 12 + .../sourcemod/scripting/freak_fortress_2.sp | 47 ++-- .../freak_fortress_2/shotgun_special.sp | 226 ++++++++++++++++++ 3 files changed, 252 insertions(+), 33 deletions(-) create mode 100644 addons/sourcemod/scripting/freak_fortress_2/shotgun_special.sp diff --git a/addons/sourcemod/gamedata/potry.txt b/addons/sourcemod/gamedata/potry.txt index 19eb71e..41ff7b3 100644 --- a/addons/sourcemod/gamedata/potry.txt +++ b/addons/sourcemod/gamedata/potry.txt @@ -19,6 +19,11 @@ "linux" "@_ZN18CTFWeaponBaseMelee5SmackEv" "windows" " " } + "CTFShotgun::PrimaryAttack" + { + "linux" "@_ZN10CTFShotgun13PrimaryAttackEv" + "windows" " " + } "CTFPlayer::CanPlayerMove" { "linux" "@_ZNK9CTFPlayer13CanPlayerMoveEv" @@ -182,6 +187,13 @@ "return" "void" "this" "entity" } + "CTFShotgun::PrimaryAttack" + { + "signature" "CTFShotgun::PrimaryAttack" + "callconv" "thiscall" + "return" "void" + "this" "entity" + } "CTFPlayer::CanPlayerMove" { "signature" "CTFPlayer::CanPlayerMove" diff --git a/addons/sourcemod/scripting/freak_fortress_2.sp b/addons/sourcemod/scripting/freak_fortress_2.sp index be87289..a17b29e 100644 --- a/addons/sourcemod/scripting/freak_fortress_2.sp +++ b/addons/sourcemod/scripting/freak_fortress_2.sp @@ -2840,17 +2840,17 @@ public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int iItemDe if(TF2_GetPlayerClass(client)==TFClass_Heavy) { - if(!StrContains(classname, "tf_weapon_shotgun", false)) - { - Handle itemOverride=PrepareItemHandle(item, _, _, "741 ; 50.0"); - //741: On Hit: Gain up to +%1$s health per attack - - if(itemOverride!=null) - { - item=itemOverride; - return Plugin_Changed; - } - } + // if(!StrContains(classname, "tf_weapon_shotgun", false)) + // { + // Handle itemOverride=PrepareItemHandle(item, _, _, "741 ; 50.0"); + // //741: On Hit: Gain up to +%1$s health per attack + + // if(itemOverride!=null) + // { + // item=itemOverride; + // return Plugin_Changed; + // } + // } if(!StrContains(classname, "tf_weapon_minigun", false)) { @@ -5909,28 +5909,9 @@ public Action OnTakeDamageAlive(int client, int& iAttacker, int& inflictor, floa } } - if(TF2_GetPlayerClass(iAttacker) == TFClass_Heavy) - { - if(StrContains(classname, "tf_weapon_shotgun")!=-1) - { - int maxHealth = GetEntProp(iAttacker, Prop_Data, "m_iMaxHealth"); - int currentHealth = GetEntProp(iAttacker, Prop_Data, "m_iHealth"); - if(currentHealth <= maxHealth * 2) - { - currentHealth += 50; - TF2Util_TakeHealth(iAttacker, 50.0, TAKEHEALTH_IGNORE_MAXHEALTH); - - if(currentHealth > maxHealth * 2) - { - SetEntProp(iAttacker, Prop_Data, "m_iHealth", maxHealth * 2); - } - } - } - - float charge = GetEntPropFloat(iAttacker, Prop_Send, "m_flRageMeter") + (damage * 0.056); - SetEntPropFloat(iAttacker, Prop_Send, "m_flRageMeter", - charge > 100.0 ? 100.0 : charge); - } + // float charge = GetEntPropFloat(iAttacker, Prop_Send, "m_flRageMeter") + (damage * 0.056); + // SetEntPropFloat(iAttacker, Prop_Send, "m_flRageMeter", + // charge > 100.0 ? 100.0 : charge); if(damagecustom==TF_WEAPON_SENTRY_BULLET) { diff --git a/addons/sourcemod/scripting/freak_fortress_2/shotgun_special.sp b/addons/sourcemod/scripting/freak_fortress_2/shotgun_special.sp new file mode 100644 index 0000000..4aae974 --- /dev/null +++ b/addons/sourcemod/scripting/freak_fortress_2/shotgun_special.sp @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define PLUGIN_VERSION "20230828" + +public Plugin myinfo= +{ + name="Freak Fortress 2: Shotgun Special", + author="Nopiedâ—Ž", + description="FF2: Shotgun's special abilities", + version=PLUGIN_VERSION, +}; + +#define min(%1,%2) (((%1) < (%2)) ? (%1) : (%2)) +#define max(%1,%2) (((%1) > (%2)) ? (%1) : (%2)) + +#define FOREACH_PLAYER(%1) for(int %1 = 1; %1 <= MaxClients; %1++) + +enum +{ + Shotgun_Normal = 0, + Shotgun_Vampire, + + ShotgunType_MAX +}; + +bool g_bShotgunFired = false; +int g_iCurrentShotgunType = Shotgun_Normal; + +int g_iPlayerShotgunType[MAXPLAYERS + 1]; + + +public void OnPluginStart() +{ + GameData gamedata = new GameData("potry"); + CreateDynamicDetour(gamedata, "CTFShotgun::PrimaryAttack", DHookCallback_PrimaryAttack_Pre); +} + +static void CreateDynamicDetour(GameData gamedata, const char[] name, DHookCallback callbackPre = INVALID_FUNCTION, DHookCallback callbackPost = INVALID_FUNCTION) +{ + DynamicDetour detour = DynamicDetour.FromConf(gamedata, name); + if (detour) + { + if (callbackPre != INVALID_FUNCTION) + detour.Enable(Hook_Pre, callbackPre); + + if (callbackPost != INVALID_FUNCTION) + detour.Enable(Hook_Post, callbackPost); + } + else + { + LogError("Failed to create detour setup handle for %s", name); + } +} + +public MRESReturn DHookCallback_PrimaryAttack_Pre(int weapon) +{ + int owner = GetEntPropEnt(weapon, Prop_Send, "m_hOwnerEntity"); + if(!IsValidClient(owner)) return MRES_Ignored; + + ShotgunAbility_Ready(g_iPlayerShotgunType[owner]); + switch(g_iCurrentShotgunType) + { + case Shotgun_Vampire: + { + ShotgunVampire_Init(owner); + } + } + + return MRES_Ignored; +} + +public void OnClientPostAdminCheck(int client) +{ + g_iPlayerShotgunType[client] = Shotgun_Normal; + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); +} + +// public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float vel[3], const float angles[3], int weapon, int subtype, int cmdnum, int tickcount, int seed, const int mouse[2]) +// { +// HealOnHit = false; +// } + +public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& newWeapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) +{ + if(!IsValidClient(client) || IsBoss(client)) return Plugin_Continue; + + int weapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); + if(IsValidEntity(weapon)) + { + // Is buttons called only once? + if((buttons & IN_ATTACK2) > 0 && IsShotgun(weapon)) + { + g_iPlayerShotgunType[client] = ++g_iPlayerShotgunType[client] % ShotgunType_MAX; + + // TODO: Sound + } + + // float attackTime = GetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack"); + // if(attackTime < GetGameTime() + // && (buttons & IN_ATTACK2) > 0) + // { + // if(IsShotgun(weapon)) + // { + // buttons &= ~IN_ATTACK2; + // buttons |= IN_ATTACK; + // bChanged = true; + // } + // } + } + + return Plugin_Continue; +} + +public void FF2_OnCalledQueue(FF2HudQueue hudQueue, int client) +{ + int weapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); + + // NOTE: IsShotgun is quite heavy, on this call. + if(!IsValidEntity(weapon) || !IsShotgun(weapon)) return; + + FF2HudDisplay hudDisplay = null; + char text[60]; + hudQueue.GetName(text, sizeof(text)); + + if(StrEqual(text, "Player Additional")) + { + // TODO: + if(g_iPlayerShotgunType[client] == Shotgun_Vampire) + { + Format(text, sizeof(text), "Shotgun Vampire"); + hudDisplay = FF2HudDisplay.CreateDisplay("Shotgun Special", text); + hudQueue.PushDisplay(hudDisplay); + } + } +} + +public Action OnTakeDamageAlive(int client, int& iAttacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3], int damagecustom) +{ + if(!g_bShotgunFired) return Plugin_Continue; + + bool bChanged = false; + + switch(g_iCurrentShotgunType) + { + case Shotgun_Vampire: + { + bChanged = true; + damagetype = DMG_BULLET; + + static float healMaxCap = 30.0; + int maxHealth = TF2Util_GetPlayerMaxHealthBoost(iAttacker, false, false), + currentHealth = GetEntProp(iAttacker, Prop_Data, "m_iHealth"); + + float realDamage = damage; + if(TF2_IsPlayerInCondition(iAttacker, TFCond_Buffed)) + realDamage *= 1.35; + + float heal = min(healMaxCap, realDamage); + + currentHealth += RoundFloat(heal); + if(currentHealth > maxHealth) + heal -= currentHealth - maxHealth; + + TF2Util_TakeHealth(iAttacker, heal, TAKEHEALTH_IGNORE_MAXHEALTH); + } + } + + return bChanged ? Plugin_Changed : Plugin_Continue; +} + +// On Fired +void ShotgunVampire_Init(int client) +{ + ShotgunAbility_Ready(Shotgun_Vampire); + float tickInterval = 0.1; + // GetTickInterval() is sometimes broken + + TF2Attrib_AddCustomPlayerAttribute(client, "crits_become_minicrits", 1.0, tickInterval); + TF2Attrib_AddCustomPlayerAttribute(client, "damage penalty", 0.5, tickInterval); +} + +public void CancelFF2WeaponAbility(int client) +{ + g_bShotgunFired = false; +} + +void ShotgunAbility_Ready(int type) +{ + g_bShotgunFired = true; + g_iCurrentShotgunType = type; +} + +bool IsShotgun(int weapon) +{ + char classname[64]; + GetEntityClassname(weapon, classname, sizeof(classname)); + + if((StrContains(classname, "tf_weapon_shotgun") != -1 + || StrContains(classname, "tf_weapon_sentry_revenge") != -1 + || StrContains(classname, "tf_weapon_scattergun") != -1) + // except this below. + // TODO: NOT classname specific, use attribute. + && !StrEqual(classname, "tf_weapon_shotgun_building_rescue")) + { + return true; + } + + return false; +} + +stock bool IsValidClient(int client) +{ + return (0 < client && client <= MaxClients && IsClientInGame(client)); +} + +stock bool IsBoss(int client) +{ + return FF2_GetBossIndex(client) != -1; +} \ No newline at end of file