Subsystem 3d: team presence events (connect/disconnect/death/respawn/AFK) + in-game !afk - #17
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add afk to CommandHelpCatalog.InGame (CommandGroup.TeamIntel, help.afk), add help.afk EN/FR descriptions to CommandLocalizationCatalog, and update CommandHelpCatalogTests.HandlerNames from 12 to 13 entries to keep the bidirectional drift test green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…afk reads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a new “Players” feature slice that detects and relays team presence/life-state transitions (connect/disconnect/death/respawn + AFK enter/exit) by diffing successive Rust+ GetTeamInfoAsync snapshots and publishing a new in-process PlayerStateChangedEvent, which is then relayed to Discord #events and in-game team chat. It also introduces a new in-game !afk command backed by a live AFK state seam exposed from the connection supervisor.
Changes:
- Add
TeamStateTrackerto diff team snapshots intoPlayerTransitions (including AFK hysteresis) and publishPlayerStateChangedEventfrom the supervisor poll loop. - Add
RustPlusBot.Features.Playersto render/relay those transitions to Discord embeds + in-game lines with EN/FR localization. - Add
IAfkState+!afkcommand and update command/help catalogs and registrations accordingly.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/RustPlusBot.Features.Players.Tests/RustPlusBot.Features.Players.Tests.csproj | Adds new test project for the Players feature. |
| tests/RustPlusBot.Features.Players.Tests/PlayerLocalizationCatalogTests.cs | Validates player-event localization catalog + fallback formatting. |
| tests/RustPlusBot.Features.Players.Tests/PlayerEventRendererTests.cs | Tests in-game line rendering for key transition types. |
| tests/RustPlusBot.Features.Players.Tests/PlayerEventRelayTests.cs | Tests relay behavior for in-game send + optional Discord posting. |
| tests/RustPlusBot.Features.Players.Tests/PlayerEventRegistrationTests.cs | Verifies Players DI wiring resolves. |
| tests/RustPlusBot.Features.Players.Tests/PlayerEventEndToEndTests.cs | Ensures all transition kinds render without throwing. |
| tests/RustPlusBot.Features.Connections.Tests/TeamStateTrackerTests.cs | Adds unit coverage for connect/disconnect/death/respawn diffing and death-location rules. |
| tests/RustPlusBot.Features.Connections.Tests/TeamStateTrackerAfkTests.cs | Adds unit coverage for AFK threshold/hysteresis behavior. |
| tests/RustPlusBot.Features.Connections.Tests/TeamInfoSnapshotTests.cs | Covers new DeathNote field behavior. |
| tests/RustPlusBot.Features.Connections.Tests/AfkStateTests.cs | Tests CurrentAfk reporting surface. |
| tests/RustPlusBot.Features.Commands.Tests/Help/CommandHelpCatalogTests.cs | Updates handler-name inventory for the new afk handler. |
| tests/RustPlusBot.Features.Commands.Tests/CommandRegistrationTests.cs | Updates command DI tests for IAfkState + handler count and presence. |
| tests/RustPlusBot.Features.Commands.Tests/AfkCommandHandlerTests.cs | Adds tests for new !afk command behavior. |
| src/RustPlusBot.Host/RustPlusBot.Host.csproj | Adds Players feature project reference to host. |
| src/RustPlusBot.Host/Program.cs | Registers Players feature via AddPlayers(). |
| src/RustPlusBot.Features.Players/RustPlusBot.Features.Players.csproj | New Players feature project definition + references. |
| src/RustPlusBot.Features.Players/Rendering/PlayerLocalizer.cs | Adds player-event localizer with fallback + culture normalization. |
| src/RustPlusBot.Features.Players/Rendering/PlayerLocalizationCatalog.cs | Adds EN/FR player-event string catalog. |
| src/RustPlusBot.Features.Players/Rendering/PlayerEventRenderer.cs | Renders transitions as Discord embeds and in-game lines (grid refs). |
| src/RustPlusBot.Features.Players/Rendering/IPlayerLocalizer.cs | Localizer abstraction for player-event rendering. |
| src/RustPlusBot.Features.Players/Relaying/PlayerEventRelay.cs | Relays transitions to in-game chat and optionally to Discord channel. |
| src/RustPlusBot.Features.Players/Posting/IPlayerChannelPoster.cs | Abstraction for Discord posting. |
| src/RustPlusBot.Features.Players/Posting/DiscordPlayerChannelPoster.cs | Discord.Net implementation for posting embeds. |
| src/RustPlusBot.Features.Players/PlayerEventServiceCollectionExtensions.cs | DI registration for Players feature slice. |
| src/RustPlusBot.Features.Players/Hosting/PlayersHostedService.cs | Event bus consumer that drives relay loop. |
| src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs | Adds team-info polling + tracker + event publication; exposes IAfkState. |
| src/RustPlusBot.Features.Connections/Listening/TeamStateTracker.cs | New tracker implementing snapshot diff + AFK hysteresis + AFK query surface. |
| src/RustPlusBot.Features.Connections/Listening/RustPlusSocketSource.cs | Maps Rust+ DeathNote into TeamInfoSnapshot. |
| src/RustPlusBot.Features.Connections/Listening/IAfkState.cs | New seam exposing live AFK members for !afk. |
| src/RustPlusBot.Features.Connections/ConnectionServiceCollectionExtensions.cs | Registers IAfkState from ConnectionSupervisor. |
| src/RustPlusBot.Features.Connections/ConnectionOptions.cs | Adds AfkThreshold and AfkEpsilon options. |
| src/RustPlusBot.Features.Commands/Localization/CommandLocalizationCatalog.cs | Adds EN/FR strings for !afk responses + help. |
| src/RustPlusBot.Features.Commands/Help/CommandHelpCatalog.cs | Adds afk to help listing. |
| src/RustPlusBot.Features.Commands/Handlers/AfkCommandHandler.cs | Implements !afk command. |
| src/RustPlusBot.Features.Commands/CommandServiceCollectionExtensions.cs | Registers AfkCommandHandler. |
| src/RustPlusBot.Abstractions/Events/PlayerTransitionKind.cs | Introduces transition kind enum for player presence events. |
| src/RustPlusBot.Abstractions/Events/PlayerTransition.cs | Introduces transition payload record. |
| src/RustPlusBot.Abstractions/Events/PlayerStateChangedEvent.cs | Introduces published event carrying transitions + map dimensions. |
| src/RustPlusBot.Abstractions/Connections/TeamInfoSnapshot.cs | Extends snapshot with optional leader DeathNote coordinate. |
| RustPlusBot.slnx | Adds Players feature project + test project to solution. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var eligible = now.IsOnline && now.IsAlive; | ||
| if (!eligible) | ||
| { | ||
| if (_afk.Remove(id)) | ||
| { | ||
| transitions.Add(new PlayerTransition(PlayerTransitionKind.ReturnedFromAfk, id, now.Name, null)); | ||
| } | ||
|
|
||
| _stillSince[id] = clock; | ||
| return; | ||
| } |
| return; | ||
| } | ||
|
|
||
| var moved = Math.Abs(now.X - was.X) > epsilon || Math.Abs(now.Y - was.Y) > epsilon; |
| _baseline = current; | ||
| return transitions; |
- AFK: clear the latch SILENTLY when a member goes offline, is dead, or died this poll (detected via LastDeathTimeUtc advancing even on a slow poll where IsAlive already flipped back), instead of emitting a contradictory ReturnedFromAfk alongside the disconnect/death line. - AFK movement: use a squared-distance check so AfkEpsilon is a true movement radius (per-axis treated diagonal moves as still). - Prune per-member stillness/AFK state for ids absent from the snapshot (members who leave the team) so the maps don't grow unbounded. - Add player.title to the localization-catalog key-coverage test. - Run jb cleanupcode (ReformatAndReorder) — fixes the Linux CI format gate that the original push skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the Copilot review + fixed the Linux CI failure in
New AFK tests cover the silent-clear-on-offline, silent-clear-on-death-while-respawned, diagonal-move, and departed-member-pruning cases. Full suite 407 green across 10 assemblies; build clean under |
Subsystem 3d — Team presence events
Fires Discord
#eventsembeds and in-game team-chat lines whenever a team member connects / disconnects / dies / respawns, plus AFK (became-AFK / returned-from-AFK), and adds the in-game!afkcommand. Always-on while a server is connected — no toggle.Scope is team members only (the Rust+ companion API only exposes presence/life state for the paired account's own in-game team). Builds on subsystems 2a (live-events relay pattern) and 3b-ii (team-intel via
GetTeamInfoAsync).How it works
ConnectionSupervisorpoll loop diffs successiveGetTeamInfoAsyncsnapshots via a new pure, unit-testedTeamStateTracker, publishingPlayerStateChangedEventon the in-process bus. First snapshot (and brand-new members) prime silently; a null/failed poll keeps the baseline and emits nothing.RustPlusBot.Features.Playersslice (mirroringFeatures.Events) consumes the event:PlayersHostedService→PlayerEventRelay→#eventsembed (viaIEventChannelLocator) + in-game line (viaITeamChatSender), EN/FR localized.TeamInfo.DeathNote(the true death spot); non-leader → the previous-poll position (captured pre-death, so a slow poll never mislabels the respawn point); neither → no location. Location shows only on death/respawn/became-AFK.AfkEpsilon) and online and alive for ≥AfkThreshold(config, default 5m), hysteresis-latched so became-AFK fires once; moving / going offline / dying clears it. Exposed via a newIAfkStateread seam over the supervisor's live tracker, surfaced as in-game!afk(no/playerscollision — that name stays reserved for the future Battlemetrics cluster).Tests
404 across 10 assemblies, all green. New:
TeamStateTrackerTests(12),TeamStateTrackerAfkTests(5),AfkStateTests(2), the fullFeatures.Players.Testssuite (12),AfkCommandHandlerTests, registration + help-drift updates.dotnet build -warnaserrorclean.Notes
TeamStateTrackeris guarded by a lock (mirroringEventStateStore) because the!afkcommand thread reads it concurrently with the poll loop.!connections/!deathshistory and was the prerequisite for!afk.🤖 Generated with Claude Code