From d839204855b4adf584d0b2a6c5eb9fc104355abe Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Wed, 9 Sep 2015 20:22:55 +0200 Subject: [PATCH] Removed the "Prepare Replay" setting (+1 squashed commit) Squashed commits: [74a16a9] Created EntityPositionTracker, which parses the whole Replay when initially loading it and remembers every Entity's Position at any timestamp. The EntityPositionTracker can be disabled using a Replay Setting. --- .../preparation/EntityPositionTracker.java | 200 ++++++++++++++++++ .../replaymod/replay/ReplayHandler.java | 13 ++ 2 files changed, 213 insertions(+) create mode 100644 src/main/java/eu/crushedpixel/replaymod/preparation/EntityPositionTracker.java diff --git a/src/main/java/eu/crushedpixel/replaymod/preparation/EntityPositionTracker.java b/src/main/java/eu/crushedpixel/replaymod/preparation/EntityPositionTracker.java new file mode 100644 index 00000000..6fb5fc39 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/preparation/EntityPositionTracker.java @@ -0,0 +1,200 @@ +package eu.crushedpixel.replaymod.preparation; + +import de.johni0702.replaystudio.PacketData; +import de.johni0702.replaystudio.io.ReplayInputStream; +import de.johni0702.replaystudio.replay.ZipReplayFile; +import de.johni0702.replaystudio.studio.ReplayStudio; +import de.johni0702.replaystudio.util.PacketUtils; +import eu.crushedpixel.replaymod.holders.AdvancedPosition; +import org.apache.commons.lang3.tuple.Pair; +import org.spacehq.mc.protocol.data.game.values.entity.player.PositionElement; +import org.spacehq.mc.protocol.packet.ingame.server.entity.ServerEntityMovementPacket; +import org.spacehq.mc.protocol.packet.ingame.server.entity.ServerEntityPositionPacket; +import org.spacehq.mc.protocol.packet.ingame.server.entity.ServerEntityPositionRotationPacket; +import org.spacehq.mc.protocol.packet.ingame.server.entity.ServerEntityTeleportPacket; +import org.spacehq.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; +import org.spacehq.mc.protocol.packet.ingame.server.entity.spawn.*; +import org.spacehq.packetlib.packet.Packet; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An EntityPositionTracker knows every Entity's position at any timestamp for a single Replay File. + * To do so, it once reads the whole Replay File and stores all Packets that set or change an Entity's position into Memory.
+ *
+ * While this significantly increases the Replay loading time, it's the only way to:
+ * 1) Properly preview the Camera Path when an Entity is spectated
+ * 2) Calculate a smooth Path from an Entity's Shoulder Cam perspective + */ +public class EntityPositionTracker { + + private static final List MOVEMENT_PACKETS = new ArrayList() { + { + add(ServerPlayerPositionRotationPacket.class); + add(ServerSpawnPlayerPacket.class); + add(ServerSpawnObjectPacket.class); + add(ServerSpawnMobPacket.class); + add(ServerSpawnPaintingPacket.class); + add(ServerSpawnExpOrbPacket.class); + add(ServerEntityMovementPacket.class); + add(ServerEntityPositionRotationPacket.class); + add(ServerEntityPositionPacket.class); + add(ServerEntityTeleportPacket.class); + } + }; + + private final File replayFile; + private boolean loaded = false; + + private Map>> entityPositions; + + public EntityPositionTracker(File replayFile) { + this.replayFile = replayFile; + this.entityPositions = new HashMap>>(); + } + + public void load() throws IOException { + ReplayStudio studio = new ReplayStudio(); + studio.setWrappingEnabled(false); + + ZipReplayFile zrf = new ZipReplayFile(studio, replayFile); + ReplayInputStream rin = zrf.getPacketData(); + + PacketData packetData; + while((packetData = rin.readPacket()) != null) { + Packet packet = packetData.getPacket(); + + if(!MOVEMENT_PACKETS.contains(packet.getClass())) continue; + + Integer entityID = PacketUtils.getEntityId(packet); + if(entityID == null) continue; + + AdvancedPosition oldPosition = new AdvancedPosition(0, 0, 0, 0, 0); + + List> positions = entityPositions.get(entityID); + + if(positions == null) positions = new ArrayList>(); + if(!positions.isEmpty()) { + oldPosition = positions.get(positions.size() - 1).getRight(); + } + + AdvancedPosition newPosition = getNewPosition(oldPosition, packet); + + if(newPosition != null) positions.add(Pair.of((int) packetData.getTime(), newPosition)); + entityPositions.put(entityID, positions); + } + + loaded = true; + } + + /** + * @param entityID The Entity's ID + * @param timestamp The timestamp + * @return The specified Entity's position at the given timestamp, or null if the entity doesn't exist at that timestamp + * @throws IllegalStateException if the EntityPositionTracker's load() method wasn't called yet + */ + public AdvancedPosition getEntityPositionAtTimestamp(int entityID, int timestamp) { + if(!loaded) throw new IllegalStateException("The EntityPositionTracker hasn't loaded the Replay File yet"); + + List> positions = entityPositions.get(entityID); + if(positions == null || positions.isEmpty()) return null; + + boolean exists = false; + + for(Pair pair : positions) { + if(pair.getKey() <= timestamp) exists = true; + if(pair.getKey() >= timestamp) { + if(exists) return pair.getValue(); + return null; + } + } + + return null; + } + + //@johni0702 - you may consider adding a similar method to the ReplayStudio PacketUtils + private AdvancedPosition getNewPosition(AdvancedPosition previous, Packet packet) { + if(packet instanceof ServerPlayerPositionRotationPacket) { + ServerPlayerPositionRotationPacket ppl = (ServerPlayerPositionRotationPacket)packet; + + double x = previous.getX() + ppl.getX(); + double y = previous.getY() + ppl.getY(); + double z = previous.getZ() + ppl.getZ(); + double yaw = previous.getYaw() + ppl.getYaw(); + double pitch = previous.getPitch() + ppl.getPitch(); + + for(PositionElement relative : ppl.getRelativeElements()) { + if(relative == PositionElement.X) { + x -= previous.getX(); + } else if(relative == PositionElement.Y) { + y -= previous.getY(); + } else if(relative == PositionElement.Z) { + z -= previous.getZ(); + } else if(relative == PositionElement.YAW) { + yaw -= previous.getYaw(); + } else if(relative == PositionElement.PITCH) { + pitch -= previous.getPitch(); + } + } + + return new AdvancedPosition(x, y, z, pitch, yaw); + } + + if(packet instanceof ServerSpawnPlayerPacket) { + ServerSpawnPlayerPacket spp = (ServerSpawnPlayerPacket)packet; + return new AdvancedPosition(spp.getX(), spp.getY(), spp.getZ(), spp.getPitch(), spp.getYaw()); + } + + if(packet instanceof ServerSpawnObjectPacket) { + ServerSpawnObjectPacket spp = (ServerSpawnObjectPacket)packet; + return new AdvancedPosition(spp.getX(), spp.getY(), spp.getZ(), spp.getPitch(), spp.getYaw()); + } + + if(packet instanceof ServerSpawnExpOrbPacket) { + ServerSpawnExpOrbPacket spp = (ServerSpawnExpOrbPacket)packet; + return new AdvancedPosition(spp.getX(), spp.getY(), spp.getZ(), 0, 0); + } + + if(packet instanceof ServerSpawnMobPacket) { + ServerSpawnMobPacket spp = (ServerSpawnMobPacket)packet; + return new AdvancedPosition(spp.getX(), spp.getY(), spp.getZ(), spp.getPitch(), spp.getYaw()); + } + + if(packet instanceof ServerSpawnPaintingPacket) { + ServerSpawnPaintingPacket spp = (ServerSpawnPaintingPacket)packet; + return new AdvancedPosition(spp.getPosition().getX(), spp.getPosition().getY(), spp.getPosition().getZ(), 0, 0); + } + + if(packet instanceof ServerEntityMovementPacket) { + ServerEntityMovementPacket ppl = (ServerEntityMovementPacket)packet; + //why is the rot/pos variable of ServerEntityMovementPacket not accessible? + boolean rot = ppl instanceof ServerEntityPositionRotationPacket; + + double x = previous.getX() + ppl.getMovementX(); + double y = previous.getY() + ppl.getMovementY(); + double z = previous.getZ() + ppl.getMovementZ(); + + double pitch = previous.getPitch(); + double yaw = previous.getYaw(); + + if(rot) { + pitch = ppl.getPitch(); + yaw = ppl.getYaw(); + } + + return new AdvancedPosition(x, y, z, pitch, yaw); + } + + if(packet instanceof ServerEntityTeleportPacket) { + ServerEntityTeleportPacket spp = (ServerEntityTeleportPacket)packet; + return new AdvancedPosition(spp.getX(), spp.getY(), spp.getZ(), spp.getPitch(), spp.getYaw()); + } + + return null; + } +} \ No newline at end of file diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index 70b7ae38..ad642ec3 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -11,6 +11,7 @@ import eu.crushedpixel.replaymod.events.ReplayExitEvent; import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; import eu.crushedpixel.replaymod.holders.*; import eu.crushedpixel.replaymod.interpolation.KeyframeList; +import eu.crushedpixel.replaymod.preparation.EntityPositionTracker; import eu.crushedpixel.replaymod.registry.LightingHandler; import eu.crushedpixel.replaymod.registry.PlayerHandler; import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry; @@ -81,6 +82,12 @@ public class ReplayHandler { */ private static Restrictions restrictions; + /** + * The EntityPositionTracker for the current Replay File. + */ + @Getter + private static EntityPositionTracker entityPositionTracker; + public static KeyframeSet[] getKeyframeRepository() { return keyframeRepository; } @@ -327,6 +334,12 @@ public class ReplayHandler { } public static void startReplay(File file, boolean asyncMode) { + entityPositionTracker = new EntityPositionTracker(file); + try { + entityPositionTracker.load(); + } catch(IOException ioe) { + ioe.printStackTrace(); + } ReplayMod.chatMessageHandler.initialize(); mc.ingameGUI.getChatGUI().clearChatMessages();