Skip to content
naydef edited this page Dec 17, 2020 · 21 revisions

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.

Example

//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)));	
			}
		}
	}
}

Interacting with subplugins

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));
	}