Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
846e707
docs: add #info server-data embeds design spec
HandyS11 Jul 20, 2026
44fa59b
docs: add #info server-data embeds implementation plan
HandyS11 Jul 20, 2026
d4b9232
refactor: move DurationFormat to Abstractions and extract Daylight he…
HandyS11 Jul 20, 2026
9897027
refactor: make the workspace message-renderer contracts public
HandyS11 Jul 20, 2026
d77846f
feat: add players, time and wipe to the #info server embed
HandyS11 Jul 20, 2026
0484dc4
feat: add the #info events embed
HandyS11 Jul 20, 2026
cffaed8
docs: correct build/test gates in the info-embeds plan
HandyS11 Jul 20, 2026
56f9a1e
feat: add the #info team embed
HandyS11 Jul 20, 2026
663fcce
Pin the online-alive survival-time tiebreak in ServerTeamMessageRende…
HandyS11 Jul 20, 2026
2dc3322
feat: declare the events and team message specs in #info
HandyS11 Jul 20, 2026
9716f05
feat: add a render-gated in-place refresh for #info
HandyS11 Jul 20, 2026
af6a5ff
fix: propagate cancellation and strengthen gate-invalidation test in …
HandyS11 Jul 20, 2026
d72de67
feat: refresh #info on a configurable interval
HandyS11 Jul 20, 2026
04ee332
refactor: delete the nine server-data slash commands
HandyS11 Jul 20, 2026
c596907
feat: add /server player to replace the #info swap select
HandyS11 Jul 20, 2026
60fb39d
Fix EditMessageAsync silently no-op'ing on a missing Discord channel
HandyS11 Jul 20, 2026
ea5b8ac
docs: update README for #info embeds and /server player
HandyS11 Jul 20, 2026
43ec691
fix: address Copilot review on #info refresh (PR #59)
HandyS11 Jul 20, 2026
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
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ recycle/craft/research/decay/upkeep calculators are all shipped. Cameras are nex
server's channels.
- **Credential pool per server** — multiple accounts can pair with the same server;
the bot keeps one live socket per `(guild, server)` with **hot-swap** of the
active player (select menu on `#info`) and **auto-failover** to a standby
active player (`/server player`) and **auto-failover** to a standby
credential when one goes invalid (owner is DM'd).
- **Lifecycle controls** — "Remove server" (on `#info`) and "Disconnect account"
(on `#setup`), both Manage-Server-gated with a confirmation step; cascade
cleanup of channels, credentials, and live state.
- **Live status** — `#info` shows connection status, player count, and a team
summary (online/total + leader), refreshed on connection-state changes.
- **Live `#info` dashboard** — three auto-refreshing embeds below the map image:
**Server** (status, players/queue, in-game time, wipe age), **Events** (cargo /
heli / chinook / both oil rigs), and **Team** (per-member presence, grid, and
survival time). Refreshed on connection-state changes and on a configurable
interval (`Workspace:InfoRefreshInterval`, default 1 min).
- **Bilingual** — every provisioned surface renders in **English or French**,
switchable from a select menu in `#settings`.

Expand All @@ -51,12 +54,13 @@ configurable per-server prefix (default `!`) and per-command cooldowns:
- **Items** — `!item`, `!recycle`, `!craft`, `!research`, `!decay`, `!upkeep` (name or id)
- **Control** — `!mute` / `!unmute` (gate all bot→game output)

### Slash commands
Live server data (population, in-game time, wipe, team, oil rigs) is no longer a
set of ephemeral commands — it renders continuously in the `#info` dashboard
above, and stays available in-game via the `!commands`.

- **Server data** (ephemeral, with a `server` selector when several are paired) —
`/pop`, `/time`, `/wipe`, `/online`, `/offline`, `/team`, `/alive`, `/small`, `/large`
- **Items** (ephemeral) — `/item`, `/recycle`, `/craft`, `/research`, `/decay`, `/upkeep` (name or id)
- **Utility** — `/help`, `/uptime`, `/leader` (Manage Server — transfer in-game team leadership)
- **Server** — `/server player` (Manage Server — hot-swap the active paired account)
- **Admin** — `/setup`, `/workspace reset`, `/workspace simulate-server`

### Live map events
Expand Down
2,772 changes: 2,772 additions & 0 deletions docs/superpowers/plans/2026-07-20-info-embeds-server-data.md

Large diffs are not rendered by default.

374 changes: 374 additions & 0 deletions docs/superpowers/specs/2026-07-20-info-embeds-server-data-design.md

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions src/RustPlusBot.Abstractions/Connections/Daylight.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Globalization;

namespace RustPlusBot.Abstractions.Connections;

/// <summary>
/// Day/night reasoning over a <see cref="ServerTimeSnapshot" />. Shared by the in-game !time
/// handler and the #info server embed so the two can never disagree. All intervals are in
/// <em>in-game</em> hours: Rust's day length is server-configurable and the API never reports
/// it, so these values must not be presented as real-world minutes.
/// </summary>
public static class Daylight
{
private const float HoursPerDay = 24f;

/// <summary>True when the in-game clock sits between sunrise (inclusive) and sunset (exclusive).</summary>
/// <param name="snapshot">The in-game time snapshot.</param>
/// <returns>True during daylight; false at night.</returns>
public static bool IsDay(ServerTimeSnapshot snapshot)
{
ArgumentNullException.ThrowIfNull(snapshot);
return snapshot.TimeOfDay >= snapshot.Sunrise && snapshot.TimeOfDay < snapshot.Sunset;
}

/// <summary>Renders the in-game clock as zero-padded "HH:mm".</summary>
/// <param name="snapshot">The in-game time snapshot.</param>
/// <returns>The clock text, e.g. "14:32".</returns>
public static string Clock(ServerTimeSnapshot snapshot)
{
ArgumentNullException.ThrowIfNull(snapshot);
var hours = (int)snapshot.TimeOfDay;
var minutes = (int)((snapshot.TimeOfDay - hours) * 60f);
return string.Create(CultureInfo.InvariantCulture, $"{hours:00}:{minutes:00}");
}

/// <summary>In-game hours until the next sunrise or sunset, wrapping past midnight.</summary>
/// <param name="snapshot">The in-game time snapshot.</param>
/// <returns>The interval in in-game hours; never negative.</returns>
public static float HoursUntilTransition(ServerTimeSnapshot snapshot)
{
ArgumentNullException.ThrowIfNull(snapshot);
var target = IsDay(snapshot) ? snapshot.Sunset : snapshot.Sunrise;
var delta = target - snapshot.TimeOfDay;
return delta >= 0f ? delta : delta + HoursPerDay;
}

/// <summary>The same interval as <see cref="HoursUntilTransition" />, as a <see cref="TimeSpan" />.</summary>
/// <param name="snapshot">The in-game time snapshot.</param>
/// <returns>The interval in in-game hours expressed as a TimeSpan.</returns>
public static TimeSpan UntilTransition(ServerTimeSnapshot snapshot)
=> TimeSpan.FromHours(HoursUntilTransition(snapshot));
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Globalization;

namespace RustPlusBot.Features.Commands.Formatting;
namespace RustPlusBot.Abstractions.Formatting;

/// <summary>Formats durations compactly for in-game replies.</summary>
internal static class DurationFormat
/// <summary>Formats durations compactly for in-game replies and Discord embeds.</summary>
public static class DurationFormat
{
/// <summary>Renders a duration as "Xd Yh" / "Yh Zm" / "Zm".</summary>
/// <param name="span">The duration to render.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using RustPlusBot.Features.Commands.Help;
using RustPlusBot.Features.Commands.Hosting;
using RustPlusBot.Features.Commands.Leader;
using RustPlusBot.Features.Commands.Servers;
using RustPlusBot.Features.ItemData;
using RustPlusBot.Localization;

Expand Down Expand Up @@ -62,8 +61,6 @@ public static IServiceCollection AddCommands(this IServiceCollection services)
// Discord surfaces (3c): help renderer, leader service, and the interaction modules.
services.AddScoped<HelpEmbedRenderer>();
services.AddScoped<LeaderService>();
services.AddScoped<ServerResolver>();
services.AddScoped<ServerQueryService>();
services.AddSingleton(new InteractionModuleAssembly(typeof(CommandServiceCollectionExtensions).Assembly));

return services;
Expand Down
1 change: 1 addition & 0 deletions src/RustPlusBot.Features.Commands/Formatting/DecayLine.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.ItemData.Data;

namespace RustPlusBot.Features.Commands.Formatting;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.ItemData.Data;
using RustPlusBot.Features.ItemData.Naming;

Expand Down
1 change: 1 addition & 0 deletions src/RustPlusBot.Features.Commands/Formatting/ItemLine.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.ItemData.Data;

namespace RustPlusBot.Features.Commands.Formatting;
Expand Down
1 change: 1 addition & 0 deletions src/RustPlusBot.Features.Commands/Formatting/SmeltLine.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.ItemData.Data;
using RustPlusBot.Features.ItemData.Naming;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.Commands.Dispatching;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Features.Connections.Listening;
using RustPlusBot.Localization;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using RustPlusBot.Abstractions.Connections;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Abstractions.Time;
using RustPlusBot.Features.Commands.Dispatching;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Localization;

namespace RustPlusBot.Features.Commands.Handlers;
Expand Down
2 changes: 1 addition & 1 deletion src/RustPlusBot.Features.Commands/Handlers/MarkerReply.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using RustPlusBot.Abstractions.Connections;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Abstractions.Time;
using RustPlusBot.Features.Commands.Dispatching;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Features.Events.Formatting;
using RustPlusBot.Features.Events.State;
using RustPlusBot.Localization;
Expand Down
2 changes: 1 addition & 1 deletion src/RustPlusBot.Features.Commands/Handlers/RigReply.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using RustPlusBot.Abstractions.Events;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.Commands.Dispatching;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Features.Events.State;
using RustPlusBot.Localization;

Expand Down
10 changes: 2 additions & 8 deletions src/RustPlusBot.Features.Commands/Handlers/TimeCommandHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Globalization;
using RustPlusBot.Abstractions.Connections;
using RustPlusBot.Features.Commands.Dispatching;
using RustPlusBot.Localization;
Expand All @@ -23,13 +22,8 @@ internal sealed class TimeCommandHandler(IRustServerQuery query, ILocalizer loca
return localizer.Get("command.notconnected", context.Culture);
}

var isDay = time.TimeOfDay >= time.Sunrise && time.TimeOfDay < time.Sunset;
var phase = localizer.Get(isDay ? "command.time.day" : "command.time.night", context.Culture);
var phase = localizer.Get(Daylight.IsDay(time) ? "command.time.day" : "command.time.night", context.Culture);

var hours = (int)time.TimeOfDay;
var minutes = (int)((time.TimeOfDay - hours) * 60f);
var clockText = string.Create(CultureInfo.InvariantCulture, $"{hours:00}:{minutes:00}");

return localizer.Get("command.time.ok", context.Culture, clockText, phase);
return localizer.Get("command.time.ok", context.Culture, Daylight.Clock(time), phase);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.Commands.Dispatching;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Features.Commands.Hosting;
using RustPlusBot.Localization;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using RustPlusBot.Abstractions.Connections;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Abstractions.Time;
using RustPlusBot.Features.Commands.Dispatching;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Localization;

namespace RustPlusBot.Features.Commands.Handlers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal static class CommandHelpCatalog
new("help", CommandGroup.Bot, "help.slash.help"),
new("uptime", CommandGroup.Bot, "help.slash.uptime"),
new("leader", CommandGroup.Bot, "help.slash.leader"),
new("server player", CommandGroup.Bot, "help.slash.server.player"),
new("item", CommandGroup.ItemDb, "help.slash.item"),
new("recycle", CommandGroup.ItemDb, "help.slash.recycle"),
new("craft", CommandGroup.ItemDb, "help.slash.craft"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Discord;
using Discord.Interactions;
using Microsoft.Extensions.DependencyInjection;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.Commands.Help;
using RustPlusBot.Features.Commands.Hosting;
using RustPlusBot.Features.Commands.Leader;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using Discord;
using Discord.Interactions;
using Microsoft.Extensions.DependencyInjection;
using RustPlusBot.Features.Commands.Formatting;
using RustPlusBot.Abstractions.Formatting;
using RustPlusBot.Features.Commands.Hosting;
using RustPlusBot.Persistence.Connections;
using RustPlusBot.Persistence.Servers;
Expand Down
132 changes: 0 additions & 132 deletions src/RustPlusBot.Features.Commands/Modules/ServerCommandModule.cs

This file was deleted.

Loading
Loading