From b954317f75b5feb11fc3a8e01d060d6fed170291 Mon Sep 17 00:00:00 2001 From: FatherToast Date: Mon, 12 Sep 2022 23:13:29 -0500 Subject: [PATCH] velocity tracker --- .../common/event/PlayerVelocityWatcher.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/main/java/fathertoast/specialmobs/common/event/PlayerVelocityWatcher.java diff --git a/src/main/java/fathertoast/specialmobs/common/event/PlayerVelocityWatcher.java b/src/main/java/fathertoast/specialmobs/common/event/PlayerVelocityWatcher.java new file mode 100644 index 0000000..9b3abda --- /dev/null +++ b/src/main/java/fathertoast/specialmobs/common/event/PlayerVelocityWatcher.java @@ -0,0 +1,107 @@ +package fathertoast.specialmobs.common.event; + +import fathertoast.specialmobs.common.core.SpecialMobs; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.management.PlayerList; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; +import net.minecraftforge.fml.server.ServerLifecycleHooks; + +import java.util.HashMap; +import java.util.UUID; + +/** + * Class that tracks all players' current positions and their positions last tick. + *

+ * If we want to get serious here, we could catch teleport/portal events and reset the tracker as well. + */ +@Mod.EventBusSubscriber( modid = SpecialMobs.MOD_ID ) +public class PlayerVelocityWatcher { + + /** A map that tracks all players in existence. */ + private static final HashMap TRACKER = new HashMap<>(); + + /** Number of server ticks since the last cleanup. */ + private static int cleanupCounter; + + /** @return The player's velocity tracker entry. If none exists, a new one will be created. */ + public static Entry get( PlayerEntity player ) { + Entry trackerEntry = TRACKER.get( player.getUUID() ); + if( trackerEntry == null ) { + trackerEntry = new Entry( player.position() ); + TRACKER.put( player.getUUID(), trackerEntry ); + } + return trackerEntry; + } + + /** Called for every player tick event. */ + @SubscribeEvent + protected static void onPlayerTick( TickEvent.PlayerTickEvent event ) { + if( event.side.isServer() && event.phase == TickEvent.Phase.END ) { + Entry trackerEntry = get( event.player ); + trackerEntry.update( event.player.position() ); + } + } + + /** Called for every server tick event. */ + @SubscribeEvent + protected static void onServerTick( TickEvent.ServerTickEvent event ) { + // Periodically remove all currently disconnected players; + // this is a hugely low priority, so we only do it about once per hour + if( event.phase == TickEvent.Phase.END && ++cleanupCounter >= 69_420 ) { + cleanupCounter = 0; + final PlayerList onlinePlayers = ServerLifecycleHooks.getCurrentServer().getPlayerList(); + TRACKER.keySet().removeIf( ( uuid ) -> onlinePlayers.getPlayer( uuid ) == null ); + } + } + + /** Called when the server is starts shutting down. */ + @SubscribeEvent + protected static void onServerStopping( FMLServerStoppingEvent event ) { + cleanupCounter = 0; + TRACKER.clear(); + } + + /** Stores the tracked data for a single player. */ + public static class Entry { + + public double xPrev, yPrev, zPrev, x, y, z; + + private Entry( Vector3d pos ) { reset( pos ); } + + /** @return X-displacement since the last tick. */ + public double dX() { return x - xPrev; } + + /** @return Y-displacement since the last tick. */ + public double dY() { return y - yPrev; } + + /** @return Z-displacement since the last tick. */ + public double dZ() { return z - zPrev; } + + /** @return Displacement since the last tick; this is effectively 'instantaneous velocity'. */ + public Vector3d velocity() { return new Vector3d( dX(), dY(), dZ() ); } + + /** @return True if speed is non-zero (above a small dead zone). */ + public boolean isMoving() { return Math.abs( dX() ) > 1.0E-4 || Math.abs( dZ() ) > 1.0E-4 || Math.abs( dY() ) > 1.0E-4; } + + /** Sets the current and previous positions to the same, current values. */ + public void reset( Vector3d pos ) { + xPrev = x = pos.x; + yPrev = y = pos.y; + zPrev = z = pos.z; + } + + /** Called once per tick to update the current and previous positions. */ + private void update( Vector3d pos ) { + xPrev = x; + yPrev = y; + zPrev = z; + x = pos.x; + y = pos.y; + z = pos.z; + } + } +} \ No newline at end of file