mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 01:50:56 +01:00
More partition and station backend
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
8
src/main/resources/data/aphelion/planet/earth.json
Normal file
8
src/main/resources/data/aphelion/planet/earth.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "minecraft:overworld",
|
||||
"orbit": "aphelion:orbit/earth",
|
||||
"orbit_distance": 1,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 1,
|
||||
"oxygen": false
|
||||
}
|
||||
Reference in New Issue
Block a user