From 5e512cae1cfb1722661aa32b5b0d678a23f39e88 Mon Sep 17 00:00:00 2001 From: XevianLight <63034748+XevianLight@users.noreply.github.com> Date: Sun, 25 Jan 2026 19:56:43 -0700 Subject: [PATCH] RocketEntites can now launch to a specified dimension and position. Added /aphelion rocket (entity) destination, launch, and launchTo --- .../aphelion/client/AphelionClient.java | 17 + .../aphelion/commands/AphelionCommand.java | 262 ++++++++---- .../aphelion/core/KeyVariables.java | 69 ++++ ...phelionRocketDimensionChangeException.java | 12 + .../entites/vehicles/RocketEntity.java | 377 +++++++++++++++++- .../entites/vehicles/RocketRenderer.java | 6 + .../aphelion/event/ModBusEvents.java | 11 +- .../aphelion/network/KeyNetwork.java | 28 ++ .../network/RocketPayloadHandlers.java | 32 ++ .../network/packet/RocketLaunchPayload.java | 24 ++ .../resources/assets/aphelion/lang/en_us.json | 7 +- .../data/aphelion/dimension/test.json | 8 +- .../data/data/aphelion/dimension/test.json | 8 +- .../data/aphelion/dimension_type/test.json | 6 +- 14 files changed, 757 insertions(+), 110 deletions(-) create mode 100644 src/main/java/net/xevianlight/aphelion/core/KeyVariables.java create mode 100644 src/main/java/net/xevianlight/aphelion/entites/vehicles/AphelionRocketDimensionChangeException.java create mode 100644 src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java create mode 100644 src/main/java/net/xevianlight/aphelion/network/RocketPayloadHandlers.java create mode 100644 src/main/java/net/xevianlight/aphelion/network/packet/RocketLaunchPayload.java diff --git a/src/main/java/net/xevianlight/aphelion/client/AphelionClient.java b/src/main/java/net/xevianlight/aphelion/client/AphelionClient.java index 0462342..62f9366 100644 --- a/src/main/java/net/xevianlight/aphelion/client/AphelionClient.java +++ b/src/main/java/net/xevianlight/aphelion/client/AphelionClient.java @@ -1,5 +1,7 @@ package net.xevianlight.aphelion.client; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.PreparableReloadListener; @@ -11,10 +13,12 @@ import net.neoforged.fml.common.Mod; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; import net.neoforged.neoforge.client.event.RegisterDimensionSpecialEffectsEvent; +import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; import net.neoforged.neoforge.client.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.client.dimension.AphelionDimensionRenderers; +import org.lwjgl.glfw.GLFW; import java.util.function.BiConsumer; @@ -23,6 +27,14 @@ import java.util.function.BiConsumer; // You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent @EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT) public class AphelionClient { + + public static final KeyMapping ROCKET_LAUNCH_KEY = new KeyMapping( + "key.aphelion.rocket_launch", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_SPACE, + "key.categories.aphelion" + ); + public AphelionClient(ModContainer container) { // Allows NeoForge to create a config screen for this mod's configs. // The config screen is accessed by going to the Mods screen > clicking on your mod > clicking on config. @@ -54,4 +66,9 @@ public class AphelionClient { new net.xevianlight.aphelion.client.dimension.SpaceSkyEffects(null) ); } + + @SubscribeEvent + public static void registerKeys(RegisterKeyMappingsEvent event) { + event.register(ROCKET_LAUNCH_KEY); + } } diff --git a/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java b/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java index 0c82f8e..3e009b0 100644 --- a/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java +++ b/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java @@ -1,6 +1,5 @@ package net.xevianlight.aphelion.commands; -import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.LongArgumentType; @@ -8,24 +7,26 @@ import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.*; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.commands.arguments.coordinates.ColumnPosArgument; +import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.ComponentUtils; -import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.*; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ColumnPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.RelativeMovement; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.core.space.SpacePartitionSavedData; import net.xevianlight.aphelion.entites.vehicles.RocketEntity; import net.xevianlight.aphelion.util.RocketStructure; import net.xevianlight.aphelion.util.SpacePartitionHelper; +import org.jetbrains.annotations.NotNull; import java.util.EnumSet; @@ -52,7 +53,7 @@ public class AphelionCommand { true ); - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -70,7 +71,7 @@ public class AphelionCommand { true ); - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -96,7 +97,7 @@ public class AphelionCommand { ); } - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -117,7 +118,7 @@ public class AphelionCommand { ); } - return Command.SINGLE_SUCCESS; + return 1; }) ) .then(Commands.literal("all") @@ -131,7 +132,7 @@ public class AphelionCommand { true ); - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -146,22 +147,14 @@ public class AphelionCommand { long key = SpacePartitionSavedData.pack(x,z); - Component clickableOutput = Component.literal(String.valueOf(key)) - .withStyle(style -> style - .withClickEvent(new ClickEvent( - ClickEvent.Action.COPY_TO_CLIPBOARD, - String.valueOf(key) - )) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) - .withColor(ChatFormatting.AQUA) - ); + Component clickableOutput = getClickableId(String.valueOf(key), ChatFormatting.AQUA); context.getSource().sendSuccess( () -> Component.translatable("command.aphelion.station.orbit.debug.posToKey", x, z, clickableOutput), true ); - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -178,22 +171,14 @@ public class AphelionCommand { String stationCoord = x + " " + z; - Component clickableOutput = ComponentUtils.wrapInSquareBrackets(Component.literal(stationCoord)) - .withStyle(style -> style - .withClickEvent(new ClickEvent( - ClickEvent.Action.COPY_TO_CLIPBOARD, - stationCoord - )) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) - .withColor(ChatFormatting.GREEN) - ); + Component clickableOutput = getClickablePos(stationCoord, ChatFormatting.GREEN); context.getSource().sendSuccess( () -> Component.translatable("command.aphelion.station.orbit.debug.keyToPos", key, clickableOutput), true ); - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -206,22 +191,14 @@ public class AphelionCommand { String stationCoord = SpacePartitionHelper.get(x) + " " + SpacePartitionHelper.get(z); - Component clickableOutput = ComponentUtils.wrapInSquareBrackets(Component.literal(stationCoord)) - .withStyle(style -> style - .withClickEvent(new ClickEvent( - ClickEvent.Action.COPY_TO_CLIPBOARD, - stationCoord - )) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) - .withColor(ChatFormatting.GREEN) - ); + Component clickableOutput = getClickablePos(stationCoord, ChatFormatting.GREEN); context.getSource().sendSuccess( () -> Component.translatable("command.aphelion.station.orbit.debug.getPartition", x, z, clickableOutput), true ); - return Command.SINGLE_SUCCESS; + return 1; }))) ) ) @@ -241,25 +218,9 @@ public class AphelionCommand { long key = SpacePartitionSavedData.pack((int) x, (int) z); - Component clickablePos = ComponentUtils.wrapInSquareBrackets(Component.literal(stationCoord)) - .withStyle(style -> style - .withClickEvent(new ClickEvent( - ClickEvent.Action.COPY_TO_CLIPBOARD, - stationCoord - )) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) - .withColor(ChatFormatting.GREEN) - ); + Component clickablePos = getClickablePos(stationCoord, ChatFormatting.GREEN); - Component clickableId = Component.literal(String.valueOf(key)) - .withStyle(style -> style - .withClickEvent(new ClickEvent( - ClickEvent.Action.COPY_TO_CLIPBOARD, - String.valueOf(key) - )) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) - .withColor(ChatFormatting.AQUA) - ); + Component clickableId = getClickableId(String.valueOf(key), ChatFormatting.AQUA); ServerLevel space = player.getServer().getLevel(ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "space"))); @@ -271,14 +232,14 @@ public class AphelionCommand { true ); - return Command.SINGLE_SUCCESS; + return 1; } context.getSource().sendFailure( Component.translatable("command.aphelion.station.teleport.failure") ); - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -292,7 +253,7 @@ public class AphelionCommand { var player = context.getSource().getEntity(); if (player == null || player.getServer() == null) { context.getSource().sendFailure(Component.translatable("command.aphelion.station.teleport.failure")); - return Command.SINGLE_SUCCESS; + return 1; } var targetDim = DimensionArgument.getDimension(context, "dimension"); @@ -301,12 +262,12 @@ public class AphelionCommand { if (targetLevel == null) { context.getSource().sendFailure(Component.translatable("command.aphelion.station.teleport.failure.invalid_level")); - return Command.SINGLE_SUCCESS; + return 1; } player.teleportTo(targetLevel, player.position().x, player.position().y, player.position().z, EnumSet.noneOf(RelativeMovement.class), player.getYRot(), player.getXRot()); - return Command.SINGLE_SUCCESS; + return 1; })) ) ) @@ -317,8 +278,9 @@ public class AphelionCommand { s.add(0,0,0, Blocks.IRON_BLOCK.defaultBlockState()); }); context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.spawn.success"), true); + assert context.getSource().getEntity() != null; RocketEntity rocket = RocketEntity.spawnRocket(context.getSource().getLevel(), context.getSource().getEntity().blockPosition(), structure); - return Command.SINGLE_SUCCESS; + return 1; }) ) .then(Commands.argument("entity", EntityArgument.entity()) @@ -334,7 +296,7 @@ public class AphelionCommand { } else { context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); } - return Command.SINGLE_SUCCESS; + return 1; }) ) ) @@ -346,15 +308,7 @@ public class AphelionCommand { RocketStructure structure = rocket.getStructure(); CompoundTag tag = structure.save(); - Component clickableId = Component.literal(tag.toString()) - .withStyle(style -> style - .withClickEvent(new ClickEvent( - ClickEvent.Action.COPY_TO_CLIPBOARD, - tag.toString() - )) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) - .withColor(ChatFormatting.AQUA) - ); + Component clickableId = getClickableId(tag.toString(), ChatFormatting.AQUA); context.getSource().sendSuccess( () -> clickableId, @@ -363,13 +317,171 @@ public class AphelionCommand { } else { context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); } - return Command.SINGLE_SUCCESS; + return 1; }) ) ) + .then(Commands.literal("destination") + .then(Commands.literal("dimension") + .then(Commands.literal("set") + .then(Commands.argument("dim", DimensionArgument.dimension()) + .executes(context -> { + Entity entity = EntityArgument.getEntity(context, "entity"); + + if (entity instanceof RocketEntity rocket) { + ResourceKey dim = DimensionArgument.getDimension(context,"dim").dimension(); + + rocket.setTargetDim(dim); + + context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.set_dim.success", dim.location().toString()), + true + ); + } else { + context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); + } + + + return 1; + }) + ) + ) + .then(Commands.literal("get") + .executes(context -> { + Entity entity = EntityArgument.getEntity(context, "entity"); + + if (entity instanceof RocketEntity rocket) { + ResourceKey dim = rocket.getTargetDim(); + + Component clickableId = getClickableId(dim.location().toString(), ChatFormatting.AQUA); + + context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.get_dim.success", clickableId), + true + ); + } else { + context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); + } + + return 1; + }) + ) + ) + .then(Commands.literal("position") + .then(Commands.literal("set") + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(context -> { + Entity entity = EntityArgument.getEntity(context, "entity"); + + if (entity instanceof RocketEntity rocket) { + BlockPos pos = BlockPosArgument.getBlockPos(context, "pos"); + + rocket.setTargetPos(pos); + + context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.set_pos.success", pos.toString()), + true + ); + } else { + context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); + } + + return 1; + }) + ) + ) + .then(Commands.literal("get") + .executes(context -> { + Entity entity = EntityArgument.getEntity(context, "entity"); + + if (entity instanceof RocketEntity rocket) { + BlockPos pos = rocket.getTargetPos(); + + if (pos == null) { + context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.get_pos.success.null"), true); + return 1; + } + + Component clickablePos = getClickablePos(pos.toShortString(), ChatFormatting.GREEN); + + context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.get_pos.success", clickablePos), + true + ); + } else { + context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); + } + + return 1; + }) + ) + ) + ) + .then(Commands.literal("launch") + .executes(context -> { + Entity entity = EntityArgument.getEntity(context, "entity"); + + if (entity instanceof RocketEntity rocket) { + rocket.launch(); + } else { + context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); + } + return 1; + }) + ) + .then(Commands.literal("launchTo") + .then(Commands.argument("dim", DimensionArgument.dimension()) + .executes(context -> { + Entity entity = EntityArgument.getEntity(context, "entity"); + + if (entity instanceof RocketEntity rocket) { + var dim = DimensionArgument.getDimension(context, "dim").dimension(); + + rocket.launchTo(dim, null); + } else { + context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); + } + return 1; + }) + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(context -> { + Entity entity = EntityArgument.getEntity(context, "entity"); + + if (entity instanceof RocketEntity rocket) { + var dim = DimensionArgument.getDimension(context, "dim").dimension(); + var pos = BlockPosArgument.getBlockPos(context, "pos"); + rocket.launchTo(dim, pos); + } else { + context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid")); + } + return 1; + }) + ) + ) + ) ) ) ); } + + private static @NotNull Component getClickableId(String dim, ChatFormatting aqua) { + return Component.literal(dim) + .withStyle(style -> style + .withClickEvent(new ClickEvent( + ClickEvent.Action.COPY_TO_CLIPBOARD, + dim + )) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) + .withColor(aqua) + ); + } + + private static @NotNull Component getClickablePos(String pos, ChatFormatting green) { + return ComponentUtils.wrapInSquareBrackets(Component.literal(pos)) + .withStyle(style -> style + .withClickEvent(new ClickEvent( + ClickEvent.Action.COPY_TO_CLIPBOARD, + pos + )) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.copy.click"))) + .withColor(green) + ); + } } diff --git a/src/main/java/net/xevianlight/aphelion/core/KeyVariables.java b/src/main/java/net/xevianlight/aphelion/core/KeyVariables.java new file mode 100644 index 0000000..0c75956 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/KeyVariables.java @@ -0,0 +1,69 @@ +package net.xevianlight.aphelion.core; + +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.player.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class KeyVariables { + + public static final Map KEY_UP = new HashMap<>(); + public static final Map KEY_DOWN = new HashMap<>(); + public static final Map KEY_RIGHT = new HashMap<>(); + public static final Map KEY_LEFT = new HashMap<>(); + public static final Map KEY_JUMP = new HashMap<>(); + public static final Map KEY_TABLET = new HashMap<>(); + + public static boolean isHoldingUp(Player player) { + return player != null && KEY_UP.getOrDefault(player.getUUID(), false); + } + + public static boolean isHoldingDown(Player player) { + return player != null && KEY_DOWN.getOrDefault(player.getUUID(), false); + } + + public static boolean isHoldingRight(Player player) { + return player != null && KEY_RIGHT.getOrDefault(player.getUUID(), false); + } + + public static boolean isHoldingLeft(Player player) { + return player != null && KEY_LEFT.getOrDefault(player.getUUID(), false); + } + + public static boolean isHoldingJump(Player player) { + return player != null && KEY_JUMP.getOrDefault(player.getUUID(), false); + } + + public static boolean getHoldingTabletPress(Player player) { + return player != null && KEY_TABLET.getOrDefault(player.getUUID(), false); + } + + + public static Map getKey(Minecraft minecraft) { + Map key = new HashMap<>(); + key.put(minecraft.options.keyUp, "key_up"); + key.put(minecraft.options.keyDown, "key_down"); + key.put(minecraft.options.keyRight, "key_right"); + key.put(minecraft.options.keyLeft, "key_left"); + key.put(minecraft.options.keyJump, "key_jump"); + return key; + } + + public static void setKeyVariable(String key, UUID uuid, Boolean bool) { + switch (key) { + case "key_up": + KEY_UP.put(uuid, bool); + case "key_down": + KEY_DOWN.put(uuid, bool); + case "key_right": + KEY_RIGHT.put(uuid, bool); + case "key_left": + KEY_LEFT.put(uuid, bool); + case "key_jump": + KEY_JUMP.put(uuid, bool); + } + } +} diff --git a/src/main/java/net/xevianlight/aphelion/entites/vehicles/AphelionRocketDimensionChangeException.java b/src/main/java/net/xevianlight/aphelion/entites/vehicles/AphelionRocketDimensionChangeException.java new file mode 100644 index 0000000..d6c4e47 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/entites/vehicles/AphelionRocketDimensionChangeException.java @@ -0,0 +1,12 @@ +package net.xevianlight.aphelion.entites.vehicles; + +public class AphelionRocketDimensionChangeException extends RuntimeException { + + public AphelionRocketDimensionChangeException(String message) { + super("Failed to transfer rocket to dimension " + message); + } + + public AphelionRocketDimensionChangeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketEntity.java b/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketEntity.java index 1a26169..43e8198 100644 --- a/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketEntity.java +++ b/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketEntity.java @@ -1,24 +1,44 @@ package net.xevianlight.aphelion.entites.vehicles; import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.nbt.Tag; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ColumnPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.*; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.vehicle.VehicleEntity; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.portal.DimensionTransition; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; import net.neoforged.neoforge.fluids.FluidType; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.core.KeyVariables; import net.xevianlight.aphelion.core.init.ModEntities; import net.xevianlight.aphelion.util.RocketStructure; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class RocketEntity extends Entity implements IEntityWithComplexSpawn { +import java.util.List; +import java.util.Objects; + +public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpawn { RocketStructure structure = new RocketStructure(s -> { s.add(0,0,0, Blocks.NETHERITE_BLOCK.defaultBlockState()); @@ -26,6 +46,23 @@ public class RocketEntity extends Entity implements IEntityWithComplexSpawn { s.add(0,2,0, Blocks.NETHERITE_BLOCK.defaultBlockState()); }); + public enum FlightPhase { IDLE, PREPARE, ASCEND, TRANSIT, DESCEND, LANDED} + private ResourceKey targetDim = ResourceKey.create(Registries.DIMENSION, Aphelion.id("test")); + private @Nullable BlockPos targetPos = null; + + private double landingPosX; + private double landingPosZ; + + private static final double TELEPORT_Y = 600.0; + private static final double ASCEND_ACCEL = 0.05; + private static final double DESCEND_SPEED = 2; + + private double yVel = 0.0; + private boolean jumpWasDown = false; + + private static final EntityDataAccessor FLIGHT_PHASE = + SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.BYTE); + private static final EntityDataAccessor STRUCTURE_TAG = SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.COMPOUND_TAG); @@ -48,6 +85,183 @@ public class RocketEntity extends Entity implements IEntityWithComplexSpawn { return rocket; } + public void launchTo(ResourceKey dim, @Nullable BlockPos pos) { + if (level().isClientSide) return; + + this.targetDim = dim; + this.targetPos = pos; + + setPhase(FlightPhase.PREPARE); + } + + public void launch() { + if (level().isClientSide) return; + if (targetDim == null) return; + if (targetDim == this.level().dimension() && targetPos == null) return; + setPhase(FlightPhase.PREPARE); + } + + public Player getFirstPlayerPassenger() { + if (!this.getPassengers().isEmpty()) { + for (int i = 0; i < this.getPassengers().size(); i++) { + if (this.getPassengers().get(i) instanceof Player player) { + return player; + } + } + } + + return null; + } + + @Override + public void tick() { + super.tick(); + + if (!level().isClientSide) { + + switch (getPhase()) { + case IDLE, LANDED -> tickIdle(); + case PREPARE -> tickPrepare(); + case ASCEND -> tickAscend(); + case TRANSIT -> { + try { + tickTransit(); + } catch (AphelionRocketDimensionChangeException e) { + Aphelion.LOGGER.error("Rocket dimension transfer failed!", e); + setPhase(FlightPhase.IDLE); + resetDeltaMovement(); + } + } + case DESCEND -> tickDescend(); + } + // Simple upward movement + + } + + move(MoverType.SELF, getDeltaMovement()); + } + + private void tickIdle() { + resetDeltaMovement(); + } + + private void tickPrepare() { +// if (targetDim == this.level().dimension()) { +// setPhase(FlightPhase.IDLE); +// Aphelion.LOGGER.info("Target dimension matches current dimension"); +// return; +// } + setPhase(FlightPhase.ASCEND); + } + + private void tickAscend() { + setDeltaMovement(0, yVel, 0); + yVel += ASCEND_ACCEL; + + if (getY() >= TELEPORT_Y) { + resetDeltaMovement(); + setPhase(FlightPhase.TRANSIT); + } + } + + private void tickTransit() throws AphelionRocketDimensionChangeException { + if (!(level() instanceof ServerLevel src)) { + setPhase(FlightPhase.IDLE); + return; + } + if (targetDim == null) { + setPhase(FlightPhase.IDLE); + return; + } + + ServerLevel dst = src.getServer().getLevel(targetDim); + if (dst == null) { + setPhase(FlightPhase.IDLE); + return; + } + + // Set destination position to rockets current position OR defined landing coordinates + if (targetPos != null) { + landingPosX = targetPos.getX() + 0.5d; + landingPosZ = targetPos.getZ() + 0.5d; + } else { + landingPosX = getX(); + landingPosZ = getZ(); + } + + // Compute landing Y in destination + int hx = (int) Math.floor(landingPosX); + int hz = (int) Math.floor(landingPosZ); + + double arrivalY = TELEPORT_Y; + + var passengers = List.copyOf(getPassengers()); + + // Dismount before changing dims + ejectPassengers(); + + RocketEntity movedRocket = (RocketEntity) this.changeDimension(new DimensionTransition( + dst, + new Vec3(landingPosX, TELEPORT_Y, landingPosZ), + Vec3.ZERO, + getYRot(), + getXRot(), + DimensionTransition.PLAY_PORTAL_SOUND + )); + + if (movedRocket == null) { + throw new AphelionRocketDimensionChangeException(targetDim.location().toString()); + } + + movedRocket.moveTo(landingPosX, arrivalY, landingPosZ, getYRot(), getXRot()); + movedRocket.resetDeltaMovement(); + + for (Entity p : passengers) { + if (p instanceof ServerPlayer sp) { + sp.teleportTo(dst, landingPosX, arrivalY, landingPosZ, sp.getYRot(), sp.getXRot()); + sp.startRiding(movedRocket, true); + } else { + Entity movedP = p.changeDimension(new DimensionTransition( + dst, + new Vec3(landingPosX, TELEPORT_Y, landingPosZ), + Vec3.ZERO, + getYRot(), + getXRot(), + DimensionTransition.PLAY_PORTAL_SOUND + )); + if (movedP != null) { + movedP.moveTo(landingPosX, arrivalY, landingPosZ, movedP.getYRot(), movedP.getXRot()); + movedP.startRiding(movedRocket, true); + } + } + } + + movedRocket.setPhase(FlightPhase.DESCEND); + movedRocket.targetDim = targetDim; + movedRocket.targetPos = targetPos; + movedRocket.landingPosX = landingPosX; + movedRocket.landingPosZ = landingPosZ; + yVel = 0; + } + + private void tickDescend() { + if (isOnGround()) { + resetDeltaMovement(); + setPhase(FlightPhase.LANDED); + return; + } + setDeltaMovement(0, -DESCEND_SPEED, 0); + } + + private boolean isOnGround() { + BlockPos below = BlockPos.containing(getX(), getBoundingBox().minY - 0.01, getZ()); + return !level().getBlockState(below).isAir(); + } + + private void resetDeltaMovement() { + setDeltaMovement(0,0,0); + } + public RocketStructure getStructure() { return structure; } @@ -57,18 +271,41 @@ public class RocketEntity extends Entity implements IEntityWithComplexSpawn { } @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { + protected void defineSynchedData(@NotNull SynchedEntityData.Builder builder) { + super.defineSynchedData(builder); builder.define(STRUCTURE_TAG, new CompoundTag()); + builder.define(FLIGHT_PHASE, (byte) FlightPhase.IDLE.ordinal()); + } + + public FlightPhase getPhase() { + int idx = this.entityData.get(FLIGHT_PHASE); + FlightPhase[] values = FlightPhase.values(); + if (idx < 0 || idx >= values.length) return FlightPhase.IDLE; + return values[idx]; + } + + public void setPhase(FlightPhase phase) { + this.entityData.set(FLIGHT_PHASE, (byte) phase.ordinal()); + } + + @Override + protected @NotNull Item getDropItem() { + return Items.AIR; } public void setStructure(RocketStructure structure) { + double x = getX(), y = getY(), z = getZ(); + float yRot = getYRot(), xRot = getXRot(); this.structure.clear(); - CompoundTag tag = structure.save(); - this.structure.load(tag); + this.structure.load(structure.save()); + this.refreshDimensions(); this.setBoundingBox(this.makeBoundingBox()); - // sync to clients + + // prevent any internal “snap” from sticking + this.moveTo(x, y, z, yRot, xRot); + if (!level().isClientSide) { this.entityData.set(STRUCTURE_TAG, this.structure.save()); } @@ -77,23 +314,107 @@ public class RocketEntity extends Entity implements IEntityWithComplexSpawn { @Override public void onSyncedDataUpdated(@NotNull EntityDataAccessor key) { super.onSyncedDataUpdated(key); + + if (FLIGHT_PHASE.equals(key)) { + FlightPhase phase = getPhase(); + handleClientFlightPhaseChange(phase); + } + if (STRUCTURE_TAG.equals(key)) { CompoundTag tag = this.entityData.get(STRUCTURE_TAG); this.applyStructureTag(tag); } } + private void handleClientFlightPhaseChange(FlightPhase phase) { + switch (phase) { + case IDLE -> { +// var x = 0; + } + case PREPARE -> { +// var x = 1; + } + case ASCEND -> { +// var x = 2; + } + case TRANSIT -> { +// var x = 3; + } + case DESCEND -> { +// var x = 4; + } + case LANDED -> { +// var x = 5; + } + } + } + @Override protected void readAdditionalSaveData(CompoundTag tag) { if (tag.contains("RocketStructure")) { CompoundTag rocketTag = tag.getCompound("RocketStructure"); structure.load(rocketTag); + + // Immediately apply correct bbox on load (server + client) + double x = getX(), y = getY(), z = getZ(); + float yRot = getYRot(), xRot = getXRot(); + + refreshDimensions(); + setBoundingBox(makeBoundingBox()); + + moveTo(x, y, z, yRot, xRot); + } + if (tag.contains("TargetDim", Tag.TAG_STRING)) { + ResourceLocation rl = ResourceLocation.tryParse(tag.getString("TargetDim")); + if (rl != null) { + targetDim = ResourceKey.create(Registries.DIMENSION, rl); + } else { + targetDim = null; + } + } else { + targetDim = null; + } + if (tag.contains("TargetPos", Tag.TAG_LONG)) { + targetPos = BlockPos.of(tag.getLong("TargetPos")); + } else { + targetPos = null; + } + + landingPosX = tag.getDouble("LandingX"); + landingPosZ = tag.getDouble("LandingZ"); + + if (tag.contains("FlightPhase", Tag.TAG_BYTE)) { + setPhase(FlightPhase.values()[tag.getByte("FlightPhase")]); } } @Override protected void addAdditionalSaveData(CompoundTag tag) { tag.put("RocketStructure", structure.save()); + if (targetDim != null) + tag.putString("TargetDim", targetDim.location().toString()); + if (targetPos != null) + tag.putLong("TargetPos", targetPos.asLong()); + tag.putByte("FlightPhase", (byte) getPhase().ordinal()); + + tag.putDouble("LandingX", landingPosX); + tag.putDouble("LandingZ", landingPosZ); + } + + public BlockPos getTargetPos() { + return targetPos; + } + + public void setTargetPos(@Nullable BlockPos targetPos) { + this.targetPos = targetPos; + } + + public ResourceKey getTargetDim() { + return targetDim; + } + + public void setTargetDim(ResourceKey targetDim) { + this.targetDim = targetDim; } @Override @@ -130,25 +451,24 @@ public class RocketEntity extends Entity implements IEntityWithComplexSpawn { return computeWorldAABBFromStructure(); } - @Override @NotNull - public EntityDimensions getDimensions(@NotNull Pose pose) { - // Example: dynamic size you already compute for your rocket - EntityDimensions base = EntityDimensions.scalable(1, 1); - - // Put “eyes” near the top (1 block below top here) - return base.withEyeHeight(0); + @Override + public @NotNull EntityDimensions getDimensions(@NotNull Pose pose) { + RocketStructure.Extents e = structure.computeExtents(); + AABB local = e.toLocalAABB(); // local coords + float w = (float) Math.max(local.getXsize(), local.getZsize()); + float h = (float) local.getYsize(); + return EntityDimensions.scalable(w, h).withEyeHeight(Math.max(0.1f, h - 0.2f)); } @Override - public void tick() { - super.tick(); + protected void positionRider(@NotNull Entity passenger, @NotNull MoveFunction moveFunction) { + if (!this.hasPassenger(passenger)) return; - if (!level().isClientSide) { - // Simple upward movement - setDeltaMovement(0, 0, 0); - } + // Choose a stable seat position relative to the rocket. + // Example: centered, and 1.5 blocks above your base Y. + Vec3 seat = new Vec3(this.getX(), this.getY() + structure.computeExtents().toLocalAABB().getYsize() / 2, this.getZ()); - move(MoverType.SELF, getDeltaMovement()); + moveFunction.accept(passenger, seat.x, seat.y, seat.z); } @Override @@ -178,8 +498,8 @@ public class RocketEntity extends Entity implements IEntityWithComplexSpawn { } @Override - public boolean hurt(DamageSource source, float amount) { - return super.hurt(source, amount); + public boolean hurt(@NotNull DamageSource source, float amount) { + return false; } @Override @@ -187,4 +507,17 @@ public class RocketEntity extends Entity implements IEntityWithComplexSpawn { return true; } + @Override + protected boolean canAddPassenger(@NotNull Entity passenger) { + return passenger instanceof Player && getPassengers().isEmpty(); + } + + @Override + public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) { + if (!level().isClientSide) { + player.startRiding(this); + } + return InteractionResult.sidedSuccess(level().isClientSide); + } + } \ No newline at end of file diff --git a/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketRenderer.java b/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketRenderer.java index a87c99d..d832648 100644 --- a/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketRenderer.java +++ b/src/main/java/net/xevianlight/aphelion/entites/vehicles/RocketRenderer.java @@ -2,6 +2,7 @@ package net.xevianlight.aphelion.entites.vehicles; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.renderer.entity.EntityRenderer; @@ -31,6 +32,10 @@ public class RocketRenderer extends EntityRenderer { RocketStructure s = entity.getStructure(); + poseStack.pushPose(); + + poseStack.mulPose(Axis.YP.rotationDegrees(-entityYaw)); + for (int i = 0; i < s.size(); i++) { int p = s.packedPosAt(i); @@ -52,6 +57,7 @@ public class RocketRenderer extends EntityRenderer { poseStack.popPose(); } + poseStack.popPose(); super.render(entity, entityYaw, partialTicks, poseStack, buffers, packedLight); } diff --git a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java index a699219..0ec0575 100644 --- a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java +++ b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java @@ -15,8 +15,10 @@ import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity; import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity; import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity; import net.xevianlight.aphelion.core.init.ModBlockEntities; +import net.xevianlight.aphelion.network.RocketPayloadHandlers; import net.xevianlight.aphelion.network.ServerPayloadHandler; import net.xevianlight.aphelion.network.packet.PartitionData; +import net.xevianlight.aphelion.network.packet.RocketLaunchPayload; @EventBusSubscriber(modid = Aphelion.MOD_ID) public class ModBusEvents { @@ -40,7 +42,14 @@ public class ModBusEvents { registrar.playToClient( PartitionData.TYPE, PartitionData.STREAM_CODEC, - ServerPayloadHandler::handleDataOnMain); + ServerPayloadHandler::handleDataOnMain + ); + + registrar.playToServer( + RocketLaunchPayload.TYPE, + RocketLaunchPayload.STREAM_CODEC, + RocketPayloadHandlers::handleRocketLaunch + ); } } diff --git a/src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java b/src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java new file mode 100644 index 0000000..1b8cfc9 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java @@ -0,0 +1,28 @@ +package net.xevianlight.aphelion.network; + + +import net.minecraft.client.Minecraft; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.network.PacketDistributor; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.entites.vehicles.RocketEntity; +import net.xevianlight.aphelion.network.packet.RocketLaunchPayload; + +import net.xevianlight.aphelion.client.AphelionClient; + +@EventBusSubscriber(modid = Aphelion.MOD_ID) +public final class KeyNetwork { + + @SubscribeEvent + public static void onClientTick(ClientTickEvent.Post event) { + Minecraft mc = Minecraft.getInstance(); + if (mc.player == null) return; + + // consumeClick makes it fire once per press, not every tick held + if (AphelionClient.ROCKET_LAUNCH_KEY.consumeClick() && mc.player.getVehicle() instanceof RocketEntity rocket) { + PacketDistributor.sendToServer(new RocketLaunchPayload(rocket.getId())); + } + } +} diff --git a/src/main/java/net/xevianlight/aphelion/network/RocketPayloadHandlers.java b/src/main/java/net/xevianlight/aphelion/network/RocketPayloadHandlers.java new file mode 100644 index 0000000..53bc766 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/RocketPayloadHandlers.java @@ -0,0 +1,32 @@ +package net.xevianlight.aphelion.network; + +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.entites.vehicles.RocketEntity; +import net.xevianlight.aphelion.network.packet.RocketLaunchPayload; + +public final class RocketPayloadHandlers { + + public static void handleRocketLaunch(final RocketLaunchPayload payload, final IPayloadContext ctx) { + Aphelion.LOGGER.info("Rocket launch command received"); + + // Ensure we run on the server thread + ctx.enqueueWork(() -> { + if (!(ctx.player() instanceof ServerPlayer sp)) return; + + var level = sp.serverLevel(); + var e = level.getEntity(payload.rocketEntityId()); + if (!(e instanceof RocketEntity rocket)) return; + + // Security: only allow if the sender is actually riding this rocket + if (sp.getVehicle() != rocket) return; + + // Start launch + if (rocket.getPhase() == RocketEntity.FlightPhase.IDLE + || rocket.getPhase() == RocketEntity.FlightPhase.LANDED) { + rocket.launch(); + } + }); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/network/packet/RocketLaunchPayload.java b/src/main/java/net/xevianlight/aphelion/network/packet/RocketLaunchPayload.java new file mode 100644 index 0000000..7fec3f5 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/packet/RocketLaunchPayload.java @@ -0,0 +1,24 @@ +package net.xevianlight.aphelion.network.packet; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; + +public record RocketLaunchPayload(int rocketEntityId) implements CustomPacketPayload { + + public static final Type TYPE = + new Type<>(ResourceLocation.fromNamespaceAndPath("aphelion", "rocket_launch")); + + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ByteBufCodecs.VAR_INT, RocketLaunchPayload::rocketEntityId, + RocketLaunchPayload::new + ); + + @Override + public Type type() { + return TYPE; + } +} \ No newline at end of file diff --git a/src/main/resources/assets/aphelion/lang/en_us.json b/src/main/resources/assets/aphelion/lang/en_us.json index d4d5fdf..509563d 100644 --- a/src/main/resources/assets/aphelion/lang/en_us.json +++ b/src/main/resources/assets/aphelion/lang/en_us.json @@ -42,5 +42,10 @@ "command.aphelion.station.teleport.failure.invalid_level": "Failed to teleport, target level is null", "command.aphelion.station.orbit.debug.getPartition": "Partition of %s, %s is %s", "command.aphelion.rocket.entity_invalid": "Entity is not a valid rocket", - "command.aphelion.rocket.spawn.success": "Summoned new rocket" + "command.aphelion.rocket.spawn.success": "Summoned new rocket", + "command.aphelion.rocket.set_dim.success": "Set target dimension of rocket to %s", + "command.aphelion.rocket.get_dim.success": "Target dimension of rocket is %s", + "command.aphelion.rocket.set_pos.success": "Set target position of rocket to %s", + "command.aphelion.rocket.get_pos.success": "Target position of rocket is %s", + "command.aphelion.rocket.get_pos.success.null": "Target position of rocket is not set" } diff --git a/src/main/resources/data/aphelion/dimension/test.json b/src/main/resources/data/aphelion/dimension/test.json index fa45db2..7326c22 100644 --- a/src/main/resources/data/aphelion/dimension/test.json +++ b/src/main/resources/data/aphelion/dimension/test.json @@ -2,10 +2,10 @@ "type": "aphelion:test", "generator": { "type": "minecraft:noise", + "settings": "minecraft:overworld", "biome_source": { - "type": "minecraft:fixed", - "biome": "aphelion:test" - }, - "settings": "aphelion:test" + "type": "minecraft:multi_noise", + "preset": "minecraft:overworld" + } } } \ No newline at end of file diff --git a/src/main/resources/data/data/aphelion/dimension/test.json b/src/main/resources/data/data/aphelion/dimension/test.json index fa45db2..7326c22 100644 --- a/src/main/resources/data/data/aphelion/dimension/test.json +++ b/src/main/resources/data/data/aphelion/dimension/test.json @@ -2,10 +2,10 @@ "type": "aphelion:test", "generator": { "type": "minecraft:noise", + "settings": "minecraft:overworld", "biome_source": { - "type": "minecraft:fixed", - "biome": "aphelion:test" - }, - "settings": "aphelion:test" + "type": "minecraft:multi_noise", + "preset": "minecraft:overworld" + } } } \ No newline at end of file diff --git a/src/main/resources/data/data/aphelion/dimension_type/test.json b/src/main/resources/data/data/aphelion/dimension_type/test.json index 5046dfa..bfac4d8 100644 --- a/src/main/resources/data/data/aphelion/dimension_type/test.json +++ b/src/main/resources/data/data/aphelion/dimension_type/test.json @@ -5,11 +5,11 @@ "has_ceiling": false, "has_raids": true, "has_skylight": true, - "height": 256, + "height": 384, "effects": "aphelion:test", "infiniburn": "#minecraft:infiniburn_overworld", - "logical_height": 256, - "min_y": 0, + "logical_height": 384, + "min_y": -64, "monster_spawn_block_light_limit": 0, "monster_spawn_light_level": { "type": "minecraft:uniform",