From 6b28dc2d2a039c845d1fc0396b905aeb16c048cf Mon Sep 17 00:00:00 2001 From: kayjaydee Date: Fri, 24 Apr 2026 17:25:31 +0200 Subject: [PATCH] refactor(command): clean GSD comments and translate user-facing messages to English --- .../gravityflip/command/DefineValidation.java | 27 ++--------- .../command/GravityFlipCommand.java | 16 +------ .../command/GravityFlipDefineSubCommand.java | 47 ++++--------------- .../command/GravityFlipDeleteSubCommand.java | 26 +++------- .../command/GravityFlipListSubCommand.java | 26 ++-------- .../command/GravityFlipToggleSubCommand.java | 29 ++++-------- .../command/GravityFlipTpSubCommand.java | 36 +++----------- .../command/GravityFlipWandSubCommand.java | 26 +++------- .../command/DefineValidationTest.java | 29 ++---------- 9 files changed, 51 insertions(+), 211 deletions(-) diff --git a/src/main/java/com/mythlane/gravityflip/command/DefineValidation.java b/src/main/java/com/mythlane/gravityflip/command/DefineValidation.java index 3208786..c9b02a6 100644 --- a/src/main/java/com/mythlane/gravityflip/command/DefineValidation.java +++ b/src/main/java/com/mythlane/gravityflip/command/DefineValidation.java @@ -2,38 +2,19 @@ package com.mythlane.gravityflip.command; import java.util.regex.Pattern; -/** - * Pure-data validation helpers for {@code /gravityflip define }. - * - *

No Hytale runtime dependency — same philosophy as {@code FallDamageGuard} - * and {@code WandSelectionStore}. Testable with JUnit alone. - * - *

Exposes : - *

- * - *

See {@code DefineValidationTest} for the accepted/rejected-name corpus and - * the rationale for the inflate-max-by-1 convention applied in - * {@code GravityFlipDefineSubCommand}. - */ +/** Pure-data validation helpers for the define sub-command (region name regex, componentwise min/max). */ public final class DefineValidation { private static final Pattern NAME = Pattern.compile("^[a-zA-Z0-9_-]{1,32}$"); private DefineValidation() {} - /** - * Returns {@code true} iff {@code n} matches {@code ^[a-zA-Z0-9_-]{1,32}$}. - * Rejects {@code null}, blank, spaces, path separators, non-ASCII. - */ + /** Returns true iff the name matches {@code ^[a-zA-Z0-9_-]{1,32}$}. */ public static boolean isValidName(String n) { return n != null && NAME.matcher(n).matches(); } - /** Returns {@code {min(a.x,b.x), min(a.y,b.y), min(a.z,b.z)}}. */ + /** Returns the per-axis minimum of two 3D integer vectors. */ public static int[] componentwiseMin(int[] a, int[] b) { return new int[]{ Math.min(a[0], b[0]), @@ -42,7 +23,7 @@ public final class DefineValidation { }; } - /** Returns {@code {max(a.x,b.x), max(a.y,b.y), max(a.z,b.z)}}. */ + /** Returns the per-axis maximum of two 3D integer vectors. */ public static int[] componentwiseMax(int[] a, int[] b) { return new int[]{ Math.max(a[0], b[0]), diff --git a/src/main/java/com/mythlane/gravityflip/command/GravityFlipCommand.java b/src/main/java/com/mythlane/gravityflip/command/GravityFlipCommand.java index 6320986..53750fb 100644 --- a/src/main/java/com/mythlane/gravityflip/command/GravityFlipCommand.java +++ b/src/main/java/com/mythlane/gravityflip/command/GravityFlipCommand.java @@ -3,23 +3,11 @@ package com.mythlane.gravityflip.command; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection; import com.mythlane.gravityflip.GravityFlipPlugin; -/** - * Root command {@code /gravityflip}. Aggregates all Gravity Flip sub-commands. - * - *

Extends {@link AbstractCommandCollection} — pattern sourced from - * {@code builtin/teleport/commands/teleport/TeleportCommand} and - * {@code modules/debug/commands/DebugCommand}. When invoked without a - * sub-command, the base class emits a usage message listing registered - * sub-commands (no extra work required here). - * - *

The plugin reference is stored for future sub-commands (04-03 define, - * 04-04 list/delete/toggle/tp) that need {@code plugin.wandSelections()} or - * {@code plugin.configHolder()}. - */ +/** Root command {@code /gravityflip} — aggregates all sub-commands. */ public final class GravityFlipCommand extends AbstractCommandCollection { public GravityFlipCommand(GravityFlipPlugin plugin) { - super("gravityflip", "Commandes de gestion des zones Gravity Flip"); + super("gravityflip", "Gravity Flip region management commands"); this.addSubCommand(new GravityFlipWandSubCommand()); this.addSubCommand(new GravityFlipDefineSubCommand(plugin)); this.addSubCommand(new GravityFlipListSubCommand(plugin)); diff --git a/src/main/java/com/mythlane/gravityflip/command/GravityFlipDefineSubCommand.java b/src/main/java/com/mythlane/gravityflip/command/GravityFlipDefineSubCommand.java index ab60040..f021d71 100644 --- a/src/main/java/com/mythlane/gravityflip/command/GravityFlipDefineSubCommand.java +++ b/src/main/java/com/mythlane/gravityflip/command/GravityFlipDefineSubCommand.java @@ -20,42 +20,16 @@ import javax.annotation.Nonnull; import java.util.UUID; import java.util.logging.Level; -/** - * {@code /gravityflip define } — consume the caller's wand selection - * (pos1 + pos2) to create and persist a new {@link GravityFlipRegion}. - * - *

Flow : - *

    - *
  1. Validate {@code name} via {@link DefineValidation#isValidName}.
  2. - *
  3. Read the caller's selection from {@link WandSelectionStore}. Bail out - * if either pos1 or pos2 is unset (builder must click 2 blocks first).
  4. - *
  5. Compute componentwise min/max so the builder can click in any order. - * Inflate max by +1 per axis so the max block is INSIDE the AABB (a block - * occupies the unit cube between {@code (x,y,z)} and {@code (x+1,y+1,z+1)}; - * without inflation, a player standing on the max block is OUT of the - * region — see {@code DefineValidationTest#boxFromCorners_inflateMax_includesMaxBlock}).
  6. - *
  7. {@code registry.add(region)} — throws {@link IllegalArgumentException} - * on duplicate name (T-04-03-05 mitigation — synchronized mutationLock).
  8. - *
  9. {@code configHolder.save().join()} forces durability before responding. - * Cost : a few ms disk. Acceptable in an interactive command.
  10. - *
  11. Clear the caller's selection — {@code define} consumes it, next define - * requires re-clicking 2 blocks (avoid chained accidental defines).
  12. - *
- * - *

On save failure the region remains in-memory (registry is already updated). - * We report this truthfully rather than silently rolling back — operators can - * inspect logs and the region is still active until restart. - */ +/** Creates and persists a new region from the caller's wand selection. */ public final class GravityFlipDefineSubCommand extends AbstractPlayerCommand { private final GravityFlipPlugin plugin; - /** Required STRING arg; full validation applied via {@link DefineValidation#isValidName}. */ private final RequiredArg nameArg = - this.withRequiredArg("name", "Nom de la région (a-z0-9_-, 1-32 chars)", ArgTypes.STRING); + this.withRequiredArg("name", "Region name (a-z0-9_-, 1-32 chars)", ArgTypes.STRING); public GravityFlipDefineSubCommand(GravityFlipPlugin plugin) { - super("define", "Créer une région Gravity Flip à partir de la sélection wand"); + super("define", "Create a Gravity Flip region from the wand selection"); this.plugin = plugin; } @@ -68,7 +42,7 @@ public final class GravityFlipDefineSubCommand extends AbstractPlayerCommand { String name = nameArg.get(ctx); if (!DefineValidation.isValidName(name)) { ctx.sendMessage(Message.raw( - "[gravityflip] Nom invalide — attendu [a-zA-Z0-9_-]{1,32}.")); + "[gravityflip] Invalid name — expected [a-zA-Z0-9_-]{1,32}.")); return; } @@ -76,14 +50,13 @@ public final class GravityFlipDefineSubCommand extends AbstractPlayerCommand { WandSelectionStore.Selection sel = plugin.wandSelections().get(uuid); if (sel.pos1 == null || sel.pos2 == null) { ctx.sendMessage(Message.raw( - "[gravityflip] Sélection incomplète — left-click puis right-click un bloc avec le wand avant define.")); + "[gravityflip] Incomplete selection — left-click then right-click a block with the wand before define.")); return; } int[] mn = DefineValidation.componentwiseMin(sel.pos1, sel.pos2); int[] mx = DefineValidation.componentwiseMax(sel.pos1, sel.pos2); - // Inflate max by +1 per axis so the block at maxBlock is inside the AABB - // (see DefineValidationTest#boxFromCorners_inflateMax_includesMaxBlock). + // Inflate max by +1 per axis so the max block is INSIDE the AABB (blocks occupy the unit cube [x,x+1]). Box box = new Box( new Vector3d(mn[0], mn[1], mn[2]), new Vector3d(mx[0] + 1.0, mx[1] + 1.0, mx[2] + 1.0)); @@ -93,7 +66,7 @@ public final class GravityFlipDefineSubCommand extends AbstractPlayerCommand { plugin.regions().add(region); } catch (IllegalArgumentException ex) { ctx.sendMessage(Message.raw( - "[gravityflip] Une région nommée '" + name + "' existe déjà.")); + "[gravityflip] A region named '" + name + "' already exists.")); return; } @@ -103,14 +76,14 @@ public final class GravityFlipDefineSubCommand extends AbstractPlayerCommand { plugin.getLogger().at(Level.WARNING).withCause(th) .log("[define] save failed for region '" + name + "'"); ctx.sendMessage(Message.raw( - "[gravityflip] Région créée (en mémoire) mais persistance échouée — voir logs.")); + "[gravityflip] Region created (in memory) but persistence failed — see logs.")); return; } plugin.wandSelections().clear(uuid); ctx.sendMessage(Message.raw( - "[gravityflip] Région '" + name + "' créée : " - + "(" + mn[0] + "," + mn[1] + "," + mn[2] + ") → " + "[gravityflip] Region '" + name + "' created: " + + "(" + mn[0] + "," + mn[1] + "," + mn[2] + ") -> " + "(" + (mx[0] + 1) + "," + (mx[1] + 1) + "," + (mx[2] + 1) + ")")); } } diff --git a/src/main/java/com/mythlane/gravityflip/command/GravityFlipDeleteSubCommand.java b/src/main/java/com/mythlane/gravityflip/command/GravityFlipDeleteSubCommand.java index b7b9386..fb7d83f 100644 --- a/src/main/java/com/mythlane/gravityflip/command/GravityFlipDeleteSubCommand.java +++ b/src/main/java/com/mythlane/gravityflip/command/GravityFlipDeleteSubCommand.java @@ -10,30 +10,16 @@ import com.mythlane.gravityflip.GravityFlipPlugin; import javax.annotation.Nonnull; import java.util.logging.Level; -/** - * {@code /gravityflip delete } — supprime la région nommée et persiste. - * - *

Étend {@link CommandBase} pour permettre l'usage depuis la console serveur - * (opération admin, pas besoin d'un joueur caller). - * - *

Flow : - *

    - *
  1. {@code registry.remove(name)} — renvoie {@code false} si nom inconnu → message - * d'erreur clair et retour précoce (pas de save inutile).
  2. - *
  3. {@code configHolder.save().join()} force la durabilité. Même pattern que - * {@code GravityFlipDefineSubCommand} : sur échec disque, on reporte truthfully - * ("en mémoire OK, persistance échouée") plutôt que silent rollback.
  4. - *
- */ +/** Deletes a Gravity Flip region and persists the change. */ public final class GravityFlipDeleteSubCommand extends CommandBase { private final GravityFlipPlugin plugin; private final RequiredArg nameArg = - this.withRequiredArg("name", "Nom de la région à supprimer", ArgTypes.STRING); + this.withRequiredArg("name", "Name of the region to delete", ArgTypes.STRING); public GravityFlipDeleteSubCommand(GravityFlipPlugin plugin) { - super("delete", "Supprime une région Gravity Flip"); + super("delete", "Delete a Gravity Flip region"); this.plugin = plugin; } @@ -41,7 +27,7 @@ public final class GravityFlipDeleteSubCommand extends CommandBase { protected void executeSync(@Nonnull CommandContext ctx) { String name = nameArg.get(ctx); if (!plugin.regions().remove(name)) { - ctx.sendMessage(Message.raw("[gravityflip] Région '" + name + "' introuvable.")); + ctx.sendMessage(Message.raw("[gravityflip] Region '" + name + "' not found.")); return; } try { @@ -50,9 +36,9 @@ public final class GravityFlipDeleteSubCommand extends CommandBase { plugin.getLogger().at(Level.WARNING).withCause(th) .log("[delete] save failed for region '" + name + "'"); ctx.sendMessage(Message.raw( - "[gravityflip] Suppression en mémoire OK, persistance échouée — voir logs.")); + "[gravityflip] Deleted in memory, but persistence failed — see logs.")); return; } - ctx.sendMessage(Message.raw("[gravityflip] Région '" + name + "' supprimée.")); + ctx.sendMessage(Message.raw("[gravityflip] Region '" + name + "' deleted.")); } } diff --git a/src/main/java/com/mythlane/gravityflip/command/GravityFlipListSubCommand.java b/src/main/java/com/mythlane/gravityflip/command/GravityFlipListSubCommand.java index 65344e4..7cb05d9 100644 --- a/src/main/java/com/mythlane/gravityflip/command/GravityFlipListSubCommand.java +++ b/src/main/java/com/mythlane/gravityflip/command/GravityFlipListSubCommand.java @@ -10,29 +10,13 @@ import com.mythlane.gravityflip.region.GravityFlipRegion; import javax.annotation.Nonnull; import java.util.Collection; -/** - * {@code /gravityflip list} — liste toutes les régions Gravity Flip persistées. - * - *

Étend {@link CommandBase} (et non {@code AbstractPlayerCommand}) pour - * fonctionner aussi depuis la console serveur : cette commande est admin-only - * de par sa permission auto-générée ({@code mythlane.gravityflip.command.gravityflip.list}), - * mais n'a pas besoin d'un {@code PlayerRef} — seulement de {@code ctx.sendMessage(...)}. - * - *

Format de sortie (une ligne par région) : - *

- *   - test-zone-1 : (10,64,10) → (21,71,21) [enabled]
- * 
- * - *

Threat surface : T-04-04-01 (Information Disclosure) accepté — les coords - * des régions sont visibles pour tout opérateur avec la permission list ; c'est - * la spec attendue pour un outil builder. - */ +/** Lists all persisted Gravity Flip regions. */ public final class GravityFlipListSubCommand extends CommandBase { private final GravityFlipPlugin plugin; public GravityFlipListSubCommand(GravityFlipPlugin plugin) { - super("list", "Liste toutes les régions Gravity Flip"); + super("list", "List all Gravity Flip regions"); this.plugin = plugin; } @@ -40,15 +24,15 @@ public final class GravityFlipListSubCommand extends CommandBase { protected void executeSync(@Nonnull CommandContext ctx) { Collection all = plugin.regions().all(); if (all.isEmpty()) { - ctx.sendMessage(Message.raw("[gravityflip] Aucune région définie.")); + ctx.sendMessage(Message.raw("[gravityflip] No regions defined.")); return; } - ctx.sendMessage(Message.raw("[gravityflip] " + all.size() + " région(s) :")); + ctx.sendMessage(Message.raw("[gravityflip] " + all.size() + " region(s):")); for (GravityFlipRegion r : all) { Vector3d mn = r.getMin(); Vector3d mx = r.getMax(); ctx.sendMessage(Message.raw(String.format( - " - %s : (%.0f,%.0f,%.0f) → (%.0f,%.0f,%.0f) [%s]", + " - %s : (%.0f,%.0f,%.0f) -> (%.0f,%.0f,%.0f) [%s]", r.getName(), mn.x, mn.y, mn.z, mx.x, mx.y, mx.z, r.isEnabled() ? "enabled" : "disabled"))); } diff --git a/src/main/java/com/mythlane/gravityflip/command/GravityFlipToggleSubCommand.java b/src/main/java/com/mythlane/gravityflip/command/GravityFlipToggleSubCommand.java index 9ca123a..b616206 100644 --- a/src/main/java/com/mythlane/gravityflip/command/GravityFlipToggleSubCommand.java +++ b/src/main/java/com/mythlane/gravityflip/command/GravityFlipToggleSubCommand.java @@ -11,29 +11,16 @@ import com.mythlane.gravityflip.region.GravityFlipRegion; import javax.annotation.Nonnull; import java.util.logging.Level; -/** - * {@code /gravityflip toggle } — flippe le flag {@code enabled} d'une région - * sans toucher aux corners ni aux autres champs. - * - *

Étend {@link CommandBase} (utilisable console + joueur). - * - *

Flow : lookup région → lecture {@code isEnabled()} → {@code registry.setEnabled(name, !current)} - * → {@code configHolder.save().join()} → message avec le nouvel état. - * - *

Race (T-04-04-04) : lookup + setEnabled non atomique entre eux. Deux toggles - * simultanés sur le même nom produisent un état final indéterminé mais cohérent - * sur disque ({@code RegionRegistry.setEnabled} est synchronisé sur {@code mutationLock}). - * Acceptable v1 (single-operator builder). - */ +/** Enables/disables a Gravity Flip region and persists the change. */ public final class GravityFlipToggleSubCommand extends CommandBase { private final GravityFlipPlugin plugin; private final RequiredArg nameArg = - this.withRequiredArg("name", "Nom de la région à toggler", ArgTypes.STRING); + this.withRequiredArg("name", "Name of the region to toggle", ArgTypes.STRING); public GravityFlipToggleSubCommand(GravityFlipPlugin plugin) { - super("toggle", "Active/désactive une région Gravity Flip"); + super("toggle", "Enable/disable a Gravity Flip region"); this.plugin = plugin; } @@ -48,13 +35,13 @@ public final class GravityFlipToggleSubCommand extends CommandBase { } } if (found == null) { - ctx.sendMessage(Message.raw("[gravityflip] Région '" + name + "' introuvable.")); + ctx.sendMessage(Message.raw("[gravityflip] Region '" + name + "' not found.")); return; } boolean next = !found.isEnabled(); if (!plugin.regions().setEnabled(name, next)) { - // Course ultra-rare : région supprimée entre all() et setEnabled(). - ctx.sendMessage(Message.raw("[gravityflip] Région '" + name + "' introuvable.")); + // Rare race: region removed between all() and setEnabled(). + ctx.sendMessage(Message.raw("[gravityflip] Region '" + name + "' not found.")); return; } try { @@ -63,10 +50,10 @@ public final class GravityFlipToggleSubCommand extends CommandBase { plugin.getLogger().at(Level.WARNING).withCause(th) .log("[toggle] save failed for region '" + name + "'"); ctx.sendMessage(Message.raw( - "[gravityflip] Toggle en mémoire OK, persistance échouée — voir logs.")); + "[gravityflip] Toggled in memory, but persistence failed — see logs.")); return; } ctx.sendMessage(Message.raw( - "[gravityflip] Région '" + name + "' " + (next ? "activée" : "désactivée") + ".")); + "[gravityflip] Region '" + name + "' " + (next ? "enabled" : "disabled") + ".")); } } diff --git a/src/main/java/com/mythlane/gravityflip/command/GravityFlipTpSubCommand.java b/src/main/java/com/mythlane/gravityflip/command/GravityFlipTpSubCommand.java index c842b50..606c1e2 100644 --- a/src/main/java/com/mythlane/gravityflip/command/GravityFlipTpSubCommand.java +++ b/src/main/java/com/mythlane/gravityflip/command/GravityFlipTpSubCommand.java @@ -20,37 +20,16 @@ import com.mythlane.gravityflip.region.GravityFlipRegion; import javax.annotation.Nonnull; -/** - * {@code /gravityflip tp } — téléporte le joueur appelant au centre de - * l'AABB d'une région Gravity Flip. Outil debug/builder (CMD-06). - * - *

Étend {@link AbstractPlayerCommand} : tp ne fait sens que pour un joueur, - * la base class gère automatiquement le message "must be player" pour un appel console. - * - *

Pattern Teleport copié ligne-à-ligne sur - * {@code com.hypixel.hytale.builtin.teleport.commands.teleport.variant.TeleportToCoordinatesCommand} - * (lignes 48-68) : - *

    - *
  1. Lire {@link TransformComponent} (rotation corps) et {@link HeadRotation} - * (rotation tête) pour préserver l'orientation du joueur.
  2. - *
  3. Calculer le centre componentwise : {@code c = (min + max) / 2}.
  4. - *
  5. Construire via {@code Teleport.createForPlayer(pos, rotation).setHeadRotation(...)}.
  6. - *
  7. {@code store.addComponent(ref, Teleport.getComponentType(), teleport)} — - * le {@code TeleportSystem} du core consomme le composant au prochain tick.
  8. - *
- * - *

T-04-04-02 : permission auto-générée {@code mythlane.gravityflip.command.gravityflip.tp} - * sert de gate. Pas de restriction GameMode v1 (builder tool). - */ +/** Teleports the calling player to the center of a Gravity Flip region's AABB. */ public final class GravityFlipTpSubCommand extends AbstractPlayerCommand { private final GravityFlipPlugin plugin; private final RequiredArg nameArg = - this.withRequiredArg("name", "Nom de la région cible", ArgTypes.STRING); + this.withRequiredArg("name", "Name of the target region", ArgTypes.STRING); public GravityFlipTpSubCommand(GravityFlipPlugin plugin) { - super("tp", "Téléporte au centre d'une région Gravity Flip"); + super("tp", "Teleport to the center of a Gravity Flip region"); this.plugin = plugin; } @@ -69,7 +48,7 @@ public final class GravityFlipTpSubCommand extends AbstractPlayerCommand { } } if (target == null) { - ctx.sendMessage(Message.raw("[gravityflip] Région '" + name + "' introuvable.")); + ctx.sendMessage(Message.raw("[gravityflip] Region '" + name + "' not found.")); return; } @@ -82,13 +61,12 @@ public final class GravityFlipTpSubCommand extends AbstractPlayerCommand { TransformComponent tc = store.getComponent(ref, TransformComponent.getComponentType()); if (tc == null) { ctx.sendMessage(Message.raw( - "[gravityflip] TransformComponent manquant — tp impossible.")); + "[gravityflip] TransformComponent missing — teleport impossible.")); return; } HeadRotation hr = store.getComponent(ref, HeadRotation.getComponentType()); - // Préserve l'orientation courante : body rotation depuis Transform, head rotation - // si disponible (sinon on laisse la default côté Teleport.createForPlayer). + // Preserve current orientation: body rotation from Transform, head rotation when available. Vector3f bodyRotation = tc.getRotation().clone(); Teleport teleport = Teleport.createForPlayer( new Vector3d(cx, cy, cz), bodyRotation); @@ -98,7 +76,7 @@ public final class GravityFlipTpSubCommand extends AbstractPlayerCommand { store.addComponent(ref, Teleport.getComponentType(), teleport); ctx.sendMessage(Message.raw(String.format( - "[gravityflip] Téléporté au centre de '%s' : (%.0f,%.0f,%.0f)", + "[gravityflip] Teleported to the center of '%s': (%.0f,%.0f,%.0f)", name, cx, cy, cz))); } } diff --git a/src/main/java/com/mythlane/gravityflip/command/GravityFlipWandSubCommand.java b/src/main/java/com/mythlane/gravityflip/command/GravityFlipWandSubCommand.java index 089dc76..b941ee1 100644 --- a/src/main/java/com/mythlane/gravityflip/command/GravityFlipWandSubCommand.java +++ b/src/main/java/com/mythlane/gravityflip/command/GravityFlipWandSubCommand.java @@ -14,27 +14,13 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import javax.annotation.Nonnull; -/** - * {@code /gravityflip wand} — donne un Gravity Flip Wand au joueur appelant. - * - *

Pattern copié ligne-à-ligne sur - * {@code com.hypixel.hytale.server.core.command.commands.player.inventory.GiveCommand} - * (cf. GiveCommand.java:47-76). La seule différence : l'item est résolu par - * constante ({@link #WAND_ITEM_ID}) au lieu d'un {@code RequiredArg}. - * - *

L'item {@code gravityflip_wand} est enregistré via le JSON bundle - * {@code src/main/resources/Items/gravityflip_wand.json} (Phase 04-01). - * Si l'asset n'est pas chargé (JSON absent / pas picked-up par l'AssetStore - * core), le lookup renvoie {@code null} et la commande envoie un message - * d'erreur clair au lieu de crasher (T-04-02 scope). - */ +/** {@code /gravityflip wand} — gives a Gravity Flip Wand to the calling player. */ public final class GravityFlipWandSubCommand extends AbstractPlayerCommand { - /** ItemID du wand défini dans {@code Items/gravityflip_wand.json} (Phase 04-01). */ private static final String WAND_ITEM_ID = "gravityflip_wand"; public GravityFlipWandSubCommand() { - super("wand", "Obtenir un Gravity Flip Wand"); + super("wand", "Get a Gravity Flip Wand"); } @Override @@ -47,22 +33,22 @@ public final class GravityFlipWandSubCommand extends AbstractPlayerCommand { if (item == null) { context.sendMessage(Message.raw( "[gravityflip] Item '" + WAND_ITEM_ID - + "' introuvable — asset pas chargé ?")); + + "' not found — asset not loaded?")); return; } Player playerComponent = store.getComponent(ref, Player.getComponentType()); if (playerComponent == null) { - context.sendMessage(Message.raw("[gravityflip] Player component manquant.")); + context.sendMessage(Message.raw("[gravityflip] Player component missing.")); return; } ItemStack stack = new ItemStack(item.getId(), 1, null); ItemStackTransaction transaction = playerComponent.giveItem(stack, ref, store); ItemStack remainder = transaction.getRemainder(); if (remainder == null || remainder.isEmpty()) { - context.sendMessage(Message.raw("[gravityflip] Wand ajouté à ton inventaire.")); + context.sendMessage(Message.raw("[gravityflip] Wand added to your inventory.")); } else { context.sendMessage(Message.raw( - "[gravityflip] Inventaire plein — impossible d'ajouter le wand.")); + "[gravityflip] Inventory full — cannot add the wand.")); } } } diff --git a/src/test/java/com/mythlane/gravityflip/command/DefineValidationTest.java b/src/test/java/com/mythlane/gravityflip/command/DefineValidationTest.java index f721b00..3404af3 100644 --- a/src/test/java/com/mythlane/gravityflip/command/DefineValidationTest.java +++ b/src/test/java/com/mythlane/gravityflip/command/DefineValidationTest.java @@ -6,21 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Pure-data tests for {@link DefineValidation} — no Hytale runtime dependency. - * - *

Covers : - *

    - *
  • Name regex accepts alnum + underscore + dash, 1..32 chars.
  • - *
  • Name regex rejects blank, spaces, special chars, non-ASCII, over-length.
  • - *
  • Componentwise min/max return smallest/largest per axis.
  • - *
  • Inflate-max-by-1 convention (max block is INSIDE the AABB only if we add 1 per axis).
  • - *
- */ +/** Pure-data tests for {@link DefineValidation} — name regex, componentwise min/max, inflate-max-by-1 convention. */ class DefineValidationTest { - // ---------- name regex ---------- - @Test void validName_acceptsAlnumUnderscoreDash() { assertTrue(DefineValidation.isValidName("abc")); @@ -36,7 +24,7 @@ class DefineValidationTest { assertFalse(DefineValidation.isValidName("")); assertFalse(DefineValidation.isValidName(" ")); assertFalse(DefineValidation.isValidName("my zone")); - assertFalse(DefineValidation.isValidName("nom avec espaces")); + assertFalse(DefineValidation.isValidName("name with spaces")); assertFalse(DefineValidation.isValidName("a\tb")); assertFalse(DefineValidation.isValidName(" leading")); assertFalse(DefineValidation.isValidName("trailing ")); @@ -47,8 +35,6 @@ class DefineValidationTest { assertFalse(DefineValidation.isValidName("has.dot")); } - // ---------- componentwise min/max ---------- - @Test void componentwiseMin_returnsSmallestPerAxis() { int[] a = {5, 10, -3}; @@ -75,22 +61,13 @@ class DefineValidationTest { DefineValidation.componentwiseMax(b, a)); } - // ---------- inflate-max convention (block inclusion) ---------- - - /** - * A block occupies the cube between (x,y,z) and (x+1,y+1,z+1). If an AABB's max is the - * raw block coord (maxBlockX, maxBlockY, maxBlockZ), a player standing on top of that - * block is OUTSIDE the AABB. We therefore inflate max by +1 per axis. - */ + /** Blocks occupy the unit cube [x,x+1], so max must be inflated by +1 per axis to include the max block. */ @Test void boxFromCorners_inflateMax_includesMaxBlock() { int[] mn = {0, 64, 0}; int[] mx = {10, 70, 10}; - // Player feet at (10.5, 70.5, 10.5) — standing in the maxBlock cube. double px = 10.5, py = 70.5, pz = 10.5; - // Without inflate: max = (10,70,10) — player OUT. assertFalse(px < mx[0] && py < mx[1] && pz < mx[2]); - // With inflate: max = (11,71,11) — player IN. int[] mxInflated = {mx[0] + 1, mx[1] + 1, mx[2] + 1}; assertTrue(px < mxInflated[0] && py < mxInflated[1] && pz < mxInflated[2]); }