mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-10 17:40:56 +01:00
Environment Data saving and Floodfill. Added additional data to PartitionPayload and PartitionData for later
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,4 +23,4 @@ run
|
||||
runs
|
||||
run-data
|
||||
|
||||
repo
|
||||
repo
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "aphelion:block/arc_furnace_casing"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "aphelion:block/vacuum_arc_furnace_controller"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"values": [
|
||||
"aphelion:launch_pad"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<PartitionData> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ByteBuf, PartitionData> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<ResourceLocation> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PartitionPayload> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data"));
|
||||
import java.util.Objects;
|
||||
|
||||
public static final StreamCodec<ByteBuf, PartitionPayload> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.STRING_UTF8,
|
||||
PartitionPayload::id,
|
||||
public record PartitionPayload(PartitionData partitionData) implements CustomPacketPayload {
|
||||
public static final Type<PartitionPayload> TYPE =
|
||||
new Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data"));
|
||||
|
||||
PartitionPayload::new
|
||||
);
|
||||
public static final StreamCodec<ByteBuf, PartitionPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
PartitionData.STREAM_CODEC,
|
||||
PartitionPayload::partitionData,
|
||||
PartitionPayload::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import net.xevianlight.aphelion.util.registries.ModRegistries;
|
||||
public record Planet(
|
||||
ResourceKey<Level> dimension,
|
||||
double orbitDistance,
|
||||
ResourceKey<StarSystem> system
|
||||
ResourceKey<StarSystem> system,
|
||||
boolean oxygen,
|
||||
double gravity
|
||||
) {
|
||||
public static final Codec<Planet> CODEC = RecordCodecBuilder.create(inst -> inst.group(
|
||||
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension),
|
||||
Codec.DOUBLE.fieldOf("orbit_distance").forGetter(Planet::orbitDistance),
|
||||
ResourceKey.codec(ModRegistries.STAR_SYSTEM).fieldOf("star_system").forGetter(Planet::system)
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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<ResourceLocation, Planet> planets) {
|
||||
|
||||
114
src/main/java/net/xevianlight/aphelion/util/FloodFill3D.java
Normal file
114
src/main/java/net/xevianlight/aphelion/util/FloodFill3D.java
Normal file
@@ -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<BlockPos> 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<BlockPos> 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);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ public class ModTags {
|
||||
public static final TagKey<Block> STORAGE_BLOCKS_STEEL = commonTag("storage_blocks/steel");
|
||||
|
||||
public static final TagKey<Block> LAUNCH_PAD = createTag("launch_pad");
|
||||
public static final TagKey<Block> PASSES_FLOOD_FILL = createTag("passes_flood_fill");
|
||||
public static final TagKey<Block> BLOCKS_FLOOD_FILL = createTag("blocks_flood_fill");
|
||||
|
||||
private static TagKey<Block> commonTag(String name) {
|
||||
return BlockTags.create(ResourceLocation.fromNamespaceAndPath("c", name));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"dimension": "minecraft:overworld",
|
||||
"orbit_distance": 1,
|
||||
"star_system": "aphelon:sol"
|
||||
"star_system": "aphelon:sol",
|
||||
"gravity": 1,
|
||||
"oxygen": true
|
||||
}
|
||||
Reference in New Issue
Block a user