From fa41966de4bbf6d3c4bbb597c0515e2c5391e0f0 Mon Sep 17 00:00:00 2001 From: TechnoDraconic Date: Thu, 5 Feb 2026 00:44:15 -0800 Subject: [PATCH] added gravity --- .../core/saveddata/GravitySavedData.java | 6 +- .../core/saveddata/types/GravityData.java | 3 +- .../aphelion/event/ModBusEvents.java | 8 +++ .../mixins/common/LivingEntityMixin.java | 7 ++ .../aphelion/network/ClientPlayerState.java | 42 ++++++++++++ .../ClientPlayerStateUpdateHandler.java | 11 ++++ .../aphelion/network/KeyNetwork.java | 17 +++++ .../packet/ClientPlayerStateUpdatePacket.java | 29 +++++++++ .../aphelion/systems/GravityService.java | 65 +++++++++++++++++++ 9 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/xevianlight/aphelion/network/ClientPlayerState.java create mode 100644 src/main/java/net/xevianlight/aphelion/network/ClientPlayerStateUpdateHandler.java create mode 100644 src/main/java/net/xevianlight/aphelion/network/packet/ClientPlayerStateUpdatePacket.java create mode 100644 src/main/java/net/xevianlight/aphelion/systems/GravityService.java diff --git a/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java b/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java index 26faf1c..435cb33 100644 --- a/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/GravitySavedData.java @@ -115,7 +115,7 @@ public class GravitySavedData extends SavedData { * Returns the strongest acceleration among all gravity regions that overlap this block position */ public float getGravityMax (BlockPos pos) { - float max = 0; + float max = -1; List regions = getGravityRegions(pos); @@ -124,6 +124,10 @@ public class GravitySavedData extends SavedData { if (accel > max) max = accel; } + if (max == -1) { + max = GravityData.DEFAULT_GRAVITY; + } + return max; } 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 index de2087a..9be3e5f 100644 --- a/src/main/java/net/xevianlight/aphelion/core/saveddata/types/GravityData.java +++ b/src/main/java/net/xevianlight/aphelion/core/saveddata/types/GravityData.java @@ -4,7 +4,8 @@ public class GravityData { private float accel; private float radius; - public static final float DEFAULT_GRAVITY = 9.80665f; // 1G + public static final float ONE_G = 9.80665f; + public static final float DEFAULT_GRAVITY = ONE_G; // 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; diff --git a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java index e0d3e92..938ea7f 100644 --- a/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java +++ b/src/main/java/net/xevianlight/aphelion/event/ModBusEvents.java @@ -13,8 +13,10 @@ import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity; import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity; import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity; import net.xevianlight.aphelion.core.init.ModBlockEntities; +import net.xevianlight.aphelion.network.ClientPlayerStateUpdateHandler; import net.xevianlight.aphelion.network.RocketPayloadHandlers; import net.xevianlight.aphelion.network.PartitionPayloadHandler; +import net.xevianlight.aphelion.network.packet.ClientPlayerStateUpdatePacket; import net.xevianlight.aphelion.network.packet.PartitionPayload; import net.xevianlight.aphelion.network.packet.RocketLaunchPayload; @@ -49,5 +51,11 @@ public class ModBusEvents { RocketPayloadHandlers::handleRocketLaunch ); + registrar.playToClient( + ClientPlayerStateUpdatePacket.TYPE, + ClientPlayerStateUpdatePacket.STREAM_CODEC, + ClientPlayerStateUpdateHandler::handleDataOnMain + ); + } } diff --git a/src/main/java/net/xevianlight/aphelion/mixins/common/LivingEntityMixin.java b/src/main/java/net/xevianlight/aphelion/mixins/common/LivingEntityMixin.java index 3d2cd63..e55737a 100644 --- a/src/main/java/net/xevianlight/aphelion/mixins/common/LivingEntityMixin.java +++ b/src/main/java/net/xevianlight/aphelion/mixins/common/LivingEntityMixin.java @@ -5,6 +5,8 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import net.xevianlight.aphelion.systems.GravityService; import net.xevianlight.aphelion.systems.OxygenService; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -26,4 +28,9 @@ public abstract class LivingEntityMixin extends Entity { OxygenService.entityTick(level, entity); } } + + @Inject(method = "travel", at = @At("HEAD")) + public void aphelion$travel(Vec3 travelVector, CallbackInfo ci) { + if (this.isControlledByLocalInstance()) GravityService.onEntityTravel(level(), (LivingEntity) (Object) this); + } } diff --git a/src/main/java/net/xevianlight/aphelion/network/ClientPlayerState.java b/src/main/java/net/xevianlight/aphelion/network/ClientPlayerState.java new file mode 100644 index 0000000..add4820 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/ClientPlayerState.java @@ -0,0 +1,42 @@ +package net.xevianlight.aphelion.network; + +import net.minecraft.world.entity.player.Player; +import net.xevianlight.aphelion.core.saveddata.types.GravityData; +import net.xevianlight.aphelion.systems.GravityService; +import net.xevianlight.aphelion.systems.OxygenService; + +/// Read-only player state object; updated by a server packet every so often +public record ClientPlayerState(boolean oxygen, float gravity, float temperature) { + // Default player state + private static ClientPlayerState localData = new ClientPlayerState(true, GravityData.DEFAULT_GRAVITY * 0.5f, 50f); + + public static void updateState(ClientPlayerState newData) { + onStateUpdate(localData, newData); + localData = newData; + } + + public static ClientPlayerState getServerStateOf(Player player) { + return new ClientPlayerState(OxygenService.hasOxygen(player), GravityService.getGravityAccel(player), 50f); + } + + /// For things like playing SFX, VFX, etc. etc. + public static void onStateUpdate(ClientPlayerState oldData, ClientPlayerState newData) { + // TODO: add sfx + if (!oldData.oxygen() && newData.oxygen()) { + // On oxygen gained + } + if (oldData.oxygen() && !newData.oxygen()) { + // On oxygen removed + } + if (newData.gravity() - 0.25f > oldData.gravity()) { + // On gravity increased by > 0.25 + } + if (oldData.gravity() - 0.25f > newData.gravity()) { + // On gravity decreased by > 0.25 + } + } + + public static ClientPlayerState getLocalData() { + return localData; + } +} diff --git a/src/main/java/net/xevianlight/aphelion/network/ClientPlayerStateUpdateHandler.java b/src/main/java/net/xevianlight/aphelion/network/ClientPlayerStateUpdateHandler.java new file mode 100644 index 0000000..d47a718 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/ClientPlayerStateUpdateHandler.java @@ -0,0 +1,11 @@ +package net.xevianlight.aphelion.network; + +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.xevianlight.aphelion.network.packet.ClientPlayerStateUpdatePacket; + +public class ClientPlayerStateUpdateHandler { + + public static void handleDataOnMain(ClientPlayerStateUpdatePacket packet, IPayloadContext context) { + context.enqueueWork(() -> ClientPlayerState.updateState(new ClientPlayerState(packet.oxygen(), packet.gravity(), packet.temp()))); + } +} diff --git a/src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java b/src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java index 850de24..1849fa8 100644 --- a/src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java +++ b/src/main/java/net/xevianlight/aphelion/network/KeyNetwork.java @@ -2,12 +2,16 @@ package net.xevianlight.aphelion.network; import net.minecraft.client.Minecraft; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.network.PacketDistributor; import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.entites.vehicles.RocketEntity; +import net.xevianlight.aphelion.network.packet.ClientPlayerStateUpdatePacket; import net.xevianlight.aphelion.network.packet.RocketLaunchPayload; import net.xevianlight.aphelion.client.AphelionClient; @@ -27,4 +31,17 @@ public final class KeyNetwork { PacketDistributor.sendToServer(new RocketLaunchPayload(rocket.getId())); } } + + @SubscribeEvent + public static void onServerTick(ServerTickEvent.Post event) { + int FREQ = 4; + for (ServerPlayer p : event.getServer().getPlayerList().getPlayers()) { + if (p.tickCount % FREQ == 0) { + ClientPlayerState state = ClientPlayerState.getServerStateOf(p); + + PacketDistributor.sendToPlayer(p, new ClientPlayerStateUpdatePacket(state.oxygen(), state.gravity(), state.temperature())); + } + } + + } } diff --git a/src/main/java/net/xevianlight/aphelion/network/packet/ClientPlayerStateUpdatePacket.java b/src/main/java/net/xevianlight/aphelion/network/packet/ClientPlayerStateUpdatePacket.java new file mode 100644 index 0000000..691fe87 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/network/packet/ClientPlayerStateUpdatePacket.java @@ -0,0 +1,29 @@ +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; + +public record ClientPlayerStateUpdatePacket(boolean oxygen, float gravity, float temp) implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "player_state_update")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, + ClientPlayerStateUpdatePacket::oxygen, + ByteBufCodecs.FLOAT, + ClientPlayerStateUpdatePacket::gravity, + ByteBufCodecs.FLOAT, + ClientPlayerStateUpdatePacket::temp, + ClientPlayerStateUpdatePacket::new + ); + + @Override + public CustomPacketPayload.Type type() { + return TYPE; + } + + +} diff --git a/src/main/java/net/xevianlight/aphelion/systems/GravityService.java b/src/main/java/net/xevianlight/aphelion/systems/GravityService.java new file mode 100644 index 0000000..89fcae1 --- /dev/null +++ b/src/main/java/net/xevianlight/aphelion/systems/GravityService.java @@ -0,0 +1,65 @@ +package net.xevianlight.aphelion.systems; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import net.xevianlight.aphelion.network.ClientPlayerState; +import net.xevianlight.aphelion.core.saveddata.GravitySavedData; +import net.xevianlight.aphelion.core.saveddata.types.GravityData; + +public class GravityService { + + /// If i did this right, there SHOULDN'T be a way to break client/server separation with this... + /// you shouldn't be able to get the "ServerLevel" object from the Client side unless you're already + /// breaking that rule, in which case, go for it lmfao + + public static float getGravityAccel(Level level, BlockPos pos) { + if (level.isClientSide) { + // Pull from the client data b/c we can't access the server's saved data + return ClientPlayerState.getLocalData().gravity(); + } + // TODO: maybe change this based on how stuff pans out + var gravity = GravitySavedData.get((ServerLevel) level).getGravityMax(pos); + + return gravity; + } + + public static float getGravityAccel(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 getGravityAccel(entity.level(), entityBlockPos); + } + + /// Called by LivingEntity$travel mixin + public static void onEntityTravel(Level level, LivingEntity entity) { + if ( + entity.isFallFlying() || entity.isInLiquid() || + entity.isUnderWater() || + entity.hasEffect(MobEffects.SLOW_FALLING) + ) return; + + float gravityAccelReal = getGravityAccel(entity); + + // How many times normal gravity you're experiencing. + // "normal gravity" varies across different entities. Thankfully, minecraft slaps a "protected" status + // on LivingEntity.getGravity(), so i graciously get to go fuck myself and not care. + // Players are 0.08 units/second/travel() of gravity (from what i've gathered) + float gravityFactor = gravityAccelReal / GravityData.ONE_G; + + // NOTE: this might cause certain entities to fly into the stratosphere at ultra low gravity, + // seeing as this isn't the same for all entities. + // Thankfully, though, this should have no effect on anything at default gravity. + float baseGameGravityAccel = 0.08f; + float translatedAccel = baseGameGravityAccel * gravityFactor; + + Vec3 currentVelocity = entity.getDeltaMovement(); + // add baseGameGravity to cancel normal gravity, then subtract the new gravity + if (translatedAccel > 0) entity.setDeltaMovement(currentVelocity.x(), currentVelocity.y() + (baseGameGravityAccel - translatedAccel), currentVelocity.z()); + else entity.setDeltaMovement(currentVelocity.x(), currentVelocity.y() + baseGameGravityAccel, currentVelocity.z()); + } +}