From 9356c43ea7239a253cf09d9ba0e556afe65faa14 Mon Sep 17 00:00:00 2001 From: TechnoDraconic Date: Fri, 6 Feb 2026 22:22:10 -0800 Subject: [PATCH] added gravity test block with gui and debug renderer --- .../59eb3dbb5f86130e09b3c62d89b9525ee01cf52d | 7 +- .../a8139181fab9cfd94e67697230cbaca839a05e1f | 6 +- .../net/xevianlight/aphelion/Aphelion.java | 6 +- .../block/custom/GravityTestBlock.java | 57 ++++++ .../entity/custom/GravityTestBlockEntity.java | 119 ++++++++++++ .../custom/renderer/OxygenTestRenderer.java | 2 + .../aphelion/client/DebugRenderUtils.java | 179 ++++++++++++++++++ .../aphelion/client/GravityDebugRender.java | 74 ++++++++ .../aphelion/client/OxygenDebugRender.java | 58 +----- .../aphelion/core/init/ModBlockEntities.java | 5 + .../aphelion/core/init/ModBlocks.java | 2 + .../aphelion/core/init/ModItems.java | 1 + .../core/saveddata/GravitySavedData.java | 3 + .../datagen/ModBlockLootTableProvider.java | 2 + .../datagen/ModBlockStateProvider.java | 2 + .../aphelion/event/ModBusEvents.java | 7 + .../UpdateGravityTestBlockHandler.java | 27 +++ .../packet/UpdateGravityTestBlockPacket.java | 30 +++ .../aphelion/screen/GravityTestBlockMenu.java | 110 +++++++++++ .../screen/GravityTestBlockScreen.java | 94 +++++++++ .../aphelion/screen/ModMenuTypes.java | 5 + .../aphelion/systems/GravityService.java | 10 +- .../textures/block/gravity_test_block.png | Bin 0 -> 280 bytes .../textures/gui/gravity_test_block/gui.png | Bin 0 -> 17037 bytes .../aphelion/textures/gui/test_block/gui.png | Bin 1222 -> 17037 bytes 25 files changed, 739 insertions(+), 67 deletions(-) create mode 100644 src/main/java/net/xevianlight/aphelion/block/custom/GravityTestBlock.java create mode 100644 src/main/java/net/xevianlight/aphelion/block/entity/custom/GravityTestBlockEntity.java create mode 100644 src/main/java/net/xevianlight/aphelion/client/DebugRenderUtils.java create mode 100644 src/main/java/net/xevianlight/aphelion/client/GravityDebugRender.java create mode 100644 src/main/java/net/xevianlight/aphelion/network/UpdateGravityTestBlockHandler.java create mode 100644 src/main/java/net/xevianlight/aphelion/network/packet/UpdateGravityTestBlockPacket.java create mode 100644 src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockMenu.java create mode 100644 src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockScreen.java create mode 100644 src/main/resources/assets/aphelion/textures/block/gravity_test_block.png create mode 100644 src/main/resources/assets/aphelion/textures/gui/gravity_test_block/gui.png diff --git a/src/generated/resources/.cache/59eb3dbb5f86130e09b3c62d89b9525ee01cf52d b/src/generated/resources/.cache/59eb3dbb5f86130e09b3c62d89b9525ee01cf52d index 1fa8913..e864cb9 100644 --- a/src/generated/resources/.cache/59eb3dbb5f86130e09b3c62d89b9525ee01cf52d +++ b/src/generated/resources/.cache/59eb3dbb5f86130e09b3c62d89b9525ee01cf52d @@ -1,11 +1,12 @@ -// 1.21.1 2026-01-28T08:47:15.3186366 Loot Tables -// 1.21.1 2026-01-26T19:04:46.4976369 Loot Tables +// 1.21.1 2026-02-06T10:36:07.5023829 Loot Tables 69d8318ddba171526d1fabb87d9d93548ed8598e data/aphelion/loot_table/blocks/arc_furnace_casing.json 05f08985e601d30116f67e2f07b48b03b40cdca6 data/aphelion/loot_table/blocks/block_steel.json ff43a9c3741faf10b1e156a7a74d5cfb035cc118 data/aphelion/loot_table/blocks/dimension_changer.json b63130d9c10485676303d729807b6fcaac080294 data/aphelion/loot_table/blocks/electric_arc_furnace.json -afb6519a03415b8e0d5bafc9fadb70905a398046 data/aphelion/loot_table/blocks/oxygen_test_block.json +086c97543700ccc2f815da4e6c29f11159766889 data/aphelion/loot_table/blocks/gravity_test_block.json b9cfe672ead8e2673a7b2f5c4cec831e7e8e7040 data/aphelion/loot_table/blocks/launch_pad.json +afb6519a03415b8e0d5bafc9fadb70905a398046 data/aphelion/loot_table/blocks/oxygen_test_block.json +2c6748b2cfb5b78e0cc95a2f3583d4e50cb4c964 data/aphelion/loot_table/blocks/rocket_assemblerblock.json 1ab50c99e9f478840b9d003fd56ebdcab12fbbce data/aphelion/loot_table/blocks/test_block.json 7d8eeb99a1bc942a6e2cf292b21fd4534062b5ab data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json 797bf9839d79e08b4832c9eaf3cb303b0471ed0c data/aphelion/loot_table/blocks/vaf_dummy_block.json diff --git a/src/generated/resources/.cache/a8139181fab9cfd94e67697230cbaca839a05e1f b/src/generated/resources/.cache/a8139181fab9cfd94e67697230cbaca839a05e1f index c021feb..bbe8db5 100644 --- a/src/generated/resources/.cache/a8139181fab9cfd94e67697230cbaca839a05e1f +++ b/src/generated/resources/.cache/a8139181fab9cfd94e67697230cbaca839a05e1f @@ -1,19 +1,21 @@ -// 1.21.1 2026-01-28T08:47:15.3176368 Block States: aphelion -// 1.21.1 2026-01-26T20:40:43.8251623 Block States: aphelion +// 1.21.1 2026-02-06T10:36:07.5013847 Block States: aphelion 851ff42f7b21dec86107c8e0cefb3934ae4ebc08 assets/aphelion/blockstates/block_steel.json 30b9c0efd7aaadb5412d98e4568f98b3632adbb9 assets/aphelion/blockstates/dimension_changer.json cb4287104006c80c8396b290ab5258df65d62cef assets/aphelion/blockstates/electric_arc_furnace.json +1975ab6c73f76642f8b6cfed00e0739b4ea5775f assets/aphelion/blockstates/gravity_test_block.json 28131a570d3666b7f323de4ad8a69e52ceec92e2 assets/aphelion/blockstates/oxygen_test_block.json b86c50fddcf6c8c6c19cb748529239d5962a3ede assets/aphelion/blockstates/test_block.json a810b97f4dace35d026f28d96cb9c47c93600d75 assets/aphelion/models/block/block_steel.json 2d3592b7ab7132908709243e97540151e0fb762e assets/aphelion/models/block/dimension_changer.json 5f7e8674070f31a63875b5d6147153bfa0eef61a assets/aphelion/models/block/electric_arc_furnace.json +ad49034f318c6bc14a4844cf598c6999296aa8a0 assets/aphelion/models/block/gravity_test_block.json 746f23f150a01524ad03cbd1eb822bfbb7cf453b assets/aphelion/models/block/oxygen_test_block.json e0971228b4a1c4bc9dbab58a7dacdc3ae6037e02 assets/aphelion/models/block/test_block.json cdc831b0f1c462be64825fd34bd446e5b95afac6 assets/aphelion/models/item/arc_furnace_casing.json 3599f9037eb2f66de1765318b97ab564c3eae92f assets/aphelion/models/item/block_steel.json db0ec473a016ce05c258cde18a217d47a9ea8324 assets/aphelion/models/item/dimension_changer.json 279080c06ada87f54fd0a7b885b256dbe25a946a assets/aphelion/models/item/electric_arc_furnace.json +8de1f7597e1fa82c5603a88040ad935aa1cb9b29 assets/aphelion/models/item/gravity_test_block.json 24cf60e70f7d9450b0e70cf017662e80971bae17 assets/aphelion/models/item/oxygen_test_block.json 74418ef1cf678e72e7534924274688ef5a68af0e assets/aphelion/models/item/test_block.json 88ca3602517e99f7feaed57eddfc96965a25761c assets/aphelion/models/item/vacuum_arc_furnace_controller.json diff --git a/src/main/java/net/xevianlight/aphelion/Aphelion.java b/src/main/java/net/xevianlight/aphelion/Aphelion.java index 4101621..dc2eef1 100644 --- a/src/main/java/net/xevianlight/aphelion/Aphelion.java +++ b/src/main/java/net/xevianlight/aphelion/Aphelion.java @@ -24,10 +24,7 @@ import net.xevianlight.aphelion.fluid.BaseFluidType; import net.xevianlight.aphelion.fluid.ModFluidTypes; import net.xevianlight.aphelion.fluid.ModFluids; import net.xevianlight.aphelion.recipe.ModRecipes; -import net.xevianlight.aphelion.screen.ElectricArcFurnaceScreen; -import net.xevianlight.aphelion.screen.ModMenuTypes; -import net.xevianlight.aphelion.screen.TestBlockScreen; -import net.xevianlight.aphelion.screen.VacuumArcFurnaceScreen; +import net.xevianlight.aphelion.screen.*; import org.slf4j.Logger; import net.xevianlight.aphelion.entites.vehicles.RocketRenderer; @@ -147,6 +144,7 @@ public class Aphelion { event.register(ModMenuTypes.TEST_BLOCK_MENU.get(), TestBlockScreen::new); event.register(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), ElectricArcFurnaceScreen::new); event.register(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), VacuumArcFurnaceScreen::new); + event.register(ModMenuTypes.GRAVITY_TEST_BLOCK_MENU.get(), GravityTestBlockScreen::new); } @SubscribeEvent diff --git a/src/main/java/net/xevianlight/aphelion/block/custom/GravityTestBlock.java b/src/main/java/net/xevianlight/aphelion/block/custom/GravityTestBlock.java new file mode 100644 index 0000000..6a2af5b --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/block/custom/GravityTestBlock.java @@ -0,0 +1,57 @@ +package net.xevianlight.aphelion.block.custom; + +import com.mojang.serialization.MapCodec; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.xevianlight.aphelion.block.custom.base.BasicEntityBlock; +import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity; +import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity; +import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class GravityTestBlock extends BasicEntityBlock { + + public GravityTestBlock(Properties properties) {super(properties, true);} + + @Override + protected MapCodec codec() {return null;} + + @Override + public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return new GravityTestBlockEntity(blockPos, blockState); + } + + public static Properties getProperties() {return Properties.of();} + + @Override + protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) { + if (state.getBlock() != newState.getBlock()) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof GravityTestBlockEntity BE) { + BE.removeGravityArea(); + } + } + super.onRemove(state, level, pos, newState, movedByPiston); + } + + @Override + public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult result) { + + if (!level.isClientSide && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof GravityTestBlockEntity testBlockEntity) { + serverPlayer.openMenu(new SimpleMenuProvider(testBlockEntity, Component.literal("Gravity Test Block")), pos); + } + return InteractionResult.sidedSuccess(level.isClientSide); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/block/entity/custom/GravityTestBlockEntity.java b/src/main/java/net/xevianlight/aphelion/block/entity/custom/GravityTestBlockEntity.java new file mode 100644 index 0000000..e23f399 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/block/entity/custom/GravityTestBlockEntity.java @@ -0,0 +1,119 @@ +package net.xevianlight.aphelion.block.entity.custom; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.items.ItemStackHandler; +import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity; +import net.xevianlight.aphelion.core.init.ModBlockEntities; +import net.xevianlight.aphelion.core.init.ModBlocks; +import net.xevianlight.aphelion.core.saveddata.GravitySavedData; +import net.xevianlight.aphelion.screen.ElectricArcFurnaceMenu; +import net.xevianlight.aphelion.screen.GravityTestBlockMenu; +import net.xevianlight.aphelion.systems.GravityService; +import org.jetbrains.annotations.Nullable; + +public class GravityTestBlockEntity extends BlockEntity implements TickableBlockEntity, MenuProvider { + + public GravityTestBlockEntity(BlockPos pos, BlockState blockState) {super(ModBlockEntities.GRAVITY_TEST_BLOCK_ENTITY.get(), pos, blockState);} + + public int areaSize = 5; + public float gravityStrength = 5f; + private boolean isInitialized = false; + public final ItemStackHandler inventory = new ItemStackHandler(0); + + @Override + protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { + pTag.putInt("areaSize", areaSize); + pTag.putFloat("gravityStrength", gravityStrength); + super.saveAdditional(pTag, pRegistries); + } + + @Override + protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { + areaSize = pTag.getInt("areaSize"); + gravityStrength = pTag.getFloat("gravityStrength"); + super.loadAdditional(pTag, pRegistries); + } + + public void setRadius(float r) { + areaSize = (int) r; + } + + public void setStrength(float s) { + gravityStrength = s; + } + + public void addGravityArea() { + Level level = getLevel(); + if (level != null && level.isClientSide()) return; + + BlockPos pos = this.getBlockPos(); + + GravityService.setGravityArea((ServerLevel) level, pos, gravityStrength, areaSize); + } + + public void removeGravityArea() { + Level level = getLevel(); + if (level != null && level.isClientSide()) return; + + BlockPos pos = this.getBlockPos(); + + GravityService.removeGravityArea((ServerLevel) level, pos); + } + + @Override + public void tick(Level entityLevel, long time, BlockState blockState, BlockPos pos) {} + + @Override + public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {} + + @Override + public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {} + + @Override + public boolean isInitialized() { + return isInitialized; + } + + @Override + public void firstTick(Level level, BlockState state, BlockPos pos) { addGravityArea(); isInitialized = true; } + + @Override + public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) { + return new GravityTestBlockMenu(i, inventory, this); + } + + @Override + public Component getDisplayName() { + return null; + } + + public void sendUpdate() { + removeGravityArea(); + addGravityArea(); + } + + @Override + public ClientboundBlockEntityDataPacket getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + return this.saveCustomOnly(registries); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/block/entity/custom/renderer/OxygenTestRenderer.java b/src/main/java/net/xevianlight/aphelion/block/entity/custom/renderer/OxygenTestRenderer.java index 604c52d..28cf257 100644 --- a/src/main/java/net/xevianlight/aphelion/block/entity/custom/renderer/OxygenTestRenderer.java +++ b/src/main/java/net/xevianlight/aphelion/block/entity/custom/renderer/OxygenTestRenderer.java @@ -42,6 +42,8 @@ public class OxygenTestRenderer implements BlockEntityRenderer> Y_OFFSET) + 1) << Y_OFFSET; + return (pos & (~Y_MAGIC)) | (y & Y_MAGIC); + } + + public static long below(long pos) { + long y = (((pos & Y_MAGIC) >> Y_OFFSET) - 1) << Y_OFFSET; + return (pos & (~Y_MAGIC)) | (y & Y_MAGIC); + } + + public static long north(long pos) { + long z = (((pos & Z_MAGIC) >> Z_OFFSET) - 1) << Z_OFFSET; + return (pos & (~Z_MAGIC)) | (z & Z_MAGIC); + } + + public static long south(long pos) { + long z = (((pos & Z_MAGIC) >> Z_OFFSET) + 1) << Z_OFFSET; + return (pos & (~Z_MAGIC)) | (z & Z_MAGIC); + } + + public static long east(long pos) { + long x = (((pos & X_MAGIC) >> X_OFFSET) + 1) << X_OFFSET; + return (pos & (~X_MAGIC)) | (x & X_MAGIC); + } + + public static long west(long pos) { + long x = (((pos & X_MAGIC) >> X_OFFSET) - 1) << X_OFFSET; + return (pos & (~X_MAGIC)) | (x & X_MAGIC); + } + + public static int getX(long pos) { + return BlockPos.getX(pos); + } + + public static int getY(long pos) { + return BlockPos.getY(pos); + } + + public static int getZ(long pos) { + return BlockPos.getZ(pos); + } + } + + public static void drawSphere(PoseStack poseStack, VertexConsumer vc, Vector3f center, float radius) { + + Matrix4f mat = poseStack.last().pose(); + + final int Y_SEGMENT_COUNT = 20; + for (int segmentY=0; segmentY < Y_SEGMENT_COUNT; segmentY++) { + double bottomAngle = (((double) segmentY / Y_SEGMENT_COUNT) - 0.5) * Math.PI; + double topAngle = (((double) (segmentY+1) / Y_SEGMENT_COUNT) - 0.5) * Math.PI; + float y0 = (float) Math.sin(bottomAngle) * radius + center.y(); + float y1 = (float) Math.sin(topAngle) * radius + center.y(); + float bottomRadius = (float) Math.cos(bottomAngle) * radius; + float topRadius = (float) Math.cos(topAngle) * radius; + + final int POLAR_SEGMENT_COUNT = 20; + for (int segmentP = 0; segmentP < POLAR_SEGMENT_COUNT; segmentP++) { + // "left" and "right" As viewed from outside the sphere + double leftAngle = (((double) segmentP / POLAR_SEGMENT_COUNT) - 1) * 2 * Math.PI; + double rightAngle = (((double) (segmentP+1) / POLAR_SEGMENT_COUNT) - 1) * 2 * Math.PI; + // Points have to wind CCW, so 0->1->2->3 is CCW. + // 0 and 1 use y0, 2 and 3 use y1. + float x0, x1, x2, x3, z0, z1, z2, z3; + + x0 = (float) Math.cos(leftAngle) * bottomRadius + center.x(); + x1 = (float) Math.cos(rightAngle) * bottomRadius + center.x(); + x2 = (float) Math.cos(rightAngle) * topRadius + center.x(); + x3 = (float) Math.cos(leftAngle) * topRadius + center.x(); + z0 = (float) Math.sin(leftAngle) * bottomRadius + center.z(); + z1 = (float) Math.sin(rightAngle) * bottomRadius + center.z(); + z2 = (float) Math.sin(rightAngle) * topRadius + center.z(); + z3 = (float) Math.sin(leftAngle) * topRadius + center.z(); + + // Draw the sphere quad + quad(mat, vc, + x0, y0, z0, + x1, y0, z1, + x2, y1, z2, + x3, y1, z3, + 0.7f, 0.2f, 0.2f, 0.5f); + } + } + } + + // Sorry LOSERS, we only let FAST data structures in HERE. + public static void drawBlockArea(PoseStack poseStack, VertexConsumer vc, LongOpenHashSet blocks) { + + for (long p : blocks) { + + + // Neighbor checks: only render faces exposed to non-oxygen + boolean up = blocks.contains(LongPos.above(p)); + boolean down = blocks.contains(LongPos.below(p)); + boolean north = blocks.contains(LongPos.north(p)); + boolean south = blocks.contains(LongPos.south(p)); + boolean east = blocks.contains(LongPos.east(p)); + boolean west = blocks.contains(LongPos.west(p)); + + if (up && down && north && south && east && west) continue; + + drawBlockFaces(poseStack, vc, p, up, down, north, south, east, west); + } + } + + public static void drawBlockFaces( + PoseStack poseStack, VertexConsumer vc, long posAsLong, + boolean up, boolean down, boolean north, boolean south, boolean east, boolean west) { + final float eps = 0.0025f; + float x0 = LongPos.getX(posAsLong) + eps; + float y0 = LongPos.getY(posAsLong) + eps; + float z0 = LongPos.getZ(posAsLong) + eps; + float x1 = LongPos.getX(posAsLong) + 1 - eps; + float y1 = LongPos.getY(posAsLong) + 1 - eps; + float z1 = LongPos.getZ(posAsLong) + 1 - eps; + + // Color (ARGB-ish but as floats) + float r = 0.2f, g = 0.8f, b = 1.0f, a = 0.18f; + + Matrix4f mat = poseStack.last().pose(); + + // IMPORTANT: vertex winding should be consistent (counter-clockwise) + if (!up) quad(mat, vc, x0,y1,z0, x1,y1,z0, x1,y1,z1, x0,y1,z1, r,g,b,a); + if (!down) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y0,z0, x0,y0,z0, r,g,b,a); + + if (!north) quad(mat, vc, x1,y0,z0, x0,y0,z0, x0,y1,z0, x1,y1,z0, r,g,b,a); + if (!south) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y1,z1, x0,y1,z1, r,g,b,a); + + if (!east) quad(mat, vc, x1,y0,z1, x1,y0,z0, x1,y1,z0, x1,y1,z1, r,g,b,a); + if (!west) quad(mat, vc, x0,y0,z0, x0,y0,z1, x0,y1,z1, x0,y1,z0, r,g,b,a); + } + + public static void quad( + Matrix4f mat, VertexConsumer vc, + float x0, float y0, float z0, + float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float r, float g, float b, float a + ) { + // POSITION_COLOR format: ONLY position + color. + vc.addVertex(mat, x0, y0, z0).setColor(r, g, b, a); + vc.addVertex(mat, x1, y1, z1).setColor(r, g, b, a); + vc.addVertex(mat, x2, y2, z2).setColor(r, g, b, a); + vc.addVertex(mat, x3, y3, z3).setColor(r, g, b, a); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/client/GravityDebugRender.java b/src/main/java/net/xevianlight/aphelion/client/GravityDebugRender.java new file mode 100644 index 0000000..dc1586c --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/client/GravityDebugRender.java @@ -0,0 +1,74 @@ +package net.xevianlight.aphelion.client; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import it.unimi.dsi.fastutil.longs.Long2IntMap; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderStateShard; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.core.BlockPos; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.core.saveddata.GravitySavedData; +import net.xevianlight.aphelion.core.saveddata.types.GravityData; + +@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT) +public class GravityDebugRender { + // Untextured translucent quads (POSITION_COLOR only) + private static final RenderType GRAVITY_FILL = RenderType.create( + "aphelion_gravity_fill", + DefaultVertexFormat.POSITION_COLOR, + VertexFormat.Mode.QUADS, + 256, + false, + true, + RenderType.CompositeState.builder() + .setShaderState(RenderStateShard.POSITION_COLOR_SHADER) + .setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY) + .setCullState(RenderStateShard.NO_CULL) + .setDepthTestState(RenderStateShard.LEQUAL_DEPTH_TEST) + .setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE) + .createCompositeState(true) + ); + + @SubscribeEvent + public static void onRenderLevel(RenderLevelStageEvent event) { + // One stage only (pick one that exists and looks good) + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return; + + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null || mc.player == null) return; + if (!mc.gui.getDebugOverlay().showDebugScreen()) return; + + PoseStack poseStack = event.getPoseStack(); + var cam = mc.gameRenderer.getMainCamera(); + var camPos = cam.getPosition(); + + poseStack.pushPose(); + poseStack.translate(-camPos.x, -camPos.y, -camPos.z); + + // I'm lazy, so i'm just gonna make this work on a singleplayer server and call it a day :P + IntegratedServer singleplayer = mc.getSingleplayerServer(); + if (singleplayer == null) return; + + MultiBufferSource.BufferSource bufferSource = mc.renderBuffers().bufferSource(); + VertexConsumer vc = bufferSource.getBuffer(GRAVITY_FILL); + + for (Long2IntMap.Entry gravityEntry : GravitySavedData.get(singleplayer.getLevel(mc.level.dimension()))._debug_getGravityData().long2IntEntrySet()) { + GravityData d = GravityData.unpack(gravityEntry.getIntValue()); + BlockPos p = BlockPos.of(gravityEntry.getLongKey()); + + DebugRenderUtils.drawSphere(poseStack, vc, p.getCenter().toVector3f(), d.getRadius()); + } + + poseStack.popPose(); + bufferSource.endBatch(GRAVITY_FILL); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/client/OxygenDebugRender.java b/src/main/java/net/xevianlight/aphelion/client/OxygenDebugRender.java index f9e9423..87bf01a 100644 --- a/src/main/java/net/xevianlight/aphelion/client/OxygenDebugRender.java +++ b/src/main/java/net/xevianlight/aphelion/client/OxygenDebugRender.java @@ -17,6 +17,8 @@ import net.neoforged.neoforge.client.event.RenderLevelStageEvent; import net.xevianlight.aphelion.Aphelion; import org.joml.Matrix4f; +import java.util.Collection; + @EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT) public final class OxygenDebugRender { @@ -56,63 +58,9 @@ public final class OxygenDebugRender { MultiBufferSource.BufferSource bufferSource = mc.renderBuffers().bufferSource(); VertexConsumer vc = bufferSource.getBuffer(OXYGEN_FILL); - // Render surface faces only (fast + pretty) - for (long l : ClientOxygenCache.OXYGEN) { - BlockPos p = BlockPos.of(l); - drawSurfaceFaces(poseStack, vc, p); - } + DebugRenderUtils.drawBlockArea(poseStack, vc, ClientOxygenCache.OXYGEN); poseStack.popPose(); bufferSource.endBatch(OXYGEN_FILL); } - - private static void drawSurfaceFaces(PoseStack poseStack, VertexConsumer vc, BlockPos p) { - // Neighbor checks: only render faces exposed to non-oxygen - boolean up = ClientOxygenCache.OXYGEN.contains(p.above().asLong()); - boolean down = ClientOxygenCache.OXYGEN.contains(p.below().asLong()); - boolean north = ClientOxygenCache.OXYGEN.contains(p.north().asLong()); - boolean south = ClientOxygenCache.OXYGEN.contains(p.south().asLong()); - boolean east = ClientOxygenCache.OXYGEN.contains(p.east().asLong()); - boolean west = ClientOxygenCache.OXYGEN.contains(p.west().asLong()); - - if (up && down && north && south && east && west) return; - - final float eps = 0.0025f; - float x0 = p.getX() + eps; - float y0 = p.getY() + eps; - float z0 = p.getZ() + eps; - float x1 = p.getX() + 1 - eps; - float y1 = p.getY() + 1 - eps; - float z1 = p.getZ() + 1 - eps; - - // Color (ARGB-ish but as floats) - float r = 0.2f, g = 0.8f, b = 1.0f, a = 0.18f; - - Matrix4f mat = poseStack.last().pose(); - - // IMPORTANT: vertex winding should be consistent (counter-clockwise) - if (!up) quad(mat, vc, x0,y1,z0, x1,y1,z0, x1,y1,z1, x0,y1,z1, r,g,b,a); - if (!down) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y0,z0, x0,y0,z0, r,g,b,a); - - if (!north) quad(mat, vc, x1,y0,z0, x0,y0,z0, x0,y1,z0, x1,y1,z0, r,g,b,a); - if (!south) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y1,z1, x0,y1,z1, r,g,b,a); - - if (!east) quad(mat, vc, x1,y0,z1, x1,y0,z0, x1,y1,z0, x1,y1,z1, r,g,b,a); - if (!west) quad(mat, vc, x0,y0,z0, x0,y0,z1, x0,y1,z1, x0,y1,z0, r,g,b,a); - } - - private static void quad( - Matrix4f mat, VertexConsumer vc, - float x0, float y0, float z0, - float x1, float y1, float z1, - float x2, float y2, float z2, - float x3, float y3, float z3, - float r, float g, float b, float a - ) { - // POSITION_COLOR format: ONLY position + color. - vc.addVertex(mat, x0, y0, z0).setColor(r, g, b, a); - vc.addVertex(mat, x1, y1, z1).setColor(r, g, b, a); - vc.addVertex(mat, x2, y2, z2).setColor(r, g, b, a); - vc.addVertex(mat, x3, y3, z3).setColor(r, g, b, a); - } } diff --git a/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java b/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java index 600035d..e8824f1 100644 --- a/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java +++ b/src/main/java/net/xevianlight/aphelion/core/init/ModBlockEntities.java @@ -51,4 +51,9 @@ public class ModBlockEntities { BLOCK_ENTITIES.register("rocket_assembler_block_entity", () -> BlockEntityType.Builder.of( RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER_BLOCK.get()).build(null) ); + + public static final Supplier> GRAVITY_TEST_BLOCK_ENTITY = + BLOCK_ENTITIES.register("gravity_test_block_entity", () -> BlockEntityType.Builder.of( + GravityTestBlockEntity::new, ModBlocks.GRAVITY_TEST_BLOCK.get()).build(null) + ); } diff --git a/src/main/java/net/xevianlight/aphelion/core/init/ModBlocks.java b/src/main/java/net/xevianlight/aphelion/core/init/ModBlocks.java index ff032a1..a237fbb 100644 --- a/src/main/java/net/xevianlight/aphelion/core/init/ModBlocks.java +++ b/src/main/java/net/xevianlight/aphelion/core/init/ModBlocks.java @@ -6,6 +6,7 @@ import net.neoforged.neoforge.registries.DeferredRegister; import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.block.custom.*; import net.xevianlight.aphelion.block.dummy.VAFMultiblockDummyBlock; +import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity; public class ModBlocks { public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(Aphelion.MOD_ID); @@ -20,4 +21,5 @@ public class ModBlocks { public static final DeferredBlock VAF_MULTIBLOCK_DUMMY_BLOCK = BLOCKS.register("vaf_dummy_block", () -> new VAFMultiblockDummyBlock(VAFMultiblockDummyBlock.getProperties())); public static final DeferredBlock OXYGEN_TEST_BLOCK = BLOCKS.register("oxygen_test_block", () -> new OxygenTestBlock(OxygenTestBlock.getProperties())); public static final DeferredBlock ROCKET_ASSEMBLER_BLOCK = BLOCKS.register("rocket_assemblerblock", () -> new RocketAssemblerBlock(RocketAssemblerBlock.getProperties())); + public static final DeferredBlock GRAVITY_TEST_BLOCK = BLOCKS.register("gravity_test_block", () -> new GravityTestBlock(GravityTestBlock.getProperties())); } diff --git a/src/main/java/net/xevianlight/aphelion/core/init/ModItems.java b/src/main/java/net/xevianlight/aphelion/core/init/ModItems.java index 3edecd6..3dd3db1 100644 --- a/src/main/java/net/xevianlight/aphelion/core/init/ModItems.java +++ b/src/main/java/net/xevianlight/aphelion/core/init/ModItems.java @@ -36,6 +36,7 @@ public static final DeferredItem MUSIC_DISC_BIT_SHIFT = ITEMS.register("mu public static final DeferredItem ARC_FURNACE_CASING_BLOCK = ITEMS.register("arc_furnace_casing", () -> new BlockItem(ModBlocks.ARC_FURNACE_CASING_BLOCK.get(), ArcFurnaceCasingBlock.getItemProperties())); public static final DeferredItem VACUUM_ARC_FURNACE_CONTROLLER = ITEMS.register("vacuum_arc_furnace_controller", () -> new BlockItem(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get(), VacuumArcFurnaceController.getItemProperties())); public static final DeferredItem OXYGEN_TEST_BLOCK = ITEMS.register("oxygen_test_block", () -> new BlockItem(ModBlocks.OXYGEN_TEST_BLOCK.get(), new Item.Properties())); + public static final DeferredItem GRAVITY_TEST_BLOCK = ITEMS.register("gravity_test_block", () -> new BlockItem(ModBlocks.GRAVITY_TEST_BLOCK.get(), new Item.Properties())); public static final DeferredItem LAUNCH_PAD = ITEMS.register("launch_pad", () -> new BlockItem(ModBlocks.LAUNCH_PAD.get(), LaunchPad.getItemProperties())); // public static final DeferredItem VAF_MULTIBLOCK_DUMMY_BLOCK = ITEMS.register("vaf_multiblock_dummy_block", () -> new BlockItem(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), VAFMultiblockDummyBlock.getItemProperties())); } diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java index 435cb33..bb888e5 100644 --- a/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java @@ -94,6 +94,9 @@ public class GravitySavedData extends SavedData { gravityData.put(pos.asLong(), data.pack()); } + public Long2IntOpenHashMap _debug_getGravityData() { + return gravityData; + } /** diff --git a/src/main/java/net/xevianlight/aphelion/datagen/ModBlockLootTableProvider.java b/src/main/java/net/xevianlight/aphelion/datagen/ModBlockLootTableProvider.java index 7471d65..9c7c521 100644 --- a/src/main/java/net/xevianlight/aphelion/datagen/ModBlockLootTableProvider.java +++ b/src/main/java/net/xevianlight/aphelion/datagen/ModBlockLootTableProvider.java @@ -26,6 +26,8 @@ public class ModBlockLootTableProvider extends BlockLootSubProvider { dropSelf(ModBlocks.OXYGEN_TEST_BLOCK.get()); dropOther(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), ItemStack.EMPTY.getItem()); dropSelf(ModBlocks.LAUNCH_PAD.get()); + dropSelf(ModBlocks.GRAVITY_TEST_BLOCK.get()); + dropSelf(ModBlocks.ROCKET_ASSEMBLER_BLOCK.get()); } @Override diff --git a/src/main/java/net/xevianlight/aphelion/datagen/ModBlockStateProvider.java b/src/main/java/net/xevianlight/aphelion/datagen/ModBlockStateProvider.java index 04b15a2..95fdb28 100644 --- a/src/main/java/net/xevianlight/aphelion/datagen/ModBlockStateProvider.java +++ b/src/main/java/net/xevianlight/aphelion/datagen/ModBlockStateProvider.java @@ -32,7 +32,9 @@ public class ModBlockStateProvider extends BlockStateProvider { // blockItem(ModBlocks.LAUNCH_PAD); blockItem(ModBlocks.ARC_FURNACE_CASING_BLOCK); + blockWithItem(ModBlocks.OXYGEN_TEST_BLOCK); + blockWithItem(ModBlocks.GRAVITY_TEST_BLOCK); } private void blockWithItem(DeferredBlock deferredBlock) { diff --git a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java index 938ea7f..6ec3e2c 100644 --- a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java +++ b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java @@ -16,9 +16,11 @@ import net.xevianlight.aphelion.core.init.ModBlockEntities; import net.xevianlight.aphelion.network.ClientPlayerStateUpdateHandler; import net.xevianlight.aphelion.network.RocketPayloadHandlers; import net.xevianlight.aphelion.network.PartitionPayloadHandler; +import net.xevianlight.aphelion.network.UpdateGravityTestBlockHandler; import net.xevianlight.aphelion.network.packet.ClientPlayerStateUpdatePacket; import net.xevianlight.aphelion.network.packet.PartitionPayload; import net.xevianlight.aphelion.network.packet.RocketLaunchPayload; +import net.xevianlight.aphelion.network.packet.UpdateGravityTestBlockPacket; @EventBusSubscriber(modid = Aphelion.MOD_ID) public class ModBusEvents { @@ -57,5 +59,10 @@ public class ModBusEvents { ClientPlayerStateUpdateHandler::handleDataOnMain ); + registrar.playToServer( + UpdateGravityTestBlockPacket.TYPE, + UpdateGravityTestBlockPacket.STREAM_CODEC, + UpdateGravityTestBlockHandler::handleDataOnMain + ); } } diff --git a/src/main/java/net/xevianlight/aphelion/network/UpdateGravityTestBlockHandler.java b/src/main/java/net/xevianlight/aphelion/network/UpdateGravityTestBlockHandler.java new file mode 100644 index 0000000..d2934b6 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/UpdateGravityTestBlockHandler.java @@ -0,0 +1,27 @@ +package net.xevianlight.aphelion.network; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity; +import net.xevianlight.aphelion.network.packet.UpdateGravityTestBlockPacket; + +public class UpdateGravityTestBlockHandler { + public static void handleDataOnMain(UpdateGravityTestBlockPacket packet, IPayloadContext context) { + context.enqueueWork(() -> { + BlockPos pos = packet.pos(); + float radius = packet.radius(); + float strength = packet.strength(); + + Level level = context.player().level(); + if (level.getBlockEntity(pos) instanceof GravityTestBlockEntity blockEntity) { + blockEntity.setRadius(radius); + blockEntity.setStrength(strength); + blockEntity.sendUpdate(); + level.sendBlockUpdated(pos, level.getBlockState(pos), level.getBlockState(pos), Block.UPDATE_ALL); + } + }); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/network/packet/UpdateGravityTestBlockPacket.java b/src/main/java/net/xevianlight/aphelion/network/packet/UpdateGravityTestBlockPacket.java new file mode 100644 index 0000000..7467817 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/packet/UpdateGravityTestBlockPacket.java @@ -0,0 +1,30 @@ +package net.xevianlight.aphelion.network.packet; + +import io.netty.buffer.ByteBuf; +import net.minecraft.core.BlockPos; +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; +import net.xevianlight.aphelion.Aphelion; + +public record UpdateGravityTestBlockPacket(BlockPos pos, float radius, float strength) implements CustomPacketPayload { + + public static final CustomPacketPayload.Type TYPE = + new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "update_oxygen_test_block")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + BlockPos.STREAM_CODEC, + UpdateGravityTestBlockPacket::pos, + ByteBufCodecs.FLOAT, + UpdateGravityTestBlockPacket::radius, + ByteBufCodecs.FLOAT, + UpdateGravityTestBlockPacket::strength, + UpdateGravityTestBlockPacket::new + ); + + @Override + public CustomPacketPayload.Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockMenu.java b/src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockMenu.java new file mode 100644 index 0000000..3999fca --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockMenu.java @@ -0,0 +1,110 @@ +package net.xevianlight.aphelion.screen; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.BrewingStandMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.neoforge.items.SlotItemHandler; +import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity; +import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity; +import org.jetbrains.annotations.NotNull; + +public class GravityTestBlockMenu extends AbstractContainerMenu { + public final GravityTestBlockEntity blockEntity; + private final Level level; + + public GravityTestBlockMenu(int i, Inventory inventory, FriendlyByteBuf extraData) { + this(i, inventory, inventory.player.level().getBlockEntity(extraData.readBlockPos())); + } + + public GravityTestBlockMenu(int i, Inventory inventory, BlockEntity blockEntity) { + super(ModMenuTypes.GRAVITY_TEST_BLOCK_MENU.get(), i); + this.blockEntity = ((GravityTestBlockEntity) blockEntity); + this.level = inventory.player.level(); + + // Init stuff + addPlayerInventory(inventory); + addPlayerHotbar(inventory); + + + } + + @Override + public boolean stillValid(Player player) { + return true; + } + + // CREDIT GOES TO: diesieben07 | https://github.com/diesieben07/SevenCommons + // must assign a slot number to each of the slots used by the GUI. + // For this container, we can see both the tile inventory's slots as well as the player inventory slots and the hotbar. + // Each time we add a Slot to the container, it automatically increases the slotIndex, which means + // 0 - 8 = hotbar slots (which will map to the InventoryPlayer slot numbers 0 - 8) + // 9 - 35 = player inventory slots (which map to the InventoryPlayer slot numbers 9 - 35) + // 36 - 44 = TileInventory slots, which map to our TileEntity slot numbers 0 - 8) + private static final int HOTBAR_SLOT_COUNT = 9; + private static final int PLAYER_INVENTORY_ROW_COUNT = 3; + private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9; + private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_COLUMN_COUNT * PLAYER_INVENTORY_ROW_COUNT; + private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT; + private static final int VANILLA_FIRST_SLOT_INDEX = 0; + private static final int TE_INVENTORY_FIRST_SLOT_INDEX = VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT; + + // THIS YOU HAVE TO DEFINE! + private static final int TE_INVENTORY_SLOT_COUNT = 0; // must be the number of slots you have! + @Override + public @NotNull ItemStack quickMoveStack(Player playerIn, int pIndex) { + Slot sourceSlot = slots.get(pIndex); + if (sourceSlot == null || !sourceSlot.hasItem()) return ItemStack.EMPTY; //EMPTY_ITEM + ItemStack sourceStack = sourceSlot.getItem(); + ItemStack copyOfSourceStack = sourceStack.copy(); + + // Check if the slot clicked is one of the vanilla container slots + if (pIndex < VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT) { + // This is a vanilla container slot so merge the stack into the tile inventory + if (!moveItemStackTo(sourceStack, TE_INVENTORY_FIRST_SLOT_INDEX, TE_INVENTORY_FIRST_SLOT_INDEX + + TE_INVENTORY_SLOT_COUNT, false)) { + blockEntity.sendUpdate(); + return ItemStack.EMPTY; // EMPTY_ITEM + } + } else if (pIndex < TE_INVENTORY_FIRST_SLOT_INDEX + TE_INVENTORY_SLOT_COUNT) { + // This is a TE slot so merge the stack into the players inventory + if (!moveItemStackTo(sourceStack, VANILLA_FIRST_SLOT_INDEX, VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT, false)) { + blockEntity.sendUpdate(); + return ItemStack.EMPTY; + } + } else { + System.out.println("Invalid slotIndex:" + pIndex); + return ItemStack.EMPTY; + } + // If stack size == 0 (the entire stack was moved) set slot contents to null + if (sourceStack.getCount() == 0) { + sourceSlot.set(ItemStack.EMPTY); + blockEntity.sendUpdate(); + } else { + blockEntity.sendUpdate(); + sourceSlot.setChanged(); + } + sourceSlot.onTake(playerIn, sourceStack); + blockEntity.sendUpdate(); + return copyOfSourceStack; + } + + private void addPlayerInventory(Inventory playerInventory) { + for (int i = 0; i < 3; ++i) { + for (int l = 0; l < 9; ++l) { + this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18)); + } + } + } + + private void addPlayerHotbar(Inventory playerInventory) { + for (int i = 0; i < 9; ++i) { + this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); + } + } +} diff --git a/src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockScreen.java b/src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockScreen.java new file mode 100644 index 0000000..b38c3df --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/screen/GravityTestBlockScreen.java @@ -0,0 +1,94 @@ +package net.xevianlight.aphelion.screen; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.gui.screens.inventory.FurnaceScreen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Inventory; +import net.neoforged.neoforge.network.PacketDistributor; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.network.packet.UpdateGravityTestBlockPacket; + +public class GravityTestBlockScreen extends AbstractContainerScreen { + + private static final ResourceLocation GUI_TEXTURE = + ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "textures/gui/gravity_test_block/gui.png"); + + public GravityTestBlockScreen(GravityTestBlockMenu menu, Inventory playerInventory, Component title) { + super(menu, playerInventory, title); + } + + private StringWidget gravityWidget; + private StringWidget rangeWidget; + + @Override + protected void init() { + super.init(); + + // Gets rid of labels + this.inventoryLabelY = 73; + this.titleLabelY = 5; + + // Increase Gravity + this.addRenderableWidget(Button.builder(Component.literal("+"), (button) -> { + PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize, menu.blockEntity.gravityStrength + 0.1f)); + }).bounds(this.leftPos + 7, this.topPos + 30, 9, 9).build()); + // Decrease Gravity + this.addRenderableWidget(Button.builder(Component.literal("-"), (button) -> { + PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize, menu.blockEntity.gravityStrength - 0.1f)); + }).bounds(this.leftPos + 19, this.topPos + 30, 9, 9).build()); + // Increase Radius + this.addRenderableWidget(Button.builder(Component.literal("+"), (button) -> { + PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize + 1, menu.blockEntity.gravityStrength)); + }).bounds(this.leftPos + 135, this.topPos + 32, 9, 9).build()); + // Decrease Radius + this.addRenderableWidget(Button.builder(Component.literal("-"), (button) -> { + PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize - 1, menu.blockEntity.gravityStrength)); + }).bounds(this.leftPos + 147, this.topPos + 32, 9, 9).build()); + + // Current Gravity + gravityWidget = new StringWidget( + this.leftPos + 11, + this.topPos+46, + 26, + 9, + Component.literal("" + menu.blockEntity.gravityStrength), + this.font); + this.addRenderableWidget(gravityWidget); + + // Current Radius + rangeWidget = new StringWidget( + this.leftPos + 139, + this.topPos+46, + 26, + 9, + Component.literal("" + menu.blockEntity.areaSize), + this.font); + this.addRenderableWidget(rangeWidget); + } + + @Override + protected void renderBg(GuiGraphics pGuiGraphics, float pPartialTick, int pMouseX, int pMouseY) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.setShaderTexture(0, GUI_TEXTURE); + int x = (width - imageWidth) / 2; + int y = (height - imageHeight) / 2; + + pGuiGraphics.blit(GUI_TEXTURE, x, y, 0, 0, imageWidth, imageHeight); + } + + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) { + renderBackground(guiGraphics, mouseX, mouseY, delta); + super.render(guiGraphics, mouseX, mouseY, delta); + renderTooltip(guiGraphics, mouseX, mouseY); + gravityWidget.setMessage(Component.literal(String.format("%.1f", menu.blockEntity.gravityStrength))); + rangeWidget.setMessage(Component.literal("" + menu.blockEntity.areaSize)); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/screen/ModMenuTypes.java b/src/main/java/net/xevianlight/aphelion/screen/ModMenuTypes.java index 635e168..c9afa6d 100644 --- a/src/main/java/net/xevianlight/aphelion/screen/ModMenuTypes.java +++ b/src/main/java/net/xevianlight/aphelion/screen/ModMenuTypes.java @@ -10,6 +10,8 @@ import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; import net.xevianlight.aphelion.Aphelion; +import java.awt.*; + public class ModMenuTypes { public static final DeferredRegister> MENUS = DeferredRegister.create(Registries.MENU, Aphelion.MOD_ID); @@ -23,6 +25,9 @@ public class ModMenuTypes { public static DeferredHolder,MenuType> VACUUM_ARC_FURNACE_MENU = registerMenuType("vacuum_arc_furnace_menu", VacuumArcFurnaceMenu::new); + public static DeferredHolder,MenuType> GRAVITY_TEST_BLOCK_MENU = + registerMenuType("gravity_test_block_menu", GravityTestBlockMenu::new); + private static DeferredHolder, MenuType> registerMenuType(String name, IContainerFactory factory) { return MENUS.register(name, () -> IMenuTypeExtension.create(factory)); diff --git a/src/main/java/net/xevianlight/aphelion/systems/GravityService.java b/src/main/java/net/xevianlight/aphelion/systems/GravityService.java index 89fcae1..3804947 100644 --- a/src/main/java/net/xevianlight/aphelion/systems/GravityService.java +++ b/src/main/java/net/xevianlight/aphelion/systems/GravityService.java @@ -13,9 +13,13 @@ import net.xevianlight.aphelion.core.saveddata.types.GravityData; public class GravityService { - /// If i did this right, there SHOULDN'T be a way to break client/server separation with this... - /// you shouldn't be able to get the "ServerLevel" object from the Client side unless you're already - /// breaking that rule, in which case, go for it lmfao + public static void setGravityArea(ServerLevel level, BlockPos pos, float accel, int radius) { + GravitySavedData.get(level).setGravityRegion(pos, new GravityData(accel, radius)); + } + + public static void removeGravityArea(ServerLevel level, BlockPos pos) { + GravitySavedData.get(level).removeGravityRegion(pos); + } public static float getGravityAccel(Level level, BlockPos pos) { if (level.isClientSide) { diff --git a/src/main/resources/assets/aphelion/textures/block/gravity_test_block.png b/src/main/resources/assets/aphelion/textures/block/gravity_test_block.png new file mode 100644 index 0000000000000000000000000000000000000000..8a8f174b49d3594e37460379751676b66e266a71 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|E_u2*hFJ6_ zrz9l&IPbuikeHB=(80s4_EBW9PTbTX=X{RuW13U+p&t#0(0n*~F z5^OrrJH^%~r3i@1ojCvkCk`C=aR1)EmPR0ObhDohq7zTLSk943{v>Z`00cHWEhLi~ z_ST-PpWzmCaB}x#Nr}au<^DbA|8RW5pB;(Hfo%q9Z;cPkoO_YiaT|vkJA=BE>ucAf SPi;WoF?hQAxvXf+DgpxHD~X7Jj{5r0b^2-k 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