Animate control surfaces and flaps from the real deflection
A GeckoLib GeoModel mixin sets the Fin bone of Warium's control surfaces to the deflection the flight physics uses, synced to nearby clients per ship. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVREx3uo7nz1FEhzveWTUh
This commit is contained in:
12 files changed
+356
-2
No files matched your search
@@ -84,6 +84,10 @@ dependencies {
|
||||
compileOnly fg.deobf("blank:WariumVS:1.1.7")
|
||||
compileOnly fg.deobf("blank:futurism:0.0.4")
|
||||
|
||||
// GeckoLib 4.8.4, which Warium VS animates its control surfaces with (optional dependency, remapped, the surface animation mixin
|
||||
// only applies when it and Warium VS are present)
|
||||
compileOnly fg.deobf("maven.modrinth:geckolib:aC5KMoNg")
|
||||
|
||||
// AzureLib 3.2.7 for the animated block models (optional dependency, the common jar uses obfuscated names, so it is remapped)
|
||||
compileOnly fg.deobf("mod.azure.azurelib:azurelib-common-${minecraft_version}:${azurelib_version}")
|
||||
}
|
||||
|
||||
@@ -220,6 +220,11 @@ public final class Config {
|
||||
.comment("Vectored afterburner nozzle: afterburner ignition and heavy afterburner sound")
|
||||
.define("soundEnabled", true);
|
||||
|
||||
public static final ForgeConfigSpec.BooleanValue ANIMATE_SURFACES = CLIENT_BUILDER
|
||||
.comment("Control surfaces and flaps visibly deflect by as much as the flight physics deflects them, instead of Warium's",
|
||||
"fixed 20 degree steps. Needs physicsRateAerodynamics on the server")
|
||||
.define("animateControlSurfaces", true);
|
||||
|
||||
static final ForgeConfigSpec CLIENT_SPEC = CLIENT_BUILDER.build();
|
||||
|
||||
private Config() {
|
||||
|
||||
@@ -29,6 +29,7 @@ public class ImprovedAerodynamics {
|
||||
MinecraftForge.EVENT_BUS.register(ExhaustSync.class);
|
||||
MinecraftForge.EVENT_BUS.register(dev.lumareth.improvedAerodynamics.flight.ControlMode.class);
|
||||
MinecraftForge.EVENT_BUS.register(dev.lumareth.improvedAerodynamics.flight.FlapInteraction.class);
|
||||
MinecraftForge.EVENT_BUS.register(dev.lumareth.improvedAerodynamics.flight.DeflectionSync.class);
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC);
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_SPEC);
|
||||
// Own file name: the default one is taken by Config.CLIENT_SPEC
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package dev.lumareth.improvedAerodynamics.client.animation;
|
||||
|
||||
import dev.lumareth.improvedAerodynamics.Config;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import software.bernie.geckolib.core.animatable.model.CoreGeoBone;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
/**
|
||||
* Client side of the control surface animation. The server tells the clients how far each control surface is deflected
|
||||
* (see {@code DeflectionSync}); this class keeps those values, eases towards them, and sets the rotation of the "Fin"
|
||||
* bone of Warium's control surface models to them after GeckoLib has played Warium's own fixed animation.
|
||||
* <p>
|
||||
* Warium's animations tilt the Fin bone by 20 degrees about X for a positive input, which GeckoLib turns into a
|
||||
* rotation of -20 degrees (it negates X for rotations), and the same sign is used here, so a surface moves the way
|
||||
* Warium's own animation moves it, only by the amount the physics uses.
|
||||
*/
|
||||
@Mod.EventBusSubscriber(modid = dev.lumareth.improvedAerodynamics.ImprovedAerodynamics.MOD_ID, value = Dist.CLIENT)
|
||||
public final class SurfaceAnimation {
|
||||
private static final String BONE = "Fin";
|
||||
// A value that has not been refreshed for this long is dropped, Warium's own animation takes over again
|
||||
private static final long STALE_NANOS = 3_000_000_000L;
|
||||
// Time in seconds the visible deflection takes to close most of the way to the latest value
|
||||
private static final double EASE_SECONDS = 0.07;
|
||||
|
||||
private static final class Entry {
|
||||
float target;
|
||||
float shown;
|
||||
long updated;
|
||||
long drawn;
|
||||
boolean placed;
|
||||
}
|
||||
|
||||
private static final Long2ObjectOpenHashMap<Entry> ENTRIES = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
private SurfaceAnimation() {
|
||||
}
|
||||
|
||||
/** New deflection of a control surface in degrees, in Warium's animation sign (positive is its "input up" tilt). */
|
||||
public static void set(long pos, float degrees) {
|
||||
Entry entry = ENTRIES.computeIfAbsent(pos, key -> new Entry());
|
||||
entry.target = degrees;
|
||||
entry.updated = System.nanoTime();
|
||||
if (!entry.placed) {
|
||||
// First value of a surface: show it right away, it may have been there before the player came near
|
||||
entry.shown = degrees;
|
||||
entry.placed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void apply(BlockEntity blockEntity, GeoModel<?> model) {
|
||||
if (ENTRIES.isEmpty() || !Config.ANIMATE_SURFACES.get()) {
|
||||
return;
|
||||
}
|
||||
Entry entry = ENTRIES.get(blockEntity.getBlockPos().asLong());
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
long now = System.nanoTime();
|
||||
if (now - entry.updated > STALE_NANOS) {
|
||||
ENTRIES.remove(blockEntity.getBlockPos().asLong());
|
||||
return;
|
||||
}
|
||||
// The same block can be drawn more than once a frame (shadows, other passes), the easing steps once per frame
|
||||
if (entry.drawn != 0L && now != entry.drawn) {
|
||||
double dt = (now - entry.drawn) / 1.0E9;
|
||||
entry.shown += (entry.target - entry.shown) * (float) (1.0 - Math.exp(-dt / EASE_SECONDS));
|
||||
}
|
||||
entry.drawn = now;
|
||||
CoreGeoBone bone = model.getAnimationProcessor().getBone(BONE);
|
||||
if (bone != null) {
|
||||
bone.setRotX((float) -Math.toRadians(entry.shown));
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLoggingOut(ClientPlayerNetworkEvent.LoggingOut event) {
|
||||
ENTRIES.clear();
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
private boolean[] stalled = new boolean[0];
|
||||
private boolean wingStalled;
|
||||
private final Map<Long, NodeState> nodeStates = new ConcurrentHashMap<>();
|
||||
// Every ship's node states by node position, for the control surface animation on the server thread
|
||||
private static final Map<Long, NodeState> ALL_NODE_STATES = new ConcurrentHashMap<>();
|
||||
private final Wrench wrench = new Wrench();
|
||||
private final Wrench probe = new Wrench();
|
||||
|
||||
@@ -57,6 +59,8 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
double trimOutput;
|
||||
// Deflection of this node's flaps in radians, moves towards what the flap lever and airspeed allow
|
||||
double flapAngle;
|
||||
// Wall clock time of the last physics substep that ran this node, to tell whether its ship is flying
|
||||
volatile long updatedMillis;
|
||||
// Total pitch input the flight computer applied last substep, pilot and trim together
|
||||
double computerOutput;
|
||||
// Torque (ship axes) one unit of pitch input on this node's pitch surfaces makes, measured every substep
|
||||
@@ -209,9 +213,15 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
for (int i = 0; i < stalled.length; i++) {
|
||||
stalled[i] = previous.get(frame.surfaces[i].key());
|
||||
}
|
||||
nodeStates.keySet().removeIf(key -> !frame.nodes.containsKey((long) key));
|
||||
nodeStates.keySet().removeIf(key -> {
|
||||
boolean gone = !frame.nodes.containsKey((long) key);
|
||||
if (gone) {
|
||||
ALL_NODE_STATES.remove(key);
|
||||
}
|
||||
return gone;
|
||||
});
|
||||
for (NodeData node : frame.nodes.values()) {
|
||||
nodeStates.computeIfAbsent(node.key(), key -> new NodeState(node));
|
||||
ALL_NODE_STATES.put(node.key(), nodeStates.computeIfAbsent(node.key(), key -> new NodeState(node)));
|
||||
}
|
||||
current = frame;
|
||||
}
|
||||
@@ -333,6 +343,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
state.yaw = moveTowards(state.yaw, effective.yaw(), step);
|
||||
state.roll = moveTowards(state.roll, effective.roll(), step);
|
||||
updateFlaps(state, node, worldVelocity.length(), dt);
|
||||
state.updatedMillis = System.currentTimeMillis();
|
||||
AutoTrim.update(state, effective, worldVelocity, altitude, rotation, inertia, w, torque, dt, improved && wingStalled);
|
||||
}
|
||||
}
|
||||
@@ -351,6 +362,11 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
state.flapAngle = moveTowards(state.flapAngle, target, Math.toRadians(Config.FLAP_SLEW_DEGREES.get()) * dt);
|
||||
}
|
||||
|
||||
/** The physics state of a control node, null if no physics runs for it. */
|
||||
static NodeState nodeState(long nodeKey) {
|
||||
return ALL_NODE_STATES.get(nodeKey);
|
||||
}
|
||||
|
||||
static double moveTowards(double value, double target, double maxStep) {
|
||||
if (Math.abs(target - value) <= maxStep) {
|
||||
return target;
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package dev.lumareth.improvedAerodynamics.flight;
|
||||
|
||||
import dev.lumareth.improvedAerodynamics.Config;
|
||||
import dev.lumareth.improvedAerodynamics.network.DeflectionPacket;
|
||||
import dev.lumareth.improvedAerodynamics.network.Network;
|
||||
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ShortOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppedEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.network.PacketDistributor;
|
||||
import org.joml.Vector3d;
|
||||
import org.valkyrienskies.core.api.ships.Ship;
|
||||
import org.valkyrienskies.mod.common.VSGameUtilsKt;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tells the clients near a ship how far its control surfaces and flaps are deflected, so they can show it, see
|
||||
* {@code SurfaceAnimation}. Every tick each Warium control surface block reports its deflection from the procedure that
|
||||
* runs it ({@link WariumCapture#begin}); at the end of the tick what changed goes out, in one packet per ship, and
|
||||
* everything again once a second so players who arrive later get it too.
|
||||
* <p>
|
||||
* The deflection is what the physics uses: the smoothed pilot input plus trim and flap angle of the surface's control
|
||||
* node. While the ship is not flying (no physics, nothing is moving) the node's raw input and flap lever are used, and the
|
||||
* flaps are moved towards their angle here, at the configured speed.
|
||||
*/
|
||||
public final class DeflectionSync {
|
||||
/** Degrees of deflection the physics gives a surface at full input, see ImprovedAero.MAX_DEFLECTION. */
|
||||
private static final double FULL_INPUT_DEGREES = 20.0;
|
||||
private static final long RESEND_TICKS = 20;
|
||||
private static final double RANGE = 256.0;
|
||||
// A node state the physics thread has not touched for this long is not flying any more
|
||||
private static final long FRESH_MILLIS = 500;
|
||||
|
||||
private record Batch(ServerLevel level, double x, double y, double z, LongArrayList positions, ShortArrayList tenths) {
|
||||
}
|
||||
|
||||
private static final Map<ResourceKey<Level>, Long2ShortOpenHashMap> LAST_VALUE = new HashMap<>();
|
||||
private static final Map<ResourceKey<Level>, Long2LongOpenHashMap> LAST_SENT = new HashMap<>();
|
||||
// Flap angle (radians) shown while a node is not flying, and the tick it was last moved
|
||||
private static final Map<Long, double[]> STATIC_FLAPS = new HashMap<>();
|
||||
private static final Map<Long, Batch> BATCHES = new HashMap<>();
|
||||
|
||||
private DeflectionSync() {
|
||||
}
|
||||
|
||||
/** Called by the procedure of every control surface block each tick, before it adds its forces. */
|
||||
static void report(ServerLevel level, BlockPos pos, SurfaceType type) {
|
||||
if (!type.controllable) {
|
||||
return;
|
||||
}
|
||||
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||
if (blockEntity == null) {
|
||||
return;
|
||||
}
|
||||
CompoundTag tag = blockEntity.getPersistentData();
|
||||
BlockPos nodePos = BlockPos.containing(tag.getDouble("ControlX"), tag.getDouble("ControlY"), tag.getDouble("ControlZ"));
|
||||
BlockEntity nodeEntity = level.getBlockEntity(nodePos);
|
||||
if (nodeEntity == null || !tag.contains("ControlX")) {
|
||||
return;
|
||||
}
|
||||
long nodeKey = nodePos.asLong();
|
||||
CompoundTag nodeTag = nodeEntity.getPersistentData();
|
||||
AeroState.NodeState state = AeroState.nodeState(nodeKey);
|
||||
boolean flying = state != null && System.currentTimeMillis() - state.updatedMillis < FRESH_MILLIS;
|
||||
|
||||
double input = axis(tag.getString("Key"), nodeTag, state, flying) + axis(tag.getString("Key2"), nodeTag, state, flying);
|
||||
double degrees = FULL_INPUT_DEGREES * Math.max(-1.0, Math.min(1.0, input));
|
||||
if (type == SurfaceType.CONTROL_SURFACE && tag.getBoolean(WariumCapture.FLAP_TAG)) {
|
||||
degrees += Math.toDegrees(flying ? state.flapAngle : staticFlap(level, nodeKey, nodeTag));
|
||||
}
|
||||
|
||||
short tenths = (short) Math.round(Math.max(-90.0, Math.min(90.0, degrees)) * 10.0);
|
||||
ResourceKey<Level> dimension = level.dimension();
|
||||
Long2ShortOpenHashMap lastValue = LAST_VALUE.computeIfAbsent(dimension, key -> new Long2ShortOpenHashMap());
|
||||
Long2LongOpenHashMap lastSent = LAST_SENT.computeIfAbsent(dimension, key -> new Long2LongOpenHashMap());
|
||||
long posKey = pos.asLong();
|
||||
long now = level.getGameTime();
|
||||
if (lastSent.containsKey(posKey) && lastValue.get(posKey) == tenths && now - lastSent.get(posKey) < RESEND_TICKS) {
|
||||
return;
|
||||
}
|
||||
lastValue.put(posKey, tenths);
|
||||
lastSent.put(posKey, now);
|
||||
|
||||
Ship ship = VSGameUtilsKt.getShipManagingPos(level, pos);
|
||||
long shipId = ship != null ? ship.getId() : Long.MIN_VALUE;
|
||||
Batch batch = BATCHES.get(shipId);
|
||||
if (batch == null) {
|
||||
Vector3d world = new Vector3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
|
||||
if (ship != null) {
|
||||
ship.getShipToWorld().transformPosition(world);
|
||||
}
|
||||
batch = new Batch(level, world.x, world.y, world.z, new LongArrayList(), new ShortArrayList());
|
||||
BATCHES.put(shipId, batch);
|
||||
}
|
||||
batch.positions.add(posKey);
|
||||
batch.tenths.add(tenths);
|
||||
}
|
||||
|
||||
/** One key's share of a surface's input, like Warium reads "Pitch+" / "Roll-". */
|
||||
private static double axis(String key, CompoundTag nodeTag, AeroState.NodeState state, boolean flying) {
|
||||
ControlAxis axis = ControlAxis.of(key);
|
||||
double multiplier = ControlAxis.multiplier(key);
|
||||
return switch (axis) {
|
||||
case PITCH -> (flying ? state.pitch + state.trimOutput : nodeTag.getDouble("Pitch")) * multiplier;
|
||||
case YAW -> (flying ? state.yaw : nodeTag.getDouble("Yaw")) * multiplier;
|
||||
case ROLL -> (flying ? state.roll : nodeTag.getDouble("Roll")) * multiplier;
|
||||
default -> 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
/** Flap angle of a node that is not flying: moves towards the lever's angle, once per tick however many flaps ask. */
|
||||
private static double staticFlap(ServerLevel level, long nodeKey, CompoundTag nodeTag) {
|
||||
double target = Math.toRadians(FlapSetting.of(nodeTag.getInt(WariumCapture.FLAP_SETTING_TAG)).degrees());
|
||||
double[] entry = STATIC_FLAPS.computeIfAbsent(nodeKey, key -> new double[]{target, Double.NaN});
|
||||
long now = level.getGameTime();
|
||||
if (entry[1] != now) {
|
||||
entry[1] = now;
|
||||
entry[0] = AeroState.moveTowards(entry[0], target, Math.toRadians(Config.FLAP_SLEW_DEGREES.get()) / 20.0);
|
||||
}
|
||||
return entry[0];
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END || BATCHES.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Batch batch : BATCHES.values()) {
|
||||
DeflectionPacket packet = new DeflectionPacket(batch.positions.toLongArray(), batch.tenths.toShortArray());
|
||||
Network.CHANNEL.send(PacketDistributor.NEAR.with(() -> new PacketDistributor.TargetPoint(batch.x, batch.y, batch.z,
|
||||
RANGE, batch.level.dimension())), packet);
|
||||
}
|
||||
BATCHES.clear();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerStopped(ServerStoppedEvent event) {
|
||||
BATCHES.clear();
|
||||
LAST_VALUE.clear();
|
||||
LAST_SENT.clear();
|
||||
STATIC_FLAPS.clear();
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,13 @@ public final class MixinPlugin implements IMixinConfigPlugin {
|
||||
|
||||
private boolean warium;
|
||||
private boolean futurism;
|
||||
private boolean geckolib;
|
||||
|
||||
@Override
|
||||
public void onLoad(String mixinPackage) {
|
||||
warium = supported("valkyrien_warium", WARIUM_VERSION, "Warium VS");
|
||||
futurism = supported("futurism", FUTURISM_VERSION, "Futurism");
|
||||
geckolib = LoadingModList.get().getModFileById("geckolib") != null;
|
||||
}
|
||||
|
||||
private static boolean supported(String modId, String version, String name) {
|
||||
@@ -49,6 +51,10 @@ public final class MixinPlugin implements IMixinConfigPlugin {
|
||||
if (mixinClassName.contains(".mixin.warium.")) {
|
||||
return warium;
|
||||
}
|
||||
if (mixinClassName.contains(".mixin.gecko.")) {
|
||||
// Animates Warium's control surfaces, which are GeckoLib models
|
||||
return warium && geckolib;
|
||||
}
|
||||
if (mixinClassName.contains(".mixin.futurism.")) {
|
||||
return futurism;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ public final class WariumCapture {
|
||||
world = level;
|
||||
pos = BlockPos.containing(x, y, z);
|
||||
state = blockState;
|
||||
if (surfaceType.controllable && level instanceof ServerLevel serverLevel && Config.PHYSICS_RATE_AERODYNAMICS.get()) {
|
||||
DeflectionSync.report(serverLevel, pos, surfaceType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package dev.lumareth.improvedAerodynamics.flight.mixin.gecko;
|
||||
|
||||
import dev.lumareth.improvedAerodynamics.client.animation.SurfaceAnimation;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import software.bernie.geckolib.core.animatable.GeoAnimatable;
|
||||
import software.bernie.geckolib.core.animation.AnimationState;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
/**
|
||||
* Runs after GeckoLib has played an animatable's animations, to put Warium's control surfaces at the deflection the
|
||||
* flight physics gives them, see {@link SurfaceAnimation}.
|
||||
*/
|
||||
@Mixin(value = GeoModel.class, remap = false)
|
||||
public abstract class GeoModelMixin {
|
||||
@Inject(method = "handleAnimations", at = @At("RETURN"))
|
||||
private void improvedAerodynamics$deflectSurface(GeoAnimatable animatable, long instanceId, AnimationState animationState, CallbackInfo ci) {
|
||||
if (animatable instanceof BlockEntity blockEntity) {
|
||||
SurfaceAnimation.apply(blockEntity, (GeoModel<?>) (Object) this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package dev.lumareth.improvedAerodynamics.network;
|
||||
|
||||
import dev.lumareth.improvedAerodynamics.client.animation.SurfaceAnimation;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** How far control surfaces are deflected, for the clients near a ship: block positions and tenths of a degree. */
|
||||
public final class DeflectionPacket {
|
||||
private final long[] positions;
|
||||
private final short[] tenths;
|
||||
|
||||
public DeflectionPacket(long[] positions, short[] tenths) {
|
||||
this.positions = positions;
|
||||
this.tenths = tenths;
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buffer) {
|
||||
buffer.writeVarInt(positions.length);
|
||||
for (int i = 0; i < positions.length; i++) {
|
||||
buffer.writeLong(positions[i]);
|
||||
buffer.writeShort(tenths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static DeflectionPacket decode(FriendlyByteBuf buffer) {
|
||||
int count = buffer.readVarInt();
|
||||
long[] positions = new long[count];
|
||||
short[] tenths = new short[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
positions[i] = buffer.readLong();
|
||||
tenths[i] = buffer.readShort();
|
||||
}
|
||||
return new DeflectionPacket(positions, tenths);
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> context) {
|
||||
long[] pos = positions;
|
||||
short[] values = tenths;
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
|
||||
for (int i = 0; i < pos.length; i++) {
|
||||
SurfaceAnimation.set(pos[i], values[i] / 10.0F);
|
||||
}
|
||||
});
|
||||
context.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,11 @@ public final class Network {
|
||||
.decoder(ControlModePacket::decode)
|
||||
.consumerMainThread(ControlModePacket::handle)
|
||||
.add();
|
||||
CHANNEL.messageBuilder(DeflectionPacket.class, 11, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(DeflectionPacket::encode)
|
||||
.decoder(DeflectionPacket::decode)
|
||||
.consumerMainThread(DeflectionPacket::handle)
|
||||
.add();
|
||||
// Higher id than the neighbours so it does not collide with packets added on other branches
|
||||
CHANNEL.messageBuilder(FlapPacket.class, 10, NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(FlapPacket::encode)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"mixins": [
|
||||
"futurism.ForceInducedShipsMixin",
|
||||
"gecko.GeoModelMixin",
|
||||
"warium.AirBrakeTickProcedureMixin",
|
||||
"warium.BypassedCompressorScriptProcedureMixin",
|
||||
"warium.ControlSurfaceTickProcedureMixin",
|
||||
|
||||
Reference in new issue
Block a user