Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Third-party modules depend on it: never change existing native/forward signatures or semantics. Add new params with defaults or add new natives. Deprecate via `#pragma deprecated`, don't delete (see `docs/update_modules.md`).
- New native: implement `Native_X` in API.sp, `RegNative(X)` in `AskPluginLoad2`, declare in `.inc`, add `MarkNativeAsOptional("VIP_X")` in `__pl_vip_core_SetNTVOptional`.
- New forward: `static GlobalForward` field + `new GlobalForward(...)` in `API_SetupForwards` + `CreateForward_*` wrapper + `forward` declaration with doc comment in `.inc`.
- Any added native/forward: bump `VIP_CORE_INTERFACE` in `.inc` once per release (3.2 → 320) and note "Requires VIP_CORE_INTERFACE N" in its doc comment; modules check it via `VIP_GetCurrentVersionInterface()`.

## Database
- Config name `vip_core` in databases.cfg → MySQL; otherwise falls back to local SQLite. Every query must work on BOTH.
Expand All @@ -32,7 +33,7 @@

## Code style (new/changed code; legacy deviations are not a reason to copy them)
- `#pragma semicolon 1`, `#pragma newdecls required`. Tabs, no trailing whitespace. Allman braces, always braced, also single-statement `if`/`for` and `case` bodies (`case MenuAction_End: delete hMenu;` is the only one-liner).
- Space after keywords: `if (`, `for (`, `while (`, `switch (`. `++i` in loops. `!bFlag`, not `bFlag == false`. Buffers: `SZF(szBuf)`, not `szBuf, sizeof(szBuf)`; `FormatEx` unless formatting a buffer into itself.
- Space after keywords: `if (`, `for (`, `while (`, `switch (`. A condition that doesn't fit one line: `if (` on its own line, one operand per line with the operator (`||`, `&&`) at the end, then `)` on its own line. `++i` in loops. `!bFlag`, not `bFlag == false`. Buffers: `SZF(szBuf)`, not `szBuf, sizeof(szBuf)`; `FormatEx` unless formatting a buffer into itself.
- Names: locals/params Hungarian `iClient`, `szName`, `hMenu`, `bFlag`, `fTime`, `eStatus`; globals `g_` + prefix (`g_hDatabase`, `g_szSID`), cvars `g_CVAR_*`; macros/consts `UPPER_SNAKE`, StringMap keys `KEY_*` / `DATA_KEY_*`.
- Functions: `PascalCase` with file prefix: `Clients_*`, `Features_*`, `Storage_*`, `DB_*`, `UTIL_*`, `API_*`. Callbacks: `SQL_Callback_*`, `SQL_TxnCallback_*`, `Timer_*`, `MenuHandler_*`, `Event_*`; commands `*_CMD`; natives `Native_*`; forward wrappers `CreateForward_*`.
- SQL: backticks around identifiers, single quotes for values, trailing `;`, user strings via `g_hDatabase.Format`/`Escape`. Callbacks: `if (hResult == null || szError[0]) { LogError("<Callback>: %s", szError); return; }`, free the DataPack on every path.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This plugin allows you to give VIP status to players for a specific period or fo
Given the plugin’s modular system, the functionality can always be expanded.
The plugin is very flexible and easy to configure, it can work with both SQLite and MySQL database.

[[VIP] Core official thread on HlMod.ru](http://hlmod.ru/resources/vip-core.245/)
[[VIP] Core official thread on HlMod.net](https://hlmod.net/resources/vip-core.245/)

[Documentation](https://r1ko.github.io/VIP-Core/)

Expand Down
2 changes: 1 addition & 1 deletion addons/sourcemod/scripting/VIP_Core.sp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <clientprefs>

#if !defined VIP_VERSION
#define VIP_VERSION "3.1.1 R"
#define VIP_VERSION "3.2.0 R"
#endif


Expand Down
63 changes: 62 additions & 1 deletion addons/sourcemod/scripting/include/vip_core.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
#endif
#define _vip_core_included

#define VIP_CORE_INTERFACE 310
// API version of this include; bumped whenever natives or forwards are added. See VIP_GetCurrentVersionInterface().
#define VIP_CORE_INTERFACE 320

/* VIP feature data types */
enum VIP_ValueType
Expand Down Expand Up @@ -156,6 +157,20 @@ forward void VIP_OnConfigsLoaded();
*/
forward void VIP_OnClientStorageLoaded(int iClient);

/**
* Called when a value in the player's storage has changed (VIP_SetClientStorageValue,
* including toggle statuses saved by the core under "FeatureStatus-<feature>" keys).
* Not called when the value is set to what it already was.
* Requires VIP_CORE_INTERFACE 320 (see VIP_GetCurrentVersionInterface).
*
* @param iClient Client index.
* @param szKey Key.
* @param szValue New value.
*
* @noreturn
*/
forward void VIP_OnClientStorageUpdate(int iClient, const char[] szKey, const char[] szValue);

/**
* Called when a VIP feature has been registered.
*
Expand Down Expand Up @@ -345,6 +360,7 @@ native bool VIP_IsValidVIPGroup(const char[] szGroup);
/**
* Adds a group (in memory only, groups.ini is not modified).
* Groups are reset when configs are reloaded; add them again in VIP_OnConfigsLoaded().
* An "extend" key is not resolved here: pass the final values (inheritance works in groups.ini only).
*
* @param szGroup Group name.
* @param hFeatures Group parameters (keys from the current position are copied into the group).
Expand All @@ -364,6 +380,36 @@ native bool VIP_AddGroup(const char[] szGroup, KeyValues hFeatures);
*/
native bool VIP_RemoveGroup(const char[] szGroup);

/**
* Adds a feature to a group or changes its value (in memory only, groups.ini is not modified).
* In-game VIPs of the group get the new value at once; a toggle callback and VIP_OnFeatureToggle
* fire only if the player's access to the feature changes.
* sm_reload_vip_cfg rereads groups.ini and drops the change: apply it again in VIP_OnConfigsLoaded().
* Requires VIP_CORE_INTERFACE 320 (see VIP_GetCurrentVersionInterface).
*
* @param szGroup Group name.
* @param szFeature Feature name (it may be not registered yet).
* @param szValue Feature value.
*
* @return true - on success.
* false - the group does not exist or szFeature is empty.
*/
native bool VIP_GroupAddFeature(const char[] szGroup, const char[] szFeature, const char[] szValue);

/**
* Removes a feature from a group (in memory only, groups.ini is not modified).
* In-game VIPs of the group lose the feature at once (toggle callback and VIP_OnFeatureToggle with NO_ACCESS).
* sm_reload_vip_cfg rereads groups.ini and drops the change: apply it again in VIP_OnConfigsLoaded().
* Requires VIP_CORE_INTERFACE 320 (see VIP_GetCurrentVersionInterface).
*
* @param szGroup Group name.
* @param szFeature Feature name.
*
* @return true - on success.
* false - the group does not exist or has no such feature.
*/
native bool VIP_GroupRemoveFeature(const char[] szGroup, const char[] szFeature);

/**
* Saves a value to the player's storage (vip_storage table).
* The storage is available for all players (not only VIP) after VIP_OnClientStorageLoaded();
Expand Down Expand Up @@ -480,6 +526,18 @@ native bool VIP_RemoveClientVIP(int iClient, bool bInDB, bool bNotify);
*/
native bool VIP_IsVIPLoaded();

/**
* Gets the API version of the running core (its VIP_CORE_INTERFACE), so a module can tell
* which natives and forwards are available, e.g. VIP_GetCurrentVersionInterface() >= 320.
* Cores before 3.2 don't have this native: check it first with
* GetFeatureStatus(FeatureType_Native, "VIP_GetCurrentVersionInterface") == FeatureStatus_Available.
*
* @noparams
*
* @return API version of the running core.
*/
native int VIP_GetCurrentVersionInterface();

/**
* Registers a new VIP feature.
*
Expand Down Expand Up @@ -741,6 +799,7 @@ public SharedPlugin __pl_vip_core=
public void __pl_vip_core_SetNTVOptional()
{
MarkNativeAsOptional("VIP_IsVIPLoaded");
MarkNativeAsOptional("VIP_GetCurrentVersionInterface");
MarkNativeAsOptional("VIP_GetDatabase");
MarkNativeAsOptional("VIP_GetDatabaseType");
MarkNativeAsOptional("VIP_RegisterFeature");
Expand All @@ -766,6 +825,8 @@ public void __pl_vip_core_SetNTVOptional()
MarkNativeAsOptional("VIP_IsGroupExists");
MarkNativeAsOptional("VIP_AddGroup");
MarkNativeAsOptional("VIP_RemoveGroup");
MarkNativeAsOptional("VIP_GroupAddFeature");
MarkNativeAsOptional("VIP_GroupRemoveFeature");
MarkNativeAsOptional("VIP_SetClientStorageValue");
MarkNativeAsOptional("VIP_GetClientStorageValue");
MarkNativeAsOptional("VIP_GetClientFeatureStatus");
Expand Down
62 changes: 62 additions & 0 deletions addons/sourcemod/scripting/vip/API.sp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ static GlobalForward g_hGlobalForward_OnFeatureUnregistered;
static GlobalForward g_hGlobalForward_OnClientDisconnect;
static GlobalForward g_hGlobalForward_OnConfigsLoaded;
static GlobalForward g_hGlobalForward_OnClientStorageLoaded;
static GlobalForward g_hGlobalForward_OnClientStorageUpdate;

void API_SetupForwards()
{
Expand All @@ -30,6 +31,7 @@ void API_SetupForwards()
g_hGlobalForward_OnClientDisconnect = new GlobalForward("VIP_OnClientDisconnect", ET_Ignore, Param_Cell, Param_Cell);
g_hGlobalForward_OnConfigsLoaded = new GlobalForward("VIP_OnConfigsLoaded", ET_Ignore);
g_hGlobalForward_OnClientStorageLoaded = new GlobalForward("VIP_OnClientStorageLoaded", ET_Ignore, Param_Cell);
g_hGlobalForward_OnClientStorageUpdate = new GlobalForward("VIP_OnClientStorageUpdate", ET_Ignore, Param_Cell, Param_String, Param_String);
}

// Global Forwards
Expand All @@ -55,6 +57,16 @@ void CreateForward_OnClientStorageLoaded(int iClient)
Call_Finish();
}

void CreateForward_OnClientStorageUpdate(int iClient, const char[] szKey, const char[] szValue)
{
DBG_API("CreateForward_OnClientStorageUpdate(%N (%d), '%s', '%s')", iClient, iClient, szKey, szValue);
Call_StartForward(g_hGlobalForward_OnClientStorageUpdate);
Call_PushCell(iClient);
Call_PushString(szKey);
Call_PushString(szValue);
Call_Finish();
}

bool CreateForward_OnClientPreLoad(int iClient)
{
DBG_API("g_hGlobalForward_OnClientPreLoad(%N (%d), %b)", iClient, iClient, g_ePlayer[iClient].bVIP);
Expand Down Expand Up @@ -233,6 +245,7 @@ public APLRes AskPluginLoad2(Handle myself, bool bLate, char[] szError, int err_
{
// Global
RegNative(IsVIPLoaded);
RegNative(GetCurrentVersionInterface);

RegNative(GetDatabase);
RegNative(GetDatabaseType);
Expand Down Expand Up @@ -271,6 +284,8 @@ public APLRes AskPluginLoad2(Handle myself, bool bLate, char[] szError, int err_
RegNative(IsGroupExists);
RegNative(AddGroup);
RegNative(RemoveGroup);
RegNative(GroupAddFeature);
RegNative(GroupRemoveFeature);

RegNative(SetClientStorageValue);
RegNative(GetClientStorageValue);
Expand Down Expand Up @@ -795,6 +810,48 @@ public int Native_RemoveGroup(Handle hPlugin, int iNumParams)
return true;
}

public int Native_GroupAddFeature(Handle hPlugin, int iNumParams)
{
char szGroup[64], szFeature[FEATURE_NAME_LENGTH], szValue[256];
GetNativeString(1, SZF(szGroup));
GetNativeString(2, SZF(szFeature));
GetNativeString(3, SZF(szValue));

if (!szFeature[0] || !UTIL_CheckValidVIPGroup(szGroup))
{
return false;
}

g_hGroups.SetString(szFeature, szValue);
g_hGroups.Rewind();

Clients_ReloadGroupFeature(szGroup, szFeature);

return true;
}

public int Native_GroupRemoveFeature(Handle hPlugin, int iNumParams)
{
char szGroup[64], szFeature[FEATURE_NAME_LENGTH];
GetNativeString(1, SZF(szGroup));
GetNativeString(2, SZF(szFeature));

if (!szFeature[0] || !UTIL_CheckValidVIPGroup(szGroup))
{
return false;
}

bool bRemoved = g_hGroups.DeleteKey(szFeature);
g_hGroups.Rewind();

if (bRemoved)
{
Clients_ReloadGroupFeature(szGroup, szFeature);
}

return bRemoved;
}

public int Native_SetClientStorageValue(Handle hPlugin, int iNumParams)
{
int iClient = GetNativeCell(1);
Expand Down Expand Up @@ -832,6 +889,11 @@ public int Native_IsVIPLoaded(Handle hPlugin, int iNumParams)
return (g_bStarted && g_hDatabase);
}

public int Native_GetCurrentVersionInterface(Handle hPlugin, int iNumParams)
{
return VIP_CORE_INTERFACE;
}

public int Native_RegisterFeature(Handle hPlugin, int iNumParams)
{
char szFeature[FEATURE_NAME_LENGTH];
Expand Down
43 changes: 43 additions & 0 deletions addons/sourcemod/scripting/vip/Clients.sp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,49 @@ void Clients_LoadVIPFeature(int iClient, const char[] szFeature)
*/
}

// A group's feature changed at runtime: reload it for the in-game VIPs of that group
void Clients_ReloadGroupFeature(const char[] szGroup, const char[] szFeature)
{
Feature eFeature;
// Not registered yet: the new value is picked up when the module registers it
if (!Features_Get(szFeature, eFeature))
{
return;
}

char szClientGroup[64];
VIP_ToggleState eOldStatus, eNewStatus;
for (int iClient = 1; iClient <= MaxClients; ++iClient)
{
if (
!IsClientInGame(iClient) ||
!g_ePlayer[iClient].bVIP ||
!g_ePlayer[iClient].hFeatures.GetString(KEY_GROUP, SZF(szClientGroup)) ||
strcmp(szClientGroup, szGroup) != 0
)
{
continue;
}

eOldStatus = Features_GetStatus(iClient, szFeature);

// GetValue only sets non-empty values, so the old one must go first or a removed/zeroed feature would stay
g_ePlayer[iClient].hFeatures.Remove(szFeature);
g_ePlayer[iClient].hFeatureStatus.Remove(szFeature);
Clients_LoadVIPFeature(iClient, szFeature);

eNewStatus = Features_GetStatus(iClient, szFeature);
if (eFeature.eType == TOGGLABLE && eNewStatus != eOldStatus)
{
if (eFeature.fnSelect != INVALID_FUNCTION)
{
Function_OnItemToggle(eFeature.hPlugin, eFeature.fnSelect, iClient, szFeature, eOldStatus, eNewStatus);
}
CreateForward_OnFeatureToggle(iClient, szFeature, eOldStatus, eNewStatus);
}
}
}

void Clients_LoadFeature(int iClient, const char[] szFeature)
{
Feature eFeature;
Expand Down
Loading
Loading