diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj
index 16c50aef..0e4575de 100644
--- a/core/msvc8/sourcemod_mm.vcproj
+++ b/core/msvc8/sourcemod_mm.vcproj
@@ -725,6 +725,10 @@
RelativePath="..\smn_float.cpp"
>
+
+
diff --git a/core/smn_functions.cpp b/core/smn_functions.cpp
new file mode 100644
index 00000000..b0f9154e
--- /dev/null
+++ b/core/smn_functions.cpp
@@ -0,0 +1,590 @@
+/**
+* ===============================================================
+* SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
+* ===============================================================
+*
+* This file is not open source and may not be copied without explicit
+* written permission of AlliedModders LLC. This file may not be redistributed
+* in whole or significant part.
+* For information, see LICENSE.txt or http://www.sourcemod.net/license.php
+*
+* Version: $Id$
+*/
+
+#include "sm_globals.h"
+#include "PluginSys.h"
+#include "ForwardSys.h"
+#include "HandleSys.h"
+
+HandleType_t g_GlobalFwdType = 0;
+HandleType_t g_PrivateFwdType = 0;
+
+static bool s_CallStarted = false;
+static ICallable *s_pCallable = NULL;
+static IPluginFunction *s_pFunction = NULL;
+static IForward *s_pForward = NULL;
+
+class ForwardNativeHelpers :
+ public SMGlobalClass,
+ public IHandleTypeDispatch
+{
+public:
+ void OnSourceModAllInitialized()
+ {
+ HandleAccess sec;
+
+ /* Set GlobalFwd handle access security */
+ g_HandleSys.InitAccessDefaults(NULL, &sec);
+ sec.access[HandleAccess_Read] = 0;
+ sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
+
+ /* Create 'GlobalFwd' handle type */
+ g_GlobalFwdType = g_HandleSys.CreateType("GlobalFwd", this, 0, NULL, &sec, g_pCoreIdent, NULL);
+
+ /* Private forwards are cloneable */
+ sec.access[HandleAccess_Clone] = 0;
+
+ /* Create 'PrivateFwd' handle type */
+ g_PrivateFwdType = g_HandleSys.CreateType("PrivateFwd", this, g_GlobalFwdType, NULL, &sec, g_pCoreIdent, NULL);
+ }
+
+ void OnSourceModShutdown()
+ {
+ g_HandleSys.RemoveType(g_PrivateFwdType, g_pCoreIdent);
+ g_HandleSys.RemoveType(g_GlobalFwdType, g_pCoreIdent);
+ }
+
+ void OnHandleDestroy(HandleType_t type, void *object)
+ {
+ IForward *pForward = static_cast(object);
+
+ g_Forwards.ReleaseForward(pForward);
+ }
+} g_ForwardNativeHelpers;
+
+
+/* Turn a public index into a function ID */
+inline funcid_t PublicIndexToFuncId(uint32_t idx)
+{
+ return (idx << 1) | (1 << 0);
+}
+
+/* Reset global function/forward call variables */
+inline void ResetCall()
+{
+ s_CallStarted = false;
+ s_pFunction = NULL;
+ s_pCallable = NULL;
+}
+
+static cell_t sm_GetFunctionByName(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t hndl = static_cast(params[1]);
+ HandleError err;
+ char *name;
+ uint32_t idx;
+ IPlugin *pPlugin;
+
+ if (hndl == 0)
+ {
+ pPlugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
+ } else {
+ pPlugin = g_PluginSys.PluginFromHandle(hndl, &err);
+
+ if (!pPlugin)
+ {
+ return pContext->ThrowNativeError("Plugin handle %x is invalid (error %d)", hndl, err);
+ }
+ }
+
+ pContext->LocalToString(params[2], &name);
+
+ /* Get public function index */
+ if (pPlugin->GetBaseContext()->FindPublicByName(name, &idx) == SP_ERROR_NOT_FOUND)
+ {
+ /* Return INVALID_FUNCTION if not found */
+ return -1;
+ }
+
+ /* Return function ID */
+ return PublicIndexToFuncId(idx);
+}
+
+static cell_t sm_CreateGlobalForward(IPluginContext *pContext, const cell_t *params)
+{
+ cell_t count = params[0];
+ char *name;
+ ParamType forwardParams[SP_MAX_EXEC_PARAMS];
+
+ if (count - 2 > SP_MAX_EXEC_PARAMS)
+ {
+ return pContext->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX, NULL);
+ }
+
+ pContext->LocalToString(params[1], &name);
+
+ cell_t *addr;
+ for (int i = 3; i <= count; i++)
+ {
+ pContext->LocalToPhysAddr(params[i], &addr);
+ forwardParams[i - 3] = static_cast(*addr);
+ }
+
+ IForward *pForward = g_Forwards.CreateForward(name, static_cast(params[2]), count - 2, forwardParams);
+
+ return g_HandleSys.CreateHandle(g_GlobalFwdType, pForward, pContext->GetIdentity(), g_pCoreIdent, NULL);
+}
+
+static cell_t sm_CreateForward(IPluginContext *pContext, const cell_t *params)
+{
+ cell_t count = params[0];
+ ParamType forwardParams[SP_MAX_EXEC_PARAMS];
+
+ if (count - 1 > SP_MAX_EXEC_PARAMS)
+ {
+ return pContext->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX, NULL);
+ }
+
+ cell_t *addr;
+ for (int i = 2; i <= count; i++)
+ {
+ pContext->LocalToPhysAddr(params[i], &addr);
+ forwardParams[i - 2] = static_cast(*addr);
+ }
+
+ IChangeableForward *pForward = g_Forwards.CreateForwardEx(NULL, static_cast(params[1]), count - 1, forwardParams);
+
+ return g_HandleSys.CreateHandle(g_PrivateFwdType, pForward, pContext->GetIdentity(), g_pCoreIdent, NULL);
+}
+
+static cell_t sm_GetForwardFunctionCount(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t hndl = static_cast(params[1]);
+ HandleError err;
+ IForward *pForward;
+
+ if ((err=g_HandleSys.ReadHandle(hndl, g_GlobalFwdType, NULL, (void **)&pForward))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid forward handle %x (error %d)", hndl, err);
+ }
+
+ return pForward->GetFunctionCount();
+}
+
+static cell_t sm_AddToForward(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t fwdHandle = static_cast(params[1]);
+ Handle_t plHandle = static_cast(params[2]);
+ HandleError err;
+ IChangeableForward *pForward;
+ IPlugin *pPlugin;
+
+ if ((err=g_HandleSys.ReadHandle(fwdHandle, g_PrivateFwdType, NULL, (void **)&pForward))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid private forward handle %x (error %d)", fwdHandle, err);
+ }
+
+ if (plHandle == 0)
+ {
+ pPlugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
+ } else {
+ pPlugin = g_PluginSys.PluginFromHandle(plHandle, &err);
+
+ if (!pPlugin)
+ {
+ return pContext->ThrowNativeError("Plugin handle %x is invalid (error %d)", plHandle, err);
+ }
+ }
+
+ IPluginFunction *pFunction = pPlugin->GetBaseContext()->GetFunctionById(params[3]);
+
+ if (!pFunction)
+ {
+ return pContext->ThrowNativeError("Invalid function id (%X)", params[3]);
+ }
+
+ return pForward->AddFunction(pFunction);
+}
+
+static cell_t sm_RemoveFromForward(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t fwdHandle = static_cast(params[1]);
+ Handle_t plHandle = static_cast(params[2]);
+ HandleError err;
+ IChangeableForward *pForward;
+ IPlugin *pPlugin;
+
+ if ((err=g_HandleSys.ReadHandle(fwdHandle, g_PrivateFwdType, NULL, (void **)&pForward))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid private forward handle %x (error %d)", fwdHandle, err);
+ }
+
+ if (plHandle == 0)
+ {
+ pPlugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
+ } else {
+ pPlugin = g_PluginSys.PluginFromHandle(plHandle, &err);
+
+ if (!pPlugin)
+ {
+ return pContext->ThrowNativeError("Plugin handle %x is invalid (error %d)", plHandle, err);
+ }
+ }
+
+ IPluginFunction *pFunction = pPlugin->GetBaseContext()->GetFunctionById(params[3]);
+
+ if (!pFunction)
+ {
+ return pContext->ThrowNativeError("Invalid function id (%X)", params[3]);
+ }
+
+ return pForward->RemoveFunction(pFunction);
+}
+
+static cell_t sm_RemoveAllFromForward(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t fwdHandle = static_cast(params[1]);
+ Handle_t plHandle = static_cast(params[2]);
+ HandleError err;
+ IChangeableForward *pForward;
+ IPlugin *pPlugin;
+
+ if ((err=g_HandleSys.ReadHandle(fwdHandle, g_PrivateFwdType, NULL, (void **)&pForward))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid private forward handle %x (error %d)", fwdHandle, err);
+ }
+
+ if (plHandle == 0)
+ {
+ pPlugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
+ } else {
+ pPlugin = g_PluginSys.PluginFromHandle(plHandle, &err);
+
+ if (!pPlugin)
+ {
+ return pContext->ThrowNativeError("Plugin handle %x is invalid (error %d)", plHandle, err);
+ }
+ }
+
+ return pForward->RemoveFunctionsOfPlugin(pPlugin);
+}
+
+static cell_t sm_CallStartFunction(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t hndl;
+ HandleError err;
+ IPlugin *pPlugin;
+
+ if (s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot start a call while one is already in progress");
+ }
+
+ hndl = static_cast(params[1]);
+
+ if (hndl == 0)
+ {
+ pPlugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
+ } else {
+ pPlugin = g_PluginSys.PluginFromHandle(hndl, &err);
+
+ if (!pPlugin)
+ {
+ return pContext->ThrowNativeError("Plugin handle %x is invalid (error %d)", hndl, err);
+ }
+ }
+
+ s_pFunction = pPlugin->GetBaseContext()->GetFunctionById(params[2]);
+
+ if (!s_pFunction)
+ {
+ return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
+ }
+
+ s_pCallable = static_cast(s_pFunction);
+
+ s_CallStarted = true;
+
+ return 1;
+}
+
+static cell_t sm_CallStartForward(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t hndl;
+ HandleError err;
+ IForward *pForward;
+
+ if (s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot start a call while one is already in progress");
+ }
+
+ hndl = static_cast(params[1]);
+
+ if ((err=g_HandleSys.ReadHandle(hndl, g_GlobalFwdType, NULL, (void **)&pForward))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid forward handle %x (error %d)", hndl, err);
+ }
+
+ s_pForward = pForward;
+
+ s_pCallable = static_cast(pForward);
+
+ s_CallStarted = true;
+
+ return 1;
+}
+
+static cell_t sm_CallPushCell(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ err = s_pCallable->PushCell(params[1]);
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallPushCellRef(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+ cell_t *addr;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ pContext->LocalToPhysAddr(params[1], &addr);
+
+ err = s_pCallable->PushCellByRef(addr);
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallPushFloat(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ err = s_pCallable->PushFloat(sp_ctof(params[1]));
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallPushFloatRef(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+ cell_t *addr;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ pContext->LocalToPhysAddr(params[1], &addr);
+
+ err = s_pCallable->PushFloatByRef(reinterpret_cast(addr));
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallPushArray(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+ cell_t *addr;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ pContext->LocalToPhysAddr(params[1], &addr);
+
+ err = s_pCallable->PushArray(addr, params[2], NULL);
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallPushArrayEx(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+ cell_t *addr;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ pContext->LocalToPhysAddr(params[1], &addr);
+
+ err = s_pCallable->PushArray(addr, params[2], params[3]);
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallPushString(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+ char *value;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ pContext->LocalToString(params[1], &value);
+
+ err = s_pCallable->PushString(value);
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallPushStringEx(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+ char *value;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot push parameters when there is no call in progress");
+ }
+
+ pContext->LocalToString(params[1], &value);
+
+ err = s_pCallable->PushStringEx(value, params[2], params[3], params[4]);
+
+ if (err)
+ {
+ s_pCallable->Cancel();
+ ResetCall();
+ return pContext->ThrowNativeErrorEx(err, NULL);
+ }
+
+ return 1;
+}
+
+static cell_t sm_CallFinish(IPluginContext *pContext, const cell_t *params)
+{
+ int err;
+ cell_t *result;
+
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot finish call when there is no call in progress");
+ }
+
+ pContext->LocalToPhysAddr(params[1], &result);
+
+ if (s_pFunction)
+ {
+ IPluginFunction *pFunction = s_pFunction;
+ err = pFunction->Execute(result);
+ } else if (s_pForward) {
+ IForward *pForward = s_pForward;
+ err = pForward->Execute(result, NULL);
+ }
+
+ ResetCall();
+
+ return err;
+}
+
+static cell_t sm_CallCancel(IPluginContext *pContext, const cell_t *params)
+{
+ if (!s_CallStarted)
+ {
+ return pContext->ThrowNativeError("Cannot cancel call when there is no call in progress");
+ }
+
+ s_pCallable->Cancel();
+ ResetCall();
+
+ return 1;
+}
+
+REGISTER_NATIVES(functionNatives)
+{
+ {"GetFunctionByName", sm_GetFunctionByName},
+ {"CreateGlobalForward", sm_CreateGlobalForward},
+ {"CreateForward", sm_CreateForward},
+ {"GetForwardFunctionCount", sm_GetForwardFunctionCount},
+ {"AddToForward", sm_AddToForward},
+ {"RemoveFromForward", sm_RemoveFromForward},
+ {"RemoveAllFromForward", sm_RemoveAllFromForward},
+ {"Call_StartFunction", sm_CallStartFunction},
+ {"Call_StartForward", sm_CallStartForward},
+ {"Call_PushCell", sm_CallPushCell},
+ {"Call_PushCellRef", sm_CallPushCellRef},
+ {"Call_PushFloat", sm_CallPushFloat},
+ {"Call_PushFloatRef", sm_CallPushFloatRef},
+ {"Call_PushArray", sm_CallPushArray},
+ {"Call_PushArrayEx", sm_CallPushArrayEx},
+ {"Call_PushString", sm_CallPushString},
+ {"Call_PushStringEx", sm_CallPushStringEx},
+ {"Call_Finish", sm_CallFinish},
+ {"Call_Cancel", sm_CallCancel},
+ {NULL, NULL},
+};
diff --git a/core/systems/ForwardSys.cpp b/core/systems/ForwardSys.cpp
index ae18abdf..f0ef70c6 100644
--- a/core/systems/ForwardSys.cpp
+++ b/core/systems/ForwardSys.cpp
@@ -601,6 +601,18 @@ bool CForward::AddFunction(IPluginContext *pContext, funcid_t index)
return AddFunction(pFunc);
}
+bool CForward::RemoveFunction(IPluginContext *pContext, funcid_t index)
+{
+ IPluginFunction *pFunc = pContext->GetFunctionById(index);
+
+ if (!pFunc)
+ {
+ return false;
+ }
+
+ return RemoveFunction(pFunc);
+}
+
bool CForward::RemoveFunction(IPluginFunction *func)
{
bool found = false;
diff --git a/core/systems/ForwardSys.h b/core/systems/ForwardSys.h
index b1d8703a..77d798c6 100644
--- a/core/systems/ForwardSys.h
+++ b/core/systems/ForwardSys.h
@@ -64,6 +64,7 @@ public: //IChangeableForward
virtual unsigned int RemoveFunctionsOfPlugin(IPlugin *plugin);
virtual bool AddFunction(IPluginFunction *func);
virtual bool AddFunction(IPluginContext *ctx, funcid_t index);
+ virtual bool RemoveFunction(IPluginContext *ctx, funcid_t index);
public:
static CForward *CreateForward(const char *name,
ExecType et,
diff --git a/core/vm/sp_vm_function.cpp b/core/vm/sp_vm_function.cpp
index 3bd995f6..58abee22 100644
--- a/core/vm/sp_vm_function.cpp
+++ b/core/vm/sp_vm_function.cpp
@@ -81,11 +81,6 @@ int CFunction::PushCell(cell_t cell)
int CFunction::PushCellByRef(cell_t *cell, int flags)
{
- if (m_curparam >= SP_MAX_EXEC_PARAMS)
- {
- return SetError(SP_ERROR_PARAMS_MAX);
- }
-
return PushArray(cell, 1, flags);
}
diff --git a/plugins/include/clients.inc b/plugins/include/clients.inc
index 25780f25..c01150dd 100644
--- a/plugins/include/clients.inc
+++ b/plugins/include/clients.inc
@@ -272,4 +272,4 @@ native CreateFakeClient(const String:name[]);
* @error Invalid client index, client not connected,
* or client not a fake client.
*/
-native SetFakeClientConVar(client, const String:cvar[], const String:value[]);
\ No newline at end of file
+native SetFakeClientConVar(client, const String:cvar[], const String:value[]);
diff --git a/plugins/include/console.inc b/plugins/include/console.inc
index fd915290..dcebe2e7 100644
--- a/plugins/include/console.inc
+++ b/plugins/include/console.inc
@@ -91,7 +91,7 @@ native ServerExecute();
* @noreturn
* @error Invalid client index, or client not connected.
*/
-native ClientCommand(client, const String:fmt[], {String,Float,Handle,_}:...);
+native ClientCommand(client, const String:fmt[], {String,Float,Handle,Function,_}:...);
/**
* Sends a message to the server console.
@@ -100,7 +100,7 @@ native ClientCommand(client, const String:fmt[], {String,Float,Handle,_}:...);
* @param ... Variable number of format parameters.
* @noreturn
*/
-native PrintToServer(const String:format[], {Handle,Float,String,_}:...);
+native PrintToServer(const String:format[], {Handle,Float,String,Function,_}:...);
/**
* Sends a message to a client's console.
@@ -111,7 +111,7 @@ native PrintToServer(const String:format[], {Handle,Float,String,_}:...);
* @noreturn
* @error If the client is not connected an error will be thrown.
*/
-native PrintToConsole(client, const String:format[], {Handle,Float,String,_}:...);
+native PrintToConsole(client, const String:format[], {Handle,Float,String,Function,_}:...);
/**
* Called when a server-only command is invoked.
diff --git a/plugins/include/core.inc b/plugins/include/core.inc
index 49a53d49..9e2272d3 100644
--- a/plugins/include/core.inc
+++ b/plugins/include/core.inc
@@ -27,6 +27,14 @@ struct PlVers
String:filevers[],
};
+/**
+ * Function helper values
+ */
+enum Function
+{
+ INVALID_FUNCTION = -1,
+};
+
/**
* Specifies what to do after a hook completes.
*/
diff --git a/plugins/include/files.inc b/plugins/include/files.inc
index dc83b13e..9fdb5406 100644
--- a/plugins/include/files.inc
+++ b/plugins/include/files.inc
@@ -54,7 +54,7 @@ enum PathType
* @param ... Format arguments.
* @return Number of bytes written to buffer (not including null terminator).
*/
-native BuildPath(PathType:type, String:buffer[], maxlength, const String:fmt[], ...);
+native BuildPath(PathType:type, String:buffer[], maxlength, const String:fmt[], {Handle,Float,String,Function,_}:...);
/**
* @brief Opens a directory/folder for contents enumeration.
@@ -186,4 +186,4 @@ native bool:RemoveDir(const String:path[]);
* @param ... Variable number of format parameters.
* @return True on success, false otherwise.
*/
-native bool:WriteFileLine(Handle:hndl, const String:format[], {Handle,Float,String,_}:...);
+native bool:WriteFileLine(Handle:hndl, const String:format[], {Handle,Float,String,Function,_}:...);
diff --git a/plugins/include/functions.inc b/plugins/include/functions.inc
index 2cea70a0..bc0bf255 100644
--- a/plugins/include/functions.inc
+++ b/plugins/include/functions.inc
@@ -13,6 +13,48 @@
* Version: $Id$
*/
+#define SP_PARAMFLAG_BYREF (1<<0) /**< Internal use only. */
+
+/**
+ * Describes the various ways to pass parameters to functions or forwards.
+ */
+enum ParamType
+{
+ Param_Any = 0, /**< Any data type can be pushed */
+ Param_Cell = (1<<1), /**< Only basic cells can be pushed */
+ Param_Float = (2<<1), /**< Only floats can be pushed */
+ Param_String = (3<<1)|SP_PARAMFLAG_BYREF, /**< Only strings can be pushed */
+ Param_Array = (4<<1)|SP_PARAMFLAG_BYREF, /**< Only arrays can be pushed */
+ Param_VarArgs = (5<<1), /**< Same as "..." in plugins, anything can be pushed, but it will always be byref */
+ Param_CellByRef = (1<<1)|SP_PARAMFLAG_BYREF, /**< Only a cell by reference can be pushed */
+ Param_FloatByRef = (2<<1)|SP_PARAMFLAG_BYREF /**< Only a float by reference can be pushed */
+};
+
+/**
+ * Defines how a forward iterates through plugin functions.
+ */
+enum ExecType
+{
+ ET_Ignore = 0, /**< Ignore all return values, return 0 */
+ ET_Single = 1, /**< Only return the last exec, ignore all others */
+ ET_Event = 2, /**< Acts as an event with the Actions defined in core.inc, no mid-Stops allowed, returns highest */
+ ET_Hook = 3 /**< Acts as a hook with the Actions defined in core.inc, mid-Stops allowed, returns highest */
+};
+
+/**
+ * @section Flags that are used with Call_PushArrayEx() and Call_PushStringEx()
+ */
+#define SM_PARAM_COPYBACK (1<<0) /**< Copy an array/reference back after call */
+#define SM_PARAM_STRING_UTF8 (1<<0) /**< String should be UTF-8 handled */
+#define SM_PARAM_STRING_COPY (1<<1) /**< String should be copied into the plugin */
+
+/**
+ * @endsection
+ */
+
+/**
+ * @section Error codes
+ */
#define SP_ERROR_NONE 0 /**< No error occurred */
#define SP_ERROR_FILE_FORMAT 1 /**< File format unrecognized */
#define SP_ERROR_DECOMPRESSOR 2 /**< A decompressor was not found */
@@ -40,6 +82,243 @@
#define SP_ERROR_NOT_RUNNABLE 24 /**< Function or plugin is not runnable */
#define SP_ERROR_ABORTED 25 /**< Function call was aborted */
+/**
+ * @endsection
+ */
+
+/**
+ * Gets a function id from a function name.
+ *
+ * @param plugin Handle of the plugin that contains the function.
+ Pass INVALID_HANDLE to search in the calling plugin.
+ * @param name Name of the function.
+ * @return Function id or INVALID_FUNCTION if not found.
+ * @error Invalid or corrupt plugin handle.
+ */
+native Function:GetFunctionByName(Handle:plugin, const String:name[]);
+
+/**
+ * Creates a global forward.
+ *
+ * @note The name used to create the forward is used as its public function in all target plugins.
+ * @note This is ideal for global, static forwards that are never changed.
+ *
+ * @param name Name of public function to use in forward.
+ * @param type Execution type to be used.
+ * @param ... Variable number of parameter types (up to 32).
+ * @return Handle to new global forward.
+ * @error More than 32 paramater types passed.
+ */
+native Handle:CreateGlobalForward(const String:name[], ExecType:type, {ParamType}:...);
+
+/**
+ * Creates a private forward.
+ *
+ * @note No functions are automatically added. Use AddToForward() to do this.
+ *
+ * @param type Execution type to be used.
+ * @param ... Variable number of parameter types (up to 32).
+ * @return Handle to new private forward.
+ * @error More than 32 paramater types passed.
+ */
+native Handle:CreateForward(ExecType:type, {ParamType}:...);
+
+/**
+ * Returns the number of functions in a global or private forward's call list.
+ *
+ * @param fwd Handle to global or private forward.
+ * @return Number of functions in forward.
+ * @error Invalid or corrupt forward handle.
+ */
+native GetForwardFunctionCount(Handle:fwd);
+
+/**
+ * Adds a function to a private forward's call list.
+ *
+ * @note Cannot be used during an incompleted call.
+ *
+ * @param fwd Handle to private forward.
+ * @param plugin Handle of the plugin that contains the function.
+ * Pass INVALID_HANDLE to specify the calling plugin.
+ * @param func Function to add to forward.
+ * @return True on success, false otherwise.
+ * @error Invalid or corrupt private forward handle, invalid or corrupt plugin handle, or invalid function.
+ */
+native bool:AddToForward(Handle:fwd, Handle:plugin, Function:func);
+
+/**
+ * Removes a function from a private forward's call list.
+ *
+ * @note Only removes one instance.
+ * @note Functions will be removed automatically if their parent plugin is unloaded.
+ *
+ * @param fwd Handle to private forward.
+ * @param plugin Handle of the plugin that contains the function.
+ * Pass INVALID_HANDLE to specify the calling plugin.
+ * @param func Function to remove from forward.
+ * @return True on success, false otherwise.
+ * @error Invalid or corrupt private forward handle, invalid or corrupt plugin handle, or invalid function.
+ */
+native bool:RemoveFromForward(Handle:fwd, Handle:plugin, Function:func);
+
+/**
+ * Removes all instances of a plugin from a private forward's call list.
+ *
+ * @note Functions will be removed automatically if their parent plugin is unloaded.
+ *
+ * @param fwd Handle to private forward.
+ * @param plugin Handle of the plugin to remove instances of.
+ * Pass INVALID_HANDLE to specify the calling plugin.
+ * @return Number of functions removed from forward.
+ * @error Invalid or corrupt private forward handle or invalid or corrupt plugin handle.
+ */
+native RemoveAllFromForward(Handle:fwd, Handle:plugin);
+
+/**
+ * Starts a call to functions in a forward's call list.
+ *
+ * @note Cannot be used during an incompleted call.
+ *
+ * @param fwd Handle to global or private forward.
+ * @noreturn
+ * @error Invalid or corrupt forward handle or called before another call has completed.
+ */
+native Call_StartForward(Handle:fwd);
+
+/**
+ * Starts a call to a function.
+ *
+ * @note Cannot be used during an incompleted call.
+ *
+ * @param plugin Handle of the plugin that contains the function.
+ * Pass INVALID_HANDLE to specify the calling plugin.
+ * @param func Function to call.
+ * @noreturn
+ * @error Invalid or corrupt plugin handle, invalid function, or called before another call has completed.
+ */
+native Call_StartFunction(Handle:plugin, Function:func);
+
+/**
+ * Pushes a cell onto the current call.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value Cell value to push.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushCell({Handle,Function,_}:value);
+
+/**
+ * Pushes a cell by reference onto the current call.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value Cell reference to push.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushCellRef(&{Handle,Function,_}:value);
+
+
+/**
+ * Pushes a float onto the current call.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value Floating point value to push.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushFloat(Float:value);
+
+/**
+ * Pushes a float by reference onto the current call.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value Floating point reference to push.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushFloatRef(&Float:value);
+
+/**
+ * Pushes an array onto the current call.
+ *
+ * @note Changes to array are not copied back to caller. Use PushArrayEx() to do this.
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value Array to push.
+ * @param size Size of array.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushArray(const {Handle,Float,Function,_}:value[], size);
+
+/**
+ * Pushes an array onto the current call.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value Array to push.
+ * @param size Size of array.
+ * @param cpflags Whether or not changes should be copied back to the input array.
+ * See SP_PARAM_* constants for details.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushArrayEx({Handle,Float,Function,_}:value[], size, cpflags);
+
+/**
+ * Pushes a string onto the current call.
+ *
+ * @note Changes to string are not copied back to caller. Use PushStringEx() to do this.
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value String to push.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushString(const String:value[]);
+
+/**
+ * Pushes a string onto the current call.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @param value String to push.
+ * @param length Length of string buffer.
+ * @param szflags Flags determining how string should be handled.
+ * See SP_PARAM_STRING_* constants for details.
+ * @param cpflags Whether or not changes should be copied back to the input array.
+ * See SP_PARAM_* constants for details.
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_PushStringEx(String:value[], length, szflags, cpflags);
+
+/**
+ * Completes a call to a function or forward's call list.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @param result Return value of function or forward's call list.
+ * @return SP_ERROR_NONE on success, any other integer on failure.
+ * @error Called before a call has been started.
+ */
+native Call_Finish(&result);
+
+/**
+ * Cancels a call to a function or forward's call list.
+ *
+ * @note Cannot be used before a call has been started.
+ *
+ * @noreturn
+ * @error Called before a call has been started.
+ */
+native Call_Cancel();
+
/**
* Defines a native function.
*
@@ -69,7 +348,7 @@ native CreateNative(const String:name[], NativeCall:func);
* @param fmt Error message format.
* @param ... Format arguments.
*/
-native ThrowNativeError(error, const String:fmt[], {Handle,Float,String,_}:...);
+native ThrowNativeError(error, const String:fmt[], {Handle,Float,String,Function,_}:...);
/**
* Retrieves the string length from a native parameter string. This is useful
@@ -78,7 +357,7 @@ native ThrowNativeError(error, const String:fmt[], {Handle,Float,String,_}:...);
*
* @param param Parameter number, starting from 1.
* @param length Stores the length of the string.
- * @return SP_ERROR_NONE on sucecss, any other integer on failure.
+ * @return SP_ERROR_NONE on success, any other integer on failure.
* @error Invalid parameter number or calling from a non-native function.
*/
native GetNativeStringLength(param, &length);
@@ -137,7 +416,7 @@ native GetNativeCellRef(param);
* @noreturn
* @error Invalid parameter number or calling from a non-native function.
*/
-native SetNativeCellRef(param, {Float,Handle,_}:value);
+native SetNativeCellRef(param, {Float,Function,Handle,_}:value);
/**
* Gets an array from a native parameter (always by reference).
@@ -148,7 +427,7 @@ native SetNativeCellRef(param, {Float,Handle,_}:value);
* @return SP_ERROR_NONE on success, anything else on failure.
* @error Invalid parameter number or calling from a non-native function.
*/
-native GetNativeArray(param, {Float,Handle,_}:local[], size);
+native GetNativeArray(param, {Float,Function,Handle,_}:local[], size);
/**
* Copies a local array into a native parameter array (always by reference).
@@ -159,7 +438,7 @@ native GetNativeArray(param, {Float,Handle,_}:local[], size);
* @return SP_ERROR_NONE on success, anything else on failure.
* @error Invalid parameter number or calling from a non-native function.
*/
-native SetNativeArray(param, const {Float,Handle,_}:local[], size);
+native SetNativeArray(param, const {Float,Function,Handle,_}:local[], size);
/**
* Formats a string using parameters from a native.
diff --git a/plugins/include/helpers.inc b/plugins/include/helpers.inc
index 61fd8f82..e33e2f58 100644
--- a/plugins/include/helpers.inc
+++ b/plugins/include/helpers.inc
@@ -44,3 +44,30 @@ stock FormatUserLogText(client, String:buffer[], maxlength)
Format(buffer, maxlength, "\"%s<%d><%s><>\"", name, userid, auth);
}
+
+/**
+ * Returns plugin handle from plugin filename.
+ *
+ * @param filename Filename of the plugin to search for.
+ * @Returns Handle to plugin if found, INVALID_HANDLE otherwise.
+ */
+stock Handle:FindPluginByFile(const String:filename[])
+{
+ decl String:buffer[256];
+
+ new Handle:iter = GetPluginIterator();
+ new Handle:pl;
+
+ while (MorePlugins(iter))
+ {
+ pl = ReadPlugin(iter);
+
+ GetPluginFilename(pl, buffer, sizeof(buffer));
+ if (StrCompare(buffer, filename) == 0)
+ {
+ return pl;
+ }
+ }
+
+ return INVALID_HANDLE;
+}
diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc
index 41baa13b..782512a8 100644
--- a/plugins/include/sourcemod.inc
+++ b/plugins/include/sourcemod.inc
@@ -189,7 +189,7 @@ native bool:GetPluginInfo(Handle:plugin, PluginInfo:info, String:buffer[], maxle
* @noreturn
* @error Always!
*/
-native ThrowError(const String:fmt[], {Handle,Float,String,_}:...);
+native ThrowError(const String:fmt[], {Handle,Float,String,Function,_}:...);
/**
* Logs a generic message to the HL2 logs.
@@ -198,7 +198,7 @@ native ThrowError(const String:fmt[], {Handle,Float,String,_}:...);
* @param ... Format arguments.
* @noreturn
*/
-native LogToGame(const String:format[], {Handle,Float,String,_}:...);
+native LogToGame(const String:format[], {Handle,Float,String,Function,_}:...);
/**
* Logs a plugin message to the SourceMod logs.
@@ -207,7 +207,7 @@ native LogToGame(const String:format[], {Handle,Float,String,_}:...);
* @param ... Format arguments.
* @noreturn
*/
-native LogMessage(const String:format[], {Handle,Float,String,_}:...);
+native LogMessage(const String:format[], {Handle,Float,String,Function,_}:...);
/**
* Logs a plugin error message to the SourceMod logs.
@@ -216,7 +216,7 @@ native LogMessage(const String:format[], {Handle,Float,String,_}:...);
* @param ... Format arguments.
* @noreturn
*/
-native LogError(const String:format[], {Handle,Float,String,_}:...);
+native LogError(const String:format[], {Handle,Float,String,Function,_}:...);
/**
* Gets the system time as a unix timestamp.
diff --git a/plugins/include/string.inc b/plugins/include/string.inc
index b8ed4180..9dee3579 100644
--- a/plugins/include/string.inc
+++ b/plugins/include/string.inc
@@ -93,7 +93,7 @@ native StrCopy(String:dest[], destLen, const String:source[]);
* @param ... Variable number of format parameters.
* @return Number of cells written.
*/
-native Format(String:buffer[], maxlength, const String:format[], {Handle,Float,String,_}:...);
+native Format(String:buffer[], maxlength, const String:format[], {Handle,Float,String,Function,_}:...);
/**
* Formats a string according to the SourceMod format rules (see documentation).
@@ -106,7 +106,7 @@ native Format(String:buffer[], maxlength, const String:format[], {Handle,Float,S
* @param ... Variable number of format parameters.
* @return Number of cells written.
*/
-native FormatEx(String:buffer[], maxlength, const String:format[], {Handle,Float,String,_}:...);
+native FormatEx(String:buffer[], maxlength, const String:format[], {Handle,Float,String,Function,_}:...);
/**
* Formats a string according to the SourceMod format rules (see documentation).
diff --git a/plugins/include/usermessages.inc b/plugins/include/usermessages.inc
index fbb6c1ae..41909fda 100644
--- a/plugins/include/usermessages.inc
+++ b/plugins/include/usermessages.inc
@@ -57,11 +57,12 @@ native bool:GetUserMessageName(UserMsg:msg_id, String:msg[], maxlength);
* @param msgname Message name to start.
* @param clients Array containing player indexes to broadcast to.
* @param numClients Number of players in the array.
+ * @param flags Optional flags to set.
* @return A handle to a bf_write bit packing structure, or
* INVALID_HANDLE on failure.
* @error Invalid message name or unable to start a message.
*/
-native Handle:StartMessage(String:msgname[], clients[], numClients, flags);
+native Handle:StartMessage(String:msgname[], clients[], numClients, flags=0);
/**
* Starts a usermessage (network message).
@@ -71,11 +72,12 @@ native Handle:StartMessage(String:msgname[], clients[], numClients, flags);
* @param msg Message index to start.
* @param clients Array containing player indexes to broadcast to.
* @param numClients Number of players in the array.
+ * @param flags Optional flags to set.
* @return A handle to a bf_write bit packing structure, or
* INVALID_HANDLE on failure.
* @error Invalid message name or unable to start a message.
*/
-native Handle:StartMessageEx(UserMsg:msg, clients[], numClients, flags);
+native Handle:StartMessageEx(UserMsg:msg, clients[], numClients, flags=0);
/**
* Ends a previously started user message (network message).
diff --git a/plugins/testsuite/callfunctest.sp b/plugins/testsuite/callfunctest.sp
new file mode 100644
index 00000000..93bc379b
--- /dev/null
+++ b/plugins/testsuite/callfunctest.sp
@@ -0,0 +1,104 @@
+#include
+
+public Plugin:myinfo =
+{
+ name = "Function Call Testing Lab",
+ author = "AlliedModders LLC",
+ description = "Tests basic function calls",
+ version = "1.0.0.0",
+ url = "http://www.sourcemod.net/"
+};
+
+public OnPluginStart()
+{
+ RegServerCmd("test_callfunc", Command_CallFunc);
+}
+
+public OnCallFuncReceived(num, Float:fnum, String:str[], String:str2[], &val, &Float:fval, array[], array2[], size, hello2[1])
+{
+ PrintToServer("Inside OnCallFuncReceived...");
+
+ PrintToServer("num = %d (expected: %d)", num, 5);
+ PrintToServer("fnum = %f (expected: %f)", fnum, 7.17);
+ PrintToServer("str[] = \"%s\" (expected: \"%s\")", str, "Gaben");
+ PrintToServer("str2[] = \"%s\" (expected: \"%s\")", str2, ".taf si nebaG");
+
+ PrintToServer("val = %d (expected %d, setting to %d)", val, 62, 15);
+ val = 15;
+
+ PrintToServer("fval = %f (expected: %f, setting to %f)", fval, 6.25, 1.5);
+ fval = 1.5;
+
+ PrintToServer("Printing %d elements of array[] (expected: %d)", size, 6);
+ for (new i = 0; i < size; i++)
+ {
+ PrintToServer("array[%d] = %d (expected: %d)", i, array[i], i);
+ }
+ for (new i = 0; i < size; i++)
+ {
+ PrintToServer("array2[%d] = %d (expected: %d)", i, array[i], i);
+ }
+
+ /* This shouldn't get copied back */
+ StrCopy(str, strlen(str) + 1, "Yeti");
+ /* This should get copied back */
+ StrCopy(str2, strlen(str2) + 1, "Gaben is fat.");
+
+ /* This should get copied back */
+ array[0] = 5;
+ array[1] = 6;
+ /* This shouldn't get copied back */
+ hello2[0] = 25;
+
+ return 42;
+}
+
+public Action:Command_CallFunc(args)
+{
+ new a = 62;
+ new Float:b = 6.25;
+ new const String:what[] = "Gaben";
+ new String:truth[] = ".taf si nebaG";
+ new hello[] = {0, 1, 2, 3, 4, 5};
+ new hello2[] = {9};
+ new pm = 6;
+ new err;
+ new ret;
+
+ new Function:func = GetFunctionByName(INVALID_HANDLE, "OnCallFuncReceived");
+
+ if (func == INVALID_FUNCTION)
+ {
+ PrintToServer("Failed to get the function id of OnCallFuncReceived");
+ return Plugin_Handled;
+ }
+
+ PrintToServer("Calling OnCallFuncReceived...");
+
+ Call_StartFunction(INVALID_HANDLE, func);
+ Call_PushCell(5);
+ Call_PushFloat(7.17);
+ Call_PushString(what);
+ Call_PushStringEx(truth, sizeof(truth), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
+ Call_PushCellRef(a);
+ Call_PushFloatRef(b);
+ Call_PushArrayEx(hello, pm, SM_PARAM_COPYBACK);
+ Call_PushArray(hello, pm);
+ Call_PushCell(pm);
+ Call_PushArray(hello2, 1);
+ err = Call_Finish(ret);
+
+ PrintToServer("Call to OnCallFuncReceived has finished!");
+ PrintToServer("Error code = %d (expected: %d)", err, 0);
+ PrintToServer("Return value = %d (expected: %d)", ret, 42);
+
+ PrintToServer("a = %d (expected: %d)", a, 15);
+ PrintToServer("b = %f (expected: %f)", b, 1.5);
+ PrintToServer("what = \"%s\" (expected: \"%s\")", what, "Gaben");
+ PrintToServer("truth = \"%s\" (expected: \"%s\")", truth, "Gaben is fat.");
+ PrintToServer("hello[0] = %d (expected: %d)", hello[0], 5);
+ PrintToServer("hello[1] = %d (expected: %d)", hello[1], 6);
+ PrintToServer("hello2[0] = %d (expected: %d)", hello2[0], 9);
+
+ return Plugin_Handled;
+}
diff --git a/plugins/testsuite/fwdtest1.sp b/plugins/testsuite/fwdtest1.sp
new file mode 100644
index 00000000..29dc4ce0
--- /dev/null
+++ b/plugins/testsuite/fwdtest1.sp
@@ -0,0 +1,169 @@
+#include
+
+public Plugin:myinfo =
+{
+ name = "Forward Testing Lab #1",
+ author = "AlliedModders LLC",
+ description = "Tests suite #1 for forwards created by plugins",
+ version = "1.0.0.0",
+ url = "http://www.sourcemod.net/"
+};
+
+new Handle:g_GlobalFwd = INVALID_HANDLE;
+new Handle:g_PrivateFwd = INVALID_HANDLE;
+
+public OnPluginStart()
+{
+ PrintToServer("OnPluginStart: %d", OnPluginStart);
+
+ RegServerCmd("test_create_gforward", Command_CreateGlobalForward);
+ RegServerCmd("test_create_pforward", Command_CreatePrivateForward);
+ RegServerCmd("test_exec_gforward", Command_ExecGlobalForward);
+ RegServerCmd("test_exec_pforward", Command_ExecPrivateForward);
+}
+
+public OnPluginEnd()
+{
+ CloseHandle(g_GlobalFwd);
+ CloseHandle(g_PrivateFwd);
+}
+
+public Action:Command_CreateGlobalForward(args)
+{
+ if (g_GlobalFwd != INVALID_HANDLE)
+ {
+ CloseHandle(g_GlobalFwd);
+ }
+
+ g_GlobalFwd = CreateGlobalForward("OnGlobalForward", ET_Ignore, Param_Any, Param_Cell, Param_Float, Param_String, Param_Array, Param_CellByRef, Param_FloatByRef);
+
+ if (g_GlobalFwd == INVALID_HANDLE)
+ {
+ PrintToServer("Failed to create global forward!");
+ }
+
+ return Plugin_Handled;
+}
+
+public Action:Command_CreatePrivateForward(args)
+{
+ new Handle:pl;
+ new Function:func;
+
+ if (g_PrivateFwd != INVALID_HANDLE)
+ {
+ CloseHandle(g_PrivateFwd);
+ }
+
+ g_PrivateFwd = CreateForward(ET_Hook, Param_Cell, Param_String, Param_VarArgs);
+
+ if (g_PrivateFwd == INVALID_HANDLE)
+ {
+ PrintToServer("Failed to create private forward!")
+ }
+
+ pl = FindPluginByFile("fwdtest2.smx");
+
+ if (!pl)
+ {
+ PrintToServer("Could not find fwdtest2.smx!");
+ return Plugin_Handled;
+ }
+
+ func = GetFunctionByName(pl, "OnPrivateForward");
+
+ /* This shouldn't happen, but oh well */
+ if (func == INVALID_FUNCTION)
+ {
+ PrintToServer("Could not find \"OnPrivateForward\" in fwdtest2.smx!");
+ return Plugin_Handled;
+ }
+
+ if (!AddToForward(g_PrivateFwd, pl, func) || !AddToForward(g_PrivateFwd, GetMyHandle(), ZohMyGod))
+ {
+ PrintToServer("Failed to add functions to private forward!");
+ return Plugin_Handled;
+ }
+
+ return Plugin_Handled;
+}
+
+public Action:Command_ExecGlobalForward(args)
+{
+ new a = 99;
+ new Float:b = 4.215;
+ new err, ret;
+
+ if (g_GlobalFwd == INVALID_HANDLE)
+ {
+ PrintToServer("Failed to execute global forward. Create it first.");
+ return Plugin_Handled;
+ }
+
+ PrintToServer("Beginning call to %d functions in global forward \"OnGlobalForward\"", GetForwardFunctionCount(g_GlobalFwd));
+
+ Call_StartForward(g_GlobalFwd);
+ Call_PushCell(OnPluginStart);
+ Call_PushCell(7);
+ Call_PushFloat(-8.5);
+ Call_PushString("Anata wa doko desu ka?");
+ Call_PushArray({0.0, 1.1, 2.2}, 3);
+ Call_PushCellRef(a);
+ Call_PushFloatRef(b);
+ err = Call_Finish(ret);
+
+ PrintToServer("Call to global forward \"OnGlobalForward\" completed");
+ PrintToServer("Error code = %d (expected: %d)", err, 0);
+ PrintToServer("Return value = %d (expected: %d)", ret, Plugin_Continue);
+
+ PrintToServer("a = %d (expected: %d)", a, 777);
+ PrintToServer("b = %f (expected: %f)", b, -0.782);
+
+ return Plugin_Handled;
+}
+
+public Action:Command_ExecPrivateForward(args)
+{
+ new err, ret;
+
+ if (g_PrivateFwd == INVALID_HANDLE)
+ {
+ PrintToServer("Failed to execute private forward. Create it first.");
+ return Plugin_Handled;
+ }
+
+ PrintToServer("Beginning call to %d functions in private forward", GetForwardFunctionCount(g_PrivateFwd));
+
+ Call_StartForward(g_PrivateFwd);
+ Call_PushCell(24);
+ Call_PushString("I am a format string: %d %d %d %d %d %d");
+ Call_PushCell(0);
+ Call_PushCell(1);
+ Call_PushCell(2);
+ Call_PushCell(3);
+ Call_PushCell(4);
+ Call_PushCell(5);
+ err = Call_Finish(ret);
+
+ PrintToServer("Call to private forward completed");
+ PrintToServer("Error code = %d (expected: %d)", err, 0);
+ PrintToServer("Return value = %d (expected: %d)", ret, Plugin_Handled);
+
+ return Plugin_Handled;
+}
+
+public Action:ZohMyGod(num, const String:format[], ...)
+{
+ decl String:buffer[128];
+
+ PrintToServer("Inside private forward #1");
+
+ PrintToServer("num = %d (expected: %d)", num, 24);
+
+ VFormat(buffer, sizeof(buffer), format, 3);
+ PrintToServer("buffer = \"%s\" (expected: \"%s\")", buffer, "I am a format string: 0 1 2 3 4 5");
+
+ PrintToServer("End private forward #1");
+
+ return Plugin_Continue;
+}
diff --git a/plugins/testsuite/fwdtest2.sp b/plugins/testsuite/fwdtest2.sp
new file mode 100644
index 00000000..a979eb68
--- /dev/null
+++ b/plugins/testsuite/fwdtest2.sp
@@ -0,0 +1,43 @@
+#include
+
+public Plugin:myinfo =
+{
+ name = "Forward Testing Lab #2",
+ author = "AlliedModders LLC",
+ description = "Tests suite #2 for forwards created by plugins",
+ version = "1.0.0.0",
+ url = "http://www.sourcemod.net/"
+};
+
+public Action:OnPrivateForward(num, const String:format[], ...)
+{
+ decl String:buffer[128];
+
+ PrintToServer("Inside private forward #2");
+
+ PrintToServer("num = %d (expected: %d)", num, 24);
+
+ VFormat(buffer, sizeof(buffer), format, 3);
+ PrintToServer("buffer = \"%s\" (expected: \"%s\")", buffer, "I am a format string: 0 1 2 3 4 5");
+
+ PrintToServer("End private forward #2");
+
+ return Plugin_Handled;
+}
+
+public OnGlobalForward(Function:a, b, Float:c, const String:d[], Float:e[3], &f, &Float:g)
+{
+ PrintToServer("Inside global forward \"OnGlobalForward\"");
+
+ PrintToServer("a = %d (expected: %d)", a, 11);
+ PrintToServer("b = %d (expected: %d)", b, 7);
+ PrintToServer("c = %f (expected: %f)", c, -8.5);
+ PrintToServer("d = \"%s\" (expected: \"%s\")", d, "Anata wa doko desu ka?");
+ PrintToServer("e = %f %f %f (expected: %f %f %f)", e[0], e[1], e[2], 0.0, 1.1, 2.2);
+
+ PrintToServer("f = %d (expected %d, setting to %d)", f, 99, 777);
+ f = 777;
+
+ PrintToServer("g = %f (expected %f, setting to %f)", g, 4.215, -0.782);
+ g = -0.782;
+}
diff --git a/public/IForwardSys.h b/public/IForwardSys.h
index da188d27..478670aa 100644
--- a/public/IForwardSys.h
+++ b/public/IForwardSys.h
@@ -227,6 +227,16 @@ namespace SourceMod
* @return True on success, otherwise false.
*/
virtual bool AddFunction(IPluginContext *ctx, funcid_t index) =0;
+
+ /**
+ * @brief Removes a function from the call list.
+ * NOTE: Only removes one instance.
+ *
+ * @param ctx Context to use as a look-up.
+ * @param index Function id to add.
+ * @return Whether or not the function was removed.
+ */
+ virtual bool RemoveFunction(IPluginContext *ctx, funcid_t index) =0;
};
#define SP_PARAMTYPE_ANY 0