Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
* text=auto eol=lf
*.sp gitlab-language=cpp
*.inc gitlab-language=cpp
*.inc gitlab-language=cpp
45 changes: 7 additions & 38 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
continue-on-error: ${{ matrix.sm-version == '1.12.x' }}
strategy:
matrix:
sm-version: [ '1.11.x', '1.12.x' ]
sm-version: [ '1.10.x', '1.11.x', '1.12.x' ]

name: "Build SM ${{ matrix.sm-version }}"
steps:
Expand Down Expand Up @@ -56,48 +56,17 @@ jobs:
if: github.ref_type == 'tag'
needs: compile
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v8

- name: Find Assets
shell: bash
run: |
echo "artifact_1_11=$(find * -maxdepth 0 -type d -name '*1.11*')" >> $GITHUB_ENV
echo "artifact_1_12=$(find * -maxdepth 0 -type d -name '*1.12*')" >> $GITHUB_ENV

# One zip per SM version that compiled (1.12 is allowed to fail)
- name: Archive Assets
run: |
zip -r ${{ env.artifact_1_11 }}.zip ${{ env.artifact_1_11 }}
zip -r ${{ env.artifact_1_12 }}.zip ${{ env.artifact_1_12 }}
run: for d in */; do zip -r "${d%/}.zip" "$d"; done

- name: Create Release
id: create_release
uses: actions/create-release@v1.1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: ${{ github.ref_name }}
draft: true
prerelease: false

- name: Upload Asset 1.11
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.artifact_1_11 }}.zip
asset_name: ${{ env.artifact_1_11 }}.zip
asset_content_type: application/zip

- name: Upload Asset 1.12
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.artifact_1_12 }}.zip
asset_name: ${{ env.artifact_1_12 }}.zip
asset_content_type: application/zip
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release create "${{ github.ref_name }}" *.zip --repo "${{ github.repository }}" --title "${{ github.ref_name }}" --draft --generate-notes
46 changes: 46 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# AGENTS.md

[VIP] Core — SourceMod (SourcePawn) plugin that grants players VIP status (timed or permanent). Extended by separate "module" plugins via natives/forwards. No tests; correctness = clean compile.

## Build
- CI (`.github/workflows/build.yml`) compiles against SM 1.10 and 1.11 (must pass) and 1.12 (allowed to fail):
`cd addons/sourcemod/scripting && mkdir -p ../plugins && spcomp VIP_Core.sp -E -o ../plugins/VIP_Core.smx -i./include`
- `-E` = warnings are errors. Fix every warning, including deprecation ones. SM 1.10 is the strictest parser (e.g. missing `;`), so code must build there too.
- Alt: `sourceknight build` (`sourceknight.yaml`, SM 1.13). It ignores spcomp's exit code — check output for `error`.
- Never commit `*.smx`, `plugins/`, `.sourceknight`.

## Git
- `dev` is the integration branch: branch from `dev`, merge back into `dev`. `master` is for releases.
- LF line endings only (enforced by `.gitattributes`); don't introduce CRLF.

## Layout
- `scripting/VIP_Core.sp` — entry point; `#include`s every `vip/*.sp` into ONE compilation unit (order matters: Global → Debug → ... → API → CMD → Clients). New file = add `#include` there.
- Compile-time switches in `VIP_Core.sp`: `DEBUG_MODE`, `USE_ADMINMENU` (gates `AdminMenu.sp` + `vip/adminmenu/*`), `USE_MORE_SERVERS`.
- `vip/Global.sp` — all globals, macros (`SZF`, `UID/CID`, `SET_BIT/UNSET_BIT`), client flag bits (`IS_VIP`, `IS_LOADED`, ...), core state bits (`IS_STARTED`, `IS_MySQL`, `IS_LOADING`).
- `vip/API.sp` — forwards (`API_SetupForwards`, `CreateForward_*`) and natives (`AskPluginLoad2`, `RegNative(X)` → `VIP_X` bound to `Native_X`).
- `vip/Database.sp` — connection, schema, queries. `UTIL.sp` — shared helpers; check here before writing new ones.
- `vip/Clients.sp` — client load/unload lifecycle, spawn handling. `Features.sp` — feature registry.
- `include/vip_core.inc` — PUBLIC API for third-party modules.
- `data/vip/cfg/*.ini` — runtime configs (groups, info, sort_menu, times). `translations/vip_core.phrases.txt` — all user-facing strings.

## Public API rules (vip_core.inc)
- Third-party modules depend on it: never change existing native/forward signatures or semantics. Add new params with defaults or add new natives.
- 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`.
- Deprecate via `#pragma deprecated`, don't delete (see `update_modules.md`).

## Database
- Config name `vip_core` in databases.cfg → MySQL; otherwise falls back to local SQLite. Every query must work on BOTH.
- Schemas differ: MySQL has `sid` column (PK `account_id, sid`), SQLite has no `sid`. Use `g_szSID` (empty on SQLite) when filtering by server.
- MySQL uses `utf8mb4`. Escape user strings with `g_hDatabase.Escape`. All queries are async (`g_hDatabase.Query` + callback); check `szError` in callbacks.
- Players are keyed by `account_id` (SteamID AccountID), not userid/client index.

## Conventions
- `#pragma semicolon 1`, `#pragma newdecls required`. Tabs. Hungarian-ish names: `iClient`, `szName`, `hMenu`, `bFlag`, globals `g_*`, cvars `g_CVAR_*`.
- Pass client across async boundaries as userid (`UID`/`CID`), never raw index.
- `GLOBAL_TRIE` / `GLOBAL_INFO` are index `[0]` of per-client arrays — slot 0 = global, not a player.
- Use methodmaps (`StringMap`, `Cookie`, `KeyValues`, `delete`), not legacy procedural calls (`CreateTrie`, `SetClientCookie`, `CloseHandle`, `Kv*`).
- Debug logging via `DBG_*` / `DebugMessage` functions from `Debug.sp` (empty unless `DEBUG_MODE 1`); calls still need a trailing `;`.
- Any new user-facing text → new phrase in `vip_core.phrases.txt` with at least `en` and `ru`.
- Comments and cvar descriptions are mostly Russian; either language is fine, keep the surrounding style.
- Bump `VIP_VERSION` in `VIP_Core.sp` only when asked; changelog lives in `updates.md`.
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.0.6 R"
#define VIP_VERSION "3.0.7 R"
#endif


Expand Down
54 changes: 27 additions & 27 deletions addons/sourcemod/scripting/vip/API.sp
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
static Handle g_hGlobalForward_OnVIPLoaded;
static Handle g_hGlobalForward_OnClientPreLoad;
static Handle g_hGlobalForward_OnClientLoaded;
static Handle g_hGlobalForward_OnVIPClientLoaded;
static Handle g_hGlobalForward_OnVIPClientAdded;
static Handle g_hGlobalForward_OnVIPClientRemoved;
static Handle g_hGlobalForward_OnPlayerSpawn;
static Handle g_hGlobalForward_OnShowClientInfo;
static Handle g_hGlobalForward_OnFeatureToggle;
static Handle g_hGlobalForward_OnFeatureRegistered;
static Handle g_hGlobalForward_OnFeatureUnregistered;
static Handle g_hGlobalForward_OnClientDisconnect;
static GlobalForward g_hGlobalForward_OnVIPLoaded;
static GlobalForward g_hGlobalForward_OnClientPreLoad;
static GlobalForward g_hGlobalForward_OnClientLoaded;
static GlobalForward g_hGlobalForward_OnVIPClientLoaded;
static GlobalForward g_hGlobalForward_OnVIPClientAdded;
static GlobalForward g_hGlobalForward_OnVIPClientRemoved;
static GlobalForward g_hGlobalForward_OnPlayerSpawn;
static GlobalForward g_hGlobalForward_OnShowClientInfo;
static GlobalForward g_hGlobalForward_OnFeatureToggle;
static GlobalForward g_hGlobalForward_OnFeatureRegistered;
static GlobalForward g_hGlobalForward_OnFeatureUnregistered;
static GlobalForward g_hGlobalForward_OnClientDisconnect;

void API_SetupForwards()
{
// Global Forwards
g_hGlobalForward_OnClientPreLoad = CreateGlobalForward("VIP_OnClientPreLoad", ET_Hook, Param_Cell);
g_hGlobalForward_OnVIPLoaded = CreateGlobalForward("VIP_OnVIPLoaded", ET_Ignore);
g_hGlobalForward_OnClientLoaded = CreateGlobalForward("VIP_OnClientLoaded", ET_Ignore, Param_Cell, Param_Cell);
g_hGlobalForward_OnVIPClientLoaded = CreateGlobalForward("VIP_OnVIPClientLoaded", ET_Ignore, Param_Cell);
g_hGlobalForward_OnVIPClientAdded = CreateGlobalForward("VIP_OnVIPClientAdded", ET_Ignore, Param_Cell, Param_Cell);
g_hGlobalForward_OnVIPClientRemoved = CreateGlobalForward("VIP_OnVIPClientRemoved", ET_Ignore, Param_Cell, Param_String, Param_Cell);
g_hGlobalForward_OnPlayerSpawn = CreateGlobalForward("VIP_OnPlayerSpawn", ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
g_hGlobalForward_OnShowClientInfo = CreateGlobalForward("VIP_OnShowClientInfo", ET_Hook, Param_Cell, Param_String, Param_String, Param_Cell);
g_hGlobalForward_OnFeatureToggle = CreateGlobalForward("VIP_OnFeatureToggle", ET_Hook, Param_Cell, Param_String, Param_Cell, Param_CellByRef);
g_hGlobalForward_OnFeatureRegistered = CreateGlobalForward("VIP_OnFeatureRegistered", ET_Ignore, Param_String);
g_hGlobalForward_OnFeatureUnregistered = CreateGlobalForward("VIP_OnFeatureUnregistered", ET_Ignore, Param_String);
g_hGlobalForward_OnClientDisconnect = CreateGlobalForward("VIP_OnClientDisconnect", ET_Ignore, Param_Cell, Param_Cell);
g_hGlobalForward_OnClientPreLoad = new GlobalForward("VIP_OnClientPreLoad", ET_Hook, Param_Cell);
g_hGlobalForward_OnVIPLoaded = new GlobalForward("VIP_OnVIPLoaded", ET_Ignore);
g_hGlobalForward_OnClientLoaded = new GlobalForward("VIP_OnClientLoaded", ET_Ignore, Param_Cell, Param_Cell);
g_hGlobalForward_OnVIPClientLoaded = new GlobalForward("VIP_OnVIPClientLoaded", ET_Ignore, Param_Cell);
g_hGlobalForward_OnVIPClientAdded = new GlobalForward("VIP_OnVIPClientAdded", ET_Ignore, Param_Cell, Param_Cell);
g_hGlobalForward_OnVIPClientRemoved = new GlobalForward("VIP_OnVIPClientRemoved", ET_Ignore, Param_Cell, Param_String, Param_Cell);
g_hGlobalForward_OnPlayerSpawn = new GlobalForward("VIP_OnPlayerSpawn", ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
g_hGlobalForward_OnShowClientInfo = new GlobalForward("VIP_OnShowClientInfo", ET_Hook, Param_Cell, Param_String, Param_String, Param_Cell);
g_hGlobalForward_OnFeatureToggle = new GlobalForward("VIP_OnFeatureToggle", ET_Hook, Param_Cell, Param_String, Param_Cell, Param_CellByRef);
g_hGlobalForward_OnFeatureRegistered = new GlobalForward("VIP_OnFeatureRegistered", ET_Ignore, Param_String);
g_hGlobalForward_OnFeatureUnregistered = new GlobalForward("VIP_OnFeatureUnregistered", ET_Ignore, Param_String);
g_hGlobalForward_OnClientDisconnect = new GlobalForward("VIP_OnClientDisconnect", ET_Ignore, Param_Cell, Param_Cell);
}

// Global Forwards
Expand Down Expand Up @@ -767,11 +767,11 @@ public int Native_RegisterFeature(Handle hPlugin, int iNumParams)

if (eType != HIDE)
{
Handle hCookie = null;
Cookie hCookie = null;
bool bRegisterCookie = (eType == SELECTABLE && iNumParams > 7 && (bCookie != false));
if (eType == TOGGLABLE || bRegisterCookie)
{
hCookie = RegClientCookie(szFeature, szFeature, CookieAccess_Private);
hCookie = new Cookie(szFeature, szFeature, CookieAccess_Private);
}

hArray.Push(hCookie);
Expand Down Expand Up @@ -1033,7 +1033,7 @@ public int Native_SetClientFeatureStatus(Handle hPlugin, int iNumParams)
if (iNumParams > 4 && GetNativeCell(5))
{
IntToString(view_as<int>(eNewStatus), SZF(szFeature));
SetClientCookie(iClient, view_as<Handle>(GetArrayCell(hArray, FEATURES_COOKIE)), szFeature);
view_as<Cookie>(hArray.Get(FEATURES_COOKIE)).Set(iClient, szFeature);
}
return true;
}
Expand Down
4 changes: 2 additions & 2 deletions addons/sourcemod/scripting/vip/AdminMenu.sp
Original file line number Diff line number Diff line change
Expand Up @@ -601,14 +601,14 @@ public int MenuHandler_GroupsList(Menu hMenu, MenuAction action, int iClient, in

public void SQL_Callback_ChangeTime(Database hOwner, DBResultSet hResult, const char[] szError, any UserID)
{
DBG_SQL_Response("SQL_Callback_ChangeTime")
DBG_SQL_Response("SQL_Callback_ChangeTime");
if (szError[0])
{
LogError("SQL_Callback_ChangeTime: %s", szError);
return;
}

DBG_SQL_Response("hResult.AffectedRows = %d", hResult.AffectedRows)
DBG_SQL_Response("hResult.AffectedRows = %d", hResult.AffectedRows);

if (hResult.AffectedRows)
{
Expand Down
Loading
Loading