Warning, GIGANTIC COMMIT!

Replaced Keyframe subclasses with Generic Types of Keyframe and replaced all instanceof calls
Replaced implementations of Linear and Spline interpolation to interpolate any Object that extends KeyframeValue and contains at least one public double Field. I'll need this for Keyframe interpolation in CustomImageObject Transformations
Made MarkerKeyframe *no* subclass of Keyframe to avoid conflicts with PositionKeyframe in instanceof checks for Keyframe#getValue
Created KeyframeList which extends ArrayList to provide some helping functions which DRY up the ReplayHandler
Split up ReplayHandler's keyframeList into timeKeyframeList and positionKeyframeList
This commit is contained in:
CrushedPixel
2015-07-08 04:13:24 +02:00
parent 89cc6938f8
commit 5100b63964
35 changed files with 765 additions and 1117 deletions

View File

@@ -101,4 +101,8 @@ public class AssetRepository {
return new ArrayList<ReplayAsset>(replayAssets.values());
}
public ReplayAsset getAssetByUUID(UUID uuid) {
return replayAssets.get(uuid);
}
}

View File

@@ -1,37 +1,44 @@
package eu.crushedpixel.replaymod.holders;
package eu.crushedpixel.replaymod.assets;
import eu.crushedpixel.replaymod.holders.*;
import eu.crushedpixel.replaymod.interpolation.GenericSplineInterpolation;
import eu.crushedpixel.replaymod.interpolation.KeyframeList;
import eu.crushedpixel.replaymod.registry.ResourceHelper;
import eu.crushedpixel.replaymod.utils.RoundUtils;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.util.ResourceLocation;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
public class CustomImageObject implements GuiEntryListEntry {
public CustomImageObject(Position position, String name, final File imageSource) throws IOException {
this.position = new ExtendedPosition(RoundUtils.round(position.getX()), RoundUtils.round(position.getY()), RoundUtils.round(position.getZ()), 0, 0);
public CustomImageObject(Transformations transformations, String name, UUID assetUUID) throws IOException {
this.name = name;
setImageFile(imageSource);
setLinkedAsset(assetUUID);
}
@Getter @Setter private ExtendedPosition position;
@Getter @Setter private String name;
@Getter private File imageFile;
@Getter private UUID linkedAsset;
public void setImageFile(final File imageSource) throws IOException {
this.imageFile = imageSource;
@Getter @Setter private float width, height;
final BufferedImage bufferedImage = ImageIO.read(imageSource);
public void setLinkedAsset(UUID assetUUID) throws IOException {
ReplayAsset asset = ReplayHandler.getAssetRepository().getAssetByUUID(assetUUID);
if(asset instanceof ReplayImageAsset) {
setImage(((ReplayImageAsset)asset).getObject());
} else {
throw new UnsupportedOperationException("A CustomImageObject requires a ReplayImageAsset");
}
}
public void setImage(final BufferedImage bufferedImage) throws IOException {
this.textureWidth = bufferedImage.getWidth();
this.textureHeight = bufferedImage.getHeight();
@@ -47,13 +54,13 @@ public class CustomImageObject implements GuiEntryListEntry {
h = 1;
}
this.position.setWidth(w);
this.position.setHeight(h);
this.setWidth(w);
this.setHeight(h);
Minecraft.getMinecraft().addScheduledTask(new Runnable() {
@Override
public void run() {
resourceLocation = new ResourceLocation("customImages/"+imageSource.getAbsolutePath());
resourceLocation = new ResourceLocation("customImages/"+linkedAsset.toString());
dynamicTexture = new DynamicTexture(bufferedImage);
}
});
@@ -78,4 +85,21 @@ public class CustomImageObject implements GuiEntryListEntry {
public String getDisplayString() {
return name;
}
/**
* Keyframing Code
*/
private KeyframeList<Keyframe<Position>> anchorPointKeyframes, positionKeyframes, orientationKeyframes;
private KeyframeList<Keyframe<Point>> scaleKeyframes;
private KeyframeList<Keyframe<TimestampValue>> opacityKeyframes;
private GenericSplineInterpolation<Position> anchorSpline, positionSpline, orientationSpline;
private GenericSplineInterpolation<Point> scaleSpline;
private GenericSplineInterpolation<TimestampValue> opacitySpline;
public Transformations getTransformationsForTimestamp(int timestamp) {
return null; //TODO
}
}

View File

@@ -160,8 +160,8 @@ public class CameraEntity extends EntityPlayer {
public void moveAbsolute(Position pos) {
this.moveAbsolute(pos.getX(), pos.getY(), pos.getZ());
rotationPitch = pos.getPitch();
rotationYaw = pos.getYaw();
rotationPitch = (float)pos.getPitch();
rotationYaw = (float)pos.getYaw();
}
public void moveAbsolute(double x, double y, double z) {
@@ -181,8 +181,8 @@ public class CameraEntity extends EntityPlayer {
}
public void movePath(Position pos) {
this.prevRotationPitch = this.rotationPitch = pos.getPitch();
this.prevRotationYaw = this.rotationYaw = pos.getYaw();
this.prevRotationPitch = this.rotationPitch = (float)pos.getPitch();
this.prevRotationYaw = this.rotationYaw = (float)pos.getYaw();
this.lastTickPosX = this.prevPosX = this.posX = pos.getX();
this.lastTickPosY = this.prevPosY = this.posY = pos.getY();
this.lastTickPosZ = this.prevPosZ = this.posZ = pos.getZ();

View File

@@ -1,15 +1,18 @@
package eu.crushedpixel.replaymod.events;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.interpolation.KeyframeList;
import lombok.AllArgsConstructor;
import lombok.Data;
import net.minecraftforge.fml.common.eventhandler.Event;
import java.util.List;
@Data
@AllArgsConstructor
public class KeyframesModifyEvent extends Event {
public List<Keyframe> keyframes;
private KeyframeList<Keyframe<Position>> positionKeyframes;
private KeyframeList<Keyframe<TimestampValue>> timeKeyframes;
public KeyframesModifyEvent(List<Keyframe> keyframes) {
this.keyframes = keyframes;
}
}

View File

@@ -1,264 +0,0 @@
package eu.crushedpixel.replaymod.gui;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.gui.elements.*;
import eu.crushedpixel.replaymod.gui.elements.listeners.FileChooseListener;
import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener;
import eu.crushedpixel.replaymod.holders.CustomImageObject;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.resources.I18n;
import javax.imageio.ImageIO;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class GuiAssetAdder extends GuiScreen {
private String screenTitle;
private boolean initialized = false;
private GuiEntryList<CustomImageObject> objectList;
private GuiButton removeButton;
private GuiFileChooser addButton;
private GuiFileChooser guiFileChooser;
private GuiAdvancedTextField nameInput;
private GuiNumberInput xInput, yInput, zInput, yawInput, pitchInput, rollInput, scaleInput, opacityInput;
private List<GuiTextField> textFields = new ArrayList<GuiTextField>();
public GuiAssetAdder() {
ReplayMod.replaySender.setReplaySpeed(0);
}
@Override
public void initGui() {
if(!initialized) {
screenTitle = I18n.format("replaymod.gui.assets.title");
objectList = new GuiEntryList<CustomImageObject>(fontRendererObj, 0, 0, 0, 0);
objectList.setEmptyMessage(I18n.format("replaymod.gui.assets.emptylist"));
for(CustomImageObject object : ReplayHandler.getCustomImageObjects()) {
objectList.addElement(object);
}
removeButton = new GuiButton(GuiConstants.ASSET_ADDER_REMOVE_BUTTON, 0, 0, I18n.format("replaymod.gui.remove"));
addButton = new GuiFileChooser(1, 0, 0, I18n.format("replaymod.gui.add"), null, ImageIO.getReaderFileSuffixes()) {
@Override protected void updateDisplayString() {}
};
addButton.addFileChooseListener(new FileChooseListener() {
@Override
public void onFileChosen(File file) {
try {
objectList.addElement(new CustomImageObject(new Position(mc.getRenderViewEntity()),
I18n.format("replaymod.gui.assets.defaultname"), file));
} catch(IOException e) {
e.printStackTrace();
}
}
});
nameInput = new GuiAdvancedTextField(fontRendererObj, 2, 0, 0, 20);
nameInput.hint = I18n.format("replaymod.gui.assets.namehint");
xInput = new GuiNumberInput(3, fontRendererObj, 0, 0, 0, null, null, 0d, true);
yInput = new GuiNumberInput(4, fontRendererObj, 0, 0, 0, null, null, 0d, true);
zInput = new GuiNumberInput(5, fontRendererObj, 0, 0, 0, null, null, 0d, true);
yawInput = new GuiNumberInput(6, fontRendererObj, 0, 0, 0, -360d, 360d, 0d, true);
pitchInput = new GuiNumberInput(7, fontRendererObj, 0, 0, 0, -360d, 360d, 0d, true);
rollInput = new GuiNumberInput(8, fontRendererObj, 0, 0, 0, -360d, 360d, 0d, true);
scaleInput = new GuiNumberInputWithText(9, fontRendererObj, 0, 0, 0, 0d, 10000d, 100d, true, "%");
opacityInput = new GuiNumberInputWithText(10, fontRendererObj, 0, 0, 0, 0d, 100d, 100d, true, "%");
guiFileChooser = new GuiFileChooser(3, 0, 0, I18n.format("replaymod.gui.assets.filechooser")+": ", null, ImageIO.getReaderFileSuffixes());
guiFileChooser.addFileChooseListener(new FileChooseListener() {
@Override
public void onFileChosen(File file) {
try {
objectList.getElement(objectList.getSelectionIndex()).setImageFile(file);
} catch(Exception e) {
e.printStackTrace();
}
}
});
guiFileChooser.enabled = false;
textFields.add(objectList);
textFields.add(nameInput);
textFields.add(xInput);
textFields.add(yInput);
textFields.add(zInput);
textFields.add(pitchInput);
textFields.add(yawInput);
textFields.add(rollInput);
textFields.add(scaleInput);
textFields.add(opacityInput);
objectList.addSelectionListener(new SelectionListener() {
@Override
public void onSelectionChanged(int selectionIndex) {
CustomImageObject object = objectList.getElement(selectionIndex);
if(object == null) {
guiFileChooser.enabled = false;
for(GuiTextField tf : textFields) {
tf.setEnabled(false);
}
return;
}
guiFileChooser.enabled = true;
for(GuiTextField tf : textFields) {
tf.setEnabled(true);
}
nameInput.setText(object.getName());
guiFileChooser.setSelectedFile(object.getImageFile());
xInput.setValue(object.getPosition().getX());
yInput.setValue(object.getPosition().getY());
zInput.setValue(object.getPosition().getZ());
pitchInput.setValue(object.getPosition().getPitch());
yawInput.setValue(object.getPosition().getYaw());
rollInput.setValue(object.getPosition().getRoll());
scaleInput.setValue(object.getPosition().getScale()*100);
opacityInput.setValue(object.getPosition().getOpacity()*100);
}
});
if(objectList.getEntryCount() > 0) {
objectList.setSelectionIndex(0);
}
}
int h = (int)Math.floor(((double)this.height-(45+20+15+20))/14);
objectList.width = guiFileChooser.width = nameInput.width = 150;
objectList.xPosition = width/2 - objectList.width - 5;
objectList.setVisibleElements(h);
objectList.yPosition = 45;
addButton.width = removeButton.width = 75;
addButton.xPosition = objectList.xPosition;
removeButton.xPosition = addButton.xPosition+addButton.width;
addButton.yPosition = removeButton.yPosition = objectList.yPosition + objectList.height + 2;
nameInput.xPosition = this.width/2 + 5;
nameInput.yPosition = 45;
guiFileChooser.xPosition = this.width/2 + 5;
guiFileChooser.yPosition = nameInput.yPosition + 20 + 5;
xInput.yPosition = yInput.yPosition = zInput.yPosition = guiFileChooser.yPosition + 20 + 5 + 15;
yawInput.yPosition = pitchInput.yPosition = rollInput.yPosition = xInput.yPosition + 20 + 5 + 15;
scaleInput.yPosition = opacityInput.yPosition = yawInput.yPosition + 20 + 5 + 15;
xInput.xPosition = scaleInput.xPosition = pitchInput.xPosition = this.width/2 + 8;
yInput.xPosition = yawInput.xPosition = xInput.xPosition + 50;
zInput.xPosition = rollInput.xPosition = yInput.xPosition + 50;
opacityInput.xPosition = scaleInput.xPosition + 75;
xInput.width = yInput.width = zInput.width = yawInput.width = pitchInput.width = rollInput.width = 43;
scaleInput.width = opacityInput.width = 68;
buttonList.add(removeButton);
buttonList.add(addButton);
buttonList.add(guiFileChooser);
initialized = true;
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
this.drawCenteredString(fontRendererObj, screenTitle, this.width / 2, 5, Color.WHITE.getRGB());
int leftBorder = 10;
int topBorder = 20;
drawGradientRect(leftBorder, topBorder, width - leftBorder, this.height - 10, -1072689136, -804253680);
for(GuiTextField textField : textFields) {
textField.drawTextBox();
}
drawCenteredString(fontRendererObj, "X", xInput.xPosition + (xInput.width/2), xInput.yPosition - 12, Color.WHITE.getRGB());
drawCenteredString(fontRendererObj, "Y", yInput.xPosition + (yInput.width/2), yInput.yPosition - 12, Color.WHITE.getRGB());
drawCenteredString(fontRendererObj, "Z", zInput.xPosition + (zInput.width/2), zInput.yPosition - 12, Color.WHITE.getRGB());
drawCenteredString(fontRendererObj, "Yaw", yawInput.xPosition + (yawInput.width/2), yawInput.yPosition - 12, Color.WHITE.getRGB());
drawCenteredString(fontRendererObj, "Pitch", pitchInput.xPosition + (pitchInput.width/2), pitchInput.yPosition - 12, Color.WHITE.getRGB());
drawCenteredString(fontRendererObj, "Roll", rollInput.xPosition + (rollInput.width/2), rollInput.yPosition - 12, Color.WHITE.getRGB());
drawCenteredString(fontRendererObj, "Scale", scaleInput.xPosition + (scaleInput.width/2), scaleInput.yPosition - 12, Color.WHITE.getRGB());
drawCenteredString(fontRendererObj, "Opacity", opacityInput.xPosition + (opacityInput.width/2), opacityInput.yPosition - 12, Color.WHITE.getRGB());
super.drawScreen(mouseX, mouseY, partialTicks);
}
@Override
public void updateScreen() {
for(GuiTextField textField : textFields) {
textField.updateCursorCounter();
}
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
for(GuiTextField textField : textFields) {
textField.mouseClicked(mouseX, mouseY, mouseButton);
}
super.mouseClicked(mouseX, mouseY, mouseButton);
}
@Override
protected void keyTyped(char typedChar, int keyCode) throws IOException {
for(GuiTextField textField : textFields) {
textField.textboxKeyTyped(typedChar, keyCode);
}
super.keyTyped(typedChar, keyCode);
if(guiFileChooser.enabled) {
CustomImageObject current = objectList.getElement(objectList.getSelectionIndex());
if(current == null) return;
current.setName(nameInput.getText().trim());
current.getPosition().setX(xInput.getPreciseValue());
current.getPosition().setY(yInput.getPreciseValue());
current.getPosition().setZ(zInput.getPreciseValue());
current.getPosition().setPitch((float) pitchInput.getPreciseValue());
current.getPosition().setYaw((float) yawInput.getPreciseValue());
current.getPosition().setRoll((float) rollInput.getPreciseValue());
current.getPosition().setScale((float) scaleInput.getPreciseValue() / 100f);
current.getPosition().setOpacity((float) opacityInput.getPreciseValue() / 100f);
}
}
@Override
public void onGuiClosed() {
ReplayHandler.setCustomImageObjects(objectList.getCopyOfElements());
}
@Override
protected void actionPerformed(GuiButton button) throws IOException {
if(!button.enabled) return;
if(button.id == GuiConstants.ASSET_ADDER_REMOVE_BUTTON) {
CustomImageObject current = objectList.getElement(objectList.getSelectionIndex());
if(current == null) return;
objectList.removeElement(objectList.getSelectionIndex());
}
}
}

View File

@@ -1,11 +1,12 @@
package eu.crushedpixel.replaymod.gui;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.events.KeyframesModifyEvent;
import eu.crushedpixel.replaymod.gui.elements.GuiAdvancedTextField;
import eu.crushedpixel.replaymod.gui.elements.GuiArrowButton;
import eu.crushedpixel.replaymod.gui.elements.GuiNumberInput;
import eu.crushedpixel.replaymod.holders.*;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.interpolation.KeyframeList;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import eu.crushedpixel.replaymod.utils.RoundUtils;
import eu.crushedpixel.replaymod.utils.TimestampUtils;
@@ -13,7 +14,6 @@ import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.FMLCommonHandler;
import org.lwjgl.input.Keyboard;
import java.awt.*;
@@ -31,8 +31,6 @@ public class GuiEditKeyframe extends GuiScreen {
private GuiNumberInput xCoord, yCoord, zCoord, pitch, yaw, roll;
private GuiNumberInput min, sec, ms;
private GuiAdvancedTextField markerNameInput;
private List<GuiTextField> inputs = new ArrayList<GuiTextField>();
private List<GuiTextField> posInputs = new ArrayList<GuiTextField>();
@@ -43,7 +41,6 @@ public class GuiEditKeyframe extends GuiScreen {
private Keyframe keyframeBackup;
private boolean save;
private boolean posKeyframe;
private boolean markerKeyframe;
private Keyframe previous, next;
@@ -57,27 +54,24 @@ public class GuiEditKeyframe extends GuiScreen {
public GuiEditKeyframe(Keyframe keyframe) {
this.keyframe = keyframe;
this.keyframeBackup = keyframe.copy();
this.posKeyframe = keyframe instanceof PositionKeyframe;
boolean timeKeyframe = keyframe instanceof TimeKeyframe;
this.markerKeyframe = keyframe instanceof MarkerKeyframe;
this.posKeyframe = keyframe.getValue() instanceof Position;
boolean timeKeyframe = keyframe.getValue() instanceof TimestampValue;
ReplayHandler.selectKeyframe(null);
KeyframeList<Keyframe<Position>> positionKeyframes = ReplayHandler.getPositionKeyframes();
KeyframeList<Keyframe<TimestampValue>> timeKeyframes = ReplayHandler.getTimeKeyframes();
if(posKeyframe) {
previous = ReplayHandler.getPreviousPositionKeyframe(keyframe.getRealTimestamp() - 1);
next = ReplayHandler.getNextPositionKeyframe(keyframe.getRealTimestamp() + 1);
previous = positionKeyframes.getPreviousKeyframe(keyframe.getRealTimestamp() - 1);
next = positionKeyframes.getNextKeyframe(keyframe.getRealTimestamp() + 1);
screenTitle = I18n.format("replaymod.gui.editkeyframe.title.pos");
} else if(timeKeyframe) {
previous = ReplayHandler.getPreviousTimeKeyframe(keyframe.getRealTimestamp() - 1);
next = ReplayHandler.getNextTimeKeyframe(keyframe.getRealTimestamp() + 1);
previous = timeKeyframes.getPreviousKeyframe(keyframe.getRealTimestamp() - 1);
next = timeKeyframes.getNextKeyframe(keyframe.getRealTimestamp() + 1);
screenTitle = I18n.format("replaymod.gui.editkeyframe.title.time");
} else if(markerKeyframe) {
previous = ReplayHandler.getPreviousMarkerKeyframe(keyframe.getRealTimestamp() - 1);
next = ReplayHandler.getNextMarkerKeyframe(keyframe.getRealTimestamp() + 1);
screenTitle = I18n.format("replaymod.gui.editkeyframe.title.marker");
}
ReplayHandler.selectKeyframe(keyframe);
@@ -113,7 +107,7 @@ public class GuiEditKeyframe extends GuiScreen {
//Position/Virtual Time Input
if(posKeyframe) {
Position pos = ((PositionKeyframe)keyframe).getPosition();
Position pos = ((Keyframe<Position>)keyframe).getValue();
xCoord = new GuiNumberInput(GuiConstants.KEYFRAME_EDITOR_X_INPUT, fontRendererObj, 0, 0, 100, null, null, RoundUtils.round(pos.getX()), true);
yCoord = new GuiNumberInput(GuiConstants.KEYFRAME_EDITOR_Y_INPUT, fontRendererObj, 0, 0, 100, null, null, RoundUtils.round(pos.getY()), true);
zCoord = new GuiNumberInput(GuiConstants.KEYFRAME_EDITOR_Z_INPUT, fontRendererObj, 0, 0, 100, null, null, RoundUtils.round(pos.getZ()), true);
@@ -129,11 +123,6 @@ public class GuiEditKeyframe extends GuiScreen {
posInputs.add(roll);
inputs.addAll(posInputs);
} else if(markerKeyframe) {
markerNameInput = new GuiAdvancedTextField(fontRendererObj, 0, 0, 300, 20);
markerNameInput.setText(((MarkerKeyframe)keyframe).getName() == null ? "" : ((MarkerKeyframe)keyframe).getName());
inputs.add(markerNameInput);
markerNameInput.hint = I18n.format("replaymod.gui.editkeyframe.markername");
}
}
@@ -170,9 +159,6 @@ public class GuiEditKeyframe extends GuiScreen {
input.yPosition = i < 3 ? virtualY + 20 + i*30 : virtualY + 20 + (i-3)*30;
i++;
}
} else if(markerKeyframe) {
markerNameInput.xPosition = (width-markerNameInput.width)/2;
markerNameInput.yPosition = virtualY + (virtualHeight-markerNameInput.height)/2;
}
saveButton.yPosition = cancelButton.yPosition = virtualY + virtualHeight - 20 - 5;
@@ -204,14 +190,12 @@ public class GuiEditKeyframe extends GuiScreen {
} else {
keyframe.setRealTimestamp(TimestampUtils.calculateTimestamp(min.getIntValue(), sec.getIntValue(), ms.getIntValue()));
if(posKeyframe) {
((PositionKeyframe)keyframe).setPosition(new Position(xCoord.getPreciseValue(), yCoord.getPreciseValue(),
zCoord.getPreciseValue(), new Float(pitch.getPreciseValue()), (float)yaw.getPreciseValue(),
(float)roll.getPreciseValue()));
} else if(markerKeyframe) {
((MarkerKeyframe)keyframe).setName(markerNameInput.getText().trim());
((Keyframe<Position>)keyframe).setValue(new Position(xCoord.getPreciseValue(), yCoord.getPreciseValue(),
zCoord.getPreciseValue(), new Float(pitch.getPreciseValue()), (float) yaw.getPreciseValue(),
(float) roll.getPreciseValue(), null));
}
}
FMLCommonHandler.instance().bus().post(new KeyframesModifyEvent(ReplayHandler.getKeyframes()));
ReplayHandler.fireKeyframesModifyEvent();
Keyboard.enableRepeatEvents(false);
}

View File

@@ -6,7 +6,6 @@ import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener;
import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.KeyframeSet;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
@@ -132,12 +131,9 @@ public class GuiKeyframeRepository extends GuiScreen implements GuiReplayOverlay
if(!button.enabled) return;
switch(button.id) {
case GuiConstants.KEYFRAME_REPOSITORY_ADD_BUTTON:
List<Keyframe> kfs = new ArrayList<Keyframe>(ReplayHandler.getKeyframes());
for(Keyframe kf : new ArrayList<Keyframe>(kfs)) {
if(kf instanceof MarkerKeyframe) kfs.remove(kf);
}
List<Keyframe> kfs = new ArrayList<Keyframe>(ReplayHandler.getAllKeyframes());
Keyframe[] keyframes = kfs.toArray(new Keyframe[ReplayHandler.getKeyframes().size()]);
Keyframe[] keyframes = kfs.toArray(new Keyframe[ReplayHandler.getAllKeyframes().size()]);
KeyframeSet newSet = new KeyframeSet(I18n.format("replaymod.gui.keyframerepository.preset.defaultname"), keyframes);
if(newSet.getPositionKeyframeCount() < 2 || newSet.getTimeKeyframeCount() < 1) {
message = I18n.format("replaymod.chat.morekeyframes");

View File

@@ -1,18 +1,12 @@
package eu.crushedpixel.replaymod.gui.elements;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.gui.GuiEditKeyframe;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
import eu.crushedpixel.replaymod.holders.TimeKeyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import eu.crushedpixel.replaymod.utils.MouseUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import org.lwjgl.util.Point;
import java.awt.*;
import java.util.ListIterator;
public class GuiKeyframeTimeline extends GuiTimeline {
@@ -22,21 +16,17 @@ public class GuiKeyframeTimeline extends GuiTimeline {
private static final int KEYFRAME_TIME_Y = 25;
private static final int KEYFRAME_SPEC_X = 74;
private static final int KEYFRAME_SPEC_Y = 30;
private static final int KEYFRAME_MARKER_X = 109;
private static final int KEYFRAME_MARKER_Y = 20;
private Keyframe clickedKeyFrame;
private long clickTime;
private boolean dragging;
private boolean markerKeyframes;
private boolean timeKeyframes;
private boolean placeKeyframes;
public GuiKeyframeTimeline(int positionX, int positionY, int width, boolean showMarkers,
boolean showMarkerKeyframes, boolean showPlaceKeyframes, boolean showTimeKeyframes) {
boolean showPlaceKeyframes, boolean showTimeKeyframes) {
super(positionX, positionY, width);
this.showMarkers = showMarkers;
this.markerKeyframes = showMarkerKeyframes;
this.timeKeyframes = showTimeKeyframes;
this.placeKeyframes = showPlaceKeyframes;
}
@@ -55,11 +45,9 @@ public class GuiKeyframeTimeline extends GuiTimeline {
Keyframe closest;
if(mouseY >= positionY + BORDER_TOP + 5 && timeKeyframes) {
closest = ReplayHandler.getClosestTimeKeyframeForRealTime((int) time, tolerance);
closest = ReplayHandler.getTimeKeyframes().getClosestKeyframeForTimestamp((int) time, tolerance);
} else if(mouseY >= positionY + BORDER_TOP && placeKeyframes) {
closest = ReplayHandler.getClosestPlaceKeyframeForRealTime((int) time, tolerance);
} else if(mouseY >= positionY + BORDER_TOP + 10 && markerKeyframes) {
closest = ReplayHandler.getClosestMarkerForRealTime((int) time, tolerance);
closest = ReplayHandler.getPositionKeyframes().getClosestKeyframeForTimestamp((int) time, tolerance);
} else {
closest = null;
}
@@ -84,26 +72,6 @@ public class GuiKeyframeTimeline extends GuiTimeline {
}
this.clickTime = currentTime;
} else if(button == 1) {
if(markerKeyframes) {
long time = getTimeAt(mouseX, mouseY);
if(time == -1) {
return;
}
int tolerance = (int) (2 * Math.round(zoom * timelineLength / width));
MarkerKeyframe closest = null;
if(mouseY >= positionY + BORDER_TOP + 10 && markerKeyframes) {
closest = ReplayHandler.getClosestMarkerForRealTime((int) time, tolerance);
}
if(closest != null) {
//Jump to clicked Marker Keyframe
ReplayHandler.setLastPosition(closest.getPosition());
ReplayMod.replaySender.jumpToTime(closest.getRealTimestamp());
}
}
}
}
@@ -117,7 +85,8 @@ public class GuiKeyframeTimeline extends GuiTimeline {
if (dragging || Math.abs(clickedKeyFrame.getRealTimestamp() - time) > tolerance) {
clickedKeyFrame.setRealTimestamp((int) time);
ReplayHandler.sortKeyframes();
ReplayHandler.getPositionKeyframes().sort();
ReplayHandler.getTimeKeyframes().sort();
dragging = true;
}
} else if (dragging && placeKeyframes) {
@@ -146,22 +115,22 @@ public class GuiKeyframeTimeline extends GuiTimeline {
//iterate over keyframes to find spectator segments
if(placeKeyframes) {
ListIterator<Keyframe> iterator = ReplayHandler.getKeyframes().listIterator();
ListIterator<Keyframe<Position>> iterator = ReplayHandler.getPositionKeyframes().listIterator();
while(iterator.hasNext()) {
Keyframe kf = iterator.next();
Keyframe<Position> kf = iterator.next();
if(!(kf instanceof PositionKeyframe) || ((PositionKeyframe) kf).getSpectatedEntityID() == null)
if(kf.getValue().getSpectatedEntityID() == null)
continue;
int i = iterator.nextIndex();
int nextSpectatorKeyframeRealTime = -1;
while(iterator.hasNext()) {
Keyframe kf2 = iterator.next();
Keyframe<Position> kf2 = iterator.next();
if(kf2 instanceof PositionKeyframe) {
if(((PositionKeyframe) kf).getSpectatedEntityID()
.equals(((PositionKeyframe) kf2).getSpectatedEntityID())) {
if(kf2.getValue() instanceof Position) {
if(kf.getValue().getSpectatedEntityID()
.equals(kf2.getValue().getSpectatedEntityID())) {
nextSpectatorKeyframeRealTime = kf2.getRealTimestamp();
}
@@ -189,7 +158,7 @@ public class GuiKeyframeTimeline extends GuiTimeline {
//Draw Keyframe logos
for (Keyframe kf : ReplayHandler.getKeyframes()) {
for (Keyframe kf : ReplayHandler.getAllKeyframes()) {
if (kf != null && !kf.equals(ReplayHandler.getSelectedKeyframe()))
drawKeyframe(kf, bodyWidth, leftTime, rightTime, segmentLength);
}
@@ -214,21 +183,6 @@ public class GuiKeyframeTimeline extends GuiTimeline {
long leftTime = Math.round(timeStart * timelineLength);
double segmentLength = timelineLength * zoom;
if(markerKeyframes) {
for(MarkerKeyframe marker : ReplayHandler.getMarkers()) {
int keyframeX = getKeyframeX(marker.getRealTimestamp(), leftTime, bodyWidth, segmentLength);
if(MouseUtils.isMouseWithinBounds(keyframeX - 2, this.positionY + BORDER_TOP + 10 + 1, 5, 5)) {
Point mouse = MouseUtils.getMousePos();
String markerName = marker.getName();
if(markerName == null) markerName = I18n.format("replaymod.gui.ingame.unnamedmarker");
ReplayMod.tooltipRenderer.drawTooltip(mouse.getX(), mouse.getY(), markerName, null, Color.WHITE);
drawn = true;
}
}
}
if(!drawn) {
super.drawOverlay(mc, mouseX, mouseY);
}
@@ -242,27 +196,22 @@ public class GuiKeyframeTimeline extends GuiTimeline {
int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, bodyWidth, segmentLength);
if(kf instanceof PositionKeyframe) {
if(kf.getValue() instanceof Position) {
if(!placeKeyframes) return;
textureX = KEYFRAME_PLACE_X;
textureY = KEYFRAME_PLACE_Y;
y += 0;
//If Spectator Keyframe, use different texture
if(((PositionKeyframe) kf).getSpectatedEntityID() != null) {
if(((Keyframe<Position>) kf).getValue().getSpectatedEntityID() != null) {
textureX = KEYFRAME_SPEC_X;
textureY = KEYFRAME_SPEC_Y;
}
} else if(kf instanceof TimeKeyframe) {
} else if(kf.getValue() instanceof TimestampValue) {
if(!timeKeyframes) return;
textureX = KEYFRAME_TIME_X;
textureY = KEYFRAME_TIME_Y;
y += 5;
} else if(kf instanceof MarkerKeyframe) {
if(!markerKeyframes) return;
textureX = KEYFRAME_MARKER_X;
textureY = KEYFRAME_MARKER_Y;
y += 10;
} else {
throw new UnsupportedOperationException("Unknown keyframe type: " + kf.getClass());
}

View File

@@ -7,10 +7,9 @@ import eu.crushedpixel.replaymod.gui.GuiMouseInput;
import eu.crushedpixel.replaymod.gui.GuiRenderSettings;
import eu.crushedpixel.replaymod.gui.GuiReplaySpeedSlider;
import eu.crushedpixel.replaymod.gui.elements.*;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
import eu.crushedpixel.replaymod.holders.TimeKeyframe;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import eu.crushedpixel.replaymod.utils.MouseUtils;
@@ -98,7 +97,7 @@ public class GuiReplayOverlay extends Gui {
@Override
public void run() {
//if not enough keyframes, abort and leave chat message
if(ReplayHandler.getPosKeyframeCount() < 2 || ReplayHandler.getTimeKeyframeCount() < 1) {
if(ReplayHandler.getPositionKeyframes().size() < 2 || ReplayHandler.getTimeKeyframes().size() < 1) {
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.morekeyframes", ChatMessageHandler.ChatMessageType.WARNING);
} else {
mc.displayGuiScreen(new GuiRenderSettings());
@@ -137,12 +136,12 @@ public class GuiReplayOverlay extends Gui {
Entity cam = mc.getRenderViewEntity();
if (cam != null) {
Position position = new Position(cam.posX, cam.posY, cam.posZ, cam.rotationPitch,
cam.rotationYaw % 360, ReplayHandler.getCameraTilt());
cam.rotationYaw % 360, ReplayHandler.getCameraTilt(), null);
if (ReplayHandler.isCamera())
ReplayHandler.addKeyframe(new PositionKeyframe(ReplayHandler.getRealTimelineCursor(), position));
ReplayHandler.addKeyframe(new Keyframe<Position>(ReplayHandler.getRealTimelineCursor(), position));
else
ReplayHandler.addKeyframe(new PositionKeyframe(ReplayHandler.getRealTimelineCursor(), cam.getEntityId()));
ReplayHandler.addKeyframe(new Keyframe<Position>(ReplayHandler.getRealTimelineCursor(), new Position(cam.getEntityId(), true)));
}
}
}, "replaymod.gui.ingame.menu.addposkeyframe");
@@ -160,12 +159,12 @@ public class GuiReplayOverlay extends Gui {
Entity cam = mc.getRenderViewEntity();
if (cam != null) {
Position position = new Position(cam.posX, cam.posY, cam.posZ, cam.rotationPitch,
cam.rotationYaw % 360, ReplayHandler.getCameraTilt());
cam.rotationYaw % 360, ReplayHandler.getCameraTilt(), null);
if (ReplayHandler.isCamera())
ReplayHandler.addKeyframe(new PositionKeyframe(ReplayHandler.getRealTimelineCursor(), position));
ReplayHandler.addKeyframe(new Keyframe<Position>(ReplayHandler.getRealTimelineCursor(), position));
else
ReplayHandler.addKeyframe(new PositionKeyframe(ReplayHandler.getRealTimelineCursor(), cam.getEntityId()));
ReplayHandler.addKeyframe(new Keyframe<Position>(ReplayHandler.getRealTimelineCursor(), new Position(cam.getEntityId(), true)));
}
}
}, "replaymod.gui.ingame.menu.addspeckeyframe");
@@ -179,10 +178,10 @@ public class GuiReplayOverlay extends Gui {
@Override
public GuiElement delegate() {
boolean selected = ReplayHandler.getSelectedKeyframe() instanceof PositionKeyframe;
boolean selected = ReplayHandler.getSelectedKeyframe() != null && ReplayHandler.getSelectedKeyframe().getValue() instanceof Position;
boolean camera;
if(selected) {
camera = ((PositionKeyframe)ReplayHandler.getSelectedKeyframe()).getSpectatedEntityID() == null;
camera = ((Keyframe<Position>)ReplayHandler.getSelectedKeyframe()).getValue().getSpectatedEntityID() == null;
} else {
camera = ReplayHandler.isCamera();
}
@@ -200,7 +199,7 @@ public class GuiReplayOverlay extends Gui {
private final GuiElement buttonNotSelected = texturedButton(BUTTON_TIME_X, BOTTOM_ROW, 0, 80, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.addKeyframe(new TimeKeyframe(ReplayHandler.getRealTimelineCursor(), ReplayMod.replaySender.currentTimeStamp()));
ReplayHandler.addKeyframe(new Keyframe<TimestampValue>(ReplayHandler.getRealTimelineCursor(), new TimestampValue(ReplayMod.replaySender.currentTimeStamp())));
}
}, "replaymod.gui.ingame.menu.addtimekeyframe");
@@ -213,7 +212,7 @@ public class GuiReplayOverlay extends Gui {
@Override
public GuiElement delegate() {
return ReplayHandler.getSelectedKeyframe() instanceof TimeKeyframe ? buttonSelected : buttonNotSelected;
return ReplayHandler.getSelectedKeyframe() != null && ReplayHandler.getSelectedKeyframe().getValue() instanceof TimestampValue ? buttonSelected : buttonNotSelected;
}
};
@@ -233,17 +232,12 @@ public class GuiReplayOverlay extends Gui {
}
}, "replaymod.gui.ingame.menu.zoomout");
private final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(TIMELINE_X, TOP_ROW - 1, WIDTH - 14 - TIMELINE_X, false, true, false, false) {
@Override
public void mouseClick(Minecraft mc, int mouseX, int mouseY, int button) {
if(!enabled) return;
super.mouseClick(mc, mouseX, mouseY, button);
if(!(ReplayHandler.getSelectedKeyframe() instanceof MarkerKeyframe))
performJump(timeline.getTimeAt(mouseX, mouseY));
}
//TODO: GuiMarkerKeyframeTimeline
private final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(TIMELINE_X, TOP_ROW - 1, WIDTH - 14 - TIMELINE_X, false, false, false) {
};
private final GuiKeyframeTimeline timelineReal = new GuiKeyframeTimeline(TIMELINE_REAL_X, BOTTOM_ROW - 1, TIMELINE_REAL_WIDTH, true, false, true, true);
private final GuiKeyframeTimeline timelineReal = new GuiKeyframeTimeline(TIMELINE_REAL_X, BOTTOM_ROW - 1, TIMELINE_REAL_WIDTH, true, true, true);
{
timelineReal.timelineLength = 10 * 60 * 1000;
}

View File

@@ -1,21 +0,0 @@
package eu.crushedpixel.replaymod.holders;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class ExtendedPosition extends Position {
public ExtendedPosition(double x, double y, double z, float width, float height) {
super(x, y, z, 0, 0);
this.width = width;
this.height = height;
}
private float anchorX, anchorY;
private float width, height;
private float scale = 1f;
private float opacity = 1f;
}

View File

@@ -1,5 +1,6 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -7,9 +8,12 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode
@AllArgsConstructor
public abstract class Keyframe {
public class Keyframe<T extends KeyframeValue> {
private int realTimestamp;
private T value;
public abstract Keyframe copy();
public Keyframe copy() {
return new Keyframe<T>(realTimestamp, value);
}
}

View File

@@ -8,8 +8,8 @@ import java.util.List;
public class KeyframeSet implements GuiEntryListEntry {
private String name;
private PositionKeyframe[] positionKeyframes;
private TimeKeyframe[] timeKeyframes;
private Keyframe<Position>[] positionKeyframes;
private Keyframe<TimestampValue>[] timeKeyframes;
public KeyframeSet(String name, Keyframe[] keyframes) {
this.name = name;
@@ -21,18 +21,18 @@ public class KeyframeSet implements GuiEntryListEntry {
}
public void setKeyframes(Keyframe[] keyframes) {
List<PositionKeyframe> posKFList = new ArrayList<PositionKeyframe>();
List<TimeKeyframe> timeKFList = new ArrayList<TimeKeyframe>();
List<Keyframe<Position>> posKFList = new ArrayList<Keyframe<Position>>();
List<Keyframe<TimestampValue>> timeKFList = new ArrayList<Keyframe<TimestampValue>>();
for(Keyframe kf : keyframes) {
if(kf instanceof PositionKeyframe)
posKFList.add((PositionKeyframe)kf);
else if(kf instanceof TimeKeyframe)
timeKFList.add((TimeKeyframe) kf);
if(kf.getValue() instanceof Position)
posKFList.add((Keyframe<Position>)kf);
else if(kf.getValue() instanceof TimestampValue)
timeKFList.add((Keyframe<TimestampValue>) kf);
}
positionKeyframes = posKFList.toArray(new PositionKeyframe[posKFList.size()]);
timeKeyframes = timeKFList.toArray(new TimeKeyframe[timeKFList.size()]);
positionKeyframes = posKFList.toArray(new Keyframe[posKFList.size()]);
timeKeyframes = timeKFList.toArray(new Keyframe[timeKFList.size()]);
}
public String getName() {

View File

@@ -1,23 +1,14 @@
package eu.crushedpixel.replaymod.holders;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public final class MarkerKeyframe extends Keyframe {
@AllArgsConstructor
public final class MarkerKeyframe {
private int realTimestamp;
private Position position;
private String name;
public MarkerKeyframe(Position position, int timestamp, String name) {
super(timestamp);
this.position = position;
this.name = name;
}
@Override
public Keyframe copy() {
return new MarkerKeyframe(position, getRealTimestamp(), name);
}
}

View File

@@ -0,0 +1,13 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public class Point extends KeyframeValue {
public double x, y;
}

View File

@@ -1,27 +1,38 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
@Data
@AllArgsConstructor
public class Position {
@NoArgsConstructor
@EqualsAndHashCode(callSuper=true)
public class Position extends KeyframeValue {
private double x, y, z;
private float pitch, yaw, roll;
public double x, y, z;
public double pitch, yaw, roll;
public Position(int entityID) {
this(Minecraft.getMinecraft().theWorld.getEntityByID(entityID));
private Integer spectatedEntityID;
public Position(int entityID, boolean spectate) {
this(Minecraft.getMinecraft().theWorld.getEntityByID(entityID), spectate);
}
public Position(Entity e) {
this(e.posX, e.posY, e.posZ, e.rotationPitch, e.rotationYaw);
public Position(Entity e, boolean spectate) {
this(e.posX, e.posY, e.posZ, e.rotationPitch, e.rotationYaw, spectate ? e.getEntityId() : null);
}
public Position(double x, double y, double z, float pitch, float yaw, Integer spectatedEntityID) {
this(x, y, z, pitch, yaw, 0, spectatedEntityID);
}
public Position(double x, double y, double z, float pitch, float yaw) {
this(x, y, z, pitch, yaw, 0);
this(x, y, z, pitch, yaw, 0, null);
}
public double distanceSquared(double x, double y, double z) {

View File

@@ -1,31 +0,0 @@
package eu.crushedpixel.replaymod.holders;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public final class PositionKeyframe extends Keyframe {
private Position position;
private Integer spectatedEntityID;
public PositionKeyframe(int realTime, Position position) {
this(realTime, position, null);
}
public PositionKeyframe(int realTime, Position position, Integer spectatedEntityID) {
super(realTime);
this.position = position;
this.spectatedEntityID = spectatedEntityID;
}
public PositionKeyframe(int realTime, int spectatedEntityID) {
this(realTime, new Position(spectatedEntityID), spectatedEntityID);
}
@Override
public Keyframe copy() {
return new PositionKeyframe(getRealTimestamp(), position, spectatedEntityID);
}
}

View File

@@ -1,21 +0,0 @@
package eu.crushedpixel.replaymod.holders;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public final class TimeKeyframe extends Keyframe {
private final int timestamp;
public TimeKeyframe(int realTime, int timestamp) {
super(realTime);
this.timestamp = timestamp;
}
@Override
public Keyframe copy() {
return new TimeKeyframe(getRealTimestamp(), getTimestamp());
}
}

View File

@@ -0,0 +1,13 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public class TimestampValue extends KeyframeValue {
public double value;
}

View File

@@ -0,0 +1,20 @@
package eu.crushedpixel.replaymod.holders;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.awt.*;
@Data
@AllArgsConstructor
public class Transformations {
private Position anchor;
private Position position;
private Position orientation;
private Point scale;
private float opacity;
private float width, height;
}

View File

@@ -0,0 +1,60 @@
package eu.crushedpixel.replaymod.interpolation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class GenericLinearInterpolation<T extends KeyframeValue> implements Interpolation<T> {
private Field[] fields;
protected List<T> points = new ArrayList<T>();
public GenericLinearInterpolation() {
points = new ArrayList<T>();
}
@Override
public void prepare() {}
public void addPoint(T point) {
points.add(point);
List<Field> fields = new ArrayList<Field>();
for(Field f : point.getClass().getDeclaredFields()) {
if(Modifier.isPublic(f.getModifiers())) {
fields.add(f);
}
}
this.fields = fields.toArray(new Field[fields.size()]);
}
@Override
public void applyPoint(float position, T toEdit) {
if(points.isEmpty()) {
throw new IllegalStateException("At least one Value needs to be added for this operation");
}
//first, get previous and next T for given position
float relative = position * (points.size()-1);
int previousIndex = (int)Math.floor(relative);
int nextIndex = (int)Math.ceil(relative);
float percentage = relative - previousIndex;
T previous = points.get(previousIndex);
T next = points.get(nextIndex);
for(Field f : fields) {
try {
f.set(toEdit, getInterpolatedValue(f.getDouble(previous), f.getDouble(next), percentage));
} catch(Exception e) {
e.printStackTrace();
}
}
}
private double getInterpolatedValue(double val1, double val2, float perc) {
return val1 + ((val2 - val1) * perc);
}
}

View File

@@ -0,0 +1,75 @@
package eu.crushedpixel.replaymod.interpolation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
public class GenericSplineInterpolation<T extends KeyframeValue> extends BasicSpline implements Interpolation<T> {
private Field[] fields;
private Vector<T> points;
private Vector<Cubic>[] cubics = new Vector[0];
public GenericSplineInterpolation() {
this.points = new Vector<T>();
}
public void addPoint(T point) {
this.points.add(point);
List<Field> fields = new ArrayList<Field>();
for(Field f : point.getClass().getDeclaredFields()) {
if(Modifier.isPublic(f.getModifiers())) {
fields.add(f);
}
}
this.fields = fields.toArray(new Field[fields.size()]);
}
public Vector<T> getPoints() {
return points;
}
@Override
public void prepare() {
if(fields.length <= 0) {
throw new IllegalStateException("The passed KeyframeValue class" +
" has to contain at least one Field");
}
if(!points.isEmpty()) {
cubics = new Vector[fields.length];
for(int i=0; i<fields.length; i++) {
cubics[i] = new Vector<Cubic>();
try {
calcNaturalCubic(points, fields[i], cubics[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
} else {
throw new IllegalStateException("At least one Value needs to be added" +
" before preparing this Spline");
}
}
public void applyPoint(float position, T toEdit) {
Vector<Cubic> first = cubics[0];
position = position * first.size();
int cubicNum = (int) Math.min(cubics[0].size() - 1, position);
float cubicPos = (position - cubicNum);
int i = 0;
for(Field f : fields) {
try {
f.set(toEdit, cubics[i].get(cubicNum).eval(cubicPos));
} catch(Exception e) {
e.printStackTrace();
}
i++;
}
}
}

View File

@@ -1,7 +1,7 @@
package eu.crushedpixel.replaymod.interpolation;
public interface Interpolation<T> {
public interface Interpolation<T extends KeyframeValue> {
void prepare();
T getPoint(float position);
void applyPoint(float position, T toEdit);
void addPoint(T pos);
}

View File

@@ -0,0 +1,170 @@
package eu.crushedpixel.replaymod.interpolation;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.KeyframeComparator;
import eu.crushedpixel.replaymod.holders.Position;
import java.util.ArrayList;
import java.util.List;
public class KeyframeList<T extends Keyframe> extends ArrayList<T> {
private static final KeyframeComparator KEYFRAME_COMPARATOR = new KeyframeComparator();
@Override
public boolean add(T t) {
boolean success = super.add(t);
sort();
return success;
}
@Override
public void add(int index, T element) {
super.add(index, element);
sort();
}
@Override
public T remove(int index) {
T removed = super.remove(index);
sort();
return removed;
}
@Override
public boolean remove(Object o) {
boolean success = super.remove(o);
sort();
return success;
}
public void sort() {
sort(KEYFRAME_COMPARATOR);
}
/**
* Returns the first Keyframe that comes before a given value.
* @param realTime The value to use
* @return The first Keyframe prior to the given value
*/
public T getPreviousKeyframe(int realTime) {
if(this.isEmpty()) return null;
T backup = null;
List<T> found = new ArrayList<T>();
for(T kf : this) {
if(kf.getRealTimestamp() < realTime) {
found.add(kf);
} else if(kf.getRealTimestamp() == realTime) {
backup = kf;
}
}
if(found.size() > 0)
return found.get(found.size() - 1); //last element is nearest
else return backup;
}
/**
* Returns the first Keyframe that comes after a given value.
* @param realTime The value to use
* @return The first Keyframe after the given value
*/
public T getNextKeyframe(int realTime) {
if(this.isEmpty()) return null;
T backup = null;
for(T kf : this) {
if(kf.getRealTimestamp() > realTime) {
return kf; //first found element is next
} else if(kf.getRealTimestamp() == realTime) {
backup = kf;
}
}
return backup;
}
/**
* Returns the Keyframe that is closest to the given timestamp.
* @param realTime The timestamp to start searching at
* @param tolerance The threshold to allow for close Keyframes
* @return The closest Keyframe, or null if no Keyframe within treshold
*/
public T getClosestKeyframeForTimestamp(int realTime, int tolerance) {
List<T> found = new ArrayList<T>();
for(T kf : this) {
if(!(kf.getValue() instanceof Position)) continue;
if(Math.abs(kf.getRealTimestamp() - realTime) <= tolerance) {
found.add(kf);
}
}
T closest = null;
for(T kf : found) {
if(closest == null || Math.abs(closest.getRealTimestamp() - realTime) > Math.abs(kf.getRealTimestamp() - realTime)) {
closest = kf;
}
}
return closest;
}
public T first() {
if(isEmpty()) return null;
return get(0);
}
public T last() {
if(isEmpty()) return null;
return get(size()-1);
}
/**
* Returns a value between 0 and 1, representing the number that should be passed
* to Interpolation#getValue() calls on this list of Keyframes.
* @param timestamp The value to use
* @return A value between 0 and 1
*/
public float getPositionOnSpline(int timestamp) {
Keyframe previousKeyframe = getPreviousKeyframe(timestamp);
Keyframe nextKeyframe = getNextKeyframe(timestamp);
int previousTimestamp = 0;
int nextTimestamp = 0;
if(nextKeyframe != null || previousKeyframe != null) {
if(nextKeyframe != null) {
nextTimestamp = nextKeyframe.getRealTimestamp();
} else {
nextTimestamp = previousKeyframe.getRealTimestamp();
}
if(previousKeyframe != null) {
previousTimestamp = previousKeyframe.getRealTimestamp();
} else {
previousTimestamp = nextKeyframe.getRealTimestamp();
}
}
int currentPosDiff = nextTimestamp - previousTimestamp;
int currentPos = timestamp - previousTimestamp;
float currentStepPercentage = (float) currentPos / (float) currentPosDiff;
if(Float.isInfinite(currentStepPercentage)) currentStepPercentage = 0;
float value = (indexOf(previousKeyframe) + currentStepPercentage) /
(float)(size() - 1);
return Math.max(0, Math.min(1, value));
}
}

View File

@@ -0,0 +1,12 @@
package eu.crushedpixel.replaymod.interpolation;
/**
* An abstract class that GenericSplineInterpolation can process.
* Subclasses simply have to declare at least one <b>public</b> double field,
* and the GenericSplineInterpolation will interpolate these.
* <br><br>
* It is recommended for KeyframeValue subclasses to have a @NoArgsConstructor annotation.
*/
public abstract class KeyframeValue {
}

View File

@@ -1,47 +0,0 @@
package eu.crushedpixel.replaymod.interpolation;
import akka.japi.Pair;
import java.util.ArrayList;
import java.util.List;
public abstract class LinearInterpolation<K> implements Interpolation<K> {
protected List<K> points = new ArrayList<K>();
public LinearInterpolation() {
points = new ArrayList<K>();
}
@Override
public void prepare() {
}
public abstract K getPoint(float position);
public void addPoint(K point) {
points.add(point);
}
protected Pair<Float, Pair<K, K>> getCurrentPoints(float position) {
if(points.size() == 0) return null;
position = position * (points.size() - 1);
int cubicNum = (int) Math.min(points.size() - 1, position);
float cubicPos = (position - cubicNum);
if(cubicNum == points.size() - 1) {
cubicNum--;
cubicPos++;
}
if(cubicNum < 0) {
return new Pair<Float, Pair<K, K>>(cubicPos, new Pair<K, K>(points.get(cubicNum + 1), points.get(cubicNum + 1)));
}
return new Pair<Float, Pair<K, K>>(cubicPos, new Pair<K, K>(points.get(cubicNum), points.get(cubicNum + 1)));
}
protected double getInterpolatedValue(double val1, double val2, float perc) {
return val1 + ((val2 - val1) * perc);
}
}

View File

@@ -1,29 +0,0 @@
package eu.crushedpixel.replaymod.interpolation;
import akka.japi.Pair;
import eu.crushedpixel.replaymod.holders.Position;
public class LinearPoint extends LinearInterpolation<Position> {
@Override
public Position getPoint(float positionIn) {
Pair<Float, Pair<Position, Position>> pair = getCurrentPoints(positionIn);
if(pair == null) return null;
float perc = pair.first();
Position first = pair.second().first();
Position second = pair.second().second();
double x = getInterpolatedValue(first.getX(), second.getX(), perc);
double y = getInterpolatedValue(first.getY(), second.getY(), perc);
double z = getInterpolatedValue(first.getZ(), second.getZ(), perc);
float pitch = (float)getInterpolatedValue(first.getPitch(), second.getPitch(), perc);
float yaw = (float)getInterpolatedValue(first.getYaw(), second.getYaw(), perc);
float rot = (float)getInterpolatedValue(first.getRoll(), second.getRoll(), perc);
return new Position(x, y, z, pitch, yaw, rot);
}
}

View File

@@ -1,19 +0,0 @@
package eu.crushedpixel.replaymod.interpolation;
import akka.japi.Pair;
public class LinearTimestamp extends LinearInterpolation<Integer> {
@Override
public Integer getPoint(float position) {
Pair<Float, Pair<Integer, Integer>> pair = getCurrentPoints(position);
if(pair == null) return null;
float perc = pair.first();
int first = pair.second().first();
int second = pair.second().second();
return (int) getInterpolatedValue(first, second, perc);
}
}

View File

@@ -1,93 +0,0 @@
package eu.crushedpixel.replaymod.interpolation;
import eu.crushedpixel.replaymod.holders.Position;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Vector;
public class SplinePoint extends BasicSpline implements Interpolation<Position> {
private Vector<Position> points;
private Vector<Cubic> xCubics;
private Vector<Cubic> yCubics;
private Vector<Cubic> zCubics;
private Vector<Cubic> pitchCubics;
private Vector<Cubic> yawCubics;
private Vector<Cubic> rotCubics;
private Field vectorX;
private Field vectorY;
private Field vectorZ;
private Field vectorPitch;
private Field vectorYaw;
private Field vectorRot;
public SplinePoint() {
this.points = new Vector<Position>();
this.xCubics = new Vector<Cubic>();
this.yCubics = new Vector<Cubic>();
this.zCubics = new Vector<Cubic>();
this.pitchCubics = new Vector<Cubic>();
this.yawCubics = new Vector<Cubic>();
this.rotCubics = new Vector<Cubic>();
try {
vectorX = Position.class.getDeclaredField("x");
vectorY = Position.class.getDeclaredField("y");
vectorZ = Position.class.getDeclaredField("z");
vectorPitch = Position.class.getDeclaredField("pitch");
vectorYaw = Position.class.getDeclaredField("yaw");
vectorRot = Position.class.getDeclaredField("roll");
vectorX.setAccessible(true);
vectorY.setAccessible(true);
vectorZ.setAccessible(true);
vectorPitch.setAccessible(true);
vectorYaw.setAccessible(true);
vectorRot.setAccessible(true);
} catch(SecurityException e) {
e.printStackTrace();
} catch(NoSuchFieldException e) {
e.printStackTrace();
}
}
public void addPoint(Position point) {
this.points.add(point);
}
public Vector<Position> getPoints() {
return points;
}
@Override
public void prepare() {
try {
calcNaturalCubic(points, vectorX, xCubics);
calcNaturalCubic(points, vectorY, yCubics);
calcNaturalCubic(points, vectorZ, zCubics);
calcNaturalCubic(points, vectorPitch, pitchCubics);
calcNaturalCubic(points, vectorYaw, yawCubics);
calcNaturalCubic(points, vectorRot, rotCubics);
} catch(IllegalArgumentException e) {
e.printStackTrace();
} catch(IllegalAccessException e) {
e.printStackTrace();
} catch(InvocationTargetException e) {
e.printStackTrace();
}
}
public Position getPoint(float position) {
position = position * xCubics.size();
int cubicNum = (int) Math.min(xCubics.size() - 1, position);
float cubicPos = (position - cubicNum);
return new Position(xCubics.get(cubicNum).eval(cubicPos),
yCubics.get(cubicNum).eval(cubicPos),
zCubics.get(cubicNum).eval(cubicPos),
(float) pitchCubics.get(cubicNum).eval(cubicPos),
(float) yawCubics.get(cubicNum).eval(cubicPos),
(float) rotCubics.get(cubicNum).eval(cubicPos));
}
}

View File

@@ -204,10 +204,10 @@ public class PacketListener extends DataListener {
}
public void addMarker() {
Position pos = new Position(Minecraft.getMinecraft().getRenderViewEntity());
Position pos = new Position(Minecraft.getMinecraft().getRenderViewEntity(), false);
int timestamp = (int) (System.currentTimeMillis() - startTime);
MarkerKeyframe marker = new MarkerKeyframe(pos, timestamp, null);
MarkerKeyframe marker = new MarkerKeyframe(timestamp, pos, null);
markers.add(marker);

View File

@@ -1,16 +1,12 @@
package eu.crushedpixel.replaymod.renderer;
import eu.crushedpixel.replaymod.holders.CustomImageObject;
import eu.crushedpixel.replaymod.holders.ExtendedPosition;
import eu.crushedpixel.replaymod.assets.CustomImageObject;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.entity.Entity;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.opengl.GL11;
/**
* Allows users to render custom images in the World.
@@ -51,6 +47,8 @@ public class CustomObjectRenderer {
}
private void drawCustomImageObject(double playerX, double playerY, double playerZ, CustomImageObject customImageObject) {
//TODO: Get current Transformations for given Timestamp
/*
GlStateManager.pushMatrix();
GlStateManager.enableTexture2D();
GlStateManager.enableLighting();
@@ -64,7 +62,7 @@ public class CustomObjectRenderer {
mc.renderEngine.bindTexture(customImageObject.getResourceLocation());
ExtendedPosition objectPosition = customImageObject.getPosition();
Transformations objectPosition = customImageObject.getPosition();
double x = objectPosition.getX() - playerX;
double y = objectPosition.getY() - playerY;
@@ -111,5 +109,6 @@ public class CustomObjectRenderer {
GlStateManager.disableTexture2D();
GlStateManager.enableLighting();
GlStateManager.popMatrix();
*/
}
}

View File

@@ -5,8 +5,7 @@ import eu.crushedpixel.replaymod.events.KeyframesModifyEvent;
import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
import eu.crushedpixel.replaymod.interpolation.SplinePoint;
import eu.crushedpixel.replaymod.interpolation.GenericSplineInterpolation;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
@@ -27,11 +26,11 @@ public class PathPreviewRenderer {
private static final Minecraft mc = Minecraft.getMinecraft();
private SplinePoint spline = new SplinePoint();
private GenericSplineInterpolation<Position> spline = new GenericSplineInterpolation<Position>();
private DistanceComparator distanceComparator = new DistanceComparator();
private List<PositionKeyframe> keyframes = new ArrayList<PositionKeyframe>();
private List<Keyframe<Position>> keyframes = new ArrayList<Keyframe<Position>>();
@SubscribeEvent
public void renderCameraPath(RenderWorldLastEvent event) {
@@ -72,7 +71,8 @@ public class PathPreviewRenderer {
float max = spline.getPoints().size() * 50;
for(int i = 0; i < max; i++) {
Position point = spline.getPoint(i / max);
Position point = new Position();
spline.applyPoint(i / max, point);
if(prev != null) {
drawConnection(doubleX, doubleY, doubleZ, prev, point, Color.RED.getRGB());
@@ -88,7 +88,7 @@ public class PathPreviewRenderer {
Collections.sort(keyframes, distanceComparator);
for(PositionKeyframe kf : keyframes) {
for(Keyframe<Position> kf : keyframes) {
drawPoint(doubleX, doubleY, doubleZ, kf);
}
@@ -102,15 +102,13 @@ public class PathPreviewRenderer {
@SubscribeEvent
public void recalcSpline(KeyframesModifyEvent event) {
keyframes = new ArrayList<PositionKeyframe>();
spline = new SplinePoint();
for(Keyframe kf : event.keyframes) {
if(kf instanceof PositionKeyframe) {
PositionKeyframe pkf = (PositionKeyframe)kf;
Position pos = pkf.getPosition();
spline.addPoint(pos);
keyframes.add(pkf);
}
keyframes = new ArrayList<Keyframe<Position>>();
spline = new GenericSplineInterpolation<Position>();
for(Keyframe kf : event.getPositionKeyframes()) {
Keyframe<Position> pkf = (Keyframe<Position>)kf;
Position pos = pkf.getValue();
spline.addPoint(pos);
keyframes.add(pkf);
}
if(spline.getPoints().size() > 1) {
@@ -118,7 +116,7 @@ public class PathPreviewRenderer {
}
}
private class DistanceComparator implements Comparator<PositionKeyframe> {
private class DistanceComparator implements Comparator<Keyframe<Position>> {
private double playerX, playerY, playerZ;
@@ -129,8 +127,8 @@ public class PathPreviewRenderer {
}
@Override
public int compare(PositionKeyframe o1, PositionKeyframe o2) {
return -(new Double(o1.getPosition().distanceSquared(playerX, playerY, playerZ)).compareTo(o2.getPosition().distanceSquared(playerX, playerY, playerZ)));
public int compare(Keyframe<Position> o1, Keyframe<Position> o2) {
return -(new Double(o1.getValue().distanceSquared(playerX, playerY, playerZ)).compareTo(o2.getValue().distanceSquared(playerX, playerY, playerZ)));
}
@Override
@@ -162,7 +160,7 @@ public class PathPreviewRenderer {
renderer.setTranslation(0, 0, 0);
}
private void drawPoint(double playerX, double playerY, double playerZ, PositionKeyframe kf) {
private void drawPoint(double playerX, double playerY, double playerZ, Keyframe<Position> kf) {
GlStateManager.pushMatrix();
GlStateManager.enableTexture2D();
GlStateManager.enableLighting();
@@ -176,7 +174,7 @@ public class PathPreviewRenderer {
mc.renderEngine.bindTexture(GuiReplayOverlay.replay_gui);
Position pos1 = kf.getPosition();
Position pos1 = kf.getValue();
double x = pos1.getX() - playerX;
double y = pos1.getY() - playerY;
@@ -205,10 +203,10 @@ public class PathPreviewRenderer {
posY += size;
}
if(kf.getSpectatedEntityID() != null) {
if(pos1.getSpectatedEntityID() != null) {
posX += size;
}
float minX = -0.5f;
float minY = -0.5f;
float maxX = 0.5f;

View File

@@ -3,10 +3,12 @@ package eu.crushedpixel.replaymod.replay;
import com.mojang.authlib.GameProfile;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.assets.AssetRepository;
import eu.crushedpixel.replaymod.assets.CustomImageObject;
import eu.crushedpixel.replaymod.entities.CameraEntity;
import eu.crushedpixel.replaymod.events.KeyframesModifyEvent;
import eu.crushedpixel.replaymod.events.ReplayExitEvent;
import eu.crushedpixel.replaymod.holders.*;
import eu.crushedpixel.replaymod.interpolation.KeyframeList;
import eu.crushedpixel.replaymod.registry.PlayerHandler;
import eu.crushedpixel.replaymod.settings.RenderOptions;
import eu.crushedpixel.replaymod.utils.ReplayFile;
@@ -41,15 +43,22 @@ public class ReplayHandler {
private static Minecraft mc = Minecraft.getMinecraft();
private static OpenEmbeddedChannel channel;
private static int realTimelinePosition = 0;
private static Keyframe selectedKeyframe;
private static MarkerKeyframe selectedMarkerKeyframe;
private static boolean inPath = false;
private static CameraEntity cameraEntity;
private static List<Keyframe> keyframes = new ArrayList<Keyframe>();
private static KeyframeList<Keyframe<Position>> positionKeyframes = new KeyframeList<Keyframe<Position>>();
private static KeyframeList<Keyframe<TimestampValue>> timeKeyframes = new KeyframeList<Keyframe<TimestampValue>>();
private static boolean inReplay = false;
private static Entity currentEntity = null;
private static Position lastPosition = null;
private static MarkerKeyframe[] initialMarkers = new MarkerKeyframe[0];
private static List<MarkerKeyframe> markerKeyframes = new ArrayList<MarkerKeyframe>();
private static float cameraTilt = 0;
@@ -85,18 +94,12 @@ public class ReplayHandler {
}
public static MarkerKeyframe[] getMarkers() {
List<MarkerKeyframe> markers = new ArrayList<MarkerKeyframe>();
for(Keyframe kf : keyframes) {
if(kf instanceof MarkerKeyframe) markers.add((MarkerKeyframe)kf);
}
return markers.toArray(new MarkerKeyframe[markers.size()]);
return markerKeyframes.toArray(new MarkerKeyframe[markerKeyframes.size()]);
}
public static void setMarkers(MarkerKeyframe[] m, boolean write) {
for(Keyframe kf : new ArrayList<Keyframe>(keyframes)) {
if(kf instanceof MarkerKeyframe) keyframes.remove(kf);
}
Collections.addAll(keyframes, m);
markerKeyframes.clear();
Collections.addAll(markerKeyframes, m);
if(write) {
try {
@@ -109,8 +112,6 @@ public class ReplayHandler {
e.printStackTrace();
}
}
FMLCommonHandler.instance().bus().post(new KeyframesModifyEvent(keyframes));
}
public static void useKeyframePresetFromRepository(int index) {
@@ -118,15 +119,17 @@ public class ReplayHandler {
}
public static void useKeyframePreset(Keyframe[] kfs) {
MarkerKeyframe[] markers = getMarkers();
positionKeyframes.clear();
timeKeyframes.clear();
for(Keyframe kf : kfs) {
if(kf.getValue() instanceof Position) {
positionKeyframes.add(kf);
} else if(kf.getValue() instanceof TimestampValue) {
timeKeyframes.add(kf);
}
}
keyframes = new ArrayList<Keyframe>(Arrays.asList(kfs));
Collections.addAll(keyframes, markers);
if(!(selectedKeyframe instanceof MarkerKeyframe)) selectedKeyframe = null;
FMLCommonHandler.instance().bus().post(new KeyframesModifyEvent(keyframes));
fireKeyframesModifyEvent();
}
public static void spectateEntity(Entity e) {
@@ -143,7 +146,7 @@ public class ReplayHandler {
public static void spectateCamera() {
if(currentEntity != null) {
Position prev = new Position(currentEntity);
Position prev = new Position(currentEntity, false);
cameraEntity.movePath(prev);
}
currentEntity = cameraEntity;
@@ -222,136 +225,75 @@ public class ReplayHandler {
cameraTilt += tilt;
}
public static void sortKeyframes() {
Collections.sort(keyframes, new KeyframeComparator());
public static void toggleMarker() {
if(selectedMarkerKeyframe != null) markerKeyframes.remove(selectedMarkerKeyframe);
else {
Position pos = new Position(mc.getRenderViewEntity(), false);
int timestamp = ReplayMod.replaySender.currentTimeStamp();
markerKeyframes.add(new MarkerKeyframe(timestamp, pos, null));
}
}
public static void toggleMarker() {
if(selectedKeyframe instanceof MarkerKeyframe) removeKeyframe(selectedKeyframe);
else {
Position pos = new Position(mc.getRenderViewEntity());
int timestamp = ReplayMod.replaySender.currentTimeStamp();
addKeyframe(new MarkerKeyframe(pos, timestamp, null));
public static void addTimeKeyframe(Keyframe<TimestampValue> keyframe) {
timeKeyframes.add(keyframe);
selectKeyframe(keyframe);
fireKeyframesModifyEvent();
}
public static void addPositionKeyframe(Keyframe<Position> keyframe) {
positionKeyframes.add(keyframe);
selectKeyframe(keyframe);
Float a = null;
Float b;
for(Keyframe kf : positionKeyframes) {
if(!(kf.getValue() instanceof Position)) continue;
Keyframe<Position> pkf = (Keyframe<Position>)kf;
Position pos = pkf.getValue();
b = (float)pos.getYaw() % 360;
if(a != null) {
float diff = b-a;
if(Math.abs(diff) > 180) {
b = a - (360 - diff) % 360;
pos.setYaw(b);
pkf.setValue(pos);
}
}
a = b;
}
fireKeyframesModifyEvent();
}
public static void addKeyframe(Keyframe keyframe) {
keyframes.add(keyframe);
selectKeyframe(keyframe);
if(keyframe instanceof PositionKeyframe) {
Float a = null;
Float b;
for(Keyframe kf : keyframes) {
if(!(kf instanceof PositionKeyframe)) continue;
PositionKeyframe pkf = (PositionKeyframe)kf;
Position pos = pkf.getPosition();
b = pos.getYaw() % 360;
if(a != null) {
float diff = b-a;
if(Math.abs(diff) > 180) {
b = a - (360 - diff) % 360;
pos.setYaw(b);
pkf.setPosition(pos);
}
}
a = b;
}
if(keyframe.getValue() instanceof Position) {
addPositionKeyframe(keyframe);
} else if(keyframe.getValue() instanceof TimestampValue) {
addTimeKeyframe(keyframe);
}
FMLCommonHandler.instance().bus().post(new KeyframesModifyEvent(keyframes));
}
public static void removeKeyframe(Keyframe keyframe) {
keyframes.remove(keyframe);
if(keyframe.getValue() instanceof Position) {
positionKeyframes.remove(keyframe);
} else if(keyframe.getValue() instanceof TimestampValue) {
timeKeyframes.remove(keyframe);
}
if(keyframe == selectedKeyframe) {
selectKeyframe(null);
} else {
sortKeyframes();
}
FMLCommonHandler.instance().bus().post(new KeyframesModifyEvent(keyframes));
}
public static int getKeyframeIndex(TimeKeyframe timeKeyframe) {
int index = 0;
for(Keyframe kf : keyframes) {
if(kf == timeKeyframe) return index;
else if(kf instanceof TimeKeyframe) index++;
}
return -1;
}
public static int getKeyframeIndex(PositionKeyframe posKeyframe) {
int index = 0;
for(Keyframe kf : keyframes) {
if(kf == posKeyframe) return index;
else if(kf instanceof PositionKeyframe) index++;
}
return -1;
}
public static int getPosKeyframeCount() {
int size = 0;
for(Keyframe kf : keyframes) {
if(kf instanceof PositionKeyframe) size++;
}
return size;
}
public static int getTimeKeyframeCount() {
int size = 0;
for(Keyframe kf : keyframes) {
if(kf instanceof TimeKeyframe) size++;
}
return size;
}
public static TimeKeyframe getClosestTimeKeyframeForRealTime(int realTime, int tolerance) {
List<TimeKeyframe> found = new ArrayList<TimeKeyframe>();
for(Keyframe kf : keyframes) {
if(!(kf instanceof TimeKeyframe)) continue;
if(Math.abs(kf.getRealTimestamp() - realTime) <= tolerance) {
found.add((TimeKeyframe) kf);
}
}
TimeKeyframe closest = null;
for(TimeKeyframe kf : found) {
if(closest == null || Math.abs(closest.getTimestamp() - realTime) > Math.abs(kf.getRealTimestamp() - realTime)) {
closest = kf;
}
}
return closest;
}
public static PositionKeyframe getClosestPlaceKeyframeForRealTime(int realTime, int tolerance) {
List<PositionKeyframe> found = new ArrayList<PositionKeyframe>();
for(Keyframe kf : keyframes) {
if(!(kf instanceof PositionKeyframe)) continue;
if(Math.abs(kf.getRealTimestamp() - realTime) <= tolerance) {
found.add((PositionKeyframe) kf);
}
}
PositionKeyframe closest = null;
for(PositionKeyframe kf : found) {
if(closest == null || Math.abs(closest.getRealTimestamp() - realTime) > Math.abs(kf.getRealTimestamp() - realTime)) {
closest = kf;
}
}
return closest;
fireKeyframesModifyEvent();
}
public static MarkerKeyframe getClosestMarkerForRealTime(int realTime, int tolerance) {
List<MarkerKeyframe> found = new ArrayList<MarkerKeyframe>();
for(Keyframe kf : keyframes) {
if(!(kf instanceof MarkerKeyframe)) continue;
for(MarkerKeyframe kf : markerKeyframes) {
if(Math.abs(kf.getRealTimestamp() - realTime) <= tolerance) {
found.add((MarkerKeyframe) kf);
found.add(kf);
}
}
@@ -365,76 +307,11 @@ public class ReplayHandler {
return closest;
}
public static PositionKeyframe getPreviousPositionKeyframe(int realTime) {
if(keyframes.isEmpty()) return null;
PositionKeyframe backup = null;
List<PositionKeyframe> found = new ArrayList<PositionKeyframe>();
for(Keyframe kf : keyframes) {
if(!(kf instanceof PositionKeyframe)) continue;
if(kf.getRealTimestamp() < realTime) {
found.add((PositionKeyframe)kf);
} else if(kf.getRealTimestamp() == realTime) {
backup = (PositionKeyframe)kf;
}
}
if(found.size() > 0)
return found.get(found.size() - 1); //last element is nearest
else return backup;
}
public static PositionKeyframe getNextPositionKeyframe(int realTime) {
if(keyframes.isEmpty()) return null;
PositionKeyframe backup = null;
for(Keyframe kf : keyframes) {
if(!(kf instanceof PositionKeyframe)) continue;
if(kf.getRealTimestamp() > realTime) {
return (PositionKeyframe)kf; //first found element is next
} else if(kf.getRealTimestamp() == realTime) {
backup = (PositionKeyframe)kf;
}
}
return backup;
}
public static TimeKeyframe getPreviousTimeKeyframe(int realTime) {
if(keyframes.isEmpty()) return null;
TimeKeyframe backup = null;
List<TimeKeyframe> found = new ArrayList<TimeKeyframe>();
for(Keyframe kf : keyframes) {
if(!(kf instanceof TimeKeyframe)) continue;
if(kf.getRealTimestamp() < realTime) {
found.add((TimeKeyframe)kf);
} else if(kf.getRealTimestamp() == realTime) {
backup = (TimeKeyframe)kf;
}
}
if(found.size() > 0)
return found.get(found.size() - 1); //last element is nearest
else return backup;
}
public static TimeKeyframe getNextTimeKeyframe(int realTime) {
if(keyframes.isEmpty()) return null;
TimeKeyframe backup = null;
for(Keyframe kf : keyframes) {
if(!(kf instanceof TimeKeyframe)) continue;
if(kf.getRealTimestamp() > realTime) {
return (TimeKeyframe) kf; //first found element is next
} else if(kf.getRealTimestamp() == realTime) {
backup = (TimeKeyframe)kf;
}
}
return backup;
}
public static MarkerKeyframe getPreviousMarkerKeyframe(int realTime) {
if(keyframes.isEmpty()) return null;
if(markerKeyframes.isEmpty()) return null;
MarkerKeyframe backup = null;
List<MarkerKeyframe> found = new ArrayList<MarkerKeyframe>();
for(Keyframe kf : keyframes) {
if(!(kf instanceof MarkerKeyframe)) continue;
for(MarkerKeyframe kf : markerKeyframes) {
if(kf.getRealTimestamp() < realTime) {
found.add((MarkerKeyframe)kf);
} else if(kf.getRealTimestamp() == realTime) {
@@ -448,21 +325,32 @@ public class ReplayHandler {
}
public static MarkerKeyframe getNextMarkerKeyframe(int realTime) {
if(keyframes.isEmpty()) return null;
if(markerKeyframes.isEmpty()) return null;
MarkerKeyframe backup = null;
for(Keyframe kf : keyframes) {
if(!(kf instanceof MarkerKeyframe)) continue;
for(MarkerKeyframe kf : markerKeyframes) {
if(kf.getRealTimestamp() > realTime) {
return (MarkerKeyframe) kf; //first found element is next
return kf; //first found element is next
} else if(kf.getRealTimestamp() == realTime) {
backup = (MarkerKeyframe)kf;
backup = kf;
}
}
return backup;
}
public static List<Keyframe> getKeyframes() {
return new ArrayList<Keyframe>(keyframes);
public static KeyframeList<Keyframe<Position>> getPositionKeyframes() {
return positionKeyframes;
}
public static KeyframeList<Keyframe<TimestampValue>> getTimeKeyframes() {
return timeKeyframes;
}
public static KeyframeList<Keyframe> getAllKeyframes() {
KeyframeList keyframeList = new KeyframeList();
keyframeList.addAll(positionKeyframes);
keyframeList.addAll(timeKeyframes);
return keyframeList;
}
public static void resetKeyframes(final boolean resetMarkers, boolean callback) {
@@ -483,19 +371,15 @@ public class ReplayHandler {
}
private static void resetKeyframes(boolean resetMarkers) {
MarkerKeyframe[] markers = getMarkers();
keyframes = new ArrayList<Keyframe>();
timeKeyframes.clear();
positionKeyframes.clear();
selectKeyframe(null);
if(!resetMarkers) {
Collections.addAll(keyframes, markers);
if(!(selectedKeyframe instanceof MarkerKeyframe))
selectKeyframe(null);
} else {
selectKeyframe(null);
if(resetMarkers) {
markerKeyframes.clear();
}
FMLCommonHandler.instance().bus().post(new KeyframesModifyEvent(keyframes));
fireKeyframesModifyEvent();
}
public static boolean isSelected(Keyframe kf) {
@@ -504,7 +388,6 @@ public class ReplayHandler {
public static void selectKeyframe(Keyframe kf) {
selectedKeyframe = kf;
sortKeyframes();
}
public static boolean isInReplay() {
@@ -681,44 +564,6 @@ public class ReplayHandler {
return currentReplayFile == null ? null : currentReplayFile.getFile();
}
public static TimeKeyframe getFirstTimeKeyframe() {
Keyframe sel = getSelectedKeyframe();
sortKeyframes();
for(Keyframe k : getKeyframes()) {
if(k instanceof TimeKeyframe) {
selectKeyframe(sel);
return (TimeKeyframe)k;
}
}
selectKeyframe(sel);
return null;
}
public static PositionKeyframe getFirstPositionKeyframe() {
Keyframe sel = getSelectedKeyframe();
sortKeyframes();
for(Keyframe k : getKeyframes()) {
if(k instanceof PositionKeyframe) {
selectKeyframe(sel);
return (PositionKeyframe)k;
}
}
selectKeyframe(sel);
return null;
}
public static TimeKeyframe getLastTimeKeyframe() {
ArrayList<Keyframe> rev = new ArrayList<Keyframe>(getKeyframes());
Collections.reverse(rev);
for(Keyframe k : rev) {
if(k instanceof TimeKeyframe) {
return (TimeKeyframe)k;
}
}
return null;
}
public static void syncTimeCursor(boolean shiftMode) {
selectKeyframe(null);
@@ -726,21 +571,21 @@ public class ReplayHandler {
int prevTime, prevRealTime;
TimeKeyframe keyframe;
Keyframe<TimestampValue> keyframe;
//if shift is down, it will refer to the previous Time Keyframe instead of the last one
if(shiftMode) {
int realTime = getRealTimelineCursor();
keyframe = getPreviousTimeKeyframe(realTime);
keyframe = timeKeyframes.getPreviousKeyframe(realTime);
} else {
keyframe = getLastTimeKeyframe();
keyframe = timeKeyframes.last();
}
if(keyframe == null) {
prevTime = 0;
prevRealTime = 0;
} else {
prevTime = keyframe.getTimestamp();
prevTime = (int)keyframe.getValue().value;
prevRealTime = keyframe.getRealTimestamp();
}
@@ -760,4 +605,9 @@ public class ReplayHandler {
public static void setCustomImageObjects(List<CustomImageObject> objects) {
customImageObjects = objects;
}
public static void fireKeyframesModifyEvent() {
FMLCommonHandler.instance().bus().post(new KeyframesModifyEvent(positionKeyframes, timeKeyframes));
}
}

View File

@@ -4,11 +4,9 @@ import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.chat.ChatMessageHandler.ChatMessageType;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
import eu.crushedpixel.replaymod.holders.TimeKeyframe;
import eu.crushedpixel.replaymod.interpolation.LinearPoint;
import eu.crushedpixel.replaymod.interpolation.LinearTimestamp;
import eu.crushedpixel.replaymod.interpolation.SplinePoint;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.interpolation.GenericLinearInterpolation;
import eu.crushedpixel.replaymod.interpolation.GenericSplineInterpolation;
import eu.crushedpixel.replaymod.settings.RenderOptions;
import eu.crushedpixel.replaymod.timer.EnchantmentTimer;
import eu.crushedpixel.replaymod.timer.ReplayTimer;
@@ -28,9 +26,9 @@ public class ReplayProcess {
private static boolean linear = false;
private static SplinePoint motionSpline = null;
private static LinearPoint motionLinear = null;
private static LinearTimestamp timeLinear = null;
private static GenericSplineInterpolation<Position> motionSpline = null;
private static GenericLinearInterpolation<Position> motionLinear = null;
private static GenericLinearInterpolation<TimestampValue> timeLinear = null;
private static double previousReplaySpeed = 0;
@@ -77,20 +75,19 @@ public class ReplayProcess {
ReplayMod.chatMessageHandler.initialize();
//if not enough keyframes, abort and leave chat message
if(ReplayHandler.getPosKeyframeCount() < 2 || ReplayHandler.getTimeKeyframeCount() < 1) {
if(ReplayHandler.getPositionKeyframes().size() < 2 || ReplayHandler.getTimeKeyframes().size() < 1) {
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.morekeyframes", ChatMessageType.WARNING);
return;
}
ReplayHandler.sortKeyframes();
ReplayHandler.setInPath(true);
ReplayMod.replaySender.setAsyncMode(false);
if (renderOptions == null) {
//gets first Timestamp and sets Replay Time to it
TimeKeyframe tf = ReplayHandler.getFirstTimeKeyframe();
//gets first Value and sets Replay Time to it
Keyframe<TimestampValue> tf = ReplayHandler.getTimeKeyframes().first();
if (tf != null) {
int ts = tf.getTimestamp();
int ts = (int)tf.getValue().value;
if (ts < ReplayMod.replaySender.currentTimeStamp()) {
mc.displayGuiScreen(null);
}
@@ -165,37 +162,29 @@ public class ReplayProcess {
if(justCheck) return;
int posCount = ReplayHandler.getPosKeyframeCount();
int timeCount = ReplayHandler.getTimeKeyframeCount();
int posCount = ReplayHandler.getPositionKeyframes().size();
int timeCount = ReplayHandler.getTimeKeyframes().size();
if(!linear && motionSpline == null) {
//set up spline path
motionSpline = new SplinePoint();
for(Keyframe kf : ReplayHandler.getKeyframes()) {
if(kf instanceof PositionKeyframe) {
PositionKeyframe pkf = (PositionKeyframe) kf;
Position pos = pkf.getPosition();
motionSpline.addPoint(pos);
}
motionSpline = new GenericSplineInterpolation<Position>();
for(Keyframe<Position> kf : ReplayHandler.getPositionKeyframes()) {
motionSpline.addPoint(kf.getValue());
}
}
if(linear && motionLinear == null) {
//set up linear path
motionLinear = new LinearPoint();
for(Keyframe kf : ReplayHandler.getKeyframes()) {
if(kf instanceof PositionKeyframe) {
PositionKeyframe pkf = (PositionKeyframe) kf;
Position pos = pkf.getPosition();
motionLinear.addPoint(pos);
}
motionLinear = new GenericLinearInterpolation<Position>();
for(Keyframe<Position> kf : ReplayHandler.getPositionKeyframes()) {
motionLinear.addPoint(kf.getValue());
}
}
if(timeLinear == null) {
timeLinear = new LinearTimestamp();
for(Keyframe kf : ReplayHandler.getKeyframes()) {
if(kf instanceof TimeKeyframe) {
timeLinear.addPoint(((TimeKeyframe) kf).getTimestamp());
timeLinear = new GenericLinearInterpolation<TimestampValue>();
for(Keyframe<TimestampValue> kf : ReplayHandler.getTimeKeyframes()) {
if(kf.getValue() instanceof TimestampValue) {
timeLinear.addPoint(kf.getValue());
}
}
}
@@ -211,16 +200,15 @@ public class ReplayProcess {
int curRealReplayTime = (int) (lastRealReplayTime + timeStep);
PositionKeyframe lastPos = ReplayHandler.getPreviousPositionKeyframe(curRealReplayTime);
PositionKeyframe nextPos = ReplayHandler.getNextPositionKeyframe(curRealReplayTime);
Keyframe<Position> lastPos = ReplayHandler.getPositionKeyframes().getPreviousKeyframe(curRealReplayTime);
Keyframe<Position> nextPos = ReplayHandler.getPositionKeyframes().getNextKeyframe(curRealReplayTime);
boolean spectating = false;
//if it's between two spectator keyframes sharing the same entity, spectate this entity
if(lastPos != null && nextPos != null) {
if(lastPos.getSpectatedEntityID() != null && nextPos.getSpectatedEntityID() != null) {
if(lastPos.getSpectatedEntityID().equals(nextPos.getSpectatedEntityID())) {
if(lastPos.getValue().getSpectatedEntityID() != null && nextPos.getValue().getSpectatedEntityID() != null) {
if(lastPos.getValue().getSpectatedEntityID().equals(nextPos.getValue().getSpectatedEntityID())) {
spectating = true;
}
}
@@ -245,8 +233,8 @@ public class ReplayProcess {
}
}
TimeKeyframe lastTime = ReplayHandler.getPreviousTimeKeyframe(curRealReplayTime);
TimeKeyframe nextTime = ReplayHandler.getNextTimeKeyframe(curRealReplayTime);
Keyframe<TimestampValue> lastTime = ReplayHandler.getTimeKeyframes().getPreviousKeyframe(curRealReplayTime);
Keyframe<TimestampValue> nextTime = ReplayHandler.getTimeKeyframes().getNextKeyframe(curRealReplayTime);
int lastTimeStamp = 0;
int nextTimeStamp = 0;
@@ -271,7 +259,7 @@ public class ReplayProcess {
}
if(!(nextTime == null || lastTime == null)) {
curSpeed = ((double) ((nextTime.getTimestamp() - lastTime.getTimestamp()))) / ((double) ((nextTimeStamp - lastTimeStamp)));
curSpeed = ((double) (((int)nextTime.getValue().value - (int)lastTime.getValue().value))) / ((double) ((nextTimeStamp - lastTimeStamp)));
}
if(lastTimeStamp == nextTimeStamp) {
@@ -292,47 +280,46 @@ public class ReplayProcess {
float currentTimeStepPerc = (float) currentTime / (float) currentTimeDiff; //The percentage of the travelled path between the current timestamps
if(Float.isInfinite(currentTimeStepPerc)) currentTimeStepPerc = 0;
float splinePos = ((float) ReplayHandler.getKeyframeIndex(lastPos) + currentPosStepPerc) / (float) (posCount - 1);
float timePos = ((float) ReplayHandler.getKeyframeIndex(lastTime) + currentTimeStepPerc) / (float) (timeCount - 1);
float splinePos = ((float) ReplayHandler.getPositionKeyframes().indexOf(lastPos) + currentPosStepPerc) / (float) (posCount - 1);
float timePos = ((float) ReplayHandler.getTimeKeyframes().indexOf(lastTime) + currentTimeStepPerc) / (float) (timeCount - 1);
if(!spectating) {
ReplayHandler.spectateCamera();
Position pos = null;
Position pos = new Position();
if(posCount > 1) {
if(!linear) {
pos = motionSpline.getPoint(Math.max(0, Math.min(1, splinePos)));
motionSpline.applyPoint(Math.max(0, Math.min(1, splinePos)), pos);
} else {
pos = motionLinear.getPoint(Math.max(0, Math.min(1, splinePos)));
motionLinear.applyPoint(Math.max(0, Math.min(1, splinePos)), pos);
}
} else {
if(posCount == 1) {
PositionKeyframe keyframe = ReplayHandler.getFirstPositionKeyframe();
Keyframe<Position> keyframe = ReplayHandler.getPositionKeyframes().first();
assert keyframe != null;
pos = keyframe.getPosition();
pos = keyframe.getValue();
}
}
if(pos != null) {
ReplayHandler.setCameraTilt(pos.getRoll());
ReplayHandler.setCameraTilt((float)pos.getRoll());
ReplayHandler.getCameraEntity().movePath(pos);
}
} else {
ReplayHandler.spectateEntity(mc.theWorld.getEntityByID(lastPos.getSpectatedEntityID()));
ReplayHandler.spectateEntity(mc.theWorld.getEntityByID(lastPos.getValue().getSpectatedEntityID()));
}
Integer curTimestamp = null;
if(timeLinear != null && timeCount > 1) {
curTimestamp = timeLinear.getPoint(Math.max(0, Math.min(1, timePos)));
TimestampValue timestampValue = new TimestampValue();
timeLinear.applyPoint(Math.max(0, Math.min(1, timePos)), timestampValue);
curTimestamp = (int) timestampValue.value;
}
if(!isVideoRecording()) ReplayMod.replaySender.setReplaySpeed(curSpeed);
//if(curSpeed > 0)
if(curTimestamp != null)
ReplayMod.replaySender.sendPacketsTill(curTimestamp);
//splinePos = (index of last entry + add) / total entries
lastRealReplayTime = curRealReplayTime;
lastRealTime = curTime;

View File

@@ -69,7 +69,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
protected boolean asyncMode;
/**
* Timestamp of the last packet sent in milliseconds since the start.
* Value of the last packet sent in milliseconds since the start.
*/
protected int lastTimeStamp;
@@ -91,7 +91,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
/**
* The next packet that should be sent.
* This is required as some actions such as jumping to a specified timestamp have to peek at the next packet.
* This is required as some actions such as jumping to a specified value have to peek at the next packet.
*/
protected PacketData nextPacket;
@@ -188,8 +188,8 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
}
/**
* Return the timestamp of the last packet sent.
* @return The timestamp in milliseconds since the start of the replay
* Return the value of the last packet sent.
* @return The value in milliseconds since the start of the replay
*/
public int currentTimeStamp() {
return lastTimeStamp;
@@ -450,7 +450,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
private long lastPacketSent;
/**
* There is no waiting performed until a packet with at least this timestamp is reached (but not yet sent).
* There is no waiting performed until a packet with at least this value is reached (but not yet sent).
* If this is -1, then timing is normal.
*/
private long desiredTimeStamp = -1;
@@ -573,7 +573,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
/**
* Return whether this replay sender is currently rushing. When rushing, all packets are sent without waiting until
* a specified timestamp is passed.
* a specified value is passed.
* @return {@code true} if currently rushing, {@code false} otherwise
*/
public boolean isHurrying() {
@@ -588,19 +588,19 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
}
/**
* Return the timestamp to which this replay sender is currently rushing. All packets with an lower or equal
* timestamp will be sent out without any sleeping.
* @return The timestamp in milliseconds since the start of the replay
* Return the value to which this replay sender is currently rushing. All packets with an lower or equal
* value will be sent out without any sleeping.
* @return The value in milliseconds since the start of the replay
*/
public long getDesiredTimestamp() {
return desiredTimeStamp;
}
/**
* Jumps to the specified timestamp when in async mode by rushing all packets until one with a timestamp greater
* than the specified timestamp is found.
* If the timestamp has already passed, this causes the replay to restart and then rush all packets.
* @param millis Timestamp in milliseconds since the start of the replay
* Jumps to the specified value when in async mode by rushing all packets until one with a value greater
* than the specified value is found.
* If the value has already passed, this causes the replay to restart and then rush all packets.
* @param millis Value in milliseconds since the start of the replay
*/
public void jumpToTime(int millis) {
Preconditions.checkState(asyncMode, "Can only jump in async mode. Use sendPacketsTill(int) instead.");
@@ -632,9 +632,9 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
/////////////////////////////////////////////////////////
/**
* Sends all packets until the specified timestamp is reached (inclusive).
* If the timestamp is smaller than the last packet sent, the replay is restarted from the beginning.
* @param timestamp The timestamp in milliseconds since the beginning of this replay
* Sends all packets until the specified value is reached (inclusive).
* If the value is smaller than the last packet sent, the replay is restarted from the beginning.
* @param timestamp The value in milliseconds since the beginning of this replay
*/
public void sendPacketsTill(int timestamp) {
Preconditions.checkState(!asyncMode, "This method cannot be used in async mode. Use jumpToTime(int) instead.");
@@ -682,7 +682,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
// Process packet
channelRead(ctx, pd.getByteArray());
// Store last timestamp
// Store last value
lastTimeStamp = nextTimeStamp;
} catch (EOFException eof) {
// Shit! We hit the end before finishing our job! What shall we do now?

View File

@@ -5,12 +5,11 @@ import eu.crushedpixel.replaymod.entities.CameraEntity;
import eu.crushedpixel.replaymod.gui.GuiVideoRenderer;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
import eu.crushedpixel.replaymod.holders.TimeKeyframe;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.interpolation.GenericLinearInterpolation;
import eu.crushedpixel.replaymod.interpolation.GenericSplineInterpolation;
import eu.crushedpixel.replaymod.interpolation.Interpolation;
import eu.crushedpixel.replaymod.interpolation.LinearPoint;
import eu.crushedpixel.replaymod.interpolation.LinearTimestamp;
import eu.crushedpixel.replaymod.interpolation.SplinePoint;
import eu.crushedpixel.replaymod.interpolation.KeyframeList;
import eu.crushedpixel.replaymod.renderer.ChunkLoadingRenderGlobal;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import eu.crushedpixel.replaymod.replay.ReplaySender;
@@ -44,7 +43,7 @@ public class VideoRenderer {
private ChunkLoadingRenderGlobal chunkLoadingRenderGlobal;
private Interpolation<Position> movement;
private Interpolation<Integer> time;
private Interpolation<TimestampValue> time;
private int framesDone;
private int totalFrames;
@@ -80,9 +79,13 @@ public class VideoRenderer {
// Note that it is impossible to also get the interpolation between their latest position
// and the one in the recording correct as there's no reliable way to tell when the server ticks
// or when we should be done with the interpolation of the entity
int videoStart = time.getPoint(0);
TimestampValue timestampValue = new TimestampValue();
time.applyPoint(0, timestampValue);
int videoStart = (int) timestampValue.value;
if (videoStart > 1000) {
int replayTime = time.getPoint(0) - 1000;
int replayTime = videoStart - 1000;
timer.elapsedPartialTicks = timer.renderPartialTicks = 0;
timer.timerSpeed = 1;
while (replayTime < videoStart) {
@@ -132,26 +135,33 @@ public class VideoRenderer {
fps = options.getFps();
if (options.isLinearMovement()) {
movement = new LinearPoint();
movement = new GenericLinearInterpolation<Position>();
} else {
movement = new SplinePoint();
movement = new GenericSplineInterpolation<Position>();
}
time = new LinearTimestamp();
time = new GenericLinearInterpolation<TimestampValue>();
int duration = 0;
int posKeyframes = 0;
for (Keyframe keyframe : ReplayHandler.getKeyframes()) {
for(Keyframe<Position> keyframe : ReplayHandler.getPositionKeyframes()) {
if (keyframe.getRealTimestamp() > duration) {
duration = keyframe.getRealTimestamp();
}
if(keyframe instanceof PositionKeyframe) {
movement.addPoint(((PositionKeyframe) keyframe).getPosition());
posKeyframes++;
}
if (keyframe instanceof TimeKeyframe) {
time.addPoint(((TimeKeyframe) keyframe).getTimestamp());
}
movement.addPoint(keyframe.getValue());
posKeyframes++;
}
for(Keyframe<TimestampValue> keyframe : ReplayHandler.getTimeKeyframes()) {
if (keyframe.getRealTimestamp() > duration) {
duration = keyframe.getRealTimestamp();
}
int timestamp = (int)keyframe.getValue().value;
time.addPoint(new TimestampValue(timestamp));
}
totalFrames = duration*fps/1000;
if (posKeyframes >= 2) {
@@ -195,6 +205,8 @@ public class VideoRenderer {
}
private void updateCam() {
KeyframeList<Keyframe<Position>> positionKeyframes = ReplayHandler.getPositionKeyframes();
if (ReplayHandler.getCameraEntity() == null) {
if (mc.theWorld == null) {
return; // World hasn't been sent yet
@@ -202,19 +214,19 @@ public class VideoRenderer {
ReplayHandler.setCameraEntity(new CameraEntity(mc.theWorld));
}
int videoTime = framesDone * 1000 / fps;
int posCount = ReplayHandler.getPosKeyframeCount();
int posCount = ReplayHandler.getPositionKeyframes().size();
Position pos;
PositionKeyframe lastPos = ReplayHandler.getPreviousPositionKeyframe(videoTime);
PositionKeyframe nextPos = null;
Position pos = new Position();
Keyframe<Position> lastPos = positionKeyframes.getPreviousKeyframe(videoTime);
Keyframe<Position> nextPos = null;
if (movement == null || lastPos == null) {
// Stay at one position, no movement
PositionKeyframe keyframe = ReplayHandler.getNextPositionKeyframe(-1);
Keyframe<Position> keyframe = positionKeyframes.getNextKeyframe(-1);
assert keyframe != null;
pos = keyframe.getPosition();
pos = keyframe.getValue();
} else {
// Position interpolation
nextPos = ReplayHandler.getNextPositionKeyframe(videoTime);
nextPos = positionKeyframes.getNextKeyframe(videoTime);
int lastPosStamp = lastPos.getRealTimestamp();
int nextPosStamp = (nextPos == null ? lastPos : nextPos).getRealTimestamp();
@@ -223,16 +235,16 @@ public class VideoRenderer {
float diffPct = (float) (videoTime - lastPosStamp) / diffLength;
if(Float.isInfinite(diffPct) || Float.isNaN(diffPct)) diffPct = 0;
float totalPct = (ReplayHandler.getKeyframeIndex(lastPos) + diffPct) / (posCount-1);
pos = movement.getPoint(Math.max(0, Math.min(1, totalPct)));
float totalPct = (positionKeyframes.indexOf(lastPos) + diffPct) / (posCount-1);
movement.applyPoint(Math.max(0, Math.min(1, totalPct)), pos);
}
boolean spectating = false;
//if it's between two spectator keyframes sharing the same entity, spectate this entity
if(lastPos != null && nextPos != null) {
if(lastPos.getSpectatedEntityID() != null && nextPos.getSpectatedEntityID() != null) {
if(lastPos.getSpectatedEntityID().equals(nextPos.getSpectatedEntityID())) {
if(lastPos.getValue().getSpectatedEntityID() != null && nextPos.getValue().getSpectatedEntityID() != null) {
if(lastPos.getValue().getSpectatedEntityID().equals(nextPos.getValue().getSpectatedEntityID())) {
spectating = true;
}
}
@@ -245,24 +257,26 @@ public class VideoRenderer {
ReplayHandler.spectateEntity(ReplayHandler.getCameraEntity());
if(pos != null) {
ReplayHandler.setCameraTilt(pos.getRoll());
ReplayHandler.setCameraTilt((float)pos.getRoll());
ReplayHandler.getCameraEntity().movePath(pos);
mc.entityRenderer.fovModifierHand = mc.entityRenderer.fovModifierHandPrev = 1;
}
} else {
ReplayHandler.spectateEntity(mc.theWorld.getEntityByID(lastPos.getSpectatedEntityID()));
ReplayHandler.spectateEntity(mc.theWorld.getEntityByID(lastPos.getValue().getSpectatedEntityID()));
}
}
private void updateTime(Timer timer, int framesDone) {
KeyframeList<Keyframe<TimestampValue>> timeKeyframes = ReplayHandler.getTimeKeyframes();
int videoTime = framesDone * 1000 / fps;
int timeCount = ReplayHandler.getTimeKeyframeCount();
int timeCount = timeKeyframes.size();
// WARNING: The rest of this method contains some magic for which Marius is responsible
// Time interpolation
TimeKeyframe lastTime = ReplayHandler.getPreviousTimeKeyframe(videoTime);
TimeKeyframe nextTime = ReplayHandler.getNextTimeKeyframe(videoTime);
Keyframe<TimestampValue> lastTime = timeKeyframes.getPreviousKeyframe(videoTime);
Keyframe<TimestampValue> nextTime = timeKeyframes.getNextKeyframe(videoTime);
int lastTimeStamp = 0;
int nextTimeStamp = 0;
@@ -286,7 +300,7 @@ public class VideoRenderer {
}
if(!(nextTime == null || lastTime == null)) {
curSpeed = ((double)((nextTime.getTimestamp()-lastTime.getTimestamp())))/((double)((nextTimeStamp-lastTimeStamp)));
curSpeed = ((double)(((int)nextTime.getValue().value-(int)lastTime.getValue().value)))/((double)((nextTimeStamp-lastTimeStamp)));
}
if(lastTimeStamp == nextTimeStamp) {
@@ -301,9 +315,11 @@ public class VideoRenderer {
float currentTimeStepPerc = (float)currentTime/(float)currentTimeDiff; //The percentage of the travelled path between the current timestamps
if(Float.isInfinite(currentTimeStepPerc) || Float.isNaN(currentTimeStepPerc)) currentTimeStepPerc = 0;
float timePos = (ReplayHandler.getKeyframeIndex(lastTime) + currentTimeStepPerc) / (timeCount-1f);
float timePos = (timeKeyframes.indexOf(lastTime) + currentTimeStepPerc) / (timeCount-1f);
Integer replayTime = time.getPoint(Math.max(0, Math.min(1, timePos)));
TimestampValue timestampValue = new TimestampValue();
time.applyPoint(Math.max(0, Math.min(1, timePos)), timestampValue);
Integer replayTime = (int) timestampValue.value;
if(replayTime != null) {
replaySender.sendPacketsTill(replayTime);
}