RocketEntites can now launch to a specified dimension and position. Added /aphelion rocket (entity) destination, launch, and launchTo

This commit is contained in:
XevianLight
2026-01-25 19:56:43 -07:00
parent 59062724ff
commit 5e512cae1c
14 changed files with 757 additions and 110 deletions

View File

@@ -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);
}
}

View File

@@ -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<Level> 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<Level> 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)
);
}
}

View File

@@ -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<UUID, Boolean> KEY_UP = new HashMap<>();
public static final Map<UUID, Boolean> KEY_DOWN = new HashMap<>();
public static final Map<UUID, Boolean> KEY_RIGHT = new HashMap<>();
public static final Map<UUID, Boolean> KEY_LEFT = new HashMap<>();
public static final Map<UUID, Boolean> KEY_JUMP = new HashMap<>();
public static final Map<UUID, Boolean> 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<KeyMapping, String> getKey(Minecraft minecraft) {
Map<KeyMapping, String> 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<Level> 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<Byte> FLIGHT_PHASE =
SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.BYTE);
private static final EntityDataAccessor<CompoundTag> 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<Level> 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<Level> getTargetDim() {
return targetDim;
}
public void setTargetDim(ResourceKey<Level> 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);
}
}

View File

@@ -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<RocketEntity> {
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<RocketEntity> {
poseStack.popPose();
}
poseStack.popPose();
super.render(entity, entityYaw, partialTicks, poseStack, buffers, packedLight);
}

View File

@@ -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
);
}
}

View File

@@ -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()));
}
}
}

View File

@@ -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();
}
});
}
}

View File

@@ -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<RocketLaunchPayload> TYPE =
new Type<>(ResourceLocation.fromNamespaceAndPath("aphelion", "rocket_launch"));
public static final StreamCodec<ByteBuf, RocketLaunchPayload> STREAM_CODEC =
StreamCodec.composite(
ByteBufCodecs.VAR_INT, RocketLaunchPayload::rocketEntityId,
RocketLaunchPayload::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}