Skip to content

Commit ac469dd

Browse files
HandyS11claude
andcommitted
feat(commands): add !upkeep in-game handler (6b)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a8f7826 commit ac469dd

9 files changed

Lines changed: 92 additions & 4 deletions

File tree

src/RustPlusBot.Features.Commands/CommandServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public static IServiceCollection AddCommands(this IServiceCollection services)
5050
services.AddScoped<ICommandHandler, CraftCommandHandler>();
5151
services.AddScoped<ICommandHandler, ResearchCommandHandler>();
5252
services.AddScoped<ICommandHandler, DecayCommandHandler>();
53+
services.AddScoped<ICommandHandler, UpkeepCommandHandler>();
5354

5455
services.AddScoped<CommandDispatcher>();
5556
services.AddHostedService<CommandsHostedService>();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using RustPlusBot.Features.Commands.Dispatching;
2+
using RustPlusBot.Features.Commands.Formatting;
3+
using RustPlusBot.Features.ItemData;
4+
using RustPlusBot.Features.ItemData.Lookup;
5+
using RustPlusBot.Features.ItemData.Naming;
6+
using RustPlusBot.Localization;
7+
8+
namespace RustPlusBot.Features.Commands.Handlers;
9+
10+
/// <summary>!upkeep — shows a building block's upkeep cost.</summary>
11+
/// <param name="database">The item database.</param>
12+
/// <param name="names">Resolves resource ids to names.</param>
13+
/// <param name="localizer">The reply localizer.</param>
14+
internal sealed class UpkeepCommandHandler(IItemDatabase database, IItemNameResolver names, ILocalizer localizer)
15+
: ICommandHandler
16+
{
17+
/// <inheritdoc />
18+
public string Name => "upkeep";
19+
20+
/// <inheritdoc />
21+
public Task<string?> ExecuteAsync(CommandContext context, CancellationToken cancellationToken)
22+
{
23+
ArgumentNullException.ThrowIfNull(context);
24+
var query = string.Join(' ', context.Args);
25+
var reply = database.Resolve(query) switch
26+
{
27+
ItemMatch.Found { Item.Upkeep: not null } f =>
28+
localizer.Get("command.upkeep.ok", context.Culture, UpkeepLine.Format(f.Item, names)),
29+
ItemMatch.Found f => localizer.Get("command.upkeep.none", context.Culture, f.Item.Name),
30+
ItemMatch.Ambiguous a => localizer.Get("command.item.ambiguous", context.Culture,
31+
string.Join(", ", a.Candidates.Select(c => c.Name))),
32+
_ => localizer.Get("command.item.notfound", context.Culture, query),
33+
};
34+
return Task.FromResult<string?>(reply);
35+
}
36+
}

src/RustPlusBot.Features.Commands/Help/CommandHelpCatalog.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ internal static class CommandHelpCatalog
3030
new("craft", CommandGroup.ItemDb, "help.craft"),
3131
new("research", CommandGroup.ItemDb, "help.research"),
3232
new("decay", CommandGroup.ItemDb, "help.decay"),
33+
new("upkeep", CommandGroup.ItemDb, "help.upkeep"),
3334
];
3435

3536
/// <summary>The Discord slash commands, in display order.</summary>

src/RustPlusBot.Localization/Strings.fr.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@
282282
<data name="command.research.ok" xml:space="preserve">
283283
<value>{0}</value>
284284
</data>
285+
<data name="command.upkeep.none" xml:space="preserve">
286+
<value>{0} n'a pas de coût d'entretien.</value>
287+
</data>
288+
<data name="command.upkeep.ok" xml:space="preserve">
289+
<value>{0}</value>
290+
</data>
285291
<data name="event.cargo.entered" xml:space="preserve">
286292
<value>🚢 Cargo Ship arrivé en {0}</value>
287293
</data>
@@ -444,6 +450,9 @@
444450
<data name="help.unmute" xml:space="preserve">
445451
<value>Réactiver la sortie du bot vers le jeu.</value>
446452
</data>
453+
<data name="help.upkeep" xml:space="preserve">
454+
<value>Coût d'entretien d'un bloc de construction</value>
455+
</data>
447456
<data name="help.uptime" xml:space="preserve">
448457
<value>Afficher depuis combien de temps le bot fonctionne.</value>
449458
</data>

src/RustPlusBot.Localization/Strings.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@
282282
<data name="command.research.ok" xml:space="preserve">
283283
<value>{0}</value>
284284
</data>
285+
<data name="command.upkeep.none" xml:space="preserve">
286+
<value>{0} has no upkeep cost.</value>
287+
</data>
288+
<data name="command.upkeep.ok" xml:space="preserve">
289+
<value>{0}</value>
290+
</data>
285291
<data name="event.cargo.entered" xml:space="preserve">
286292
<value>🚢 Cargo Ship entered at {0}</value>
287293
</data>
@@ -444,6 +450,9 @@
444450
<data name="help.unmute" xml:space="preserve">
445451
<value>Resume bot output to the game.</value>
446452
</data>
453+
<data name="help.upkeep" xml:space="preserve">
454+
<value>Upkeep cost for a building block</value>
455+
</data>
447456
<data name="help.uptime" xml:space="preserve">
448457
<value>Show how long the bot has been running.</value>
449458
</data>

tests/RustPlusBot.Features.Commands.Tests/CommandRegistrationTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void Dispatcher_and_handlers_resolve()
5151
using var scope = provider.CreateScope();
5252
Assert.NotNull(scope.ServiceProvider.GetRequiredService<CommandDispatcher>());
5353
var handlers = scope.ServiceProvider.GetServices<ICommandHandler>().ToList();
54-
Assert.Equal(24, handlers.Count);
54+
Assert.Equal(25, handlers.Count);
5555
Assert.Contains(handlers, h => h.Name == "mute");
5656
Assert.Contains(handlers, h => h.Name == "pop");
5757
Assert.Contains(handlers, h => h.Name == "time");
@@ -70,6 +70,7 @@ public void Dispatcher_and_handlers_resolve()
7070
Assert.Contains(handlers, h => h.Name == "large");
7171
Assert.Contains(handlers, h => h.Name == "afk");
7272
Assert.Contains(handlers, h => h.Name == "decay");
73+
Assert.Contains(handlers, h => h.Name == "upkeep");
7374
Assert.NotNull(scope.ServiceProvider.GetRequiredService<ServerResolver>());
7475
Assert.NotNull(scope.ServiceProvider.GetRequiredService<ServerQueryService>());
7576
}

tests/RustPlusBot.Features.Commands.Tests/Handlers/DecayUpkeepHandlersTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using RustPlusBot.Features.Commands.Dispatching;
22
using RustPlusBot.Features.Commands.Handlers;
33
using RustPlusBot.Features.ItemData;
4+
using RustPlusBot.Features.ItemData.Naming;
45
using RustPlusBot.Localization;
56

67
namespace RustPlusBot.Features.Commands.Tests.Handlers;
@@ -9,6 +10,7 @@ public sealed class DecayUpkeepHandlersTests
910
{
1011
private readonly IItemDatabase _db = new EmbeddedItemDatabase();
1112
private readonly ILocalizer _loc = new ResxLocalizer();
13+
private readonly IItemNameResolver _names = new ItemDatabaseNameResolver(new EmbeddedItemDatabase());
1214

1315
private static CommandContext Ctx(params string[] args) => new(1, Guid.NewGuid(), "en", 99, "Caller", args);
1416

@@ -49,4 +51,33 @@ public async Task Decay_French_returnsFrenchNone()
4951
var reply = await new DecayCommandHandler(_db, _loc).ExecuteAsync(fr, CancellationToken.None);
5052
Assert.Contains("dégrade", reply, StringComparison.Ordinal);
5153
}
54+
55+
[Fact]
56+
public void Upkeep_name_is_upkeep()
57+
=> Assert.Equal("upkeep", new UpkeepCommandHandler(_db, _names, _loc).Name);
58+
59+
[Fact]
60+
public async Task Upkeep_Found_returnsUpkeepLine()
61+
{
62+
var reply = await new UpkeepCommandHandler(_db, _names, _loc)
63+
.ExecuteAsync(Ctx("Wooden Door"), CancellationToken.None);
64+
Assert.Contains("Wooden Door", reply, StringComparison.Ordinal);
65+
Assert.Contains("upkeep", reply, StringComparison.OrdinalIgnoreCase);
66+
}
67+
68+
[Fact]
69+
public async Task Upkeep_NoUpkeepData_returnsNone()
70+
{
71+
var reply = await new UpkeepCommandHandler(_db, _names, _loc)
72+
.ExecuteAsync(Ctx("Assault Rifle"), CancellationToken.None);
73+
Assert.Contains("Assault Rifle", reply, StringComparison.Ordinal);
74+
}
75+
76+
[Fact]
77+
public async Task Upkeep_French_returnsFrenchNone()
78+
{
79+
var fr = new CommandContext(1, Guid.NewGuid(), "fr", 99, "Caller", ["Assault Rifle"]);
80+
var reply = await new UpkeepCommandHandler(_db, _names, _loc).ExecuteAsync(fr, CancellationToken.None);
81+
Assert.Contains("entretien", reply, StringComparison.Ordinal);
82+
}
5283
}

tests/RustPlusBot.Features.Commands.Tests/Help/CommandHelpCatalogTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ namespace RustPlusBot.Features.Commands.Tests.Help;
55

66
public sealed class CommandHelpCatalogTests
77
{
8-
/// <summary>The 18 registered in-game handler names (see AddCommands / CommandRegistrationTests).</summary>
8+
/// <summary>The 19 registered in-game handler names (see AddCommands / CommandRegistrationTests).</summary>
99
private static readonly string[] HandlerNames =
1010
[
1111
"mute", "unmute", "uptime", "pop", "wipe", "time",
1212
"online", "offline", "team", "steamid", "alive", "afk", "prox",
13-
"item", "recycle", "craft", "research", "decay",
13+
"item", "recycle", "craft", "research", "decay", "upkeep",
1414
];
1515

1616
[Fact]

tests/RustPlusBot.Localization.Tests/StringsResourceParityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ public void English_covers_every_french_key()
4242
[Fact]
4343
public void Catalog_has_expected_key_count()
4444
{
45-
Assert.Equal(233, EnglishKeys().Count);
45+
Assert.Equal(236, EnglishKeys().Count);
4646
}
4747
}

0 commit comments

Comments
 (0)