feat(04-01): register GravityFlipWand interaction + item JSON (WAND-01, WAND-02)
- GravityFlipWandInteraction extends SimpleInstantInteraction; Primary/Secondary routed through firstRun(), writes to WandSelectionStore via volatile bindStore() - Items/gravityflip_wand.json: Utility item (Icon Torch_Fire) whose Primary and Secondary Interactions reference Type=GravityFlipWand - GravityFlipPlugin.setup(): constructs WandSelectionStore, injects it into the interaction class, then registers via getCodecRegistry(Interaction.CODEC) (pattern from InstancesPlugin:158 / ExitInstanceInteraction) - Expose wandSelections() getter for Phase 04-02+ commands
This commit is contained in:
@@ -15,6 +15,9 @@ import com.mythlane.gravityflip.region.GravityFlipRegion;
|
|||||||
import com.mythlane.gravityflip.region.RegionRegistry;
|
import com.mythlane.gravityflip.region.RegionRegistry;
|
||||||
import com.mythlane.gravityflip.tick.RegionTickLoop;
|
import com.mythlane.gravityflip.tick.RegionTickLoop;
|
||||||
import com.mythlane.gravityflip.viz.RegionVisualizer;
|
import com.mythlane.gravityflip.viz.RegionVisualizer;
|
||||||
|
import com.mythlane.gravityflip.wand.GravityFlipWandInteraction;
|
||||||
|
import com.mythlane.gravityflip.wand.WandSelectionStore;
|
||||||
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
|
||||||
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -46,6 +49,7 @@ public class GravityFlipPlugin extends JavaPlugin {
|
|||||||
private GravityApplier gravityApplier;
|
private GravityApplier gravityApplier;
|
||||||
private FallDamageGuard fallDamageGuard;
|
private FallDamageGuard fallDamageGuard;
|
||||||
private RegionVisualizer regionVisualizer;
|
private RegionVisualizer regionVisualizer;
|
||||||
|
private WandSelectionStore wandSelectionStore;
|
||||||
|
|
||||||
public GravityFlipPlugin(JavaPluginInit init) {
|
public GravityFlipPlugin(JavaPluginInit init) {
|
||||||
super(init);
|
super(init);
|
||||||
@@ -69,6 +73,18 @@ public class GravityFlipPlugin extends JavaPlugin {
|
|||||||
getEntityStoreRegistry().registerSystem(new FallDamageSuppressorSystem(
|
getEntityStoreRegistry().registerSystem(new FallDamageSuppressorSystem(
|
||||||
fallDamageGuard,
|
fallDamageGuard,
|
||||||
th -> getLogger().at(Level.WARNING).withCause(th).log("fallDamageSuppressor handle failed")));
|
th -> getLogger().at(Level.WARNING).withCause(th).log("fallDamageSuppressor handle failed")));
|
||||||
|
|
||||||
|
// Plan 04-01 : Gravity Flip wand.
|
||||||
|
// Interaction binding pattern per 04-00 SPIKE-RESULT (Finding 3) — same shape as
|
||||||
|
// InstancesPlugin.java:158 / ExitInstanceInteraction. The JSON Item at
|
||||||
|
// src/main/resources/Items/gravityflip_wand.json references this Type in
|
||||||
|
// Interactions.Primary / Interactions.Secondary.
|
||||||
|
this.wandSelectionStore = new WandSelectionStore();
|
||||||
|
GravityFlipWandInteraction.bindStore(this.wandSelectionStore);
|
||||||
|
getCodecRegistry(Interaction.CODEC).register(
|
||||||
|
"GravityFlipWand",
|
||||||
|
GravityFlipWandInteraction.class,
|
||||||
|
GravityFlipWandInteraction.CODEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -151,6 +167,14 @@ public class GravityFlipPlugin extends JavaPlugin {
|
|||||||
/** Exposed for Phase 3 (gravity physics) and Phase 4 (commands). */
|
/** Exposed for Phase 3 (gravity physics) and Phase 4 (commands). */
|
||||||
public RegionRegistry regions() { return registry; }
|
public RegionRegistry regions() { return registry; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-player wand selection store. Populated by
|
||||||
|
* {@link GravityFlipWandInteraction}; consumed by
|
||||||
|
* {@code /gravityflip define} (Phase 04-02+).
|
||||||
|
* <p>Returns {@code null} until {@link #setup()} has run.
|
||||||
|
*/
|
||||||
|
public WandSelectionStore wandSelections() { return wandSelectionStore; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessor for the region config holder. <strong>SAVE CONTRACT:</strong> any
|
* Accessor for the region config holder. <strong>SAVE CONTRACT:</strong> any
|
||||||
* caller that mutates {@code configHolder().get().getRegions()} MUST call
|
* caller that mutates {@code configHolder().get().getRegions()} MUST call
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.mythlane.gravityflip.wand;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
||||||
|
import com.hypixel.hytale.component.CommandBuffer;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.protocol.BlockPosition;
|
||||||
|
import com.hypixel.hytale.protocol.InteractionType;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.entity.InteractionContext;
|
||||||
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
|
||||||
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gravity Flip wand interaction — reads Primary / Secondary clicks and pushes
|
||||||
|
* the targeted block position into a {@link WandSelectionStore} keyed by the
|
||||||
|
* clicker's player UUID.
|
||||||
|
*
|
||||||
|
* <p><b>Binding :</b> registered at {@code setup()} time via
|
||||||
|
* {@code getCodecRegistry(Interaction.CODEC).register("GravityFlipWand", …)} —
|
||||||
|
* same pattern as {@code ExitInstanceInteraction} (cf. 04-00 SPIKE-RESULT,
|
||||||
|
* Finding 3). The matching {@code "Type": "GravityFlipWand"} reference in
|
||||||
|
* {@code Items/gravityflip_wand.json} wires the click packet to this class.
|
||||||
|
*
|
||||||
|
* <p><b>One class for both click types :</b> {@link #firstRun} receives the
|
||||||
|
* {@link InteractionType}. Primary → {@code setPos1}, Secondary → {@code setPos2}.
|
||||||
|
* Centralising both in one class keeps a single registration entry and a
|
||||||
|
* single CODEC — any other split would duplicate wiring for no gain.
|
||||||
|
*
|
||||||
|
* <p><b>Store injection :</b> the Hytale CODEC instantiates interactions via a
|
||||||
|
* no-arg constructor, so we cannot inject the store through the constructor.
|
||||||
|
* Instead, {@link #bindStore(WandSelectionStore)} installs a {@code volatile}
|
||||||
|
* reference at plugin start. If the store is not bound (mis-wired plugin),
|
||||||
|
* {@link #firstRun} is a safe no-op — fail-silent.
|
||||||
|
*
|
||||||
|
* <p><b>Threat surface :</b>
|
||||||
|
* <ul>
|
||||||
|
* <li>Chat feedback uses {@link PlayerRef#sendMessage} — directed to the
|
||||||
|
* clicker only, no broadcast (T-04-01-03).</li>
|
||||||
|
* <li>{@code TargetBlock} coordinates are stored raw as {@code int[]} — no
|
||||||
|
* numeric processing in this plan; {@code /gravityflip define} (04-03)
|
||||||
|
* is responsible for clamping / validating before region creation
|
||||||
|
* (T-04-01-01).</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class GravityFlipWandInteraction extends SimpleInstantInteraction {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static final BuilderCodec<GravityFlipWandInteraction> CODEC =
|
||||||
|
((BuilderCodec.Builder) BuilderCodec
|
||||||
|
.builder(GravityFlipWandInteraction.class,
|
||||||
|
GravityFlipWandInteraction::new,
|
||||||
|
SimpleInstantInteraction.CODEC)
|
||||||
|
.documentation(
|
||||||
|
"Gravity Flip wand: Primary click sets pos1, "
|
||||||
|
+ "Secondary click sets pos2, for the clicker's selection."))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store shared by every instance of this interaction — injected at plugin
|
||||||
|
* start via {@link #bindStore}. Volatile so the writer thread ({@code setup()})
|
||||||
|
* publishes a safe reference to the reader threads (interaction dispatch).
|
||||||
|
*/
|
||||||
|
private static volatile WandSelectionStore STORE;
|
||||||
|
|
||||||
|
/** Wire the selection store before any click can be processed. Call once at {@code setup()}. */
|
||||||
|
public static void bindStore(WandSelectionStore store) {
|
||||||
|
STORE = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Required no-arg constructor used by the CODEC factory. */
|
||||||
|
public GravityFlipWandInteraction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void firstRun(@Nonnull InteractionType type,
|
||||||
|
@Nonnull InteractionContext context,
|
||||||
|
@Nonnull CooldownHandler cooldownHandler) {
|
||||||
|
WandSelectionStore store = STORE;
|
||||||
|
if (store == null) {
|
||||||
|
// Plugin mis-wired (bindStore never called). Silent no-op — don't crash the click.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<EntityStore> entityRef = context.getEntity();
|
||||||
|
CommandBuffer<EntityStore> commandBuffer = context.getCommandBuffer();
|
||||||
|
if (entityRef == null || commandBuffer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerRef playerRef = commandBuffer.getComponent(entityRef, PlayerRef.getComponentType());
|
||||||
|
if (playerRef == null) {
|
||||||
|
// Clicker is not a player (mob, arrow, …) — ignore.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockPosition bp = context.getTargetBlock();
|
||||||
|
if (bp == null) {
|
||||||
|
playerRef.sendMessage(Message.raw("[gravityflip] No block targeted."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID uuid = playerRef.getUuid();
|
||||||
|
if (type == InteractionType.Primary) {
|
||||||
|
store.setPos1(uuid, bp.x, bp.y, bp.z);
|
||||||
|
playerRef.sendMessage(Message.raw(
|
||||||
|
"[gravityflip] pos1 set: (%d, %d, %d)".formatted(bp.x, bp.y, bp.z)));
|
||||||
|
} else if (type == InteractionType.Secondary) {
|
||||||
|
store.setPos2(uuid, bp.x, bp.y, bp.z);
|
||||||
|
playerRef.sendMessage(Message.raw(
|
||||||
|
"[gravityflip] pos2 set: (%d, %d, %d)".formatted(bp.x, bp.y, bp.z)));
|
||||||
|
}
|
||||||
|
// Any other InteractionType (Ability1, Pick, Equipped, …) is ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"Icon": "Torch_Fire",
|
||||||
|
"TranslationProperties": {
|
||||||
|
"Translation": "Gravity Flip Wand"
|
||||||
|
},
|
||||||
|
"ResourceType": "Utility",
|
||||||
|
"MaxStackSize": 1,
|
||||||
|
"Interactions": {
|
||||||
|
"Primary": {
|
||||||
|
"Type": "GravityFlipWand"
|
||||||
|
},
|
||||||
|
"Secondary": {
|
||||||
|
"Type": "GravityFlipWand"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user