More partition and station backend

This commit is contained in:
XevianLight
2026-04-09 16:17:19 -06:00
parent 341ed8a17d
commit 302acaaa18
29 changed files with 1022 additions and 42 deletions

View File

@@ -32,6 +32,7 @@ public class StationFlightComputerBlock extends BasicHorizontalEntityBlock {
@Override
protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) {
super.onRemove(state, level, pos, newState, movedByPiston);
if (level.getBlockEntity(pos) instanceof StationFlightComputerBlockEntity computerBE) {
PartitionData data = computerBE.getData();
if (data != null) {
@@ -39,4 +40,15 @@ public class StationFlightComputerBlock extends BasicHorizontalEntityBlock {
}
}
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
if (level.getBlockEntity(pos) instanceof StationFlightComputerBlockEntity computerBE) {
PartitionData data = computerBE.getData();
if (data != null) {
data.setTraveling(true);
}
}
}
}

View File

@@ -28,8 +28,6 @@ public class StationFlightComputerBlockEntity extends BlockEntity implements Tic
@Override
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
if (data == null) return;
data.setTraveling(true);
}
public @Nullable PartitionData getData() {
@@ -42,7 +40,7 @@ public class StationFlightComputerBlockEntity extends BlockEntity implements Tic
if (level instanceof ServerLevel serverLevel) {
if (serverLevel.dimension() == ModDimensions.SPACE) {
data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos);
setTraveling(true);
}
}
isInitialized = true;

View File

@@ -54,8 +54,8 @@ public class StationRocketEngineBlockEntity extends StationEngineBlockEntity {
if (data.getDestination() != null && data.isTraveling()) {
if (!tank.isEmpty() && tank.getFluid().is(ModFluidTags.ROCKET_FUEL) && tank.getFluidAmount() >= FUEL_CONSUMPTION) { // has enough fuel?
if (data.travel(getTravelSpeed()))
tank.drain(FUEL_CONSUMPTION, IFluidHandler.FluidAction.EXECUTE);
data.travel(getTravelSpeed());
tank.drain(FUEL_CONSUMPTION, IFluidHandler.FluidAction.EXECUTE);
} else {
// not enough fuel
}

View File

@@ -54,4 +54,11 @@ public abstract class StationEngineBlockEntity extends BlockEntity implements Ti
}
isInitialized = true;
}
@Override
public void onRemoved() {
if (data != null)
data.removeEngine(worldPosition);
TickableBlockEntity.super.onRemoved();
}
}

View File

@@ -4,9 +4,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.dimension.DimensionType;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
@@ -14,8 +14,12 @@ import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects;
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
import net.xevianlight.aphelion.planet.Planet;
import net.xevianlight.aphelion.planet.PlanetCache;
import net.xevianlight.aphelion.util.SpacePartition;
import java.util.Arrays;
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
public class AphelionDebugOverlay {
@@ -46,19 +50,28 @@ public class AphelionDebugOverlay {
int x = SpacePartition.get(Math.floor(mc.player.position().x));
int z = SpacePartition.get(Math.floor(mc.player.position().z));
ResourceLocation orbit = PartitionClientState.lastData().getOrbit();
Planet planet = PlanetCache.getByOrbitOrDefault(orbit);
var dimension = planet.dimension();
// Left side of F3
event.getLeft().add("");
event.getLeft().add("Aphelion:");
event.getLeft().add(" Orbit: " + PartitionClientState.lastData().getOrbit());
event.getLeft().add(" Orbit: " + orbit);
event.getLeft().add(" Planet: " + PlanetCache.getByOrbitOrNull(orbit));
event.getLeft().add(" Associated Dimension: " + dimension.location().toString());
// event.getLeft().add(" Sky: " + rendererSummary);
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z));
event.getLeft().add(" Station Destination: " + PartitionClientState.lastData().getDestination());
event.getLeft().add(" Station Destination AU: " + PlanetCache.getOrDefault(PartitionClientState.lastData().getDestination()).orbitDistance());
event.getLeft().add(" Station Owner: " + PartitionClientState.lastData().getOwner());
event.getLeft().add(" Station Engines: " + PartitionClientState.lastData().getEngines().toArray().length);
event.getLeft().add(" Station Engines: " + Arrays.toString(PartitionClientState.lastData().getEngines().toArray()));
event.getLeft().add(" Station Landing Pads: " + PartitionClientState.lastData().getLandingPadContollersAsArray().length);
event.getLeft().add(" Station Traveling: " + PartitionClientState.lastData().isTraveling());
event.getLeft().add(" Station Orbital Distance AU: " + PartitionClientState.lastData().getOrbitDistance());
event.getLeft().add(" Station Trip Distance AU: " + PartitionClientState.lastData().getTripDistanceAU());
event.getLeft().add(" Station Distance Traveled AU: " + PartitionClientState.lastData().getDistanceTraveledAU());
event.getLeft().add(" Station Trip Traveled AU: " + PartitionClientState.lastData().getDistanceTraveledAU());
event.getLeft().add(" Station PosData: " + PartitionClientState.lastData().getPosData().toString());
var server = mc.getSingleplayerServer();
ServerLevel singlePlayerLevel;
if (server != null) {

View File

@@ -11,6 +11,7 @@ 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 net.xevianlight.aphelion.core.saveddata.types.PosData;
import net.xevianlight.aphelion.util.SpacePartition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -74,6 +75,14 @@ public class SpacePartitionSavedData extends SavedData {
pd.setLandingPadContollersFromArray(e.getLongArray("LandingPads"));
}
if (e.contains("PosData", CompoundTag.TAG_LONG)) {
pd.setPosData(PosData.unpacker(e.getLong("PosData")));
}
if (e.contains("OrbitDistance", CompoundTag.TAG_DOUBLE)) {
pd.setOrbitDistance(e.getDouble("OrbitDistance"));
}
data.map.put(key, pd);
}
@@ -115,6 +124,10 @@ public class SpacePartitionSavedData extends SavedData {
e.putLongArray("LandingPads", pd.getLandingPadContollersAsArray());
e.putLong("PosData", pd.getPosDataOrDefault().pack());
e.putDouble("OrbitDistance", pd.getOrbitDistance());
entries.add(e);
});
@@ -178,10 +191,11 @@ public class SpacePartitionSavedData extends SavedData {
if (data == null) {
// pick a sensible default orbit, or null if you truly allow it
data = new PartitionData(Aphelion.id("orbit/default"));
data = new PartitionData(Aphelion.id("orbit/unassigned"));
map.put(key, data);
setDirty();
}
data.setDirtyCallback(this::setDirty);
return data;
}
@@ -210,6 +224,7 @@ public class SpacePartitionSavedData extends SavedData {
map.put(key, data);
setDirty();
}
data.setDirtyCallback(this::setDirty);
return data;
}
@@ -237,6 +252,7 @@ public class SpacePartitionSavedData extends SavedData {
map.put(key, data);
setDirty();
}
data.setDirtyCallback(this::setDirty);
return data;
}

View File

@@ -2,8 +2,6 @@ package net.xevianlight.aphelion.core.saveddata.types;
public record EnvironmentData (boolean oxygen, short temperature, float gravity){
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

View File

@@ -6,6 +6,7 @@ import net.minecraft.core.UUIDUtil;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.xevianlight.aphelion.planet.Planet;
import net.xevianlight.aphelion.planet.PlanetCache;
import net.xevianlight.aphelion.util.BigCodec;
import org.jetbrains.annotations.Nullable;
@@ -18,24 +19,32 @@ public class PartitionData {
@Nullable private ResourceLocation orbit;
@Nullable private ResourceLocation destination;
@Nullable private ResourceLocation system;
private boolean traveling;
/// How far we've already gone
private double distanceTraveledAU;
/// Total trip distance, from start to finish
/// Total trip distance, from start to finish. Used with distanceTraveledAU to determine trip progress for UI or other. Not used in trip calculation.
private double tripDistanceAU;
private boolean generated;
private UUID owner;
private List<BlockPos> landingPadControllers;
private List<BlockPos> engines;
private double currentOrbitDistanceAU;
/// Data object containing station rotation.
private PosData posData;
private double orbitDistance;
/// Cache the planet that corresponds to our orbit so we don't have to constantly look it up from PlanetCache. Will be accurate as long as setOrbit() is used exclusively.
@Nullable private Planet cachedPlanet;
/// Cache the planet that corresponds to our destination so we don't have to constantly look it up from PlanetCache. Will be accurate as long as setDestination() is used exclusively.
@Nullable private Planet cachedDestination;
public PartitionData() {
}
public PartitionData(@Nullable ResourceLocation orbit) {
this.orbit = orbit;
this.destination = null;
setOrbit(orbit);
setDestination(null);
this.traveling = false;
this.distanceTraveledAU = 0;
this.tripDistanceAU = 0;
@@ -43,18 +52,25 @@ public class PartitionData {
this.owner = null;
this.landingPadControllers = List.of();
this.engines = new ArrayList<>(List.of());
this.posData = new PosData();
setOrbitDistance(1);
}
public PartitionData(PartitionData other) {
this.orbit = other.orbit;
this.cachedPlanet = other.cachedPlanet;
this.destination = other.destination;
this.cachedDestination = other.cachedDestination;
this.traveling = other.traveling;
this.distanceTraveledAU = other.distanceTraveledAU;
this.tripDistanceAU = other.tripDistanceAU;
this.tripDistanceAU = other.tripDistanceAU; // copy directly, no recalculation
this.generated = other.generated;
this.owner = other.owner;
this.landingPadControllers = other.landingPadControllers;
this.engines = other.engines;
this.engines = other.getEngines(); // defensive copy
this.landingPadControllers = other.getLandingPadControllers();
this.posData = other.posData;
this.orbitDistance = other.orbitDistance;
// don't set dirty callback — caller must do that
}
public static final StreamCodec<ByteBuf, PartitionData> STREAM_CODEC =
@@ -66,6 +82,9 @@ public class PartitionData {
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
d -> Optional.ofNullable(d.getDestination()),
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
d -> Optional.ofNullable(d.getSystem()),
ByteBufCodecs.BOOL,
PartitionData::isTraveling,
@@ -88,9 +107,16 @@ public class PartitionData {
BLOCKPOS_LIST_CODEC,
PartitionData::getEngines,
(orbitOpt, destOpt, traveling, distTraveled, distToDest, ownerOpt, generated, controllers, engines) -> {
ByteBufCodecs.VAR_LONG,
PartitionData::getPosDataPacked,
ByteBufCodecs.DOUBLE,
PartitionData::getOrbitDistance,
(orbitOpt, destOpt, systemOpt, traveling, distTraveled, distToDest, ownerOpt, generated, controllers, engines, posData, distance) -> {
PartitionData data = new PartitionData(orbitOpt.orElse(null));
data.destination = destOpt.orElse(null);
data.setDestination(destOpt.orElse(null));
data.setSystem(systemOpt.orElse(null));
data.traveling = traveling;
data.distanceTraveledAU = distTraveled;
data.tripDistanceAU = distToDest;
@@ -98,28 +124,51 @@ public class PartitionData {
data.generated = generated;
data.landingPadControllers = controllers;
data.engines = engines;
data.posData = PosData.unpacker(posData);
data.setOrbitDistance(distance);
return data;
}
);
private Long getPosDataPacked() {
if (posData == null) posData = new PosData();
return posData.pack();
}
public @Nullable ResourceLocation getOrbit() {
return this.orbit;
}
public void setOrbit(@Nullable ResourceLocation orbit) {
this.orbit = orbit;
cachedPlanet = PlanetCache.getByOrbitOrNull(orbit);
if (cachedPlanet != null && this.posData != null)
setOrbitDistance(cachedPlanet.orbitDistance());
recalculateTripDistAU();
distanceTraveledAU = 0;
markDirty();
}
public @Nullable ResourceLocation getDestination() {
cachedDestination = PlanetCache.getOrNull(destination);
return destination;
}
public void setDestination(@Nullable ResourceLocation destination) {
this.destination = destination;
cachedDestination = PlanetCache.getOrNull(destination);
recalculateTripDistAU();
distanceTraveledAU = 0;
markDirty();
}
public @Nullable ResourceLocation getSystem() {
return system;
}
public void setSystem(@Nullable ResourceLocation system) {
this.system = system;
markDirty();
}
public boolean isTraveling() {
@@ -127,7 +176,9 @@ public class PartitionData {
}
public void setTraveling(boolean traveling) {
if (this.traveling == traveling) return;
this.traveling = traveling;
markDirty();
}
public double getDistanceTraveledAU() {
@@ -136,11 +187,13 @@ public class PartitionData {
public void setDistanceTraveledAU(double distanceTraveledAU) {
this.distanceTraveledAU = distanceTraveledAU;
markDirty();
}
public double recalculateTripDistAU() {
var currentPlanet = PlanetCache.getByOrbitOrNull(orbit);
if (currentPlanet == null) {
markDirty();
return -1;
}
@@ -148,6 +201,7 @@ public class PartitionData {
var dist = destPlanet.orbitDistance() - currentPlanet.orbitDistance();
this.tripDistanceAU = dist;
markDirty();
return dist;
}
@@ -155,6 +209,20 @@ public class PartitionData {
return tripDistanceAU;
}
public double getTripDeltaAU() {
if (cachedDestination == null) return 0;
return cachedDestination.orbitDistance() - orbitDistance;
}
public double getOrbitDistance() {
return orbitDistance;
}
public void setOrbitDistance(double orbitDistance) {
this.orbitDistance = orbitDistance;
markDirty();
}
public void setTripDistanceAU(double tripDistanceAU) {
this.tripDistanceAU = tripDistanceAU;
}
@@ -162,26 +230,38 @@ public class PartitionData {
/**
* Advances travel progress by the specified distance in AU.
*
* <p>This increases {@code distanceTraveledAU} by the given amount and clamps
* the result so it never exceeds {@code tripDistanceAU}.</p>
*
* <p>If the requested distance would overshoot the destination, the traveled
* distance is set to exactly {@code tripDistanceAU}.</p>
* <p>Each call moves the station's current AU position toward the destination
* planet's AU value by the given amount. If the step would overshoot, the
* position is clamped to the destination exactly and arrival is triggered.</p>
*
* @param distance the distance to advance in astronomical units (AU)
* @return {@code true} when we arrive at our destination, {@code false} otherwise.
* @return {@code true} when the station has arrived at its destination,
* {@code false} if travel is still in progress.
*/
public boolean travel(double distance) {
if (distanceTraveledAU + distance > tripDistanceAU) {
if (cachedDestination == null) return false;
if (cachedPlanet == null) return false;
double delta = getTripDeltaAU();
double step = distance * Math.signum(delta);
if (Math.abs(delta) <= distance) {
this.orbitDistance = (cachedDestination.orbitDistance());
this.orbit = cachedDestination.orbit().location();
this.cachedPlanet = cachedDestination;
this.destination = null;
this.cachedDestination = null;
this.traveling = false;
distanceTraveledAU = tripDistanceAU;
var destinationPlanet = PlanetCache.getOrNull(destination);
if (destinationPlanet != null) {
setOrbit(destinationPlanet.orbit().location());
}
return false;
markDirty();
return true;
} else {
distanceTraveledAU += distance;
return true;
this.orbitDistance += step;
markDirty();
return false;
}
}
@@ -191,6 +271,7 @@ public class PartitionData {
public void setGenerated(boolean generated) {
this.generated = generated;
markDirty();
}
public @Nullable UUID getOwner() {
@@ -199,6 +280,7 @@ public class PartitionData {
public void setOwner(@Nullable UUID owner) {
this.owner = owner;
markDirty();
}
/**
@@ -222,6 +304,7 @@ public class PartitionData {
public void setLandingPadControllers(List<BlockPos> landingPadControllers) {
this.landingPadControllers = landingPadControllers;
markDirty();
}
@@ -254,7 +337,11 @@ public class PartitionData {
* @return {@code true} if a controller was removed, {@code false} otherwise
*/
public boolean removeLandingPadController(BlockPos pos) {
return landingPadControllers.remove(pos);
if (landingPadControllers.remove(pos)) {
markDirty();
return true;
}
return false;
}
/**
@@ -279,6 +366,7 @@ public class PartitionData {
public void setEngines(List<BlockPos> engines) {
this.engines = engines;
markDirty();
}
/**
@@ -294,13 +382,18 @@ public class PartitionData {
public boolean addEngine(BlockPos pos) {
if (!engines.contains(pos)) {
engines.add(pos);
markDirty();
return true;
}
return false;
}
public boolean removeEngine(BlockPos pos) {
return engines.remove(pos);
if (engines.remove(pos)) {
markDirty();
return true;
}
return false;
}
@Override
@@ -314,9 +407,14 @@ public class PartitionData {
&& Objects.equals(this.destination, that.destination)
&& this.traveling == that.traveling
&& Double.compare(this.distanceTraveledAU, that.distanceTraveledAU) == 0
&& Double.compare(this.tripDistanceAU, that.tripDistanceAU) == 0
&& Double.compare(this.orbitDistance, that.orbitDistance) == 0
&& this.generated == that.generated
&& Objects.equals(this.owner, that.owner);
&& Objects.equals(this.owner, that.owner)
&& Objects.equals(this.engines, that.engines)
&& Objects.equals(this.landingPadControllers, that.landingPadControllers);
/* tripDistanceAU intentionally excluded — it is a derived value computed from
* orbit and destination, and may fluctuate due to recalculation timing.
* It should never drive packet equality decisions. */
}
public long[] getLandingPadContollersAsArray() {
@@ -336,6 +434,31 @@ public class PartitionData {
newList.add(BlockPos.of(packedPos));
i++;
}
markDirty();
setLandingPadControllers(newList);
}
public PosData getPosData() {
return posData;
}
public PosData getPosDataOrDefault() {
if (posData == null) return new PosData();
return posData;
}
public void setPosData(PosData posData) {
markDirty();
this.posData = posData;
}
private Runnable onDirty;
public void setDirtyCallback(Runnable onDirty) {
this.onDirty = onDirty;
}
private void markDirty() {
if (onDirty != null) onDirty.run();
}
}

View File

@@ -0,0 +1,218 @@
package net.xevianlight.aphelion.core.saveddata.types;
import org.joml.Vector3f;
/**
* Stores the positional and rotational data for a space station.
*
* <p>Rotation is stored as three 16-bit fixed-point values (pitch, yaw, roll),
* where {@code 0} maps to {@code 0°} and {@code 65536} maps to {@code 360°}.
* This representation allows angle arithmetic to wrap correctly via natural
* short overflow, with no explicit modulo required.
*
* <p>All four fields (pitch, yaw, roll, distance) can be packed into a single
* {@code long} for efficient NBT storage.
*
* @see #pack()
* @see #packer(PosData)
* @see #unpacker(long)
*/
public class PosData {
public static final float AU_SCALE = 1.0f / 512.0f;
/// Fixed-point pitch rotation. {@code 0} = 0°, {@code 32768} = 180°. <p>{@code 0.005493°} precision.
public short pitch;
/// Fixed-point yaw rotation. {@code 0} = 0°, {@code 32768} = 180°. <p>{@code 0.005493°} precision.
public short yaw;
/// Fixed-point roll rotation. {@code 0} = 0°, {@code 32768} = 180°. <p>{@code 0.005493°} precision.
public short roll;
/**
* Orbital distance, stored as an unsigned 16-bit value. <p> {@code 0.00195 AU} precision.
* <p>Defaults to {@code 1 AU}.
*/
public PosData() {
pitch = 0;
yaw = 0;
roll = 0;
}
@Deprecated
public PosData(short pitch, short yaw, short roll) {
this.pitch = fromDegrees(pitch);
this.yaw = yaw;
this.roll = roll;
}
public PosData(PosData other) {
this.pitch = other.pitch;
this.yaw = other.yaw;
this.roll = other.roll;
}
/**
* Packs this instance into a single {@code long} for NBT storage.
*
* @return the packed {@code long} representation
* @see #packer(PosData)
*/
public long pack() {
return packer(this);
}
/**
* Packs a {@code PosData} into a single {@code long}.
*
* <p>Layout (low to high bits):
* <pre>
* [0..15] pitch
* [16..31] yaw
* [32..47] roll
* [48..63] distance
* </pre>
*
* @param data the {@code PosData} to pack
* @return the packed {@code long}
*/
public static long packer(PosData data) {
return ((long) data.pitch & 0xFFFFL )
| (((long) data.yaw & 0xFFFFL) << 16)
| (((long) data.roll & 0xFFFFL) << 32);
}
/**
* Unpacks a {@code long} into a new {@code PosData} instance.
*
* @param packed the {@code long} to unpack, as produced by {@link #packer(PosData)}
* @return a new {@code PosData} with fields restored from the packed value
*/
public static PosData unpacker(long packed) {
PosData data = new PosData();
data.pitch = (short) (packed & 0xFFFFL);
data.yaw = (short) ((packed >> 16) & 0xFFFFL);
data.roll = (short) ((packed >> 32) & 0xFFFFL);
return data;
}
/**
* Converts a degree value to the fixed-point short representation.
*
* <p>{@code 0°} maps to {@code 0}, {@code 360°} maps to {@code 65536}
* (which overflows to {@code 0}, preserving wrap-around correctness).
*
* @param degrees the angle in degrees
* @return the fixed-point short representation
*/
public static short fromDegrees(float degrees) {
return (short) Math.round((degrees / 360.0f) * 65536.0f);
}
public static float pitchDegrees(PosData data) { return toDegrees(data.pitch); }
public static float yawDegrees(PosData data) { return toDegrees(data.yaw); }
public static float rollDegrees(PosData data) { return toDegrees(data.roll); }
public float pitchDegrees() { return toDegrees(pitch); }
public float yawDegrees() { return toDegrees(yaw); }
public float rollDegrees() { return toDegrees(roll); }
/**
* Converts a fixed-point short to degrees.
*
* @param s the fixed-point value
* @return the angle in degrees, in the range {@code [0°, 360°)}
*/
private static float toDegrees(short s) {
return ((s & 0xFFFF) / 65536.0f) * 360.0f;
}
/**
* Converts a fixed-point short to radians.
*
* @param s the fixed-point value
* @return the angle in radians, in the range {@code [0, 2π)}
*/
private static float toRadians(short s) {
return ((s & 0xFFFF) / 65536.0f) * (float) (2 * Math.PI);
}
// -------------------------------------------------------------------------
// Euler angles (for rendering)
// -------------------------------------------------------------------------
/**
* Returns the rotation as a {@link Vector3f} of Euler angles in degrees
* ({@code x} = pitch, {@code y} = yaw, {@code z} = roll).
*
* @return Euler angles in degrees
*/
public Vector3f eulerAnglesDeg() {
return new Vector3f(pitchDegrees(), yawDegrees(), rollDegrees());
}
/**
* Returns the rotation of the given {@code PosData} as a {@link Vector3f}
* of Euler angles in degrees ({@code x} = pitch, {@code y} = yaw, {@code z} = roll).
*
* @param data the instance to read from
* @return Euler angles in degrees
*/
public static Vector3f eulerAnglesDeg(PosData data) {
return new Vector3f(pitchDegrees(data), yawDegrees(data), rollDegrees(data));
}
/**
* Returns the rotation as a {@link Vector3f} of Euler angles in radians
* ({@code x} = pitch, {@code y} = yaw, {@code z} = roll).
* Suitable for direct use with JOML rotation methods.
*
* @return Euler angles in radians
*/
public Vector3f eulerAnglesRad() {
return new Vector3f(toRadians(pitch), toRadians(yaw), toRadians(roll));
}
/**
* Returns the rotation of the given {@code PosData} as a {@link Vector3f}
* of Euler angles in radians ({@code x} = pitch, {@code y} = yaw, {@code z} = roll).
* Suitable for direct use with JOML rotation methods.
*
* @param data the instance to read from
* @return Euler angles in radians
*/
public static Vector3f eulerAnglesRad(PosData data) {
return new Vector3f(toRadians(data.pitch), toRadians(data.yaw), toRadians(data.roll));
}
// -------------------------------------------------------------------------
// Setters from degrees (convenience)
// -------------------------------------------------------------------------
public void setPitchDegrees(float degrees) { this.pitch = fromDegrees(degrees); }
public void setYawDegrees(float degrees) { this.yaw = fromDegrees(degrees); }
public void setRollDegrees(float degrees) { this.roll = fromDegrees(degrees); }
public void addPitchDegrees(float degrees) { this.pitch += fromDegrees(degrees); }
public void addYawDegrees(float degrees) { this.yaw += fromDegrees(degrees); }
public void addRollDegrees(float degrees) { this.roll += fromDegrees(degrees); }
// -------------------------------------------------------------------------
// Utility
// -------------------------------------------------------------------------
/**
* Returns a human-readable representation of this {@code PosData},
* with rotations in degrees and distance as an unsigned integer.
*
* @return a formatted string representation
*/
@Override
public String toString() {
return "PosData{pitch=%.2f°, yaw=%.2f°, roll=%.2f°}"
.formatted(pitchDegrees(), yawDegrees(), rollDegrees());
}
}

View File

@@ -34,8 +34,15 @@ public final class PartitionSync {
// If it is different, send them the new one
if (prev == null || !prev.equals(now)) {
Aphelion.LOGGER.debug("Partition changed for {}: prev={} now={}", sp.getName().getString(),
prev == null ? "null" : String.format("orbit=%s dest=%s traveling=%s distTraveled=%s tripDist=%s orbitDist=%s",
prev.partitionData().getOrbit(), prev.partitionData().getDestination(), prev.partitionData().isTraveling(),
prev.partitionData().getDistanceTraveledAU(), prev.partitionData().getTripDistanceAU(), prev.partitionData().getOrbitDistance()),
String.format("orbit=%s dest=%s traveling=%s distTraveled=%s tripDist=%s orbitDist=%s",
now.partitionData().getOrbit(), now.partitionData().getDestination(), now.partitionData().isTraveling(),
now.partitionData().getDistanceTraveledAU(), now.partitionData().getTripDistanceAU(), now.partitionData().getOrbitDistance())
);
PacketDistributor.sendToPlayer(sp, now);
// Store this packet for later
LAST_SENT.put(sp.getUUID(), now);
}
}

View File

@@ -7,13 +7,16 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import net.xevianlight.aphelion.util.registries.ModRegistries;
import java.util.Optional;
public record Planet (
ResourceKey<Level> dimension,
ResourceKey<Orbit> orbit,
double orbitDistance,
ResourceKey<StarSystem> system,
boolean oxygen,
float gravity
float gravity,
Optional<ResourceKey<Planet>> parentPlanet /// nullable moon parent
) {
public static final Codec<Planet> CODEC = RecordCodecBuilder.create(inst -> inst.group(
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension),
@@ -21,7 +24,8 @@ public record Planet (
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.FLOAT.fieldOf("gravity").forGetter(Planet::gravity)
Codec.FLOAT.fieldOf("gravity").forGetter(Planet::gravity),
ResourceKey.codec(ModRegistries.PLANET).optionalFieldOf("parent_planet").forGetter(Planet::parentPlanet)
).apply(inst, Planet::new));
}

View File

@@ -6,15 +6,19 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.util.registries.ModRegistries;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public final class PlanetCache {
public static final Map<ResourceLocation, Planet> PLANETS = new HashMap<>();
public static final Map<ResourceKey<Level>, ResourceLocation> PLANET_BY_DIMENSION = new HashMap<>();
public static final Map<ResourceLocation, Planet> PLANET_BY_ORBIT = new HashMap<>();
public static final Planet DEFAULT = new Planet(
ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")),
@@ -22,7 +26,8 @@ public final class PlanetCache {
1,
ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol")),
true,
1
1,
Optional.empty()
);
public static void registerPlanets(Map<ResourceLocation, Planet> planets) {
@@ -34,6 +39,7 @@ public final class PlanetCache {
planets.forEach((planetId, planet) -> {
var dim = planet.dimension();
var prev = PLANET_BY_DIMENSION.put(dim, planetId);
PLANET_BY_ORBIT.put(planet.orbit().location(), planet);
if (prev != null) {
Aphelion.LOGGER.warn(
"Dimension {} is claimed by multiple planets: {} and {}. Keeping latest: {}",
@@ -61,8 +67,27 @@ public final class PlanetCache {
.orElse(null);
}
public static @NotNull Planet getByOrbitOrDefault(ResourceLocation id) {
return PLANETS.values().stream()
.filter(planet -> planet.orbit().location().equals(id))
.findFirst()
.orElse(DEFAULT);
}
public static Planet getByDimensionOrNull(ResourceKey<Level> dimension) {
ResourceLocation planetId = PLANET_BY_DIMENSION.get(dimension);
return planetId == null ? null : PLANETS.get(planetId);
}
public static @NotNull List<Planet> getSatellites(ResourceLocation id) {
return PLANETS.values().stream()
.filter(planet -> planet.parentPlanet()
.map(key -> key.location().equals(id))
.orElse(false))
.toList();
}
public static @NotNull List<Planet> getSatellites(ResourceKey<Planet> key) {
return getSatellites(key.location());
}
}

View File

@@ -0,0 +1,8 @@
{
"dimension": "minecraft:overworld",
"orbit": "aphelion:orbit/earth",
"orbit_distance": 1,
"star_system": "aphelion:sol",
"gravity": 1,
"oxygen": false
}