Kits
Claimable kits with cooldowns
Source: src/scripting/managed/samples/Kits/ · version 1.0.0. The code below is read from the repository at build time.
What it shows
Section titled “What it shows”- commands
- Brigadier command trees (v1)
- config (v1)
- permissions (v1)
Build and run
Section titled “Build and run”dotnet build src/scripting/managed/samples/Kits -c Release# the output lands in build/bin/dotnet/samples/Kits/ when built through CMake (MC_BUILD_SCRIPTING);# copy the dll into your plugins folder and start the server with --pluginsSource
Section titled “Source”Kits.cs
Section titled “Kits.cs”using System.Text.Json.Serialization;
namespace Kits;
/// <summary>A status effect a kit grants.</summary>public sealed class KitEffect{ /// <summary>The effect key, e.g. <c>minecraft:strength</c>.</summary> public string Effect { get; set; } = "minecraft:regeneration"; /// <summary>How long it lasts in seconds.</summary> public int Seconds { get; set; } = 30; /// <summary>The level minus one.</summary> public int Amplifier { get; set; }}
/// <summary>One kit.</summary>public sealed class KitConfig{ /// <summary>Seconds a player must wait before claiming this kit again.</summary> public int CooldownSeconds { get; set; } = 600; /// <summary>Experience levels granted.</summary> public int ExperienceLevels { get; set; } /// <summary>Food level restored (0 leaves hunger alone).</summary> public int Food { get; set; } /// <summary>Status effects granted.</summary> public List<KitEffect> Effects { get; set; } = []; /// <summary>The message shown when the kit is claimed.</summary> public string Message { get; set; } = "Kit claimed!";}
/// <summary>The settings of the Kits plugin: the kits by name. Item kits arrive with the inventory API; this version grants effects, experience and food.</summary>public sealed class KitsConfig{ /// <summary>The kits.</summary> public Dictionary<string, KitConfig> Kits { get; set; } = new() { ["starter"] = new KitConfig { CooldownSeconds = 3600, Food = 20, ExperienceLevels = 3, Effects = [new KitEffect { Effect = "minecraft:regeneration", Seconds = 20 }, new KitEffect { Effect = "minecraft:speed", Seconds = 60 }], Message = "Welcome kit: food, three levels and a short boost", }, ["warrior"] = new KitConfig { CooldownSeconds = 1800, Effects = [new KitEffect { Effect = "minecraft:strength", Seconds = 120, Amplifier = 1 }, new KitEffect { Effect = "minecraft:resistance", Seconds = 120 }], Message = "Warrior kit: strength and resistance for two minutes", }, };}
[JsonSerializable(typeof(KitsConfig))]internal sealed partial class KitsJson : JsonSerializerContext;
/// <summary>Sample: <c>/kit <name></c> with kits defined in the config, per-kit permission nodes declared at runtime from the config (<c>kits.kit.<name></c>), cooldowns/// measured in server ticks, tab completion that only offers the kits the player may claim, and a live config reload.</summary>[PluginInfo("Kits", Version = "1.0.0", Author = "Ramjet", Description = "Claimable kits with cooldowns")]public sealed class KitsPlugin : Plugin{ private Config<KitsConfig> _config = null!; private readonly Dictionary<(Uuid, string), ulong> _claimed = [];
protected override void OnEnable() { _config = Config.Load(KitsJson.Default.KitsConfig); DeclarePermissions(); _config.Changed += _ => DeclarePermissions(); // new kits get their node without a restart _config.Watch = true;
Permissions.Register("kits.reload", PermissionDefault.OpLevel(3), "Reload the kits configuration");
Commands.Register(Command.Literal("kit") .Then(Command.Argument("name", Arg.Word()).Suggests(ctx => ctx.Player is { } p ? Available(p) : _config.Value.Kits.Keys).Executes(Claim)) .Then(Command.Literal("reload").Permission("kits.reload").Executes(ctx => { ctx.Reply(_config.Reload() ? "Kits reloaded" : "kits config is invalid, see the log"); return 1; })), description: "Claim a kit");
Commands.Register(Command.Literal("kits").Executes(ctx => { var player = ctx.RequirePlayer(); var names = Available(player).Order().ToList(); ctx.Reply(names.Count == 0 ? "No kits available" : "Kits: " + string.Join(", ", names)); return names.Count; })); }
private void DeclarePermissions() { foreach (string name in _config.Value.Kits.Keys) Permissions.Register($"kits.kit.{name}", PermissionDefault.True, $"Claim the {name} kit"); }
private IEnumerable<string> Available(Player player) => _config.Value.Kits.Keys.Where(k => player.HasPermission($"kits.kit.{k}"));
private int Claim(CommandContext ctx) { var player = ctx.RequirePlayer(); string name = ctx.GetString("name"); if (!_config.Value.Kits.TryGetValue(name, out var kit)) throw new CommandFailure($"There is no kit called '{name}'"); if (!player.HasPermission($"kits.kit.{name}")) throw new CommandFailure($"You may not use the {name} kit");
ulong now = Server.CurrentTick; if (_claimed.TryGetValue((player.Uuid, name), out ulong last)) { long wait = kit.CooldownSeconds * 20L - (long)(now - last); if (wait > 0) throw new CommandFailure($"The {name} kit is ready again in {(wait + 19) / 20} seconds"); } _claimed[(player.Uuid, name)] = now;
if (kit.Food > 0) { player.FoodLevel = Math.Max(player.FoodLevel, kit.Food); player.Saturation = Math.Max(player.Saturation, kit.Food); } if (kit.ExperienceLevels > 0) player.GiveExperienceLevels(kit.ExperienceLevels); foreach (var effect in kit.Effects) player.AddEffect(new PotionEffect(effect.Effect, effect.Seconds * 20, effect.Amplifier)); player.PlaySound("minecraft:entity.player.levelup", SoundCategory.Players, 0.8f); player.SendActionBar(Component.Text(kit.Message).Color(TextColor.Green)); return 1; }}Kits.csproj
Section titled “Kits.csproj”<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <McApiProject>$(MSBuildThisFileDirectory)..\..\Mc.Scripting.Api\Mc.Scripting.Api.csproj</McApiProject> </PropertyGroup> <Import Project="..\..\Mc.Scripting.Plugin.props" /> <PropertyGroup Condition="'$(McOutDir)' != ''"> <OutputPath>$(McOutDir)samples/Kits/</OutputPath> </PropertyGroup></Project>