Skip to content

1.9.0: unban delay, admin loading, rename persistence, css_history - #293

Merged
Ravid-A merged 1 commit into
mainfrom
fix/stability-and-penalty-history
Oct 2, 2026
Merged

Ravid-A merged 1 commit into
mainfrom
fix/stability-and-penalty-history

Conversation

@Ravid-A

@Ravid-A Ravid-A commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes for issues reported by server owners in the Discord release thread, plus one new command. One migration (017), version bumped to 1.9.0.

Fixes

Unbans took up to a minute, and on SQLite never applied at all.
css_unban only wrote to the database, but the connect check reads an in-memory cache that syncs on a 61s timer. On SQLite it was worse: there is no ON UPDATE CURRENT_TIMESTAMP and the unban/expire queries never set updated_at, so the delta refresh never saw the change and the ban stayed active until a plugin reload.

  • BanManager.UnbanPlayer marks the bans UNBANNED in CacheManager and rebuilds the indexes, and logs failures instead of swallowing them
  • SQLite unban/expire queries set updated_at
  • The delta query uses >= so a change written in the same second as the refresh snapshot is not skipped
  • removeid is issued after the rows are updated, for whichever pattern matched them, clearing the engine-level banid that css_ban adds
  • RebuildIndexes builds fresh dictionaries and swaps them under a lock, so the game thread cannot read a half-built index now that unbans can trigger a rebuild off the timer thread

Admin permissions were intermittently missing after a restart.
The startup reload fired at a fixed 3s, usually before ServerManager had resolved ServerId, so the admin query ran with a null server and returned only global admins, with nothing reloading afterwards.

  • ServerManager reloads admins once ServerId is set, and on both of its failure paths so global admins still load
  • Loading the plugin mid-map (css_plugins load) fires no map start, so server loading is kicked off after 5s if it has not happened
  • CreateAdminsJsonFile no longer strips permissions from inside the ToDictionary lambda, where it ran once per name group and seconds before the new data was loaded. It returns the new cache and ApplyAdminCache applies it on the main thread immediately before LoadAdminData
  • Reloads are serialized with a semaphore and carry a generation, so overlapping ones cannot race on the JSON files or apply a stale snapshot after the hibernation cap
  • GetAllPlayersFlags and GetAllGroupsData rethrow instead of returning an empty list. A transient database error previously wrote an empty admins.json and stripped every admin on the server

Permanent renames did not survive a restart.

  • New sa_renames table (migration 017); css_prename upserts or deletes, loaded into memory once the server is ready
  • The rename is re-applied on connect before the DatabaseProvider/CacheManager early return, which previously skipped it whenever the DB or cache was not ready yet
  • New EventPlayerChangename handler; it and OnPlayerInfo re-check after a short delay instead of comparing names at event time, since the engine may not have written the client's new name yet and Rename itself takes 0.4s to settle

Smaller fixes

  • css_say printed ADMIN: <text> with no name. It now passes the caller through and honours ShowActivityType the same way admin activity does, so an operator who hides admin identities keeps them hidden here too. The shared name rule was extracted into Helper.ResolveActivityName and reused by ShowAdminActivity and ShowAdminActivityLocalized, which both carried their own copy
  • css_penalties <player> with @css/kick reassigned only the local user id while every lookup kept using caller.SteamID, so an admin checking another player always saw their own penalties
  • css_rename returned early for an admin in silent mode, which skipped the rename itself rather than just the announcement

New

css_history <#userid|name|steamid64> [bans|gags|mutes|silences|warns] (@css/kick) prints every ban, gag, mute, silence and warn with status, duration, the admin who issued it, the reason and, for lifted bans and mutes, who lifted it, when and why. A SteamID64 works for players who are not online. Also in the admin menu under Players > History.

The data was always there: unban, unmute and expiry only flip the status column, rows are never deleted.

Build

  • The csproj listed all 32 migration files by hand, so a new migration silently never reached the output. Replaced with a Database\Migrations\**\*.sql glob
  • Commands missing from an existing Commands.json are registered under their default name, so new commands appear without deleting the file. An empty alias list still disables a command

Testing

  • dotnet build -c Release --no-incremental: 0 errors, no new CS warnings
  • All 13 lang files parse; new keys present and translated, placeholder arity matches every call site
  • Migration 017 applied twice against a real SQLite database to confirm idempotency, and the upsert verified to update in place
  • The history query and both modified UPDATE sa_bans statements executed against a scratch SQLite schema
  • Build output confirmed to contain all 34 migration files including 017

Not yet exercised on a live server. Worth smoke testing before release: css_say in chat and via @, css_unban by SteamID / name / IP, css_history against MySQL and SQLite, and a restart to confirm css_prename persistence.

…1.9.0)

Fixes for issues reported by server owners, plus one new command.

Unbans took up to a minute to apply, and on SQLite never applied at all:
- css_unban only wrote to the database, but the connect check reads an
  in-memory cache that syncs on a 61s timer. BanManager.UnbanPlayer now
  marks the bans UNBANNED in CacheManager and rebuilds the indexes, and
  logs failures instead of swallowing them.
- SQLite has no ON UPDATE CURRENT_TIMESTAMP and the unban/expire queries
  never set updated_at, so the delta refresh never saw the change and
  the ban stayed active until a plugin reload. Both queries set it now.
- The delta query uses >= so a change written in the same second as the
  refresh snapshot is not skipped.
- css_unban issues removeid when UnlockedCommands is on, clearing the
  engine-level banid that css_ban adds.
- RebuildIndexes builds fresh dictionaries and swaps them under a lock,
  so the game thread cannot read a half-built index now that unbans can
  trigger a rebuild off the timer thread.

Admin permissions were intermittently missing after a restart:
- The startup reload fired at a fixed 3s, usually before ServerManager
  had resolved ServerId, so the admin query ran with a null server and
  returned only global admins, with nothing reloading afterwards.
  ServerManager now reloads admins once ServerId is set, and on both of
  its failure paths so global admins still load.
- Loading the plugin mid-map (css_plugins load) fires no map start, so
  server loading is kicked off after 5s if it has not happened.
- CreateAdminsJsonFile no longer strips permissions from inside the
  ToDictionary lambda, where it ran once per name group and seconds
  before the new data was loaded. It returns the new cache and
  PermissionManager.ApplyAdminCache applies it on the main thread
  immediately before AdminManager.LoadAdminData.
- Reloads are serialized with a semaphore and carry a generation, so
  overlapping ones cannot race on the JSON files or apply a stale
  snapshot after the hibernation cap.
- GetAllPlayersFlags and GetAllGroupsData rethrow instead of returning
  an empty list; ReloadAdmins catches, logs and aborts. A transient
  database error previously wrote an empty admins.json and stripped
  every admin on the server.

Permanent renames did not survive a restart:
- New sa_renames table (migration 017); css_prename upserts or deletes,
  and the table is loaded into memory once the server is ready.
- The rename is re-applied on connect before the DatabaseProvider /
  CacheManager early return, which previously skipped it whenever the
  DB or cache was not ready yet.
- New EventPlayerChangename handler, and both it and OnPlayerInfo
  re-check after a short delay instead of comparing names at event
  time; the engine may not have written the client's new name yet, and
  Rename itself takes 0.4s to settle.

Smaller fixes:
- css_say printed "ADMIN: <text>" with no name. The caller name is
  passed to the localizer and sa_adminsay_prefix carries {1} in all 13
  languages.
- css_penalties <player> with @css/kick reassigned only the local user
  id while every lookup kept using caller.SteamID, so an admin checking
  another player always saw their own penalties. It now uses the
  target's SteamID, and replies instead of throwing when that player's
  connect data has not finished loading.
- css_rename returned early for an admin in silent mode, which skipped
  the rename itself rather than just the announcement.

New: css_history <#userid|name|steamid64> [bans|gags|mutes|silences|warns]
(@css/kick) prints every ban, gag, mute, silence and warn with status,
duration, the admin who issued it, the reason and, for lifted bans and
mutes, who lifted it, when and why. A SteamID64 works for players who
are not online. Also available in the admin menu under Players >
History. The data was always there: unban, unmute and expiry only flip
the status column, rows are never deleted.

Build:
- The csproj listed all 32 migration files by hand, so a new migration
  silently never reached the output. Replaced with a glob.
- Commands missing from an existing Commands.json are registered under
  their default name, so new commands appear without deleting the file.
  An empty alias list still disables a command.
@Ravid-A
Ravid-A merged commit 2813e91 into main Oct 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant