added gravity

This commit is contained in:
TechnoDraconic
2026-02-05 00:44:15 -08:00
parent 3b5d10f414
commit fa41966de4
9 changed files with 186 additions and 2 deletions

View File

@@ -115,7 +115,7 @@ public class GravitySavedData extends SavedData {
* Returns the strongest acceleration among all gravity regions that overlap this block position * Returns the strongest acceleration among all gravity regions that overlap this block position
*/ */
public float getGravityMax (BlockPos pos) { public float getGravityMax (BlockPos pos) {
float max = 0; float max = -1;
List<GravityData> regions = getGravityRegions(pos); List<GravityData> regions = getGravityRegions(pos);
@@ -124,6 +124,10 @@ public class GravitySavedData extends SavedData {
if (accel > max) max = accel; if (accel > max) max = accel;
} }
if (max == -1) {
max = GravityData.DEFAULT_GRAVITY;
}
return max; return max;
} }

View File

@@ -4,7 +4,8 @@ public class GravityData {
private float accel; private float accel;
private float radius; 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 GRAVITY_PRECISION = 100.0f;
public static final float RADIUS_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_RADIUS = Short.MAX_VALUE / RADIUS_PRECISION;

View File

@@ -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.TestBlockEntity;
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity; import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities; import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.network.ClientPlayerStateUpdateHandler;
import net.xevianlight.aphelion.network.RocketPayloadHandlers; import net.xevianlight.aphelion.network.RocketPayloadHandlers;
import net.xevianlight.aphelion.network.PartitionPayloadHandler; 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.PartitionPayload;
import net.xevianlight.aphelion.network.packet.RocketLaunchPayload; import net.xevianlight.aphelion.network.packet.RocketLaunchPayload;
@@ -49,5 +51,11 @@ public class ModBusEvents {
RocketPayloadHandlers::handleRocketLaunch RocketPayloadHandlers::handleRocketLaunch
); );
registrar.playToClient(
ClientPlayerStateUpdatePacket.TYPE,
ClientPlayerStateUpdatePacket.STREAM_CODEC,
ClientPlayerStateUpdateHandler::handleDataOnMain
);
} }
} }

View File

@@ -5,6 +5,8 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level; 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 net.xevianlight.aphelion.systems.OxygenService;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@@ -26,4 +28,9 @@ public abstract class LivingEntityMixin extends Entity {
OxygenService.entityTick(level, 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);
}
} }

View File

@@ -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;
}
}

View File

@@ -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())));
}
}

View File

@@ -2,12 +2,16 @@ package net.xevianlight.aphelion.network;
import net.minecraft.client.Minecraft; 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.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.ClientTickEvent; import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.event.tick.ServerTickEvent;
import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.PacketDistributor;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.entites.vehicles.RocketEntity; 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.network.packet.RocketLaunchPayload;
import net.xevianlight.aphelion.client.AphelionClient; import net.xevianlight.aphelion.client.AphelionClient;
@@ -27,4 +31,17 @@ public final class KeyNetwork {
PacketDistributor.sendToServer(new RocketLaunchPayload(rocket.getId())); 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()));
}
}
}
} }

View File

@@ -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<ClientPlayerStateUpdatePacket> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "player_state_update"));
public static final StreamCodec<ByteBuf, ClientPlayerStateUpdatePacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL,
ClientPlayerStateUpdatePacket::oxygen,
ByteBufCodecs.FLOAT,
ClientPlayerStateUpdatePacket::gravity,
ByteBufCodecs.FLOAT,
ClientPlayerStateUpdatePacket::temp,
ClientPlayerStateUpdatePacket::new
);
@Override
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}

View File

@@ -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());
}
}