diff --git a/addons/sourcemod/scripting/freak_fortress_2.sp b/addons/sourcemod/scripting/freak_fortress_2.sp index 9b0da6f..f9cc9b7 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)) { @@ -4502,24 +4502,80 @@ public Action BossTimer(Handle timer) return Plugin_Continue; } -public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) +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(!Enabled || CheckRoundState() != FF2RoundState_RoundRunning) return Plugin_Continue; // This also check Replay, SourceTV players. if(!IsValidClient(client) || !IsPlayerAlive(client)) return Plugin_Continue; + bool bChanged = false; // 이 구문은 HUD 표기와 관련 없이 능력이나 내부 연산에만 사용됨. if(IsBoss(client)) OnBossThink(client); -/* else - OnClientThink(client); -*/ + { + // OnClientThink(client); + int weapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); + if(IsValidEntity(weapon)) { + float attackTime = GetEntPropFloat(weapon, Prop_Send, "m_flNextPrimaryAttack"); + if(attackTime < GetGameTime() + && (buttons & IN_ATTACK2) > 0) + { + if(TryFF2WeaponAbility(client, weapon)) + { + buttons &= ~IN_ATTACK2; + buttons |= IN_ATTACK; + bChanged = true; + } + } + } + } + LastCharge[client] = GetEntPropFloat(client, Prop_Send, "m_flChargeMeter"); + RequestFrame(CancelFF2WeaponAbility, client); - return Plugin_Continue; + return bChanged ? Plugin_Changed : Plugin_Continue; +} + +// 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; +// } + +bool TryFF2WeaponAbility(int owner, int weapon) +{ + char classname[64]; + GetEntityClassname(weapon, classname, sizeof(classname)); + + // IT goes to freak_fortress_2/shotgun_special.sp + // if((StrContains(classname, "tf_weapon_shotgun") != -1 + // || StrContains(classname, "tf_weapon_sentry_revenge") != -1 + // || StrContains(classname, "tf_weapon_scattergun") != -1) + // && !StrEqual(classname, "tf_weapon_shotgun_building_rescue")) // This also shotgun + // { + + // ShotgunHeal_Init(owner); + // return true; + // } + + return false; +} + +public void CancelFF2WeaponAbility(int client) +{ + HealOnHit = false; +} + +void ShotgunHeal_Init(int client) +{ + HealOnHit = true; + float tickInterval = GetTickInterval(); + + // TF2Attrib_AddCustomPlayerAttribute(client, "crit_dmg_falloff", 1.0, tickInterval); + TF2Attrib_AddCustomPlayerAttribute(client, "crits_become_minicrits", 1.0, tickInterval); + TF2Attrib_AddCustomPlayerAttribute(client, "damage penalty", 0.5, tickInterval); } void OnBossThink(int client) @@ -5744,17 +5800,18 @@ public Action OnTakeDamageAlive(int client, int& iAttacker, int& inflictor, floa // rage: 62.3, drain: 54.6 float realDamage = damage * 1.53846; // player damamge -35% - if(damagetype & DMG_CRIT && TF2_IsPlayerCritBuffed(iAttacker)) + if(TF2_IsPlayerCritBuffed(iAttacker)) realDamage *= 3.0; - else if(damagetype & DMG_CRIT && TF2_IsPlayerInCondition(iAttacker, TFCond_Buffed)) + else if(TF2_IsPlayerInCondition(iAttacker, TFCond_Buffed)) realDamage *= 1.35; damage *= 0.3; int buffer = TF2Util_GetPlayerConditionProvider(client, TFCond_DefenseBuffed); if(!IsValidClient(buffer)) return Plugin_Changed; - float rage = GetEntPropFloat(buffer, Prop_Send, "m_flRageMeter"), - drain = rage - (max(0.0, (realDamage - damage) / 1200.0) * 100.0); + float multiplier = TF2Attrib_HookValueFloat(1.0, "mod_buff_duration", buffer), + rage = GetEntPropFloat(buffer, Prop_Send, "m_flRageMeter"), + drain = rage - (max(0.0, (realDamage - damage) / (1200.0 * multiplier)) * 100.0); SetEntPropFloat(buffer, Prop_Send, "m_flRageMeter", drain); // PrintToChatAll("realDamage: %.1f, damage: %.1f", realDamage, damage); @@ -5908,20 +5965,28 @@ public Action OnTakeDamageAlive(int client, int& iAttacker, int& inflictor, floa } } - if(StrContains(classname, "tf_weapon_shotgun")!=-1) + if(HealOnHit) { - 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); + bChanged = true; + damagetype = DMG_BULLET; - if(currentHealth > maxHealth * 2) - { - SetEntProp(iAttacker, Prop_Data, "m_iHealth", maxHealth * 2); - } - } + int maxHealth = TF2Util_GetPlayerMaxHealthBoost(iAttacker, false, false), + currentHealth = GetEntProp(iAttacker, Prop_Data, "m_iHealth"); + static float healMaxCap = 30.0; + + 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; + + // PrintToChatAll("maxHealth = %d", maxHealth, overhealCap); + + TF2Util_TakeHealth(iAttacker, heal, TAKEHEALTH_IGNORE_MAXHEALTH); } // float charge = GetEntPropFloat(iAttacker, Prop_Send, "m_flRageMeter") + (damage * 0.056); @@ -7612,11 +7677,27 @@ public void OnEntityCreated(int entity, const char[] classname) { SDKHook(entity, SDKHook_Spawn, OnItemSpawned); } - - if(StrEqual(classname, "tf_logic_koth")) + else if(StrEqual(classname, "tf_logic_koth")) { SDKHook(entity, SDKHook_Spawn, Spawn_Koth); } + else + { + SDKHook(entity, SDKHook_Spawn, OnEntitySpawned); + } + +} + +public void OnEntitySpawned(int entity) +{ + if(g_hBaseEntityMap != null // Before PluginStart + && IsEntNetworkable(entity)) + { + int entRef = EntIndexToEntRef(entity); + g_hBaseEntityMap.AddEntity(new FF2BaseEntity(entRef)); + + // g_hBaseEntityMap.PrintMe(); + } } public void OnEntityDestroyed(int entity) 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