diff --git a/.gitignore b/.gitignore index 31d2550..b4b9525 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ run runs run-data -repo \ No newline at end of file +repo diff --git a/src/generated/resources/assets/aphelion/blockstates/electric_arc_furnace.json b/src/generated/resources/assets/aphelion/blockstates/electric_arc_furnace.json new file mode 100644 index 0000000..0d761dc --- /dev/null +++ b/src/generated/resources/assets/aphelion/blockstates/electric_arc_furnace.json @@ -0,0 +1,64 @@ +{ + "variants": { + "facing=east,formed=false,lit=false": { + "model": "aphelion:block/electric_arc_furnace", + "y": 90 + }, + "facing=east,formed=false,lit=true": { + "model": "aphelion:block/electric_arc_furnace", + "y": 90 + }, + "facing=east,formed=true,lit=false": { + "model": "aphelion:block/electric_arc_furnace", + "y": 90 + }, + "facing=east,formed=true,lit=true": { + "model": "aphelion:block/electric_arc_furnace", + "y": 90 + }, + "facing=north,formed=false,lit=false": { + "model": "aphelion:block/electric_arc_furnace" + }, + "facing=north,formed=false,lit=true": { + "model": "aphelion:block/electric_arc_furnace" + }, + "facing=north,formed=true,lit=false": { + "model": "aphelion:block/electric_arc_furnace" + }, + "facing=north,formed=true,lit=true": { + "model": "aphelion:block/electric_arc_furnace" + }, + "facing=south,formed=false,lit=false": { + "model": "aphelion:block/electric_arc_furnace", + "y": 180 + }, + "facing=south,formed=false,lit=true": { + "model": "aphelion:block/electric_arc_furnace", + "y": 180 + }, + "facing=south,formed=true,lit=false": { + "model": "aphelion:block/electric_arc_furnace", + "y": 180 + }, + "facing=south,formed=true,lit=true": { + "model": "aphelion:block/electric_arc_furnace", + "y": 180 + }, + "facing=west,formed=false,lit=false": { + "model": "aphelion:block/electric_arc_furnace", + "y": 270 + }, + "facing=west,formed=false,lit=true": { + "model": "aphelion:block/electric_arc_furnace", + "y": 270 + }, + "facing=west,formed=true,lit=false": { + "model": "aphelion:block/electric_arc_furnace", + "y": 270 + }, + "facing=west,formed=true,lit=true": { + "model": "aphelion:block/electric_arc_furnace", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/aphelion/models/block/electric_arc_furnace.json b/src/generated/resources/assets/aphelion/models/block/electric_arc_furnace.json new file mode 100644 index 0000000..07f6a0a --- /dev/null +++ b/src/generated/resources/assets/aphelion/models/block/electric_arc_furnace.json @@ -0,0 +1,8 @@ +{ + "parent": "minecraft:block/orientable", + "textures": { + "front": "aphelion:block/electric_arc_furnace_front", + "side": "minecraft:block/blast_furnace_side", + "top": "minecraft:block/blast_furnace_top" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/aphelion/models/item/arc_furnace_casing.json b/src/generated/resources/assets/aphelion/models/item/arc_furnace_casing.json new file mode 100644 index 0000000..c83a895 --- /dev/null +++ b/src/generated/resources/assets/aphelion/models/item/arc_furnace_casing.json @@ -0,0 +1,3 @@ +{ + "parent": "aphelion:block/arc_furnace_casing" +} \ No newline at end of file diff --git a/src/generated/resources/assets/aphelion/models/item/vacuum_arc_furnace_controller.json b/src/generated/resources/assets/aphelion/models/item/vacuum_arc_furnace_controller.json new file mode 100644 index 0000000..8ba7348 --- /dev/null +++ b/src/generated/resources/assets/aphelion/models/item/vacuum_arc_furnace_controller.json @@ -0,0 +1,3 @@ +{ + "parent": "aphelion:block/vacuum_arc_furnace_controller" +} \ No newline at end of file diff --git a/src/generated/resources/data/aphelion/loot_table/blocks/arc_furnace_casing.json b/src/generated/resources/data/aphelion/loot_table/blocks/arc_furnace_casing.json new file mode 100644 index 0000000..f8283e4 --- /dev/null +++ b/src/generated/resources/data/aphelion/loot_table/blocks/arc_furnace_casing.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "aphelion:arc_furnace_casing" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "aphelion:blocks/arc_furnace_casing" +} \ No newline at end of file diff --git a/src/generated/resources/data/aphelion/loot_table/blocks/launch_pad.json b/src/generated/resources/data/aphelion/loot_table/blocks/launch_pad.json new file mode 100644 index 0000000..565eadc --- /dev/null +++ b/src/generated/resources/data/aphelion/loot_table/blocks/launch_pad.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "aphelion:launch_pad" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "aphelion:blocks/launch_pad" +} \ No newline at end of file diff --git a/src/generated/resources/data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json b/src/generated/resources/data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json new file mode 100644 index 0000000..459b45b --- /dev/null +++ b/src/generated/resources/data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "aphelion:vacuum_arc_furnace_controller" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "aphelion:blocks/vacuum_arc_furnace_controller" +} \ No newline at end of file diff --git a/src/generated/resources/data/aphelion/loot_table/blocks/vaf_dummy_block.json b/src/generated/resources/data/aphelion/loot_table/blocks/vaf_dummy_block.json new file mode 100644 index 0000000..1c0e60c --- /dev/null +++ b/src/generated/resources/data/aphelion/loot_table/blocks/vaf_dummy_block.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "minecraft:air" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "aphelion:blocks/vaf_dummy_block" +} \ No newline at end of file diff --git a/src/generated/resources/data/aphelion/tags/block/launch_pad.json b/src/generated/resources/data/aphelion/tags/block/launch_pad.json new file mode 100644 index 0000000..bdf9b28 --- /dev/null +++ b/src/generated/resources/data/aphelion/tags/block/launch_pad.json @@ -0,0 +1,5 @@ +{ + "values": [ + "aphelion:launch_pad" + ] +} \ No newline at end of file diff --git a/src/main/java/net/xevianlight/aphelion/block/dummy/entity/BaseMultiblockDummyBlockEntity.java b/src/main/java/net/xevianlight/aphelion/block/dummy/entity/BaseMultiblockDummyBlockEntity.java index cf03ce1..b667ccb 100644 --- a/src/main/java/net/xevianlight/aphelion/block/dummy/entity/BaseMultiblockDummyBlockEntity.java +++ b/src/main/java/net/xevianlight/aphelion/block/dummy/entity/BaseMultiblockDummyBlockEntity.java @@ -142,7 +142,7 @@ public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMult // Force rerender on client if (level != null) { level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3); - requestModelDataUpdate(); // if you rely on model data + requestModelDataUpdate(); // if you rely on model partitionData } } @@ -179,7 +179,7 @@ public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMult setChanged(); if (level != null) { level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3); - requestModelDataUpdate(); // only if you use model data + requestModelDataUpdate(); // only if you use model partitionData } } } diff --git a/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java b/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java index ed9a2ca..9d40ff1 100644 --- a/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java +++ b/src/main/java/net/xevianlight/aphelion/client/AphelionDebugOverlay.java @@ -11,7 +11,7 @@ import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.client.dimension.DimensionRenderer; import net.xevianlight.aphelion.client.dimension.DimensionRendererCache; import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects; -import net.xevianlight.aphelion.core.space.SpacePartitionSavedData; +import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData; import net.xevianlight.aphelion.util.SpacePartitionHelper; @EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT) @@ -50,5 +50,6 @@ public class AphelionDebugOverlay { event.getLeft().add(" Orbit: " + orbitId); // event.getLeft().add(" Sky: " + rendererSummary); event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z)); + event.getLeft().add(" Station Destination:" + PartitionClientState.lastData().getDestination()); } } \ No newline at end of file diff --git a/src/main/java/net/xevianlight/aphelion/client/PartitionClientState.java b/src/main/java/net/xevianlight/aphelion/client/PartitionClientState.java index 76cefec..f559ae4 100644 --- a/src/main/java/net/xevianlight/aphelion/client/PartitionClientState.java +++ b/src/main/java/net/xevianlight/aphelion/client/PartitionClientState.java @@ -1,5 +1,6 @@ package net.xevianlight.aphelion.client; +import net.xevianlight.aphelion.core.saveddata.types.PartitionData; import net.xevianlight.aphelion.network.packet.PartitionPayload; import java.util.Optional; @@ -14,7 +15,15 @@ public final class PartitionClientState { } public static String idOrUnknown() { - return last != null ? last.id() : "unknown"; + String orbit = String.valueOf(last.partitionData().getOrbit()); + if (orbit == null) { + return "aphleion:orbit/default"; + } + return last != null ? orbit : "unknown"; + } + + public static PartitionData lastData() { + return last.partitionData(); } // // public static int pxOr(int fallback) { diff --git a/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionSkyEffects.java b/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionSkyEffects.java index d917fde..ed54b7b 100644 --- a/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionSkyEffects.java +++ b/src/main/java/net/xevianlight/aphelion/client/dimension/DimensionSkyEffects.java @@ -71,7 +71,7 @@ public class DimensionSkyEffects extends DimensionSpecialEffects { // int py = PartitionClientState.pyOr(0); var data = ResourceLocation.parse(PartitionClientState.idOrUnknown()); -// var data = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z); +// var partitionData = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z); if (data != null) return data; return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default"); diff --git a/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java b/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java index 9a56231..72d4f83 100644 --- a/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java +++ b/src/main/java/net/xevianlight/aphelion/client/dimension/SpaceSkyEffects.java @@ -5,12 +5,9 @@ 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.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.phys.Vec3; 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; @@ -29,7 +26,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects { return ResourceLocation.withDefaultNamespace("overworld"); } if (effectsId.equals(Aphelion.id("space"))) { - return SpaceSkyEffects.orbitForPos(camera.getPosition()); // or inline this logic + return orbitForPos(camera.getPosition()); // or inline this logic } return effectsId; } @@ -80,7 +77,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects { // int py = PartitionClientState.pyOr(0); var data = ResourceLocation.parse(PartitionClientState.idOrUnknown()); -// var data = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z); +// var partitionData = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z); if (data != null) return data; return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default"); diff --git a/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java b/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java index 3e009b0..6fff033 100644 --- a/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java +++ b/src/main/java/net/xevianlight/aphelion/commands/AphelionCommand.java @@ -15,17 +15,18 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.*; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ColumnPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.RelativeMovement; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.xevianlight.aphelion.Aphelion; -import net.xevianlight.aphelion.core.space.SpacePartitionSavedData; +import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData; import net.xevianlight.aphelion.entites.vehicles.RocketEntity; +import net.xevianlight.aphelion.planet.Planet; import net.xevianlight.aphelion.util.RocketStructure; import net.xevianlight.aphelion.util.SpacePartitionHelper; +import net.xevianlight.aphelion.util.registries.ModRegistries; import org.jetbrains.annotations.NotNull; import java.util.EnumSet; @@ -244,6 +245,24 @@ public class AphelionCommand { ) ) ) + .then(Commands.literal("destination") + .then(Commands.literal("set").then( + Commands.argument("pos", ColumnPosArgument.columnPos()) + .then(Commands.argument("id", ResourceLocationArgument.id()) + .executes(context -> { + int x = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").x()); + int z = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").z()); + ResourceLocation orbit = ResourceLocationArgument.getId(context, "id"); + + ServerLevel level = context.getSource().getLevel(); + SpacePartitionSavedData.get(level).getData(x,z).setDestination(orbit); + + return 1; + }) + ) + ) + ) + ) ) .then(Commands.literal("planet") .then(Commands.literal("tp") diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/EnvironmentSavedData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/EnvironmentSavedData.java new file mode 100644 index 0000000..e5a40fa --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/EnvironmentSavedData.java @@ -0,0 +1,129 @@ +package net.xevianlight.aphelion.core.saveddata; + +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; +import net.xevianlight.aphelion.core.saveddata.types.EnvironmentData; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * Pattern: + * - World-level SavedData + * - Outer map keyed by section (chunkX, sectionY, chunkZ) packed into a long + * - Inner map keyed by localIndex (0..4095) -> packed int env value + * + * Sparse by design: blocks not present in the inner map are implicitly "default environment". + */ +public class EnvironmentSavedData extends SavedData { + + private final Long2IntOpenHashMap envData = new Long2IntOpenHashMap(); + + private static final String NAME = "aphelion_environment"; + + public static EnvironmentSavedData create() { + return new EnvironmentSavedData(); + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag tag, @NotNull HolderLookup.Provider provider) { + int size = envData.size(); + long[] positions = new long[size]; + int[] data = new int[size]; + + int i = 0; + for (var e : envData.long2IntEntrySet()) { + positions[i] = e.getLongKey(); + data[i] = e.getIntValue(); + i++; + } + + tag.putLongArray("Position", positions); + tag.putIntArray("Value", data); + + return tag; + } + + public static EnvironmentSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) { + EnvironmentSavedData data = create(); + + if (!tag.contains("Position", Tag.TAG_LONG_ARRAY) || !tag.contains("Value", Tag.TAG_INT_ARRAY)) { return data; } + + long[] positions = tag.getLongArray("Positions"); + int[] values = tag.getIntArray("Value"); + + int length = Math.min(positions.length, values.length); + + data.envData.ensureCapacity(length); + + for (int i = 0; i < length; i++) { + data.envData.put(positions[i], values[i]); + } + + return data; + } + + public EnvironmentData getDataForPosition(BlockPos pos) { + int packed = envData.getOrDefault(pos.asLong(), EnvironmentData.DEFAULT_PACKED); + return EnvironmentData.unpack(packed); + } + + public void setDataForPosition(BlockPos pos, EnvironmentData data) { + putOrRemove(pos.asLong(), data.pack()); + } + + public boolean hasOxygen(BlockPos pos) { + var data = getDataForPosition(pos); + return data.hasOxygen(); + } + + public void setOxygen(BlockPos pos, boolean value) { + var data = getDataForPosition(pos); + data.setOxygen(value); + putOrRemove(pos.asLong(), data.pack()); + } + + public float getGravity(BlockPos pos) { + var data = getDataForPosition(pos); + return data.getGravity(); + } + + public void setGravity(BlockPos pos, float value) { + var data = getDataForPosition(pos); + data.setGravity(value); + putOrRemove(pos.asLong(), data.pack()); + } + + public short getTemperature(BlockPos pos) { + var data = getDataForPosition(pos); + return data.getTemperature(); + } + + public void setTemperature(BlockPos pos, short value) { + var data = getDataForPosition(pos); + data.setTemperature(value); + putOrRemove(pos.asLong(), data.pack()); + } + + private void putOrRemove(long key, int packed) { + if (packed == EnvironmentData.DEFAULT_PACKED) { + envData.remove(key); + } else { + envData.put(key, packed); + } + setDirty(); + } + + public static EnvironmentSavedData get(ServerLevel level) { + return level.getDataStorage().computeIfAbsent( + new Factory<>(EnvironmentSavedData::create, EnvironmentSavedData::load), + NAME + ); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/SpacePartitionSavedData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/SpacePartitionSavedData.java new file mode 100644 index 0000000..b10a717 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/SpacePartitionSavedData.java @@ -0,0 +1,185 @@ +package net.xevianlight.aphelion.core.saveddata; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; +import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.core.saveddata.types.PartitionData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class SpacePartitionSavedData extends SavedData { + + private static final String NAME = "aphelion_station_partitions"; + + private final Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); + + public static SpacePartitionSavedData create() { + return new SpacePartitionSavedData(); + } + + public static SpacePartitionSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) { + SpacePartitionSavedData data = create(); + + ListTag entries = tag.getList("Entries", CompoundTag.TAG_COMPOUND); + for (int i = 0; i < entries.size(); i++) { + CompoundTag e = entries.getCompound(i); + + long key = e.getLong("Key"); + + ResourceLocation orbitRL = null; + if (e.contains("Orbit", CompoundTag.TAG_STRING)) { + orbitRL = ResourceLocation.tryParse(e.getString("Orbit")); + } + + PartitionData pd = new PartitionData(orbitRL); + + // Destination (optional) + if (e.contains("Destination", CompoundTag.TAG_STRING)) { + ResourceLocation destRL = ResourceLocation.tryParse(e.getString("Destination")); + pd.setDestination(destRL); // ok if null (parse fail) + } else { + pd.setDestination(null); + } + + // Traveling (optional; default false) + if (e.contains("Traveling", CompoundTag.TAG_BYTE)) { + pd.setTraveling(e.getBoolean("Traveling")); + } + + // Distances (optional; default 0.0) + if (e.contains("DistanceTraveled", CompoundTag.TAG_DOUBLE)) { + pd.setDistanceTraveled(e.getDouble("DistanceTraveled")); + } + if (e.contains("DistanceToDest", CompoundTag.TAG_DOUBLE)) { + pd.setDistanceToDest(e.getDouble("DistanceToDest")); + } + + data.map.put(key, pd); + } + + return data; + } + + @Override + public @NotNull CompoundTag save(CompoundTag tag, HolderLookup.@NotNull Provider registries) { + ListTag entries = new ListTag(); + + map.long2ObjectEntrySet().forEach(entry -> { + long key = entry.getLongKey(); + PartitionData pd = entry.getValue(); + + CompoundTag e = new CompoundTag(); + e.putLong("Key", key); + + // Orbit + if (pd.getOrbit() != null) { + e.putString("Orbit", pd.getOrbit().toString()); + } + + // Destination (only if present) + if (pd.getDestination() != null) { + e.putString("Destination", pd.getDestination().toString()); + } + + // Traveling + distances + e.putBoolean("Traveling", pd.isTraveling()); + + e.putDouble("DistanceTraveled", pd.getDistanceTraveled()); + e.putDouble("DistanceToDest", pd.getDistanceToDest()); + + entries.add(e); + }); + + tag.put("Entries", entries); + return tag; + } + + + public @Nullable ResourceLocation getOrbitForPartition(int px, int pz) { + PartitionData data = map.get(pack(px, pz)); + if (data == null) return null; + return map.get(pack(px, pz)).getOrbit(); + } + + public void setOrbitForPartition(int px, int pz, ResourceLocation orbit) { + long key = pack(px, pz); + PartitionData prev = map.get(key); + PartitionData newData = new PartitionData(prev); + newData.setOrbit(orbit); + if (!newData.equals(prev)) { + map.put(key, newData); + setDirty(); + } + } + + public boolean clearOrbitForPartition(int px, int pz) { + long key = pack(px, pz); + PartitionData removed = map.remove(key); + if (removed != null) { + setDirty(); + return true; + } + return false; + } + + + + public void clearAllOrbits() { + if (!map.isEmpty()) { + map.clear(); + setDirty(); + } + } + + public @Nullable PartitionData getData(int px, int pz) { + long key = pack(px, pz); + PartitionData data = map.get(key); + if (data == null) { + // pick a sensible default orbit, or null if you truly allow it + data = new PartitionData(Aphelion.id("orbit/default")); + map.put(key, data); + setDirty(); + } + return data; + } + + public void overwriteAllExistingOrbits(ResourceLocation orbit) { + if (map.isEmpty()) return; + + boolean changed = false; + for (var entry : map.long2ObjectEntrySet()) { + if(!orbit.equals(entry.getValue())) { + entry.getValue().setOrbit(orbit); + changed = true; + } + } + + if (changed) setDirty(); + } + + public static SpacePartitionSavedData get(ServerLevel level) { + return level.getDataStorage().computeIfAbsent( + new Factory<>(SpacePartitionSavedData::create, SpacePartitionSavedData::load), + NAME + ); + } + + public static long pack(int px, int pz) { + return (((long) px) << 32) | (pz & 0xffffffffL); + } + + public static int unpackX(long key) { + return (int)(key >> 32); + } + + public static int unpackZ(long key) { + return (int)key; + } + +} diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/EnvironmentData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/EnvironmentData.java new file mode 100644 index 0000000..32be1e6 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/EnvironmentData.java @@ -0,0 +1,98 @@ +package net.xevianlight.aphelion.core.saveddata.types; + +public final class EnvironmentData { + + + + public static final boolean DEFAULT_OXYGEN = true; + public static final short DEFAULT_TEMPERATURE = (short) 294.2611; // 70F + public static final float DEFAULT_GRAVITY = 9.80665f; // 1G + + public static final int DEFAULT_PACKED = new EnvironmentData(DEFAULT_OXYGEN, DEFAULT_TEMPERATURE, DEFAULT_GRAVITY).pack(); + + /* We can pack all of this into an int value per block position. + * If we have to store partitionData for an entire chunk section (16^3), this amounts to 16kB per section. + * 1000 sections touched is 16MB + * This is acceptable for partitionData that will only exist where it is not equal to the default values + */ + + private static final int OXYGEN_BITS = 1; // Boolean. Do we have oxygen or no? + private static final int TEMPERATURE_BITS = Short.SIZE; // 16 bits should suffice for temperature, gives 0k to 65536k range, more than enough + private static final int GRAVITY_BITS = 15; // Leftover bits can be assigned to gravity, 32768 values + + private static final float GRAVITY_PRECISION = 100.0f; // 2 decimal precision + + private static final int OXYGEN_BIT = 0; + private static final int TEMPERATURE_BIT = OXYGEN_BIT + OXYGEN_BITS; // next 16 bits + private static final int GRAVITY_BIT = TEMPERATURE_BIT + TEMPERATURE_BITS; // next 15 bits + + private boolean oxygen; + private short temperature; + private float gravity; + + public EnvironmentData(boolean oxygen, short temperature, float gravity) { + this.oxygen = oxygen; + this.temperature = temperature; + this.gravity = gravity; + } + + public int pack() { + int packedData = 0; + + packedData |= (this.oxygen ? 1 : 0) << OXYGEN_BIT; + packedData |= (this.temperature & ((1 << TEMPERATURE_BITS) - 1)) << TEMPERATURE_BIT; + packedData |= (int) (this.gravity * GRAVITY_PRECISION) << GRAVITY_BIT; + + return packedData; + } + + public static EnvironmentData unpack(int packedData) { + boolean oxygen = ((packedData >> OXYGEN_BIT) & 1) == 1; + short temperature = (short) ((packedData >> TEMPERATURE_BIT) & ((1 << TEMPERATURE_BITS) - 1)); + float gravity = ((packedData >> GRAVITY_BIT) & ((1 << GRAVITY_BITS) - 1)) / GRAVITY_PRECISION; + + return new EnvironmentData(oxygen, temperature, gravity); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (EnvironmentData) obj; + return this.oxygen == that.oxygen && + this.temperature == that.temperature && + Float.floatToIntBits(this.gravity) == Float.floatToIntBits(that.gravity); + } + + @Override + public String toString() { + return "EnvironmentData[" + + "oxygen=" + oxygen + ", " + + "temperature=" + temperature + ", " + + "gravity=" + gravity + ']'; + } + + public boolean hasOxygen() { + return oxygen; + } + + public void setOxygen(boolean oxygen) { + this.oxygen = oxygen; + } + + public short getTemperature() { + return temperature; + } + + public void setTemperature(short temperature) { + this.temperature = temperature; + } + + public float getGravity() { + return gravity; + } + + public void setGravity(float gravity) { + this.gravity = gravity; + } +} diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PartitionData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PartitionData.java new file mode 100644 index 0000000..25b2feb --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/PartitionData.java @@ -0,0 +1,121 @@ +package net.xevianlight.aphelion.core.saveddata.types; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; + +public class PartitionData { + @Nullable private ResourceLocation orbit; + @Nullable private ResourceLocation destination; + private boolean traveling; + private double distanceTraveled; + private double distanceToDest; + + public PartitionData(@Nullable ResourceLocation orbit) { + this.orbit = orbit; + this.destination = null; + this.traveling = false; + this.distanceTraveled = 0; + this.distanceToDest = 0; + } + + public PartitionData(PartitionData other) { + this.orbit = other.orbit; + this.destination = other.destination; + this.traveling = other.traveling; + this.distanceTraveled = other.distanceTraveled; + this.distanceToDest = other.distanceToDest; + } + + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + // orbit is nullable -> optional codec + ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC), + d -> Optional.ofNullable(d.getOrbit()), + + ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC), + d -> Optional.ofNullable(d.getDestination()), + + ByteBufCodecs.BOOL, + PartitionData::isTraveling, + + // doubles -> DOUBLE codec + ByteBufCodecs.DOUBLE, + PartitionData::getDistanceTraveled, + + ByteBufCodecs.DOUBLE, + PartitionData::getDistanceToDest, + + (orbitOpt, destOpt, traveling, distTraveled, distToDest) -> { + PartitionData data = new PartitionData(orbitOpt.orElse(null)); + data.destination = destOpt.orElse(null); + data.traveling = traveling; + data.distanceTraveled = distTraveled; + data.distanceToDest = distToDest; + return data; + } + ); + + public @Nullable ResourceLocation getOrbit() { + return this.orbit; + } + + public void setOrbit(ResourceLocation orbit) { + this.orbit = orbit; + } + + public @Nullable ResourceLocation getDestination() { + return destination; + } + + public void setDestination(@Nullable ResourceLocation destination) { + this.destination = destination; + } + + public boolean isTraveling() { + return traveling; + } + + public void setTraveling(boolean traveling) { + this.traveling = traveling; + } + + public double getDistanceTraveled() { + return distanceTraveled; + } + + public void setDistanceTraveled(double distanceTraveled) { + this.distanceTraveled = distanceTraveled; + } + + public double getDistanceToDest() { + return distanceToDest; + } + + public void setDistanceToDest(double distanceToDest) { + this.distanceToDest = distanceToDest; + } + + public void travel(double distance) { + distanceTraveled = Math.min( distanceTraveled + distance, distanceToDest); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + + PartitionData that = (PartitionData) obj; + + return Objects.equals(this.orbit, that.orbit) + && Objects.equals(this.destination, that.destination) + && this.traveling == that.traveling + && Double.compare(this.distanceTraveled, that.distanceTraveled) == 0 + && Double.compare(this.distanceToDest, that.distanceToDest) == 0; + } +} diff --git a/src/main/java/net/xevianlight/aphelion/core/space/SpacePartitionSavedData.java b/src/main/java/net/xevianlight/aphelion/core/space/SpacePartitionSavedData.java deleted file mode 100644 index d254cd9..0000000 --- a/src/main/java/net/xevianlight/aphelion/core/space/SpacePartitionSavedData.java +++ /dev/null @@ -1,121 +0,0 @@ -package net.xevianlight.aphelion.core.space; - -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import net.minecraft.core.HolderLookup; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.saveddata.SavedData; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; - -public class SpacePartitionSavedData extends SavedData { - - private static final String NAME = "aphelion_station_partitions"; - - private final Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); - - public static SpacePartitionSavedData create() { - return new SpacePartitionSavedData(); - } - - public static SpacePartitionSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) { - SpacePartitionSavedData data = create(); - - ListTag entires = tag.getList("Entries", CompoundTag.TAG_COMPOUND); - for (int i = 0; i < entires.size(); i++) { - CompoundTag e = entires.getCompound(i); - long key = e.getLong("Key"); - String orbit = e.getString("Orbit"); // "aphelion/mars" - ResourceLocation orbitRL = ResourceLocation.tryParse(orbit); - if (orbitRL != null) - data.map.put(key, orbitRL); - } - - return data; - } - - @Override - public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { - ListTag entries = new ListTag(); - - map.long2ObjectEntrySet().forEach(entry -> { - CompoundTag e = new CompoundTag(); - e.putLong("Key", entry.getLongKey()); - e.putString("Orbit", entry.getValue().toString()); - entries.add(e); - }); - - tag.put("Entries", entries); - return tag; - } - - public @Nullable ResourceLocation getOrbitForPartition(int px, int pz) { - return map.get(pack(px, pz)); - } - - public void setOrbitForPartition(int px, int pz, ResourceLocation orbit) { - long key = pack(px, pz); - ResourceLocation prev = map.get(key); - if (!orbit.equals(prev)) { - map.put(key, orbit); - setDirty(); - } - } - - public boolean clearOrbitForPartition(int px, int pz) { - long key = pack(px, pz); - ResourceLocation removed = map.remove(key); - if (removed != null) { - setDirty();; - return true; - } - return false; - } - - public void clearAllOrbits() { - if (!map.isEmpty()) { - map.clear(); - setDirty(); - } - } - - public void overwriteAllExistingOrbits(ResourceLocation orbit) { - if (map.isEmpty()) return; - - boolean changed = false; - for (var entry : map.long2ObjectEntrySet()) { - if(!orbit.equals(entry.getValue())) { - entry.setValue(orbit); - changed = true; - } - } - - if (changed) setDirty(); - } - - public static SpacePartitionSavedData get(ServerLevel level) { - return level.getDataStorage().computeIfAbsent( - new Factory<>(SpacePartitionSavedData::create, SpacePartitionSavedData::load), - NAME - ); - } - - public static long pack(int px, int pz) { - return (((long) px) << 32) | (pz & 0xffffffffL); - } - - public static int unpackX(long key) { - return (int)(key >> 32); - } - - public static int unpackZ(long key) { - return (int)key; - } - -} diff --git a/src/main/java/net/xevianlight/aphelion/network/PartitionPayloadHandler.java b/src/main/java/net/xevianlight/aphelion/network/PartitionPayloadHandler.java index d4dca81..a57d446 100644 --- a/src/main/java/net/xevianlight/aphelion/network/PartitionPayloadHandler.java +++ b/src/main/java/net/xevianlight/aphelion/network/PartitionPayloadHandler.java @@ -11,6 +11,6 @@ public class PartitionPayloadHandler { public static void handleDataOnMain(PartitionPayload data, IPayloadContext context) { // Set our local partition state to the packet we just received. PartitionClientState.set(data); - Aphelion.LOGGER.info("Partition packet received! id={}", data.id()); + Aphelion.LOGGER.info("Partition packet received! id={}", data.partitionData()); } } diff --git a/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java b/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java index a64f5e5..186807c 100644 --- a/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java +++ b/src/main/java/net/xevianlight/aphelion/network/PartitionSync.java @@ -4,16 +4,14 @@ package net.xevianlight.aphelion.network; import net.minecraft.server.level.ServerPlayer; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.network.PacketDistributor; import net.xevianlight.aphelion.Aphelion; -import net.xevianlight.aphelion.core.space.SpacePartitionSavedData; +import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData; +import net.xevianlight.aphelion.core.saveddata.types.PartitionData; import net.xevianlight.aphelion.network.packet.PartitionPayload; import net.xevianlight.aphelion.util.SpacePartitionHelper; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; @EventBusSubscriber(modid = Aphelion.MOD_ID) @@ -44,14 +42,14 @@ public final class PartitionSync { } private static PartitionPayload computePartitionFor(ServerPlayer sp) { - // convert player position to partition coords int px = (int)Math.floor(sp.getX() / SpacePartitionHelper.SIZE); int pz = (int)Math.floor(sp.getZ() / SpacePartitionHelper.SIZE); - // Get the orbit for the partition the player is in and create a packet for it - var orbit = SpacePartitionSavedData.get(sp.serverLevel()).getOrbitForPartition(px, pz); - String orbitId = (orbit != null) ? orbit.toString() : "aphelion:orbit/default"; + PartitionData live = SpacePartitionSavedData.get(sp.serverLevel()).getData(px, pz); - return new PartitionPayload(orbitId); + // snapshot so mutations later don’t affect cached payloads + PartitionData snapshot = (live == null) ? null : new PartitionData(live); + + return new PartitionPayload(snapshot); } } diff --git a/src/main/java/net/xevianlight/aphelion/network/packet/PartitionPayload.java b/src/main/java/net/xevianlight/aphelion/network/packet/PartitionPayload.java index 723612e..0062170 100644 --- a/src/main/java/net/xevianlight/aphelion/network/packet/PartitionPayload.java +++ b/src/main/java/net/xevianlight/aphelion/network/packet/PartitionPayload.java @@ -1,24 +1,39 @@ package net.xevianlight.aphelion.network.packet; import io.netty.buffer.ByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; import net.xevianlight.aphelion.Aphelion; +import net.xevianlight.aphelion.core.saveddata.types.PartitionData; -public record PartitionPayload(String id) implements CustomPacketPayload { - public static final Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data")); +import java.util.Objects; - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.STRING_UTF8, - PartitionPayload::id, +public record PartitionPayload(PartitionData partitionData) implements CustomPacketPayload { + public static final Type TYPE = + new Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data")); - PartitionPayload::new - ); + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + PartitionData.STREAM_CODEC, + PartitionPayload::partitionData, + PartitionPayload::new + ); @Override public Type type() { return TYPE; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + PartitionPayload that = (PartitionPayload) o; + return partitionData.equals(that.partitionData); + } + + @Override + public int hashCode() { + return Objects.hashCode(partitionData); + } } diff --git a/src/main/java/net/xevianlight/aphelion/planet/Planet.java b/src/main/java/net/xevianlight/aphelion/planet/Planet.java index 5901d93..810ef2b 100644 --- a/src/main/java/net/xevianlight/aphelion/planet/Planet.java +++ b/src/main/java/net/xevianlight/aphelion/planet/Planet.java @@ -10,12 +10,16 @@ import net.xevianlight.aphelion.util.registries.ModRegistries; public record Planet( ResourceKey dimension, double orbitDistance, - ResourceKey system + ResourceKey system, + boolean oxygen, + double gravity ) { public static final Codec 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) + 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), + Codec.BOOL.fieldOf("oxygen").forGetter(Planet::oxygen), + Codec.DOUBLE.fieldOf("gravity").forGetter(Planet::gravity) ).apply(inst, Planet::new)); } diff --git a/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java b/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java index c1ce7c1..b7d32c6 100644 --- a/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java +++ b/src/main/java/net/xevianlight/aphelion/planet/PlanetCache.java @@ -18,7 +18,9 @@ public final class PlanetCache { public static final Planet DEFAULT = new Planet( ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")), 1, - ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol")) + ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol")), + true, + 1 ); public static void registerPlanets(Map planets) { diff --git a/src/main/java/net/xevianlight/aphelion/util/FloodFill3D.java b/src/main/java/net/xevianlight/aphelion/util/FloodFill3D.java new file mode 100644 index 0000000..4bf9a77 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/util/FloodFill3D.java @@ -0,0 +1,114 @@ +package net.xevianlight.aphelion.util; + +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +public final class FloodFill3D { + + private static final Direction[] DIRECTIONS = Direction.values(); + + public static final SolidBlockPredicate TEST_FULL_SEAL = (level, pos, state, positions, queue, direction) -> { + if (state.isAir()) return true; + if (state.is(ModTags.Blocks.PASSES_FLOOD_FILL)) return true; + if (state.is(ModTags.Blocks.BLOCKS_FLOOD_FILL)) return false; + if (state.isCollisionShapeFullBlock(level, pos)) return false; + + VoxelShape collisionShape = state.getCollisionShape(level, pos); + + if (collisionShape.isEmpty()) return true; + if (!isSideSolid(collisionShape, direction)) return true; + if (!isFaceSturdy(collisionShape, direction) && !isFaceSturdy(collisionShape, direction.getOpposite())) { + return true; + } + + // Check the other directions to find a potential path for the partial block. + for (Direction dir : DIRECTIONS) { + if (dir.getAxis() == direction.getAxis()) continue; + var adjacentPos = pos.relative(dir); + var adjacentState = level.getBlockState(adjacentPos); + if (adjacentState.isAir()) return true; + } + + positions.add(pos.asLong()); + return false; + }; + + public static Set run(Level level, BlockPos start, int limit, SolidBlockPredicate predicate, boolean retainOrder) { + level.getProfiler().push("adastra-floodfill"); + + LongSet positions = retainOrder ? new LongLinkedOpenHashSet(limit) : new LongOpenHashSet(limit); + LongArrayFIFOQueue queue = new LongArrayFIFOQueue(limit); + queue.enqueue(start.asLong()); + + while (!queue.isEmpty() && positions.size() < limit) { + long packedPos = queue.dequeueLong(); + if (positions.contains(packedPos)) continue; + positions.add(packedPos); + + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(BlockPos.getX(packedPos), BlockPos.getY(packedPos), BlockPos.getZ(packedPos)); + for (Direction direction : DIRECTIONS) { + pos.set(packedPos); + pos.move(direction); + BlockState state = level.getBlockState(pos); + if (!predicate.test(level, pos, state, positions, queue, direction)) continue; + queue.enqueue(pos.asLong()); + } + } + + Set result = retainOrder ? new LinkedHashSet<>(positions.size()) : new HashSet<>(positions.size()); + for (long pos : positions) { + result.add(BlockPos.of(pos)); + } + + level.getProfiler().pop(); + return result; + } + + private static boolean isSideSolid(VoxelShape collisionShape, Direction dir) { + return switch (dir.getAxis()) { + case X -> isAxisCovered(collisionShape, Direction.Axis.Y, Direction.Axis.Z); + case Y -> isAxisCovered(collisionShape, Direction.Axis.X, Direction.Axis.Z); + case Z -> isAxisCovered(collisionShape, Direction.Axis.X, Direction.Axis.Y); + }; + } + + private static boolean isAxisCovered(VoxelShape shape, Direction.Axis axis1, Direction.Axis axis2) { + return shape.min(axis1) <= 0 && shape.max(axis1) >= 1 && shape.min(axis2) <= 0 && shape.max(axis2) >= 1; + } + + + private static boolean isFaceSturdy(VoxelShape collisionShape, Direction dir) { + VoxelShape faceShape = collisionShape.getFaceShape(dir); + if (faceShape.isEmpty()) return true; + var aabbs = faceShape.toAabbs(); + if (aabbs.isEmpty()) return true; + return checkBounds(aabbs.get(0), dir.getAxis()); + } + + private static boolean checkBounds(AABB bounds, Direction.Axis axis) { + return switch (axis) { + case X -> bounds.minY <= 0 && bounds.maxY >= 1 && bounds.minZ <= 0 && bounds.maxZ >= 1; + case Y -> bounds.minX <= 0 && bounds.maxX >= 1 && bounds.minZ <= 0 && bounds.maxZ >= 1; + case Z -> bounds.minX <= 0 && bounds.maxX >= 1 && bounds.minY <= 0 && bounds.maxY >= 1; + }; + } + + @FunctionalInterface + public interface SolidBlockPredicate { + + boolean test(Level level, BlockPos pos, BlockState state, LongSet positions, LongArrayFIFOQueue queue, Direction direction); + } +} \ No newline at end of file diff --git a/src/main/java/net/xevianlight/aphelion/util/ModTags.java b/src/main/java/net/xevianlight/aphelion/util/ModTags.java index a22954d..f82348c 100644 --- a/src/main/java/net/xevianlight/aphelion/util/ModTags.java +++ b/src/main/java/net/xevianlight/aphelion/util/ModTags.java @@ -18,6 +18,8 @@ public class ModTags { public static final TagKey STORAGE_BLOCKS_STEEL = commonTag("storage_blocks/steel"); public static final TagKey LAUNCH_PAD = createTag("launch_pad"); + public static final TagKey PASSES_FLOOD_FILL = createTag("passes_flood_fill"); + public static final TagKey BLOCKS_FLOOD_FILL = createTag("blocks_flood_fill"); private static TagKey commonTag(String name) { return BlockTags.create(ResourceLocation.fromNamespaceAndPath("c", name)); diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 0ba7281..28dce77 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -1,4 +1,4 @@ -# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods. +# This is an example neoforge.mods.toml file. It contains the partitionData relating to the loading mods. # There are several mandatory fields (#mandatory), and many more that are optional (#optional). # The overall format is standard TOML format, v0.5.0. # Note that there are a couple of TOML lists in this file. diff --git a/src/main/resources/data/aphelion/planet/overworld.json b/src/main/resources/data/aphelion/planet/overworld.json index 763f65d..7b594d2 100644 --- a/src/main/resources/data/aphelion/planet/overworld.json +++ b/src/main/resources/data/aphelion/planet/overworld.json @@ -1,5 +1,7 @@ { "dimension": "minecraft:overworld", "orbit_distance": 1, - "star_system": "aphelon:sol" + "star_system": "aphelon:sol", + "gravity": 1, + "oxygen": true } \ No newline at end of file