feat(04-04): add list/delete/toggle subcommands (CommandBase, CMD-03/04/05)
- GravityFlipListSubCommand: iterate registry.all(), format 'name : (min) -> (max) [state]' - GravityFlipDeleteSubCommand: registry.remove + save, clear error on unknown name - GravityFlipToggleSubCommand: read current enabled, flip via setEnabled, save, report new state - All three extend CommandBase (works from server console, not just player) - Save-failure path: keep in-memory change, truthful message (mirror define pattern)
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
package com.mythlane.gravityflip.command;
|
||||||
|
|
||||||
|
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.CommandBase;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
public GravityFlipDeleteSubCommand(GravityFlipPlugin plugin) {
|
||||||
|
super("delete", "Supprime une région Gravity Flip");
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
plugin.configHolder().save().join();
|
||||||
|
} catch (Throwable th) {
|
||||||
|
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."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.sendMessage(Message.raw("[gravityflip] Région '" + name + "' supprimée."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.mythlane.gravityflip.command;
|
||||||
|
|
||||||
|
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.basecommands.CommandBase;
|
||||||
|
import com.mythlane.gravityflip.GravityFlipPlugin;
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public final class GravityFlipListSubCommand extends CommandBase {
|
||||||
|
|
||||||
|
private final GravityFlipPlugin plugin;
|
||||||
|
|
||||||
|
public GravityFlipListSubCommand(GravityFlipPlugin plugin) {
|
||||||
|
super("list", "Liste toutes les régions Gravity Flip");
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.sendMessage(Message.raw("[gravityflip] " + all.size() + " région(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]",
|
||||||
|
r.getName(), mn.x, mn.y, mn.z, mx.x, mx.y, mx.z,
|
||||||
|
r.isEnabled() ? "enabled" : "disabled")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.mythlane.gravityflip.command;
|
||||||
|
|
||||||
|
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.CommandBase;
|
||||||
|
import com.mythlane.gravityflip.GravityFlipPlugin;
|
||||||
|
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).
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
public GravityFlipToggleSubCommand(GravityFlipPlugin plugin) {
|
||||||
|
super("toggle", "Active/désactive une région Gravity Flip");
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executeSync(@Nonnull CommandContext ctx) {
|
||||||
|
String name = nameArg.get(ctx);
|
||||||
|
GravityFlipRegion found = null;
|
||||||
|
for (GravityFlipRegion r : plugin.regions().all()) {
|
||||||
|
if (r.getName().equals(name)) {
|
||||||
|
found = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found == null) {
|
||||||
|
ctx.sendMessage(Message.raw("[gravityflip] Région '" + name + "' introuvable."));
|
||||||
|
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."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
plugin.configHolder().save().join();
|
||||||
|
} catch (Throwable th) {
|
||||||
|
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."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.sendMessage(Message.raw(
|
||||||
|
"[gravityflip] Région '" + name + "' " + (next ? "activée" : "désactivée") + "."));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user