diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/EnvironmentSavedData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/EnvironmentSavedData.java index 8535f71..c4ecfd1 100644 --- a/src/main/java/net/xevianlight/aphelion/core/saveddata/EnvironmentSavedData.java +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/EnvironmentSavedData.java @@ -31,7 +31,7 @@ import java.util.Map; */ public class EnvironmentSavedData extends SavedData { - private final Long2IntOpenHashMap envData = new Long2IntOpenHashMap(); + private static final Long2IntOpenHashMap envData = new Long2IntOpenHashMap(); private static final String NAME = "aphelion_environment"; diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java new file mode 100644 index 0000000..26faf1c --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java @@ -0,0 +1,166 @@ +package net.xevianlight.aphelion.core.saveddata; + +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraft.client.Minecraft; +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.Level; +import net.minecraft.world.level.saveddata.SavedData; +import net.xevianlight.aphelion.client.ClientOxygenCache; +import net.xevianlight.aphelion.core.saveddata.types.EnvironmentData; +import net.xevianlight.aphelion.core.saveddata.types.GravityData; +import net.xevianlight.aphelion.planet.Planet; +import net.xevianlight.aphelion.planet.PlanetCache; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * 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 GravitySavedData extends SavedData { + + private final Long2IntOpenHashMap gravityData = new Long2IntOpenHashMap(); + + private static final String NAME = "aphelion_gravity"; + + public static GravitySavedData create() { + return new GravitySavedData(); + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag tag, @NotNull HolderLookup.Provider provider) { + int size = gravityData.size(); + long[] positions = new long[size]; + int[] data = new int[size]; + + int i = 0; + for (var e : gravityData.long2IntEntrySet()) { + positions[i] = e.getLongKey(); + data[i] = e.getIntValue(); + i++; + } + + tag.putLongArray("Position", positions); + tag.putIntArray("Value", data); + + return tag; + } + + public static GravitySavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) { + GravitySavedData data = create(); + + if (!tag.contains("Position", Tag.TAG_LONG_ARRAY) || !tag.contains("Value", Tag.TAG_INT_ARRAY)) { return data; } + + long[] positions = tag.getLongArray("Position"); + int[] values = tag.getIntArray("Value"); + + int length = Math.min(positions.length, values.length); + + data.gravityData.ensureCapacity(length); + + for (int i = 0; i < length; i++) { + data.gravityData.put(positions[i], values[i]); + } + + return data; + } + + private static final int ABSENT = Integer.MIN_VALUE; + + public @Nullable GravityData getGravityRegionData (Level level, BlockPos center) { + GravityData data = GravityData.unpack(gravityData.getOrDefault(center.asLong(), ABSENT)); + return data.pack() == ABSENT ? null : data; + } + + public void removeGravityRegion (BlockPos pos) { + gravityData.remove(pos.asLong()); + } + + public void setGravityRegion (BlockPos pos, GravityData data) { + gravityData.put(pos.asLong(), data.pack()); + } + + + + /** + * Returns the cumulative sum of the acceleration for all gravity regions that overlap this block position + */ + public float getGravitySum (BlockPos pos) { + float sum = 0; + + List regions = getGravityRegions(pos); + + for (var e : regions) { + sum += e.getAccel(); + } + + return sum; + } + + /** + * Returns the strongest acceleration among all gravity regions that overlap this block position + */ + public float getGravityMax (BlockPos pos) { + float max = 0; + + List regions = getGravityRegions(pos); + + for (var e : regions) { + var accel = e.getAccel(); + if (accel > max) max = accel; + } + + return max; + } + + /** + * Returns a list of all gravity data objects overlapping this position. NOTE: does not contain position of the regions + */ + public List getGravityRegions (BlockPos pos) { + List regions = new ArrayList<>(); + for (var entry : gravityData.long2IntEntrySet()) { + GravityData data = GravityData.unpack(entry.getIntValue()); + BlockPos center = BlockPos.of(entry.getLongKey()); + + if (contains(pos, center, data.getRadius())) { + regions.add(data); + } + } + + return regions; + } + + + private static boolean contains(BlockPos pos, BlockPos center, float radius) { + float distanceSquared = ((center.getX() - pos.getX()) * (center.getX() - pos.getX())) + ((center.getY() - pos.getY()) * (center.getY() - pos.getY())) + ((center.getZ() - pos.getZ()) * (center.getZ() - pos.getZ())); + return distanceSquared <= radius * radius; + } + + private static float defaultGravity (Level level) { + Planet planet = PlanetCache.getByDimensionOrNull(level.dimension()); + if (planet == null) return GravityData.DEFAULT_GRAVITY; + return planet.gravity(); + } + + public static GravitySavedData get(ServerLevel level) { + return level.getDataStorage().computeIfAbsent( + new Factory<>(GravitySavedData::create, GravitySavedData::load), + NAME + ); + } + +} \ No newline at end of file diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/GravityData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/GravityData.java new file mode 100644 index 0000000..de2087a --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/GravityData.java @@ -0,0 +1,49 @@ +package net.xevianlight.aphelion.core.saveddata.types; + +public class GravityData { + private float accel; + private float radius; + + public static final float DEFAULT_GRAVITY = 9.80665f; // 1G + public static final float GRAVITY_PRECISION = 100.0f; + public static final float RADIUS_PRECISION = 100.0f; + public static final float MAX_RADIUS = Short.MAX_VALUE / RADIUS_PRECISION; + public static final float MAX_GRAVITY = Short.MAX_VALUE / GRAVITY_PRECISION; + + public GravityData(float accel, float radius) { + this.accel = accel; + this.radius = radius; + } + + public int pack() { + int packed = 0; + + packed |= (int) (this.accel * GRAVITY_PRECISION); + packed |= (int) (this.radius * RADIUS_PRECISION) << 16; + + return packed; + } + + public float getAccel() { + return accel; + } + + public void setAccel(float accel) { + this.accel = accel; + } + + public float getRadius() { + return radius; + } + + public void setRadius(short radius) { + this.radius = radius; + } + + public static GravityData unpack(int packed) { + float accel = (packed & 0xFFFF) / GRAVITY_PRECISION; + float radius = (packed >> 16) / RADIUS_PRECISION; + + return new GravityData(accel, radius); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/planet/Planet.java b/src/main/java/net/xevianlight/aphelion/planet/Planet.java index 810ef2b..3e84bd1 100644 --- a/src/main/java/net/xevianlight/aphelion/planet/Planet.java +++ b/src/main/java/net/xevianlight/aphelion/planet/Planet.java @@ -12,14 +12,14 @@ public record Planet( double orbitDistance, ResourceKey system, boolean oxygen, - double gravity + float 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), Codec.BOOL.fieldOf("oxygen").forGetter(Planet::oxygen), - Codec.DOUBLE.fieldOf("gravity").forGetter(Planet::gravity) + Codec.FLOAT.fieldOf("gravity").forGetter(Planet::gravity) ).apply(inst, Planet::new)); } diff --git a/src/main/java/net/xevianlight/aphelion/systems/OxygenService.java b/src/main/java/net/xevianlight/aphelion/systems/OxygenService.java index 9a4611b..63864d5 100644 --- a/src/main/java/net/xevianlight/aphelion/systems/OxygenService.java +++ b/src/main/java/net/xevianlight/aphelion/systems/OxygenService.java @@ -23,6 +23,7 @@ public class OxygenService { public static boolean hasOxygen(Entity entity) { // Not sure if this is at the entity's feet, head, or the middle... research later + // Blockpos is from entity's feet ~Xev BlockPos entityBlockPos = BlockPos.containing(entity.getX(), entity.getY(), entity.getZ()); return hasOxygen(entity.level(), entityBlockPos); }