Rockets now support inventories. Fixed pad underside textures.

This commit is contained in:
XevianLight
2026-02-08 22:23:29 -07:00
parent bc8bb4ee05
commit 018886768e
19 changed files with 309 additions and 54 deletions

View File

@@ -0,0 +1,14 @@
package net.xevianlight.aphelion.block.custom;
import net.xevianlight.aphelion.block.custom.base.BaseRocketFuelTank;
public class BasicRocketFuelTank extends BaseRocketFuelTank {
public BasicRocketFuelTank(Properties properties) {
super(properties);
}
@Override
public int getFuelCapacity() {
return 1000;
}
}

View File

@@ -0,0 +1,29 @@
package net.xevianlight.aphelion.block.custom.base;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
public class BaseRocketFuelTank extends Block implements IRocketFuelUpgrade {
public BaseRocketFuelTank(Properties properties) {
super(properties);
}
public static Properties getProperties() {
return Properties
.of()
.sound(SoundType.METAL)
.destroyTime(2f)
.explosionResistance(10f)
.requiresCorrectToolForDrops();
}
public static Item.Properties getItemProperties() {
return new Item.Properties();
}
@Override
public int getFuelCapacity() {
return 0;
}
}

View File

@@ -0,0 +1,21 @@
package net.xevianlight.aphelion.block.custom.base;
/**
* Used for blocks which should provide energy storage capacity to a rocket.
* <p>Note that blocks implementing this interface should not store energy themselves.
* Rockets determine their energy capacity from the sum of these blocks installed on them.</p>
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
*/
public interface IRocketEnergyUpgrade {
/**
* Used to determine how much FE of energy storage a rocket receives from having this block is installed.
*/
int getEnergyCapacity();
/**
* Used to determine how much FE transfer rate bonus a rocket receives from having this block is installed. This is added onto the base rockets energy transfer limit.
*/
default int getMaxTransferBonus() {
return 0;
};
}

View File

@@ -0,0 +1,14 @@
package net.xevianlight.aphelion.block.custom.base;
/**
* Used for blocks which should provide fluid storage capacity to a rocket.
* <p>Note that blocks implementing this interface should not store fluids themselves.
* Rockets determine their fluid container capacity from the sum of these blocks installed on them.</p>
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
*/
public interface IRocketFluidUpgrade {
/**
* Used to determine how many millibuckets of fluid storage a rocket receives from having this block is installed.
*/
int getFluidCapacity();
}

View File

@@ -0,0 +1,16 @@
package net.xevianlight.aphelion.block.custom.base;
import net.neoforged.neoforge.fluids.capability.templates.FluidTank;
/**
* Used for blocks which should provide fuel storage capacity to a rocket.
* <p>Note that blocks implementing this interface should not store fuel themselves.
* Rockets determine their fuel container capacity from the sum of these blocks installed on them.</p>
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
*/
public interface IRocketFuelUpgrade {
/**
* Used to determine how many millibuckets of fuel storage a rocket receives from having this block is installed.
*/
int getFuelCapacity();
}

View File

@@ -0,0 +1,15 @@
package net.xevianlight.aphelion.block.custom.base;
/**
* Used for blocks which should provide item slots to a rockets inventory.
* <p>Note that blocks implementing this interface should not store items themselves.
* Rockets determine their inventory slot count from the sum of these blocks installed on them.</p>
* <p>Keep in mind that {@code TileEntity} blocks cannot be included in a {@code RocketStructure}.</p>
*/
public interface IRocketInventoryUpgrade {
/**
* Used to determine how many inventory slots a rocket receives from having this block installed.
*/
int getSlotCapacity();
}

View File

@@ -33,5 +33,9 @@
}
public abstract void onEnergyChanged();
public void setCapacity(int capacity) {
this.capacity = capacity;
}
}

View File

@@ -2,6 +2,7 @@ package net.xevianlight.aphelion.entites.vehicles;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
@@ -13,13 +14,16 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.FluidTags;
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.item.ItemEntity;
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.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
@@ -27,9 +31,14 @@ 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.FluidStack;
import net.neoforged.neoforge.fluids.FluidType;
import net.neoforged.neoforge.fluids.capability.templates.FluidTank;
import net.neoforged.neoforge.items.ItemStackHandler;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.entity.energy.ModEnergyStorage;
import net.xevianlight.aphelion.core.init.ModEntities;
import net.xevianlight.aphelion.fluid.ModFluids;
import net.xevianlight.aphelion.util.RocketStructure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -63,6 +72,43 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
private static final EntityDataAccessor<CompoundTag> STRUCTURE_TAG =
SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.COMPOUND_TAG);
private final FluidTank FUEL_TANK = newFuelTank(0);
private final FluidTank FLUID_STORAGE = newFluidTank(0);
private final ModEnergyStorage ENERGY_STORAGE = newEnergyStorage(0, 0);
private final ItemStackHandler INVENTORY = new ItemStackHandler(0);
public ItemStackHandler getInventory() {
return INVENTORY;
}
private static @NotNull FluidTank newFluidTank(int capacity) {
return new FluidTank(capacity) {
@Override
public boolean isFluidValid(@NotNull FluidStack stack) {
return true;
}
};
}
private static @NotNull FluidTank newFuelTank(int capacity) {
return new FluidTank(capacity) {
@Override
public boolean isFluidValid(@NotNull FluidStack stack) {
return stack.is(FluidTags.WATER);
}
};
}
private static @NotNull ModEnergyStorage newEnergyStorage(int capacity, int transfer) {
return new ModEnergyStorage(capacity, transfer) {
@Override
public void onEnergyChanged() {
}
};
}
public static RocketEntity spawnRocket(Level level, BlockPos pos, RocketStructure structure) {
if (level.isClientSide) return null;
@@ -79,9 +125,25 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
rocket.setStructure(structure);
level.addFreshEntity(rocket);
rocket.FUEL_TANK.setFluid(new FluidStack(ModFluids.OIL.get(), 1000));
rocket.INVENTORY.setSize(rocket.INVENTORY.getSlots() + 1);
rocket.INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND, 1), false);
return rocket;
}
private void recalculateCapacitiesFromStructure() {
int inv = RocketStructure.calculateInventoryCapacity(structure);
int fuelCap = RocketStructure.calculateFuelCapacity(structure);
int fluidCap = RocketStructure.calculateFluidCapacity(structure);
int energyCap = RocketStructure.calculateEnergyCapacity(structure);
INVENTORY.setSize(inv);
FUEL_TANK.setCapacity(fuelCap);
FLUID_STORAGE.setCapacity(fluidCap);
ENERGY_STORAGE.setCapacity(energyCap);
}
public void launchTo(ResourceKey<Level> dim, @Nullable BlockPos pos) {
if (level().isClientSide) return;
@@ -119,7 +181,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
}
case DESCEND -> tickDescend();
}
// Simple upward movement
}
@@ -280,12 +341,13 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
this.refreshDimensions();
this.setBoundingBox(this.makeBoundingBox());
// prevent any internal “snap” from sticking
this.moveTo(x, y, z, yRot, xRot);
if (!level().isClientSide) {
this.entityData.set(STRUCTURE_TAG, this.structure.save());
}
recalculateCapacitiesFromStructure();
}
@Override
@@ -326,9 +388,12 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
HolderLookup.Provider registries = level().registryAccess();
if (tag.contains("RocketStructure")) {
CompoundTag rocketTag = tag.getCompound("RocketStructure");
structure.load(rocketTag);
recalculateCapacitiesFromStructure();
// Immediately apply correct bbox on load (server + client)
double x = getX(), y = getY(), z = getZ();
@@ -362,10 +427,22 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
if (tag.contains("FlightPhase", Tag.TAG_BYTE)) {
setPhase(FlightPhase.values()[tag.getByte("FlightPhase")]);
}
if (tag.contains("Inventory", CompoundTag.TAG_COMPOUND)){
INVENTORY.deserializeNBT(registries, tag.getCompound("Inventory"));
}
FUEL_TANK.readFromNBT(registries, tag);
FLUID_STORAGE.readFromNBT(registries, tag);
if (tag.contains("Energy", CompoundTag.TAG_COMPOUND)) {
ENERGY_STORAGE.deserializeNBT(registries, tag.getCompound("Energy"));
}
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
HolderLookup.Provider registries = level().registryAccess();
tag.put("RocketStructure", structure.save());
if (targetDim != null)
tag.putString("TargetDim", targetDim.location().toString());
@@ -376,6 +453,10 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
tag.putDouble("LandingX", landingPosX);
tag.putDouble("LandingZ", landingPosZ);
tag.putDouble("yVelocity", yVel);
tag.put("Inventory", INVENTORY.serializeNBT(registries));
tag = FUEL_TANK.writeToNBT(registries, tag);
tag = FLUID_STORAGE.writeToNBT(registries, tag);
tag.put("Energy", ENERGY_STORAGE.serializeNBT(registries));
}
public @Nullable BlockPos getTargetPos() {
@@ -438,7 +519,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
}
public Vec3 getSeatWorldPos(int seatIndex, float partialTicks) {
public Vec3 getSeatWorldPos(int seatIndex) {
int packed = structure.packedSeatAt(seatIndex);
int dx = RocketStructure.unpackX(packed);
@@ -449,10 +530,10 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
}
@Override
public void positionRider(Entity passenger, MoveFunction move) {
public void positionRider(@NotNull Entity passenger, @NotNull MoveFunction move) {
if (!this.hasPassenger(passenger)) return;
Vec3 seat = getSeatWorldPos(0, 0); // primary seat
Vec3 seat = getSeatWorldPos(0); // primary seat
move.accept(passenger, seat.x, seat.y, seat.z);
}
@@ -473,7 +554,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
public void applyStructureTag(CompoundTag structureTag) {
this.structure.load(structureTag);
this.refreshDimensions(); // if your hitbox/eye depends on structure
this.refreshDimensions();
}
private AABB computeWorldAABBFromStructure() {
@@ -510,16 +591,14 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
if (level().isClientSide) return false;
if (!(level() instanceof ServerLevel server)) return false;
// Optional: only allow when not in flight
if (getPhase() == FlightPhase.ASCEND || getPhase() == FlightPhase.TRANSIT || getPhase() == FlightPhase.DESCEND) {
return false;
}
// In rare instances we can disassemble a rocket AFTER it has been killed.
// This usually only happens if another class instance still has a reference to this object stored and calls rocketEntity.disassemble().
// This SHOULD fix that
if (!this.isAlive()) return false; // dead
if (this.isRemoved()) return false; // discarded / removed
// Kick riders off first (so we don't place blocks inside them)
ejectPassengers();
// Anchor: blocks were captured relative to the scan origin.
// Your rocket is spawned at seatPos + (0.5, 0, 0.5), so we convert back to the block origin.
BlockPos origin = BlockPos.containing(getX(), getY(), getZ());
// Place blocks
@@ -538,16 +617,9 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
// Safety: don't overwrite existing blocks
if (!server.getBlockState(wp).isAir()) {
// If you want strict behavior, abort entirely:
// return false;
// Otherwise just skip conflicts:
continue;
}
// Extra safety: avoid accidentally placing into portal/void/etc if desired
// if (!server.isInWorldBounds(wp)) continue;
server.setBlock(wp, stateToPlace, 3);
}
@@ -556,4 +628,30 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
return true;
}
public static void dropItemStackHandler(ServerLevel level, Vec3 pos, ItemStackHandler handler) {
for (int i = 0; i < handler.getSlots(); i++) {
ItemStack stack = handler.getStackInSlot(i);
if (!stack.isEmpty()) {
ItemStack toDrop = stack.copy();
handler.setStackInSlot(i, ItemStack.EMPTY);
ItemEntity ent = new ItemEntity(level, pos.x, pos.y, pos.z, toDrop);
level.addFreshEntity(ent);
}
}
}
@Override
public void kill() {
if (!level().isClientSide())
dropItemStackHandler((ServerLevel) level(), position(), INVENTORY);
super.kill();
}
@Override
public void onRemovedFromLevel() {
if (!level().isClientSide())
dropItemStackHandler((ServerLevel) level(), position(), INVENTORY);
super.onRemovedFromLevel();
}
}

View File

@@ -13,6 +13,8 @@ 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.core.init.ModEntities;
import net.xevianlight.aphelion.entites.vehicles.RocketEntity;
import net.xevianlight.aphelion.network.RocketPayloadHandlers;
import net.xevianlight.aphelion.network.PartitionPayloadHandler;
import net.xevianlight.aphelion.network.packet.PartitionPayload;
@@ -30,6 +32,8 @@ public class ModBusEvents {
// event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), VAFMultiblockDummyBlockEntity::getItemHandler);
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), BaseMultiblockDummyBlockEntity::getItemHandler);
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), BaseMultiblockDummyBlockEntity::getEnergyStorage);
event.registerEntity(Capabilities.ItemHandler.ENTITY, ModEntities.ROCKET.get(), (rocket, ctx) -> rocket.getInventory());
}
@SubscribeEvent

View File

@@ -24,19 +24,19 @@ public class ModFluids {
public static final DeferredRegister<Fluid> FLUIDS =
DeferredRegister.create(BuiltInRegistries.FLUID, Aphelion.MOD_ID);
public static final Supplier<FlowingFluid> SOURCE_OIL_FLUID = FLUIDS.register("oil",
public static final Supplier<FlowingFluid> OIL = FLUIDS.register("oil",
() -> new BaseFlowingFluid.Source(ModFluids.OIL_PROPERTIES));
public static final Supplier<FlowingFluid> FLOWING_OIL_FLUID = FLUIDS.register("flowing_oil",
public static final Supplier<FlowingFluid> FLOWING_OIL = FLUIDS.register("flowing_oil",
() -> new BaseFlowingFluid.Flowing(ModFluids.OIL_PROPERTIES));
public static final DeferredBlock<LiquidBlock> OIL_BLOCK = ModBlocks.BLOCKS.register("oil",
() -> new LiquidBlock(ModFluids.SOURCE_OIL_FLUID.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
() -> new LiquidBlock(ModFluids.OIL.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
public static final DeferredItem<Item> OIL_BUCKET = ModItems.ITEMS.registerItem("oil_bucket",
properties -> new BucketItem(ModFluids.SOURCE_OIL_FLUID.get(), properties.stacksTo(1).craftRemainder(Items.BUCKET)));
properties -> new BucketItem(ModFluids.OIL.get(), properties.stacksTo(1).craftRemainder(Items.BUCKET)));
public static final BaseFlowingFluid.Properties OIL_PROPERTIES = new BaseFlowingFluid.Properties(
ModFluidTypes.OIL_FLUID_TYPE, SOURCE_OIL_FLUID, FLOWING_OIL_FLUID)
ModFluidTypes.OIL_FLUID_TYPE, OIL, FLOWING_OIL)
.slopeFindDistance(2).levelDecreasePerBlock(2).tickRate(10)
.block(ModFluids.OIL_BLOCK).bucket(ModFluids.OIL_BUCKET);

View File

@@ -11,6 +11,11 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.phys.AABB;
import net.xevianlight.aphelion.block.custom.base.IRocketEnergyUpgrade;
import net.xevianlight.aphelion.block.custom.base.IRocketFluidUpgrade;
import net.xevianlight.aphelion.block.custom.base.IRocketFuelUpgrade;
import net.xevianlight.aphelion.block.custom.base.IRocketInventoryUpgrade;
import org.apache.commons.lang3.NotImplementedException;
import java.util.ArrayList;
import java.util.List;
@@ -151,27 +156,6 @@ public final class RocketStructure {
return new Extents(minX, minY, minZ, maxX, maxY, maxZ);
}
public static RocketStructure capture(Level level, BlockPos origin, int rx, int ry, int rz) {
return new RocketStructure(s -> {
for (int dy = -ry; dy <= ry; dy++) {
for (int dx = -rx; dx <= rx; dx++) {
for (int dz = -rz; dz <= rz; dz++) {
BlockPos p = origin.offset(dx, dy, dz);
BlockState st = level.getBlockState(p);
// Skip air and unbreakables/forbidden blocks as you like
if (st.isAir()) continue;
// Optional: ignore the assembler block itself
// if (p.equals(origin)) continue;
s.add(dx, dy, dz, st);
}
}
}
});
}
public static void clearCaptured(Level level, BlockPos origin, RocketStructure struct) {
final int flags = Block.UPDATE_CLIENTS;
@@ -230,4 +214,60 @@ public final class RocketStructure {
public void addSeatOffset(int dx, int dy, int dz) {
seatOffsets.add(packPos(dx, dy, dz));
}
public static int calculateInventoryCapacity(RocketStructure structure) {
int totalSlots = 0;
for (int i = 0; i < structure.size(); i++) {
BlockState st = structure.stateAt(i);
Block block = st.getBlock();
if (block instanceof IRocketInventoryUpgrade upgrade) {
int slots = upgrade.getSlotCapacity();
if (slots > 0) totalSlots += slots;
}
}
return totalSlots;
}
public static int calculateFuelCapacity(RocketStructure structure) {
int totalMB = 0;
for (int i = 0; i < structure.size(); i++) {
BlockState st = structure.stateAt(i);
Block block = st.getBlock();
if (block instanceof IRocketFuelUpgrade upgrade) {
int mb = upgrade.getFuelCapacity();
if (mb > 0) totalMB += mb;
}
}
return totalMB;
}
public static int calculateFluidCapacity(RocketStructure structure) {
int totalMB = 0;
for (int i = 0; i < structure.size(); i++) {
BlockState st = structure.stateAt(i);
Block block = st.getBlock();
if (block instanceof IRocketFluidUpgrade upgrade) {
int mb = upgrade.getFluidCapacity();
if (mb > 0) totalMB += mb;
}
}
return totalMB;
}
public static int calculateEnergyCapacity(RocketStructure structure) {
int totalFE = 0;
for (int i = 0; i < structure.size(); i++) {
BlockState st = structure.stateAt(i);
Block block = st.getBlock();
if (block instanceof IRocketEnergyUpgrade upgrade) {
int fe = upgrade.getEnergyCapacity();
if (fe > 0) totalFE += fe;
}
}
return totalFE;
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/0010",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/0010"
"bottom": "aphelion:block/launch_pad/1000"
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/0011",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/0011"
"bottom": "aphelion:block/launch_pad/1001"
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/0110",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/0110"
"bottom": "aphelion:block/launch_pad/1100"
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/0111",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/0111"
"bottom": "aphelion:block/launch_pad/1101"
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/1000",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/1000"
"bottom": "aphelion:block/launch_pad/0010"
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/1001",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/1001"
"bottom": "aphelion:block/launch_pad/0011"
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/1100",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/1100"
"bottom": "aphelion:block/launch_pad/0110"
}
}

View File

@@ -3,6 +3,6 @@
"textures": {
"top": "aphelion:block/launch_pad/1101",
"side": "aphelion:block/launch_pad/topbottom",
"bottom": "aphelion:block/launch_pad/1101"
"bottom": "aphelion:block/launch_pad/0111"
}
}