diff --git a/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java b/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java index 5792fc7a..d41dea3d 100644 --- a/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java +++ b/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java @@ -27,8 +27,8 @@ public class CustomImageObject implements GuiEntryListEntry { @Getter @Setter private float width, height; - private ResourceLocation resourceLocation; - private DynamicTexture dynamicTexture; + private transient ResourceLocation resourceLocation; + private transient DynamicTexture dynamicTexture; @Getter private float textureWidth, textureHeight; @@ -38,6 +38,7 @@ public class CustomImageObject implements GuiEntryListEntry { ReplayAsset asset = ReplayHandler.getAssetRepository().getAssetByUUID(assetUUID); if(asset instanceof ReplayImageAsset) { + this.linkedAsset = assetUUID; setImage(((ReplayImageAsset)asset).getObject()); } else if(asset != null) { throw new UnsupportedOperationException("A CustomImageObject requires a ReplayImageAsset"); @@ -63,16 +64,30 @@ public class CustomImageObject implements GuiEntryListEntry { this.setWidth(w); this.setHeight(h); + resourceLocation = new ResourceLocation(linkedAsset.toString()); + Minecraft.getMinecraft().addScheduledTask(new Runnable() { @Override public void run() { - resourceLocation = new ResourceLocation("customImages/"+linkedAsset.toString()); dynamicTexture = new DynamicTexture(bufferedImage); + + ResourceHelper.freeResource(resourceLocation); } }); } public ResourceLocation getResourceLocation() { + if(resourceLocation == null) { + ReplayAsset asset = ReplayHandler.getAssetRepository().getAssetByUUID(linkedAsset); + if(asset instanceof ReplayImageAsset) { + try { + setImage(((ReplayImageAsset) asset).getObject()); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + if(!ResourceHelper.isRegistered(resourceLocation)) { ResourceHelper.registerResource(resourceLocation); Minecraft.getMinecraft().getTextureManager().loadTexture(resourceLocation, dynamicTexture); diff --git a/src/main/java/eu/crushedpixel/replaymod/assets/CustomObjectRepository.java b/src/main/java/eu/crushedpixel/replaymod/assets/CustomObjectRepository.java new file mode 100644 index 00000000..22856e4d --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/assets/CustomObjectRepository.java @@ -0,0 +1,18 @@ +package eu.crushedpixel.replaymod.assets; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.ArrayList; + +@Data +@AllArgsConstructor +public class CustomObjectRepository { + + public CustomObjectRepository() { + this.objects = new ArrayList(); + } + + private ArrayList objects; + +} diff --git a/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java b/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java index 501a6f90..df3e3f78 100644 --- a/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java +++ b/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java @@ -1,6 +1,7 @@ package eu.crushedpixel.replaymod.assets; import eu.crushedpixel.replaymod.registry.ResourceHelper; +import eu.crushedpixel.replaymod.replay.ReplayHandler; import eu.crushedpixel.replaymod.utils.BoundingUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; @@ -46,6 +47,12 @@ public class ReplayImageAsset implements ReplayAsset { public void loadFromStream(InputStream inputStream) throws IOException { this.object = ImageIO.read(inputStream); ResourceHelper.freeResource(previewResource); + + for(CustomImageObject object : ReplayHandler.getCustomImageObjects()) { + if(object.getLinkedAsset().equals(ReplayHandler.getAssetRepository().getUUIDForAsset(this))) { + object.setImage(this.object); + } + } } @Override diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java index d87b7b5f..e0cd9ec5 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java @@ -1,8 +1,11 @@ package eu.crushedpixel.replaymod.gui; +import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.assets.CustomImageObject; +import eu.crushedpixel.replaymod.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.assets.ReplayAsset; import eu.crushedpixel.replaymod.gui.elements.*; +import eu.crushedpixel.replaymod.gui.elements.listeners.NumberValueChangeListener; import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener; import eu.crushedpixel.replaymod.gui.elements.timelines.GuiTimeline; import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; @@ -10,6 +13,8 @@ import eu.crushedpixel.replaymod.holders.*; import eu.crushedpixel.replaymod.interpolation.KeyframeList; import eu.crushedpixel.replaymod.replay.ReplayHandler; import eu.crushedpixel.replaymod.utils.MouseUtils; +import eu.crushedpixel.replaymod.utils.ReplayFile; +import eu.crushedpixel.replaymod.utils.ReplayFileIO; import lombok.Getter; import lombok.Setter; import net.minecraft.client.Minecraft; @@ -19,10 +24,14 @@ import net.minecraft.client.resources.I18n; import org.lwjgl.util.Point; import java.awt.*; +import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.UUID; +import org.lwjgl.util.Point; + public class GuiObjectManager extends GuiScreen { private boolean initialized = false; @@ -37,11 +46,15 @@ public class GuiObjectManager extends GuiScreen { private GuiDraggingNumberInput anchorXInput, anchorYInput, anchorZInput; private GuiDraggingNumberInput positionXInput, positionYInput, positionZInput; - private GuiDraggingNumberInput scaleXInput, scaleYInput, scaleZInput; private GuiDraggingNumberInput orientationXInput, orientationYInput, orientationZInput; + private GuiDraggingNumberInput scaleXInput, scaleYInput, scaleZInput; private GuiDraggingNumberInput opacityInput; + + private NumberInputGroup anchorNumberInputs, positionNumberInputs, orientationNumberInputs, scaleNumberInputs, opacityNumberInputs; + @Getter private GuiObjectKeyframeTimeline objectKeyframeTimeline; + private GuiScrollbar timelineScrollbar; private GuiTexturedButton zoomInButton, zoomOutButton; @@ -97,29 +110,35 @@ public class GuiObjectManager extends GuiScreen { @Override public void initGui() { if(!initialized) { - anchorXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); - anchorYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); - anchorZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); + anchorXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true, "", 0.1); + anchorYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true, "", 0.1); + anchorZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true, "", 0.1); + anchorNumberInputs = new NumberPositionInputGroup(null, anchorXInput, anchorYInput, anchorZInput); positionXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); positionYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); positionZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); - - scaleXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%"); - scaleYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%"); - scaleZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%"); + positionNumberInputs = new NumberPositionInputGroup(null, positionXInput, positionYInput, positionZInput); orientationXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); orientationYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); orientationZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); + orientationNumberInputs = new NumberPositionInputGroup(null, orientationXInput, orientationYInput, orientationZInput); - opacityInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, 0d, 100d, 100d, true, "%"); + scaleXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%", 0.5); + scaleYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%", 0.5); + scaleZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%", 0.5); + scaleNumberInputs = new NumberPositionInputGroup(null, scaleXInput, scaleYInput, scaleZInput); + + opacityInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, 0d, 100d, 100d, true, "%", 0.5); + opacityNumberInputs = new NumberValueInputGroup(null, opacityInput); dropdownLabel = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.assets.filechooser")+": "); assetDropdown = new GuiDropdown>(fontRendererObj, 0, 0, 0, 5); if(ReplayHandler.getAssetRepository().getCopyOfReplayAssets().isEmpty()) { assetDropdown.addElement(new GuiEntryListValueEntry(I18n.format("replaymod.gui.assets.emptylist"), null)); } else { + assetDropdown.addElement(new GuiEntryListValueEntry(I18n.format("replaymod.gui.assets.noselection"), null)); for(ReplayAsset asset : ReplayHandler.getAssetRepository().getCopyOfReplayAssets()) { assetDropdown.addElement(new GuiEntryListValueEntry( asset.getDisplayString(), ReplayHandler.getAssetRepository().getUUIDForAsset(asset))); @@ -152,6 +171,10 @@ public class GuiObjectManager extends GuiScreen { nameInput = new GuiAdvancedTextField(fontRendererObj, 0, 0, 0, 20); nameInput.hint = I18n.format("replaymod.gui.objects.properties.name"); + for(CustomImageObject customImageObject : ReplayHandler.getCustomImageObjects()) { + objectList.addElement(customImageObject); + } + objectList.addSelectionListener(new SelectionListener() { @Override public void onSelectionChanged(int selectionIndex) { @@ -176,7 +199,17 @@ public class GuiObjectManager extends GuiScreen { assetDropdown.setSelectionIndexQuietly(sel); - objectKeyframeTimeline.setTransformations(selectedObject.getTransformations()); + Transformations transformations = selectedObject.getTransformations(); + + objectKeyframeTimeline.setTransformations(transformations); + + anchorNumberInputs.setUnderlyingKeyframeList(transformations.getAnchorKeyframes()); + positionNumberInputs.setUnderlyingKeyframeList(transformations.getPositionKeyframes()); + orientationNumberInputs.setUnderlyingKeyframeList(transformations.getOrientationKeyframes()); + scaleNumberInputs.setUnderlyingKeyframeList(transformations.getScaleKeyframes()); + opacityNumberInputs.setUnderlyingKeyframeList(transformations.getOpacityKeyframes()); + + updateValuesForTransformation(objectKeyframeTimeline.getTransformations().getTransformationForTimestamp(objectKeyframeTimeline.cursorPosition)); } else { disableElements.setEnabled(false); } @@ -202,8 +235,8 @@ public class GuiObjectManager extends GuiScreen { String[] labelStrings = new String[] { I18n.format("replaymod.gui.objects.properties.anchor"), I18n.format("replaymod.gui.objects.properties.position"), - I18n.format("replaymod.gui.objects.properties.scale"), I18n.format("replaymod.gui.objects.properties.orientation"), + I18n.format("replaymod.gui.objects.properties.scale"), I18n.format("replaymod.gui.objects.properties.opacity"), }; @@ -222,10 +255,10 @@ public class GuiObjectManager extends GuiScreen { anchorInputs = new ComposedElement(anchorXInput, anchorYInput, anchorZInput); positionInputs = new ComposedElement(positionXInput, positionYInput, positionZInput); - scaleInputs = new ComposedElement(scaleXInput, scaleYInput, scaleZInput); orientationInputs = new ComposedElement(orientationXInput, orientationYInput, orientationZInput); + scaleInputs = new ComposedElement(scaleXInput, scaleYInput, scaleZInput); opacityInputs = new ComposedElement(opacityInput); - numberInputs = new ComposedElement(anchorInputs, positionInputs, scaleInputs, orientationInputs, opacityInputs); + numberInputs = new ComposedElement(anchorInputs, positionInputs, orientationInputs, scaleInputs, opacityInputs); for(int i = numberInputs.getParts().size()-1; i >= 0; i--) { int yPos = this.height-5-10-(25*(numberInputs.getParts().size()-i)); @@ -329,6 +362,43 @@ public class GuiObjectManager extends GuiScreen { initialized = true; } + private void saveOnQuit() { + ArrayList objects = objectList.getCopyOfElements(); + ReplayHandler.setCustomImageObjects(objects); + + if(objects.size() > 0) { + try { + File f = File.createTempFile(ReplayFile.ENTRY_CUSTOM_OBJECTS, "json"); + ReplayFileIO.write(new CustomObjectRepository(objects), f); + ReplayMod.replayFileAppender.registerModifiedFile(f, ReplayFile.ENTRY_CUSTOM_OBJECTS, ReplayHandler.getReplayFile()); + } catch(Exception e) { + e.printStackTrace(); + } + } else { + ReplayMod.replayFileAppender.registerModifiedFile(null, ReplayFile.ENTRY_CUSTOM_OBJECTS, ReplayHandler.getReplayFile()); + } + } + + private void updateValuesForTransformation(Transformation transformation) { + anchorXInput.setValueQuietly(transformation.getAnchor().getX()); + anchorYInput.setValueQuietly(transformation.getAnchor().getY()); + anchorZInput.setValueQuietly(transformation.getAnchor().getZ()); + + positionXInput.setValueQuietly(transformation.getPosition().getX()); + positionYInput.setValueQuietly(transformation.getPosition().getY()); + positionZInput.setValueQuietly(transformation.getPosition().getZ()); + + orientationXInput.setValueQuietly(transformation.getOrientation().getX()); + orientationYInput.setValueQuietly(transformation.getOrientation().getY()); + orientationZInput.setValueQuietly(transformation.getOrientation().getZ()); + + scaleXInput.setValueQuietly(transformation.getScale().getX()); + scaleYInput.setValueQuietly(transformation.getScale().getY()); + scaleZInput.setValueQuietly(transformation.getScale().getZ()); + + opacityInput.setValueQuietly(transformation.getOpacity()); + } + @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawDefaultBackground(); @@ -377,6 +447,70 @@ public class GuiObjectManager extends GuiScreen { super.mouseReleased(mouseX, mouseY, mouseButton); } + @Override + public void onGuiClosed() { + saveOnQuit(); + } + + public abstract class NumberInputGroup implements NumberValueChangeListener { + + KeyframeList toModify; + + public void setUnderlyingKeyframeList(KeyframeList toModify) { + this.toModify = toModify; + } + + } + + public class NumberValueInputGroup extends NumberInputGroup { + + public NumberValueInputGroup(KeyframeList toModify, GuiNumberInput input) { + this.toModify = toModify; + + input.addValueChangeListener(this); + } + + @Override + public void onValueChange(double value) { + NumberValue numberValue = new NumberValue(value); + + //if keyframe selected, overwrite its value + if(toModify.contains(objectKeyframeTimeline.getSelectedKeyframe())) { + objectKeyframeTimeline.getSelectedKeyframe().setValue(numberValue); + } else { + toModify.add(new Keyframe(objectKeyframeTimeline.cursorPosition, numberValue)); + } + } + } + + public class NumberPositionInputGroup extends NumberInputGroup { + + public NumberPositionInputGroup(KeyframeList toModify, GuiNumberInput xInput, GuiNumberInput yInput, GuiNumberInput zInput) { + this.toModify = toModify; + this.xInput = xInput; + this.yInput = yInput; + this.zInput = zInput; + + xInput.addValueChangeListener(this); + yInput.addValueChangeListener(this); + zInput.addValueChangeListener(this); + } + + private GuiNumberInput xInput, yInput, zInput; + + @Override + public void onValueChange(double value) { + Position position = new Position(xInput.getPreciseValue(), yInput.getPreciseValue(), zInput.getPreciseValue()); + + //if keyframe selected, overwrite its value + if(toModify.contains(objectKeyframeTimeline.getSelectedKeyframe())) { + objectKeyframeTimeline.getSelectedKeyframe().setValue(position); + } else { + toModify.add(new Keyframe(objectKeyframeTimeline.cursorPosition, position)); + } + } + } + public class GuiObjectKeyframeTimeline extends GuiTimeline { private static final int KEYFRAME_X = 74; @@ -481,6 +615,8 @@ public class GuiObjectManager extends GuiScreen { } cursorPosition = time; + updateValuesForTransformation(getTransformations().getTransformationForTimestamp(cursorPosition)); + dragging = true; return true; } @@ -494,7 +630,10 @@ public class GuiObjectManager extends GuiScreen { super.mouseDrag(mc, mouseX, mouseY, button); if(dragging) { int time = (int) getTimeAt(mouseX, mouseY); - if(time != -1) cursorPosition = time; + if(time != -1) { + cursorPosition = time; + updateValuesForTransformation(getTransformations().getTransformationForTimestamp(cursorPosition)); + } } } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java index 25d51858..6e7b26dc 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java @@ -9,19 +9,23 @@ public class GuiDraggingNumberInput extends GuiNumberInputWithText { public GuiDraggingNumberInput(FontRenderer fontRenderer, int xPos, int yPos, int width, Double minimum, Double maximum, Double defaultValue, boolean acceptFloats) { - this(fontRenderer, xPos, yPos, width, minimum, maximum, defaultValue, acceptFloats, ""); + this(fontRenderer, xPos, yPos, width, minimum, maximum, defaultValue, acceptFloats, "", 0.5f); } public GuiDraggingNumberInput(FontRenderer fontRenderer, int xPos, int yPos, int width, Double minimum, - Double maximum, Double defaultValue, boolean acceptFloats, String suffix) { + Double maximum, Double defaultValue, boolean acceptFloats, String suffix, double stepSize) { super(fontRenderer, xPos, yPos, width, minimum, maximum, defaultValue, acceptFloats, suffix); + + this.stepSize = stepSize; } private int prevMouseX; private boolean dragging; private boolean clicked; + private double stepSize; + @Override public boolean mouseClick(Minecraft mc, int mouseX, int mouseY, int button) { if(MouseUtils.isMouseWithinBounds(xPosition, yPosition, width, height) && isEnabled) { @@ -40,16 +44,10 @@ public class GuiDraggingNumberInput extends GuiNumberInputWithText { int diff = mouseX - prevMouseX; prevMouseX = mouseX; - int bounds = 100; - - if(minimum != null && maximum != null) { - bounds = (int)(maximum-minimum); - } - double value = getPreciseValue(); - double valueDiff = RoundUtils.round2Decimals((diff / 200f) * bounds); + double valueDiff = diff * stepSize; - this.setValue(value+valueDiff); + this.setValue(RoundUtils.round2Decimals(value + valueDiff)); } super.mouseDrag(mc, mouseX, mouseY, button); } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java index fa2de206..c44553f2 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java @@ -150,7 +150,7 @@ public class GuiEntryList extends GuiAdvancedTextFi fireSelectionChangeEvent(); } - public List getCopyOfElements() { + public ArrayList getCopyOfElements() { return new ArrayList(elements); } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java index a26cc5c5..4772ae1c 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java @@ -1,9 +1,15 @@ package eu.crushedpixel.replaymod.gui.elements; +import eu.crushedpixel.replaymod.gui.elements.listeners.NumberValueChangeListener; import net.minecraft.client.gui.FontRenderer; +import java.util.ArrayList; +import java.util.List; + public class GuiNumberInput extends GuiAdvancedTextField { + private List valueChangeListeners = new ArrayList(); + protected Double minimum, maximum; protected boolean acceptFloats = false; @@ -25,7 +31,11 @@ public class GuiNumberInput extends GuiAdvancedTextField { this(fontRenderer, xPos, yPos, width, (double) minimum, (double) maximum, (double) defaultValue, acceptFloats); } - public void setValue(double value) { + /** + * Sets this GuiNumberInput's value without notifying the listeners. + * @param value The value to set + */ + public void setValueQuietly(double value) { if(minimum != null && value < minimum) { setText(acceptFloats ? minimum.toString() : Integer.toString((int) Math.round(minimum))); } else if(maximum != null && value > maximum) { @@ -40,6 +50,22 @@ public class GuiNumberInput extends GuiAdvancedTextField { setCursorPositionZero(); } + public void setValue(double value) { + setValueQuietly(value); + fireValueChangeEvent(); + } + + public void addValueChangeListener(NumberValueChangeListener listener) { + this.valueChangeListeners.add(listener); + } + + private void fireValueChangeEvent() { + double val = acceptFloats ? getPreciseValue() : getIntValue(); + for(NumberValueChangeListener listener : valueChangeListeners) { + listener.onValueChange(val); + } + } + @Override public void writeText(String text) { String textBefore = getText(); @@ -59,6 +85,8 @@ public class GuiNumberInput extends GuiAdvancedTextField { } else if(maximum != null && val > maximum) { setText(acceptFloats ? maximum.toString() : Integer.toString((int) Math.round(maximum))); } + + fireValueChangeEvent(); } catch(NumberFormatException e) { setText(textBefore); setCursorPosition(cursorPositionBefore); diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/listeners/NumberValueChangeListener.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/listeners/NumberValueChangeListener.java new file mode 100644 index 00000000..dd1f0181 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/listeners/NumberValueChangeListener.java @@ -0,0 +1,7 @@ +package eu.crushedpixel.replaymod.gui.elements.listeners; + +public interface NumberValueChangeListener { + + void onValueChange(double value); + +} diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java b/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java index af766dec..d0383cf2 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java @@ -7,10 +7,30 @@ import lombok.Data; @AllArgsConstructor public class Transformation { + /** + * The object's anchor around which modifiers like orientation or position should be applied. + * x=0, y=0, z=0 is the object's center. + */ private Position anchor; + + /** + * The object's position in the Minecraft world. + */ private Position position; + + /** + * The object's orientation. + */ private Position orientation; + + /** + * The object's scale, individual values between 0 and 100. + */ private Position scale; + + /** + * The object's opacity. Value between 0 and 100. + */ private double opacity; } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java b/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java index 55805bf1..7b5db43c 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java @@ -10,6 +10,12 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class Transformations { + private Position defaultAnchor = new Position(0, 0, 0), + defaultPosition = new Position(0, 0, 0), + defaultOrientation = new Position(0, 0, 0), + defaultScale = new Position(100, 100, 100); + private NumberValue defaultOpacity = new NumberValue(100); + private KeyframeList anchorKeyframes = new KeyframeList(); private KeyframeList positionKeyframes = new KeyframeList(); private KeyframeList orientationKeyframes = new KeyframeList(); @@ -34,12 +40,22 @@ public class Transformations { } public Transformation getTransformationForTimestamp(int timestamp) { - return new Transformation( - anchorKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - positionKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - orientationKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - scaleKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - opacityKeyframes.getInterpolatedValueForTimestamp(timestamp, true).value); + Position anchor = anchorKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(anchor == null) anchor = defaultAnchor; + + Position position = positionKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(position == null) position = defaultPosition; + + Position orientation = orientationKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(orientation == null) orientation = defaultOrientation; + + Position scale = scaleKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(scale == null) scale = defaultScale; + + NumberValue opacity = opacityKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(opacity == null) opacity = defaultOpacity; + + return new Transformation(anchor, position, orientation, scale, opacity.value); } } diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java index ce25a975..4e9fc8de 100644 --- a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java @@ -17,6 +17,13 @@ public class KeyframeList extends ArrayList @Override public boolean add(Keyframe t) { + //remove keyframes that have same timestamp + for(Keyframe kf : new ArrayList(this)) { + if(kf.getRealTimestamp() == t.getRealTimestamp()) { + super.remove(kf); + } + } + boolean success = super.add(t); sort(); return success; @@ -137,6 +144,9 @@ public class KeyframeList extends ArrayList } public K getInterpolatedValueForPathPosition(float pathPosition, boolean linear) { + if(first() == null) return null; + if(size() == 1) return first().getValue(); + K toApply = (K)first().getValue().newInstance(); if(previousCallLinear != (Boolean)linear) { diff --git a/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java b/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java index 942c2289..e50e03ae 100644 --- a/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java @@ -1,12 +1,18 @@ package eu.crushedpixel.replaymod.renderer; import eu.crushedpixel.replaymod.assets.CustomImageObject; +import eu.crushedpixel.replaymod.gui.GuiObjectManager; +import eu.crushedpixel.replaymod.holders.Position; +import eu.crushedpixel.replaymod.holders.Transformation; import eu.crushedpixel.replaymod.replay.ReplayHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.entity.Entity; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.util.ResourceLocation; 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. @@ -19,12 +25,9 @@ public class CustomObjectRenderer { public void renderCustomObjects(RenderWorldLastEvent event) { if(!ReplayHandler.isInReplay()) return; - Entity entity = ReplayHandler.getCameraEntity(); - if(entity == null) return; - - double doubleX = entity.posX; - double doubleY = entity.posY; - double doubleZ = entity.posZ; + double dX = mc.getRenderViewEntity().lastTickPosX + (mc.getRenderViewEntity().posX - mc.getRenderViewEntity().lastTickPosX) * (double)event.partialTicks; + double dY = mc.getRenderViewEntity().lastTickPosY + (mc.getRenderViewEntity().posY - mc.getRenderViewEntity().lastTickPosY) * (double)event.partialTicks; + double dZ = mc.getRenderViewEntity().lastTickPosZ + (mc.getRenderViewEntity().posZ - mc.getRenderViewEntity().lastTickPosZ) * (double)event.partialTicks; GlStateManager.pushAttrib(); GlStateManager.pushMatrix(); @@ -35,7 +38,7 @@ public class CustomObjectRenderer { GlStateManager.disableTexture2D(); for(CustomImageObject object : ReplayHandler.getCustomImageObjects()) { - drawCustomImageObject(doubleX, doubleY, doubleZ, object); + drawCustomImageObject(dX, dY, dZ, object); } GlStateManager.enableTexture2D(); @@ -47,8 +50,12 @@ public class CustomObjectRenderer { } private void drawCustomImageObject(double playerX, double playerY, double playerZ, CustomImageObject customImageObject) { - //TODO: Get current Transformations for given Timestamp - /* + ResourceLocation resourceLocation = customImageObject.getResourceLocation(); + + if(customImageObject.getLinkedAsset() == null + || ReplayHandler.getAssetRepository().getAssetByUUID(customImageObject.getLinkedAsset()) == null + || resourceLocation == null) return; + GlStateManager.pushMatrix(); GlStateManager.enableTexture2D(); GlStateManager.enableLighting(); @@ -60,9 +67,20 @@ public class CustomObjectRenderer { Tessellator tessellator = Tessellator.getInstance(); WorldRenderer renderer = tessellator.getWorldRenderer(); - mc.renderEngine.bindTexture(customImageObject.getResourceLocation()); + mc.renderEngine.bindTexture(resourceLocation); - Transformations objectPosition = customImageObject.getPosition(); + int renderTimestamp; + if(mc.currentScreen instanceof GuiObjectManager) { + renderTimestamp = ((GuiObjectManager)mc.currentScreen).getObjectKeyframeTimeline().cursorPosition; + } else { + renderTimestamp = ReplayHandler.getRealTimelineCursor(); + } + + Transformation transformation = customImageObject.getTransformations().getTransformationForTimestamp(renderTimestamp); + + Position objectAnchor = transformation.getAnchor(); + Position objectPosition = transformation.getPosition(); + Position objectOrientation = transformation.getOrientation(); double x = objectPosition.getX() - playerX; double y = objectPosition.getY() - playerY; @@ -72,23 +90,23 @@ public class CustomObjectRenderer { GlStateManager.translate(x, y + 1.4, z); - GlStateManager.rotate(-objectPosition.getYaw(), 0, 1, 0); - GlStateManager.rotate(objectPosition.getRoll(), 0, 0, 1); - GlStateManager.rotate(objectPosition.getPitch(), 1, 0, 0); + GlStateManager.rotate((float)-objectOrientation.getX(), 0, 1, 0); + GlStateManager.rotate((float)objectOrientation.getY(), 0, 0, 1); + GlStateManager.rotate((float) objectOrientation.getZ(), 1, 0, 0); GlStateManager.enableBlend(); GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - float opacity = objectPosition.getOpacity(); + float opacity = (float)transformation.getOpacity() / 100; GlStateManager.color(1, 1, 1, opacity); - float width = objectPosition.getWidth() * objectPosition.getScale(); - float height = objectPosition.getHeight() * objectPosition.getScale(); + float width = (float)(customImageObject.getWidth() * transformation.getScale().getX() / 100f); + float height = (float)(customImageObject.getHeight() * transformation.getScale().getY() / 100f); - float minX = -width/2 + objectPosition.getAnchorX(); - float maxX = width/2 + objectPosition.getAnchorX(); - float minY = -height/2 + objectPosition.getAnchorY(); - float maxY = height/2 + objectPosition.getAnchorY(); + float minX = (float)(-width/2 + objectAnchor.getX()); + float maxX = (float)(width/2 + objectAnchor.getX()); + float minY = (float)(-height/2 + objectAnchor.getY()); + float maxY = (float)(height/2 + objectAnchor.getY()); renderer.startDrawingQuads(); @@ -109,6 +127,5 @@ public class CustomObjectRenderer { GlStateManager.disableTexture2D(); GlStateManager.enableLighting(); GlStateManager.popMatrix(); - */ } } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index 7dcda87e..c056da48 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -4,6 +4,7 @@ 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.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.entities.CameraEntity; import eu.crushedpixel.replaymod.events.KeyframesModifyEvent; import eu.crushedpixel.replaymod.events.ReplayExitEvent; @@ -68,7 +69,7 @@ public class ReplayHandler { @Getter @Setter private static AssetRepository assetRepository; - private static List customImageObjects = new ArrayList(); + private static CustomObjectRepository customImageObjects = new CustomObjectRepository(); /** * The file currently being played. @@ -455,6 +456,11 @@ public class ReplayHandler { AssetRepository assets = currentReplayFile.assetRepository().get(); assetRepository = assets; + //load custom image objects + CustomObjectRepository objectRepository = currentReplayFile.customImageObjects().get(); + customImageObjects = objectRepository; + if(customImageObjects == null) customImageObjects = new CustomObjectRepository(); + ReplayMod.replaySender = new ReplaySender(currentReplayFile, asyncMode); channel.pipeline().addFirst(ReplayMod.replaySender); channel.pipeline().fireChannelActive(); @@ -604,15 +610,15 @@ public class ReplayHandler { } public static List getCustomImageObjects() { - return customImageObjects; + return customImageObjects.getObjects(); } public static void addCustomImageObject(CustomImageObject object) { - customImageObjects.add(object); + customImageObjects.getObjects().add(object); } - public static void setCustomImageObjects(List objects) { - customImageObjects = objects; + public static void setCustomImageObjects(ArrayList objects) { + customImageObjects.setObjects(objects); } public static void fireKeyframesModifyEvent() { diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java index 6e993d83..73b9ecf5 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java @@ -5,6 +5,7 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import eu.crushedpixel.replaymod.assets.AssetRepository; +import eu.crushedpixel.replaymod.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.holders.MarkerKeyframe; import eu.crushedpixel.replaymod.holders.PlayerVisibility; @@ -31,11 +32,12 @@ public class ReplayFile extends ZipFile { public static final String ENTRY_PATHS = "paths" + JSON_FILE_EXTENSION; public static final String ENTRY_THUMB = "thumb"; public static final String ENTRY_RESOURCE_PACK = "resourcepack/%s.zip"; - public static final String ENTRY_RESOURCE_PACK_INDEX = "resourcepack/index.json"; + public static final String ENTRY_RESOURCE_PACK_INDEX = "resourcepack/index"+JSON_FILE_EXTENSION; public static final String ENTRY_VISIBILITY_OLD = "visibility"; public static final String ENTRY_VISIBILITY = "visibility" + JSON_FILE_EXTENSION; public static final String ENTRY_MARKERS = "markers" + JSON_FILE_EXTENSION; public static final String ENTRY_ASSET_FOLDER = "asset/"; + public static final String ENTRY_CUSTOM_OBJECTS = "objects"+JSON_FILE_EXTENSION; private final File file; @@ -258,4 +260,27 @@ public class ReplayFile extends ZipFile { } }; } + + public ZipArchiveEntry customObjectsEntry() { + return getEntry(ENTRY_CUSTOM_OBJECTS); + } + + public Supplier customImageObjects() { + return new Supplier() { + @Override + public CustomObjectRepository get() { + try { + ZipArchiveEntry entry = customObjectsEntry(); + if(entry == null) return null; + + BufferedReader reader = new BufferedReader(new InputStreamReader(getInputStream(entry))); + return new Gson().fromJson(reader, CustomObjectRepository.class); + } catch(Exception e) { + e.printStackTrace(); + } + + return null; + } + }; + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java index dd298749..d0f2c646 100755 --- a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java @@ -2,6 +2,7 @@ package eu.crushedpixel.replaymod.utils; import com.google.gson.Gson; import eu.crushedpixel.replaymod.ReplayMod; +import eu.crushedpixel.replaymod.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.holders.MarkerKeyframe; import eu.crushedpixel.replaymod.holders.PacketData; @@ -169,6 +170,10 @@ public class ReplayFileIO { write((Object) markers, file); } + public static void write(CustomObjectRepository repository, File file) throws IOException { + write((Object) repository, file); + } + /** * Adds Files as entries to a ZIP File. * @param zipFile The ZIP File to add the files to. diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java index d90e8ff6..05e5a5d3 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java @@ -1,7 +1,23 @@ package eu.crushedpixel.replaymod.utils; +import java.text.DecimalFormat; + public class RoundUtils { - public static double round2Decimals(double val) { return Math.round(val*100.0) / 100.0; } + public static double round2Decimals(double val) { + return round(val, 2); + } + + public static double round(double value, int places) { + if (places < 0) throw new IllegalArgumentException(); + + StringBuilder format = new StringBuilder("#."); + for(int i=0; i