-
Notifications
You must be signed in to change notification settings - Fork 27
Subplugins
Subplugins are Sourcemod plugins that add additional functionality and boss abilities to Freak Fortress 2.
The possible slots are as follow:
Argument 0 - Ability Slot
-1 - When Boss lost life (if he has over 1)
0 - Rage
1 - Brave Jump
2 - Demopan's charge of targe, projectiles etc
3 - Weighdown
4 - Killed player (not used for sounds)
5 - Boss killed (not used for sounds)
6 - Boss backstabbed (not used for sounds)
7..9 - Not used.
In order to find out the slot number, we can use FF2_GetAbilityArgument/FF2_GetArg(I/F/S)
.
int slot=FF2_GetAbilityArgument(boss, this_plugin_name, ability_name, 0);
or
int slot=FF2_GetArgI(boss, this_plugin_name, ability_name, "slot");
But we don't really need to care about the slot number, what really matters is the ability that is being called.
....
if(!StrContains(ability_name, "rage_uber"))
{
Rage_Uber(ability_name, boss, slot);
}
else if(!StrContains(ability_name, "rage_cloneattack"))
{
Rage_Cloneattack(ability_name, boss);
}
....
Rage_Uber(const String:ability_name[], boss, slot)
{
//do something
}
Rage_Cloneattack(const String:ability_name[], boss)
{
//do something
}
Important: You SHOULD consider using StrContains
rather than strcmp
to allow multiple usages of the same ability in one config when AMS/Ability Manager is in use. Refer to the examples below for correct usage.
//Don't forget to rename the compiled plugin to *.ff2
#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <tf2_stocks>
#include <morecolors>
#include <freak_fortress_2>
#include <freak_fortress_2_subplugin> //Provides ability_name, this_plugin_name, etc.
#define PLUGIN_VERSION "Example"
new Handle:OnHaleRage=INVALID_HANDLE;
public Plugin:myinfo=
{
name="Very good subplugin",
author="You",
description="FF2: Abilities used by some bosses",
version=PLUGIN_VERSION,
};
public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
OnHaleRage=CreateGlobalForward("VSH_OnDoRage", ET_Hook, Param_FloatByRef);
return APLRes_Success;
}
public Action:FF2_OnAbility2(boss, const String:plugin_name[], const String:ability_name[], status)
{
new slot=FF2_GetAbilityArgument(client, this_plugin_name, ability_name, 0);
if(!slot) //In other words, check if it's the rage slot
{
if(!boss) //This will be going away soon, but just know that this checks to see if the boss exists
{
new Float:distance=FF2_GetRageDist(boss, this_plugin_name, ability_name);
new Float:newDistance=distance;
new Action:action=Plugin_Continue;
Call_StartForward(OnHaleRage);
Call_PushFloatRef(newDistance);
Call_Finish(action);
if(action!=Plugin_Continue && action!=Plugin_Changed)
{
return Plugin_Continue;
}
else if(action==Plugin_Changed)
{
distance=newDistance;
}
}
}
if(!StrContains(ability_name, "rage_speed"))
{
new client=GetClientOfUserId(FF2_GetBossUserId(boss));
SetEntDataFloat(client, FindSendPropInfo("CTFPlayer", "m_flMaxspeed"), 400.0, true);
TF2_AddCondition(client, TFCond_SpeedBuffAlly, FF2_GetAbilityArgumentFloat(boss, this_plugin_name, ability_name, 1, 5.0));
TF2_AddCondition(client, TFCond_HalloweenSpeedBoost, FF2_GetAbilityArgumentFloat(boss, this_plugin_name, ability_name, 1, 5.0));
CreateTimer(FF2_GetAbilityArgumentFloat(boss, this_plugin_name, ability_name, 1, 5.0), Timer_StopSpeed, boss);
}
else if(!StrContains(ability_name, "rage_friendlyfire"))
{
SetConVarBool(FindConvar("mp_friendlyfire"), true);
CreateTimer(FF2_GetAbilityArgumentFloat(boss, this_plugin_name, ability_name, 1, 5.0), Timer_StopFriendlyFire);
}
else if(!StrContains(ability_name, "rage_stun"))
{
Rage_Stun(ability_name, boss);
}
return Plugin_Continue;
}
public Action:Timer_StopSpeed(Handle:timer, any:boss)
{
SetEntDataFloat(GetClientOfUserId(FF2_GetBossUserId(boss)), FindSendPropInfo("CTFPlayer", "m_flMaxspeed"), 280.0, true);
return Plugin_Continue;
}
public Action:Timer_StopFriendlyFire(Handle:timer)
{
SetConVarBool(FindConvar("mp_friendlyfire"), false);
return Plugin_Continue;
}
Rage_Stun(const String:ability_name[], boss)
{
new Float:bossPosition[3], Float:targetPosition[3];
new Float:duration=FF2_GetAbilityArgumentFloat(boss, this_plugin_name, ability_name, 1, 5.0);
new client=GetClientOfUserId(FF2_GetBossUserId(boss));
new Float:distance=FF2_GetRageDist(boss, this_plugin_name, ability_name);
GetEntPropVector(client, Prop_Send, "m_vecOrigin", bossPosition);
for(new target=1; target<=MaxClients; target++)
{
if(IsClientInGame(target) && IsPlayerAlive(target) && GetClientTeam(target)!=BossTeam)
{
GetEntPropVector(target, Prop_Send, "m_vecOrigin", targetPosition);
if(!TF2_IsPlayerInCondition(target, TFCond_Ubercharged) && (GetVectorDistance(bossPosition, targetPosition)<=distance))
{
TF2_StunPlayer(target, duration, 0.0, TF_STUNFLAGS_GHOSTSCARE|TF_STUNFLAG_NOSOUNDOREFFECT, client);
CreateTimer(duration, RemoveEntity, EntIndexToEntRef(AttachParticle(target, "yikes_fx", 75.0)));
}
}
}
}
Additionally, one can check if a boss has a certain ability, even if it's from a different subplugin. For this, we use
FF2_HasAbility(boss, this_plugin_name, ability_name)
This is one way for plugins to interact.
Let's say you want to use a different version of 'rage_cloneattack' if the boss has 'rage_stun' in its abilities:
if(!StrContains(ability_name, "rage_cloneattack"))
{
if(FF2_HasAbility(boss, "default_abilities", "rage_stun"))
{
Rage_Clone2(ability_name, boss);
}
else
{
Rage_Clone(ability_name, boss);
}
}
Another way for subplugins to interact is the use of reflection. Due to load order concerns and compatibility reasons, natives may not work correctly as a way for subplugins to intereact. To use reflection, we require a 'public' analogue. Here is an example:
On subplugin A, which we'll call 'default_abilities_myedition':
public Rage_Stun(boss, client, Float:duration, Float:distance)
{
new Float:bossPosition[3], Float:targetPosition[3];
GetEntPropVector(client, Prop_Send, "m_vecOrigin", bossPosition);
for(new target=1; target<=MaxClients; target++)
{
if(IsClientInGame(target) && IsPlayerAlive(target) && GetClientTeam(target)!=BossTeam)
{
GetEntPropVector(target, Prop_Send, "m_vecOrigin", targetPosition);
if(!TF2_IsPlayerInCondition(target, TFCond_Ubercharged) && (GetVectorDistance(bossPosition, targetPosition)<=distance))
{
TF2_StunPlayer(target, duration, 0.0, TF_STUNFLAGS_GHOSTSCARE|TF_STUNFLAG_NOSOUNDOREFFECT, client);
CreateTimer(duration, RemoveEntity, EntIndexToEntRef(AttachParticle(target, "yikes_fx", 75.0)));
}
}
}
}
Now, for subplugin B, we want to call rage_stun from subplugin A. First we want to make sure the plugin exists, so we add this stock:
stock Handle:FindPlugin(String: pluginName[])
{
new String: buffer[256];
new String: path[PLATFORM_MAX_PATH];
new Handle: iter = GetPluginIterator();
new Handle: pl = INVALID_HANDLE;
while (MorePlugins(iter))
{
pl = ReadPlugin(iter);
Format(path, sizeof(path), "freaks/%s.ff2", pluginName);
GetPluginFilename(pl, buffer, sizeof(buffer));
if (!StrContains(buffer, path, false))
break;
else
pl = INVALID_HANDLE;
}
CloseHandle(iter);
return pl;
}
Now we make the reflective call on subplugin B:
stock Rage_Stun(boss, client, Float:duration, Float:distance)
{
new Handle:plugin = FindPlugin("default_abilities_myedition");
if (plugin != INVALID_HANDLE)
{
new Function:func = GetFunctionByName(plugin, "Rage_Stun");
if (func != INVALID_FUNCTION)
{
Call_StartFunction(plugin, func);
Call_PushCell(boss);
Call_PushCell(client);
Call_PushCell(duration);
Call_PushCell(distance);
Call_Finish();
}
else
LogError("ERROR: Unable to initialize default_abilities_myedition:Rage_Stun()");
}
else
{
LogError("ERROR: Unable to initialize default_abilities_myedition:Rage_Stun(). Make sure this plugin exists!");
}
}
For string values, use Call_PushString(stringname)
instead of Call_PushCell(value)
. If the ability is also meant to return a value, use Call_Finish(result)
.
Now, it'll use Rage_Stun
if it's able to find it, otherwise, it won't use it. This will allow backwards compatibility with older versions of subplugins.
if(!StrContains(ability_name, "rage_cloneattack"))
{
Rage_Clone(ability_name, boss);
Rage_Stun(boss, GetClientOfUserId(FF2_GetBossUserId(boss)), FF2_GetAbilityArgumentFloat(boss, this_plugin_name, ability_name, 1, 5.0), FF2_GetRageDist(boss, this_plugin_name, ability_name));
}