RocketEntity added. Uses RocketRenderer and RocketStructure to render blocks. RocketStructure supports volumes up to 128^3.

This commit is contained in:
XevianLight
2026-01-24 21:24:20 -07:00
parent 5500b78e53
commit 59062724ff
29 changed files with 769 additions and 52 deletions

View File

@@ -1,13 +1,17 @@
package net.xevianlight.aphelion;
import net.minecraft.client.renderer.entity.ThrownItemRenderer;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
import net.neoforged.neoforge.event.AddReloadListenerEvent;
import net.xevianlight.aphelion.block.dummy.renderer.MultiblockDummyRenderer;
import net.xevianlight.aphelion.client.AphelionConfig;
import net.xevianlight.aphelion.planet.AphelionPlanetJSONLoader;
import net.xevianlight.aphelion.core.init.*;
import net.xevianlight.aphelion.fluid.BaseFluidType;
import net.xevianlight.aphelion.fluid.ModFluidTypes;
@@ -18,6 +22,7 @@ import net.xevianlight.aphelion.screen.ModMenuTypes;
import net.xevianlight.aphelion.screen.TestBlockScreen;
import net.xevianlight.aphelion.screen.VacuumArcFurnaceScreen;
import org.slf4j.Logger;
import net.xevianlight.aphelion.entites.vehicles.RocketRenderer;
import com.mojang.logging.LogUtils;
@@ -59,6 +64,7 @@ public class Aphelion {
ModFluids.register(MOD_BUS);
ModSounds.register(MOD_BUS);
ModRecipes.register(MOD_BUS);
ModEntities.register(MOD_BUS);
// Register ourselves for server and other game events we are interested in.
// Note that this is necessary if and only if we want *this* class (ExtremeRocketry) to respond directly to events.
@@ -74,6 +80,10 @@ public class Aphelion {
modContainer.registerConfig(ModConfig.Type.COMMON, AphelionConfig.SPEC);
}
public static ResourceLocation id(String path) {
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, path);
}
private void commonSetup(FMLCommonSetupEvent event) {
}
@@ -97,6 +107,12 @@ public class Aphelion {
}
@SubscribeEvent
public void onAddReloadListeners(AddReloadListenerEvent event) {
// Set up the planet json listener. This reloads on /reload
event.addListener(new AphelionPlanetJSONLoader());
}
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
@EventBusSubscriber(modid = MOD_ID, value = Dist.CLIENT)
public static class ClientModEvents {
@@ -124,5 +140,10 @@ public class Aphelion {
event.register(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), ElectricArcFurnaceScreen::new);
event.register(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), VacuumArcFurnaceScreen::new);
}
@SubscribeEvent
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(ModEntities.ROCKET.get(), RocketRenderer::new);
}
}
}

View File

@@ -68,7 +68,8 @@ public class VacuumArcFurnaceController extends BaseEntityBlock {
.sound(SoundType.NETHERITE_BLOCK)
.destroyTime(2f)
.explosionResistance(10f)
.requiresCorrectToolForDrops();
.requiresCorrectToolForDrops()
.lightLevel(state -> state.getValue(BlockStateProperties.LIT) ? 15 : 0);
}
public static Item.Properties getItemProperties() {

View File

@@ -24,7 +24,7 @@ import org.jetbrains.annotations.Nullable;
public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMultiblockPart {
@Nullable private BlockPos controllerPos;
private BlockState mimicing = Blocks.AIR.defaultBlockState();
private BlockState mimicking = Blocks.AIR.defaultBlockState();
@Nullable
private ItemStackHandler getControllerInventory() {
@@ -116,7 +116,7 @@ public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMult
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.saveAdditional(tag, registries);
if (controllerPos != null) tag.putLong("controller", controllerPos.asLong());
tag.put("mimic", NbtUtils.writeBlockState(mimicing));
tag.put("mimic", NbtUtils.writeBlockState(mimicking));
}
@Override
@@ -170,12 +170,12 @@ public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMult
@Override
public BlockState getMimicing() {
return mimicing;
return mimicking;
}
@Override
public void setMimicing(BlockState newState) {
mimicing = newState;
mimicking = newState;
setChanged();
if (level != null) {
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3);

View File

@@ -1,7 +1,6 @@
package net.xevianlight.aphelion.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.neoforged.api.distmarker.Dist;
@@ -44,6 +43,7 @@ public class AphelionClient {
}
public static void onAddReloadListener(BiConsumer<ResourceLocation, PreparableReloadListener> consumer) {
// Set up the dimension renderers json listener. This reloads on F3+T
consumer.accept(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "planet_renderers"), new AphelionDimensionRenderers());
}
@@ -51,7 +51,7 @@ public class AphelionClient {
public static void onRegisterDimensionEffects(RegisterDimensionSpecialEffectsEvent event) {
event.register(
ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "space"),
new net.xevianlight.aphelion.client.dimension.SpaceSkyEffects()
new net.xevianlight.aphelion.client.dimension.SpaceSkyEffects(null)
);
}
}

View File

@@ -2,6 +2,7 @@ package net.xevianlight.aphelion.client;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.dimension.DimensionType;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.api.distmarker.Dist;
@@ -21,13 +22,16 @@ public class AphelionDebugOverlay {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null) return;
// Only show in your space dimension (optional)
if (!mc.level.dimension().location().equals(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "space"))) {
return;
}
// // Only show in your space dimension (optional)
// if (!mc.level.dimension().location().equals(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "space"))) {
// return;
// }
var camPos = mc.gameRenderer.getMainCamera().getPosition();
ResourceLocation orbitId = SpaceSkyEffects.orbitForPos(camPos);
DimensionType type = mc.level.dimensionType();
ResourceLocation effectsId = type.effectsLocation();
var camPos = mc.gameRenderer.getMainCamera();
ResourceLocation orbitId = SpaceSkyEffects.resolvedId(effectsId, camPos);
DimensionRenderer r = DimensionRendererCache.getOrDefault(orbitId);

View File

@@ -1,11 +1,14 @@
package net.xevianlight.aphelion.client.dimension;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.PartitionClientState;
import net.xevianlight.aphelion.util.SpacePartitionHelper;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
@@ -56,5 +59,23 @@ public class DimensionSkyEffects extends DimensionSpecialEffects {
return new float[]{0,0,0,0};
}
public static ResourceLocation orbitForPos(Vec3 pos) {
int x = SpacePartitionHelper.get(pos.x);
int z = SpacePartitionHelper.get(pos.z);
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
// int px = PartitionClientState.pxOr(0);
// int py = PartitionClientState.pyOr(0);
var data = ResourceLocation.parse(PartitionClientState.idOrUnknown());
// var data = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
if (data != null) return data;
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
}
}

View File

@@ -1,9 +0,0 @@
package net.xevianlight.aphelion.client.dimension;
public class DimensionSkyRenderer {
DimensionRenderer renderer;
DimensionSkyRenderer(DimensionRenderer renderer) { this.renderer = renderer; }
}

View File

@@ -1,12 +0,0 @@
package net.xevianlight.aphelion.client.dimension;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
public record OrbitSkyDefinition(
ResourceLocation id,
Vec3 skyColor
) {
}

View File

@@ -12,14 +12,27 @@ import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.PartitionClientState;
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
import net.xevianlight.aphelion.util.SpacePartitionHelper;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
public class SpaceSkyEffects extends DimensionSpecialEffects {
public SpaceSkyEffects() {
private final ResourceLocation effectsId;
public SpaceSkyEffects(@Nullable ResourceLocation effectsId) {
super(192, false, SkyType.NORMAL, false, false);
this.effectsId = effectsId;
}
public static ResourceLocation resolvedId(ResourceLocation effectsId, Camera camera) {
if (effectsId == null) {
return ResourceLocation.withDefaultNamespace("overworld");
}
if (effectsId.equals(Aphelion.id("space"))) {
return SpaceSkyEffects.orbitForPos(camera.getPosition()); // or inline this logic
}
return effectsId;
}
@Override
@@ -49,8 +62,8 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
@Override
public boolean isFoggyAt(int i, int i1) {
ResourceLocation id = orbitForPos(net.minecraft.client.Minecraft.getInstance()
.gameRenderer.getMainCamera().getPosition());
ResourceLocation id = resolvedId(effectsId ,net.minecraft.client.Minecraft.getInstance()
.gameRenderer.getMainCamera());
return DimensionRendererCache.getOrDefault(id).hasThickFog();
}

View File

@@ -10,6 +10,7 @@ import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.*;
import net.minecraft.commands.arguments.coordinates.ColumnPosArgument;
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;
@@ -17,10 +18,13 @@ import net.minecraft.network.chat.HoverEvent;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
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 java.util.EnumSet;
@@ -62,7 +66,7 @@ public class AphelionCommand {
SpacePartitionSavedData.get(level).overwriteAllExistingOrbits(orbit);
context.getSource().sendSuccess(
() -> Component.translatable("command.aphelion.station.orbit.overwriteall", orbit.toString()),
() -> Component.translatable("command.aphelion.station.orbit.overwrite_all", orbit.toString()),
true
);
@@ -123,7 +127,7 @@ public class AphelionCommand {
SpacePartitionSavedData.get(level).clearAllOrbits();
context.getSource().sendSuccess(
() -> Component.translatable("command.aphelion.station.orbit.clearall"),
() -> Component.translatable("command.aphelion.station.orbit.clear_all"),
true
);
@@ -280,6 +284,92 @@ public class AphelionCommand {
)
)
)
.then(Commands.literal("planet")
.then(Commands.literal("tp")
.then(Commands.argument("dimension", DimensionArgument.dimension())
.executes(context -> {
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;
}
var targetDim = DimensionArgument.getDimension(context, "dimension");
ServerLevel targetLevel = player.getServer().getLevel(targetDim.dimension());
if (targetLevel == null) {
context.getSource().sendFailure(Component.translatable("command.aphelion.station.teleport.failure.invalid_level"));
return Command.SINGLE_SUCCESS;
}
player.teleportTo(targetLevel, player.position().x, player.position().y, player.position().z, EnumSet.noneOf(RelativeMovement.class), player.getYRot(), player.getXRot());
return Command.SINGLE_SUCCESS;
}))
)
)
.then(Commands.literal("rocket")
.then(Commands.literal("summon")
.executes(context -> {
RocketStructure structure = new RocketStructure(s -> {
s.add(0,0,0, Blocks.IRON_BLOCK.defaultBlockState());
});
context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.spawn.success"), true);
RocketEntity rocket = RocketEntity.spawnRocket(context.getSource().getLevel(), context.getSource().getEntity().blockPosition(), structure);
return Command.SINGLE_SUCCESS;
})
)
.then(Commands.argument("entity", EntityArgument.entity())
.then(Commands.literal("structure")
.then(Commands.literal("set")
.then(Commands.argument("nbt", CompoundTagArgument.compoundTag())
.executes(context -> {
Entity entity = EntityArgument.getEntity(context, "entity");
if (entity instanceof RocketEntity rocket) {
RocketStructure structure = new RocketStructure(RocketStructure::clear);
structure.load(CompoundTagArgument.getCompoundTag(context, "nbt"));
rocket.setStructure(structure);
} else {
context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid"));
}
return Command.SINGLE_SUCCESS;
})
)
)
.then(Commands.literal("get")
.executes(context -> {
Entity entity = EntityArgument.getEntity(context, "entity");
if (entity instanceof RocketEntity rocket) {
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)
);
context.getSource().sendSuccess(
() -> clickableId,
true
);
} else {
context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid"));
}
return Command.SINGLE_SUCCESS;
})
)
)
)
)
);
}
}

View File

@@ -40,5 +40,6 @@ public class ModCreativeTabs {
output.accept(ModItems.ELECTRIC_ARC_FURNACE);
output.accept(ModItems.BLOCK_STEEL);
output.accept(ModItems.ARC_FURNACE_CASING_BLOCK);
output.accept(ModItems.VACUUM_ARC_FURNACE_CONTROLLER);
}).build());
}

View File

@@ -0,0 +1,29 @@
package net.xevianlight.aphelion.core.init;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobCategory;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredRegister;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.entites.vehicles.RocketEntity;
import java.util.function.Supplier;
public class ModEntities {
public static final DeferredRegister<EntityType<?>> ENTITIES =
DeferredRegister.create(Registries.ENTITY_TYPE, Aphelion.MOD_ID);
public static final Supplier<EntityType<RocketEntity>> ROCKET =
ENTITIES.register("rocket", () ->
EntityType.Builder.<RocketEntity>of(RocketEntity::new, MobCategory.MISC)
.sized(0.75f, 2.0f) // tall-ish rocket
.clientTrackingRange(8)
.updateInterval(1)
.build("rocket")
);
public static void register(IEventBus bus) {
ENTITIES.register(bus);
}
}

View File

@@ -4,6 +4,7 @@ import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.loot.BlockLootSubProvider;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.xevianlight.aphelion.core.init.ModBlocks;
@@ -22,6 +23,7 @@ public class ModBlockLootTableProvider extends BlockLootSubProvider {
dropSelf(ModBlocks.ELECTRIC_ARC_FURNACE.get());
dropSelf(ModBlocks.ARC_FURNACE_CASING_BLOCK.get());
dropSelf(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get());
dropOther(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), ItemStack.EMPTY.getItem());
}
@Override

View File

@@ -23,13 +23,17 @@ public class ModBlockTagProvider extends BlockTagsProvider {
.add(ModBlocks.TEST_BLOCK.get())
.add(ModBlocks.ELECTRIC_ARC_FURNACE.get())
.add(ModBlocks.BLOCK_STEEL.get())
.add(ModBlocks.ARC_FURNACE_CASING_BLOCK.get());
.add(ModBlocks.ARC_FURNACE_CASING_BLOCK.get())
.add(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get())
.add(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get());
tag(BlockTags.NEEDS_STONE_TOOL)
.add(ModBlocks.TEST_BLOCK.get())
.add(ModBlocks.ELECTRIC_ARC_FURNACE.get())
.add(ModBlocks.BLOCK_STEEL.get())
.add(ModBlocks.ARC_FURNACE_CASING_BLOCK.get());
.add(ModBlocks.ARC_FURNACE_CASING_BLOCK.get())
.add(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get())
.add(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get());
tag(ModTags.Blocks.STORAGE_BLOCKS_STEEL)
.add(ModBlocks.BLOCK_STEEL.get());

View File

@@ -0,0 +1,190 @@
package net.xevianlight.aphelion.entites.vehicles;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
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.world.damagesource.DamageSource;
import net.minecraft.world.entity.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.AABB;
import net.neoforged.neoforge.entity.IEntityWithComplexSpawn;
import net.neoforged.neoforge.fluids.FluidType;
import net.xevianlight.aphelion.core.init.ModEntities;
import net.xevianlight.aphelion.util.RocketStructure;
import org.jetbrains.annotations.NotNull;
public class RocketEntity extends Entity implements IEntityWithComplexSpawn {
RocketStructure structure = new RocketStructure(s -> {
s.add(0,0,0, Blocks.NETHERITE_BLOCK.defaultBlockState());
s.add(0,1,0, Blocks.NETHERITE_BLOCK.defaultBlockState());
s.add(0,2,0, Blocks.NETHERITE_BLOCK.defaultBlockState());
});
private static final EntityDataAccessor<CompoundTag> STRUCTURE_TAG =
SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.COMPOUND_TAG);
public static RocketEntity spawnRocket(Level level, BlockPos pos, RocketStructure structure) {
if (level.isClientSide) return null;
RocketEntity rocket = new RocketEntity(ModEntities.ROCKET.get(), level);
rocket.moveTo(
pos.getX() + 0.5f,
pos.getY(),
pos.getZ() + 0.5f,
0.0f,
0.0f
);
rocket.setStructure(structure);
level.addFreshEntity(rocket);
return rocket;
}
public RocketStructure getStructure() {
return structure;
}
public RocketEntity(EntityType<?> type, Level level) {
super(type, level);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(STRUCTURE_TAG, new CompoundTag());
}
public void setStructure(RocketStructure structure) {
this.structure.clear();
CompoundTag tag = structure.save();
this.structure.load(tag);
this.refreshDimensions();
this.setBoundingBox(this.makeBoundingBox());
// sync to clients
if (!level().isClientSide) {
this.entityData.set(STRUCTURE_TAG, this.structure.save());
}
}
@Override
public void onSyncedDataUpdated(@NotNull EntityDataAccessor<?> key) {
super.onSyncedDataUpdated(key);
if (STRUCTURE_TAG.equals(key)) {
CompoundTag tag = this.entityData.get(STRUCTURE_TAG);
this.applyStructureTag(tag);
}
}
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
if (tag.contains("RocketStructure")) {
CompoundTag rocketTag = tag.getCompound("RocketStructure");
structure.load(rocketTag);
}
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
tag.put("RocketStructure", structure.save());
}
@Override
public boolean isNoGravity() {
return true;
}
@Override
public boolean canBeCollidedWith() {
return true;
}
@Override
public boolean isPushedByFluid(@NotNull FluidType type) {
return false;
}
@Override @NotNull
protected AABB makeBoundingBox() {
if (structure == null) {
double half = 0.5;
return new AABB(
getX() - half, getY() - half + 0.5, getZ() - half,
getX() + half, getY() + half + 0.5, getZ() + half
);
}
return computeWorldAABBFromStructure();
}
@Override @NotNull
public AABB getBoundingBoxForCulling() {
if (structure == null)
return super.getBoundingBoxForCulling();
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 void tick() {
super.tick();
if (!level().isClientSide) {
// Simple upward movement
setDeltaMovement(0, 0, 0);
}
move(MoverType.SELF, getDeltaMovement());
}
@Override
public void writeSpawnData(RegistryFriendlyByteBuf buf) {
buf.writeNbt(structure.save());
}
@Override
public void readSpawnData(RegistryFriendlyByteBuf buf) {
CompoundTag tag = buf.readNbt();
if (tag != null) {
structure.load(tag);
refreshDimensions();
}
}
public void applyStructureTag(CompoundTag structureTag) {
this.structure.load(structureTag);
this.refreshDimensions(); // if your hitbox/eye depends on structure
}
private AABB computeWorldAABBFromStructure() {
RocketStructure.Extents e = structure.computeExtents();
AABB local = e.toLocalAABB(); // [min, max+1] in local structure coords
return local.move(getX(), getY(), getZ());
}
@Override
public boolean hurt(DamageSource source, float amount) {
return super.hurt(source, amount);
}
@Override
public boolean isPickable() {
return true;
}
}

View File

@@ -0,0 +1,65 @@
package net.xevianlight.aphelion.entites.vehicles;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import net.xevianlight.aphelion.util.RocketStructure;
import org.jetbrains.annotations.NotNull;
public class RocketRenderer extends EntityRenderer<RocketEntity> {
private final BlockRenderDispatcher blockRenderer;
public RocketRenderer(EntityRendererProvider.Context ctx) {
super(ctx);
this.blockRenderer = ctx.getBlockRenderDispatcher(); // ✅ correct source
this.shadowRadius = 0.8f;
}
@Override
public void render(@NotNull RocketEntity entity,
float entityYaw,
float partialTicks,
PoseStack poseStack,
@NotNull MultiBufferSource buffers,
int packedLight) {
RocketStructure s = entity.getStructure();
for (int i = 0; i < s.size(); i++) {
int p = s.packedPosAt(i);
poseStack.pushPose();
poseStack.translate(
RocketStructure.unpackX(p) - 0.5d,
RocketStructure.unpackY(p),
RocketStructure.unpackZ(p) - 0.5d
);
blockRenderer.renderSingleBlock(
s.stateAt(i),
poseStack,
buffers,
packedLight,
OverlayTexture.NO_OVERLAY
);
poseStack.popPose();
}
super.render(entity, entityYaw, partialTicks, poseStack, buffers, packedLight);
}
@Override
public ResourceLocation getTextureLocation(RocketEntity entity) {
// Not used by block rendering, but required.
return ResourceLocation.withDefaultNamespace("textures/misc/white.png");
}
}

View File

@@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.dimension.DimensionType;
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
import net.xevianlight.aphelion.client.dimension.DimensionSkyEffects;
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -18,7 +19,7 @@ public abstract class DimensionSpecialEffectsMixin {
ResourceLocation effectsId = type.effectsLocation();
if (DimensionRendererCache.RENDERERS.containsKey(effectsId)) {
cir.setReturnValue(new DimensionSkyEffects(effectsId));
cir.setReturnValue(new SpaceSkyEffects(effectsId));
}
}
}

View File

@@ -0,0 +1,43 @@
package net.xevianlight.aphelion.planet;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.profiling.ProfilerFiller;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
import net.xevianlight.aphelion.util.Constants;
import java.util.HashMap;
import java.util.Map;
public class AphelionPlanetJSONLoader extends SimpleJsonResourceReloadListener {
public AphelionPlanetJSONLoader() {
super(Constants.GSON, "planet");
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> object,
ResourceManager resourceManager,
ProfilerFiller profiler) {
Map<ResourceLocation, Planet> planets = new HashMap<>();
object.forEach((key, value) -> {
JsonObject json = GsonHelper.convertToJsonObject(value, "planet");
Planet planet = Planet.CODEC.parse(JsonOps.INSTANCE, json).getOrThrow();
// IMPORTANT: use the *resource id* of the json as the lookup key
// so "effects": "aphelion:space" maps to space.json automatically.
planets.put(key, planet);
});
Aphelion.LOGGER.info("Loaded planets " + planets);
PlanetCache.registerPlanets(planets);
}
}

View File

@@ -0,0 +1,21 @@
package net.xevianlight.aphelion.planet;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import net.xevianlight.aphelion.util.registries.ModRegistries;
public record Planet(
ResourceKey<Level> dimension,
double orbitDistance,
ResourceKey<StarSystem> system
) {
public static final Codec<Planet> CODEC = RecordCodecBuilder.create(inst -> inst.group(
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension),
Codec.DOUBLE.fieldOf("orbit_distance").forGetter(Planet::orbitDistance),
ResourceKey.codec(ModRegistries.STAR_SYSTEM).fieldOf("star_system").forGetter(Planet::system)
).apply(inst, Planet::new));
}

View File

@@ -0,0 +1,53 @@
package net.xevianlight.aphelion.planet;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.util.registries.ModRegistries;
import java.util.HashMap;
import java.util.Map;
public final class PlanetCache {
public static final Map<ResourceLocation, Planet> PLANETS = new HashMap<>();
public static final Map<ResourceKey<Level>, ResourceLocation> PLANET_BY_DIMENSION = new HashMap<>();
public static final Planet DEFAULT = new Planet(
ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")),
1,
ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol"))
);
public static void registerPlanets(Map<ResourceLocation, Planet> planets) {
PLANETS.clear();
PLANET_BY_DIMENSION.clear();
PLANETS.putAll(planets);
planets.forEach((planetId, planet) -> {
var dim = planet.dimension();
var prev = PLANET_BY_DIMENSION.put(dim, planetId);
if (prev != null) {
Aphelion.LOGGER.warn(
"Dimension {} is claimed by multiple planets: {} and {}. Keeping latest: {}",
dim.location(), prev, planetId, planetId
);
}
});
Aphelion.LOGGER.info("Loaded {} planets; {} dimension mappings",
PLANETS.size(), PLANET_BY_DIMENSION.size());
}
public static Planet getOrDefault(ResourceLocation id) {
return PLANETS.getOrDefault(id, DEFAULT);
}
public static Planet getByDimensionOrNull(ResourceKey<Level> dimension) {
ResourceLocation planetId = PLANET_BY_DIMENSION.get(dimension);
return planetId == null ? null : PLANETS.get(planetId);
}
}

View File

@@ -0,0 +1,12 @@
package net.xevianlight.aphelion.planet;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
public record StarSystem(
int temp
) {
public static final Codec<StarSystem> CODEC = RecordCodecBuilder.create(inst -> inst.group(
Codec.INT.fieldOf("dimension").forGetter(StarSystem::temp)
).apply(inst, StarSystem::new));
}

View File

@@ -0,0 +1,138 @@
package net.xevianlight.aphelion.util;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.nbt.*;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import java.util.ArrayList;
import java.util.List;
public final class RocketStructure {
private final List<BlockState> palette = new ArrayList<>();
private final IntList packedPositions = new IntArrayList();
private final IntList paletteIndices = new IntArrayList();
public RocketStructure(Builder builder) {
builder.build(this);
}
@FunctionalInterface
public interface Builder {
void build(RocketStructure s);
}
public int size() { return packedPositions.size(); }
public void clear() {
palette.clear();
packedPositions.clear();
paletteIndices.clear();
}
public void add(int x, int y, int z, BlockState state) {
if (state == Blocks.AIR.defaultBlockState()) return;
int idx = palette.indexOf(state);
if (idx < 0) {
palette.add(state);
idx = palette.size() - 1;
}
packedPositions.add(packPos(x, y, z));
paletteIndices.add(idx); // ← REQUIRED
}
public BlockState stateAt(int i) { return palette.get(paletteIndices.getInt(i)); }
public int packedPosAt(int i) { return packedPositions.getInt(i); }
public CompoundTag save() {
CompoundTag tag = new CompoundTag();
// palette
ListTag pal = new ListTag();
for (BlockState st : palette) {
pal.add(BlockState.CODEC.encodeStart(NbtOps.INSTANCE, st)
.getOrThrow());
}
tag.put("palette", pal);
// blocks
IntArrayTag posArr = new IntArrayTag(packedPositions.toIntArray());
IntArrayTag idxArr = new IntArrayTag(paletteIndices.toIntArray());
tag.put("pos", posArr);
tag.put("idx", idxArr);
return tag;
}
public void load(CompoundTag tag) {
clear();
// palette
ListTag pal = tag.getList("palette", Tag.TAG_COMPOUND);
for (int i = 0; i < pal.size(); i++) {
Tag stTag = pal.get(i);
BlockState st = BlockState.CODEC.parse(NbtOps.INSTANCE, stTag)
.getOrThrow();
palette.add(st);
}
// blocks
int[] pos = tag.getIntArray("pos");
int[] idx = tag.getIntArray("idx");
for (int i = 0; i < pos.length; i++) {
packedPositions.add(pos[i]);
paletteIndices.add(idx[i]);
}
}
public static int packPos (int x, int y, int z) {
return (x & 0xFF) | ((y & 0xFF) << 8) | ((z & 0xFF) << 16);
}
public static int unpackX (int p) { return (byte) (p & 0xFF); }
public static int unpackY (int p) { return (byte) (( p >> 8) & 0xFF); }
public static int unpackZ (int p) { return (byte) (( p >> 16) & 0xFF); }
public record Extents(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
public int sizeX() { return maxX - minX + 1; }
public int sizeY() { return maxY - minY + 1; }
public int sizeZ() { return maxZ - minZ + 1; }
public AABB toLocalAABB() {
return new AABB(minX - 0.5, minY, minZ - 0.5, maxX + 0.5, maxY + 1, maxZ + 0.5);
}
}
public Extents computeExtents() {
if (size() == 0) {
return new Extents(0, 0, 0, 0, 0, 0);
}
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE, maxZ = Integer.MIN_VALUE;
for (int i = 0; i < size(); i++) {
int p = packedPosAt(i);
int x = unpackX(p);
int y = unpackY(p);
int z = unpackZ(p);
if (x < minX) minX = x;
if (y < minY) minY = y;
if (z < minZ) minZ = z;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
if (z > maxZ) maxZ = z;
}
return new Extents(minX, minY, minZ, maxX, maxY, maxZ);
}
}

View File

@@ -0,0 +1,16 @@
package net.xevianlight.aphelion.util.registries;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.planet.Planet;
import net.xevianlight.aphelion.planet.StarSystem;
public class ModRegistries {
public static final ResourceKey<Registry<StarSystem>> STAR_SYSTEM = createRegistryKey("star_system");
public static final ResourceKey<Registry<Planet>> PLANET = createRegistryKey("planet");
private static <T> ResourceKey<Registry<T>> createRegistryKey(String name) {
return ResourceKey.createRegistryKey(Aphelion.id(name));
}
}