Skip to content

Commit a8f7826

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

9 files changed

Lines changed: 111 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
@@ -49,6 +49,7 @@ public static IServiceCollection AddCommands(this IServiceCollection services)
4949
services.AddScoped<ICommandHandler, RecycleCommandHandler>();
5050
services.AddScoped<ICommandHandler, CraftCommandHandler>();
5151
services.AddScoped<ICommandHandler, ResearchCommandHandler>();
52+
services.AddScoped<ICommandHandler, DecayCommandHandler>();
5253

5354
services.AddScoped<CommandDispatcher>();
5455
services.AddHostedService<CommandsHostedService>();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.Localization;
6+
7+
namespace RustPlusBot.Features.Commands.Handlers;
8+
9+
/// <summary>!decay — shows an item's decay time and HP.</summary>
10+
/// <param name="database">The item database.</param>
11+
/// <param name="localizer">The reply localizer.</param>
12+
internal sealed class DecayCommandHandler(IItemDatabase database, ILocalizer localizer)
13+
: ICommandHandler
14+
{
15+
/// <inheritdoc />
16+
public string Name => "decay";
17+
18+
/// <inheritdoc />
19+
public Task<string?> ExecuteAsync(CommandContext context, CancellationToken cancellationToken)
20+
{
21+
ArgumentNullException.ThrowIfNull(context);
22+
var query = string.Join(' ', context.Args);
23+
var reply = database.Resolve(query) switch
24+
{
25+
ItemMatch.Found { Item.Decay: not null } f =>
26+
localizer.Get("command.decay.ok", context.Culture, DecayLine.Format(f.Item)),
27+
ItemMatch.Found f => localizer.Get("command.decay.none", context.Culture, f.Item.Name),
28+
ItemMatch.Ambiguous a => localizer.Get("command.item.ambiguous", context.Culture,
29+
string.Join(", ", a.Candidates.Select(c => c.Name))),
30+
_ => localizer.Get("command.item.notfound", context.Culture, query),
31+
};
32+
return Task.FromResult<string?>(reply);
33+
}
34+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ internal static class CommandHelpCatalog
2929
new("recycle", CommandGroup.ItemDb, "help.recycle"),
3030
new("craft", CommandGroup.ItemDb, "help.craft"),
3131
new("research", CommandGroup.ItemDb, "help.research"),
32+
new("decay", CommandGroup.ItemDb, "help.decay"),
3233
];
3334

3435
/// <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
@@ -255,6 +255,12 @@
255255
<data name="command.craft.ok" xml:space="preserve">
256256
<value>{0}</value>
257257
</data>
258+
<data name="command.decay.none" xml:space="preserve">
259+
<value>{0} ne se dégrade pas.</value>
260+
</data>
261+
<data name="command.decay.ok" xml:space="preserve">
262+
<value>{0}</value>
263+
</data>
258264
<data name="command.item.ambiguous" xml:space="preserve">
259265
<value>Vouliez-vous dire : {0} ?</value>
260266
</data>
@@ -372,6 +378,9 @@
372378
<data name="help.group.teamintel" xml:space="preserve">
373379
<value>Équipe</value>
374380
</data>
381+
<data name="help.decay" xml:space="preserve">
382+
<value>Temps de dégradation d'un objet</value>
383+
</data>
375384
<data name="help.item" xml:space="preserve">
376385
<value>Afficher le nom, l'id, la pile et la disparition d'un objet</value>
377386
</data>

src/RustPlusBot.Localization/Strings.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@
255255
<data name="command.craft.ok" xml:space="preserve">
256256
<value>{0}</value>
257257
</data>
258+
<data name="command.decay.none" xml:space="preserve">
259+
<value>{0} does not decay.</value>
260+
</data>
261+
<data name="command.decay.ok" xml:space="preserve">
262+
<value>{0}</value>
263+
</data>
258264
<data name="command.item.ambiguous" xml:space="preserve">
259265
<value>Did you mean: {0}?</value>
260266
</data>
@@ -372,6 +378,9 @@
372378
<data name="help.group.teamintel" xml:space="preserve">
373379
<value>Team</value>
374380
</data>
381+
<data name="help.decay" xml:space="preserve">
382+
<value>Decay time for an item</value>
383+
</data>
375384
<data name="help.item" xml:space="preserve">
376385
<value>Look up an item's name, id, stack, and despawn</value>
377386
</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(23, handlers.Count);
54+
Assert.Equal(24, 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");
@@ -69,6 +69,7 @@ public void Dispatcher_and_handlers_resolve()
6969
Assert.Contains(handlers, h => h.Name == "small");
7070
Assert.Contains(handlers, h => h.Name == "large");
7171
Assert.Contains(handlers, h => h.Name == "afk");
72+
Assert.Contains(handlers, h => h.Name == "decay");
7273
Assert.NotNull(scope.ServiceProvider.GetRequiredService<ServerResolver>());
7374
Assert.NotNull(scope.ServiceProvider.GetRequiredService<ServerQueryService>());
7475
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using RustPlusBot.Features.Commands.Dispatching;
2+
using RustPlusBot.Features.Commands.Handlers;
3+
using RustPlusBot.Features.ItemData;
4+
using RustPlusBot.Localization;
5+
6+
namespace RustPlusBot.Features.Commands.Tests.Handlers;
7+
8+
public sealed class DecayUpkeepHandlersTests
9+
{
10+
private readonly IItemDatabase _db = new EmbeddedItemDatabase();
11+
private readonly ILocalizer _loc = new ResxLocalizer();
12+
13+
private static CommandContext Ctx(params string[] args) => new(1, Guid.NewGuid(), "en", 99, "Caller", args);
14+
15+
[Fact]
16+
public void Decay_name_is_decay()
17+
=> Assert.Equal("decay", new DecayCommandHandler(_db, _loc).Name);
18+
19+
[Fact]
20+
public async Task Decay_Found_returnsDecayLine()
21+
{
22+
var reply = await new DecayCommandHandler(_db, _loc)
23+
.ExecuteAsync(Ctx("Stone Barricade"), CancellationToken.None);
24+
Assert.Contains("Stone Barricade", reply, StringComparison.Ordinal);
25+
Assert.Contains("15m", reply, StringComparison.Ordinal);
26+
}
27+
28+
[Fact]
29+
public async Task Decay_NoDecayData_returnsNone()
30+
{
31+
// "Assault Rifle" (id 1545779598) is in the bundle with null decay.
32+
var reply = await new DecayCommandHandler(_db, _loc)
33+
.ExecuteAsync(Ctx("Assault Rifle"), CancellationToken.None);
34+
Assert.Contains("Assault Rifle", reply, StringComparison.Ordinal);
35+
}
36+
37+
[Fact]
38+
public async Task Decay_NotFound_returnsMessage()
39+
{
40+
var reply = await new DecayCommandHandler(_db, _loc)
41+
.ExecuteAsync(Ctx("zzzzz"), CancellationToken.None);
42+
Assert.Contains("zzzzz", reply, StringComparison.Ordinal);
43+
}
44+
45+
[Fact]
46+
public async Task Decay_French_returnsFrenchNone()
47+
{
48+
var fr = new CommandContext(1, Guid.NewGuid(), "fr", 99, "Caller", ["Assault Rifle"]);
49+
var reply = await new DecayCommandHandler(_db, _loc).ExecuteAsync(fr, CancellationToken.None);
50+
Assert.Contains("dégrade", reply, StringComparison.Ordinal);
51+
}
52+
}

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 17 registered in-game handler names (see AddCommands / CommandRegistrationTests).</summary>
8+
/// <summary>The 18 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",
13+
"item", "recycle", "craft", "research", "decay",
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(230, EnglishKeys().Count);
45+
Assert.Equal(233, EnglishKeys().Count);
4646
}
4747
}

0 commit comments

Comments
 (0)