refactor(command): clean GSD comments and translate user-facing messages to English
This commit is contained in:
@@ -2,38 +2,19 @@ package com.mythlane.gravityflip.command;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Pure-data validation helpers for {@code /gravityflip define <name>}.
|
||||
*
|
||||
* <p>No Hytale runtime dependency — same philosophy as {@code FallDamageGuard}
|
||||
* and {@code WandSelectionStore}. Testable with JUnit alone.
|
||||
*
|
||||
* <p>Exposes :
|
||||
* <ul>
|
||||
* <li>{@link #isValidName(String)} — region name regex gate.</li>
|
||||
* <li>{@link #componentwiseMin(int[], int[])} / {@link #componentwiseMax(int[], int[])}
|
||||
* — so the builder can click pos1/pos2 in any order.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>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]),
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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).
|
||||
*
|
||||
* <p>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));
|
||||
|
||||
@@ -20,42 +20,16 @@ import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* {@code /gravityflip define <name>} — consume the caller's wand selection
|
||||
* (pos1 + pos2) to create and persist a new {@link GravityFlipRegion}.
|
||||
*
|
||||
* <p>Flow :
|
||||
* <ol>
|
||||
* <li>Validate {@code name} via {@link DefineValidation#isValidName}.</li>
|
||||
* <li>Read the caller's selection from {@link WandSelectionStore}. Bail out
|
||||
* if either pos1 or pos2 is unset (builder must click 2 blocks first).</li>
|
||||
* <li>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}).</li>
|
||||
* <li>{@code registry.add(region)} — throws {@link IllegalArgumentException}
|
||||
* on duplicate name (T-04-03-05 mitigation — synchronized mutationLock).</li>
|
||||
* <li>{@code configHolder.save().join()} forces durability before responding.
|
||||
* Cost : a few ms disk. Acceptable in an interactive command.</li>
|
||||
* <li>Clear the caller's selection — {@code define} consumes it, next define
|
||||
* requires re-clicking 2 blocks (avoid chained accidental defines).</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>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<String> 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) + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,30 +10,16 @@ import com.mythlane.gravityflip.GravityFlipPlugin;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* {@code /gravityflip delete <name>} — supprime la région nommée et persiste.
|
||||
*
|
||||
* <p>Étend {@link CommandBase} pour permettre l'usage depuis la console serveur
|
||||
* (opération admin, pas besoin d'un joueur caller).
|
||||
*
|
||||
* <p>Flow :
|
||||
* <ol>
|
||||
* <li>{@code registry.remove(name)} — renvoie {@code false} si nom inconnu → message
|
||||
* d'erreur clair et retour précoce (pas de save inutile).</li>
|
||||
* <li>{@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.</li>
|
||||
* </ol>
|
||||
*/
|
||||
/** Deletes a Gravity Flip region and persists the change. */
|
||||
public final class GravityFlipDeleteSubCommand extends CommandBase {
|
||||
|
||||
private final GravityFlipPlugin plugin;
|
||||
|
||||
private final RequiredArg<String> 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."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>É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(...)}.
|
||||
*
|
||||
* <p>Format de sortie (une ligne par région) :
|
||||
* <pre>
|
||||
* - test-zone-1 : (10,64,10) → (21,71,21) [enabled]
|
||||
* </pre>
|
||||
*
|
||||
* <p>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<GravityFlipRegion> 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")));
|
||||
}
|
||||
|
||||
@@ -11,29 +11,16 @@ import com.mythlane.gravityflip.region.GravityFlipRegion;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* {@code /gravityflip toggle <name>} — flippe le flag {@code enabled} d'une région
|
||||
* sans toucher aux corners ni aux autres champs.
|
||||
*
|
||||
* <p>Étend {@link CommandBase} (utilisable console + joueur).
|
||||
*
|
||||
* <p>Flow : lookup région → lecture {@code isEnabled()} → {@code registry.setEnabled(name, !current)}
|
||||
* → {@code configHolder.save().join()} → message avec le nouvel état.
|
||||
*
|
||||
* <p>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<String> 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") + "."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,37 +20,16 @@ import com.mythlane.gravityflip.region.GravityFlipRegion;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* {@code /gravityflip tp <name>} — téléporte le joueur appelant au centre de
|
||||
* l'AABB d'une région Gravity Flip. Outil debug/builder (CMD-06).
|
||||
*
|
||||
* <p>É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.
|
||||
*
|
||||
* <p>Pattern Teleport copié ligne-à-ligne sur
|
||||
* {@code com.hypixel.hytale.builtin.teleport.commands.teleport.variant.TeleportToCoordinatesCommand}
|
||||
* (lignes 48-68) :
|
||||
* <ol>
|
||||
* <li>Lire {@link TransformComponent} (rotation corps) et {@link HeadRotation}
|
||||
* (rotation tête) pour préserver l'orientation du joueur.</li>
|
||||
* <li>Calculer le centre componentwise : {@code c = (min + max) / 2}.</li>
|
||||
* <li>Construire via {@code Teleport.createForPlayer(pos, rotation).setHeadRotation(...)}.</li>
|
||||
* <li>{@code store.addComponent(ref, Teleport.getComponentType(), teleport)} —
|
||||
* le {@code TeleportSystem} du core consomme le composant au prochain tick.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>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<String> 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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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<Item>}.
|
||||
*
|
||||
* <p>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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user