add(feat): elytra speed meter

This commit is contained in:
iceBear67 committed 2024-07-14 19:51:31 +08:00
1 parent 20f874096d
commit cae3495225
8 files changed
+285 -25

No files matched your search

@@ -40,6 +40,7 @@ public class SFCraftInitializer extends GuiceModInitializer {
registerFeature(MotdModule.class);
registerFeature(WelcomeModule.class);
registerFeature(OfflineExemptModule.class);
registerFeature(ElytraSpeedMeterModule.class);
registerFeature(PlayLimitModule.class);
registerFeature(BackModule.class);
registerFeature(ManagementModule.class);
@@ -33,6 +33,12 @@ public interface SFCallbacks {
l -> (p, a) -> forEach(l, i -> i.onSwitchIdle(p, a)));
Event<PlayerSneakingCallback> PLAYER_SNEAKING = EventFactory.createArrayBacked(PlayerSneakingCallback.class,
l -> (p, a) -> forEach(l, i -> i.onSneaking(p, a)));
Event<PlayerFlyingCallback> PLAYER_FLYING = EventFactory.createArrayBacked(PlayerFlyingCallback.class,
l -> (p, t, f) -> forEach(l, i -> i.onFlyingTick(p, t, f)));
interface PlayerFlyingCallback {
void onFlyingTick(PlayerEntity player, long flyingTick, boolean flying);
}
interface PlayerSneakingCallback {
void onSneaking(PlayerEntity player, boolean sneak);
@@ -1,41 +1,39 @@
package io.ib67.sfcraft.mixin.server;
import com.mojang.datafixers.util.Either;
import io.ib67.sfcraft.callback.SFCallbacks;
import net.minecraft.entity.damage.DamageSource;
import io.ib67.sfcraft.mixin.server.bridge.EntityBridge;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Unit;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ServerPlayerEntity.class)
public class PlayerEntityMixin {
@Shadow
private long lastActionTime;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin {
private static final int THRESHLD_OF_ELYTRA_FLY = 2;
@Unique
private long flyTime;
@Inject(method = "onDeath", at = @At("TAIL"))
public void onDeath(DamageSource damageSource, CallbackInfo ci) {
SFCallbacks.PLAYER_DEATH.invoker().onPlayerDeath((ServerPlayerEntity) (Object) this, damageSource);
@Inject(method = "tickMovement", at = @At("TAIL"))
public void tickMovement(CallbackInfo ci) {
var eb = ((EntityBridge) $this());
if (eb.sfcraft$getFlag(EntityBridge.sfcraft$getFlyingFlagIndex())) {
flyTime++;
if (flyTime > 20 * THRESHLD_OF_ELYTRA_FLY) { // todo config
SFCallbacks.PLAYER_FLYING.invoker().onFlyingTick($this(), flyTime - 20*THRESHLD_OF_ELYTRA_FLY, true);
}
@Inject(method = "trySleep", at = @At("HEAD"), cancellable = true)
public void onSleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> cir) {
var result = SFCallbacks.PLAYER_SLEEP.invoker().onPlayerSleep((ServerPlayerEntity) (Object) this, pos);
if (result.left().isPresent()) {
cir.setReturnValue(result);
} else {
if (flyTime != 0) {
SFCallbacks.PLAYER_FLYING.invoker().onFlyingTick($this(), flyTime, false);
}
flyTime = 0;
}
}
@Inject(method = "updateLastActionTime", at = @At("HEAD"))
public void leaveAFK(CallbackInfo ci) {
if (lastActionTime == Long.MAX_VALUE) {
SFCallbacks.PLAYER_IDLE.invoker().onSwitchIdle((ServerPlayerEntity) (Object) this, false);
}
@Unique
public PlayerEntity $this() {
return (PlayerEntity) (Object) this;
}
}
@@ -0,0 +1,41 @@
package io.ib67.sfcraft.mixin.server;
import com.mojang.datafixers.util.Either;
import io.ib67.sfcraft.callback.SFCallbacks;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Unit;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ServerPlayerEntity.class)
public class ServerPlayerEntityMixin {
@Shadow
private long lastActionTime;
@Inject(method = "onDeath", at = @At("TAIL"))
public void onDeath(DamageSource damageSource, CallbackInfo ci) {
SFCallbacks.PLAYER_DEATH.invoker().onPlayerDeath((ServerPlayerEntity) (Object) this, damageSource);
}
@Inject(method = "trySleep", at = @At("HEAD"), cancellable = true)
public void onSleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> cir) {
var result = SFCallbacks.PLAYER_SLEEP.invoker().onPlayerSleep((ServerPlayerEntity) (Object) this, pos);
if (result.left().isPresent()) {
cir.setReturnValue(result);
}
}
@Inject(method = "updateLastActionTime", at = @At("HEAD"))
public void leaveAFK(CallbackInfo ci) {
if (lastActionTime == Long.MAX_VALUE) {
SFCallbacks.PLAYER_IDLE.invoker().onSwitchIdle((ServerPlayerEntity) (Object) this, false);
}
}
}
@@ -0,0 +1,17 @@
package io.ib67.sfcraft.mixin.server.bridge;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Entity.class)
public interface EntityBridge {
@Accessor("FALL_FLYING_FLAG_INDEX")
static int sfcraft$getFlyingFlagIndex() {
throw new AssertionError();
}
@Invoker("getFlag")
boolean sfcraft$getFlag(int index);
}
@@ -0,0 +1,156 @@
package io.ib67.sfcraft.module;
import io.ib67.sfcraft.ServerModule;
import io.ib67.sfcraft.callback.SFCallbacks;
import io.ib67.sfcraft.util.ColorHelper;
import it.unimi.dsi.fastutil.objects.*;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ElytraItem;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Colors;
import net.minecraft.util.math.BlockPos;
import java.awt.*;
import java.text.DecimalFormat;
public class ElytraSpeedMeterModule extends ServerModule {
private static final int BAR_LEN = 20;
private static final int MAX_NORMAL_SPEED = 6;
private static final Color BAR_LEFT = new Color(85, 85, 255);
private static final Color BAR_RIGHT = new Color(255, 87, 51);
private static final Text[] ELYTRA_DURABILITY = new Text[100];
private static final Text[] PROGRESS_BARS = new Text[BAR_LEN];
private static final Text[] Y_METER = new Text[256];
private final Object2ObjectMap<PlayerEntity, BlockPos> playerFlyMap = new Object2ObjectOpenHashMap<>();
private final Object2FloatMap<PlayerEntity> lastMeasuredSpeed = new Object2FloatOpenHashMap<>();
static {
for (int i = 1; i <= BAR_LEN; i++) {
PROGRESS_BARS[i - 1] = buildProgressBar(i);
}
for (int i = 1; i <= 256; i++) {
Y_METER[i - 1] = makeYMeter(i);
}
for (int i = 1; i <= 100; i++) {
ELYTRA_DURABILITY[i - 1] = Text.literal("ELYTRA: " + i + "%").withColor(getDamageColor(i));
}
}
@Override
public void onInitialize() {
SFCallbacks.PLAYER_FLYING.register(this::onFlying);
ServerPlayConnectionEvents.DISCONNECT.register(this::onDisconnect);
}
private void onDisconnect(ServerPlayNetworkHandler serverPlayNetworkHandler, MinecraftServer minecraftServer) {
clean(serverPlayNetworkHandler.getPlayer());
}
private void clean(ServerPlayerEntity player) {
playerFlyMap.remove(player);
lastMeasuredSpeed.removeFloat(player);
}
private void onFlying(PlayerEntity player, long f, boolean b) {
if (b) {
var text = Text.literal("");
text.append(generateYMeter(f, player));
text.append(generateSpeedMeter(f, player));
text.append(generateDurabilityMeter(f, player));
player.sendMessage(text, true);
} else {
clean((ServerPlayerEntity) player);
}
}
private Text generateDurabilityMeter(long f, PlayerEntity player) {
var stack = player.getInventory().getArmorStack(PlayerInventory.ARMOR_SLOTS[2]);
if (stack.isDamageable() && stack.isDamaged()) {
var remaining = stack.getMaxDamage() - stack.getDamage();
var percent = (int) Math.ceil(((double) remaining / stack.getMaxDamage()) * 100);
return ELYTRA_DURABILITY[percent];
}
return Text.literal(" ELYTRA: INFINITY").withColor(Colors.BLUE);
}
private static int getDamageColor(int percent) {
if (percent > 70) {
return Colors.GREEN;
} else if (percent > 50) {
return Colors.LIGHT_YELLOW;
} else if (percent > 30) {
return Colors.LIGHT_RED;
}
return Colors.RED;
}
private Text generateYMeter(long f, PlayerEntity player) {
var y = player.getBlockY();
if (y <= 256 && y > 0) {
return Y_METER[y - 1];
} else {
return makeYMeter(y);
}
}
private static Text makeYMeter(int y) {
return Text.literal("Y: " + y + " | ").withColor(getYColor(y));
}
private static int getYColor(int y) {
if (y < 120) {
return Colors.YELLOW;
} else if (y <= 180) {
return Colors.GREEN;
} else {
return Colors.BLUE;
}
}
private Text generateSpeedMeter(long flyTime, PlayerEntity player) {
final var MEASURE_TIME = 5;
if (!playerFlyMap.containsKey(player)) {
playerFlyMap.put(player, player.getBlockPos());
return Text.empty();
}
float speed;
if (flyTime % MEASURE_TIME == 0) {
var pos = playerFlyMap.get(player);
var distance = player.squaredDistanceTo(pos.getX(), pos.getY(), pos.getZ());
speed = (float) (Math.abs(Math.round(distance / Math.pow(MEASURE_TIME, 2) * 100) * 0.01));
lastMeasuredSpeed.put(player, speed);
playerFlyMap.put(player, player.getBlockPos());
} else {
speed = lastMeasuredSpeed.getFloat(player);
}
var percentToSix = speed / MAX_NORMAL_SPEED;
var result = Text.literal("SPD: [");
int steps = (int) Math.ceil(percentToSix * BAR_LEN);
if (steps > BAR_LEN - 1) {
steps = BAR_LEN - 1;
}
result.append(PROGRESS_BARS[steps]);
result.append("] " + Math.ceil(speed * 100) / 100 + "/s | ");
if (speed > MAX_NORMAL_SPEED) {
result.withColor(Colors.LIGHT_RED);
}
return result;
}
private static Text buildProgressBar(int steps) {
var result = Text.literal("");
for (int i = 0; i < steps; i++) {
var color = ColorHelper.interpolate(ColorHelper.GradientType.HSV, BAR_LEFT, BAR_RIGHT, (float) ((double) i / BAR_LEN));
result.append(Text.literal("|").withColor(color.getRGB()));
}
result.append(Text.literal("|".repeat(BAR_LEN - steps)).withColor(Colors.LIGHT_GRAY));
return result;
}
}
@@ -0,0 +1,39 @@
package io.ib67.sfcraft.util;
import java.awt.*;
public class ColorHelper {
public enum GradientType {
LINEAR,
HSV
}
public static Color interpolate(GradientType type, Color color1, Color color2, float t) {
switch (type) {
case LINEAR:
return linearInterpolation(color1, color2, t);
case HSV:
return hsvInterpolation(color1, color2, t);
default:
throw new IllegalArgumentException("Unknown gradient type: " + type);
}
}
private static Color linearInterpolation(Color color1, Color color2, float t) {
int r = (int) (color1.getRed() + (color2.getRed() - color1.getRed()) * t);
int g = (int) (color1.getGreen() + (color2.getGreen() - color1.getGreen()) * t);
int b = (int) (color1.getBlue() + (color2.getBlue() - color1.getBlue()) * t);
int a = (int) (color1.getAlpha() + (color2.getAlpha() - color1.getAlpha()) * t);
return new Color(r, g, b, a);
}
private static Color hsvInterpolation(Color color1, Color color2, float t) {
float[] hsv1 = Color.RGBtoHSB(color1.getRed(), color1.getGreen(), color1.getBlue(), null);
float[] hsv2 = Color.RGBtoHSB(color2.getRed(), color2.getGreen(), color2.getBlue(), null);
float h = (hsv1[0] + (hsv2[0] - hsv1[0]) * t);
float s = (hsv1[1] + (hsv2[1] - hsv1[1]) * t);
float v = (hsv1[2] + (hsv2[2] - hsv1[2]) * t);
return Color.getHSBColor(h, s, v);
}
}
+2
View File
@@ -11,6 +11,7 @@
"server.fixes.EndermanEntityMixin$ChaseGoalMixin",
"server.bridge.ServerWorldBridge",
"server.bridge.ServerPlayerBridge",
"server.bridge.EntityBridge",
"server.MinecraftServerMixin",
"server.ServerPlayNetworkHandlerMixin",
"server.SleepManagerMixin",
@@ -19,6 +20,7 @@
"server.AbstractFurnaceBlockMixin",
"server.ServerHandshakeNetworkHandlerMixin",
"server.LegacyQueryHandlerMixin",
"server.ServerPlayerEntityMixin",
"server.PlayerEntityMixin",
"server.ServerLoginNetworkHandlerMixin"
],