I wrote HookMan a few years ago (which is the implementation of this; for my
forever WIP zombie plugin). It's about 250 lines and provides nearly all of the above (see attached). However, there's no security model that comes along with it. It wouldn't be hard to extend (albeit, the line count would double).
Protecting natives I don't think will pan out too well (unfortunately). Abstracting these calls can be dangerous in terms of performance. We're forced to incur either children storing auth states, or calling an auth plugin (doubling the initial overhead of the pawn native call).
The one basic rule I've tried to follow with my own set is a plugin should either be:
- An API Provider.
- Utilizer of an API.
- Middleware, utilize an API, and provide an API to others.
Here's an example of Middle-Ware.
PHP Code:
public RegisterHooks()
{
HM_RegisterHook(g_hOnClientKnockbackAddition, "ZME_OnClientKnockbackAddition");
HM_RegisterHook(g_hOnClientKnockbackMultiplication, "ZME_OnClientKnockbackMultiplication");
}
public ProcessThroughHooks(bool:bCheck, const String:sName[])
{
AddHookIfExists(bCheck, g_iGlobalBits, eTraceAttackPost, "ZME_OnClientTraceAttack_Post", ZME_OnClientTraceAttack_Post, sName);
}
public ZME_OnClientTraceAttack_Post(client, CliTeam, attacker, AttaTeam, inflictor, Float:damage, damagetype, ammotype, hitbox, hitgroup)
{
if (!attacker || CliTeam == eHuman || AttaTeam != eHuman || damage < 0.1)
{
return;
}
new Float:fKnockBack = 0.0;
if (!CallKnockBackForward(g_hOnClientKnockbackAddition, client, attacker, inflictor, hitgroup, damage, fKnockBack) || \
!CallKnockBackForward(g_hOnClientKnockbackMultiplication, client, attacker, inflictor, hitgroup, damage, fKnockBack) || fKnockBack == 0.0)
{
return;
}
decl Float:fClientLoc[3], Float:fAttackerLoc[3];
GetEntPropVector(client, Prop_Send, "m_vecOrigin", fClientLoc);
GetEntPropVector(attacker, Prop_Send, "m_vecOrigin", fAttackerLoc);
/* From Zombie:Reloaded */
MakeVectorFromPoints(fClientLoc, fAttackerLoc, fClientLoc);
NormalizeVector(fClientLoc, fClientLoc);
ScaleVector(fClientLoc, (fKnockBack * -1.0));
GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", fAttackerLoc);
AddVectors(fClientLoc, fAttackerLoc, fClientLoc);
TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, fClientLoc);
}
While the plugin applies attributes to the client, the plugin has no logic (brain) that makes any game based decisions. The only thing the plugin does is abstract calls from a more specific API.
Example of an End User (2):
PHP Code:
public ProcessThroughHooks(bool:bCheck, const String:sName[])
{
AddHookIfExists(bCheck, g_iGlobalBits, eZombifyClientPost, "ZME_OnZombifyClient_Post", ZME_OnZombifyClient_Post, sName);
AddHookIfExists(bCheck, g_iGlobalBits, eHookClassApply, "CM_OnClassUsed", CM_OnClassUsed, sName);
AddHookIfExists(bCheck, g_iGlobalBits, eHookClientManager, "CM_OnClassesChanged", CM_OnClassesChanged, sName);
}
public ZME_OnZombifyClient_Post(client, attacker, bool:bMother)
{
if (g_fShakeArray[client][2] == 0.0)
{
return;
}
new Handle:hShake = StartMessageOne("Shake", client);
if (hShake == INVALID_HANDLE)
{
return;
}
BfWriteByte(hShake, 0);
for (new i = (sizeof(g_fShakeArray) - 1); i >= 0; --i)
{
BfWriteFloat(hShake, g_fShakeArray[client][i]);
}
EndMessage();
}
public CM_OnClassUsed(iClient, iClass, iTeam)
{
new iArraySize = GetArraySize(g_hClassArray);
if (!(0 <= iClass < iArraySize))
{
CM_OnClassesChanged(CM_RegenerateClasses);
}
GetArrayArray(g_hClassArray, iClass, g_fShakeArray[iClient]);
}
public CM_OnClassesChanged(iClassNumber)
{
ClearArray(g_hClassArray);
if (iClassNumber == CM_RegenerateClasses)
{
iClassNumber = CM_NumberOfClasses();
}
if (!iClassNumber)
{
return;
}
decl String:sShakeString[64];
new Float:fInfo[sizeof(g_fShakeArray[])];
static const String:sScan[sizeof(fInfo)][] = { "ShakeDuration", "ShakeFrequency", "ShakeAmplitude" };
for (new i; i < iClassNumber; i++)
{
for (new k = (sizeof(sScan) - 1); k >= 0; --k)
{
if (!CM_GetString(i, sScan[k], sShakeString, sizeof(sShakeString)))
{
fInfo[k] = 0.0;
}
else
{
fInfo[k] = StringToFloat(sShakeString);
}
}
PushArrayArray(g_hClassArray, fInfo);
}
}
The `Brain` plugin provides no API, only a feature (screen shake on infection). If the plugin is removed, the only thing the user notices is the screen no longer shakes on infection. If the plugin is loaded mid-round, if someone is infected they should have their screen shake.
Lastly, the API plugin (1) should hold no hooks with other plugins, rather stock SM features. The single thing they do is provide an API to other plugins.
PHP Code:
public RegisterMyForwards()
{
if (g_iGlobalBits & eForwardsRegistered)
{
return;
}
HM_RegisterHook(g_hGlobalTeams, "ZME_OnClientArrayChangeTeam");
HM_RegisterHook(g_hSinglePlayerTeam, "ZME_OnClientChangeTeam");
HM_RegisterHook(g_hSinglePlayerTeamPost, "ZME_OnClientChangeTeam_Post");
g_iGlobalBits |= eForwardsRegistered;
}
public Native_OnChangeArrayClientTeam(Handle:plugin, numParams) /* iPlayer A / iTeam A / iCount */
{
decl iPlayerArray[MAXPLAYERS+1];
GetNativeArray(1, iPlayerArray, MAXPLAYERS+1);
new iMaxRead = GetNativeCell(2);
new iTeam = GetNativeCell(3);
for (new i; i < iMaxRead; i++)
{
g_iClientTeam[iPlayerArray[i]] = iTeam;
}
Call_StartForward(g_hGlobalTeams);
Call_PushArray(iPlayerArray, iMaxRead);
Call_PushCell(iMaxRead);
Call_PushCell(iTeam);
Call_Finish();
}
Hopefully this helps, and makes sense.