Add first person spectator keyframes to simplepathing
This commit is contained in:
Submodule ReplayStudio updated: ef307b4678...4cb8cad6c8
@@ -50,7 +50,9 @@ public class CameraProperties extends AbstractPropertyGroup {
|
||||
|
||||
@Override
|
||||
public void applyToGame(Triple<Double, Double, Double> value, @NonNull Object replayHandler) {
|
||||
((ReplayHandler) replayHandler).getCameraEntity().setCameraPosition(value.getLeft(), value.getMiddle(), value.getRight());
|
||||
ReplayHandler handler = ((ReplayHandler) replayHandler);
|
||||
handler.spectateCamera();
|
||||
handler.getCameraEntity().setCameraPosition(value.getLeft(), value.getMiddle(), value.getRight());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,7 +88,9 @@ public class CameraProperties extends AbstractPropertyGroup {
|
||||
|
||||
@Override
|
||||
public void applyToGame(Triple<Float, Float, Float> value, @NonNull Object replayHandler) {
|
||||
((ReplayHandler) replayHandler).getCameraEntity().setCameraRotation(value.getLeft(), value.getMiddle(), value.getRight());
|
||||
ReplayHandler handler = ((ReplayHandler) replayHandler);
|
||||
handler.spectateCamera();
|
||||
handler.getCameraEntity().setCameraRotation(value.getLeft(), value.getMiddle(), value.getRight());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.replaymod.pathing.properties;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import com.replaymod.replaystudio.pathing.property.AbstractProperty;
|
||||
import com.replaymod.replaystudio.pathing.property.PropertyPart;
|
||||
import com.replaymod.replaystudio.pathing.property.PropertyParts;
|
||||
import lombok.NonNull;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Property for the camera spectating an entity.
|
||||
*/
|
||||
public class SpectatorProperty extends AbstractProperty<Integer> {
|
||||
public static final SpectatorProperty PROPERTY = new SpectatorProperty();
|
||||
public final PropertyPart<Integer> ENTITY_ID = new PropertyParts.ForInteger(this, false);
|
||||
private SpectatorProperty() {
|
||||
super("spectate", "replaymod.gui.playeroverview.spectate", null, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PropertyPart<Integer>> getParts() {
|
||||
return Collections.singletonList(ENTITY_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToGame(Integer value, @NonNull Object replayHandler) {
|
||||
ReplayHandler handler = ((ReplayHandler) replayHandler);
|
||||
World world = handler.getCameraEntity().getEntityWorld();
|
||||
// Lookup entity by id, returns null if an entity with the id does not exists
|
||||
Entity target = world.getEntityByID(value);
|
||||
// Spectate entity, when called with null, returns to camera
|
||||
handler.spectateEntity(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Integer value) throws IOException {
|
||||
writer.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextInt();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.SpectatorProperty;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.replay.events.ReplayOpenEvent;
|
||||
import com.replaymod.replaystudio.pathing.PathingRegistry;
|
||||
@@ -79,6 +80,7 @@ public class ReplayModSimplePathing implements PathingRegistry {
|
||||
timeline.registerProperty(TimestampProperty.PROPERTY);
|
||||
timeline.registerProperty(CameraProperties.POSITION);
|
||||
timeline.registerProperty(CameraProperties.ROTATION);
|
||||
timeline.registerProperty(SpectatorProperty.PROPERTY);
|
||||
|
||||
return timeline;
|
||||
}
|
||||
@@ -107,4 +109,8 @@ public class ReplayModSimplePathing implements PathingRegistry {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public ReplayMod getCore() {
|
||||
return core;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.SpectatorProperty;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.replaystudio.pathing.path.Keyframe;
|
||||
import com.replaymod.replaystudio.pathing.path.Path;
|
||||
import com.replaymod.replaystudio.pathing.path.PathSegment;
|
||||
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.element.advanced.AbstractGuiTimeline;
|
||||
@@ -53,6 +55,9 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
|
||||
? KEYFRAME_SIZE : 0);
|
||||
int v = KEYFRAME_TEXTURE_Y;
|
||||
if (keyframe.getValue(CameraProperties.POSITION).isPresent()) {
|
||||
if (keyframe.getValue(SpectatorProperty.PROPERTY).isPresent()) {
|
||||
v += 2 * KEYFRAME_SIZE;
|
||||
}
|
||||
renderer.drawTexturedRect(positonX, BORDER_TOP, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
|
||||
}
|
||||
if (keyframe.getValue(TimestampProperty.PROPERTY).isPresent()) {
|
||||
@@ -62,6 +67,27 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
|
||||
}
|
||||
}
|
||||
|
||||
// Draw colored quads on spectator path segments
|
||||
for (PathSegment segment : mod.getCurrentTimeline().getPaths().get(GuiPathing.POSITION_PATH).getSegments()) {
|
||||
if (segment.getInterpolator() == null
|
||||
|| !segment.getInterpolator().getKeyframeProperties().contains(SpectatorProperty.PROPERTY)) {
|
||||
continue; // Not a spectator segment
|
||||
}
|
||||
long startFrameTime = segment.getStartKeyframe().getTime();
|
||||
long endFrameTime = segment.getEndKeyframe().getTime();
|
||||
if (startFrameTime >= endTime || endFrameTime <= startTime) {
|
||||
continue; // Segment out of display range
|
||||
}
|
||||
|
||||
double relativeStart = startFrameTime - startTime;
|
||||
double relativeEnd = endFrameTime - startTime;
|
||||
int startX = BORDER_LEFT + Math.max(0, (int) (relativeStart / visibleTime * visibleWidth) + KEYFRAME_SIZE / 2 + 1);
|
||||
int endX = BORDER_LEFT + Math.min(visibleWidth, (int) (relativeEnd / visibleTime * visibleWidth) - KEYFRAME_SIZE / 2);
|
||||
if (startX < endX) {
|
||||
renderer.drawRect(startX + 1, BORDER_TOP + 1, endX - startX - 2, KEYFRAME_SIZE - 2, 0xFF0088FF);
|
||||
}
|
||||
}
|
||||
|
||||
super.drawTimelineCursor(renderer, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.gui.GuiKeyframeRepository;
|
||||
import com.replaymod.pathing.player.RealtimeTimelinePlayer;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.SpectatorProperty;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.render.gui.GuiRenderSettings;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
@@ -19,18 +20,26 @@ import com.replaymod.replaystudio.pathing.path.Keyframe;
|
||||
import com.replaymod.replaystudio.pathing.path.Path;
|
||||
import com.replaymod.replaystudio.pathing.path.PathSegment;
|
||||
import com.replaymod.replaystudio.pathing.path.Timeline;
|
||||
import com.replaymod.replaystudio.util.EntityPositionTracker;
|
||||
import com.replaymod.replaystudio.util.Location;
|
||||
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||
import de.johni0702.minecraft.gui.element.*;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime;
|
||||
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraftforge.fml.common.Loader;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
@@ -38,6 +47,9 @@ import org.lwjgl.util.ReadablePoint;
|
||||
import org.lwjgl.util.WritablePoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
@@ -47,6 +59,8 @@ public class GuiPathing {
|
||||
public static final int TIME_PATH = 0;
|
||||
public static final int POSITION_PATH = 1;
|
||||
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20)
|
||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
|
||||
|
||||
@@ -174,6 +188,8 @@ public class GuiPathing {
|
||||
private final ReplayHandler replayHandler;
|
||||
private final RealtimeTimelinePlayer player;
|
||||
|
||||
private EntityPositionTracker entityTracker;
|
||||
|
||||
public GuiPathing(final ReplayMod core, final ReplayModSimplePathing mod, final ReplayHandler replayHandler) {
|
||||
this.mod = mod;
|
||||
this.replayHandler = replayHandler;
|
||||
@@ -315,6 +331,11 @@ public class GuiPathing {
|
||||
* @param isTime {@code true} for the time property button, {@code false} for the place property button
|
||||
*/
|
||||
private void updateKeyframe(final boolean isTime) {
|
||||
if (entityTracker == null) {
|
||||
loadEntityTracker(() -> updateKeyframe(isTime));
|
||||
return;
|
||||
}
|
||||
|
||||
int time = timeline.getCursorPosition();
|
||||
Timeline timeline = mod.getCurrentTimeline();
|
||||
Path path = timeline.getPaths().get(isTime ? TIME_PATH : POSITION_PATH);
|
||||
@@ -339,31 +360,64 @@ public class GuiPathing {
|
||||
CameraEntity camera = replayHandler.getCameraEntity();
|
||||
builder.setValue(CameraProperties.POSITION, Triple.of(camera.posX, camera.posY, camera.posZ));
|
||||
builder.setValue(CameraProperties.ROTATION, Triple.of(camera.rotationYaw, camera.rotationPitch, camera.roll));
|
||||
if (!replayHandler.isCameraView()) {
|
||||
Entity spectated = replayHandler.getOverlay().getMinecraft().getRenderViewEntity();
|
||||
builder.setValue(SpectatorProperty.PROPERTY, spectated.getEntityId());
|
||||
}
|
||||
}
|
||||
UpdateKeyframeProperties updateChange = builder.done();
|
||||
updateChange.apply(timeline);
|
||||
// If this new keyframe formed the first segment, then create and set the appropriate interpolator
|
||||
if (path.getSegments().size() == 1) {
|
||||
change = CombinedChange.createFromApplied(change, updateChange);
|
||||
|
||||
// If this new keyframe formed the first segment of the time path
|
||||
if (isTime && path.getSegments().size() == 1) {
|
||||
PathSegment segment = path.getSegments().iterator().next();
|
||||
Interpolator interpolator;
|
||||
if (isTime) {
|
||||
interpolator = new LinearInterpolator();
|
||||
interpolator.registerProperty(TimestampProperty.PROPERTY);
|
||||
} else {
|
||||
interpolator = new CubicSplineInterpolator();
|
||||
interpolator.registerProperty(CameraProperties.POSITION);
|
||||
interpolator.registerProperty(CameraProperties.ROTATION);
|
||||
}
|
||||
Interpolator interpolator = new LinearInterpolator();
|
||||
interpolator.registerProperty(TimestampProperty.PROPERTY);
|
||||
SetInterpolator setInterpolator = SetInterpolator.create(segment, interpolator);
|
||||
setInterpolator.apply(timeline);
|
||||
timeline.pushChange(CombinedChange.createFromApplied(change, updateChange, setInterpolator));
|
||||
} else {
|
||||
timeline.pushChange(CombinedChange.createFromApplied(change, updateChange));
|
||||
change = CombinedChange.createFromApplied(change, setInterpolator);
|
||||
}
|
||||
} else {
|
||||
timeline.pushChange(change);
|
||||
}
|
||||
|
||||
// Update interpolators for spectator keyframes
|
||||
// while this is overkill, it is far simpler than updating differently for every possible case
|
||||
if (!isTime) {
|
||||
Interpolator interpolator = null;
|
||||
boolean isSpectatorInterpolator = false;
|
||||
for (PathSegment segment : path.getSegments()) {
|
||||
if (segment.getStartKeyframe().getValue(SpectatorProperty.PROPERTY).isPresent()
|
||||
&& segment.getEndKeyframe().getValue(SpectatorProperty.PROPERTY).isPresent()) {
|
||||
// Spectator segment
|
||||
if (!isSpectatorInterpolator) {
|
||||
isSpectatorInterpolator = true;
|
||||
interpolator = new LinearInterpolator();
|
||||
interpolator.registerProperty(SpectatorProperty.PROPERTY);
|
||||
}
|
||||
SetInterpolator setInterpolator = SetInterpolator.create(segment, interpolator);
|
||||
setInterpolator.apply(timeline);
|
||||
change = CombinedChange.createFromApplied(change, setInterpolator);
|
||||
} else {
|
||||
// Normal segment
|
||||
if (isSpectatorInterpolator || interpolator == null) {
|
||||
isSpectatorInterpolator = false;
|
||||
interpolator = new CubicSplineInterpolator();
|
||||
interpolator.registerProperty(CameraProperties.POSITION);
|
||||
interpolator.registerProperty(CameraProperties.ROTATION);
|
||||
}
|
||||
SetInterpolator setInterpolator = SetInterpolator.create(segment, interpolator);
|
||||
setInterpolator.apply(timeline);
|
||||
change = CombinedChange.createFromApplied(change, setInterpolator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Change specPosUpdate = updateSpectatorPositions();
|
||||
specPosUpdate.apply(timeline);
|
||||
change = CombinedChange.createFromApplied(change, specPosUpdate);
|
||||
|
||||
timeline.pushChange(change);
|
||||
|
||||
if (isTime) {
|
||||
mod.setSelectedTimeKeyframe(keyframe);
|
||||
} else {
|
||||
@@ -371,7 +425,78 @@ public class GuiPathing {
|
||||
}
|
||||
}
|
||||
|
||||
private Change updateSpectatorPositions() {
|
||||
List<Change> changes = new ArrayList<>();
|
||||
Path positionPath = mod.getCurrentTimeline().getPaths().get(POSITION_PATH);
|
||||
Path timePath = mod.getCurrentTimeline().getPaths().get(TIME_PATH);
|
||||
timePath.updateAll();
|
||||
for (Keyframe keyframe : positionPath.getKeyframes()) {
|
||||
Optional<Integer> spectator = keyframe.getValue(SpectatorProperty.PROPERTY);
|
||||
if (spectator.isPresent()) {
|
||||
Optional<Integer> time = timePath.getValue(TimestampProperty.PROPERTY, keyframe.getTime());
|
||||
if (!time.isPresent()) {
|
||||
continue; // No time keyframes set at this video time, cannot determine replay time
|
||||
}
|
||||
Location expected = entityTracker.getEntityPositionAtTimestamp(spectator.get(), time.get());
|
||||
if (expected == null) {
|
||||
continue; // We don't have any data on this entity for some reason
|
||||
}
|
||||
Triple<Double, Double, Double> pos = keyframe.getValue(CameraProperties.POSITION).orElse(Triple.of(0D, 0D, 0D));
|
||||
Triple<Float, Float, Float> rot = keyframe.getValue(CameraProperties.ROTATION).orElse(Triple.of(0F, 0F, 0F));
|
||||
Location actual = new Location(pos.getLeft(), pos.getMiddle(), pos.getRight(), rot.getLeft(), rot.getRight());
|
||||
if (!expected.equals(actual)) {
|
||||
changes.add(UpdateKeyframeProperties.create(positionPath, keyframe)
|
||||
.setValue(CameraProperties.POSITION, Triple.of(expected.getX(), expected.getY(), expected.getZ()))
|
||||
.setValue(CameraProperties.ROTATION, Triple.of(expected.getYaw(), expected.getPitch(), 0f)).done()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return CombinedChange.create(changes.toArray(new Change[changes.size()]));
|
||||
}
|
||||
|
||||
private void loadEntityTracker(Runnable runnable) {
|
||||
LoadEntityTrackerPopup popup = new LoadEntityTrackerPopup(replayHandler.getOverlay());
|
||||
new Thread(() -> {
|
||||
EntityPositionTracker tracker = new EntityPositionTracker(replayHandler.getReplayFile());
|
||||
try {
|
||||
tracker.load(c -> popup.progressBar.setProgress((float)(double) c));
|
||||
} catch (IOException e) {
|
||||
logger.error("Loading entity tracker:", e);
|
||||
mod.getCore().runLater(() -> {
|
||||
mod.getCore().printWarningToChat("Error loading entity tracker: %s", e.getLocalizedMessage());
|
||||
popup.close();
|
||||
});
|
||||
}
|
||||
entityTracker = tracker;
|
||||
mod.getCore().runLater(() -> {
|
||||
popup.close();
|
||||
runnable.run();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
public ReplayModSimplePathing getMod() {
|
||||
return mod;
|
||||
}
|
||||
|
||||
private class LoadEntityTrackerPopup extends AbstractGuiPopup<LoadEntityTrackerPopup> {
|
||||
private final GuiProgressBar progressBar = new GuiProgressBar(popup).setSize(300, 20)
|
||||
.setI18nLabel("replaymod.gui.loadentitytracker");
|
||||
|
||||
public LoadEntityTrackerPopup(GuiContainer container) {
|
||||
super(container);
|
||||
open();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LoadEntityTrackerPopup getThis() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.preparation;
|
||||
|
||||
import com.replaymod.replaystudio.PacketData;
|
||||
import com.replaymod.replaystudio.io.ReplayInputStream;
|
||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||
import com.replaymod.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.<br>
|
||||
* <br>
|
||||
* While this significantly increases the Replay loading time, it's the only way to:<br>
|
||||
* 1) Properly preview the Camera Path when an Entity is spectated<br>
|
||||
* 2) Calculate a smooth Path from an Entity's Shoulder Cam perspective
|
||||
*/
|
||||
public class EntityPositionTracker {
|
||||
|
||||
private static final List<Class> MOVEMENT_PACKETS = new ArrayList<Class>() {
|
||||
{
|
||||
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<Integer, List<Pair<Integer, AdvancedPosition>>> entityPositions;
|
||||
|
||||
public EntityPositionTracker(File replayFile) {
|
||||
this.replayFile = replayFile;
|
||||
this.entityPositions = new HashMap<Integer, List<Pair<Integer, AdvancedPosition>>>();
|
||||
}
|
||||
|
||||
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<Pair<Integer, AdvancedPosition>> positions = entityPositions.get(entityID);
|
||||
|
||||
if(positions == null) positions = new ArrayList<Pair<Integer, AdvancedPosition>>();
|
||||
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<Pair<Integer, AdvancedPosition>> positions = entityPositions.get(entityID);
|
||||
if(positions == null || positions.isEmpty()) return null;
|
||||
|
||||
boolean exists = false;
|
||||
|
||||
for(Pair<Integer, AdvancedPosition> pair : positions) {
|
||||
if(pair.getKey() <= timestamp) exists = true;
|
||||
if(pair.getKey() >= timestamp) {
|
||||
if(exists) return pair.getValue().copy();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,8 @@ replaymod.gui.restorereplay1=It seems like Minecraft has not quit normally.
|
||||
replaymod.gui.restorereplay2=The Replay "%1$s" was not saved correctly.
|
||||
replaymod.gui.restorereplay3=Do you wish to recover it?
|
||||
|
||||
replaymod.gui.loadentitytracker=Loading entity positions: %%d%%%%
|
||||
|
||||
#Only change these if it's neccessary
|
||||
replaymod.gui.replayviewer=Replay Viewer
|
||||
replaymod.gui.replaycenter=Replay Center
|
||||
|
||||
Reference in New Issue
Block a user