-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(wip): shotgun's special ability
- Loading branch information
Showing
3 changed files
with
252 additions
and
33 deletions.
There are no files selected for viewing
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
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
226 changes: 226 additions & 0 deletions
226
addons/sourcemod/scripting/freak_fortress_2/shotgun_special.sp
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
#include <sourcemod> | ||
#include <sdkhooks> | ||
#include <sdktools> | ||
#include <tf2attributes> | ||
#include <dhooks> | ||
#include <tf2utils> | ||
#include <freak_fortress_2> | ||
#include <ff2_modules/general> | ||
|
||
#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; | ||
} |