feat(04-03): add /gravityflip define <name> subcommand (CMD-02)
- GravityFlipDefineSubCommand: consume wand selection, componentwise min/max, inflate max +1, registry.add, save().join, clear selection - Wire into GravityFlipCommand ctor - Duplicate name → IllegalArgumentException → user message - Save failure → in-memory region preserved, truthful message
This commit is contained in:
@@ -21,7 +21,7 @@ public final class GravityFlipCommand extends AbstractCommandCollection {
|
||||
public GravityFlipCommand(GravityFlipPlugin plugin) {
|
||||
super("gravityflip", "Commandes de gestion des zones Gravity Flip");
|
||||
this.addSubCommand(new GravityFlipWandSubCommand());
|
||||
// 04-03 will addSubCommand(new GravityFlipDefineSubCommand(plugin));
|
||||
this.addSubCommand(new GravityFlipDefineSubCommand(plugin));
|
||||
// 04-04 will addSubCommand(new GravityFlipListSubCommand(plugin));
|
||||
// addSubCommand(new GravityFlipDeleteSubCommand(plugin));
|
||||
// addSubCommand(new GravityFlipToggleSubCommand(plugin));
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.mythlane.gravityflip.command;
|
||||
|
||||
import com.hypixel.hytale.component.Ref;
|
||||
import com.hypixel.hytale.component.Store;
|
||||
import com.hypixel.hytale.math.shape.Box;
|
||||
import com.hypixel.hytale.math.vector.Vector3d;
|
||||
import com.hypixel.hytale.server.core.Message;
|
||||
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
|
||||
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
|
||||
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
|
||||
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||
import com.hypixel.hytale.server.core.universe.world.World;
|
||||
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||
import com.mythlane.gravityflip.GravityFlipPlugin;
|
||||
import com.mythlane.gravityflip.region.GravityFlipRegion;
|
||||
import com.mythlane.gravityflip.wand.WandSelectionStore;
|
||||
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
|
||||
public GravityFlipDefineSubCommand(GravityFlipPlugin plugin) {
|
||||
super("define", "Créer une région Gravity Flip à partir de la sélection wand");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(@Nonnull CommandContext ctx,
|
||||
@Nonnull Store<EntityStore> store,
|
||||
@Nonnull Ref<EntityStore> ref,
|
||||
@Nonnull PlayerRef playerRef,
|
||||
@Nonnull World world) {
|
||||
String name = nameArg.get(ctx);
|
||||
if (!DefineValidation.isValidName(name)) {
|
||||
ctx.sendMessage(Message.raw(
|
||||
"[gravityflip] Nom invalide — attendu [a-zA-Z0-9_-]{1,32}."));
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = playerRef.getUuid();
|
||||
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."));
|
||||
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).
|
||||
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));
|
||||
GravityFlipRegion region = new GravityFlipRegion(name, box, true);
|
||||
|
||||
try {
|
||||
plugin.regions().add(region);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
ctx.sendMessage(Message.raw(
|
||||
"[gravityflip] Une région nommée '" + name + "' existe déjà."));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
plugin.configHolder().save().join();
|
||||
} catch (Throwable th) {
|
||||
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."));
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.wandSelections().clear(uuid);
|
||||
ctx.sendMessage(Message.raw(
|
||||
"[gravityflip] Région '" + name + "' créée : "
|
||||
+ "(" + mn[0] + "," + mn[1] + "," + mn[2] + ") → "
|
||||
+ "(" + (mx[0] + 1) + "," + (mx[1] + 1) + "," + (mx[2] + 1) + ")"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user