Release 2.5.0
This commit is contained in:
@@ -178,7 +178,7 @@ val doRelease by tasks.registering {
|
|||||||
defaultTasks("shadowJar")
|
defaultTasks("shadowJar")
|
||||||
|
|
||||||
preprocess {
|
preprocess {
|
||||||
"1.16.3"(11603, "yarn") {
|
"1.16.4"(11604, "yarn") {
|
||||||
"1.16.1"(11601, "yarn") {
|
"1.16.1"(11601, "yarn") {
|
||||||
"1.15.2"(11502, "yarn") {
|
"1.15.2"(11502, "yarn") {
|
||||||
"1.14.4"(11404, "yarn", file("versions/mapping-fabric-1.15.2-1.14.4.txt")) {
|
"1.14.4"(11404, "yarn", file("versions/mapping-fabric-1.15.2-1.14.4.txt")) {
|
||||||
|
|||||||
@@ -22,14 +22,17 @@ To render your creations with **Replay Mod** you will need to have FFmpeg instal
|
|||||||
### Windows [windows]
|
### Windows [windows]
|
||||||
Download the latest FFmpeg release from https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip.
|
Download the latest FFmpeg release from https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip.
|
||||||
|
|
||||||
In your `.minecraft` folder, create a `ffmpeg` folder. Extract the downloaded .zip file into this folder. The FFmpeg executable should end up at `.minecraft\ffmpeg\bin\ffmpeg.exe`.
|
Extract the downloaded .zip file into your `.minecraft` folder.
|
||||||
|
|
||||||
|
Historically the exact location inside the `.minecraft` folder was important, this is no longer the case.
|
||||||
|
It is sufficient for the `ffmpeg.exe` to be anywhere inside your `.minecraft` folder.
|
||||||
|
|
||||||
**Notes for alternative launchers**
|
**Notes for alternative launchers**
|
||||||
- Twitch launcher by default installs Minecraft instances in C:\\Users\\*username*\\Twitch\\Minecraft\\Instances\\*instancename*\\
|
- Twitch launcher by default installs Minecraft instances in C:\\Users\\*username*\\Twitch\\Minecraft\\Instances\\*instancename*\\
|
||||||
- GD Launcher by default installs Minecraft instances in C:\\Users\\*username*\\AppData\\Roaming\\gdlauncher_next\\instances\\*instancename*\\
|
- GD Launcher by default installs Minecraft instances in C:\\Users\\*username*\\AppData\\Roaming\\gdlauncher_next\\instances\\*instancename*\\
|
||||||
- MultiMC by default installs Minecraft instances in C:\\Program Files (x86)\\MultiMC\\instances\\*Instancename*\\.minecraft\\
|
- MultiMC by default installs Minecraft instances in C:\\Program Files (x86)\\MultiMC\\instances\\*Instancename*\\.minecraft\\
|
||||||
|
|
||||||
For these launchers, make sure FFmpeg exists in `instancename\ffmpeg\bin\ffmpeg.exe` (Twitch / GD) or `Instancename\.minecraft\ffmpeg\bin\ffmpeg.exe` (MultiMC)
|
For these launchers, make sure FFmpeg exists inside the corresponding folder.
|
||||||
|
|
||||||
### Mac OSX [mac]
|
### Mac OSX [mac]
|
||||||
On OSX, you can install **FFmpeg** with **[Homebrew](http://brew.sh/)** using `brew install ffmpeg`.
|
On OSX, you can install **FFmpeg** with **[Homebrew](http://brew.sh/)** using `brew install ffmpeg`.
|
||||||
@@ -144,6 +147,8 @@ If two Keyframes are 5 seconds apart, the Camera Path will take 5 seconds to int
|
|||||||
|
|
||||||
You can drag Keyframes on the **Keyframe Timeline** by left-clicking them, holding the mouse button and moving your mouse.
|
You can drag Keyframes on the **Keyframe Timeline** by left-clicking them, holding the mouse button and moving your mouse.
|
||||||
|
|
||||||
|
> **Hint:** Made a mistake? You can use `Ctrl+Z` to undo your changes to the keyframe timeline. Changed you mind? Press `Ctrl+Y` (or `Ctrl+Shift+Z`) to redo them.
|
||||||
|
|
||||||
### Position Keyframes [place]
|
### Position Keyframes [place]
|
||||||
The basic components of a Camera Path are **Position Keyframes**.
|
The basic components of a Camera Path are **Position Keyframes**.
|
||||||
A Position Keyframe stores a **Camera Position** (x, y, z, yaw, pitch, roll).
|
A Position Keyframe stores a **Camera Position** (x, y, z, yaw, pitch, roll).
|
||||||
@@ -354,6 +359,12 @@ There are **7 Encoding Presets** you can choose from:
|
|||||||
- **PNG Sequence**
|
- **PNG Sequence**
|
||||||
Exports the sequence as individual frames in the **PNG Format**.
|
Exports the sequence as individual frames in the **PNG Format**.
|
||||||
**Warning:** This can create a huge amount of files, so make sure to save them in a separate folder.
|
**Warning:** This can create a huge amount of files, so make sure to save them in a separate folder.
|
||||||
|
- **OpenEXR Sequence** (Minecraft 1.14 and above)
|
||||||
|
Exports the sequence as individual frames in the **OpenEXR Format**.
|
||||||
|
The images are of perfect quality and may contain
|
||||||
|
additional image layers beyond the visible one (e.g. [depth](#replaying-render-advanced-depth-map)).
|
||||||
|
As such, this format is a good choice if you have plenty of disk space to spare and want to perform additional editing
|
||||||
|
on the video in a third-party video editor.
|
||||||
|
|
||||||
### Advanced Settings [advanced]
|
### Advanced Settings [advanced]
|
||||||

|

|
||||||
@@ -380,6 +391,21 @@ Using a **Video Editing Software** like **Adobe After Effects** or **Sony Vegas*
|
|||||||
|
|
||||||
> **Note:** For best results, you should **disable clouds** before rendering, as they are transparent.
|
> **Note:** For best results, you should **disable clouds** before rendering, as they are transparent.
|
||||||
|
|
||||||
|
#### Depth Map [depth-map]
|
||||||
|

|
||||||
|
"Suzanne" model added into a scene using Blender
|
||||||
|
|
||||||
|
This setting causes depth information to be recorded and included in video formats which support it
|
||||||
|
(currently only [OpenEXR](#replaying-render-settings-quality)).
|
||||||
|
|
||||||
|
Using a video editor which supports it (e.g. [Blender](https://www.blender.org/)), you can then for example (among many
|
||||||
|
other effects) add arbitrary geometry onto your rendered video in such a way that foreground objects in the
|
||||||
|
video will still occlude background geometry as if it actually was part of the world.
|
||||||
|
|
||||||
|
> **Note:** For best results, avoid semi-transparent blocks in front of your geometry, as they cannot be accurately represented in the depth map.
|
||||||
|
|
||||||
|
> **Note:** Anti-aliasing is not supported when exporting depth. Instead simply render and edit your video at a higher resolution and scale it down at the very end.
|
||||||
|
|
||||||
### Command Line Settings [commandline]
|
### Command Line Settings [commandline]
|
||||||

|

|
||||||
The **Command Line Render Settings**
|
The **Command Line Render Settings**
|
||||||
|
|||||||
BIN
docs/img/depthmapsuzanne.jpg
Normal file
BIN
docs/img/depthmapsuzanne.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 573 KiB |
BIN
docs/img/depthmapsuzanne.png
Normal file
BIN
docs/img/depthmapsuzanne.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
2
jGui
2
jGui
Submodule jGui updated: 6602aac42a...6b16892d64
@@ -29,7 +29,7 @@ val jGuiVersions = listOf(
|
|||||||
"1.14.4",
|
"1.14.4",
|
||||||
"1.15.2",
|
"1.15.2",
|
||||||
"1.16.1",
|
"1.16.1",
|
||||||
"1.16.3"
|
"1.16.4"
|
||||||
)
|
)
|
||||||
val replayModVersions = listOf(
|
val replayModVersions = listOf(
|
||||||
"1.7.10",
|
"1.7.10",
|
||||||
@@ -46,7 +46,7 @@ val replayModVersions = listOf(
|
|||||||
"1.14.4",
|
"1.14.4",
|
||||||
"1.15.2",
|
"1.15.2",
|
||||||
"1.16.1",
|
"1.16.1",
|
||||||
"1.16.3"
|
"1.16.4"
|
||||||
)
|
)
|
||||||
|
|
||||||
include(":jGui")
|
include(":jGui")
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package com.replaymod.core;
|
|||||||
|
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.replaymod.core.mixin.KeyBindingAccessor;
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||||
|
import com.replaymod.core.events.KeyBindingEventCallback;
|
||||||
|
import com.replaymod.core.events.KeyEventCallback;
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import net.minecraft.client.options.KeyBinding;
|
import net.minecraft.client.options.KeyBinding;
|
||||||
import net.minecraft.util.crash.CrashReport;
|
import net.minecraft.util.crash.CrashReport;
|
||||||
@@ -20,24 +23,24 @@ import static com.replaymod.core.ReplayMod.MOD_ID;
|
|||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
import com.replaymod.core.events.KeyBindingEventCallback;
|
|
||||||
import com.replaymod.core.events.KeyEventCallback;
|
|
||||||
import com.replaymod.core.events.PreRenderCallback;
|
import com.replaymod.core.events.PreRenderCallback;
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
//#else
|
//#else
|
||||||
//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||||
//$$ import net.minecraftforge.fml.common.gameevent.InputEvent;
|
|
||||||
//$$ import net.minecraftforge.fml.common.gameevent.TickEvent;
|
//$$ import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||||
//$$ import org.lwjgl.input.Keyboard;
|
//$$ import org.lwjgl.input.Keyboard;
|
||||||
//$$ import static com.replaymod.core.versions.MCVer.FML_BUS;
|
//$$ import static com.replaymod.core.versions.MCVer.FML_BUS;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class KeyBindingRegistry extends EventRegistrations {
|
public class KeyBindingRegistry extends EventRegistrations {
|
||||||
private static final String CATEGORY = "replaymod.title";
|
private static final String CATEGORY = "replaymod.title";
|
||||||
@@ -45,23 +48,25 @@ public class KeyBindingRegistry extends EventRegistrations {
|
|||||||
static { net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry.INSTANCE.addCategory(CATEGORY); }
|
static { net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry.INSTANCE.addCategory(CATEGORY); }
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
private Map<String, KeyBinding> keyBindings = new HashMap<String, KeyBinding>();
|
private final Map<String, Binding> bindings = new HashMap<>();
|
||||||
private Set<KeyBinding> onlyInReplay = new HashSet<>();
|
private Set<KeyBinding> onlyInReplay = new HashSet<>();
|
||||||
private Multimap<KeyBinding, Runnable> keyBindingHandlers = ArrayListMultimap.create();
|
private Multimap<Integer, Supplier<Boolean>> rawHandlers = ArrayListMultimap.create();
|
||||||
private Multimap<KeyBinding, Runnable> repeatedKeyBindingHandlers = ArrayListMultimap.create();
|
|
||||||
private Multimap<Integer, Runnable> rawHandlers = ArrayListMultimap.create();
|
|
||||||
|
|
||||||
public void registerKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
|
public Binding registerKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
|
||||||
keyBindingHandlers.put(registerKeyBinding(name, keyCode, onlyInRepay), whenPressed);
|
Binding binding = registerKeyBinding(name, keyCode, onlyInRepay);
|
||||||
|
binding.handlers.add(whenPressed);
|
||||||
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerRepeatedKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
|
public Binding registerRepeatedKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
|
||||||
repeatedKeyBindingHandlers.put(registerKeyBinding(name, keyCode, onlyInRepay), whenPressed);
|
Binding binding = registerKeyBinding(name, keyCode, onlyInRepay);
|
||||||
|
binding.repeatedHandlers.add(whenPressed);
|
||||||
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyBinding registerKeyBinding(String name, int keyCode, boolean onlyInRepay) {
|
private Binding registerKeyBinding(String name, int keyCode, boolean onlyInRepay) {
|
||||||
KeyBinding keyBinding = keyBindings.get(name);
|
Binding binding = bindings.get(name);
|
||||||
if (keyBinding == null) {
|
if (binding == null) {
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
keyCode = -1;
|
keyCode = -1;
|
||||||
@@ -69,27 +74,28 @@ public class KeyBindingRegistry extends EventRegistrations {
|
|||||||
Identifier id = new Identifier(MOD_ID, name.substring(LangResourcePack.LEGACY_KEY_PREFIX.length()));
|
Identifier id = new Identifier(MOD_ID, name.substring(LangResourcePack.LEGACY_KEY_PREFIX.length()));
|
||||||
FabricKeyBinding fabricKeyBinding = FabricKeyBinding.Builder.create(id, InputUtil.Type.KEYSYM, keyCode, CATEGORY).build();
|
FabricKeyBinding fabricKeyBinding = FabricKeyBinding.Builder.create(id, InputUtil.Type.KEYSYM, keyCode, CATEGORY).build();
|
||||||
net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry.INSTANCE.register(fabricKeyBinding);
|
net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry.INSTANCE.register(fabricKeyBinding);
|
||||||
keyBinding = fabricKeyBinding;
|
KeyBinding keyBinding = fabricKeyBinding;
|
||||||
//#else
|
//#else
|
||||||
//$$ keyBinding = new KeyBinding(name, keyCode, CATEGORY);
|
//$$ KeyBinding keyBinding = new KeyBinding(name, keyCode, CATEGORY);
|
||||||
//$$ ClientRegistry.registerKeyBinding(keyBinding);
|
//$$ ClientRegistry.registerKeyBinding(keyBinding);
|
||||||
//#endif
|
//#endif
|
||||||
keyBindings.put(name, keyBinding);
|
binding = new Binding(name, keyBinding);
|
||||||
|
bindings.put(name, binding);
|
||||||
if (onlyInRepay) {
|
if (onlyInRepay) {
|
||||||
this.onlyInReplay.add(keyBinding);
|
this.onlyInReplay.add(keyBinding);
|
||||||
}
|
}
|
||||||
} else if (!onlyInRepay) {
|
} else if (!onlyInRepay) {
|
||||||
this.onlyInReplay.remove(keyBinding);
|
this.onlyInReplay.remove(binding.keyBinding);
|
||||||
}
|
}
|
||||||
return keyBinding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerRaw(int keyCode, Runnable whenPressed) {
|
public void registerRaw(int keyCode, Supplier<Boolean> whenPressed) {
|
||||||
rawHandlers.put(keyCode, whenPressed);
|
rawHandlers.put(keyCode, whenPressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, KeyBinding> getKeyBindings() {
|
public Map<String, Binding> getBindings() {
|
||||||
return Collections.unmodifiableMap(keyBindings);
|
return Collections.unmodifiableMap(bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<KeyBinding> getOnlyInReplay() {
|
public Set<KeyBinding> getOnlyInReplay() {
|
||||||
@@ -97,17 +103,9 @@ public class KeyBindingRegistry extends EventRegistrations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
{ on(KeyBindingEventCallback.EVENT, this::handleKeyBindings); }
|
|
||||||
{ on(KeyEventCallback.EVENT, (keyCode, scanCode, action, modifiers) -> handleRaw(keyCode, action)); }
|
|
||||||
{ on(PreRenderCallback.EVENT, this::handleRepeatedKeyBindings); }
|
{ on(PreRenderCallback.EVENT, this::handleRepeatedKeyBindings); }
|
||||||
//#else
|
//#else
|
||||||
//$$ @SubscribeEvent
|
//$$ @SubscribeEvent
|
||||||
//$$ public void onKeyInput(InputEvent.KeyInputEvent event) {
|
|
||||||
//$$ handleKeyBindings();
|
|
||||||
//$$ handleRaw();
|
|
||||||
//$$ }
|
|
||||||
//$$
|
|
||||||
//$$ @SubscribeEvent
|
|
||||||
//$$ public void onTick(TickEvent.RenderTickEvent event) {
|
//$$ public void onTick(TickEvent.RenderTickEvent event) {
|
||||||
//$$ if (event.phase != TickEvent.Phase.START) return;
|
//$$ if (event.phase != TickEvent.Phase.START) return;
|
||||||
//$$ handleRepeatedKeyBindings();
|
//$$ handleRepeatedKeyBindings();
|
||||||
@@ -115,52 +113,106 @@ public class KeyBindingRegistry extends EventRegistrations {
|
|||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
public void handleRepeatedKeyBindings() {
|
public void handleRepeatedKeyBindings() {
|
||||||
for (Map.Entry<KeyBinding, Collection<Runnable>> entry : repeatedKeyBindingHandlers.asMap().entrySet()) {
|
for (Binding binding : bindings.values()) {
|
||||||
if (entry.getKey().isPressed()) {
|
if (binding.keyBinding.isPressed()) {
|
||||||
invokeKeyBindingHandlers(entry.getKey(), entry.getValue());
|
invokeKeyBindingHandlers(binding, binding.repeatedHandlers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleKeyBindings() {
|
{ on(KeyBindingEventCallback.EVENT, this::handleKeyBindings); }
|
||||||
for (Map.Entry<KeyBinding, Collection<Runnable>> entry : keyBindingHandlers.asMap().entrySet()) {
|
private void handleKeyBindings() {
|
||||||
while (entry.getKey().wasPressed()) {
|
for (Binding binding : bindings.values()) {
|
||||||
invokeKeyBindingHandlers(entry.getKey(), entry.getValue());
|
while (binding.keyBinding.wasPressed()) {
|
||||||
|
invokeKeyBindingHandlers(binding, binding.handlers);
|
||||||
|
invokeKeyBindingHandlers(binding, binding.repeatedHandlers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invokeKeyBindingHandlers(KeyBinding keyBinding, Collection<Runnable> handlers) {
|
private void invokeKeyBindingHandlers(Binding binding, Collection<Runnable> handlers) {
|
||||||
for (final Runnable runnable : handlers) {
|
for (final Runnable runnable : handlers) {
|
||||||
try {
|
try {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
} catch (Throwable cause) {
|
} catch (Throwable cause) {
|
||||||
CrashReport crashReport = CrashReport.create(cause, "Handling Key Binding");
|
CrashReport crashReport = CrashReport.create(cause, "Handling Key Binding");
|
||||||
CrashReportSection category = crashReport.addElement("Key Binding");
|
CrashReportSection category = crashReport.addElement("Key Binding");
|
||||||
MCVer.addDetail(category, "Key Binding", keyBinding::toString);
|
MCVer.addDetail(category, "Key Binding", () -> binding.name);
|
||||||
MCVer.addDetail(category, "Handler", runnable::toString);
|
MCVer.addDetail(category, "Handler", runnable::toString);
|
||||||
throw new CrashException(crashReport);
|
throw new CrashException(crashReport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#if MC>=11400
|
{ on(KeyEventCallback.EVENT, (keyCode, scanCode, action, modifiers) -> handleRaw(keyCode, action)); }
|
||||||
private void handleRaw(int keyCode, int action) {
|
private boolean handleRaw(int keyCode, int action) {
|
||||||
if (action != GLFW.GLFW_PRESS) return;
|
if (action != KeyEventCallback.ACTION_PRESS) return false;
|
||||||
//#else
|
for (final Supplier<Boolean> handler : rawHandlers.get(keyCode)) {
|
||||||
//$$ private void handleRaw() {
|
|
||||||
//$$ int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey();
|
|
||||||
//#endif
|
|
||||||
for (final Runnable runnable : rawHandlers.get(keyCode)) {
|
|
||||||
try {
|
try {
|
||||||
runnable.run();
|
if (handler.get()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} catch (Throwable cause) {
|
} catch (Throwable cause) {
|
||||||
CrashReport crashReport = CrashReport.create(cause, "Handling Raw Key Binding");
|
CrashReport crashReport = CrashReport.create(cause, "Handling Raw Key Binding");
|
||||||
CrashReportSection category = crashReport.addElement("Key Binding");
|
CrashReportSection category = crashReport.addElement("Key Binding");
|
||||||
MCVer.addDetail(category, "Key Code", () -> "" + keyCode);
|
MCVer.addDetail(category, "Key Code", () -> "" + keyCode);
|
||||||
MCVer.addDetail(category, "Handler", runnable::toString);
|
MCVer.addDetail(category, "Handler", handler::toString);
|
||||||
throw new CrashException(crashReport);
|
throw new CrashException(crashReport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Binding {
|
||||||
|
public final String name;
|
||||||
|
public final KeyBinding keyBinding;
|
||||||
|
private final List<Runnable> handlers = new ArrayList<>();
|
||||||
|
private final List<Runnable> repeatedHandlers = new ArrayList<>();
|
||||||
|
private boolean autoActivation;
|
||||||
|
private Consumer<Boolean> autoActivationUpdate;
|
||||||
|
|
||||||
|
public Binding(String name, KeyBinding keyBinding) {
|
||||||
|
this.name = name;
|
||||||
|
this.keyBinding = keyBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoundKey() {
|
||||||
|
return MCVer.getBoundKey(keyBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBound() {
|
||||||
|
//#if MC>=11400
|
||||||
|
return !keyBinding.isUnbound();
|
||||||
|
//#else
|
||||||
|
//$$ return keyBinding.getKeyCode() != 0;
|
||||||
|
//#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void trigger() {
|
||||||
|
KeyBindingAccessor acc = (KeyBindingAccessor) keyBinding;
|
||||||
|
acc.setPressTime(acc.getPressTime() + 1);
|
||||||
|
handleKeyBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerAutoActivationSupport(boolean active, Consumer<Boolean> update) {
|
||||||
|
this.autoActivation = active;
|
||||||
|
this.autoActivationUpdate = update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsAutoActivation() {
|
||||||
|
return autoActivationUpdate != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoActivating() {
|
||||||
|
return supportsAutoActivation() && autoActivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoActivating(boolean active) {
|
||||||
|
if (this.autoActivation == active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.autoActivation = active;
|
||||||
|
this.autoActivationUpdate.accept(active);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package com.replaymod.core;
|
package com.replaymod.core;
|
||||||
|
|
||||||
import com.google.common.io.Files;
|
import com.google.common.net.PercentEscaper;
|
||||||
import com.replaymod.compat.ReplayModCompat;
|
import com.replaymod.compat.ReplayModCompat;
|
||||||
import com.replaymod.core.gui.GuiBackgroundProcesses;
|
import com.replaymod.core.gui.GuiBackgroundProcesses;
|
||||||
import com.replaymod.core.gui.GuiReplaySettings;
|
import com.replaymod.core.gui.GuiReplaySettings;
|
||||||
import com.replaymod.core.gui.RestoreReplayGui;
|
import com.replaymod.core.gui.RestoreReplayGui;
|
||||||
import com.replaymod.core.handler.MainMenuHandler;
|
|
||||||
import com.replaymod.core.mixin.MinecraftAccessor;
|
import com.replaymod.core.mixin.MinecraftAccessor;
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.editor.ReplayModEditor;
|
import com.replaymod.editor.ReplayModEditor;
|
||||||
@@ -13,6 +12,8 @@ import com.replaymod.extras.ReplayModExtras;
|
|||||||
import com.replaymod.recording.ReplayModRecording;
|
import com.replaymod.recording.ReplayModRecording;
|
||||||
import com.replaymod.render.ReplayModRender;
|
import com.replaymod.render.ReplayModRender;
|
||||||
import com.replaymod.replay.ReplayModReplay;
|
import com.replaymod.replay.ReplayModReplay;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||||
import com.replaymod.replaystudio.us.myles.ViaVersion.api.protocol.ProtocolVersion;
|
import com.replaymod.replaystudio.us.myles.ViaVersion.api.protocol.ProtocolVersion;
|
||||||
import com.replaymod.replaystudio.util.I18n;
|
import com.replaymod.replaystudio.util.I18n;
|
||||||
@@ -28,6 +29,7 @@ import net.minecraft.text.LiteralText;
|
|||||||
import net.minecraft.text.TranslatableText;
|
import net.minecraft.text.TranslatableText;
|
||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.util.Formatting;
|
||||||
import net.minecraft.util.crash.CrashException;
|
import net.minecraft.util.crash.CrashException;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
@@ -89,10 +91,16 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.SimpleFileVisitor;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
@@ -136,6 +144,7 @@ public class ReplayMod implements
|
|||||||
|
|
||||||
public static final Identifier TEXTURE = new Identifier("replaymod", "replay_gui.png");
|
public static final Identifier TEXTURE = new Identifier("replaymod", "replay_gui.png");
|
||||||
public static final int TEXTURE_SIZE = 256;
|
public static final int TEXTURE_SIZE = 256;
|
||||||
|
public static final Identifier LOGO_FAVICON = new Identifier("replaymod", "favicon_logo.png");
|
||||||
|
|
||||||
private static final MinecraftClient mc = MCVer.getMinecraft();
|
private static final MinecraftClient mc = MCVer.getMinecraft();
|
||||||
|
|
||||||
@@ -216,11 +225,54 @@ public class ReplayMod implements
|
|||||||
return settingsRegistry;
|
return settingsRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getReplayFolder() throws IOException {
|
public Path getReplayFolder() throws IOException {
|
||||||
String path = getSettingsRegistry().get(Setting.RECORDING_PATH);
|
String str = getSettingsRegistry().get(Setting.RECORDING_PATH);
|
||||||
File folder = new File(path.startsWith("./") ? getMinecraft().runDirectory : null, path);
|
return Files.createDirectories(getMinecraft().runDirectory.toPath().resolve(str));
|
||||||
FileUtils.forceMkdir(folder);
|
}
|
||||||
return folder;
|
|
||||||
|
/**
|
||||||
|
* Folder into which replay backups are saved before the MarkerProcessor is unleashed.
|
||||||
|
*/
|
||||||
|
public Path getRawReplayFolder() throws IOException {
|
||||||
|
return Files.createDirectories(getReplayFolder().resolve("raw"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folder into which replays are recorded.
|
||||||
|
* Distinct from the main folder, so they cannot be opened while they are still saving.
|
||||||
|
*/
|
||||||
|
public Path getRecordingFolder() throws IOException {
|
||||||
|
return Files.createDirectories(getReplayFolder().resolve("recording"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folder in which replay cache files are stored.
|
||||||
|
* Distinct from the recording folder cause people kept confusing them with recordings.
|
||||||
|
*/
|
||||||
|
public Path getCacheFolder() throws IOException {
|
||||||
|
String str = getSettingsRegistry().get(Setting.CACHE_PATH);
|
||||||
|
Path path = getMinecraft().runDirectory.toPath().resolve(str);
|
||||||
|
Files.createDirectories(path);
|
||||||
|
try {
|
||||||
|
Files.setAttribute(path, "dos:hidden", true);
|
||||||
|
} catch (UnsupportedOperationException ignored) {
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final PercentEscaper CACHE_FILE_NAME_ENCODER = new PercentEscaper("-_ ", false);
|
||||||
|
|
||||||
|
public Path getCachePathForReplay(Path replay) throws IOException {
|
||||||
|
Path replayFolder = getReplayFolder();
|
||||||
|
Path cacheFolder = getCacheFolder();
|
||||||
|
Path relative = replayFolder.toAbsolutePath().relativize(replay.toAbsolutePath());
|
||||||
|
return cacheFolder.resolve(CACHE_FILE_NAME_ENCODER.escape(relative.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getReplayPathForCache(Path cache) throws IOException {
|
||||||
|
String relative = URLDecoder.decode(cache.getFileName().toString(), "UTF-8");
|
||||||
|
Path replayFolder = getReplayFolder();
|
||||||
|
return replayFolder.resolve(relative);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final DirectoryResourcePack jGuiResourcePack;
|
public static final DirectoryResourcePack jGuiResourcePack;
|
||||||
@@ -323,8 +375,6 @@ public class ReplayMod implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initClient() {
|
public void initClient() {
|
||||||
new MainMenuHandler().register();
|
|
||||||
|
|
||||||
//#if MC<=10710
|
//#if MC<=10710
|
||||||
//$$ FML_BUS.register(this); // For runLater(Runnable)
|
//$$ FML_BUS.register(this); // For runLater(Runnable)
|
||||||
//#endif
|
//#endif
|
||||||
@@ -348,31 +398,56 @@ public class ReplayMod implements
|
|||||||
runPostStartup(() -> {
|
runPostStartup(() -> {
|
||||||
final long DAYS = 24 * 60 * 60 * 1000;
|
final long DAYS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
// Cleanup raw folder content three weeks after creation (these are pretty valuable for debugging)
|
// Cleanup any cache folders still remaining in the recording folder (we once used to put them there)
|
||||||
try {
|
try {
|
||||||
File[] files = new File(getReplayFolder(), "raw").listFiles();
|
Files.walkFileTree(getReplayFolder(), new SimpleFileVisitor<Path>() {
|
||||||
if (files != null) {
|
@Override
|
||||||
for (File file : files) {
|
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||||
if (file.lastModified() + 21 * DAYS < System.currentTimeMillis()) {
|
String name = dir.getFileName().toString();
|
||||||
FileUtils.forceDelete(file);
|
if (name.endsWith(".mcpr.cache")) {
|
||||||
|
FileUtils.deleteDirectory(dir.toFile());
|
||||||
|
return FileVisitResult.SKIP_SUBTREE;
|
||||||
}
|
}
|
||||||
|
return super.preVisitDirectory(dir, attrs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup raw folder content three weeks after creation (these are pretty valuable for debugging)
|
||||||
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getRawReplayFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
if (Files.getLastModifiedTime(path).toMillis() + 21 * DAYS < System.currentTimeMillis()) {
|
||||||
|
Files.delete(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup cache folders 7 days after last modification or when its replay is gone
|
// Move anything which is still in the recording folder into the regular replay folder
|
||||||
try {
|
// so it can be opened and/or recovered
|
||||||
File[] files = getReplayFolder().listFiles();
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getRecordingFolder())) {
|
||||||
if (files != null) {
|
for (Path path : paths) {
|
||||||
for (File file : files) {
|
Path destination = getReplayFolder().resolve(path.getFileName());
|
||||||
String name = file.getName();
|
if (Files.exists(destination)) {
|
||||||
if (file.isDirectory() && name.endsWith(".mcpr.cache")) {
|
continue; // better play it save
|
||||||
File replay = new File(getReplayFolder(), name.substring(0, name.length() - ".cache".length()));
|
|
||||||
if (file.lastModified() + 7 * DAYS < System.currentTimeMillis() || !replay.exists()) {
|
|
||||||
FileUtils.deleteDirectory(file);
|
|
||||||
}
|
}
|
||||||
|
Files.move(path, destination);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup cache folders 7 days after last modification or when its replay is gone
|
||||||
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getCacheFolder())) {
|
||||||
|
for (Path path : paths) {
|
||||||
|
if (Files.isDirectory(path)) {
|
||||||
|
Path replay = getReplayPathForCache(path);
|
||||||
|
long lastModified = Files.getLastModifiedTime(path).toMillis();
|
||||||
|
if (lastModified + 7 * DAYS < System.currentTimeMillis() || !Files.exists(replay)) {
|
||||||
|
FileUtils.deleteDirectory(path.toFile());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,14 +456,13 @@ public class ReplayMod implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup deleted corrupted replays
|
// Cleanup deleted corrupted replays
|
||||||
try {
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getReplayFolder())) {
|
||||||
File[] files = getReplayFolder().listFiles();
|
for (Path path : paths) {
|
||||||
if (files != null) {
|
String name = path.getFileName().toString();
|
||||||
for (File file : files) {
|
if (name.endsWith(".mcpr.del") && Files.isDirectory(path)) {
|
||||||
if (file.isDirectory() && file.getName().endsWith(".mcpr.del")) {
|
long lastModified = Files.getLastModifiedTime(path).toMillis();
|
||||||
if (file.lastModified() + 2 * DAYS < System.currentTimeMillis()) {
|
if (lastModified + 2 * DAYS < System.currentTimeMillis()) {
|
||||||
FileUtils.deleteDirectory(file);
|
FileUtils.deleteDirectory(path.toFile());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,14 +471,12 @@ public class ReplayMod implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore corrupted replays
|
// Restore corrupted replays
|
||||||
try {
|
try (DirectoryStream<Path> paths = Files.newDirectoryStream(getReplayFolder())) {
|
||||||
File[] files = getReplayFolder().listFiles();
|
for (Path path : paths) {
|
||||||
if (files != null) {
|
String name = path.getFileName().toString();
|
||||||
for (File file : files) {
|
if (name.endsWith(".mcpr.tmp") && Files.isDirectory(path)) {
|
||||||
if (file.isDirectory() && file.getName().endsWith(".mcpr.tmp")) {
|
Path original = path.resolveSibling(FilenameUtils.getBaseName(name));
|
||||||
File origFile = new File(file.getParentFile(), Files.getNameWithoutExtension(file.getName()));
|
new RestoreReplayGui(this, GuiScreen.wrap(mc.currentScreen), original.toFile()).display();
|
||||||
new RestoreReplayGui(GuiScreen.wrap(mc.currentScreen), origFile).display();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -700,4 +772,17 @@ public class ReplayMod implements
|
|||||||
return new ReplayStudio().isCompatible(fileFormatVersion, protocolVersion, MCVer.getProtocolVersion());
|
return new ReplayStudio().isCompatible(fileFormatVersion, protocolVersion, MCVer.getProtocolVersion());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReplayFile openReplay(Path path) throws IOException {
|
||||||
|
return openReplay(path, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplayFile openReplay(Path input, Path output) throws IOException {
|
||||||
|
return new ZipReplayFile(
|
||||||
|
new ReplayStudio(),
|
||||||
|
input != null ? input.toFile() : null,
|
||||||
|
output.toFile(),
|
||||||
|
getCachePathForReplay(output).toFile()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.replaymod.core;
|
|||||||
public final class Setting<T> extends SettingsRegistry.SettingKeys<T> {
|
public final class Setting<T> extends SettingsRegistry.SettingKeys<T> {
|
||||||
public static final Setting<Boolean> NOTIFICATIONS = make("notifications", "notifications", true);
|
public static final Setting<Boolean> NOTIFICATIONS = make("notifications", "notifications", true);
|
||||||
public static final Setting<String> RECORDING_PATH = advanced("recordingPath", null, "./replay_recordings/");
|
public static final Setting<String> RECORDING_PATH = advanced("recordingPath", null, "./replay_recordings/");
|
||||||
|
public static final Setting<String> CACHE_PATH = advanced("cachePath", null, "./.replay_cache/");
|
||||||
|
|
||||||
private static <T> Setting<T> make(String key, String displayName, T defaultValue) {
|
private static <T> Setting<T> make(String key, String displayName, T defaultValue) {
|
||||||
return new Setting<>("core", key, displayName, defaultValue);
|
return new Setting<>("core", key, displayName, defaultValue);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class SettingsRegistry {
|
|||||||
JsonElement category = root.get(key.getCategory());
|
JsonElement category = root.get(key.getCategory());
|
||||||
if (category != null && category.isJsonObject()) {
|
if (category != null && category.isJsonObject()) {
|
||||||
JsonElement valueElem = category.getAsJsonObject().get(key.getKey());
|
JsonElement valueElem = category.getAsJsonObject().get(key.getKey());
|
||||||
if (!valueElem.isJsonPrimitive()) continue;
|
if (valueElem == null || !valueElem.isJsonPrimitive()) continue;
|
||||||
JsonPrimitive value = valueElem.getAsJsonPrimitive();
|
JsonPrimitive value = valueElem.getAsJsonPrimitive();
|
||||||
if (key.getDefault() instanceof Boolean && value.isBoolean()) {
|
if (key.getDefault() instanceof Boolean && value.isBoolean()) {
|
||||||
entry.setValue(value.getAsBoolean());
|
entry.setValue(value.getAsBoolean());
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//#if MC>=11400
|
|
||||||
package com.replaymod.core.events;
|
package com.replaymod.core.events;
|
||||||
|
|
||||||
import de.johni0702.minecraft.gui.utils.Event;
|
import de.johni0702.minecraft.gui.utils.Event;
|
||||||
@@ -14,4 +13,3 @@ public interface KeyBindingEventCallback {
|
|||||||
|
|
||||||
void onKeybindingEvent();
|
void onKeybindingEvent();
|
||||||
}
|
}
|
||||||
//#endif
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//#if MC>=11400
|
|
||||||
package com.replaymod.core.events;
|
package com.replaymod.core.events;
|
||||||
|
|
||||||
import de.johni0702.minecraft.gui.utils.Event;
|
import de.johni0702.minecraft.gui.utils.Event;
|
||||||
@@ -7,11 +6,21 @@ public interface KeyEventCallback {
|
|||||||
Event<KeyEventCallback> EVENT = Event.create((listeners) ->
|
Event<KeyEventCallback> EVENT = Event.create((listeners) ->
|
||||||
(key, scanCode, action, modifiers) -> {
|
(key, scanCode, action, modifiers) -> {
|
||||||
for (KeyEventCallback listener : listeners) {
|
for (KeyEventCallback listener : listeners) {
|
||||||
listener.onKeyEvent(key, scanCode, action, modifiers);
|
if (listener.onKeyEvent(key, scanCode, action, modifiers)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
void onKeyEvent(int key, int scanCode, int action, int modifiers);
|
//#if MC>=11400
|
||||||
|
int ACTION_RELEASE = org.lwjgl.glfw.GLFW.GLFW_RELEASE;
|
||||||
|
int ACTION_PRESS = org.lwjgl.glfw.GLFW.GLFW_PRESS;
|
||||||
|
//#else
|
||||||
|
//$$ int ACTION_RELEASE = 0;
|
||||||
|
//$$ int ACTION_PRESS = 1;
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
boolean onKeyEvent(int key, int scanCode, int action, int modifiers);
|
||||||
}
|
}
|
||||||
//#endif
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ public class GuiBackgroundProcesses extends EventRegistrations {
|
|||||||
//#endif
|
//#endif
|
||||||
if (guiScreen != getMinecraft().currentScreen) return; // people tend to construct GuiScreens without opening them
|
if (guiScreen != getMinecraft().currentScreen) return; // people tend to construct GuiScreens without opening them
|
||||||
|
|
||||||
VanillaGuiScreen.setup(guiScreen).setLayout(new CustomLayout<GuiScreen>() {
|
VanillaGuiScreen vanillaGui = VanillaGuiScreen.wrap(guiScreen);
|
||||||
|
vanillaGui.setLayout(new CustomLayout<GuiScreen>(vanillaGui.getLayout()) {
|
||||||
@Override
|
@Override
|
||||||
protected void layout(GuiScreen container, int width, int height) {
|
protected void layout(GuiScreen container, int width, int height) {
|
||||||
pos(panel, width - 5 - width(panel), 5);
|
pos(panel, width - 5 - width(panel), 5);
|
||||||
@@ -76,6 +77,11 @@ public class GuiBackgroundProcesses extends EventRegistrations {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayer() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadableDimension getMinSize() {
|
public ReadableDimension getMinSize() {
|
||||||
ReadableDimension minSize = inner.getMinSize();
|
ReadableDimension minSize = inner.getMinSize();
|
||||||
|
|||||||
17
src/main/java/com/replaymod/core/gui/GuiReplayButton.java
Normal file
17
src/main/java/com/replaymod/core/gui/GuiReplayButton.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.replaymod.core.gui;
|
||||||
|
|
||||||
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
|
||||||
|
public class GuiReplayButton extends GuiButton {
|
||||||
|
@Override
|
||||||
|
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||||
|
super.draw(renderer, size, renderInfo);
|
||||||
|
|
||||||
|
renderer.bindTexture(ReplayMod.LOGO_FAVICON);
|
||||||
|
renderer.drawTexturedRect(3, 3, 0, 0, size.getWidth() - 6, size.getHeight() - 6, 1, 1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,32 +2,40 @@ package com.replaymod.core.gui;
|
|||||||
|
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.core.utils.Utils;
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.replaystudio.io.ReplayInputStream;
|
import com.replaymod.replaystudio.io.ReplayInputStream;
|
||||||
import com.replaymod.replaystudio.io.ReplayOutputStream;
|
import com.replaymod.replaystudio.io.ReplayOutputStream;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
|
||||||
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
|
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
|
||||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||||
|
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||||
|
import de.johni0702.minecraft.gui.utils.Colors;
|
||||||
|
import net.minecraft.util.crash.CrashReport;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static com.replaymod.replaystudio.util.Utils.readInt;
|
import static com.replaymod.replaystudio.util.Utils.readInt;
|
||||||
import static com.replaymod.replaystudio.util.Utils.writeInt;
|
import static com.replaymod.replaystudio.util.Utils.writeInt;
|
||||||
|
|
||||||
public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
||||||
|
private static Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
public final GuiScreen parent;
|
public final GuiScreen parent;
|
||||||
public final File file;
|
public final File file;
|
||||||
@@ -37,8 +45,10 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
|||||||
textPanel, buttonPanel).setLayout(new VerticalLayout().setSpacing(20));
|
textPanel, buttonPanel).setLayout(new VerticalLayout().setSpacing(20));
|
||||||
public final GuiButton yesButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.yes");
|
public final GuiButton yesButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.yes");
|
||||||
public final GuiButton noButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.no");
|
public final GuiButton noButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.no");
|
||||||
|
private final ReplayMod core;
|
||||||
|
|
||||||
public RestoreReplayGui(GuiScreen parent, File file) {
|
public RestoreReplayGui(ReplayMod core, GuiScreen parent, File file) {
|
||||||
|
this.core = core;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
|
|
||||||
@@ -47,12 +57,62 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
|||||||
new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())),
|
new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())),
|
||||||
new GuiLabel().setI18nText("replaymod.gui.restorereplay3"));
|
new GuiLabel().setI18nText("replaymod.gui.restorereplay3"));
|
||||||
yesButton.onClick(() -> {
|
yesButton.onClick(() -> {
|
||||||
|
recoverInBackground();
|
||||||
|
parent.display();
|
||||||
|
});
|
||||||
|
noButton.onClick(() -> {
|
||||||
try {
|
try {
|
||||||
ReplayStudio studio = new ReplayStudio();
|
File tmp = new File(file.getParentFile(), file.getName() + ".tmp");
|
||||||
ReplayFile replayFile = new ZipReplayFile(studio, null, file);
|
File deleted = new File(file.getParentFile(), file.getName() + ".del");
|
||||||
|
if (deleted.exists()) {
|
||||||
|
FileUtils.deleteDirectory(deleted);
|
||||||
|
}
|
||||||
|
Files.move(tmp, deleted);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
parent.display();
|
||||||
|
});
|
||||||
|
|
||||||
|
setLayout(new CustomLayout<RestoreReplayGui>() {
|
||||||
|
@Override
|
||||||
|
protected void layout(RestoreReplayGui container, int width, int height) {
|
||||||
|
pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RestoreReplayGui getThis() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recoverInBackground() {
|
||||||
|
GuiLabel label = new GuiLabel().setI18nText("replaymod.gui.replaysaving.title").setColor(Colors.BLACK);
|
||||||
|
GuiProgressBar progressBar = new GuiProgressBar().setHeight(14);
|
||||||
|
GuiPanel savingProcess = new GuiPanel()
|
||||||
|
.setLayout(new VerticalLayout())
|
||||||
|
.addElements(new VerticalLayout.Data(0.5), label, progressBar);
|
||||||
|
new Thread(() -> {
|
||||||
|
core.runLater(() -> core.getBackgroundProcesses().addProcess(savingProcess));
|
||||||
|
try {
|
||||||
|
tryRecover(progressBar::setProgress);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Recovering replay file:", e);
|
||||||
|
CrashReport crashReport = CrashReport.create(e, "Recovering replay file");
|
||||||
|
core.runLater(() -> Utils.error(LOGGER, VanillaGuiScreen.wrap(getMinecraft().currentScreen), crashReport, () -> {}));
|
||||||
|
} finally {
|
||||||
|
core.runLater(() -> core.getBackgroundProcesses().removeProcess(savingProcess));
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryRecover(Consumer<Float> progress) throws IOException {
|
||||||
|
ReplayFile replayFile = ReplayMod.instance.openReplay(file.toPath());
|
||||||
// Commit all not-yet-committed files into the main zip file.
|
// Commit all not-yet-committed files into the main zip file.
|
||||||
// If we don't do this, then re-writing packet data below can actually overwrite uncommitted packet data!
|
// If we don't do this, then re-writing packet data below can actually overwrite uncommitted packet data!
|
||||||
replayFile.save();
|
replayFile.save();
|
||||||
|
progress.accept(0.4f);
|
||||||
ReplayMetaData metaData = replayFile.getMetaData();
|
ReplayMetaData metaData = replayFile.getMetaData();
|
||||||
if (metaData != null && metaData.getDuration() == 0) {
|
if (metaData != null && metaData.getDuration() == 0) {
|
||||||
// Try to restore replay duration
|
// Try to restore replay duration
|
||||||
@@ -88,37 +148,10 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
|||||||
out.write(json.getBytes());
|
out.write(json.getBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
progress.accept(0.6f);
|
||||||
replayFile.save();
|
replayFile.save();
|
||||||
|
progress.accept(0.9f);
|
||||||
replayFile.close();
|
replayFile.close();
|
||||||
} catch (IOException e) {
|
progress.accept(1f);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
parent.display();
|
|
||||||
});
|
|
||||||
noButton.onClick(() -> {
|
|
||||||
try {
|
|
||||||
File tmp = new File(file.getParentFile(), file.getName() + ".tmp");
|
|
||||||
File deleted = new File(file.getParentFile(), file.getName() + ".del");
|
|
||||||
if (deleted.exists()) {
|
|
||||||
FileUtils.deleteDirectory(deleted);
|
|
||||||
}
|
|
||||||
Files.move(tmp, deleted);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
parent.display();
|
|
||||||
});
|
|
||||||
|
|
||||||
setLayout(new CustomLayout<RestoreReplayGui>() {
|
|
||||||
@Override
|
|
||||||
protected void layout(RestoreReplayGui container, int width, int height) {
|
|
||||||
pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RestoreReplayGui getThis() {
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
package com.replaymod.core.handler;
|
|
||||||
|
|
||||||
import com.replaymod.core.mixin.GuiMainMenuAccessor;
|
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.screen.TitleScreen;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import org.lwjgl.opengl.GL11;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.replaymod.core.versions.MCVer.*;
|
|
||||||
|
|
||||||
//#if MC>=11400 && MC<11600
|
|
||||||
//$$ import net.minecraft.realms.RealmsScreenProxy;
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
//#if FABRIC>=1
|
|
||||||
import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback;
|
|
||||||
//#else
|
|
||||||
//$$ import net.minecraftforge.client.event.GuiScreenEvent;
|
|
||||||
//$$ import net.minecraftforge.common.MinecraftForge;
|
|
||||||
//$$ import net.minecraftforge.eventbus.api.SubscribeEvent;
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
//#if MC>=11400
|
|
||||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
//#else
|
|
||||||
//$$ import net.minecraft.client.gui.GuiButton;
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves certain buttons on the main menu upwards so we can inject our own.
|
|
||||||
*/
|
|
||||||
public class MainMenuHandler extends EventRegistrations {
|
|
||||||
//#if FABRIC>=1
|
|
||||||
{ on(InitScreenCallback.EVENT, this::onInit); }
|
|
||||||
public void onInit(Screen guiScreen, List<AbstractButtonWidget> buttonList) {
|
|
||||||
//#else
|
|
||||||
//$$ @SubscribeEvent
|
|
||||||
//$$ public void onInit(GuiScreenEvent.InitGuiEvent.Post event) {
|
|
||||||
//$$ Screen guiScreen = getGui(event);
|
|
||||||
//$$ List<Widget> buttonList = getButtonList(event);
|
|
||||||
//#endif
|
|
||||||
if (guiScreen instanceof TitleScreen) {
|
|
||||||
TitleScreen gui = (TitleScreen) guiScreen;
|
|
||||||
int realmsOffset = 0;
|
|
||||||
//#if MC>=11400
|
|
||||||
final String BUTTON_REALMS = I18n.translate("menu.online");
|
|
||||||
for (AbstractButtonWidget button : buttonList) {
|
|
||||||
//#else
|
|
||||||
//$$ for (GuiButton button : buttonList) {
|
|
||||||
//#endif
|
|
||||||
// Buttons that aren't in a rectangle directly above our space don't need moving
|
|
||||||
if (button.x + width(button) < gui.width / 2 - 100
|
|
||||||
|| button.x > gui.width / 2 + 100
|
|
||||||
|| button.y > gui.height / 4 + 10 + 4 * 24 + 20) continue;
|
|
||||||
// Move button up to make space for one rows of buttons
|
|
||||||
// and then move back down by 10 to compensate for the space to the exit button that was already there
|
|
||||||
int offset = -1 * 24 + 10;
|
|
||||||
button.y += offset;
|
|
||||||
|
|
||||||
//#if MC>=11400
|
|
||||||
//#if MC>=11400
|
|
||||||
if (BUTTON_REALMS.equals(button.getMessage())) {
|
|
||||||
//#else
|
|
||||||
//$$ if (button.id == 14) {
|
|
||||||
//#endif
|
|
||||||
realmsOffset = offset;
|
|
||||||
}
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
//#if MC>=11400 && MC<11600
|
|
||||||
//$$ GuiMainMenuAccessor guiA = (GuiMainMenuAccessor) gui;
|
|
||||||
//$$ if (realmsOffset != 0 && guiA.getRealmsNotification() instanceof RealmsScreenProxy) {
|
|
||||||
//$$ guiA.setRealmsNotification(new RealmsNotificationProxy((RealmsScreenProxy) guiA.getRealmsNotification(), realmsOffset));
|
|
||||||
//$$ }
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#if MC>=11400 && MC<11600
|
|
||||||
//$$ private static class RealmsNotificationProxy extends Screen {
|
|
||||||
//$$ private final RealmsScreenProxy proxy;
|
|
||||||
//$$ private final int offset;
|
|
||||||
//$$
|
|
||||||
//$$ private RealmsNotificationProxy(RealmsScreenProxy proxy, int offset) {
|
|
||||||
//#if MC>=11400
|
|
||||||
//$$ super(null);
|
|
||||||
//#endif
|
|
||||||
//$$ this.proxy = proxy;
|
|
||||||
//$$ this.offset = offset;
|
|
||||||
//$$ }
|
|
||||||
//$$
|
|
||||||
//$$ @Override
|
|
||||||
//$$ public void init(MinecraftClient mc, int width, int height) {
|
|
||||||
//$$ proxy.init(mc, width, height);
|
|
||||||
//$$ }
|
|
||||||
//$$
|
|
||||||
//$$ @Override
|
|
||||||
//$$ public void tick() {
|
|
||||||
//$$ proxy.tick();
|
|
||||||
//$$ }
|
|
||||||
//$$
|
|
||||||
//$$ @Override
|
|
||||||
//$$ public void render(int mouseX, int mouseY, float partialTicks) {
|
|
||||||
//$$ GL11.glTranslated(0, offset, 0);
|
|
||||||
//$$ proxy.render(mouseX, mouseY - offset, partialTicks);
|
|
||||||
//$$ GL11.glTranslated(0, -offset, 0);
|
|
||||||
//$$ }
|
|
||||||
//$$
|
|
||||||
//$$ @Override
|
|
||||||
//$$ public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
|
|
||||||
//$$ return proxy.mouseClicked(mouseX, mouseY - offset, mouseButton);
|
|
||||||
//$$ }
|
|
||||||
//$$
|
|
||||||
//$$ @Override
|
|
||||||
//$$ public void removed() {
|
|
||||||
//$$ proxy.removed();
|
|
||||||
//$$ }
|
|
||||||
//$$ }
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,51 @@
|
|||||||
//#if MC>=11400
|
|
||||||
package com.replaymod.core.mixin;
|
package com.replaymod.core.mixin;
|
||||||
|
|
||||||
import com.replaymod.core.events.KeyBindingEventCallback;
|
import com.replaymod.core.events.KeyBindingEventCallback;
|
||||||
import com.replaymod.core.events.KeyEventCallback;
|
import com.replaymod.core.events.KeyEventCallback;
|
||||||
import net.minecraft.client.Keyboard;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
//#if MC>=11400
|
||||||
|
import net.minecraft.client.Keyboard;
|
||||||
|
//#else
|
||||||
|
//$$ import net.minecraft.client.settings.KeyBinding;
|
||||||
|
//$$ import org.lwjgl.input.Keyboard;
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
//#if MC>=11400
|
||||||
@Mixin(Keyboard.class)
|
@Mixin(Keyboard.class)
|
||||||
public class MixinKeyboardListener {
|
public class MixinKeyboardListener {
|
||||||
@Inject(method = "onKey", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/options/KeyBinding;onKeyPressed(Lnet/minecraft/client/util/InputUtil$Key;)V", shift = At.Shift.AFTER))
|
private static final String ON_KEY_PRESSED = "Lnet/minecraft/client/options/KeyBinding;onKeyPressed(Lnet/minecraft/client/util/InputUtil$Key;)V";
|
||||||
|
|
||||||
|
@Inject(method = "onKey", at = @At(value = "INVOKE", target = ON_KEY_PRESSED), cancellable = true)
|
||||||
|
private void beforeKeyBindingTick(long windowPointer, int key, int scanCode, int action, int modifiers, CallbackInfo ci) {
|
||||||
|
if (KeyEventCallback.EVENT.invoker().onKeyEvent(key, scanCode, action, modifiers)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onKey", at = @At(value = "INVOKE", target = ON_KEY_PRESSED, shift = At.Shift.AFTER))
|
||||||
private void afterKeyBindingTick(long windowPointer, int key, int scanCode, int action, int modifiers, CallbackInfo ci) {
|
private void afterKeyBindingTick(long windowPointer, int key, int scanCode, int action, int modifiers, CallbackInfo ci) {
|
||||||
KeyBindingEventCallback.EVENT.invoker().onKeybindingEvent();
|
KeyBindingEventCallback.EVENT.invoker().onKeybindingEvent();
|
||||||
KeyEventCallback.EVENT.invoker().onKeyEvent(key, scanCode, action, modifiers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//#else
|
||||||
|
//$$ @Mixin(KeyBinding.class)
|
||||||
|
//$$ public class MixinKeyboardListener {
|
||||||
|
//$$ @Inject(method = "onTick", at = @At("HEAD"), cancellable = true)
|
||||||
|
//$$ private static void beforeKeyBindingTick(CallbackInfo ci) {
|
||||||
|
//$$ int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey();
|
||||||
|
//$$ int action = Keyboard.getEventKeyState() ? KeyEventCallback.ACTION_PRESS : KeyEventCallback.ACTION_RELEASE;
|
||||||
|
//$$ if (KeyEventCallback.EVENT.invoker().onKeyEvent(keyCode, 0, action, 0)) {
|
||||||
|
//$$ ci.cancel();
|
||||||
|
//$$ }
|
||||||
|
//$$ }
|
||||||
|
//$$
|
||||||
|
//$$ @Inject(method = "onTick", at = @At("RETURN"))
|
||||||
|
//$$ private static void afterKeyBindingTick(CallbackInfo ci) {
|
||||||
|
//$$ KeyBindingEventCallback.EVENT.invoker().onKeybindingEvent();
|
||||||
|
//$$ }
|
||||||
|
//$$ }
|
||||||
//#endif
|
//#endif
|
||||||
|
|||||||
@@ -367,4 +367,9 @@ public class Utils {
|
|||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T configure(T instance, Consumer<T> configure) {
|
||||||
|
configure.accept(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.replaymod.replaystudio.us.myles.ViaVersion.api.protocol.ProtocolVersi
|
|||||||
import com.replaymod.replaystudio.us.myles.ViaVersion.packets.State;
|
import com.replaymod.replaystudio.us.myles.ViaVersion.packets.State;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.client.options.KeyBinding;
|
||||||
import net.minecraft.client.world.ClientWorld;
|
import net.minecraft.client.world.ClientWorld;
|
||||||
import net.minecraft.client.render.Tessellator;
|
import net.minecraft.client.render.Tessellator;
|
||||||
import net.minecraft.client.render.entity.EntityRenderDispatcher;
|
import net.minecraft.client.render.entity.EntityRenderDispatcher;
|
||||||
@@ -37,7 +38,6 @@ import java.util.ArrayList;
|
|||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
import com.replaymod.core.mixin.AbstractButtonWidgetAccessor;
|
import com.replaymod.core.mixin.AbstractButtonWidgetAccessor;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.client.gui.screen.TitleScreen;
|
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
||||||
|
|
||||||
@@ -597,13 +597,6 @@ public class MCVer {
|
|||||||
//#endif
|
//#endif
|
||||||
) {
|
) {
|
||||||
GuiScreenAccessor acc = (GuiScreenAccessor) screen;
|
GuiScreenAccessor acc = (GuiScreenAccessor) screen;
|
||||||
//#if MC>=11400
|
|
||||||
if (screen instanceof TitleScreen && isModLoaded("modmenu")) {
|
|
||||||
// Since we bypass the usual addButton, we need to manually move our buttons down
|
|
||||||
// https://github.com/Prospector/ModMenu/blob/eea70ec37581a7229142cf21df795fdacc6a7b4c/src/main/java/io/github/prospector/modmenu/mixin/TitleScreenMixin.java#L42
|
|
||||||
button.y += 12;
|
|
||||||
}
|
|
||||||
//#endif
|
|
||||||
acc.getButtons().add(button);
|
acc.getButtons().add(button);
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
acc.getChildren().add(button);
|
acc.getChildren().add(button);
|
||||||
@@ -748,6 +741,23 @@ public class MCVer {
|
|||||||
//#endif
|
//#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getBoundKey(KeyBinding keyBinding) {
|
||||||
|
try {
|
||||||
|
//#if MC>=11600
|
||||||
|
return keyBinding.getBoundKeyLocalizedText().getString();
|
||||||
|
//#else
|
||||||
|
//#if MC>=11400
|
||||||
|
//$$ return keyBinding.getLocalizedName();
|
||||||
|
//#else
|
||||||
|
//$$ return Keyboard.getKeyName(keyBinding.getKeyCode());
|
||||||
|
//#endif
|
||||||
|
//#endif
|
||||||
|
} catch (ArrayIndexOutOfBoundsException e) {
|
||||||
|
// Apparently windows likes to press strange keys, see https://www.replaymod.com/forum/thread/55
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
private static Boolean hasOptifine;
|
private static Boolean hasOptifine;
|
||||||
public static boolean hasOptifine() {
|
public static boolean hasOptifine() {
|
||||||
@@ -869,6 +879,10 @@ public class MCVer {
|
|||||||
//$$ public static final int KEY_Z = org.lwjgl.input.Keyboard.KEY_Z;
|
//$$ public static final int KEY_Z = org.lwjgl.input.Keyboard.KEY_Z;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
public static boolean hasControlDown() {
|
||||||
|
return Screen.hasControlDown();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isKeyDown(int keyCode) {
|
public static boolean isKeyDown(int keyCode) {
|
||||||
//#if MC>=11500
|
//#if MC>=11500
|
||||||
return InputUtil.isKeyPressed(getMinecraft().getWindow().getHandle(), keyCode);
|
return InputUtil.isKeyPressed(getMinecraft().getWindow().getHandle(), keyCode);
|
||||||
@@ -893,6 +907,10 @@ public class MCVer {
|
|||||||
//$$ public static boolean getEventKeyState() {
|
//$$ public static boolean getEventKeyState() {
|
||||||
//$$ return org.lwjgl.input.Keyboard.getEventKeyState();
|
//$$ return org.lwjgl.input.Keyboard.getEventKeyState();
|
||||||
//$$ }
|
//$$ }
|
||||||
|
//$$
|
||||||
|
//$$ public static String getKeyName(int code) {
|
||||||
|
//$$ return org.lwjgl.input.Keyboard.getKeyName(code);
|
||||||
|
//$$ }
|
||||||
//#endif
|
//#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import com.replaymod.core.utils.Utils;
|
|||||||
import com.replaymod.editor.ReplayModEditor;
|
import com.replaymod.editor.ReplayModEditor;
|
||||||
import com.replaymod.replay.gui.overlay.GuiMarkerTimeline;
|
import com.replaymod.replay.gui.overlay.GuiMarkerTimeline;
|
||||||
import com.replaymod.replaystudio.data.Marker;
|
import com.replaymod.replaystudio.data.Marker;
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
import de.johni0702.minecraft.gui.element.GuiHorizontalScrollbar;
|
import de.johni0702.minecraft.gui.element.GuiHorizontalScrollbar;
|
||||||
import de.johni0702.minecraft.gui.element.GuiTexturedButton;
|
|
||||||
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||||
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||||
import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime;
|
import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime;
|
||||||
@@ -42,14 +40,14 @@ public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
|
|||||||
private final GuiHorizontalScrollbar scrollbar = new GuiHorizontalScrollbar().setSize(300, 9);
|
private final GuiHorizontalScrollbar scrollbar = new GuiHorizontalScrollbar().setSize(300, 9);
|
||||||
|
|
||||||
|
|
||||||
private final GuiTexturedButton zoomInButton = new GuiTexturedButton().setSize(9, 9)
|
private final GuiButton zoomInButton = new GuiButton().setSize(9, 9)
|
||||||
.onClick(() -> zoomTimeline(2d / 3d))
|
.onClick(() -> zoomTimeline(2d / 3d))
|
||||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20)
|
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setSpriteUV(40, 20)
|
||||||
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomin"));
|
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomin"));
|
||||||
|
|
||||||
private final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9)
|
private final GuiButton zoomOutButton = new GuiButton().setSize(9, 9)
|
||||||
.onClick(() -> zoomTimeline(3d / 2d))
|
.onClick(() -> zoomTimeline(3d / 2d))
|
||||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30)
|
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setSpriteUV(40, 30)
|
||||||
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomout"));
|
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomout"));
|
||||||
|
|
||||||
private final GuiPanel zoomButtonPanel = new GuiPanel()
|
private final GuiPanel zoomButtonPanel = new GuiPanel()
|
||||||
@@ -62,7 +60,7 @@ public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
|
|||||||
super(container);
|
super(container);
|
||||||
this.inputPath = inputPath;
|
this.inputPath = inputPath;
|
||||||
|
|
||||||
try (ZipReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), inputPath.toFile())) {
|
try (ReplayFile replayFile = ReplayMod.instance.openReplay(inputPath)) {
|
||||||
markers = replayFile.getMarkers().or(HashSet::new);
|
markers = replayFile.getMarkers().or(HashSet::new);
|
||||||
timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers);
|
timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers);
|
||||||
timeline.setSize(300, 20)
|
timeline.setSize(300, 20)
|
||||||
@@ -149,7 +147,7 @@ public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
|
|||||||
ProgressPopup progressPopup = new ProgressPopup(this);
|
ProgressPopup progressPopup = new ProgressPopup(this);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try (ZipReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), inputPath.toFile())) {
|
try (ReplayFile replayFile = ReplayMod.instance.openReplay(inputPath)) {
|
||||||
replayFile.writeMarkers(markers);
|
replayFile.writeMarkers(markers);
|
||||||
replayFile.save();
|
replayFile.save();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.replaymod.editor.gui;
|
package com.replaymod.editor.gui;
|
||||||
|
|
||||||
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.replaystudio.PacketData;
|
import com.replaymod.replaystudio.PacketData;
|
||||||
import com.replaymod.replaystudio.Studio;
|
|
||||||
import com.replaymod.replaystudio.data.Marker;
|
import com.replaymod.replaystudio.data.Marker;
|
||||||
import com.replaymod.replaystudio.filter.SquashFilter;
|
import com.replaymod.replaystudio.filter.SquashFilter;
|
||||||
import com.replaymod.replaystudio.filter.StreamFilter;
|
import com.replaymod.replaystudio.filter.StreamFilter;
|
||||||
@@ -11,13 +11,11 @@ import com.replaymod.replaystudio.io.ReplayOutputStream;
|
|||||||
import com.replaymod.replaystudio.protocol.PacketTypeRegistry;
|
import com.replaymod.replaystudio.protocol.PacketTypeRegistry;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
|
||||||
import com.replaymod.replaystudio.stream.IteratorStream;
|
import com.replaymod.replaystudio.stream.IteratorStream;
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair;
|
||||||
import com.replaymod.replaystudio.util.Utils;
|
import com.replaymod.replaystudio.util.Utils;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -50,7 +48,7 @@ public class MarkerProcessor {
|
|||||||
public static final String MARKER_NAME_SPLIT = "_RM_SPLIT";
|
public static final String MARKER_NAME_SPLIT = "_RM_SPLIT";
|
||||||
|
|
||||||
private static boolean hasWork(Path path) throws IOException {
|
private static boolean hasWork(Path path) throws IOException {
|
||||||
try (ZipReplayFile inputReplayFile = new ZipReplayFile(new ReplayStudio(), path.toFile())) {
|
try (ReplayFile inputReplayFile = ReplayMod.instance.openReplay(path)) {
|
||||||
return inputReplayFile.getMarkers().or(HashSet::new).stream().anyMatch(m -> m.getName() != null && m.getName().startsWith("_RM_"));
|
return inputReplayFile.getMarkers().or(HashSet::new).stream().anyMatch(m -> m.getName() != null && m.getName().startsWith("_RM_"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,27 +104,33 @@ public class MarkerProcessor {
|
|||||||
return suffixes;
|
return suffixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void apply(Path path, Consumer<Float> progress) throws IOException {
|
public static List<Pair<Path, ReplayMetaData>> apply(Path path, Consumer<Float> progress) throws IOException {
|
||||||
|
ReplayMod mod = ReplayMod.instance;
|
||||||
if (!hasWork(path)) {
|
if (!hasWork(path)) {
|
||||||
return;
|
ReplayMetaData metaData;
|
||||||
|
try (ReplayFile inputReplayFile = mod.openReplay(path)) {
|
||||||
|
metaData = inputReplayFile.getMetaData();
|
||||||
|
}
|
||||||
|
return Collections.singletonList(new Pair<>(path, metaData));
|
||||||
}
|
}
|
||||||
|
|
||||||
String replayName = FilenameUtils.getBaseName(path.getFileName().toString());
|
String replayName = FilenameUtils.getBaseName(path.getFileName().toString());
|
||||||
int splitCounter = 0;
|
int splitCounter = 0;
|
||||||
|
|
||||||
PacketTypeRegistry registry = MCVer.getPacketTypeRegistry(true);
|
PacketTypeRegistry registry = MCVer.getPacketTypeRegistry(true);
|
||||||
Studio studio = new ReplayStudio();
|
|
||||||
SquashFilter squashFilter = new SquashFilter();
|
SquashFilter squashFilter = new SquashFilter();
|
||||||
squashFilter.init(studio, null);
|
|
||||||
|
|
||||||
Path inputPath = path.resolveSibling("raw").resolve(path.getFileName());
|
List<Pair<Path, ReplayMetaData>> outputPaths = new ArrayList<>();
|
||||||
|
|
||||||
|
Path rawFolder = ReplayMod.instance.getRawReplayFolder();
|
||||||
|
Path inputPath = rawFolder.resolve(path.getFileName());
|
||||||
for (int i = 1; Files.exists(inputPath); i++) {
|
for (int i = 1; Files.exists(inputPath); i++) {
|
||||||
inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr");
|
inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr");
|
||||||
}
|
}
|
||||||
Files.createDirectories(inputPath.getParent());
|
Files.createDirectories(inputPath.getParent());
|
||||||
Files.move(path, inputPath);
|
Files.move(path, inputPath);
|
||||||
|
|
||||||
try (ZipReplayFile inputReplayFile = new ZipReplayFile(studio, inputPath.toFile())) {
|
try (ReplayFile inputReplayFile = mod.openReplay(inputPath)) {
|
||||||
List<Marker> markers = inputReplayFile.getMarkers().or(HashSet::new)
|
List<Marker> markers = inputReplayFile.getMarkers().or(HashSet::new)
|
||||||
.stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList());
|
.stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList());
|
||||||
Iterator<Marker> markerIterator = markers.iterator();
|
Iterator<Marker> markerIterator = markers.iterator();
|
||||||
@@ -142,8 +146,8 @@ public class MarkerProcessor {
|
|||||||
Marker nextMarker = markerIterator.next();
|
Marker nextMarker = markerIterator.next();
|
||||||
|
|
||||||
while (nextPacket != null && outputFileSuffixes.hasNext()) {
|
while (nextPacket != null && outputFileSuffixes.hasNext()) {
|
||||||
File outputFile = path.resolveSibling(replayName + outputFileSuffixes.next() + ".mcpr").toFile();
|
Path outputPath = path.resolveSibling(replayName + outputFileSuffixes.next() + ".mcpr");
|
||||||
try (ZipReplayFile outputReplayFile = new ZipReplayFile(studio, null, outputFile)) {
|
try (ReplayFile outputReplayFile = mod.openReplay(null, outputPath)) {
|
||||||
long duration = 0;
|
long duration = 0;
|
||||||
Set<Marker> outputMarkers = new HashSet<>();
|
Set<Marker> outputMarkers = new HashSet<>();
|
||||||
ReplayMetaData metaData = inputReplayFile.getMetaData();
|
ReplayMetaData metaData = inputReplayFile.getMetaData();
|
||||||
@@ -240,6 +244,8 @@ public class MarkerProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outputReplayFile.save();
|
outputReplayFile.save();
|
||||||
|
|
||||||
|
outputPaths.add(new Pair(outputPath, metaData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,5 +254,7 @@ public class MarkerProcessor {
|
|||||||
cutFilter.release();
|
cutFilter.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return outputPaths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class GuiHandler extends EventRegistrations {
|
|||||||
replayViewer.replaySpecificButtons.add(new GuiButton(replayViewer.editorButton).onClick(() -> {
|
replayViewer.replaySpecificButtons.add(new GuiButton(replayViewer.editorButton).onClick(() -> {
|
||||||
if (Utils.ifMinimalModeDoPopup(replayViewer, () -> {})) return;
|
if (Utils.ifMinimalModeDoPopup(replayViewer, () -> {})) return;
|
||||||
try {
|
try {
|
||||||
new GuiEditReplay(replayViewer, replayViewer.list.getSelected().file.toPath()) {
|
new GuiEditReplay(replayViewer, replayViewer.list.getSelected().get(0).file.toPath()) {
|
||||||
@Override
|
@Override
|
||||||
protected void close() {
|
protected void close() {
|
||||||
super.close();
|
super.close();
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import de.johni0702.minecraft.gui.element.IGuiImage;
|
|||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||||
|
import net.minecraft.entity.effect.StatusEffects;
|
||||||
|
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
import com.replaymod.core.events.PreRenderCallback;
|
import com.replaymod.core.events.PreRenderCallback;
|
||||||
@@ -21,6 +23,7 @@ import com.replaymod.core.events.PostRenderCallback;
|
|||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
public class FullBrightness extends EventRegistrations implements Extra {
|
public class FullBrightness extends EventRegistrations implements Extra {
|
||||||
|
private ReplayMod core;
|
||||||
private ReplayModReplay module;
|
private ReplayModReplay module;
|
||||||
|
|
||||||
private final IGuiImage indicator = new GuiImage().setTexture(ReplayMod.TEXTURE, 90, 20, 19, 16).setSize(19, 16);
|
private final IGuiImage indicator = new GuiImage().setTexture(ReplayMod.TEXTURE, 90, 20, 19, 16).setSize(19, 16);
|
||||||
@@ -35,6 +38,7 @@ public class FullBrightness extends EventRegistrations implements Extra {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void register(final ReplayMod mod) throws Exception {
|
public void register(final ReplayMod mod) throws Exception {
|
||||||
|
this.core = mod;
|
||||||
this.module = ReplayModReplay.instance;
|
this.module = ReplayModReplay.instance;
|
||||||
this.mc = mod.getMinecraft();
|
this.mc = mod.getMinecraft();
|
||||||
|
|
||||||
@@ -58,6 +62,16 @@ public class FullBrightness extends EventRegistrations implements Extra {
|
|||||||
register();
|
register();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Type getType() {
|
||||||
|
String str = core.getSettingsRegistry().get(Setting.FULL_BRIGHTNESS);
|
||||||
|
for (Type type : Type.values()) {
|
||||||
|
if (type.toString().equals(str)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Type.Gamma;
|
||||||
|
}
|
||||||
|
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
{ on(PreRenderCallback.EVENT, this::preRender); }
|
{ on(PreRenderCallback.EVENT, this::preRender); }
|
||||||
private void preRender() {
|
private void preRender() {
|
||||||
@@ -67,9 +81,21 @@ public class FullBrightness extends EventRegistrations implements Extra {
|
|||||||
//$$ if (event.phase != TickEvent.Phase.START) return;
|
//$$ if (event.phase != TickEvent.Phase.START) return;
|
||||||
//#endif
|
//#endif
|
||||||
if (active && module.getReplayHandler() != null) {
|
if (active && module.getReplayHandler() != null) {
|
||||||
|
Type type = getType();
|
||||||
|
if (type == Type.Gamma || type == Type.Both) {
|
||||||
originalGamma = mc.options.gamma;
|
originalGamma = mc.options.gamma;
|
||||||
mc.options.gamma = 1000;
|
mc.options.gamma = 1000;
|
||||||
}
|
}
|
||||||
|
if (type == Type.NightVision || type == Type.Both) {
|
||||||
|
if (mc.player != null) {
|
||||||
|
mc.player.addStatusEffect(new StatusEffectInstance(StatusEffects.NIGHT_VISION
|
||||||
|
//#if MC<=10809
|
||||||
|
//$$ .id
|
||||||
|
//#endif
|
||||||
|
, Integer.MAX_VALUE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
@@ -81,8 +107,20 @@ public class FullBrightness extends EventRegistrations implements Extra {
|
|||||||
//$$ if (event.phase != TickEvent.Phase.END) return;
|
//$$ if (event.phase != TickEvent.Phase.END) return;
|
||||||
//#endif
|
//#endif
|
||||||
if (active && module.getReplayHandler() != null) {
|
if (active && module.getReplayHandler() != null) {
|
||||||
|
Type type = getType();
|
||||||
|
if (type == Type.Gamma || type == Type.Both) {
|
||||||
mc.options.gamma = originalGamma;
|
mc.options.gamma = originalGamma;
|
||||||
}
|
}
|
||||||
|
if (type == Type.NightVision || type == Type.Both) {
|
||||||
|
if (mc.player != null) {
|
||||||
|
mc.player.removeStatusEffect(StatusEffects.NIGHT_VISION
|
||||||
|
//#if MC<=10809
|
||||||
|
//$$ .id
|
||||||
|
//#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{ on(ReplayOpenedCallback.EVENT, replayHandler -> updateIndicator(replayHandler.getOverlay())); }
|
{ on(ReplayOpenedCallback.EVENT, replayHandler -> updateIndicator(replayHandler.getOverlay())); }
|
||||||
@@ -93,4 +131,16 @@ public class FullBrightness extends EventRegistrations implements Extra {
|
|||||||
overlay.statusIndicatorPanel.removeElement(indicator);
|
overlay.statusIndicatorPanel.removeElement(indicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
Gamma,
|
||||||
|
NightVision,
|
||||||
|
Both,
|
||||||
|
;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "replaymod.gui.settings.fullbrightness." + name().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,23 @@ package com.replaymod.extras;
|
|||||||
|
|
||||||
import com.replaymod.core.KeyBindingRegistry;
|
import com.replaymod.core.KeyBindingRegistry;
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.core.mixin.KeyBindingAccessor;
|
|
||||||
import com.replaymod.replay.events.ReplayOpenedCallback;
|
import com.replaymod.replay.events.ReplayOpenedCallback;
|
||||||
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
|
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.RenderInfo;
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
import de.johni0702.minecraft.gui.element.GuiElement;
|
import de.johni0702.minecraft.gui.element.GuiElement;
|
||||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||||
import de.johni0702.minecraft.gui.element.GuiTexturedButton;
|
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.GridLayout;
|
import de.johni0702.minecraft.gui.layout.GridLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
|
||||||
import de.johni0702.minecraft.gui.layout.LayoutData;
|
import de.johni0702.minecraft.gui.layout.LayoutData;
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import net.minecraft.client.resource.language.I18n;
|
import net.minecraft.client.resource.language.I18n;
|
||||||
|
|
||||||
//#if MC>=11400
|
|
||||||
//#else
|
|
||||||
//$$ import org.lwjgl.input.Keyboard;
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -44,15 +36,15 @@ public class HotkeyButtons extends EventRegistrations implements Extra {
|
|||||||
|
|
||||||
{ on(ReplayOpenedCallback.EVENT, replayHandler -> new Gui(mod, replayHandler.getOverlay())); }
|
{ on(ReplayOpenedCallback.EVENT, replayHandler -> new Gui(mod, replayHandler.getOverlay())); }
|
||||||
public static final class Gui {
|
public static final class Gui {
|
||||||
private final GuiTexturedButton toggleButton;
|
private final GuiButton toggleButton;
|
||||||
private final GridLayout panelLayout;
|
private final GridLayout panelLayout;
|
||||||
private final GuiPanel panel;
|
private final GuiPanel panel;
|
||||||
|
|
||||||
private boolean open;
|
private boolean open;
|
||||||
|
|
||||||
public Gui(ReplayMod mod, GuiReplayOverlay overlay) {
|
public Gui(ReplayMod mod, GuiReplayOverlay overlay) {
|
||||||
toggleButton = new GuiTexturedButton(overlay).setSize(20, 20)
|
toggleButton = new GuiButton(overlay).setSize(20, 20)
|
||||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(0, 120)
|
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setSpriteUV(0, 120)
|
||||||
.onClick(new Runnable() {
|
.onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -73,50 +65,51 @@ public class HotkeyButtons extends EventRegistrations implements Extra {
|
|||||||
}.setLayout(panelLayout = new GridLayout().setSpacingX(5).setSpacingY(5).setColumns(1));
|
}.setLayout(panelLayout = new GridLayout().setSpacingX(5).setSpacingY(5).setColumns(1));
|
||||||
|
|
||||||
final KeyBindingRegistry keyBindingRegistry = mod.getKeyBindingRegistry();
|
final KeyBindingRegistry keyBindingRegistry = mod.getKeyBindingRegistry();
|
||||||
keyBindingRegistry.getKeyBindings().values().stream()
|
keyBindingRegistry.getBindings().values().stream()
|
||||||
.sorted(Comparator.comparing(it -> I18n.translate(it.getTranslationKey())))
|
.sorted(Comparator.comparing(it -> I18n.translate(it.name)))
|
||||||
.forEachOrdered(keyBinding -> {
|
.forEachOrdered(keyBinding -> {
|
||||||
GuiButton button = new GuiButton(){
|
GuiButton button = new GuiButton(){
|
||||||
@Override
|
@Override
|
||||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||||
// There doesn't seem to be an KeyBindingUpdate event, so we'll just update it every time
|
// There doesn't seem to be an KeyBindingUpdate event, so we'll just update it every time
|
||||||
String keyName = "???";
|
setLabel(keyBinding.isBound() ? keyBinding.getBoundKey() : "");
|
||||||
try {
|
|
||||||
//#if MC>=11600
|
if (keyBinding.supportsAutoActivation()) {
|
||||||
keyName = keyBinding.getBoundKeyLocalizedText().asString();
|
setTooltip(new GuiTooltip().setText(new String[]{
|
||||||
//#else
|
I18n.translate("replaymod.gui.ingame.autoactivating"),
|
||||||
//#if MC>=11400
|
I18n.translate("replaymod.gui.ingame.autoactivating."
|
||||||
//$$ keyName = keyBinding.getLocalizedName();
|
+ (keyBinding.isAutoActivating() ? "disable" : "enable")),
|
||||||
//#else
|
}));
|
||||||
//$$ keyName = Keyboard.getKeyName(keyBinding.getKeyCode());
|
setLabelColor(keyBinding.isAutoActivating() ? 0x00ff00 : 0xe0e0e0);
|
||||||
//#endif
|
|
||||||
//#endif
|
|
||||||
} catch (ArrayIndexOutOfBoundsException e) {
|
|
||||||
// Apparently windows likes to press strange keys, see https://www.replaymod.com/forum/thread/55
|
|
||||||
}
|
}
|
||||||
setLabel(keyName);
|
|
||||||
super.draw(renderer, size, renderInfo);
|
super.draw(renderer, size, renderInfo);
|
||||||
}
|
}
|
||||||
}.onClick(() -> {
|
}.onClick(() -> {
|
||||||
KeyBindingAccessor acc = (KeyBindingAccessor) keyBinding;
|
if (keyBinding.supportsAutoActivation() && Screen.hasControlDown()) {
|
||||||
acc.setPressTime(acc.getPressTime() + 1);
|
keyBinding.setAutoActivating(!keyBinding.isAutoActivating());
|
||||||
keyBindingRegistry.handleKeyBindings();
|
} else {
|
||||||
|
keyBinding.trigger();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
panel.addElements(null, new GuiPanel().setSize(150, 20).setLayout(new HorizontalLayout().setSpacing(2))
|
GuiLabel label = new GuiLabel().setI18nText(keyBinding.name);
|
||||||
.addElements(new HorizontalLayout.Data(0.5),
|
panel.addElements(null, new GuiPanel().setLayout(new CustomLayout<GuiPanel>() {
|
||||||
new GuiPanel().setLayout(new CustomLayout<GuiPanel>() {
|
|
||||||
@Override
|
@Override
|
||||||
protected void layout(GuiPanel container, int width, int height) {
|
protected void layout(GuiPanel container, int width, int height) {
|
||||||
size(button, width, height);
|
width(button, Math.max(10 /* consistent min width */, width(button)) + 10 /* padding */);
|
||||||
}
|
height(button, 20);
|
||||||
|
|
||||||
@Override
|
int textWidth = width(label);
|
||||||
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
|
||||||
return new Dimension(Math.max(10, button.getMinSize().getWidth()) + 10, 20);
|
x(label, width(button) + 4);
|
||||||
|
width(label, width - x(label));
|
||||||
|
|
||||||
|
if (textWidth > width - x(label)) {
|
||||||
|
height(label, height(label) * 2); // split over two lines
|
||||||
}
|
}
|
||||||
}).addElements(null, button),
|
y(label, (height - height(label)) / 2);
|
||||||
new GuiLabel().setI18nText(keyBinding.getTranslationKey())
|
}
|
||||||
));
|
}).addElements(null, button, label).setSize(150, 20));
|
||||||
});
|
});
|
||||||
|
|
||||||
overlay.setLayout(new CustomLayout<GuiReplayOverlay>(overlay.getLayout()) {
|
overlay.setLayout(new CustomLayout<GuiReplayOverlay>(overlay.getLayout()) {
|
||||||
|
|||||||
@@ -2,9 +2,18 @@ package com.replaymod.extras;
|
|||||||
|
|
||||||
import com.replaymod.core.SettingsRegistry;
|
import com.replaymod.core.SettingsRegistry;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public final class Setting<T> {
|
public final class Setting<T> {
|
||||||
public static final SettingsRegistry.SettingKey<Boolean> ASK_FOR_OPEN_EYE =
|
public static final SettingsRegistry.SettingKey<Boolean> ASK_FOR_OPEN_EYE =
|
||||||
new SettingsRegistry.SettingKeys<>("advanced", "askForOpenEye", null, true);
|
new SettingsRegistry.SettingKeys<>("advanced", "askForOpenEye", null, true);
|
||||||
public static final SettingsRegistry.SettingKey<Boolean> SKIP_POST_SCREENSHOT_GUI =
|
public static final SettingsRegistry.SettingKey<Boolean> SKIP_POST_SCREENSHOT_GUI =
|
||||||
new SettingsRegistry.SettingKeys<>("advanced", "skipPostScreenshotGui", null, false);
|
new SettingsRegistry.SettingKeys<>("advanced", "skipPostScreenshotGui", null, false);
|
||||||
|
public static final SettingsRegistry.MultipleChoiceSettingKeys<String> FULL_BRIGHTNESS = new SettingsRegistry.MultipleChoiceSettingKeys<>(
|
||||||
|
"advanced", "fullBrightness", "replaymod.gui.settings.fullbrightness",
|
||||||
|
FullBrightness.Type.Gamma.toString());
|
||||||
|
static {
|
||||||
|
FULL_BRIGHTNESS.setChoices(Arrays.stream(FullBrightness.Type.values()).map(Object::toString).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ public class AdvancedScreenshots implements Extra {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void takeScreenshot() {
|
private void takeScreenshot() {
|
||||||
ReplayMod.instance.runLater(() -> new GuiCreateScreenshot(mod).display());
|
ReplayMod.instance.runLater(() -> new GuiCreateScreenshot(mod).open());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable {
|
|||||||
private final ReplayMod mod;
|
private final ReplayMod mod;
|
||||||
|
|
||||||
public GuiCreateScreenshot(ReplayMod mod) {
|
public GuiCreateScreenshot(ReplayMod mod) {
|
||||||
super(null, null);
|
super(GuiRenderSettings.createBaseScreen(), null, null);
|
||||||
|
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable {
|
|||||||
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
error(LOGGER, GuiCreateScreenshot.this, CrashReport.create(t, "Rendering video"), () -> {});
|
error(LOGGER, GuiCreateScreenshot.this, CrashReport.create(t, "Rendering video"), () -> {});
|
||||||
display(); // Re-show the render settings gui and the new error popup
|
getScreen().display(); // Re-show the render settings gui and the new error popup
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -75,6 +75,18 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable {
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open() {
|
||||||
|
super.open();
|
||||||
|
getScreen().display();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
getMinecraft().openScreen(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
// pause replay when opening this gui
|
// pause replay when opening this gui
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import com.replaymod.core.ReplayMod;
|
|||||||
import com.replaymod.core.utils.Utils;
|
import com.replaymod.core.utils.Utils;
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.extras.ReplayModExtras;
|
import com.replaymod.extras.ReplayModExtras;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
import com.replaymod.render.rendering.FrameConsumer;
|
import com.replaymod.render.rendering.FrameConsumer;
|
||||||
import com.replaymod.replay.ReplayModReplay;
|
import com.replaymod.replay.ReplayModReplay;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
@@ -13,8 +14,9 @@ import net.minecraft.util.crash.CrashReport;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ScreenshotWriter implements FrameConsumer<RGBFrame> {
|
public class ScreenshotWriter implements FrameConsumer<BitmapFrame> {
|
||||||
|
|
||||||
private final File outputFile;
|
private final File outputFile;
|
||||||
|
|
||||||
@@ -23,7 +25,9 @@ public class ScreenshotWriter implements FrameConsumer<RGBFrame> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(RGBFrame frame) {
|
public void consume(Map<Channel, BitmapFrame> channels) {
|
||||||
|
BitmapFrame frame = channels.get(Channel.BRGA);
|
||||||
|
|
||||||
// skip the first frame, in which not all chunks are properly loaded
|
// skip the first frame, in which not all chunks are properly loaded
|
||||||
if (frame.getFrameId() == 0) return;
|
if (frame.getFrameId() == 0) return;
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ public class ModCoreInstaller {
|
|||||||
|
|
||||||
public static int initialize(File gameDir, String minecraftVersion) {
|
public static int initialize(File gameDir, String minecraftVersion) {
|
||||||
if (isInitalized()) return -1;
|
if (isInitalized()) return -1;
|
||||||
|
JsonHolder jsonHolder = fetchJSON(VERSION_URL);
|
||||||
|
if (!jsonHolder.has(minecraftVersion)) {
|
||||||
|
System.out.println("No ModCore target for " + minecraftVersion + ". This in fine, unless you're specifically looking for ModCore.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
dataDir = new File(gameDir, "modcore");
|
dataDir = new File(gameDir, "modcore");
|
||||||
if (!dataDir.exists()) {
|
if (!dataDir.exists()) {
|
||||||
if (!dataDir.mkdirs()) {
|
if (!dataDir.mkdirs()) {
|
||||||
@@ -129,11 +134,6 @@ public class ModCoreInstaller {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JsonHolder jsonHolder = fetchJSON(VERSION_URL);
|
|
||||||
if (!jsonHolder.has(minecraftVersion)) {
|
|
||||||
System.out.println("No ModCore target for " + minecraftVersion + ". This in fine, unless you're specifically looking for ModCore.");
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
String latestRemote = jsonHolder.optString(minecraftVersion);
|
String latestRemote = jsonHolder.optString(minecraftVersion);
|
||||||
boolean failed = jsonHolder.getKeys().size() == 0 || (jsonHolder.has("success") && !jsonHolder.optBoolean("success"));
|
boolean failed = jsonHolder.getKeys().size() == 0 || (jsonHolder.has("success") && !jsonHolder.optBoolean("success"));
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ import com.google.common.util.concurrent.Futures;
|
|||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.core.utils.Utils;
|
import com.replaymod.core.utils.Utils;
|
||||||
|
import com.replaymod.core.versions.MCVer;
|
||||||
|
import com.replaymod.render.gui.GuiRenderQueue;
|
||||||
|
import com.replaymod.render.gui.GuiRenderSettings;
|
||||||
|
import com.replaymod.replay.ReplayHandler;
|
||||||
import com.replaymod.replay.ReplayModReplay;
|
import com.replaymod.replay.ReplayModReplay;
|
||||||
import com.replaymod.replaystudio.pathing.PathingRegistry;
|
import com.replaymod.replaystudio.pathing.PathingRegistry;
|
||||||
import com.replaymod.replaystudio.pathing.path.Path;
|
import com.replaymod.replaystudio.pathing.path.Path;
|
||||||
import com.replaymod.replaystudio.pathing.path.Timeline;
|
import com.replaymod.replaystudio.pathing.path.Timeline;
|
||||||
|
import com.replaymod.replaystudio.pathing.serialize.TimelineSerialization;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.RenderInfo;
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
@@ -18,32 +23,46 @@ import de.johni0702.minecraft.gui.container.GuiPanel;
|
|||||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
||||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiElement;
|
||||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||||
import de.johni0702.minecraft.gui.element.GuiTextField;
|
import de.johni0702.minecraft.gui.element.GuiTextField;
|
||||||
import de.johni0702.minecraft.gui.element.IGuiButton;
|
|
||||||
import de.johni0702.minecraft.gui.function.Closeable;
|
import de.johni0702.minecraft.gui.function.Closeable;
|
||||||
|
import de.johni0702.minecraft.gui.function.Typeable;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
import de.johni0702.minecraft.gui.layout.GridLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||||
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
||||||
import de.johni0702.minecraft.gui.utils.Colors;
|
import de.johni0702.minecraft.gui.utils.Colors;
|
||||||
import de.johni0702.minecraft.gui.utils.Consumer;
|
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
|
||||||
|
import net.minecraft.util.crash.CrashReport;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static de.johni0702.minecraft.gui.versions.MCVer.getClipboardString;
|
||||||
|
import static de.johni0702.minecraft.gui.versions.MCVer.setClipboardString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gui for loading and saving {@link Timeline Timelines}.
|
* Gui for loading and saving {@link Timeline Timelines}.
|
||||||
*/
|
*/
|
||||||
public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
public class GuiKeyframeRepository extends GuiScreen implements Closeable, Typeable {
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT);
|
public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT);
|
||||||
public final GuiLabel title = new GuiLabel(contentPanel).setI18nText("replaymod.gui.keyframerepository.title");
|
public final GuiLabel title = new GuiLabel(contentPanel).setI18nText("replaymod.gui.keyframerepository.title");
|
||||||
public final GuiVerticalList list = new GuiVerticalList(contentPanel).setDrawShadow(true).setDrawSlider(true);
|
public final GuiVerticalList list = new GuiVerticalList(contentPanel).setDrawShadow(true).setDrawSlider(true);
|
||||||
public final GuiPanel buttonPanel = new GuiPanel(contentPanel).setLayout(new HorizontalLayout().setSpacing(5));
|
public final GuiButton overwriteButton = new GuiButton().onClick(new Runnable() {
|
||||||
public final GuiButton overwriteButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
||||||
@@ -51,14 +70,16 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
).setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no");
|
).setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no");
|
||||||
Utils.addCallback(popup.getFuture(), doIt -> {
|
Utils.addCallback(popup.getFuture(), doIt -> {
|
||||||
if (doIt) {
|
if (doIt) {
|
||||||
timelines.put(selectedEntry.name, currentTimeline);
|
for (Entry entry : selectedEntries) {
|
||||||
|
timelines.put(entry.name, currentTimeline);
|
||||||
|
}
|
||||||
overwriteButton.setDisabled();
|
overwriteButton.setDisabled();
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
}, Throwable::printStackTrace);
|
}, Throwable::printStackTrace);
|
||||||
}
|
}
|
||||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.overwrite").setDisabled();
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.overwrite").setDisabled();
|
||||||
public final GuiButton saveAsButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
public final GuiButton saveAsButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true);
|
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true);
|
||||||
@@ -100,12 +121,12 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.saveas");
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.saveas");
|
||||||
public final GuiButton loadButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
public final GuiButton loadButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
getMinecraft().openScreen(null);
|
getMinecraft().openScreen(null);
|
||||||
try {
|
try {
|
||||||
Timeline timeline = timelines.get(selectedEntry.name);
|
Timeline timeline = timelines.get(selectedEntries.iterator().next().name);
|
||||||
for (Path path : timeline.getPaths()) {
|
for (Path path : timeline.getPaths()) {
|
||||||
path.updateAll();
|
path.updateAll();
|
||||||
}
|
}
|
||||||
@@ -115,9 +136,10 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.load").setDisabled();
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.load").setDisabled();
|
||||||
public final GuiButton renameButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
public final GuiButton renameButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
Entry selectedEntry = selectedEntries.iterator().next();
|
||||||
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(selectedEntry.name);
|
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(selectedEntry.name);
|
||||||
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
||||||
new GuiLabel().setI18nText("replaymod.gui.rename").setColor(Colors.BLACK),
|
new GuiLabel().setI18nText("replaymod.gui.rename").setColor(Colors.BLACK),
|
||||||
@@ -158,7 +180,7 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.rename").setDisabled();
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.rename").setDisabled();
|
||||||
public final GuiButton removeButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
public final GuiButton removeButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
||||||
@@ -168,14 +190,13 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Boolean delete) {
|
public void onSuccess(Boolean delete) {
|
||||||
if (delete) {
|
if (delete) {
|
||||||
timelines.remove(selectedEntry.name);
|
for (Entry entry : selectedEntries) {
|
||||||
list.getListPanel().removeElement(selectedEntry);
|
timelines.remove(entry.name);
|
||||||
|
list.getListPanel().removeElement(entry);
|
||||||
|
}
|
||||||
|
|
||||||
selectedEntry = null;
|
selectedEntries.clear();
|
||||||
overwriteButton.setDisabled();
|
updateButtons();
|
||||||
loadButton.setDisabled();
|
|
||||||
renameButton.setDisabled();
|
|
||||||
removeButton.setDisabled();
|
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,13 +209,102 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
}
|
}
|
||||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.remove").setDisabled();
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.remove").setDisabled();
|
||||||
|
|
||||||
|
public final GuiButton copyButton = new GuiButton().onClick(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Map<String, Timeline> toBeSerialized = new HashMap<>();
|
||||||
|
for (Entry entry : selectedEntries) {
|
||||||
|
toBeSerialized.put(entry.name, timelines.get(entry.name));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
TimelineSerialization serialization = new TimelineSerialization(registry, null);
|
||||||
|
setClipboardString(serialization.serialize(toBeSerialized));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
CrashReport report = CrashReport.create(t, "Copying timeline(s)");
|
||||||
|
Utils.error(LOGGER, GuiKeyframeRepository.this, report, () -> {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.copy").setDisabled();
|
||||||
|
|
||||||
|
public final GuiButton pasteButton = new GuiButton().onClick(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
TimelineSerialization serialization = new TimelineSerialization(registry, null);
|
||||||
|
for (Map.Entry<String, Timeline> entry : serialization.deserialize(getClipboardString()).entrySet()) {
|
||||||
|
String name = entry.getKey();
|
||||||
|
while (timelines.containsKey(name)) {
|
||||||
|
name += " (Copy)";
|
||||||
|
}
|
||||||
|
timelines.put(name, entry.getValue());
|
||||||
|
list.getListPanel().addElements(null, new Entry(name));
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Intentionally not making a fuzz about it cause this will likely happen when they have anything
|
||||||
|
// else in their clipboard.
|
||||||
|
// If it's actually not working, they'll go complain and we'll just check the log.
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.paste");
|
||||||
|
|
||||||
|
public final GuiButton addToQueueButton = new GuiButton().onClick(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler();
|
||||||
|
GuiRenderQueue queue = new GuiRenderQueue(GuiKeyframeRepository.this, replayHandler, null);
|
||||||
|
queue.open();
|
||||||
|
|
||||||
|
Set<String> selected = selectedEntries.stream().map(e -> e.name).collect(Collectors.toSet());
|
||||||
|
ArrayDeque<Map.Entry<String, Timeline>> toBeAdded = new ArrayDeque<>();
|
||||||
|
// Iterating over timelines to get consistent ordering (cause selectedEntries is an unordered set)
|
||||||
|
// and we need to get the Timelines anyway.
|
||||||
|
for (Map.Entry<String, Timeline> entry : timelines.entrySet()) {
|
||||||
|
if (selected.contains(entry.getKey())) {
|
||||||
|
toBeAdded.offerLast(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Map.Entry<String, Timeline> entry = toBeAdded.pollFirst();
|
||||||
|
if (entry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String name = entry.getKey();
|
||||||
|
Timeline timeline = entry.getValue();
|
||||||
|
GuiRenderSettings settingsGui = queue.addJob(timeline);
|
||||||
|
settingsGui.buttonPanel.removeElement(settingsGui.renderButton);
|
||||||
|
settingsGui.setOutputFileBaseName(name);
|
||||||
|
Runnable orgOnClick = settingsGui.queueButton.getOnClick();
|
||||||
|
settingsGui.queueButton.onClick(() -> {
|
||||||
|
orgOnClick.run();
|
||||||
|
this.run();
|
||||||
|
});
|
||||||
|
settingsGui.open();
|
||||||
|
}
|
||||||
|
}.run();
|
||||||
|
}
|
||||||
|
}).setSize(75, 20).setI18nLabel("replaymod.gui.rendersettings.addtoqueue");
|
||||||
|
|
||||||
|
public final GuiPanel buttonPanel = new GuiPanel(contentPanel)
|
||||||
|
.setLayout(new GridLayout()
|
||||||
|
.setColumns(4)
|
||||||
|
.setSpacingX(5)
|
||||||
|
.setSpacingY(5))
|
||||||
|
.addElements(null,
|
||||||
|
overwriteButton, saveAsButton, renameButton, removeButton,
|
||||||
|
loadButton, addToQueueButton, copyButton, pasteButton);
|
||||||
|
|
||||||
private final Map<String, Timeline> timelines = new LinkedHashMap<>();
|
private final Map<String, Timeline> timelines = new LinkedHashMap<>();
|
||||||
private final Timeline currentTimeline;
|
private final Timeline currentTimeline;
|
||||||
private final SettableFuture<Timeline> future = SettableFuture.create();
|
private final SettableFuture<Timeline> future = SettableFuture.create();
|
||||||
private final PathingRegistry registry;
|
private final PathingRegistry registry;
|
||||||
private final ReplayFile replayFile;
|
private final ReplayFile replayFile;
|
||||||
|
|
||||||
private Entry selectedEntry;
|
private final Set<Entry> selectedEntries = new HashSet<>();
|
||||||
|
|
||||||
{
|
{
|
||||||
setBackground(Background.NONE);
|
setBackground(Background.NONE);
|
||||||
@@ -232,6 +342,19 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
if (entry.getKey().isEmpty()) continue; // don't show auto-save slot
|
if (entry.getKey().isEmpty()) continue; // don't show auto-save slot
|
||||||
list.getListPanel().addElements(null, new Entry(entry.getKey()));
|
list.getListPanel().addElements(null, new Entry(entry.getKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateButtons() {
|
||||||
|
int selected = selectedEntries.size();
|
||||||
|
|
||||||
|
overwriteButton.setEnabled(selected >= 1);
|
||||||
|
loadButton.setEnabled(selected == 1);
|
||||||
|
renameButton.setEnabled(selected == 1);
|
||||||
|
removeButton.setEnabled(selected >= 1);
|
||||||
|
copyButton.setEnabled(selected >= 1);
|
||||||
|
addToQueueButton.setEnabled(selected >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -258,6 +381,33 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
|
||||||
|
if (MCVer.Keyboard.hasControlDown()) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case MCVer.Keyboard.KEY_A:
|
||||||
|
if (selectedEntries.size() < timelines.size()) {
|
||||||
|
for (GuiElement<?> child : list.getListPanel().getChildren()) {
|
||||||
|
if (child instanceof Entry) {
|
||||||
|
selectedEntries.add((Entry) child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedEntries.clear();
|
||||||
|
}
|
||||||
|
updateButtons();
|
||||||
|
return true;
|
||||||
|
case MCVer.Keyboard.KEY_C:
|
||||||
|
copyButton.onClick();
|
||||||
|
return true;
|
||||||
|
case MCVer.Keyboard.KEY_V:
|
||||||
|
pasteButton.onClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public class Entry extends AbstractGuiClickableContainer<Entry> {
|
public class Entry extends AbstractGuiClickableContainer<Entry> {
|
||||||
public final GuiLabel label = new GuiLabel(this);
|
public final GuiLabel label = new GuiLabel(this);
|
||||||
private String name;
|
private String name;
|
||||||
@@ -281,13 +431,20 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onClick() {
|
protected void onClick() {
|
||||||
selectedEntry = this;
|
if (!MCVer.Keyboard.hasControlDown()) {
|
||||||
buttonPanel.forEach(IGuiButton.class).setEnabled();
|
selectedEntries.clear();
|
||||||
|
}
|
||||||
|
if (selectedEntries.contains(this)) {
|
||||||
|
selectedEntries.remove(this);
|
||||||
|
} else {
|
||||||
|
selectedEntries.add(this);
|
||||||
|
}
|
||||||
|
updateButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||||
if (selectedEntry == this) {
|
if (selectedEntries.contains(this)) {
|
||||||
renderer.drawRect(0, 0, size.getWidth(), size.getHeight(), Colors.BLACK);
|
renderer.drawRect(0, 0, size.getWidth(), size.getHeight(), Colors.BLACK);
|
||||||
renderer.drawRect(0, 0, 2, size.getHeight(), Colors.WHITE);
|
renderer.drawRect(0, 0, 2, size.getHeight(), Colors.WHITE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public abstract class AbstractTimelinePlayer extends EventRegistrations {
|
|||||||
private final ReplayHandler replayHandler;
|
private final ReplayHandler replayHandler;
|
||||||
private Timeline timeline;
|
private Timeline timeline;
|
||||||
protected long startOffset;
|
protected long startOffset;
|
||||||
|
private boolean wasAsyncMode;
|
||||||
private long lastTime;
|
private long lastTime;
|
||||||
private long lastTimestamp;
|
private long lastTimestamp;
|
||||||
private ListenableFuture<Void> future;
|
private ListenableFuture<Void> future;
|
||||||
@@ -69,6 +70,7 @@ public abstract class AbstractTimelinePlayer extends EventRegistrations {
|
|||||||
}.max(iter).getTime();
|
}.max(iter).getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wasAsyncMode = replayHandler.getReplaySender().isAsyncMode();
|
||||||
replayHandler.getReplaySender().setSyncModeAndWait();
|
replayHandler.getReplaySender().setSyncModeAndWait();
|
||||||
register();
|
register();
|
||||||
lastTime = 0;
|
lastTime = 0;
|
||||||
@@ -103,7 +105,9 @@ public abstract class AbstractTimelinePlayer extends EventRegistrations {
|
|||||||
MinecraftAccessor mcA = (MinecraftAccessor) mc;
|
MinecraftAccessor mcA = (MinecraftAccessor) mc;
|
||||||
mcA.setTimer(((ReplayTimer) mcA.getTimer()).getWrapped());
|
mcA.setTimer(((ReplayTimer) mcA.getTimer()).getWrapped());
|
||||||
replayHandler.getReplaySender().setReplaySpeed(0);
|
replayHandler.getReplaySender().setReplaySpeed(0);
|
||||||
|
if (wasAsyncMode) {
|
||||||
replayHandler.getReplaySender().setAsyncMode(true);
|
replayHandler.getReplaySender().setAsyncMode(true);
|
||||||
|
}
|
||||||
unregister();
|
unregister();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/main/java/com/replaymod/recording/ServerInfoExt.java
Normal file
17
src/main/java/com/replaymod/recording/ServerInfoExt.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.replaymod.recording;
|
||||||
|
|
||||||
|
import net.minecraft.client.network.ServerInfo;
|
||||||
|
|
||||||
|
/** Extension interface for {@link net.minecraft.client.network.ServerInfo}. */
|
||||||
|
public interface ServerInfoExt {
|
||||||
|
|
||||||
|
static ServerInfoExt from(ServerInfo base) {
|
||||||
|
return (ServerInfoExt) base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Per-server optional overwrite for {@link Setting#AUTO_START_RECORDING}. */
|
||||||
|
Boolean getAutoRecording();
|
||||||
|
|
||||||
|
/** Per-server optional overwrite for {@link Setting#AUTO_START_RECORDING}. */
|
||||||
|
void setAutoRecording(Boolean autoRecording);
|
||||||
|
}
|
||||||
@@ -69,11 +69,11 @@ public class GuiRecordingControls extends EventRegistrations {
|
|||||||
updateState();
|
updateState();
|
||||||
}).setSize(98, 20);
|
}).setSize(98, 20);
|
||||||
|
|
||||||
public GuiRecordingControls(ReplayMod core, PacketListener packetListener) {
|
public GuiRecordingControls(ReplayMod core, PacketListener packetListener, boolean autoStart) {
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this.packetListener = packetListener;
|
this.packetListener = packetListener;
|
||||||
|
|
||||||
paused = stopped = !core.getSettingsRegistry().get(Setting.AUTO_START_RECORDING);
|
paused = stopped = !autoStart;
|
||||||
|
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,8 @@ public class GuiRecordingControls extends EventRegistrations {
|
|||||||
.orElse(MCVer.findButton(buttonList, "menu.disconnect", 1))
|
.orElse(MCVer.findButton(buttonList, "menu.disconnect", 1))
|
||||||
.<Function<Integer, Integer>>map(it -> (height) -> it.y)
|
.<Function<Integer, Integer>>map(it -> (height) -> it.y)
|
||||||
.orElse((height) -> height / 4 + 120 - 16);
|
.orElse((height) -> height / 4 + 120 - 16);
|
||||||
VanillaGuiScreen.setup(guiScreen).setLayout(new CustomLayout<de.johni0702.minecraft.gui.container.GuiScreen>() {
|
VanillaGuiScreen vanillaGui = VanillaGuiScreen.wrap(guiScreen);
|
||||||
|
vanillaGui.setLayout(new CustomLayout<de.johni0702.minecraft.gui.container.GuiScreen>(vanillaGui.getLayout()) {
|
||||||
@Override
|
@Override
|
||||||
protected void layout(de.johni0702.minecraft.gui.container.GuiScreen container, int width, int height) {
|
protected void layout(de.johni0702.minecraft.gui.container.GuiScreen container, int width, int height) {
|
||||||
pos(panel, width / 2 - 100, yPos.apply(height) + 16 + 8);
|
pos(panel, width / 2 - 100, yPos.apply(height) + 16 + 8);
|
||||||
|
|||||||
161
src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java
Normal file
161
src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package com.replaymod.recording.gui;
|
||||||
|
|
||||||
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.core.utils.Utils;
|
||||||
|
import com.replaymod.replay.gui.screen.GuiReplayViewer;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
|
import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair;
|
||||||
|
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
||||||
|
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||||
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
|
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiTextField;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||||
|
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||||
|
import de.johni0702.minecraft.gui.function.Focusable;
|
||||||
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
|
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||||
|
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||||
|
import de.johni0702.minecraft.gui.utils.Colors;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.util.crash.CrashReport;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.replaymod.core.versions.MCVer.getMinecraft;
|
||||||
|
import static de.johni0702.minecraft.gui.utils.Utils.link;
|
||||||
|
|
||||||
|
public class GuiSavingReplay {
|
||||||
|
|
||||||
|
private static final MinecraftClient mc = getMinecraft();
|
||||||
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
|
private final GuiLabel label = new GuiLabel()
|
||||||
|
.setI18nText("replaymod.gui.replaysaving.title")
|
||||||
|
.setColor(Colors.BLACK);
|
||||||
|
|
||||||
|
private final GuiProgressBar progressBar = new GuiProgressBar()
|
||||||
|
.setHeight(14);
|
||||||
|
|
||||||
|
private final GuiPanel panel = new GuiPanel()
|
||||||
|
.setLayout(new VerticalLayout().setSpacing(2))
|
||||||
|
.addElements(new VerticalLayout.Data(0.5), label, progressBar);
|
||||||
|
|
||||||
|
private final ReplayMod core;
|
||||||
|
private final List<Runnable> apply = new ArrayList<>();
|
||||||
|
|
||||||
|
public GuiSavingReplay(ReplayMod core) {
|
||||||
|
this.core = core;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open() {
|
||||||
|
core.getBackgroundProcesses().addProcess(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
core.getBackgroundProcesses().removeProcess(panel);
|
||||||
|
AbstractGuiScreen<?> currentScreen = GuiScreen.from(mc.currentScreen);
|
||||||
|
if (currentScreen instanceof GuiReplayViewer) {
|
||||||
|
((GuiReplayViewer) currentScreen).list.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GuiProgressBar getProgressBar() {
|
||||||
|
return progressBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void presentRenameDialog(List<Pair<Path, ReplayMetaData>> outputPaths) {
|
||||||
|
panel.removeElement(progressBar);
|
||||||
|
|
||||||
|
link(outputPaths.stream().map(it -> addOutput(it.getKey(), it.getValue())).toArray(Focusable[]::new));
|
||||||
|
|
||||||
|
GuiButton applyButton = new GuiButton()
|
||||||
|
.setSize(150, 20)
|
||||||
|
.setI18nLabel("replaymod.gui.done")
|
||||||
|
.onClick(() -> {
|
||||||
|
apply.forEach(Runnable::run);
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.addElements(new VerticalLayout.Data(0.5), applyButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GuiTextField addOutput(Path path, ReplayMetaData metaData) {
|
||||||
|
String originalName = Utils.fileNameToReplayName(path.getFileName().toString());
|
||||||
|
GuiTextField textField = new GuiTextField()
|
||||||
|
.setSize(130, 20)
|
||||||
|
.setText(originalName)
|
||||||
|
.setI18nHint("replaymod.gui.delete")
|
||||||
|
.setTextColorDisabled(Colors.RED)
|
||||||
|
.setTooltip(createTooltip(path, metaData));
|
||||||
|
GuiButton clearButton = new GuiButton()
|
||||||
|
.setSize(20, 20)
|
||||||
|
.setLabel("X")
|
||||||
|
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.delete"))
|
||||||
|
.onClick(() -> textField.setText(""));
|
||||||
|
GuiPanel row = new GuiPanel()
|
||||||
|
.setLayout(new HorizontalLayout())
|
||||||
|
.addElements(null, textField, clearButton);
|
||||||
|
panel.addElements(new VerticalLayout.Data(0.5), row);
|
||||||
|
|
||||||
|
apply.add(() -> applyOutput(path, textField.getText()));
|
||||||
|
|
||||||
|
return textField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GuiPanel createTooltip(Path path, ReplayMetaData metaData) {
|
||||||
|
GuiTooltip tooltip = new GuiTooltip();
|
||||||
|
GuiReplayViewer.GuiReplayEntry entry = new GuiReplayViewer.GuiReplayEntry(path.toFile(), metaData, null, new ArrayList<>());
|
||||||
|
return new GuiPanel().setLayout(new CustomLayout<GuiPanel>() {
|
||||||
|
@Override
|
||||||
|
protected void layout(GuiPanel container, int width, int height) {
|
||||||
|
pos(entry, 4, 4);
|
||||||
|
size(entry, width - 8, height - 8);
|
||||||
|
size(tooltip, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
||||||
|
ReadableDimension size = entry.calcMinSize();
|
||||||
|
return new Dimension(size.getWidth() + 8, size.getHeight() + 8);
|
||||||
|
}
|
||||||
|
}).addElements(null, tooltip, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyOutput(Path path, String newName) {
|
||||||
|
if (newName.isEmpty()) {
|
||||||
|
try {
|
||||||
|
Files.delete(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Deleting replay file:", e);
|
||||||
|
CrashReport crashReport = CrashReport.create(e, "Deleting replay file");
|
||||||
|
core.runLater(() -> Utils.error(logger, VanillaGuiScreen.wrap(mc.currentScreen), crashReport, () -> {}));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Path replaysFolder = core.getReplayFolder();
|
||||||
|
Path newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName));
|
||||||
|
for (int i = 1; Files.exists(newPath); i++) {
|
||||||
|
newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName + " (" + i + ")"));
|
||||||
|
}
|
||||||
|
Files.move(path, newPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Renaming replay file:", e);
|
||||||
|
CrashReport crashReport = CrashReport.create(e, "Renaming replay file");
|
||||||
|
core.runLater(() -> Utils.error(logger, VanillaGuiScreen.wrap(mc.currentScreen), crashReport, () -> {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.replaymod.core.ReplayMod;
|
|||||||
import com.replaymod.core.utils.ModCompat;
|
import com.replaymod.core.utils.ModCompat;
|
||||||
import com.replaymod.core.utils.Utils;
|
import com.replaymod.core.utils.Utils;
|
||||||
import com.replaymod.editor.gui.MarkerProcessor;
|
import com.replaymod.editor.gui.MarkerProcessor;
|
||||||
|
import com.replaymod.recording.ServerInfoExt;
|
||||||
import com.replaymod.recording.Setting;
|
import com.replaymod.recording.Setting;
|
||||||
import com.replaymod.recording.gui.GuiRecordingControls;
|
import com.replaymod.recording.gui.GuiRecordingControls;
|
||||||
import com.replaymod.recording.gui.GuiRecordingOverlay;
|
import com.replaymod.recording.gui.GuiRecordingOverlay;
|
||||||
@@ -11,10 +12,10 @@ import com.replaymod.recording.mixin.NetworkManagerAccessor;
|
|||||||
import com.replaymod.recording.packet.PacketListener;
|
import com.replaymod.recording.packet.PacketListener;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.network.ServerInfo;
|
||||||
|
import net.minecraft.client.resource.language.I18n;
|
||||||
import net.minecraft.network.ClientConnection;
|
import net.minecraft.network.ClientConnection;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ import net.minecraft.world.World;
|
|||||||
//#endif
|
//#endif
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
import java.io.File;
|
import java.nio.file.Path;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
@@ -89,14 +90,26 @@ public class ConnectionEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String worldName;
|
String worldName;
|
||||||
|
String serverName = null;
|
||||||
|
boolean autoStart = core.getSettingsRegistry().get(Setting.AUTO_START_RECORDING);
|
||||||
if (local) {
|
if (local) {
|
||||||
//#if MC>=11600
|
//#if MC>=11600
|
||||||
worldName = mc.getServer().getSaveProperties().getLevelName();
|
worldName = mc.getServer().getSaveProperties().getLevelName();
|
||||||
//#else
|
//#else
|
||||||
//$$ worldName = mc.getServer().getLevelName();
|
//$$ worldName = mc.getServer().getLevelName();
|
||||||
//#endif
|
//#endif
|
||||||
|
serverName = worldName;
|
||||||
} else if (mc.getCurrentServerEntry() != null) {
|
} else if (mc.getCurrentServerEntry() != null) {
|
||||||
worldName = mc.getCurrentServerEntry().address;
|
ServerInfo serverInfo = mc.getCurrentServerEntry();
|
||||||
|
worldName = serverInfo.address;
|
||||||
|
if (!I18n.translate("selectServer.defaultName").equals(serverInfo.name)) {
|
||||||
|
serverName = serverInfo.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean autoStartServer = ServerInfoExt.from(serverInfo).getAutoRecording();
|
||||||
|
if (autoStartServer != null) {
|
||||||
|
autoStart = autoStartServer;
|
||||||
|
}
|
||||||
//#if MC>=11100
|
//#if MC>=11100
|
||||||
} else if (mc.isConnectedToRealms()) {
|
} else if (mc.isConnectedToRealms()) {
|
||||||
// we can't access the server name without tapping too deep in the Realms Library
|
// we can't access the server name without tapping too deep in the Realms Library
|
||||||
@@ -107,34 +120,38 @@ public class ConnectionEventHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File folder = core.getReplayFolder();
|
if (ReplayMod.isMinimalMode()) {
|
||||||
|
// Recording controls are not supported in minimal mode, so always auto-start
|
||||||
|
autoStart = true;
|
||||||
|
}
|
||||||
|
|
||||||
String name = sdf.format(Calendar.getInstance().getTime());
|
String name = sdf.format(Calendar.getInstance().getTime());
|
||||||
File currentFile = new File(folder, Utils.replayNameToFileName(name));
|
Path outputPath = core.getRecordingFolder().resolve(Utils.replayNameToFileName(name));
|
||||||
ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), currentFile);
|
ReplayFile replayFile = core.openReplay(outputPath);
|
||||||
|
|
||||||
replayFile.writeModInfo(ModCompat.getInstalledNetworkMods());
|
replayFile.writeModInfo(ModCompat.getInstalledNetworkMods());
|
||||||
|
|
||||||
ReplayMetaData metaData = new ReplayMetaData();
|
ReplayMetaData metaData = new ReplayMetaData();
|
||||||
metaData.setSingleplayer(local);
|
metaData.setSingleplayer(local);
|
||||||
metaData.setServerName(worldName);
|
metaData.setServerName(worldName);
|
||||||
|
metaData.setCustomServerName(serverName);
|
||||||
metaData.setGenerator("ReplayMod v" + ReplayMod.instance.getVersion());
|
metaData.setGenerator("ReplayMod v" + ReplayMod.instance.getVersion());
|
||||||
metaData.setDate(System.currentTimeMillis());
|
metaData.setDate(System.currentTimeMillis());
|
||||||
metaData.setMcVersion(ReplayMod.getMinecraftVersion());
|
metaData.setMcVersion(ReplayMod.getMinecraftVersion());
|
||||||
packetListener = new PacketListener(core, currentFile.toPath(), replayFile, metaData);
|
packetListener = new PacketListener(core, outputPath, replayFile, metaData);
|
||||||
Channel channel = ((NetworkManagerAccessor) networkManager).getChannel();
|
Channel channel = ((NetworkManagerAccessor) networkManager).getChannel();
|
||||||
channel.pipeline().addBefore(packetHandlerKey, "replay_recorder", packetListener);
|
channel.pipeline().addBefore(packetHandlerKey, "replay_recorder", packetListener);
|
||||||
|
|
||||||
recordingEventHandler = new RecordingEventHandler(packetListener);
|
recordingEventHandler = new RecordingEventHandler(packetListener);
|
||||||
recordingEventHandler.register();
|
recordingEventHandler.register();
|
||||||
|
|
||||||
guiControls = new GuiRecordingControls(core, packetListener);
|
guiControls = new GuiRecordingControls(core, packetListener, autoStart);
|
||||||
guiControls.register();
|
guiControls.register();
|
||||||
|
|
||||||
guiOverlay = new GuiRecordingOverlay(mc, core.getSettingsRegistry(), guiControls);
|
guiOverlay = new GuiRecordingOverlay(mc, core.getSettingsRegistry(), guiControls);
|
||||||
guiOverlay.register();
|
guiOverlay.register();
|
||||||
|
|
||||||
if (core.getSettingsRegistry().get(Setting.AUTO_START_RECORDING) || ReplayMod.isMinimalMode()) {
|
if (autoStart) {
|
||||||
core.printInfoToChat("replaymod.chat.recordingstarted");
|
core.printInfoToChat("replaymod.chat.recordingstarted");
|
||||||
} else {
|
} else {
|
||||||
packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT, 0);
|
packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT, 0);
|
||||||
|
|||||||
@@ -2,14 +2,23 @@ package com.replaymod.recording.handler;
|
|||||||
|
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.core.SettingsRegistry;
|
import com.replaymod.core.SettingsRegistry;
|
||||||
|
import com.replaymod.core.gui.GuiReplayButton;
|
||||||
|
import com.replaymod.recording.ServerInfoExt;
|
||||||
import com.replaymod.recording.Setting;
|
import com.replaymod.recording.Setting;
|
||||||
|
import com.replaymod.recording.mixin.AddServerScreenAccessor;
|
||||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
|
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
import de.johni0702.minecraft.gui.element.GuiCheckbox;
|
import de.johni0702.minecraft.gui.element.GuiCheckbox;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiToggleButton;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
|
import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||||
|
import net.minecraft.client.gui.screen.AddServerScreen;
|
||||||
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
||||||
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
|
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
|
||||||
|
import net.minecraft.client.network.ServerInfo;
|
||||||
|
import net.minecraft.client.resource.language.I18n;
|
||||||
|
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback;
|
import de.johni0702.minecraft.gui.versions.callbacks.InitScreenCallback;
|
||||||
@@ -51,7 +60,8 @@ public class GuiHandler extends EventRegistrations {
|
|||||||
settingsRegistry.save();
|
settingsRegistry.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
VanillaGuiScreen.setup(gui).setLayout(new CustomLayout<GuiScreen>() {
|
VanillaGuiScreen vanillaGui = VanillaGuiScreen.wrap(gui);
|
||||||
|
vanillaGui.setLayout(new CustomLayout<GuiScreen>(vanillaGui.getLayout()) {
|
||||||
@Override
|
@Override
|
||||||
protected void layout(GuiScreen container, int width, int height) {
|
protected void layout(GuiScreen container, int width, int height) {
|
||||||
//size(recordingCheckbox, 200, 20);
|
//size(recordingCheckbox, 200, 20);
|
||||||
@@ -59,5 +69,34 @@ public class GuiHandler extends EventRegistrations {
|
|||||||
}
|
}
|
||||||
}).addElements(null, recordingCheckbox);
|
}).addElements(null, recordingCheckbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gui instanceof AddServerScreen) {
|
||||||
|
VanillaGuiScreen vanillaGui = VanillaGuiScreen.wrap(gui);
|
||||||
|
GuiButton replayButton = new GuiReplayButton().onClick(() -> {
|
||||||
|
ServerInfo serverInfo = ((AddServerScreenAccessor) gui).getServer();
|
||||||
|
ServerInfoExt serverInfoExt = ServerInfoExt.from(serverInfo);
|
||||||
|
Boolean state = serverInfoExt.getAutoRecording();
|
||||||
|
GuiToggleButton<String> autoRecording = new GuiToggleButton<String>()
|
||||||
|
.setI18nLabel("replaymod.gui.settings.autostartrecording")
|
||||||
|
.setValues(
|
||||||
|
I18n.translate("replaymod.gui.settings.default"),
|
||||||
|
I18n.translate("options.off"),
|
||||||
|
I18n.translate("options.on")
|
||||||
|
)
|
||||||
|
.setSelected(state == null ? 0 : state ? 2 : 1);
|
||||||
|
autoRecording.onClick(() -> {
|
||||||
|
int selected = autoRecording.getSelected();
|
||||||
|
serverInfoExt.setAutoRecording(selected == 0 ? null : selected == 2);
|
||||||
|
});
|
||||||
|
GuiInfoPopup.open(vanillaGui, autoRecording);
|
||||||
|
});
|
||||||
|
vanillaGui.setLayout(new CustomLayout<GuiScreen>(vanillaGui.getLayout()) {
|
||||||
|
@Override
|
||||||
|
protected void layout(GuiScreen container, int width, int height) {
|
||||||
|
size(replayButton, 20, 20);
|
||||||
|
pos(replayButton, width - width(replayButton) - 5, 5);
|
||||||
|
}
|
||||||
|
}).addElements(null, replayButton);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.replaymod.recording.mixin;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.screen.AddServerScreen;
|
||||||
|
import net.minecraft.client.network.ServerInfo;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(AddServerScreen.class)
|
||||||
|
public interface AddServerScreenAccessor {
|
||||||
|
@Accessor
|
||||||
|
ServerInfo getServer();
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.replaymod.recording.mixin;
|
||||||
|
|
||||||
|
import com.replaymod.recording.ServerInfoExt;
|
||||||
|
import net.minecraft.client.network.ServerInfo;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(ServerInfo.class)
|
||||||
|
public abstract class MixinServerInfo implements ServerInfoExt {
|
||||||
|
private Boolean autoRecording;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getAutoRecording() {
|
||||||
|
return autoRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAutoRecording(Boolean autoRecording) {
|
||||||
|
this.autoRecording = autoRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "serialize", at = @At("RETURN"))
|
||||||
|
private void serialize(CallbackInfoReturnable<CompoundTag> ci) {
|
||||||
|
CompoundTag tag = ci.getReturnValue();
|
||||||
|
if (autoRecording != null) {
|
||||||
|
tag.putBoolean("autoRecording", autoRecording);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "deserialize", at = @At("RETURN"))
|
||||||
|
private static void deserialize(CompoundTag tag, CallbackInfoReturnable<ServerInfo> ci) {
|
||||||
|
ServerInfoExt serverInfo = ServerInfoExt.from(ci.getReturnValue());
|
||||||
|
if (tag.contains("autoRecording")) {
|
||||||
|
serverInfo.setAutoRecording(tag.getBoolean("autoRecording"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "copyFrom", at = @At("RETURN"))
|
||||||
|
public void copyFrom(ServerInfo serverInfo, CallbackInfo ci) {
|
||||||
|
ServerInfoExt from = ServerInfoExt.from(serverInfo);
|
||||||
|
this.autoRecording = from.getAutoRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import com.replaymod.core.versions.MCVer;
|
|||||||
import com.replaymod.editor.gui.MarkerProcessor;
|
import com.replaymod.editor.gui.MarkerProcessor;
|
||||||
import com.replaymod.recording.ReplayModRecording;
|
import com.replaymod.recording.ReplayModRecording;
|
||||||
import com.replaymod.recording.Setting;
|
import com.replaymod.recording.Setting;
|
||||||
|
import com.replaymod.recording.gui.GuiSavingReplay;
|
||||||
import com.replaymod.recording.handler.ConnectionEventHandler;
|
import com.replaymod.recording.handler.ConnectionEventHandler;
|
||||||
import com.replaymod.recording.mixin.SPacketSpawnMobAccessor;
|
import com.replaymod.recording.mixin.SPacketSpawnMobAccessor;
|
||||||
import com.replaymod.recording.mixin.SPacketSpawnPlayerAccessor;
|
import com.replaymod.recording.mixin.SPacketSpawnPlayerAccessor;
|
||||||
@@ -19,9 +20,8 @@ import com.replaymod.replaystudio.data.Marker;
|
|||||||
import com.replaymod.replaystudio.io.ReplayOutputStream;
|
import com.replaymod.replaystudio.io.ReplayOutputStream;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
|
import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair;
|
||||||
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
|
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
|
||||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
|
||||||
import de.johni0702.minecraft.gui.utils.Colors;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@@ -67,7 +67,9 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -245,9 +247,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
GuiLabel savingLabel = new GuiLabel().setI18nText("replaymod.gui.replaysaving.title").setColor(Colors.BLACK);
|
GuiSavingReplay guiSavingReplay = new GuiSavingReplay(core);
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
core.runLater(() -> core.getBackgroundProcesses().addProcess(savingLabel));
|
core.runLater(guiSavingReplay::open);
|
||||||
|
|
||||||
saveService.shutdown();
|
saveService.shutdown();
|
||||||
try {
|
try {
|
||||||
@@ -261,22 +263,26 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
|
|||||||
logger.error("Failed to close packet output stream:", e);
|
logger.error("Failed to close packet output stream:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Pair<Path, ReplayMetaData>> outputPaths;
|
||||||
synchronized (replayFile) {
|
synchronized (replayFile) {
|
||||||
try {
|
try {
|
||||||
replayFile.save();
|
replayFile.save();
|
||||||
replayFile.close();
|
replayFile.close();
|
||||||
|
|
||||||
if (core.getSettingsRegistry().get(Setting.AUTO_POST_PROCESS) && !ReplayMod.isMinimalMode()) {
|
if (core.getSettingsRegistry().get(Setting.AUTO_POST_PROCESS) && !ReplayMod.isMinimalMode()) {
|
||||||
MarkerProcessor.apply(outputPath, progress -> {});
|
outputPaths = MarkerProcessor.apply(outputPath, guiSavingReplay.getProgressBar()::setProgress);
|
||||||
|
} else {
|
||||||
|
outputPaths = Collections.singletonList(new Pair<>(outputPath, metaData));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Saving replay file:", e);
|
logger.error("Saving replay file:", e);
|
||||||
CrashReport crashReport = CrashReport.create(e, "Saving replay file");
|
CrashReport crashReport = CrashReport.create(e, "Saving replay file");
|
||||||
core.runLater(() -> Utils.error(logger, VanillaGuiScreen.setup(mc.currentScreen), crashReport, () -> {}));
|
core.runLater(() -> Utils.error(logger, VanillaGuiScreen.wrap(mc.currentScreen), crashReport, guiSavingReplay::close));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
core.runLater(() -> core.getBackgroundProcesses().removeProcess(savingLabel));
|
core.runLater(() -> guiSavingReplay.presentRenameDialog(outputPaths));
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
227
src/main/java/com/replaymod/render/CameraPathExporter.java
Normal file
227
src/main/java/com/replaymod/render/CameraPathExporter.java
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
package com.replaymod.render;
|
||||||
|
|
||||||
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.core.versions.MCVer;
|
||||||
|
import com.replaymod.replay.camera.CameraEntity;
|
||||||
|
import de.javagl.jgltf.impl.v2.Accessor;
|
||||||
|
import de.javagl.jgltf.impl.v2.Animation;
|
||||||
|
import de.javagl.jgltf.impl.v2.AnimationChannel;
|
||||||
|
import de.javagl.jgltf.impl.v2.AnimationChannelTarget;
|
||||||
|
import de.javagl.jgltf.impl.v2.AnimationSampler;
|
||||||
|
import de.javagl.jgltf.impl.v2.Asset;
|
||||||
|
import de.javagl.jgltf.impl.v2.Buffer;
|
||||||
|
import de.javagl.jgltf.impl.v2.BufferView;
|
||||||
|
import de.javagl.jgltf.impl.v2.Camera;
|
||||||
|
import de.javagl.jgltf.impl.v2.CameraPerspective;
|
||||||
|
import de.javagl.jgltf.impl.v2.GlTF;
|
||||||
|
import de.javagl.jgltf.impl.v2.Node;
|
||||||
|
import de.javagl.jgltf.model.io.v2.GltfAssetV2;
|
||||||
|
import de.javagl.jgltf.model.io.v2.GltfAssetWriterV2;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.vector.Quaternion;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.vector.Vector4f;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
|
||||||
|
//#if MC>=11400
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
//#else
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
import static com.replaymod.core.utils.Utils.configure;
|
||||||
|
|
||||||
|
public class CameraPathExporter {
|
||||||
|
|
||||||
|
private final MinecraftClient mc = MCVer.getMinecraft();
|
||||||
|
private final RenderSettings settings;
|
||||||
|
private int framesDone;
|
||||||
|
private ByteBuffer timeBuffer;
|
||||||
|
private ByteBuffer cameraTranslationBuffer;
|
||||||
|
private ByteBuffer cameraRotationBuffer;
|
||||||
|
|
||||||
|
public CameraPathExporter(RenderSettings settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setup(int totalFrames) {
|
||||||
|
timeBuffer = ByteBuffer.allocate(4 * totalFrames).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
cameraTranslationBuffer = ByteBuffer.allocate(4 * totalFrames * 3).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
cameraRotationBuffer = ByteBuffer.allocate(4 * totalFrames * 4).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordFrame(float tickDelta) {
|
||||||
|
//#if MC>=10800
|
||||||
|
Entity entity = mc.getCameraEntity() == null ? mc.player : mc.getCameraEntity();
|
||||||
|
//#else
|
||||||
|
//$$ Entity entity = mc.renderViewEntity == null ? mc.thePlayer : mc.renderViewEntity;
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
//#if MC>=11400
|
||||||
|
net.minecraft.client.render.Camera camera = mc.gameRenderer.getCamera();
|
||||||
|
Vec3d vec = camera.getPos();
|
||||||
|
float x = (float) vec.getX();
|
||||||
|
float y = (float) vec.getY();
|
||||||
|
float z = (float) vec.getZ();
|
||||||
|
float yaw = camera.getYaw() + 180;
|
||||||
|
float pitch = camera.getPitch();
|
||||||
|
//#else
|
||||||
|
//#if MC>=10800
|
||||||
|
//$$ float eyeHeight = entity.getEyeHeight();
|
||||||
|
//#else
|
||||||
|
//$$ float eyeHeight = 1.62f - entity.yOffset;
|
||||||
|
//#endif
|
||||||
|
//$$ float x = (float) (entity.prevPosX + (entity.posX - entity.prevPosX) * tickDelta);
|
||||||
|
//$$ float y = (float) (entity.prevPosY + (entity.posY - entity.prevPosY) * tickDelta + eyeHeight);
|
||||||
|
//$$ float z = (float) (entity.prevPosZ + (entity.posZ - entity.prevPosZ) * tickDelta);
|
||||||
|
//$$ float yaw = entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * tickDelta + 180;
|
||||||
|
//$$ float pitch = entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * tickDelta;
|
||||||
|
//#endif
|
||||||
|
float roll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0;
|
||||||
|
|
||||||
|
Quaternion quatYaw = new Quaternion();
|
||||||
|
Quaternion quatPitch = new Quaternion();
|
||||||
|
Quaternion quatRoll = new Quaternion();
|
||||||
|
|
||||||
|
quatYaw.setFromAxisAngle(new Vector4f(0, -1, 0, (float) Math.toRadians(yaw)));
|
||||||
|
quatPitch.setFromAxisAngle(new Vector4f(-1, 0, 0, (float) Math.toRadians(pitch)));
|
||||||
|
quatRoll.setFromAxisAngle(new Vector4f(0, 0, 1, (float) Math.toRadians(roll)));
|
||||||
|
|
||||||
|
Quaternion quaternion = new Quaternion(0, 0, 0, 1);
|
||||||
|
Quaternion.mul(quaternion, quatYaw, quaternion);
|
||||||
|
Quaternion.mul(quaternion, quatPitch, quaternion);
|
||||||
|
Quaternion.mul(quaternion, quatRoll, quaternion);
|
||||||
|
quaternion.normalise(quaternion);
|
||||||
|
|
||||||
|
float[] translation = new float[] { x, y, z };
|
||||||
|
float[] rotation = new float[] { quaternion.getX(), quaternion.getY(), quaternion.getZ(), quaternion.getW() };
|
||||||
|
|
||||||
|
timeBuffer.putFloat(framesDone / (float) settings.getFramesPerSecond());
|
||||||
|
for (float f : translation) {
|
||||||
|
cameraTranslationBuffer.putFloat(f);
|
||||||
|
}
|
||||||
|
for (float f : rotation) {
|
||||||
|
cameraRotationBuffer.putFloat(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
framesDone++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finish() throws IOException {
|
||||||
|
int timeBufferSize = timeBuffer.rewind().remaining();
|
||||||
|
int cameraTranslationBufferSize = cameraTranslationBuffer.rewind().remaining();
|
||||||
|
int cameraRotationBufferSize = cameraRotationBuffer.rewind().remaining();
|
||||||
|
|
||||||
|
int binaryDataSize = 0;
|
||||||
|
binaryDataSize += timeBufferSize;
|
||||||
|
binaryDataSize += cameraTranslationBufferSize;
|
||||||
|
binaryDataSize += cameraRotationBufferSize;
|
||||||
|
|
||||||
|
ByteBuffer binaryData = ByteBuffer.allocate(binaryDataSize);
|
||||||
|
int timeBufferOffset = binaryData.position();
|
||||||
|
binaryData.put(timeBuffer);
|
||||||
|
int cameraTranslationBufferOffset = binaryData.position();
|
||||||
|
binaryData.put(cameraTranslationBuffer);
|
||||||
|
int cameraRotationBufferOffset = binaryData.position();
|
||||||
|
binaryData.put(cameraRotationBuffer);
|
||||||
|
binaryData.rewind();
|
||||||
|
|
||||||
|
GlTF glTF = new GlTF();
|
||||||
|
glTF.setAsset(configure(new Asset(), asset -> {
|
||||||
|
asset.setVersion("2.0");
|
||||||
|
asset.setGenerator("ReplayMod v" + ReplayMod.instance.getVersion());
|
||||||
|
}));
|
||||||
|
glTF.addAnimations(configure(new Animation(), animation -> {
|
||||||
|
animation.addChannels(configure(new AnimationChannel(), channel -> {
|
||||||
|
channel.setTarget(configure(new AnimationChannelTarget(), target -> {
|
||||||
|
target.setNode(0);
|
||||||
|
target.setPath("translation");
|
||||||
|
}));
|
||||||
|
channel.setSampler(0);
|
||||||
|
}));
|
||||||
|
animation.addChannels(configure(new AnimationChannel(), channel -> {
|
||||||
|
channel.setTarget(configure(new AnimationChannelTarget(), target -> {
|
||||||
|
target.setNode(0);
|
||||||
|
target.setPath("rotation");
|
||||||
|
}));
|
||||||
|
channel.setSampler(1);
|
||||||
|
}));
|
||||||
|
animation.addSamplers(configure(new AnimationSampler(), sampler -> {
|
||||||
|
sampler.setInput(0);
|
||||||
|
sampler.setOutput(1);
|
||||||
|
}));
|
||||||
|
animation.addSamplers(configure(new AnimationSampler(), sampler -> {
|
||||||
|
sampler.setInput(0);
|
||||||
|
sampler.setOutput(2);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
glTF.addCameras(configure(new Camera(), camera -> {
|
||||||
|
camera.setType("perspective");
|
||||||
|
camera.setPerspective(configure(new CameraPerspective(), perspective -> {
|
||||||
|
float aspectRatio = (float) settings.getVideoWidth() / (float) settings.getVideoHeight();
|
||||||
|
perspective.setAspectRatio(aspectRatio);
|
||||||
|
perspective.setYfov((float) Math.toRadians(mc.options.fov));
|
||||||
|
perspective.setZnear(0.05f);
|
||||||
|
perspective.setZfar((float) mc.options.viewDistance * 16 * 4);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
glTF.addNodes(configure(new Node(), node -> node.setCamera(0)));
|
||||||
|
glTF.addBuffers(configure(new Buffer(), buffer -> buffer.setByteLength(binaryData.limit())));
|
||||||
|
// Time
|
||||||
|
glTF.addBufferViews(configure(new BufferView(), bufferView -> {
|
||||||
|
bufferView.setBuffer(0);
|
||||||
|
bufferView.setByteOffset(timeBufferOffset);
|
||||||
|
bufferView.setByteLength(timeBufferSize);
|
||||||
|
}));
|
||||||
|
glTF.addAccessors(configure(new Accessor(), accessor -> {
|
||||||
|
accessor.setBufferView(0);
|
||||||
|
accessor.setType("SCALAR");
|
||||||
|
accessor.setComponentType(GL11.GL_FLOAT);
|
||||||
|
accessor.setCount(framesDone);
|
||||||
|
}));
|
||||||
|
// Camera translation
|
||||||
|
glTF.addBufferViews(configure(new BufferView(), bufferView -> {
|
||||||
|
bufferView.setBuffer(0);
|
||||||
|
bufferView.setByteOffset(cameraTranslationBufferOffset);
|
||||||
|
bufferView.setByteLength(cameraTranslationBufferSize);
|
||||||
|
}));
|
||||||
|
glTF.addAccessors(configure(new Accessor(), accessor -> {
|
||||||
|
accessor.setBufferView(1);
|
||||||
|
accessor.setType("VEC3");
|
||||||
|
accessor.setComponentType(GL11.GL_FLOAT);
|
||||||
|
accessor.setCount(framesDone);
|
||||||
|
}));
|
||||||
|
// Camera rotation
|
||||||
|
glTF.addBufferViews(configure(new BufferView(), bufferView -> {
|
||||||
|
bufferView.setBuffer(0);
|
||||||
|
bufferView.setByteOffset(cameraRotationBufferOffset);
|
||||||
|
bufferView.setByteLength(cameraRotationBufferSize);
|
||||||
|
}));
|
||||||
|
glTF.addAccessors(configure(new Accessor(), accessor -> {
|
||||||
|
accessor.setBufferView(2);
|
||||||
|
accessor.setType("VEC4");
|
||||||
|
accessor.setComponentType(GL11.GL_FLOAT);
|
||||||
|
accessor.setCount(framesDone);
|
||||||
|
}));
|
||||||
|
|
||||||
|
java.nio.file.Path videoPath = settings.getOutputFile().toPath();
|
||||||
|
java.nio.file.Path glbBasePath = Files.isDirectory(videoPath)
|
||||||
|
? videoPath.resolve("camera.glb")
|
||||||
|
: videoPath.resolveSibling(videoPath.getFileName() + ".glb");
|
||||||
|
java.nio.file.Path glbPath = glbBasePath;
|
||||||
|
for (int i = 0; Files.exists(glbPath); i++) {
|
||||||
|
String baseName = FilenameUtils.getBaseName(glbBasePath.getFileName().toString());
|
||||||
|
glbPath = glbBasePath.resolveSibling(baseName + "." + i + ".glb");
|
||||||
|
}
|
||||||
|
try (OutputStream out = Files.newOutputStream(glbPath)) {
|
||||||
|
GltfAssetV2 asset = new GltfAssetV2(glTF, binaryData);
|
||||||
|
new GltfAssetWriterV2().writeBinary(asset, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/main/java/com/replaymod/render/EXRWriter.java
Normal file
125
src/main/java/com/replaymod/render/EXRWriter.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
//#if MC>=11400
|
||||||
|
package com.replaymod.render;
|
||||||
|
|
||||||
|
import com.replaymod.core.versions.MCVer;
|
||||||
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
|
import com.replaymod.render.rendering.FrameConsumer;
|
||||||
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
import net.minecraft.util.crash.CrashReport;
|
||||||
|
import org.lwjgl.PointerBuffer;
|
||||||
|
import org.lwjgl.util.tinyexr.EXRChannelInfo;
|
||||||
|
import org.lwjgl.util.tinyexr.EXRHeader;
|
||||||
|
import org.lwjgl.util.tinyexr.EXRImage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.lwjgl.system.MemoryStack.*;
|
||||||
|
import static org.lwjgl.system.MemoryUtil.*;
|
||||||
|
import static org.lwjgl.util.tinyexr.TinyEXR.*;
|
||||||
|
|
||||||
|
public class EXRWriter implements FrameConsumer<BitmapFrame> {
|
||||||
|
|
||||||
|
private final Path outputFolder;
|
||||||
|
|
||||||
|
public EXRWriter(Path outputFolder) throws IOException {
|
||||||
|
this.outputFolder = outputFolder;
|
||||||
|
|
||||||
|
Files.createDirectories(outputFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(Map<Channel, BitmapFrame> channels) {
|
||||||
|
BitmapFrame bgraFrame = channels.get(Channel.BRGA);
|
||||||
|
BitmapFrame depthFrame = channels.get(Channel.DEPTH);
|
||||||
|
|
||||||
|
Path path = outputFolder.resolve(bgraFrame.getFrameId() + ".exr");
|
||||||
|
ReadableDimension size = bgraFrame.getSize();
|
||||||
|
ByteBuffer bgra = bgraFrame.getByteBuffer();
|
||||||
|
int width = size.getWidth();
|
||||||
|
int height = size.getHeight();
|
||||||
|
int numChannels = 4 + (depthFrame != null ? 1 : 0);
|
||||||
|
|
||||||
|
stackPush();
|
||||||
|
EXRHeader header = EXRHeader.mallocStack(); InitEXRHeader(header);
|
||||||
|
EXRChannelInfo.Buffer channelInfos = EXRChannelInfo.mallocStack(numChannels);
|
||||||
|
IntBuffer pixelTypes = stackMallocInt(numChannels);
|
||||||
|
IntBuffer requestedPixelTypes = stackMallocInt(numChannels);
|
||||||
|
EXRImage image = EXRImage.mallocStack(); InitEXRImage(image);
|
||||||
|
PointerBuffer imagePointers = stackMallocPointer(numChannels);
|
||||||
|
FloatBuffer images = memAllocFloat(width * height * numChannels);
|
||||||
|
PointerBuffer err = stackMallocPointer(1);
|
||||||
|
try {
|
||||||
|
header.num_channels(numChannels);
|
||||||
|
header.channels(channelInfos);
|
||||||
|
header.pixel_types(pixelTypes);
|
||||||
|
header.requested_pixel_types(requestedPixelTypes);
|
||||||
|
|
||||||
|
// Some readers ignore this, so we use the most expected order
|
||||||
|
memASCII("A", true, channelInfos.get(0).name());
|
||||||
|
memASCII("B", true, channelInfos.get(1).name());
|
||||||
|
memASCII("G", true, channelInfos.get(2).name());
|
||||||
|
memASCII("R", true, channelInfos.get(3).name());
|
||||||
|
for (int i = 0; i < numChannels; i++) {
|
||||||
|
pixelTypes.put(i, TINYEXR_PIXELTYPE_FLOAT);
|
||||||
|
requestedPixelTypes.put(i, TINYEXR_PIXELTYPE_HALF);
|
||||||
|
}
|
||||||
|
if (depthFrame != null) {
|
||||||
|
memASCII("Z", true, channelInfos.get(4).name());
|
||||||
|
requestedPixelTypes.put(4, TINYEXR_PIXELTYPE_FLOAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
image.num_channels(numChannels);
|
||||||
|
image.width(width);
|
||||||
|
image.height(height);
|
||||||
|
image.images(imagePointers);
|
||||||
|
FloatBuffer[] bgrChannels = new FloatBuffer[4];
|
||||||
|
FloatBuffer depthChannel = null;
|
||||||
|
for (int i = 0; i < numChannels; i++) {
|
||||||
|
FloatBuffer channel = images.slice();
|
||||||
|
channel.position(width * height * i);
|
||||||
|
imagePointers.put(i, channel.slice());
|
||||||
|
if (i == 4) {
|
||||||
|
depthChannel = channel;
|
||||||
|
} else {
|
||||||
|
bgrChannels[(i + 3) % 4] = channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (FloatBuffer channel : bgrChannels) {
|
||||||
|
channel.put(((int) bgra.get() & 0xff) / 255f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (depthFrame != null && depthChannel != null) {
|
||||||
|
depthChannel.put(depthFrame.getByteBuffer().asFloatBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = SaveEXRImageToFile(image, header, path.toString(), err);
|
||||||
|
if (ret != TINYEXR_SUCCESS) {
|
||||||
|
String message = err.getStringASCII(0);
|
||||||
|
FreeEXRErrorMessage(err.getByteBuffer(0));
|
||||||
|
throw new IOException(message);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
MCVer.getMinecraft().setCrashReport(CrashReport.create(t, "Exporting EXR frame"));
|
||||||
|
} finally {
|
||||||
|
memFree(images);
|
||||||
|
stackPop();
|
||||||
|
channels.values().forEach(it -> ByteBufferPool.release(it.getByteBuffer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package com.replaymod.render;
|
package com.replaymod.render;
|
||||||
|
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
import com.replaymod.render.rendering.FrameConsumer;
|
import com.replaymod.render.rendering.FrameConsumer;
|
||||||
import com.replaymod.render.rendering.VideoRenderer;
|
import com.replaymod.render.rendering.VideoRenderer;
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
@@ -21,12 +22,13 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static com.replaymod.render.ReplayModRender.LOGGER;
|
import static com.replaymod.render.ReplayModRender.LOGGER;
|
||||||
import static org.apache.commons.lang3.Validate.isTrue;
|
import static org.apache.commons.lang3.Validate.isTrue;
|
||||||
|
|
||||||
public class VideoWriter implements FrameConsumer<RGBFrame> {
|
public class FFmpegWriter implements FrameConsumer<BitmapFrame> {
|
||||||
|
|
||||||
private final VideoRenderer renderer;
|
private final VideoRenderer renderer;
|
||||||
private final RenderSettings settings;
|
private final RenderSettings settings;
|
||||||
@@ -38,7 +40,7 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
|
|||||||
|
|
||||||
private ByteArrayOutputStream ffmpegLog = new ByteArrayOutputStream(4096);
|
private ByteArrayOutputStream ffmpegLog = new ByteArrayOutputStream(4096);
|
||||||
|
|
||||||
public VideoWriter(final VideoRenderer renderer) throws IOException {
|
public FFmpegWriter(final VideoRenderer renderer) throws IOException {
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
this.settings = renderer.getRenderSettings();
|
this.settings = renderer.getRenderSettings();
|
||||||
|
|
||||||
@@ -103,11 +105,11 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(RGBFrame frame) {
|
public void consume(Map<Channel, BitmapFrame> channels) {
|
||||||
|
BitmapFrame frame = channels.get(Channel.BRGA);
|
||||||
try {
|
try {
|
||||||
checkSize(frame.getSize());
|
checkSize(frame.getSize());
|
||||||
channel.write(frame.getByteBuffer());
|
channel.write(frame.getByteBuffer());
|
||||||
ByteBufferPool.release(frame.getByteBuffer());
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
if (aborted) {
|
if (aborted) {
|
||||||
return;
|
return;
|
||||||
@@ -127,6 +129,8 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
|
|||||||
MCVer.addDetail(exportDetails, "Export command", settings::getExportCommand);
|
MCVer.addDetail(exportDetails, "Export command", settings::getExportCommand);
|
||||||
MCVer.addDetail(exportDetails, "Export args", commandArgs::toString);
|
MCVer.addDetail(exportDetails, "Export args", commandArgs::toString);
|
||||||
MCVer.getMinecraft().setCrashReport(report);
|
MCVer.getMinecraft().setCrashReport(report);
|
||||||
|
} finally {
|
||||||
|
channels.values().forEach(it -> ByteBufferPool.release(it.getByteBuffer()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,12 @@ import net.minecraft.client.resource.language.I18n;
|
|||||||
import net.minecraft.util.Util;
|
import net.minecraft.util.Util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.SimpleFileVisitor;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -73,6 +79,8 @@ public class RenderSettings {
|
|||||||
|
|
||||||
BLEND(null, "blend"),
|
BLEND(null, "blend"),
|
||||||
|
|
||||||
|
EXR(null, "exr"),
|
||||||
|
|
||||||
PNG("\"%FILENAME%-%06d.png\"", "png");
|
PNG("\"%FILENAME%-%06d.png\"", "png");
|
||||||
|
|
||||||
private final String preset;
|
private final String preset;
|
||||||
@@ -105,6 +113,13 @@ public class RenderSettings {
|
|||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
if (this == BLEND) {
|
if (this == BLEND) {
|
||||||
return RenderMethod.BLEND.isSupported();
|
return RenderMethod.BLEND.isSupported();
|
||||||
|
} else if (this == EXR) {
|
||||||
|
// Need LJWGL 3
|
||||||
|
//#if MC>=11400
|
||||||
|
return true;
|
||||||
|
//#else
|
||||||
|
//$$ return false;
|
||||||
|
//#endif
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -150,6 +165,8 @@ public class RenderSettings {
|
|||||||
private final int sphericalFovX;
|
private final int sphericalFovX;
|
||||||
private final int sphericalFovY;
|
private final int sphericalFovY;
|
||||||
private final boolean injectSphericalMetadata;
|
private final boolean injectSphericalMetadata;
|
||||||
|
private final boolean depthMap;
|
||||||
|
private final boolean cameraPathExport;
|
||||||
private final AntiAliasing antiAliasing;
|
private final AntiAliasing antiAliasing;
|
||||||
|
|
||||||
private final String exportCommand;
|
private final String exportCommand;
|
||||||
@@ -180,6 +197,8 @@ public class RenderSettings {
|
|||||||
int sphericalFovX,
|
int sphericalFovX,
|
||||||
int sphericalFovY,
|
int sphericalFovY,
|
||||||
boolean injectSphericalMetadata,
|
boolean injectSphericalMetadata,
|
||||||
|
boolean depthMap,
|
||||||
|
boolean cameraPathExport,
|
||||||
AntiAliasing antiAliasing,
|
AntiAliasing antiAliasing,
|
||||||
String exportCommand,
|
String exportCommand,
|
||||||
String exportArguments,
|
String exportArguments,
|
||||||
@@ -200,6 +219,8 @@ public class RenderSettings {
|
|||||||
this.sphericalFovX = sphericalFovX;
|
this.sphericalFovX = sphericalFovX;
|
||||||
this.sphericalFovY = sphericalFovY;
|
this.sphericalFovY = sphericalFovY;
|
||||||
this.injectSphericalMetadata = injectSphericalMetadata;
|
this.injectSphericalMetadata = injectSphericalMetadata;
|
||||||
|
this.depthMap = depthMap;
|
||||||
|
this.cameraPathExport = cameraPathExport;
|
||||||
this.antiAliasing = antiAliasing;
|
this.antiAliasing = antiAliasing;
|
||||||
this.exportCommand = exportCommand;
|
this.exportCommand = exportCommand;
|
||||||
this.exportArguments = exportArguments;
|
this.exportArguments = exportArguments;
|
||||||
@@ -223,6 +244,8 @@ public class RenderSettings {
|
|||||||
sphericalFovX,
|
sphericalFovX,
|
||||||
sphericalFovY,
|
sphericalFovY,
|
||||||
injectSphericalMetadata,
|
injectSphericalMetadata,
|
||||||
|
depthMap,
|
||||||
|
cameraPathExport,
|
||||||
antiAliasing,
|
antiAliasing,
|
||||||
exportCommand,
|
exportCommand,
|
||||||
exportArguments,
|
exportArguments,
|
||||||
@@ -277,11 +300,35 @@ public class RenderSettings {
|
|||||||
switch (Util.getOperatingSystem()) {
|
switch (Util.getOperatingSystem()) {
|
||||||
case WINDOWS:
|
case WINDOWS:
|
||||||
// Allow windows users to unpack the ffmpeg archive into a sub-folder of their .minecraft folder
|
// Allow windows users to unpack the ffmpeg archive into a sub-folder of their .minecraft folder
|
||||||
File inDotMinecraft = new File(MCVer.getMinecraft().runDirectory, "ffmpeg/bin/ffmpeg.exe");
|
File dotMinecraft = MCVer.getMinecraft().runDirectory;
|
||||||
|
File inDotMinecraft = new File(dotMinecraft, "ffmpeg/bin/ffmpeg.exe");
|
||||||
if (inDotMinecraft.exists()) {
|
if (inDotMinecraft.exists()) {
|
||||||
LOGGER.debug("FFmpeg found in .minecraft/ffmpeg");
|
LOGGER.debug("FFmpeg found in .minecraft/ffmpeg");
|
||||||
return inDotMinecraft.getAbsolutePath();
|
return inDotMinecraft.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
// But a significant amount of people are not even able to follow instruction to do that.
|
||||||
|
// Instead they'll regularly put it at `.minecraft/ffmpeg-version/bin/ffmpeg.exe`
|
||||||
|
// or `.minecraft/ffmpeg/ffmpeg-version/bin/ffmpeg.exe`
|
||||||
|
// So, for support's sake, let's just search the entire .minecraft folder for an `ffmpeg.exe` file.
|
||||||
|
// We retain above check to have a fast path for any old installations.
|
||||||
|
try {
|
||||||
|
Path[] result = new Path[1];
|
||||||
|
Files.walkFileTree(dotMinecraft.toPath(), new SimpleFileVisitor<Path>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
if ("ffmpeg.exe".equals(file.getFileName().toString())) {
|
||||||
|
result[0] = file;
|
||||||
|
return FileVisitResult.TERMINATE;
|
||||||
|
}
|
||||||
|
return super.visitFile(file, attrs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (result[0] != null) {
|
||||||
|
return result[0].toAbsolutePath().toString();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.debug("Error searching .minecraft for ffmpeg.exe:", e);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case OSX:
|
case OSX:
|
||||||
// The PATH doesn't seem to be set as expected on OSX, therefore we check some common locations ourselves
|
// The PATH doesn't seem to be set as expected on OSX, therefore we check some common locations ourselves
|
||||||
@@ -373,6 +420,14 @@ public class RenderSettings {
|
|||||||
return injectSphericalMetadata;
|
return injectSphericalMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDepthMap() {
|
||||||
|
return depthMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCameraPathExport() {
|
||||||
|
return cameraPathExport;
|
||||||
|
}
|
||||||
|
|
||||||
public AntiAliasing getAntiAliasing() {
|
public AntiAliasing getAntiAliasing() {
|
||||||
return antiAliasing;
|
return antiAliasing;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ package com.replaymod.render;
|
|||||||
|
|
||||||
import com.replaymod.core.Module;
|
import com.replaymod.core.Module;
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.core.utils.Utils;
|
||||||
import com.replaymod.render.utils.RenderJob;
|
import com.replaymod.render.utils.RenderJob;
|
||||||
|
import com.replaymod.replay.ReplayHandler;
|
||||||
import com.replaymod.replay.events.ReplayClosedCallback;
|
import com.replaymod.replay.events.ReplayClosedCallback;
|
||||||
|
import com.replaymod.replay.events.ReplayOpenedCallback;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import de.johni0702.minecraft.gui.container.VanillaGuiScreen;
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||||
import net.minecraft.util.crash.CrashReport;
|
import net.minecraft.util.crash.CrashReport;
|
||||||
import net.minecraft.util.crash.CrashException;
|
import net.minecraft.util.crash.CrashException;
|
||||||
@@ -17,8 +22,6 @@ import java.nio.file.Path;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.replaymod.core.versions.MCVer.*;
|
|
||||||
|
|
||||||
public class ReplayModRender extends EventRegistrations implements Module {
|
public class ReplayModRender extends EventRegistrations implements Module {
|
||||||
{ instance = this; }
|
{ instance = this; }
|
||||||
public static ReplayModRender instance;
|
public static ReplayModRender instance;
|
||||||
@@ -27,6 +30,7 @@ public class ReplayModRender extends EventRegistrations implements Module {
|
|||||||
|
|
||||||
public static Logger LOGGER = LogManager.getLogger();
|
public static Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
|
private ReplayFile replayFile;
|
||||||
private final List<RenderJob> renderQueue = new ArrayList<>();
|
private final List<RenderJob> renderQueue = new ArrayList<>();
|
||||||
|
|
||||||
public ReplayModRender(ReplayMod core) {
|
public ReplayModRender(ReplayMod core) {
|
||||||
@@ -41,8 +45,6 @@ public class ReplayModRender extends EventRegistrations implements Module {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initClient() {
|
public void initClient() {
|
||||||
on(ReplayClosedCallback.EVENT, replayHandler -> renderQueue.clear());
|
|
||||||
|
|
||||||
register();
|
register();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,4 +66,31 @@ public class ReplayModRender extends EventRegistrations implements Module {
|
|||||||
public List<RenderJob> getRenderQueue() {
|
public List<RenderJob> getRenderQueue() {
|
||||||
return renderQueue;
|
return renderQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ on(ReplayOpenedCallback.EVENT, this::onReplayOpened); }
|
||||||
|
private void onReplayOpened(ReplayHandler replayHandler) {
|
||||||
|
replayFile = replayHandler.getReplayFile();
|
||||||
|
try {
|
||||||
|
renderQueue.addAll(RenderJob.readQueue(replayFile));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new CrashException(CrashReport.create(e, "Reading timeline"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ on(ReplayClosedCallback.EVENT, replayHandler -> onReplayClosed()); }
|
||||||
|
private void onReplayClosed() {
|
||||||
|
renderQueue.clear();
|
||||||
|
replayFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveRenderQueue() {
|
||||||
|
try {
|
||||||
|
RenderJob.writeQueue(replayFile, renderQueue);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
VanillaGuiScreen screen = VanillaGuiScreen.wrap(getCore().getMinecraft().currentScreen);
|
||||||
|
CrashReport report = CrashReport.create(e, "Reading timeline");
|
||||||
|
Utils.error(LOGGER, screen, report, () -> {});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ package com.replaymod.render.blend;
|
|||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.render.capturer.RenderInfo;
|
import com.replaymod.render.capturer.RenderInfo;
|
||||||
import com.replaymod.render.capturer.WorldRenderer;
|
import com.replaymod.render.capturer.WorldRenderer;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
import com.replaymod.render.rendering.FrameCapturer;
|
import com.replaymod.render.rendering.FrameCapturer;
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class BlendFrameCapturer implements FrameCapturer<RGBFrame> {
|
public class BlendFrameCapturer implements FrameCapturer<BitmapFrame> {
|
||||||
protected final WorldRenderer worldRenderer;
|
protected final WorldRenderer worldRenderer;
|
||||||
protected final RenderInfo renderInfo;
|
protected final RenderInfo renderInfo;
|
||||||
protected int framesDone;
|
protected int framesDone;
|
||||||
@@ -26,7 +29,7 @@ public class BlendFrameCapturer implements FrameCapturer<RGBFrame> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RGBFrame process() {
|
public Map<Channel, BitmapFrame> process() {
|
||||||
if (framesDone == 0) {
|
if (framesDone == 0) {
|
||||||
BlendState.getState().setup();
|
BlendState.getState().setup();
|
||||||
}
|
}
|
||||||
@@ -37,7 +40,8 @@ public class BlendFrameCapturer implements FrameCapturer<RGBFrame> {
|
|||||||
worldRenderer.renderWorld(MCVer.getRenderPartialTicks(), null);
|
worldRenderer.renderWorld(MCVer.getRenderPartialTicks(), null);
|
||||||
BlendState.getState().postFrame(framesDone);
|
BlendState.getState().postFrame(framesDone);
|
||||||
|
|
||||||
return new RGBFrame(framesDone++, new Dimension(0, 0), ByteBufferPool.allocate(0));
|
BitmapFrame frame = new BitmapFrame(framesDone++, new Dimension(0, 0), 0, ByteBufferPool.allocate(0));
|
||||||
|
return Collections.singletonMap(Channel.BRGA, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package com.replaymod.render.capturer;
|
package com.replaymod.render.capturer;
|
||||||
|
|
||||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
||||||
public enum Data implements CaptureData {
|
public enum Data implements CaptureData {
|
||||||
@@ -25,14 +29,15 @@ public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer<CubicOpenGlFra
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CubicOpenGlFrame process() {
|
public Map<Channel, CubicOpenGlFrame> process() {
|
||||||
float partialTicks = renderInfo.updateForNextFrame();
|
float partialTicks = renderInfo.updateForNextFrame();
|
||||||
int frameId = framesDone++;
|
int frameId = framesDone++;
|
||||||
return new CubicOpenGlFrame(renderFrame(frameId, partialTicks, Data.LEFT),
|
CubicOpenGlFrame frame = new CubicOpenGlFrame(renderFrame(frameId, partialTicks, Data.LEFT),
|
||||||
renderFrame(frameId, partialTicks, Data.RIGHT),
|
renderFrame(frameId, partialTicks, Data.RIGHT),
|
||||||
renderFrame(frameId, partialTicks, Data.FRONT),
|
renderFrame(frameId, partialTicks, Data.FRONT),
|
||||||
renderFrame(frameId, partialTicks, Data.BACK),
|
renderFrame(frameId, partialTicks, Data.BACK),
|
||||||
renderFrame(frameId, partialTicks, Data.TOP),
|
renderFrame(frameId, partialTicks, Data.TOP),
|
||||||
renderFrame(frameId, partialTicks, Data.BOTTOM));
|
renderFrame(frameId, partialTicks, Data.BOTTOM));
|
||||||
|
return Collections.singletonMap(Channel.BRGA, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import com.replaymod.render.frame.CubicOpenGlFrame;
|
|||||||
import com.replaymod.render.frame.OpenGlFrame;
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
|
|
||||||
public class CubicPboOpenGlFrameCapturer extends
|
public class CubicPboOpenGlFrameCapturer extends
|
||||||
MultiFramePboOpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
PboOpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
||||||
|
|
||||||
private final int frameSize;
|
private final int frameSize;
|
||||||
public CubicPboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) {
|
public CubicPboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.replaymod.render.capturer;
|
package com.replaymod.render.capturer;
|
||||||
|
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||||
import com.replaymod.render.RenderSettings;
|
import com.replaymod.render.RenderSettings;
|
||||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||||
@@ -17,6 +18,8 @@ import net.minecraft.util.Identifier;
|
|||||||
import static com.mojang.blaze3d.platform.GlStateManager.*;
|
import static com.mojang.blaze3d.platform.GlStateManager.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
||||||
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
|
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
|
||||||
@@ -125,16 +128,22 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ODSOpenGlFrame process() {
|
public Map<Channel, ODSOpenGlFrame> process() {
|
||||||
bindProgram();
|
bindProgram();
|
||||||
leftEyeVariable.set(true);
|
leftEyeVariable.set(true);
|
||||||
CubicOpenGlFrame leftFrame = left.process();
|
Map<Channel, CubicOpenGlFrame> leftChannels = left.process();
|
||||||
leftEyeVariable.set(false);
|
leftEyeVariable.set(false);
|
||||||
CubicOpenGlFrame rightFrame = right.process();
|
Map<Channel, CubicOpenGlFrame> rightChannels = right.process();
|
||||||
unbindProgram();
|
unbindProgram();
|
||||||
|
|
||||||
if (leftFrame != null && rightFrame != null) {
|
if (leftChannels != null && rightChannels != null) {
|
||||||
return new ODSOpenGlFrame(leftFrame, rightFrame);
|
Map<Channel, ODSOpenGlFrame> result = new HashMap<>();
|
||||||
|
for (Channel channel : Channel.values()) {
|
||||||
|
CubicOpenGlFrame leftFrame = leftChannels.get(channel);
|
||||||
|
CubicOpenGlFrame rightFrame = rightChannels.get(channel);
|
||||||
|
result.put(channel, new ODSOpenGlFrame(leftFrame, rightFrame));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData
|
|||||||
frameBuffer().endWrite();
|
frameBuffer().endWrite();
|
||||||
buffer.rewind();
|
buffer.rewind();
|
||||||
|
|
||||||
return new OpenGlFrame(frameId, new Dimension(getFrameWidth(), getFrameHeight()), buffer);
|
return new OpenGlFrame(frameId, new Dimension(getFrameWidth(), getFrameHeight()), 4, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void resize(int width, int height) {
|
protected void resize(int width, int height) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.replaymod.render.capturer;
|
package com.replaymod.render.capturer;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.GlStateManager;
|
||||||
import com.replaymod.render.frame.OpenGlFrame;
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
import com.replaymod.render.rendering.Frame;
|
import com.replaymod.render.rendering.Frame;
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
import com.replaymod.render.utils.PixelBufferObject;
|
import com.replaymod.render.utils.PixelBufferObject;
|
||||||
@@ -9,17 +11,21 @@ import org.lwjgl.opengl.GL12;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public abstract class MultiFramePboOpenGlFrameCapturer<F extends Frame, D extends Enum<D> & CaptureData>
|
public abstract class PboOpenGlFrameCapturer<F extends Frame, D extends Enum<D> & CaptureData>
|
||||||
extends OpenGlFrameCapturer<F, D> {
|
extends OpenGlFrameCapturer<F, D> {
|
||||||
|
private final boolean withDepth;
|
||||||
private final D[] data;
|
private final D[] data;
|
||||||
private PixelBufferObject pbo, otherPBO;
|
private PixelBufferObject pbo, otherPBO;
|
||||||
|
|
||||||
public MultiFramePboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, Class<D> type, int framePixels) {
|
public PboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, Class<D> type, int framePixels) {
|
||||||
super(worldRenderer, renderInfo);
|
super(worldRenderer, renderInfo);
|
||||||
|
|
||||||
|
withDepth = renderInfo.getRenderSettings().isDepthMap();
|
||||||
data = type.getEnumConstants();
|
data = type.getEnumConstants();
|
||||||
int bufferSize = framePixels * 4 * data.length;
|
int bufferSize = framePixels * (4 /* bgra */ + (withDepth ? 4 /* float */ : 0)) * data.length;
|
||||||
pbo = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
pbo = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
||||||
otherPBO = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
otherPBO = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
||||||
}
|
}
|
||||||
@@ -37,29 +43,36 @@ public abstract class MultiFramePboOpenGlFrameCapturer<F extends Frame, D extend
|
|||||||
return framesDone >= renderInfo.getTotalFrames() + 2;
|
return framesDone >= renderInfo.getTotalFrames() + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private F readFromPbo(ByteBuffer pboBuffer, int bytesPerPixel) {
|
||||||
|
OpenGlFrame[] frames = new OpenGlFrame[data.length];
|
||||||
|
int frameBufferSize = getFrameWidth() * getFrameHeight() * bytesPerPixel;
|
||||||
|
for (int i = 0; i < frames.length; i++) {
|
||||||
|
ByteBuffer frameBuffer = ByteBufferPool.allocate(frameBufferSize);
|
||||||
|
pboBuffer.limit(pboBuffer.position() + frameBufferSize);
|
||||||
|
frameBuffer.put(pboBuffer);
|
||||||
|
frameBuffer.rewind();
|
||||||
|
frames[i] = new OpenGlFrame(framesDone - 2, frameSize, bytesPerPixel, frameBuffer);
|
||||||
|
}
|
||||||
|
return create(frames);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public F process() {
|
public Map<Channel, F> process() {
|
||||||
F frame = null;
|
Map<Channel, F> channels = null;
|
||||||
|
|
||||||
if (framesDone > 1) {
|
if (framesDone > 1) {
|
||||||
// Read pbo to memory
|
// Read pbo to memory
|
||||||
pbo.bind();
|
pbo.bind();
|
||||||
ByteBuffer pboBuffer = pbo.mapReadOnly();
|
ByteBuffer pboBuffer = pbo.mapReadOnly();
|
||||||
|
|
||||||
OpenGlFrame[] frames = new OpenGlFrame[data.length];
|
channels = new HashMap<>();
|
||||||
int frameBufferSize = getFrameWidth() * getFrameHeight() * 4;
|
channels.put(Channel.BRGA, readFromPbo(pboBuffer, 4));
|
||||||
for (int i = 0; i < frames.length; i++) {
|
if (withDepth) {
|
||||||
ByteBuffer frameBuffer = ByteBufferPool.allocate(frameBufferSize);
|
channels.put(Channel.DEPTH, readFromPbo(pboBuffer, 4));
|
||||||
pboBuffer.limit(pboBuffer.position() + frameBufferSize);
|
|
||||||
frameBuffer.put(pboBuffer);
|
|
||||||
frameBuffer.rewind();
|
|
||||||
frames[i] = new OpenGlFrame(framesDone - 2, frameSize, frameBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pbo.unmap();
|
pbo.unmap();
|
||||||
pbo.unbind();
|
pbo.unbind();
|
||||||
|
|
||||||
frame = create(frames);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (framesDone < renderInfo.getTotalFrames()) {
|
if (framesDone < renderInfo.getTotalFrames()) {
|
||||||
@@ -72,7 +85,7 @@ public abstract class MultiFramePboOpenGlFrameCapturer<F extends Frame, D extend
|
|||||||
|
|
||||||
framesDone++;
|
framesDone++;
|
||||||
swapPBOs();
|
swapPBOs();
|
||||||
return frame;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,6 +95,10 @@ public abstract class MultiFramePboOpenGlFrameCapturer<F extends Frame, D extend
|
|||||||
int offset = captureData.ordinal() * getFrameWidth() * getFrameHeight() * 4;
|
int offset = captureData.ordinal() * getFrameWidth() * getFrameHeight() * 4;
|
||||||
frameBuffer().beginWrite(true);
|
frameBuffer().beginWrite(true);
|
||||||
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, offset);
|
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, offset);
|
||||||
|
if (withDepth) {
|
||||||
|
offset += data.length * getFrameWidth() * getFrameHeight() * 4;
|
||||||
|
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, offset);
|
||||||
|
}
|
||||||
frameBuffer().endWrite();
|
frameBuffer().endWrite();
|
||||||
|
|
||||||
pbo.unbind();
|
pbo.unbind();
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package com.replaymod.render.capturer;
|
package com.replaymod.render.capturer;
|
||||||
|
|
||||||
import com.replaymod.render.frame.OpenGlFrame;
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class SimpleOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame, CaptureData> {
|
public class SimpleOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame, CaptureData> {
|
||||||
|
|
||||||
@@ -9,8 +13,9 @@ public class SimpleOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OpenGlFrame process() {
|
public Map<Channel, OpenGlFrame> process() {
|
||||||
float partialTicks = renderInfo.updateForNextFrame();
|
float partialTicks = renderInfo.updateForNextFrame();
|
||||||
return renderFrame(framesDone++, partialTicks);
|
OpenGlFrame frame = renderFrame(framesDone++, partialTicks);
|
||||||
|
return Collections.singletonMap(Channel.BRGA, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,20 @@
|
|||||||
package com.replaymod.render.capturer;
|
package com.replaymod.render.capturer;
|
||||||
|
|
||||||
import com.replaymod.render.frame.OpenGlFrame;
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
|
||||||
import com.replaymod.render.utils.PixelBufferObject;
|
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
|
||||||
import org.lwjgl.opengl.GL11;
|
|
||||||
import org.lwjgl.opengl.GL12;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
public class SimplePboOpenGlFrameCapturer extends PboOpenGlFrameCapturer<OpenGlFrame, SimplePboOpenGlFrameCapturer.SinglePass> {
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
public class SimplePboOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame, CaptureData> {
|
|
||||||
private final int bufferSize;
|
|
||||||
private PixelBufferObject pbo, otherPBO;
|
|
||||||
|
|
||||||
public SimplePboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
public SimplePboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
||||||
super(worldRenderer, renderInfo);
|
super(worldRenderer, renderInfo, SinglePass.class,
|
||||||
|
renderInfo.getFrameSize().getWidth() * renderInfo.getFrameSize().getHeight());
|
||||||
ReadableDimension size = renderInfo.getFrameSize();
|
|
||||||
bufferSize = size.getHeight() * size.getWidth() * 4;
|
|
||||||
pbo = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
|
||||||
otherPBO = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void swapPBOs() {
|
|
||||||
PixelBufferObject old = pbo;
|
|
||||||
pbo = otherPBO;
|
|
||||||
otherPBO = old;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDone() {
|
protected OpenGlFrame create(OpenGlFrame[] from) {
|
||||||
return framesDone >= renderInfo.getTotalFrames() + 2;
|
return from[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public enum SinglePass implements CaptureData {
|
||||||
public OpenGlFrame process() {
|
SINGLE_PASS
|
||||||
OpenGlFrame frame = null;
|
|
||||||
|
|
||||||
if (framesDone > 1) {
|
|
||||||
// Read pbo to memory
|
|
||||||
pbo.bind();
|
|
||||||
ByteBuffer pboBuffer = pbo.mapReadOnly();
|
|
||||||
ByteBuffer buffer = ByteBufferPool.allocate(bufferSize);
|
|
||||||
buffer.put(pboBuffer);
|
|
||||||
buffer.rewind();
|
|
||||||
pbo.unmap();
|
|
||||||
pbo.unbind();
|
|
||||||
frame = new OpenGlFrame(framesDone - 2, frameSize, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (framesDone < renderInfo.getTotalFrames()) {
|
|
||||||
// Then fill it again
|
|
||||||
renderFrame(framesDone, renderInfo.updateForNextFrame());
|
|
||||||
}
|
|
||||||
|
|
||||||
framesDone++;
|
|
||||||
swapPBOs();
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected OpenGlFrame captureFrame(int frameId, CaptureData data) {
|
|
||||||
pbo.bind();
|
|
||||||
|
|
||||||
frameBuffer().beginWrite(true);
|
|
||||||
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, 0);
|
|
||||||
frameBuffer().endWrite();
|
|
||||||
|
|
||||||
pbo.unbind();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
super.close();
|
|
||||||
pbo.delete();
|
|
||||||
otherPBO.delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ package com.replaymod.render.capturer;
|
|||||||
|
|
||||||
import com.replaymod.render.frame.OpenGlFrame;
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class StereoscopicOpenGlFrameCapturer
|
public class StereoscopicOpenGlFrameCapturer
|
||||||
extends OpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
extends OpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
||||||
@@ -19,11 +23,12 @@ public class StereoscopicOpenGlFrameCapturer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StereoscopicOpenGlFrame process() {
|
public Map<Channel, StereoscopicOpenGlFrame> process() {
|
||||||
float partialTicks = renderInfo.updateForNextFrame();
|
float partialTicks = renderInfo.updateForNextFrame();
|
||||||
int frameId = framesDone++;
|
int frameId = framesDone++;
|
||||||
OpenGlFrame left = renderFrame(frameId, partialTicks, Data.LEFT_EYE);
|
OpenGlFrame left = renderFrame(frameId, partialTicks, Data.LEFT_EYE);
|
||||||
OpenGlFrame right = renderFrame(frameId, partialTicks, Data.RIGHT_EYE);
|
OpenGlFrame right = renderFrame(frameId, partialTicks, Data.RIGHT_EYE);
|
||||||
return new StereoscopicOpenGlFrame(left, right);
|
StereoscopicOpenGlFrame frame = new StereoscopicOpenGlFrame(left, right);
|
||||||
|
return Collections.singletonMap(Channel.BRGA, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import com.replaymod.render.frame.OpenGlFrame;
|
|||||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||||
|
|
||||||
public class StereoscopicPboOpenGlFrameCapturer
|
public class StereoscopicPboOpenGlFrameCapturer
|
||||||
extends MultiFramePboOpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
extends PboOpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
||||||
|
|
||||||
public StereoscopicPboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
public StereoscopicPboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
||||||
super(worldRenderer, renderInfo, StereoscopicOpenGlFrameCapturer.Data.class,
|
super(worldRenderer, renderInfo, StereoscopicOpenGlFrameCapturer.Data.class,
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ import org.apache.commons.lang3.Validate;
|
|||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class RGBFrame implements Frame {
|
public class BitmapFrame implements Frame {
|
||||||
private final int frameId;
|
private final int frameId;
|
||||||
|
|
||||||
private final ReadableDimension size;
|
private final ReadableDimension size;
|
||||||
|
private final int bytesPerPixel;
|
||||||
private final ByteBuffer byteBuffer;
|
private final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
public RGBFrame(int frameId, ReadableDimension size, ByteBuffer byteBuffer) {
|
public BitmapFrame(int frameId, ReadableDimension size, int bytesPerPixel, ByteBuffer byteBuffer) {
|
||||||
Validate.isTrue(size.getWidth() * size.getHeight() * 4 == byteBuffer.remaining(),
|
Validate.isTrue(size.getWidth() * size.getHeight() * bytesPerPixel == byteBuffer.remaining(),
|
||||||
"Buffer size is %d (cap: %d) but should be %d",
|
"Buffer size is %d (cap: %d) but should be %d",
|
||||||
byteBuffer.remaining(), byteBuffer.capacity(), size.getWidth() * size.getHeight() * 4);
|
byteBuffer.remaining(), byteBuffer.capacity(), size.getWidth() * size.getHeight() * bytesPerPixel);
|
||||||
this.frameId = frameId;
|
this.frameId = frameId;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
this.bytesPerPixel = bytesPerPixel;
|
||||||
this.byteBuffer = byteBuffer;
|
this.byteBuffer = byteBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +30,10 @@ public class RGBFrame implements Frame {
|
|||||||
return this.size;
|
return this.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getBytesPerPixel() {
|
||||||
|
return bytesPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
public ByteBuffer getByteBuffer() {
|
public ByteBuffer getByteBuffer() {
|
||||||
return this.byteBuffer;
|
return this.byteBuffer;
|
||||||
}
|
}
|
||||||
@@ -7,14 +7,14 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
public class OpenGlFrame implements Frame {
|
public class OpenGlFrame implements Frame {
|
||||||
private final int frameId;
|
private final int frameId;
|
||||||
|
|
||||||
private final ReadableDimension size;
|
private final ReadableDimension size;
|
||||||
|
private final int bytesPerPixel;
|
||||||
private final ByteBuffer byteBuffer;
|
private final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
public OpenGlFrame(int frameId, ReadableDimension size, ByteBuffer byteBuffer) {
|
public OpenGlFrame(int frameId, ReadableDimension size, int bytesPerPixel, ByteBuffer byteBuffer) {
|
||||||
this.frameId = frameId;
|
this.frameId = frameId;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
this.bytesPerPixel = bytesPerPixel;
|
||||||
this.byteBuffer = byteBuffer;
|
this.byteBuffer = byteBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +26,10 @@ public class OpenGlFrame implements Frame {
|
|||||||
return this.size;
|
return this.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getBytesPerPixel() {
|
||||||
|
return bytesPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
public ByteBuffer getByteBuffer() {
|
public ByteBuffer getByteBuffer() {
|
||||||
return this.byteBuffer;
|
return this.byteBuffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.replaymod.render.gui;
|
|||||||
|
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.render.RenderSettings;
|
import com.replaymod.render.RenderSettings;
|
||||||
import com.replaymod.render.VideoWriter;
|
import com.replaymod.render.FFmpegWriter;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
||||||
@@ -23,7 +23,7 @@ import static com.replaymod.core.versions.MCVer.addDetail;
|
|||||||
import static com.replaymod.render.ReplayModRender.LOGGER;
|
import static com.replaymod.render.ReplayModRender.LOGGER;
|
||||||
|
|
||||||
public class GuiExportFailed extends GuiScreen {
|
public class GuiExportFailed extends GuiScreen {
|
||||||
public static GuiExportFailed tryToRecover(VideoWriter.FFmpegStartupException e, Consumer<RenderSettings> doRestart) {
|
public static GuiExportFailed tryToRecover(FFmpegWriter.FFmpegStartupException e, Consumer<RenderSettings> doRestart) {
|
||||||
// Always log the error first
|
// Always log the error first
|
||||||
LOGGER.error("Rendering video:", e);
|
LOGGER.error("Rendering video:", e);
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ public class GuiExportFailed extends GuiScreen {
|
|||||||
setBackground(Background.DIRT);
|
setBackground(Background.DIRT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GuiExportFailed(VideoWriter.FFmpegStartupException e, Consumer<RenderSettings> doRestart) {
|
public GuiExportFailed(FFmpegWriter.FFmpegStartupException e, Consumer<RenderSettings> doRestart) {
|
||||||
logList.getListPanel().addElements(null,
|
logList.getListPanel().addElements(null,
|
||||||
Arrays.stream(e.getLog().replace("\t", " ").split("\n"))
|
Arrays.stream(e.getLog().replace("\t", " ").split("\n"))
|
||||||
.map(l -> new GuiLabel().setText(l))
|
.map(l -> new GuiLabel().setText(l))
|
||||||
@@ -98,6 +98,8 @@ public class GuiExportFailed extends GuiScreen {
|
|||||||
oldSettings.getSphericalFovX(),
|
oldSettings.getSphericalFovX(),
|
||||||
oldSettings.getSphericalFovY(),
|
oldSettings.getSphericalFovY(),
|
||||||
oldSettings.isInjectSphericalMetadata(),
|
oldSettings.isInjectSphericalMetadata(),
|
||||||
|
oldSettings.isDepthMap(),
|
||||||
|
oldSettings.isCameraPathExport(),
|
||||||
oldSettings.getAntiAliasing(),
|
oldSettings.getAntiAliasing(),
|
||||||
oldSettings.getExportCommand(),
|
oldSettings.getExportCommand(),
|
||||||
oldSettings.getEncodingPreset().getValue(),
|
oldSettings.getEncodingPreset().getValue(),
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package com.replaymod.render.gui;
|
package com.replaymod.render.gui;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.core.utils.Utils;
|
import com.replaymod.core.utils.Utils;
|
||||||
|
import com.replaymod.core.versions.MCVer;
|
||||||
|
import com.replaymod.render.RenderSettings;
|
||||||
import com.replaymod.render.ReplayModRender;
|
import com.replaymod.render.ReplayModRender;
|
||||||
import com.replaymod.render.VideoWriter;
|
import com.replaymod.render.FFmpegWriter;
|
||||||
import com.replaymod.render.rendering.VideoRenderer;
|
import com.replaymod.render.rendering.VideoRenderer;
|
||||||
import com.replaymod.render.utils.RenderJob;
|
import com.replaymod.render.utils.RenderJob;
|
||||||
import com.replaymod.replay.ReplayHandler;
|
import com.replaymod.replay.ReplayHandler;
|
||||||
|
import com.replaymod.replay.ReplayModReplay;
|
||||||
|
import com.replaymod.replay.ReplaySender;
|
||||||
import com.replaymod.replaystudio.pathing.path.Timeline;
|
import com.replaymod.replaystudio.pathing.path.Timeline;
|
||||||
import com.replaymod.replaystudio.util.I18n;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair;
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.RenderInfo;
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
import de.johni0702.minecraft.gui.container.AbstractGuiClickableContainer;
|
import de.johni0702.minecraft.gui.container.AbstractGuiClickableContainer;
|
||||||
@@ -20,35 +23,47 @@ import de.johni0702.minecraft.gui.container.GuiContainer;
|
|||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
||||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiElement;
|
||||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||||
import de.johni0702.minecraft.gui.element.GuiTextField;
|
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||||
|
import de.johni0702.minecraft.gui.function.Typeable;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.GridLayout;
|
import de.johni0702.minecraft.gui.layout.GridLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||||
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
||||||
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
|
||||||
import de.johni0702.minecraft.gui.utils.Colors;
|
import de.johni0702.minecraft.gui.utils.Colors;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.gui.screen.NoticeScreen;
|
import net.minecraft.client.gui.screen.NoticeScreen;
|
||||||
import net.minecraft.util.crash.CrashReport;
|
import net.minecraft.util.crash.CrashReport;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static com.replaymod.render.ReplayModRender.LOGGER;
|
import static com.replaymod.render.ReplayModRender.LOGGER;
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
import net.minecraft.text.TranslatableText;
|
import net.minecraft.text.TranslatableText;
|
||||||
|
//#else
|
||||||
|
//$$ import com.replaymod.replaystudio.util.I18n;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements Typeable {
|
||||||
private final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.renderqueue.title").setColor(Colors.BLACK);
|
private final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.renderqueue.title").setColor(Colors.BLACK);
|
||||||
private final GuiVerticalList list = new GuiVerticalList().setDrawShadow(true).setDrawSlider(true);
|
private final GuiVerticalList list = new GuiVerticalList().setDrawShadow(true).setDrawSlider(true);
|
||||||
private final GuiButton addButton = new GuiButton().setI18nLabel("replaymod.gui.renderqueue.add").setSize(150, 20);
|
private final GuiButton addButton = new GuiButton().setI18nLabel("replaymod.gui.renderqueue.add").setSize(150, 20);
|
||||||
private final GuiButton renameButton = new GuiButton().setI18nLabel("replaymod.gui.rename").setSize(73, 20);
|
private final GuiButton editButton = new GuiButton().setI18nLabel("replaymod.gui.edit").setSize(73, 20);
|
||||||
private final GuiButton removeButton = new GuiButton().setI18nLabel("replaymod.gui.remove").setSize(73, 20);
|
private final GuiButton removeButton = new GuiButton().setI18nLabel("replaymod.gui.remove").setSize(73, 20);
|
||||||
private final GuiButton renderButton = new GuiButton().setI18nLabel("replaymod.gui.render").setSize(150, 20);
|
private final GuiButton renderButton = new GuiButton().setSize(150, 20);
|
||||||
private final GuiButton closeButton = new GuiButton().setI18nLabel("replaymod.gui.close").setSize(150, 20).onClick(this::close);
|
private final GuiButton closeButton = new GuiButton().setI18nLabel("replaymod.gui.close").setSize(150, 20).onClick(this::close);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -56,7 +71,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
|||||||
|---------------------------------|
|
|---------------------------------|
|
||||||
| Add | Render |
|
| Add | Render |
|
||||||
|---------------------------------|
|
|---------------------------------|
|
||||||
| Rename | Remove | Close |
|
| Edit | Remove | Close |
|
||||||
|---------------------------------|
|
|---------------------------------|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
@@ -66,12 +81,14 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
|||||||
addButton,
|
addButton,
|
||||||
renderButton,
|
renderButton,
|
||||||
new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4)).addElements(null,
|
new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4)).addElements(null,
|
||||||
renameButton, removeButton),
|
editButton, removeButton),
|
||||||
closeButton);
|
closeButton);
|
||||||
|
|
||||||
private final AbstractGuiScreen container;
|
private final AbstractGuiScreen<?> container;
|
||||||
private final ReplayHandler replayHandler;
|
private final ReplayHandler replayHandler;
|
||||||
private Entry selectedEntry;
|
private final Set<Entry> selectedEntries = new HashSet<>();
|
||||||
|
private final Supplier<Timeline> timelineSupplier;
|
||||||
|
private boolean opened;
|
||||||
|
|
||||||
{
|
{
|
||||||
popup.setLayout(new CustomLayout<GuiPanel>() {
|
popup.setLayout(new CustomLayout<GuiPanel>() {
|
||||||
@@ -92,120 +109,59 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
|||||||
}).addElements(null, title, list, buttonPanel);
|
}).addElements(null, title, list, buttonPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GuiRenderQueue(AbstractGuiScreen container, GuiRenderSettings guiRenderSettings, ReplayHandler replayHandler, Timeline timeline) {
|
private final ReplayModRender mod = ReplayModRender.instance;
|
||||||
|
private final List<RenderJob> jobs = mod.getRenderQueue();
|
||||||
|
|
||||||
|
public GuiRenderQueue(AbstractGuiScreen<?> container, ReplayHandler replayHandler, Supplier<Timeline> timelineSupplier) {
|
||||||
super(container);
|
super(container);
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.replayHandler = replayHandler;
|
this.replayHandler = replayHandler;
|
||||||
|
this.timelineSupplier = timelineSupplier;
|
||||||
LOGGER.trace("Opening render queue popup");
|
LOGGER.trace("Opening render queue popup");
|
||||||
|
|
||||||
setBackgroundColor(Colors.DARK_TRANSPARENT);
|
setBackgroundColor(Colors.DARK_TRANSPARENT);
|
||||||
|
|
||||||
List<RenderJob> queue = ReplayModRender.instance.getRenderQueue();
|
for (RenderJob renderJob : jobs) {
|
||||||
|
|
||||||
for (RenderJob renderJob : queue) {
|
|
||||||
LOGGER.trace("Adding {} to job queue list", renderJob);
|
LOGGER.trace("Adding {} to job queue list", renderJob);
|
||||||
list.getListPanel().addElements(null, new Entry(renderJob));
|
list.getListPanel().addElements(null, new Entry(renderJob));
|
||||||
}
|
}
|
||||||
|
|
||||||
addButton.onClick(() -> {
|
addButton.onClick(this::addButtonClicked);
|
||||||
LOGGER.trace("Add button clicked");
|
|
||||||
// Open popup
|
|
||||||
GuiYesNoPopup popup = GuiYesNoPopup.open(container)
|
|
||||||
.setYesI18nLabel("replaymod.gui.add").setNoI18nLabel("replaymod.gui.cancel");
|
|
||||||
popup.getInfo().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5));
|
|
||||||
// Add content
|
|
||||||
GuiLabel label = new GuiLabel().setI18nText("replaymod.gui.renderqueue.jobname").setColor(Colors.BLACK);
|
|
||||||
GuiTextField nameField = new GuiTextField().setSize(150, 20).setFocused(true);
|
|
||||||
popup.getInfo().addElements(new HorizontalLayout.Data(0.5), label, nameField);
|
|
||||||
// Disable "Yes" button while name is empty
|
|
||||||
nameField.onTextChanged(old -> popup.getYesButton().setEnabled(!nameField.getText().isEmpty())).onEnter(() -> {
|
|
||||||
if (popup.getYesButton().isEnabled()) {
|
|
||||||
popup.getYesButton().onClick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
popup.getYesButton().setDisabled();
|
|
||||||
// Register callback
|
|
||||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(@Nullable Boolean result) {
|
|
||||||
if (result == Boolean.TRUE) {
|
|
||||||
RenderJob newJob = new RenderJob();
|
|
||||||
newJob.setName(nameField.getText());
|
|
||||||
newJob.setSettings(guiRenderSettings.save(false));
|
|
||||||
newJob.setTimeline(timeline);
|
|
||||||
LOGGER.trace("Adding new job: {}", newJob);
|
|
||||||
queue.add(newJob);
|
|
||||||
list.getListPanel().addElements(null, new Entry(newJob));
|
|
||||||
} else {
|
|
||||||
LOGGER.trace("Adding cancelled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
editButton.onClick(() -> {
|
||||||
public void onFailure(Throwable t) {
|
Entry job = selectedEntries.iterator().next();
|
||||||
LOGGER.error("Add Job popup:", t);
|
GuiRenderSettings gui = job.edit();
|
||||||
}
|
gui.open();
|
||||||
});
|
|
||||||
});
|
|
||||||
if (guiRenderSettings != null) {
|
|
||||||
addButton.setEnabled(guiRenderSettings.renderButton.isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
renameButton.onClick(() -> {
|
|
||||||
LOGGER.trace("Rename button clicked for {}", selectedEntry.job);
|
|
||||||
// Open popup
|
|
||||||
GuiYesNoPopup popup = GuiYesNoPopup.open(container)
|
|
||||||
.setYesI18nLabel("replaymod.gui.rename").setNoI18nLabel("replaymod.gui.cancel");
|
|
||||||
popup.getInfo().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5));
|
|
||||||
// Add content
|
|
||||||
GuiLabel label = new GuiLabel().setI18nText("replaymod.gui.renderqueue.jobname").setColor(Colors.BLACK);
|
|
||||||
GuiTextField nameField = new GuiTextField().setSize(150, 20).setFocused(true)
|
|
||||||
.setText(selectedEntry.job.getName());
|
|
||||||
popup.getInfo().addElements(new HorizontalLayout.Data(0.5), label, nameField);
|
|
||||||
// Disable "Yes" button while name is empty
|
|
||||||
nameField.onTextChanged(old -> popup.getYesButton().setEnabled(!nameField.getText().isEmpty())).onEnter(() -> {
|
|
||||||
if (popup.getYesButton().isEnabled()) {
|
|
||||||
popup.getYesButton().onClick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Register callback
|
|
||||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(@Nullable Boolean result) {
|
|
||||||
if (result == Boolean.TRUE) {
|
|
||||||
LOGGER.trace("Renaming {} to \"{}\"", selectedEntry.job, nameField.getText());
|
|
||||||
selectedEntry.setName(nameField.getText());
|
|
||||||
} else {
|
|
||||||
LOGGER.trace("Renaming cancelled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable t) {
|
|
||||||
LOGGER.error("Rename Job popup:", t);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
removeButton.onClick(() -> {
|
removeButton.onClick(() -> {
|
||||||
LOGGER.trace("Remove button clicked for {}", selectedEntry.job);
|
for (Entry entry : selectedEntries) {
|
||||||
list.getListPanel().removeElement(selectedEntry);
|
LOGGER.trace("Remove button clicked for {}", entry.job);
|
||||||
queue.remove(selectedEntry.job);
|
list.getListPanel().removeElement(entry);
|
||||||
selectedEntry = null;
|
jobs.remove(entry.job);
|
||||||
|
}
|
||||||
|
selectedEntries.clear();
|
||||||
updateButtons();
|
updateButtons();
|
||||||
|
mod.saveRenderQueue();
|
||||||
});
|
});
|
||||||
|
|
||||||
renderButton.onClick(() -> {
|
renderButton.onClick(() -> {
|
||||||
LOGGER.trace("Render button clicked");
|
LOGGER.trace("Render button clicked");
|
||||||
ReplayMod.instance.runLaterWithoutLock(() -> processQueue(queue));
|
List<RenderJob> renderQueue = new ArrayList<>();
|
||||||
|
for (Entry entry : selectedEntries) {
|
||||||
|
renderQueue.add(entry.job);
|
||||||
|
}
|
||||||
|
ReplayMod.instance.runLaterWithoutLock(() -> processQueue(container, replayHandler, renderQueue, () -> {}));
|
||||||
});
|
});
|
||||||
|
|
||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processQueue(Iterable<RenderJob> queue) {
|
private static void processQueue(AbstractGuiScreen<?> container, ReplayHandler replayHandler, Iterable<RenderJob> queue, Runnable done) {
|
||||||
|
MinecraftClient mc = MCVer.getMinecraft();
|
||||||
|
|
||||||
// Close all GUIs (so settings in GuiRenderSettings are saved)
|
// Close all GUIs (so settings in GuiRenderSettings are saved)
|
||||||
getMinecraft().openScreen(null);
|
mc.openScreen(null);
|
||||||
// Start rendering
|
// Start rendering
|
||||||
int jobsDone = 0;
|
int jobsDone = 0;
|
||||||
for (RenderJob renderJob : queue) {
|
for (RenderJob renderJob : queue) {
|
||||||
@@ -213,7 +169,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
|||||||
try {
|
try {
|
||||||
VideoRenderer videoRenderer = new VideoRenderer(renderJob.getSettings(), replayHandler, renderJob.getTimeline());
|
VideoRenderer videoRenderer = new VideoRenderer(renderJob.getSettings(), replayHandler, renderJob.getTimeline());
|
||||||
videoRenderer.renderVideo();
|
videoRenderer.renderVideo();
|
||||||
} catch (VideoWriter.NoFFmpegException e) {
|
} catch (FFmpegWriter.NoFFmpegException e) {
|
||||||
LOGGER.error("Rendering video:", e);
|
LOGGER.error("Rendering video:", e);
|
||||||
NoticeScreen errorScreen = new NoticeScreen(
|
NoticeScreen errorScreen = new NoticeScreen(
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
@@ -225,29 +181,150 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
|||||||
//$$ I18n.format("replaymod.gui.rendering.error.message")
|
//$$ I18n.format("replaymod.gui.rendering.error.message")
|
||||||
//#endif
|
//#endif
|
||||||
);
|
);
|
||||||
getMinecraft().openScreen(errorScreen);
|
mc.openScreen(errorScreen);
|
||||||
return;
|
return;
|
||||||
} catch (VideoWriter.FFmpegStartupException e) {
|
} catch (FFmpegWriter.FFmpegStartupException e) {
|
||||||
int jobsToSkip = jobsDone;
|
int jobsToSkip = jobsDone;
|
||||||
GuiExportFailed.tryToRecover(e, newSettings -> {
|
GuiExportFailed.tryToRecover(e, newSettings -> {
|
||||||
// Update current job with fixed ffmpeg arguments
|
// Update current job with fixed ffmpeg arguments
|
||||||
renderJob.setSettings(newSettings);
|
renderJob.setSettings(newSettings);
|
||||||
// Restart queue, skipping the already completed jobs
|
// Restart queue, skipping the already completed jobs
|
||||||
ReplayMod.instance.runLaterWithoutLock(() -> processQueue(Iterables.skip(queue, jobsToSkip)));
|
ReplayMod.instance.runLaterWithoutLock(() -> processQueue(container, replayHandler, Iterables.skip(queue, jobsToSkip), done));
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Utils.error(LOGGER, this, CrashReport.create(t, "Rendering video"), () -> {});
|
Utils.error(LOGGER, container, CrashReport.create(t, "Rendering video"), () -> {});
|
||||||
container.display(); // Re-show the queue popup and the new error popup
|
container.display(); // Re-show the queue popup and the new error popup
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jobsDone++;
|
jobsDone++;
|
||||||
}
|
}
|
||||||
|
done.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void processMultipleReplays(
|
||||||
|
AbstractGuiScreen<?> container,
|
||||||
|
ReplayModReplay mod,
|
||||||
|
Iterator<Pair<File, List<RenderJob>>> queue,
|
||||||
|
Runnable done
|
||||||
|
) {
|
||||||
|
if (!queue.hasNext()) {
|
||||||
|
done.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Pair<File, List<RenderJob>> next = queue.next();
|
||||||
|
|
||||||
|
LOGGER.info("Opening replay {} for {} render jobs", next.getKey(), next.getValue().size());
|
||||||
|
ReplayHandler replayHandler;
|
||||||
|
ReplayFile replayFile = null;
|
||||||
|
try {
|
||||||
|
replayFile = mod.getCore().openReplay(next.getKey().toPath());
|
||||||
|
replayHandler = mod.startReplay(replayFile, true, false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {});
|
||||||
|
container.display(); // Re-show the queue popup and the new error popup
|
||||||
|
IOUtils.closeQuietly(replayFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (replayHandler == null) {
|
||||||
|
LOGGER.warn("Replay failed to open (missing mods?), skipping..");
|
||||||
|
IOUtils.closeQuietly(replayFile);
|
||||||
|
processMultipleReplays(container, mod, queue, done);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReplaySender replaySender = replayHandler.getReplaySender();
|
||||||
|
|
||||||
|
MinecraftClient mc = mod.getCore().getMinecraft();
|
||||||
|
int jumpTo = 1000;
|
||||||
|
while (mc.world == null && jumpTo < replayHandler.getReplayDuration()) {
|
||||||
|
replaySender.sendPacketsTill(jumpTo);
|
||||||
|
jumpTo += 1000;
|
||||||
|
}
|
||||||
|
if (mc.world == null) {
|
||||||
|
LOGGER.warn("Replay failed to load world (corrupted?), skipping..");
|
||||||
|
IOUtils.closeQuietly(replayFile);
|
||||||
|
processMultipleReplays(container, mod, queue, done);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processQueue(container, replayHandler, next.getValue(), () -> {
|
||||||
|
try {
|
||||||
|
replayHandler.endReplay();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Utils.error(LOGGER, container, CrashReport.create(e, "Closing replay"), () -> {});
|
||||||
|
container.display(); // Re-show the queue popup and the new error popup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processMultipleReplays(container, mod, queue, done);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private GuiRenderSettings addButtonClicked() {
|
||||||
|
Timeline timeline = timelineSupplier.get();
|
||||||
|
if (timeline != null) {
|
||||||
|
GuiRenderSettings popup = addJob(timeline);
|
||||||
|
popup.open();
|
||||||
|
return popup;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GuiRenderSettings addJob(Timeline timeline) {
|
||||||
|
return new GuiRenderSettings(container, replayHandler, timeline) {
|
||||||
|
{
|
||||||
|
if (!jobs.isEmpty()) {
|
||||||
|
buttonPanel.removeElement(renderButton);
|
||||||
|
}
|
||||||
|
queueButton.onClick(() -> {
|
||||||
|
RenderSettings settings = save(false);
|
||||||
|
|
||||||
|
RenderJob newJob = new RenderJob();
|
||||||
|
newJob.setSettings(settings);
|
||||||
|
newJob.setTimeline(timeline);
|
||||||
|
LOGGER.trace("Adding new job: {}", newJob);
|
||||||
|
jobs.add(newJob);
|
||||||
|
list.getListPanel().addElements(null, new Entry(newJob));
|
||||||
|
updateButtons();
|
||||||
|
mod.saveRenderQueue();
|
||||||
|
|
||||||
|
// Need to close the inner popup before we can open the outer one
|
||||||
|
close();
|
||||||
|
if (!opened) {
|
||||||
|
GuiRenderQueue.this.open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
if (!opened && jobs.isEmpty()) {
|
||||||
|
GuiRenderQueue.this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void open() {
|
public void open() {
|
||||||
|
if (jobs.isEmpty() && timelineSupplier != null) {
|
||||||
|
if (addButtonClicked() == null) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
super.open();
|
super.open();
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void close() {
|
||||||
|
if (opened) {
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
opened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -256,9 +333,35 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateButtons() {
|
public void updateButtons() {
|
||||||
renameButton.setEnabled(selectedEntry != null);
|
int selected = selectedEntries.size();
|
||||||
removeButton.setEnabled(selectedEntry != null);
|
addButton.setEnabled(timelineSupplier != null);
|
||||||
renderButton.setEnabled(!list.getListPanel().getChildren().isEmpty());
|
editButton.setEnabled(selected == 1);
|
||||||
|
removeButton.setEnabled(selected >= 1);
|
||||||
|
renderButton.setEnabled(selected > 0);
|
||||||
|
renderButton.setI18nLabel("replaymod.gui.renderqueue.render" + (selected > 0 ? "selected" : "all"));
|
||||||
|
|
||||||
|
String[] compatError = VideoRenderer.checkCompat();
|
||||||
|
if (compatError != null) {
|
||||||
|
renderButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
|
||||||
|
if (MCVer.Keyboard.hasControlDown() && keyCode == MCVer.Keyboard.KEY_A) {
|
||||||
|
if (selectedEntries.size() < list.getListPanel().getChildren().size()) {
|
||||||
|
for (GuiElement<?> child : list.getListPanel().getChildren()) {
|
||||||
|
if (child instanceof Entry) {
|
||||||
|
selectedEntries.add((Entry) child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedEntries.clear();
|
||||||
|
}
|
||||||
|
updateButtons();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Entry extends AbstractGuiClickableContainer<Entry> {
|
public class Entry extends AbstractGuiClickableContainer<Entry> {
|
||||||
@@ -284,27 +387,42 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onClick() {
|
protected void onClick() {
|
||||||
selectedEntry = this;
|
if (!MCVer.Keyboard.hasControlDown()) {
|
||||||
|
selectedEntries.clear();
|
||||||
|
}
|
||||||
|
if (selectedEntries.contains(this)) {
|
||||||
|
selectedEntries.remove(this);
|
||||||
|
} else {
|
||||||
|
selectedEntries.add(this);
|
||||||
|
}
|
||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||||
if (selectedEntry == this) {
|
if (selectedEntries.contains(this)) {
|
||||||
renderer.drawRect(0, 0, size.getWidth(), size.getHeight(), Colors.BLACK);
|
renderer.drawRect(0, 0, size.getWidth(), size.getHeight(), Colors.BLACK);
|
||||||
renderer.drawRect(0, 0, 2, size.getHeight(), Colors.WHITE);
|
renderer.drawRect(0, 0, 2, size.getHeight(), Colors.WHITE);
|
||||||
}
|
}
|
||||||
super.draw(renderer, size, renderInfo);
|
super.draw(renderer, size, renderInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
job.setName(name);
|
|
||||||
label.setText(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Entry getThis() {
|
protected Entry getThis() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GuiRenderSettings edit() {
|
||||||
|
GuiRenderSettings gui = new GuiRenderSettings(container, replayHandler, job.getTimeline());
|
||||||
|
gui.buttonPanel.removeElement(gui.renderButton);
|
||||||
|
gui.queueButton.setI18nLabel("replaymod.gui.done").onClick(() -> {
|
||||||
|
job.setSettings(gui.save(false));
|
||||||
|
label.setText(job.getName());
|
||||||
|
mod.saveRenderQueue();
|
||||||
|
gui.close();
|
||||||
|
});
|
||||||
|
gui.load(job.getSettings());
|
||||||
|
return gui;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import com.google.gson.JsonSyntaxException;
|
|||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.render.RenderSettings;
|
import com.replaymod.render.RenderSettings;
|
||||||
import com.replaymod.render.ReplayModRender;
|
import com.replaymod.render.ReplayModRender;
|
||||||
import com.replaymod.render.VideoWriter;
|
import com.replaymod.render.FFmpegWriter;
|
||||||
import com.replaymod.render.rendering.VideoRenderer;
|
import com.replaymod.render.rendering.VideoRenderer;
|
||||||
import com.replaymod.replay.ReplayHandler;
|
import com.replaymod.replay.ReplayHandler;
|
||||||
import com.replaymod.replaystudio.pathing.path.Timeline;
|
import com.replaymod.replaystudio.pathing.path.Timeline;
|
||||||
|
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
||||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
@@ -21,11 +22,11 @@ import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
|||||||
import de.johni0702.minecraft.gui.element.*;
|
import de.johni0702.minecraft.gui.element.*;
|
||||||
import de.johni0702.minecraft.gui.element.advanced.GuiColorPicker;
|
import de.johni0702.minecraft.gui.element.advanced.GuiColorPicker;
|
||||||
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
|
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
|
||||||
import de.johni0702.minecraft.gui.function.Closeable;
|
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.GridLayout;
|
import de.johni0702.minecraft.gui.layout.GridLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||||
|
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
||||||
import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup;
|
import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup;
|
||||||
import de.johni0702.minecraft.gui.utils.Colors;
|
import de.johni0702.minecraft.gui.utils.Colors;
|
||||||
import de.johni0702.minecraft.gui.utils.Consumer;
|
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||||
@@ -44,6 +45,7 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@@ -57,8 +59,9 @@ import static com.replaymod.render.ReplayModRender.LOGGER;
|
|||||||
import net.minecraft.text.TranslatableText;
|
import net.minecraft.text.TranslatableText;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
public class GuiRenderSettings extends GuiScreen implements Closeable {
|
public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
|
||||||
public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT);
|
{ disablePopupBackground(); }
|
||||||
|
public final GuiPanel contentPanel = new GuiPanel(popup).setBackgroundColor(new Color(0, 0, 0, 230));
|
||||||
public final GuiVerticalList settingsList = new GuiVerticalList(contentPanel).setDrawSlider(true);
|
public final GuiVerticalList settingsList = new GuiVerticalList(contentPanel).setDrawSlider(true);
|
||||||
|
|
||||||
public final GuiDropdownMenu<RenderSettings.RenderMethod> renderMethodDropdown =
|
public final GuiDropdownMenu<RenderSettings.RenderMethod> renderMethodDropdown =
|
||||||
@@ -193,6 +196,12 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
public final GuiCheckbox injectSphericalMetadata = new GuiCheckbox()
|
public final GuiCheckbox injectSphericalMetadata = new GuiCheckbox()
|
||||||
.setI18nLabel("replaymod.gui.rendersettings.sphericalmetadata");
|
.setI18nLabel("replaymod.gui.rendersettings.sphericalmetadata");
|
||||||
|
|
||||||
|
public final GuiCheckbox depthMap = new GuiCheckbox()
|
||||||
|
.setI18nLabel("replaymod.gui.rendersettings.depthmap");
|
||||||
|
|
||||||
|
public final GuiCheckbox cameraPathExport = new GuiCheckbox()
|
||||||
|
.setI18nLabel("replaymod.gui.rendersettings.camerapath");
|
||||||
|
|
||||||
public final GuiDropdownMenu<RenderSettings.AntiAliasing> antiAliasingDropdown = new GuiDropdownMenu<RenderSettings.AntiAliasing>()
|
public final GuiDropdownMenu<RenderSettings.AntiAliasing> antiAliasingDropdown = new GuiDropdownMenu<RenderSettings.AntiAliasing>()
|
||||||
.setSize(200, 20).setValues(RenderSettings.AntiAliasing.values()).setSelected(RenderSettings.AntiAliasing.NONE);
|
.setSize(200, 20).setValues(RenderSettings.AntiAliasing.values()).setSelected(RenderSettings.AntiAliasing.NONE);
|
||||||
|
|
||||||
@@ -203,16 +212,25 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel,
|
new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel,
|
||||||
chromaKeyingCheckbox, chromaKeyingColor,
|
chromaKeyingCheckbox, chromaKeyingColor,
|
||||||
injectSphericalMetadata, sphericalFovSlider,
|
injectSphericalMetadata, sphericalFovSlider,
|
||||||
|
depthMap, new GuiLabel(),
|
||||||
|
cameraPathExport, new GuiLabel(),
|
||||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.antialiasing"), antiAliasingDropdown));
|
new GuiLabel().setI18nText("replaymod.gui.rendersettings.antialiasing"), antiAliasingDropdown));
|
||||||
|
|
||||||
public final GuiTextField exportCommand = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.command")
|
public final GuiTextField exportCommand = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.command")
|
||||||
.setSize(55, 20).setMaxLength(100);
|
.setSize(55, 20).setMaxLength(100).onTextChanged((old) -> updateInputs());
|
||||||
public final GuiTextField exportArguments = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.arguments")
|
public final GuiTextField exportArguments = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.arguments")
|
||||||
.setMinSize(new Dimension(245, 20)).setMaxLength(500);
|
.setMinSize(new Dimension(245, 20)).setMaxLength(500).onTextChanged((old) -> updateInputs());
|
||||||
|
public final GuiButton exportReset = new GuiButton().setLabel("X")
|
||||||
|
.setSize(20, 20)
|
||||||
|
.onClick(() -> {
|
||||||
|
exportCommand.setText("");
|
||||||
|
exportArguments.setText(encodingPresetDropdown.getSelectedValue().getValue());
|
||||||
|
updateInputs();
|
||||||
|
});
|
||||||
|
|
||||||
public final GuiPanel commandlinePanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10))
|
public final GuiPanel commandlinePanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10))
|
||||||
.addElements(null,
|
.addElements(null,
|
||||||
new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)).addElements(null, exportCommand, exportArguments),
|
new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)).addElements(null, exportCommand, exportArguments, exportReset),
|
||||||
new GuiLabel(new GuiPanel().setLayout(new CustomLayout<GuiPanel>() {
|
new GuiLabel(new GuiPanel().setLayout(new CustomLayout<GuiPanel>() {
|
||||||
@Override
|
@Override
|
||||||
protected void layout(GuiPanel container, int width, int height) {
|
protected void layout(GuiPanel container, int width, int height) {
|
||||||
@@ -227,12 +245,9 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
|
|
||||||
|
|
||||||
public final GuiPanel buttonPanel = new GuiPanel(contentPanel).setLayout(new HorizontalLayout().setSpacing(4));
|
public final GuiPanel buttonPanel = new GuiPanel(contentPanel).setLayout(new HorizontalLayout().setSpacing(4));
|
||||||
public final GuiButton queueButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
public final GuiButton queueButton = new GuiButton(buttonPanel)
|
||||||
@Override
|
.setSize(100, 20)
|
||||||
public void run() {
|
.setI18nLabel("replaymod.gui.rendersettings.addtoqueue");
|
||||||
new GuiRenderQueue(GuiRenderSettings.this, GuiRenderSettings.this, replayHandler, timeline).open();
|
|
||||||
}
|
|
||||||
}).setSize(100, 20).setI18nLabel("replaymod.gui.renderqueue.open");
|
|
||||||
public final GuiButton renderButton = new GuiButton(buttonPanel).onClick(() -> ReplayMod.instance.runLaterWithoutLock(new Runnable() {
|
public final GuiButton renderButton = new GuiButton(buttonPanel).onClick(() -> ReplayMod.instance.runLaterWithoutLock(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -241,11 +256,11 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
try {
|
try {
|
||||||
VideoRenderer videoRenderer = new VideoRenderer(save(false), replayHandler, timeline);
|
VideoRenderer videoRenderer = new VideoRenderer(save(false), replayHandler, timeline);
|
||||||
videoRenderer.renderVideo();
|
videoRenderer.renderVideo();
|
||||||
} catch (VideoWriter.NoFFmpegException e) {
|
} catch (FFmpegWriter.NoFFmpegException e) {
|
||||||
LOGGER.error("Rendering video:", e);
|
LOGGER.error("Rendering video:", e);
|
||||||
NoticeScreen errorScreen = new NoticeScreen(
|
NoticeScreen errorScreen = new NoticeScreen(
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
GuiRenderSettings.this::display,
|
getScreen()::display,
|
||||||
new TranslatableText("replaymod.gui.rendering.error.title"),
|
new TranslatableText("replaymod.gui.rendering.error.title"),
|
||||||
new TranslatableText("replaymod.gui.rendering.error.message")
|
new TranslatableText("replaymod.gui.rendering.error.message")
|
||||||
//#else
|
//#else
|
||||||
@@ -254,7 +269,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
//#endif
|
//#endif
|
||||||
);
|
);
|
||||||
getMinecraft().openScreen(errorScreen);
|
getMinecraft().openScreen(errorScreen);
|
||||||
} catch (VideoWriter.FFmpegStartupException e) {
|
} catch (FFmpegWriter.FFmpegStartupException e) {
|
||||||
GuiExportFailed.tryToRecover(e, newSettings -> {
|
GuiExportFailed.tryToRecover(e, newSettings -> {
|
||||||
// Update settings with fixed ffmpeg arguments
|
// Update settings with fixed ffmpeg arguments
|
||||||
exportArguments.setText(newSettings.getExportArguments());
|
exportArguments.setText(newSettings.getExportArguments());
|
||||||
@@ -263,26 +278,17 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
});
|
});
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
error(LOGGER, GuiRenderSettings.this, CrashReport.create(t, "Rendering video"), () -> {});
|
error(LOGGER, GuiRenderSettings.this, CrashReport.create(t, "Rendering video"), () -> {});
|
||||||
display(); // Re-show the render settings gui and the new error popup
|
getScreen().display(); // Re-show the render settings gui and the new error popup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})).setSize(100, 20).setI18nLabel("replaymod.gui.render");
|
})).setSize(100, 20).setI18nLabel("replaymod.gui.render");
|
||||||
public final GuiButton cancelButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
public final GuiButton cancelButton = new GuiButton(buttonPanel)
|
||||||
@Override
|
.onClick(this::close)
|
||||||
public void run() {
|
.setSize(100, 20)
|
||||||
getMinecraft().openScreen(null);
|
.setI18nLabel("replaymod.gui.cancel");
|
||||||
}
|
|
||||||
}).setSize(100, 20).setI18nLabel("replaymod.gui.cancel");
|
|
||||||
|
|
||||||
{
|
{
|
||||||
setBackground(Background.NONE);
|
|
||||||
Utils.link(videoWidth, videoHeight, bitRateField);
|
Utils.link(videoWidth, videoHeight, bitRateField);
|
||||||
setLayout(new CustomLayout<GuiScreen>() {
|
|
||||||
@Override
|
|
||||||
protected void layout(GuiScreen container, int width, int height) {
|
|
||||||
pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
contentPanel.setLayout(new CustomLayout<GuiPanel>() {
|
contentPanel.setLayout(new CustomLayout<GuiPanel>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -294,7 +300,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
||||||
ReadableDimension screenSize = getMinSize();
|
ReadableDimension screenSize = getContainer().getMinSize();
|
||||||
return new Dimension(screenSize.getWidth() - 40, screenSize.getHeight() - 40);
|
return new Dimension(screenSize.getWidth() - 40, screenSize.getHeight() - 40);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -319,12 +325,15 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final AbstractGuiScreen<?> screen;
|
||||||
private final ReplayHandler replayHandler;
|
private final ReplayHandler replayHandler;
|
||||||
private final Timeline timeline;
|
private final Timeline timeline;
|
||||||
private File outputFile;
|
private File outputFile;
|
||||||
private boolean userDefinedOutputFileName;
|
private boolean userDefinedOutputFileName;
|
||||||
|
|
||||||
public GuiRenderSettings(ReplayHandler replayHandler, Timeline timeline) {
|
public GuiRenderSettings(AbstractGuiScreen<?> container, ReplayHandler replayHandler, Timeline timeline) {
|
||||||
|
super(container);
|
||||||
|
this.screen = container;
|
||||||
this.replayHandler = replayHandler;
|
this.replayHandler = replayHandler;
|
||||||
this.timeline = timeline;
|
this.timeline = timeline;
|
||||||
|
|
||||||
@@ -358,17 +367,26 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
videoWidth.setEnabled(!renderMethod.hasFixedAspectRatio());
|
videoWidth.setEnabled(!renderMethod.hasFixedAspectRatio());
|
||||||
|
|
||||||
// Validate video width and height
|
// Validate video width and height
|
||||||
String error = updateResolution();
|
String resolutionError = updateResolution();
|
||||||
if (error == null) {
|
if (resolutionError == null) {
|
||||||
renderButton.setEnabled().setTooltip(null);
|
queueButton.setEnabled().setTooltip(null);
|
||||||
videoWidth.setTextColor(Colors.WHITE);
|
videoWidth.setTextColor(Colors.WHITE);
|
||||||
videoHeight.setTextColor(Colors.WHITE);
|
videoHeight.setTextColor(Colors.WHITE);
|
||||||
} else {
|
} else {
|
||||||
renderButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(error));
|
queueButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(resolutionError));
|
||||||
videoWidth.setTextColor(Colors.RED);
|
videoWidth.setTextColor(Colors.RED);
|
||||||
videoHeight.setTextColor(Colors.RED);
|
videoHeight.setTextColor(Colors.RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String[] compatError = VideoRenderer.checkCompat();
|
||||||
|
if (resolutionError != null) {
|
||||||
|
renderButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(resolutionError));
|
||||||
|
} else if (compatError != null) {
|
||||||
|
renderButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
|
||||||
|
} else {
|
||||||
|
renderButton.setEnabled().setTooltip(null);
|
||||||
|
}
|
||||||
|
|
||||||
// Enable/Disable bitrate input field and dropdown
|
// Enable/Disable bitrate input field and dropdown
|
||||||
if (encodingPresetDropdown.getSelectedValue().hasBitrateSetting()) {
|
if (encodingPresetDropdown.getSelectedValue().hasBitrateSetting()) {
|
||||||
bitRateField.setEnabled();
|
bitRateField.setEnabled();
|
||||||
@@ -402,15 +420,29 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable/Disable various options for blend export
|
// Enable/Disable various options for blend export
|
||||||
|
boolean isEXR = encodingPresetDropdown.getSelectedValue() == RenderSettings.EncodingPreset.EXR;
|
||||||
boolean isBlend = renderMethod == RenderSettings.RenderMethod.BLEND;
|
boolean isBlend = renderMethod == RenderSettings.RenderMethod.BLEND;
|
||||||
|
boolean isFFmpeg = !isBlend && !isEXR;
|
||||||
if (isBlend) {
|
if (isBlend) {
|
||||||
videoWidth.setDisabled();
|
videoWidth.setDisabled();
|
||||||
videoHeight.setDisabled();
|
videoHeight.setDisabled();
|
||||||
}
|
}
|
||||||
encodingPresetDropdown.setEnabled(!isBlend);
|
encodingPresetDropdown.setEnabled(!isBlend);
|
||||||
exportCommand.setEnabled(!isBlend);
|
exportCommand.setEnabled(isFFmpeg);
|
||||||
exportArguments.setEnabled(!isBlend);
|
exportArguments.setEnabled(isFFmpeg);
|
||||||
antiAliasingDropdown.setEnabled(!isBlend);
|
antiAliasingDropdown.setEnabled(isFFmpeg);
|
||||||
|
|
||||||
|
if (isEXR) {
|
||||||
|
depthMap.setEnabled().setTooltip(null);
|
||||||
|
} else {
|
||||||
|
depthMap.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED)
|
||||||
|
.setI18nText("replaymod.gui.rendersettings.depthmap.onlyexr"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/Disable export args reset button
|
||||||
|
boolean commandChanged = !exportCommand.getText().isEmpty();
|
||||||
|
boolean argsChanged = !encodingPresetDropdown.getSelectedValue().getValue().equals(exportArguments.getText());
|
||||||
|
exportReset.setEnabled(commandChanged || argsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String updateResolution() {
|
protected String updateResolution() {
|
||||||
@@ -511,7 +543,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
userDefinedOutputFileName = false;
|
userDefinedOutputFileName = false;
|
||||||
} else if (savedOutputFile.exists()) {
|
} else if (savedOutputFile.exists()) {
|
||||||
String name = generateOutputFile(encodingPreset).getName();
|
String name = generateOutputFile(encodingPreset).getName();
|
||||||
this.outputFile = new File(savedOutputFile.isDirectory() ? savedOutputFile : savedOutputFile.getParentFile(), name);
|
boolean isFolder = savedOutputFile.isDirectory() && !savedOutputFile.getName().endsWith(".exr");
|
||||||
|
this.outputFile = new File(isFolder ? savedOutputFile : savedOutputFile.getParentFile(), name);
|
||||||
userDefinedOutputFileName = false;
|
userDefinedOutputFileName = false;
|
||||||
} else {
|
} else {
|
||||||
this.outputFile = conformExtension(savedOutputFile, encodingPreset);
|
this.outputFile = conformExtension(savedOutputFile, encodingPreset);
|
||||||
@@ -531,6 +564,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
}
|
}
|
||||||
sphericalFovSlider.setValue((settings.getSphericalFovX() - MIN_SPHERICAL_FOV) / SPHERICAL_FOV_STEP_SIZE);
|
sphericalFovSlider.setValue((settings.getSphericalFovX() - MIN_SPHERICAL_FOV) / SPHERICAL_FOV_STEP_SIZE);
|
||||||
injectSphericalMetadata.setChecked(settings.isInjectSphericalMetadata());
|
injectSphericalMetadata.setChecked(settings.isInjectSphericalMetadata());
|
||||||
|
depthMap.setChecked(settings.isDepthMap());
|
||||||
|
cameraPathExport.setChecked(settings.isCameraPathExport());
|
||||||
antiAliasingDropdown.setSelected(settings.getAntiAliasing());
|
antiAliasingDropdown.setSelected(settings.getAntiAliasing());
|
||||||
exportCommand.setText(settings.getExportCommand());
|
exportCommand.setText(settings.getExportCommand());
|
||||||
String exportArguments = settings.getExportArguments();
|
String exportArguments = settings.getExportArguments();
|
||||||
@@ -561,7 +596,9 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
chromaKeyingCheckbox.isChecked() ? chromaKeyingColor.getColor() : null,
|
chromaKeyingCheckbox.isChecked() ? chromaKeyingColor.getColor() : null,
|
||||||
sphericalFov, Math.min(180, sphericalFov),
|
sphericalFov, Math.min(180, sphericalFov),
|
||||||
injectSphericalMetadata.isChecked() && (serialize || injectSphericalMetadata.isEnabled()),
|
injectSphericalMetadata.isChecked() && (serialize || injectSphericalMetadata.isEnabled()),
|
||||||
antiAliasingDropdown.getSelectedValue(),
|
depthMap.isChecked() && (serialize || depthMap.isEnabled()),
|
||||||
|
cameraPathExport.isChecked(),
|
||||||
|
serialize || antiAliasingDropdown.isEnabled() ? antiAliasingDropdown.getSelectedValue() : RenderSettings.AntiAliasing.NONE,
|
||||||
exportCommand.getText(),
|
exportCommand.getText(),
|
||||||
exportArguments.getText(),
|
exportArguments.getText(),
|
||||||
net.minecraft.client.gui.screen.Screen.hasControlDown()
|
net.minecraft.client.gui.screen.Screen.hasControlDown()
|
||||||
@@ -574,6 +611,24 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
return new File(folder, fileName + "." + encodingPreset.getFileExtension());
|
return new File(folder, fileName + "." + encodingPreset.getFileExtension());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AbstractGuiScreen<?> getScreen() {
|
||||||
|
return screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputFileBaseName(String base) {
|
||||||
|
RenderSettings.EncodingPreset preset = encodingPresetDropdown.getSelectedValue();
|
||||||
|
File file = new File(outputFile.getParentFile(), base + "." + preset.getFileExtension());
|
||||||
|
// Ensure the file name is valid
|
||||||
|
try {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
file.toPath();
|
||||||
|
outputFile = file;
|
||||||
|
outputFileButton.setLabel(file.getName());
|
||||||
|
} catch (InvalidPathException ignored) {
|
||||||
|
setOutputFileBaseName("filename_invalid_" + base.hashCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected File conformExtension(File file, RenderSettings.EncodingPreset preset) {
|
protected File conformExtension(File file, RenderSettings.EncodingPreset preset) {
|
||||||
String name = file.getName();
|
String name = file.getName();
|
||||||
if (name.contains(".")) {
|
if (name.contains(".")) {
|
||||||
@@ -588,7 +643,12 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
|
|
||||||
private RenderSettings getDefaultRenderSettings() {
|
private RenderSettings getDefaultRenderSettings() {
|
||||||
return new RenderSettings(RenderSettings.RenderMethod.DEFAULT, RenderSettings.EncodingPreset.MP4_DEFAULT, 1920, 1080, 60, 10 << 20, null,
|
return new RenderSettings(RenderSettings.RenderMethod.DEFAULT, RenderSettings.EncodingPreset.MP4_DEFAULT, 1920, 1080, 60, 10 << 20, null,
|
||||||
true, false, false, false, null, 360, 180, false, RenderSettings.AntiAliasing.NONE, "", RenderSettings.EncodingPreset.MP4_DEFAULT.getValue(), false);
|
true, false, false, false, null, 360, 180, false, false, false, RenderSettings.AntiAliasing.NONE, "", RenderSettings.EncodingPreset.MP4_DEFAULT.getValue(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open() {
|
||||||
|
super.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -600,9 +660,21 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error("Saving render settings:", e);
|
LOGGER.error("Saving render settings:", e);
|
||||||
}
|
}
|
||||||
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReplayHandler getReplayHandler() {
|
public ReplayHandler getReplayHandler() {
|
||||||
return replayHandler;
|
return replayHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GuiRenderSettings getThis() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GuiScreen createBaseScreen() {
|
||||||
|
GuiScreen screen = new GuiScreen();
|
||||||
|
screen.setBackground(AbstractGuiScreen.Background.NONE);
|
||||||
|
return screen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.replaymod.render.gui;
|
package com.replaymod.render.gui;
|
||||||
|
|
||||||
import com.replaymod.core.utils.Utils;
|
import com.replaymod.core.utils.Utils;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
import com.replaymod.render.rendering.VideoRenderer;
|
import com.replaymod.render.rendering.VideoRenderer;
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.RenderInfo;
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
@@ -265,9 +265,8 @@ public class GuiVideoRenderer extends GuiScreen implements Tickable {
|
|||||||
guiRenderer.drawTexturedRect(x, y, 0, 0, width, height, videoWidth, videoHeight, videoWidth, videoHeight);
|
guiRenderer.drawTexturedRect(x, y, 0, 0, width, height, videoWidth, videoHeight, videoWidth, videoHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updatePreview(RGBFrame frame) {
|
public void updatePreview(ByteBuffer buffer, ReadableDimension size) {
|
||||||
if (previewCheckbox.isChecked() && previewTexture != null) {
|
if (previewCheckbox.isChecked() && previewTexture != null) {
|
||||||
ByteBuffer buffer = frame.getByteBuffer();
|
|
||||||
buffer.mark();
|
buffer.mark();
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
@@ -279,7 +278,6 @@ public class GuiVideoRenderer extends GuiScreen implements Tickable {
|
|||||||
// Note: Optifine changes the texture data array to be three times as long (for use by shaders),
|
// Note: Optifine changes the texture data array to be three times as long (for use by shaders),
|
||||||
// we only want to initialize the first third and since we use our frame size, not the array size,
|
// we only want to initialize the first third and since we use our frame size, not the array size,
|
||||||
// we're good to go.
|
// we're good to go.
|
||||||
ReadableDimension size = frame.getSize();
|
|
||||||
int width = size.getWidth();
|
int width = size.getWidth();
|
||||||
for (int y = 0; y < size.getHeight(); y++) {
|
for (int y = 0; y < size.getHeight(); y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
|
|||||||
@@ -63,15 +63,6 @@ public class ChunkLoadingRenderGlobal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void install() {
|
private void install() {
|
||||||
//#if FABRIC>=1
|
|
||||||
if (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("sodium")) {
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Rendering is not currently supported while Sodium is installed.\n" +
|
|
||||||
"See https://github.com/ReplayMod/ReplayMod/issues/150\n" +
|
|
||||||
"For now, you need to uninstall Sodium before rendering!"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
//#endif
|
|
||||||
try {
|
try {
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
Field hookField = WorldRenderer.class.getField("replayModRender_hook");
|
Field hookField = WorldRenderer.class.getField("replayModRender_hook");
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.replaymod.render.mixin;
|
||||||
|
|
||||||
|
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||||
|
import net.minecraft.client.render.GameRenderer;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
|
||||||
|
@Mixin(GameRenderer.class)
|
||||||
|
public abstract class Mixin_PreserveDepthDuringHandRendering {
|
||||||
|
@ModifyArg(
|
||||||
|
method = "renderWorld",
|
||||||
|
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V"),
|
||||||
|
index = 0
|
||||||
|
)
|
||||||
|
private int replayModRender_skipClearWhenRecordingDepth(int mask) {
|
||||||
|
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) this).replayModRender_getHandler();
|
||||||
|
if (handler != null && handler.getSettings().isDepthMap()) {
|
||||||
|
mask = mask & ~GL11.GL_DEPTH_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.replaymod.render.processor;
|
||||||
|
|
||||||
|
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||||
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static com.replaymod.render.utils.Utils.openGlBytesToBitmap;
|
||||||
|
|
||||||
|
public class CubicToBitmapProcessor extends AbstractFrameProcessor<CubicOpenGlFrame, BitmapFrame> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BitmapFrame process(CubicOpenGlFrame rawFrame) {
|
||||||
|
int size = rawFrame.getLeft().getSize().getWidth();
|
||||||
|
int bpp = rawFrame.getLeft().getBytesPerPixel();
|
||||||
|
int width = size * 4;
|
||||||
|
int height = size * 3;
|
||||||
|
ByteBuffer result = ByteBufferPool.allocate(width * height * bpp);
|
||||||
|
openGlBytesToBitmap(rawFrame.getLeft(), 0, size, result, width);
|
||||||
|
openGlBytesToBitmap(rawFrame.getFront(), size, size, result, width);
|
||||||
|
openGlBytesToBitmap(rawFrame.getRight(), size * 2, size, result, width);
|
||||||
|
openGlBytesToBitmap(rawFrame.getBack(), size * 3, size, result, width);
|
||||||
|
openGlBytesToBitmap(rawFrame.getTop(), size, 0, result, width);
|
||||||
|
openGlBytesToBitmap(rawFrame.getBottom(), size, size * 2, result, width);
|
||||||
|
ByteBufferPool.release(rawFrame.getLeft().getByteBuffer());
|
||||||
|
ByteBufferPool.release(rawFrame.getRight().getByteBuffer());
|
||||||
|
ByteBufferPool.release(rawFrame.getFront().getByteBuffer());
|
||||||
|
ByteBufferPool.release(rawFrame.getBack().getByteBuffer());
|
||||||
|
ByteBufferPool.release(rawFrame.getTop().getByteBuffer());
|
||||||
|
ByteBufferPool.release(rawFrame.getBottom().getByteBuffer());
|
||||||
|
return new BitmapFrame(rawFrame.getFrameId(), new Dimension(width, height), bpp, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.replaymod.render.processor;
|
|
||||||
|
|
||||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static com.replaymod.render.utils.Utils.openGlBytesToRBG;
|
|
||||||
|
|
||||||
public class CubicToRGBProcessor extends AbstractFrameProcessor<CubicOpenGlFrame, RGBFrame> {
|
|
||||||
@Override
|
|
||||||
public RGBFrame process(CubicOpenGlFrame rawFrame) {
|
|
||||||
int size = rawFrame.getLeft().getSize().getWidth();
|
|
||||||
int width = size * 4;
|
|
||||||
int height = size * 3;
|
|
||||||
ByteBuffer result = ByteBufferPool.allocate(width * height * 4);
|
|
||||||
openGlBytesToRBG(rawFrame.getLeft().getByteBuffer(), size, 0, size, result, width);
|
|
||||||
openGlBytesToRBG(rawFrame.getFront().getByteBuffer(), size, size, size, result, width);
|
|
||||||
openGlBytesToRBG(rawFrame.getRight().getByteBuffer(), size, size * 2, size, result, width);
|
|
||||||
openGlBytesToRBG(rawFrame.getBack().getByteBuffer(), size, size * 3, size, result, width);
|
|
||||||
openGlBytesToRBG(rawFrame.getTop().getByteBuffer(), size, size, 0, result, width);
|
|
||||||
openGlBytesToRBG(rawFrame.getBottom().getByteBuffer(), size, size, size * 2, result, width);
|
|
||||||
ByteBufferPool.release(rawFrame.getLeft().getByteBuffer());
|
|
||||||
ByteBufferPool.release(rawFrame.getRight().getByteBuffer());
|
|
||||||
ByteBufferPool.release(rawFrame.getFront().getByteBuffer());
|
|
||||||
ByteBufferPool.release(rawFrame.getBack().getByteBuffer());
|
|
||||||
ByteBufferPool.release(rawFrame.getTop().getByteBuffer());
|
|
||||||
ByteBufferPool.release(rawFrame.getBottom().getByteBuffer());
|
|
||||||
return new RGBFrame(rawFrame.getFrameId(), new Dimension(width, height), result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.replaymod.render.processor;
|
package com.replaymod.render.processor;
|
||||||
|
|
||||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
@@ -10,7 +10,7 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
import static java.lang.Math.PI;
|
import static java.lang.Math.PI;
|
||||||
|
|
||||||
public class EquirectangularToRGBProcessor extends AbstractFrameProcessor<CubicOpenGlFrame, RGBFrame> {
|
public class EquirectangularToBitmapProcessor extends AbstractFrameProcessor<CubicOpenGlFrame, BitmapFrame> {
|
||||||
private static final byte IMAGE_BACK = 0;
|
private static final byte IMAGE_BACK = 0;
|
||||||
private static final byte IMAGE_FRONT = 1;
|
private static final byte IMAGE_FRONT = 1;
|
||||||
private static final byte IMAGE_LEFT = 2;
|
private static final byte IMAGE_LEFT = 2;
|
||||||
@@ -26,7 +26,7 @@ public class EquirectangularToRGBProcessor extends AbstractFrameProcessor<CubicO
|
|||||||
private final int[][] imageX;
|
private final int[][] imageX;
|
||||||
private final int[][] imageY;
|
private final int[][] imageY;
|
||||||
|
|
||||||
public EquirectangularToRGBProcessor(int outputWidth, int outputHeight, int sphericalFovX) {
|
public EquirectangularToBitmapProcessor(int outputWidth, int outputHeight, int sphericalFovX) {
|
||||||
// calculate the dimensions of the original equirectangular projection
|
// calculate the dimensions of the original equirectangular projection
|
||||||
// (before cropping according to FOV)
|
// (before cropping according to FOV)
|
||||||
width = outputWidth;
|
width = outputWidth;
|
||||||
@@ -103,16 +103,17 @@ public class EquirectangularToRGBProcessor extends AbstractFrameProcessor<CubicO
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RGBFrame process(CubicOpenGlFrame rawFrame) {
|
public BitmapFrame process(CubicOpenGlFrame rawFrame) {
|
||||||
Validate.isTrue(rawFrame.getLeft().getSize().getWidth() == frameSize, "Frame size must be %d but was %d",
|
Validate.isTrue(rawFrame.getLeft().getSize().getWidth() == frameSize, "Frame size must be %d but was %d",
|
||||||
frameSize, rawFrame.getLeft().getSize().getWidth());
|
frameSize, rawFrame.getLeft().getSize().getWidth());
|
||||||
ByteBuffer result = ByteBufferPool.allocate(width * height * 4);
|
int bpp = rawFrame.getLeft().getBytesPerPixel();
|
||||||
|
ByteBuffer result = ByteBufferPool.allocate(width * height * bpp);
|
||||||
ByteBuffer[] images = {
|
ByteBuffer[] images = {
|
||||||
rawFrame.getBack().getByteBuffer(), rawFrame.getFront().getByteBuffer(),
|
rawFrame.getBack().getByteBuffer(), rawFrame.getFront().getByteBuffer(),
|
||||||
rawFrame.getLeft().getByteBuffer(), rawFrame.getRight().getByteBuffer(),
|
rawFrame.getLeft().getByteBuffer(), rawFrame.getRight().getByteBuffer(),
|
||||||
rawFrame.getTop().getByteBuffer(), rawFrame.getBottom().getByteBuffer()
|
rawFrame.getTop().getByteBuffer(), rawFrame.getBottom().getByteBuffer()
|
||||||
};
|
};
|
||||||
byte[] pixel = new byte[4];
|
byte[] pixel = new byte[bpp];
|
||||||
byte[] image;
|
byte[] image;
|
||||||
int[] imageX, imageY;
|
int[] imageX, imageY;
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
@@ -121,7 +122,7 @@ public class EquirectangularToRGBProcessor extends AbstractFrameProcessor<CubicO
|
|||||||
imageY = this.imageY[y];
|
imageY = this.imageY[y];
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
ByteBuffer source = images[image[x]];
|
ByteBuffer source = images[image[x]];
|
||||||
source.position((imageX[x] + imageY[x] * frameSize) * 4);
|
source.position((imageX[x] + imageY[x] * frameSize) * bpp);
|
||||||
source.get(pixel);
|
source.get(pixel);
|
||||||
result.put(pixel);
|
result.put(pixel);
|
||||||
}
|
}
|
||||||
@@ -134,7 +135,7 @@ public class EquirectangularToRGBProcessor extends AbstractFrameProcessor<CubicO
|
|||||||
ByteBufferPool.release(rawFrame.getBack().getByteBuffer());
|
ByteBufferPool.release(rawFrame.getBack().getByteBuffer());
|
||||||
ByteBufferPool.release(rawFrame.getTop().getByteBuffer());
|
ByteBufferPool.release(rawFrame.getTop().getByteBuffer());
|
||||||
ByteBufferPool.release(rawFrame.getBottom().getByteBuffer());
|
ByteBufferPool.release(rawFrame.getBottom().getByteBuffer());
|
||||||
return new RGBFrame(rawFrame.getFrameId(), new Dimension(width, height), result);
|
return new BitmapFrame(rawFrame.getFrameId(), new Dimension(width, height), bpp, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFrameSize() {
|
public int getFrameSize() {
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.replaymod.render.processor;
|
||||||
|
|
||||||
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
|
public class GlToAbsoluteDepthProcessor extends AbstractFrameProcessor<BitmapFrame, BitmapFrame> {
|
||||||
|
// absolute depth is 2 * near * far / (far + near - (far - near) * (2 * z - 1))
|
||||||
|
// precomputed: [ a ] [ b ] [ c ]
|
||||||
|
private final float a;
|
||||||
|
private final float b;
|
||||||
|
private final float c;
|
||||||
|
|
||||||
|
public GlToAbsoluteDepthProcessor(float near, float far) {
|
||||||
|
a = 2 * near * far;
|
||||||
|
b = far + near;
|
||||||
|
c = far - near;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BitmapFrame process(BitmapFrame frame) {
|
||||||
|
|
||||||
|
FloatBuffer buffer = frame.getByteBuffer().asFloatBuffer();
|
||||||
|
int len = buffer.remaining();
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
float z = buffer.get(i);
|
||||||
|
z = a / (b - c * (2 * z - 1));
|
||||||
|
buffer.put(i, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.replaymod.render.processor;
|
package com.replaymod.render.processor;
|
||||||
|
|
||||||
import com.replaymod.render.frame.ODSOpenGlFrame;
|
import com.replaymod.render.frame.ODSOpenGlFrame;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
@@ -9,25 +9,26 @@ import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class ODSToRGBProcessor extends AbstractFrameProcessor<ODSOpenGlFrame, RGBFrame> {
|
public class ODSToBitmapProcessor extends AbstractFrameProcessor<ODSOpenGlFrame, BitmapFrame> {
|
||||||
private final EquirectangularToRGBProcessor processor;
|
private final EquirectangularToBitmapProcessor processor;
|
||||||
|
|
||||||
public ODSToRGBProcessor(int outputWidth, int outputHeight, int sphericalFovX) {
|
public ODSToBitmapProcessor(int outputWidth, int outputHeight, int sphericalFovX) {
|
||||||
processor = new EquirectangularToRGBProcessor(outputWidth, outputHeight / 2, sphericalFovX);
|
processor = new EquirectangularToBitmapProcessor(outputWidth, outputHeight / 2, sphericalFovX);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RGBFrame process(ODSOpenGlFrame rawFrame) {
|
public BitmapFrame process(ODSOpenGlFrame rawFrame) {
|
||||||
RGBFrame leftFrame = processor.process(rawFrame.getLeft());
|
BitmapFrame leftFrame = processor.process(rawFrame.getLeft());
|
||||||
RGBFrame rightFrame = processor.process(rawFrame.getRight());
|
BitmapFrame rightFrame = processor.process(rawFrame.getRight());
|
||||||
ReadableDimension size = new Dimension(leftFrame.getSize().getWidth(), leftFrame.getSize().getHeight() * 2);
|
ReadableDimension size = new Dimension(leftFrame.getSize().getWidth(), leftFrame.getSize().getHeight() * 2);
|
||||||
ByteBuffer result = ByteBufferPool.allocate(size.getWidth() * size.getHeight() * 4);
|
int bpp = rawFrame.getLeft().getLeft().getBytesPerPixel();
|
||||||
|
ByteBuffer result = ByteBufferPool.allocate(size.getWidth() * size.getHeight() * bpp);
|
||||||
result.put(leftFrame.getByteBuffer());
|
result.put(leftFrame.getByteBuffer());
|
||||||
result.put(rightFrame.getByteBuffer());
|
result.put(rightFrame.getByteBuffer());
|
||||||
result.rewind();
|
result.rewind();
|
||||||
ByteBufferPool.release(leftFrame.getByteBuffer());
|
ByteBufferPool.release(leftFrame.getByteBuffer());
|
||||||
ByteBufferPool.release(rightFrame.getByteBuffer());
|
ByteBufferPool.release(rightFrame.getByteBuffer());
|
||||||
return new RGBFrame(rawFrame.getFrameId(), size, result);
|
return new BitmapFrame(rawFrame.getFrameId(), size, bpp, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
package com.replaymod.render.processor;
|
package com.replaymod.render.processor;
|
||||||
|
|
||||||
import com.replaymod.render.frame.OpenGlFrame;
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class OpenGlToRGBProcessor extends AbstractFrameProcessor<OpenGlFrame, RGBFrame> {
|
public class OpenGlToBitmapProcessor extends AbstractFrameProcessor<OpenGlFrame, BitmapFrame> {
|
||||||
|
|
||||||
private byte[] row, rowSwap;
|
private byte[] row, rowSwap;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RGBFrame process(OpenGlFrame rawFrame) {
|
public BitmapFrame process(OpenGlFrame rawFrame) {
|
||||||
// Flip whole image in place
|
// Flip whole image in place
|
||||||
|
|
||||||
ReadableDimension size = rawFrame.getSize();
|
ReadableDimension size = rawFrame.getSize();
|
||||||
int rowSize = size.getWidth() * 4;
|
int bpp = rawFrame.getBytesPerPixel();
|
||||||
|
int rowSize = size.getWidth() * bpp;
|
||||||
if (row == null || row.length < rowSize) {
|
if (row == null || row.length < rowSize) {
|
||||||
row = new byte[rowSize];
|
row = new byte[rowSize];
|
||||||
rowSwap = new byte[rowSize];
|
rowSwap = new byte[rowSize];
|
||||||
@@ -37,6 +38,6 @@ public class OpenGlToRGBProcessor extends AbstractFrameProcessor<OpenGlFrame, RG
|
|||||||
buffer.put(rowSwap);
|
buffer.put(rowSwap);
|
||||||
}
|
}
|
||||||
buffer.rewind();
|
buffer.rewind();
|
||||||
return new RGBFrame(rawFrame.getFrameId(), size, buffer);
|
return new BitmapFrame(rawFrame.getFrameId(), size, bpp, buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.replaymod.render.processor;
|
||||||
|
|
||||||
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||||
|
import com.replaymod.render.utils.ByteBufferPool;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static com.replaymod.render.utils.Utils.openGlBytesToBitmap;
|
||||||
|
|
||||||
|
public class StereoscopicToBitmapProcessor extends AbstractFrameProcessor<StereoscopicOpenGlFrame, BitmapFrame> {
|
||||||
|
@Override
|
||||||
|
public BitmapFrame process(StereoscopicOpenGlFrame rawFrame) {
|
||||||
|
ReadableDimension size = rawFrame.getLeft().getSize();
|
||||||
|
int width = size.getWidth();
|
||||||
|
int bpp = rawFrame.getLeft().getBytesPerPixel();
|
||||||
|
ByteBuffer result = ByteBufferPool.allocate(width * 2 * size.getHeight() * bpp);
|
||||||
|
openGlBytesToBitmap(rawFrame.getLeft(), 0, 0, result, width * 2);
|
||||||
|
openGlBytesToBitmap(rawFrame.getRight(), size.getWidth(), 0, result, width * 2);
|
||||||
|
ByteBufferPool.release(rawFrame.getLeft().getByteBuffer());
|
||||||
|
ByteBufferPool.release(rawFrame.getRight().getByteBuffer());
|
||||||
|
return new BitmapFrame(rawFrame.getFrameId(), new Dimension(width * 2, size.getHeight()), bpp, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.replaymod.render.processor;
|
|
||||||
|
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
|
||||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
|
||||||
import com.replaymod.render.utils.ByteBufferPool;
|
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static com.replaymod.render.utils.Utils.openGlBytesToRBG;
|
|
||||||
|
|
||||||
public class StereoscopicToRGBProcessor extends AbstractFrameProcessor<StereoscopicOpenGlFrame, RGBFrame> {
|
|
||||||
@Override
|
|
||||||
public RGBFrame process(StereoscopicOpenGlFrame rawFrame) {
|
|
||||||
ReadableDimension size = rawFrame.getLeft().getSize();
|
|
||||||
int width = size.getWidth();
|
|
||||||
ByteBuffer leftBuffer = rawFrame.getLeft().getByteBuffer();
|
|
||||||
ByteBuffer rightBuffer = rawFrame.getRight().getByteBuffer();
|
|
||||||
ByteBuffer result = ByteBufferPool.allocate(width * 2 * size.getHeight() * 4);
|
|
||||||
openGlBytesToRBG(leftBuffer, width, 0, 0, result, width * 2);
|
|
||||||
openGlBytesToRBG(rightBuffer, width, size.getWidth(), 0, result, width * 2);
|
|
||||||
ByteBufferPool.release(leftBuffer);
|
|
||||||
ByteBufferPool.release(rightBuffer);
|
|
||||||
return new RGBFrame(rawFrame.getFrameId(), new Dimension(width * 2, size.getHeight()), result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.replaymod.render.rendering;
|
||||||
|
|
||||||
|
public enum Channel {
|
||||||
|
BRGA,
|
||||||
|
DEPTH,
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package com.replaymod.render.rendering;
|
package com.replaymod.render.rendering;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public interface FrameCapturer<R extends Frame> extends Closeable {
|
public interface FrameCapturer<R extends Frame> extends Closeable {
|
||||||
|
|
||||||
boolean isDone();
|
boolean isDone();
|
||||||
|
|
||||||
R process();
|
Map<Channel, R> process();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package com.replaymod.render.rendering;
|
package com.replaymod.render.rendering;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public interface FrameConsumer<P extends Frame> extends Closeable {
|
public interface FrameConsumer<P extends Frame> extends Closeable {
|
||||||
|
|
||||||
void consume(P frame);
|
void consume(Map<Channel, P> channels);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.replaymod.render.rendering;
|
|||||||
import com.replaymod.core.mixin.MinecraftAccessor;
|
import com.replaymod.core.mixin.MinecraftAccessor;
|
||||||
import com.replaymod.core.versions.MCVer;
|
import com.replaymod.core.versions.MCVer;
|
||||||
import com.replaymod.render.capturer.WorldRenderer;
|
import com.replaymod.render.capturer.WorldRenderer;
|
||||||
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
|
import com.replaymod.render.processor.GlToAbsoluteDepthProcessor;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.util.crash.CrashReport;
|
import net.minecraft.util.crash.CrashReport;
|
||||||
import net.minecraft.util.crash.CrashException;
|
import net.minecraft.util.crash.CrashException;
|
||||||
@@ -13,11 +15,14 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
//$$ import org.lwjgl.opengl.Display;
|
//$$ import org.lwjgl.opengl.Display;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static com.replaymod.core.versions.MCVer.getMinecraft;
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
import static com.replaymod.core.versions.MCVer.getWindow;
|
import static com.replaymod.core.versions.MCVer.getWindow;
|
||||||
//#endif
|
//#endif
|
||||||
@@ -27,6 +32,7 @@ public class Pipeline<R extends Frame, P extends Frame> implements Runnable {
|
|||||||
private final WorldRenderer worldRenderer;
|
private final WorldRenderer worldRenderer;
|
||||||
private final FrameCapturer<R> capturer;
|
private final FrameCapturer<R> capturer;
|
||||||
private final FrameProcessor<R, P> processor;
|
private final FrameProcessor<R, P> processor;
|
||||||
|
private final GlToAbsoluteDepthProcessor depthProcessor;
|
||||||
private int consumerNextFrame;
|
private int consumerNextFrame;
|
||||||
private final Object consumerLock = new Object();
|
private final Object consumerLock = new Object();
|
||||||
private final FrameConsumer<P> consumer;
|
private final FrameConsumer<P> consumer;
|
||||||
@@ -38,6 +44,10 @@ public class Pipeline<R extends Frame, P extends Frame> implements Runnable {
|
|||||||
this.capturer = capturer;
|
this.capturer = capturer;
|
||||||
this.processor = processor;
|
this.processor = processor;
|
||||||
this.consumer = consumer;
|
this.consumer = consumer;
|
||||||
|
|
||||||
|
float near = 0.05f;
|
||||||
|
float far = getMinecraft().options.viewDistance * 16 * 4;
|
||||||
|
this.depthProcessor = new GlToAbsoluteDepthProcessor(near, far);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,7 +80,7 @@ public class Pipeline<R extends Frame, P extends Frame> implements Runnable {
|
|||||||
processService.shutdown();
|
processService.shutdown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
R rawFrame = capturer.process();
|
Map<Channel, R> rawFrame = capturer.process();
|
||||||
if (rawFrame != null) {
|
if (rawFrame != null) {
|
||||||
processService.submit(new ProcessTask(rawFrame));
|
processService.submit(new ProcessTask(rawFrame));
|
||||||
}
|
}
|
||||||
@@ -99,25 +109,37 @@ public class Pipeline<R extends Frame, P extends Frame> implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ProcessTask implements Runnable {
|
private class ProcessTask implements Runnable {
|
||||||
private final R rawFrame;
|
private final Map<Channel, R> rawChannels;
|
||||||
|
|
||||||
public ProcessTask(R rawFrame) {
|
public ProcessTask(Map<Channel, R> rawChannels) {
|
||||||
this.rawFrame = rawFrame;
|
this.rawChannels = rawChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
P processedFrame = processor.process(rawFrame);
|
Integer frameId = null;
|
||||||
|
Map<Channel, P> processedChannels = new HashMap<>();
|
||||||
|
for (Map.Entry<Channel, R> entry : rawChannels.entrySet()) {
|
||||||
|
P processedFrame = processor.process(entry.getValue());
|
||||||
|
if (entry.getKey() == Channel.DEPTH && processedFrame instanceof BitmapFrame) {
|
||||||
|
depthProcessor.process((BitmapFrame) processedFrame);
|
||||||
|
}
|
||||||
|
processedChannels.put(entry.getKey(), processedFrame);
|
||||||
|
frameId = processedFrame.getFrameId();
|
||||||
|
}
|
||||||
|
if (frameId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
synchronized (consumerLock) {
|
synchronized (consumerLock) {
|
||||||
while (consumerNextFrame != processedFrame.getFrameId()) {
|
while (consumerNextFrame != frameId) {
|
||||||
try {
|
try {
|
||||||
consumerLock.wait();
|
consumerLock.wait();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
consumer.consume(processedFrame);
|
consumer.consume(processedChannels);
|
||||||
consumerNextFrame++;
|
consumerNextFrame++;
|
||||||
consumerLock.notifyAll();
|
consumerLock.notifyAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,21 @@ import com.replaymod.render.capturer.WorldRenderer;
|
|||||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||||
import com.replaymod.render.frame.ODSOpenGlFrame;
|
import com.replaymod.render.frame.ODSOpenGlFrame;
|
||||||
import com.replaymod.render.frame.OpenGlFrame;
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||||
import com.replaymod.render.processor.CubicToRGBProcessor;
|
import com.replaymod.render.processor.CubicToBitmapProcessor;
|
||||||
import com.replaymod.render.processor.DummyProcessor;
|
import com.replaymod.render.processor.DummyProcessor;
|
||||||
import com.replaymod.render.processor.EquirectangularToRGBProcessor;
|
import com.replaymod.render.processor.EquirectangularToBitmapProcessor;
|
||||||
import com.replaymod.render.processor.ODSToRGBProcessor;
|
import com.replaymod.render.processor.ODSToBitmapProcessor;
|
||||||
import com.replaymod.render.processor.OpenGlToRGBProcessor;
|
import com.replaymod.render.processor.OpenGlToBitmapProcessor;
|
||||||
import com.replaymod.render.processor.StereoscopicToRGBProcessor;
|
import com.replaymod.render.processor.StereoscopicToBitmapProcessor;
|
||||||
import com.replaymod.render.utils.PixelBufferObject;
|
import com.replaymod.render.utils.PixelBufferObject;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class Pipelines {
|
public class Pipelines {
|
||||||
public static Pipeline newPipeline(RenderSettings.RenderMethod method, RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
public static Pipeline newPipeline(RenderSettings.RenderMethod method, RenderInfo renderInfo, FrameConsumer<BitmapFrame> consumer) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case DEFAULT:
|
case DEFAULT:
|
||||||
return newDefaultPipeline(renderInfo, consumer);
|
return newDefaultPipeline(renderInfo, consumer);
|
||||||
@@ -44,51 +46,51 @@ public class Pipelines {
|
|||||||
throw new UnsupportedOperationException("Unknown method: " + method);
|
throw new UnsupportedOperationException("Unknown method: " + method);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pipeline<OpenGlFrame, RGBFrame> newDefaultPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
public static Pipeline<OpenGlFrame, BitmapFrame> newDefaultPipeline(RenderInfo renderInfo, FrameConsumer<BitmapFrame> consumer) {
|
||||||
RenderSettings settings = renderInfo.getRenderSettings();
|
RenderSettings settings = renderInfo.getRenderSettings();
|
||||||
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
||||||
FrameCapturer<OpenGlFrame> capturer;
|
FrameCapturer<OpenGlFrame> capturer;
|
||||||
if (PixelBufferObject.SUPPORTED) {
|
if (PixelBufferObject.SUPPORTED || settings.isDepthMap()) {
|
||||||
capturer = new SimplePboOpenGlFrameCapturer(worldRenderer, renderInfo);
|
capturer = new SimplePboOpenGlFrameCapturer(worldRenderer, renderInfo);
|
||||||
} else {
|
} else {
|
||||||
capturer = new SimpleOpenGlFrameCapturer(worldRenderer, renderInfo);
|
capturer = new SimpleOpenGlFrameCapturer(worldRenderer, renderInfo);
|
||||||
}
|
}
|
||||||
return new Pipeline<>(worldRenderer, capturer, new OpenGlToRGBProcessor(), consumer);
|
return new Pipeline<>(worldRenderer, capturer, new OpenGlToBitmapProcessor(), consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pipeline<StereoscopicOpenGlFrame, RGBFrame> newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
public static Pipeline<StereoscopicOpenGlFrame, BitmapFrame> newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer<BitmapFrame> consumer) {
|
||||||
RenderSettings settings = renderInfo.getRenderSettings();
|
RenderSettings settings = renderInfo.getRenderSettings();
|
||||||
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
||||||
FrameCapturer<StereoscopicOpenGlFrame> capturer;
|
FrameCapturer<StereoscopicOpenGlFrame> capturer;
|
||||||
if (PixelBufferObject.SUPPORTED) {
|
if (PixelBufferObject.SUPPORTED || settings.isDepthMap()) {
|
||||||
capturer = new StereoscopicPboOpenGlFrameCapturer(worldRenderer, renderInfo);
|
capturer = new StereoscopicPboOpenGlFrameCapturer(worldRenderer, renderInfo);
|
||||||
} else {
|
} else {
|
||||||
capturer = new StereoscopicOpenGlFrameCapturer(worldRenderer, renderInfo);
|
capturer = new StereoscopicOpenGlFrameCapturer(worldRenderer, renderInfo);
|
||||||
}
|
}
|
||||||
return new Pipeline<>(worldRenderer, capturer, new StereoscopicToRGBProcessor(), consumer);
|
return new Pipeline<>(worldRenderer, capturer, new StereoscopicToBitmapProcessor(), consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pipeline<CubicOpenGlFrame, RGBFrame> newCubicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
public static Pipeline<CubicOpenGlFrame, BitmapFrame> newCubicPipeline(RenderInfo renderInfo, FrameConsumer<BitmapFrame> consumer) {
|
||||||
RenderSettings settings = renderInfo.getRenderSettings();
|
RenderSettings settings = renderInfo.getRenderSettings();
|
||||||
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
||||||
FrameCapturer<CubicOpenGlFrame> capturer;
|
FrameCapturer<CubicOpenGlFrame> capturer;
|
||||||
if (PixelBufferObject.SUPPORTED) {
|
if (PixelBufferObject.SUPPORTED || settings.isDepthMap()) {
|
||||||
capturer = new CubicPboOpenGlFrameCapturer(worldRenderer, renderInfo, settings.getVideoWidth() / 4);
|
capturer = new CubicPboOpenGlFrameCapturer(worldRenderer, renderInfo, settings.getVideoWidth() / 4);
|
||||||
} else {
|
} else {
|
||||||
capturer = new CubicOpenGlFrameCapturer(worldRenderer, renderInfo, settings.getVideoWidth() / 4);
|
capturer = new CubicOpenGlFrameCapturer(worldRenderer, renderInfo, settings.getVideoWidth() / 4);
|
||||||
}
|
}
|
||||||
return new Pipeline<>(worldRenderer, capturer, new CubicToRGBProcessor(), consumer);
|
return new Pipeline<>(worldRenderer, capturer, new CubicToBitmapProcessor(), consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pipeline<CubicOpenGlFrame, RGBFrame> newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
public static Pipeline<CubicOpenGlFrame, BitmapFrame> newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer<BitmapFrame> consumer) {
|
||||||
RenderSettings settings = renderInfo.getRenderSettings();
|
RenderSettings settings = renderInfo.getRenderSettings();
|
||||||
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
||||||
|
|
||||||
EquirectangularToRGBProcessor processor = new EquirectangularToRGBProcessor(settings.getVideoWidth(),
|
EquirectangularToBitmapProcessor processor = new EquirectangularToBitmapProcessor(settings.getVideoWidth(),
|
||||||
settings.getVideoHeight(), settings.getSphericalFovX());
|
settings.getVideoHeight(), settings.getSphericalFovX());
|
||||||
|
|
||||||
FrameCapturer<CubicOpenGlFrame> capturer;
|
FrameCapturer<CubicOpenGlFrame> capturer;
|
||||||
if (PixelBufferObject.SUPPORTED) {
|
if (PixelBufferObject.SUPPORTED || settings.isDepthMap()) {
|
||||||
capturer = new CubicPboOpenGlFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
|
capturer = new CubicPboOpenGlFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
|
||||||
} else {
|
} else {
|
||||||
capturer = new CubicOpenGlFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
|
capturer = new CubicOpenGlFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
|
||||||
@@ -96,11 +98,11 @@ public class Pipelines {
|
|||||||
return new Pipeline<>(worldRenderer, capturer, processor, consumer);
|
return new Pipeline<>(worldRenderer, capturer, processor, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pipeline<ODSOpenGlFrame, RGBFrame> newODSPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
public static Pipeline<ODSOpenGlFrame, BitmapFrame> newODSPipeline(RenderInfo renderInfo, FrameConsumer<BitmapFrame> consumer) {
|
||||||
RenderSettings settings = renderInfo.getRenderSettings();
|
RenderSettings settings = renderInfo.getRenderSettings();
|
||||||
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
||||||
|
|
||||||
ODSToRGBProcessor processor = new ODSToRGBProcessor(settings.getVideoWidth(),
|
ODSToBitmapProcessor processor = new ODSToBitmapProcessor(settings.getVideoWidth(),
|
||||||
settings.getVideoHeight(), settings.getSphericalFovX());
|
settings.getVideoHeight(), settings.getSphericalFovX());
|
||||||
|
|
||||||
FrameCapturer<ODSOpenGlFrame> capturer =
|
FrameCapturer<ODSOpenGlFrame> capturer =
|
||||||
@@ -108,13 +110,13 @@ public class Pipelines {
|
|||||||
return new Pipeline<>(worldRenderer, capturer, processor, consumer);
|
return new Pipeline<>(worldRenderer, capturer, processor, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pipeline<RGBFrame, RGBFrame> newBlendPipeline(RenderInfo renderInfo) {
|
public static Pipeline<BitmapFrame, BitmapFrame> newBlendPipeline(RenderInfo renderInfo) {
|
||||||
RenderSettings settings = renderInfo.getRenderSettings();
|
RenderSettings settings = renderInfo.getRenderSettings();
|
||||||
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
WorldRenderer worldRenderer = new EntityRendererHandler(settings, renderInfo);
|
||||||
FrameCapturer<RGBFrame> capturer = new BlendFrameCapturer(worldRenderer, renderInfo);
|
FrameCapturer<BitmapFrame> capturer = new BlendFrameCapturer(worldRenderer, renderInfo);
|
||||||
FrameConsumer<RGBFrame> consumer = new FrameConsumer<RGBFrame>() {
|
FrameConsumer<BitmapFrame> consumer = new FrameConsumer<BitmapFrame>() {
|
||||||
@Override
|
@Override
|
||||||
public void consume(RGBFrame frame) {
|
public void consume(Map<Channel, BitmapFrame> channels) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import com.replaymod.core.versions.MCVer;
|
|||||||
import com.replaymod.pathing.player.AbstractTimelinePlayer;
|
import com.replaymod.pathing.player.AbstractTimelinePlayer;
|
||||||
import com.replaymod.pathing.player.ReplayTimer;
|
import com.replaymod.pathing.player.ReplayTimer;
|
||||||
import com.replaymod.pathing.properties.TimestampProperty;
|
import com.replaymod.pathing.properties.TimestampProperty;
|
||||||
|
import com.replaymod.render.CameraPathExporter;
|
||||||
import com.replaymod.render.RenderSettings;
|
import com.replaymod.render.RenderSettings;
|
||||||
import com.replaymod.render.ReplayModRender;
|
import com.replaymod.render.ReplayModRender;
|
||||||
import com.replaymod.render.VideoWriter;
|
import com.replaymod.render.FFmpegWriter;
|
||||||
import com.replaymod.render.blend.BlendState;
|
import com.replaymod.render.blend.BlendState;
|
||||||
import com.replaymod.render.capturer.RenderInfo;
|
import com.replaymod.render.capturer.RenderInfo;
|
||||||
import com.replaymod.render.events.ReplayRenderCallback;
|
import com.replaymod.render.events.ReplayRenderCallback;
|
||||||
import com.replaymod.render.frame.RGBFrame;
|
import com.replaymod.render.frame.BitmapFrame;
|
||||||
import com.replaymod.render.gui.GuiRenderingDone;
|
import com.replaymod.render.gui.GuiRenderingDone;
|
||||||
import com.replaymod.render.gui.GuiVideoRenderer;
|
import com.replaymod.render.gui.GuiVideoRenderer;
|
||||||
import com.replaymod.render.metadata.MetadataInjector;
|
import com.replaymod.render.metadata.MetadataInjector;
|
||||||
@@ -43,6 +44,7 @@ import org.lwjgl.opengl.GL11;
|
|||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
|
import com.replaymod.render.EXRWriter;
|
||||||
import com.replaymod.render.mixin.MainWindowAccessor;
|
import com.replaymod.render.mixin.MainWindowAccessor;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
@@ -83,7 +85,8 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
private final ReplayHandler replayHandler;
|
private final ReplayHandler replayHandler;
|
||||||
private final Timeline timeline;
|
private final Timeline timeline;
|
||||||
private final Pipeline renderingPipeline;
|
private final Pipeline renderingPipeline;
|
||||||
private final VideoWriter videoWriter;
|
private final FFmpegWriter ffmpegWriter;
|
||||||
|
private final CameraPathExporter cameraPathExporter;
|
||||||
|
|
||||||
private int fps;
|
private int fps;
|
||||||
private boolean mouseWasGrabbed;
|
private boolean mouseWasGrabbed;
|
||||||
@@ -119,16 +122,41 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
BlendState.setState(new BlendState(settings.getOutputFile()));
|
BlendState.setState(new BlendState(settings.getOutputFile()));
|
||||||
|
|
||||||
this.renderingPipeline = Pipelines.newBlendPipeline(this);
|
this.renderingPipeline = Pipelines.newBlendPipeline(this);
|
||||||
this.videoWriter = null;
|
this.ffmpegWriter = null;
|
||||||
} else {
|
} else {
|
||||||
this.renderingPipeline = Pipelines.newPipeline(settings.getRenderMethod(), this,
|
FrameConsumer<BitmapFrame> frameConsumer;
|
||||||
videoWriter = new VideoWriter(this) {
|
if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.EXR) {
|
||||||
@Override
|
ffmpegWriter = null;
|
||||||
public void consume(RGBFrame frame) {
|
//#if MC>=11400
|
||||||
gui.updatePreview(frame);
|
frameConsumer = new EXRWriter(settings.getOutputFile().toPath());
|
||||||
super.consume(frame);
|
//#else
|
||||||
|
//$$ throw new UnsupportedOperationException("EXR requires LWJGL3");
|
||||||
|
//#endif
|
||||||
|
} else {
|
||||||
|
frameConsumer = ffmpegWriter = new FFmpegWriter(this);
|
||||||
}
|
}
|
||||||
});
|
FrameConsumer<BitmapFrame> previewingFrameConsumer = new FrameConsumer<BitmapFrame>() {
|
||||||
|
@Override
|
||||||
|
public void consume(Map<Channel, BitmapFrame> channels) {
|
||||||
|
BitmapFrame bgra = channels.get(Channel.BRGA);
|
||||||
|
if (bgra != null) {
|
||||||
|
gui.updatePreview(bgra.getByteBuffer(), bgra.getSize());
|
||||||
|
}
|
||||||
|
frameConsumer.consume(channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
frameConsumer.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.renderingPipeline = Pipelines.newPipeline(settings.getRenderMethod(), this, previewingFrameConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.isCameraPathExport()) {
|
||||||
|
this.cameraPathExporter = new CameraPathExporter(settings);
|
||||||
|
} else {
|
||||||
|
this.cameraPathExporter = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +295,10 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
//$$ mc.displayHeight = displayHeightBefore;
|
//$$ mc.displayHeight = displayHeightBefore;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
if (cameraPathExporter != null) {
|
||||||
|
cameraPathExporter.recordFrame(timer.tickDelta);
|
||||||
|
}
|
||||||
|
|
||||||
framesDone++;
|
framesDone++;
|
||||||
return timer.tickDelta;
|
return timer.tickDelta;
|
||||||
}
|
}
|
||||||
@@ -328,6 +360,10 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
|
|
||||||
totalFrames = (int) (duration*fps/1000);
|
totalFrames = (int) (duration*fps/1000);
|
||||||
|
|
||||||
|
if (cameraPathExporter != null) {
|
||||||
|
cameraPathExporter.setup(totalFrames);
|
||||||
|
}
|
||||||
|
|
||||||
updateDisplaySize();
|
updateDisplaySize();
|
||||||
|
|
||||||
//#if MC<=10809
|
//#if MC<=10809
|
||||||
@@ -378,13 +414,21 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
}
|
}
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
if (!hasFailed() && cameraPathExporter != null) {
|
||||||
|
try {
|
||||||
|
cameraPathExporter.finish();
|
||||||
|
} catch (IOException e) {
|
||||||
|
setFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
new SoundHandler().playRenderSuccessSound();
|
new SoundHandler().playRenderSuccessSound();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!hasFailed() && videoWriter != null) {
|
if (!hasFailed() && ffmpegWriter != null) {
|
||||||
new GuiRenderingDone(ReplayModRender.instance, videoWriter.getVideoFile(), totalFrames, settings).display();
|
new GuiRenderingDone(ReplayModRender.instance, ffmpegWriter.getVideoFile(), totalFrames, settings).display();
|
||||||
}
|
}
|
||||||
} catch (VideoWriter.FFmpegStartupException e) {
|
} catch (FFmpegWriter.FFmpegStartupException e) {
|
||||||
setFailure(e);
|
setFailure(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,8 +696,8 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
if (videoWriter != null) {
|
if (ffmpegWriter != null) {
|
||||||
videoWriter.abort();
|
ffmpegWriter.abort();
|
||||||
}
|
}
|
||||||
this.cancelled = true;
|
this.cancelled = true;
|
||||||
renderingPipeline.cancel();
|
renderingPipeline.cancel();
|
||||||
@@ -683,4 +727,17 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
return getVideoTime();
|
return getVideoTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String[] checkCompat() {
|
||||||
|
//#if FABRIC>=1
|
||||||
|
if (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("sodium")) {
|
||||||
|
return new String[] {
|
||||||
|
"Rendering is not currently supported while Sodium is installed.",
|
||||||
|
"See https://github.com/ReplayMod/ReplayMod/issues/150",
|
||||||
|
"For now, you need to uninstall Sodium before rendering!"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
package com.replaymod.render.utils;
|
package com.replaymod.render.utils;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
import com.replaymod.render.RenderSettings;
|
import com.replaymod.render.RenderSettings;
|
||||||
|
import com.replaymod.replaystudio.pathing.PathingRegistry;
|
||||||
import com.replaymod.replaystudio.pathing.path.Timeline;
|
import com.replaymod.replaystudio.pathing.path.Timeline;
|
||||||
|
import com.replaymod.replaystudio.pathing.serialize.TimelineSerialization;
|
||||||
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
|
import com.replaymod.simplepathing.SPTimeline;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class RenderJob {
|
public class RenderJob {
|
||||||
private String name;
|
|
||||||
private Timeline timeline;
|
private Timeline timeline;
|
||||||
private RenderSettings settings;
|
private RenderSettings settings;
|
||||||
|
|
||||||
@@ -14,7 +32,7 @@ public class RenderJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name;
|
return settings.getOutputFile().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Timeline getTimeline() {
|
public Timeline getTimeline() {
|
||||||
@@ -25,10 +43,6 @@ public class RenderJob {
|
|||||||
return this.settings;
|
return this.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimeline(Timeline timeline) {
|
public void setTimeline(Timeline timeline) {
|
||||||
this.timeline = timeline;
|
this.timeline = timeline;
|
||||||
}
|
}
|
||||||
@@ -42,22 +56,76 @@ public class RenderJob {
|
|||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
RenderJob renderJob = (RenderJob) o;
|
RenderJob renderJob = (RenderJob) o;
|
||||||
return name.equals(renderJob.name) &&
|
return timeline.equals(renderJob.timeline) &&
|
||||||
timeline.equals(renderJob.timeline) &&
|
|
||||||
settings.equals(renderJob.settings);
|
settings.equals(renderJob.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(name, timeline, settings);
|
return Objects.hash(timeline, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RenderJob{" +
|
return "RenderJob{" +
|
||||||
"name='" + name + '\'' +
|
"timeline=" + timeline +
|
||||||
", timeline=" + timeline +
|
|
||||||
", settings=" + settings +
|
", settings=" + settings +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<RenderJob> readQueue(ReplayFile replayFile) throws IOException {
|
||||||
|
synchronized (replayFile) {
|
||||||
|
Optional<InputStream> optIn = replayFile.get("renderQueue.json");
|
||||||
|
if (!optIn.isPresent()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
try (InputStream in = optIn.get();
|
||||||
|
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||||
|
return new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Timeline.class, new TimelineTypeAdapter())
|
||||||
|
.create()
|
||||||
|
.fromJson(reader, new TypeToken<List<RenderJob>>(){}.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeQueue(ReplayFile replayFile, List<RenderJob> renderQueue) throws IOException {
|
||||||
|
synchronized (replayFile) {
|
||||||
|
try (OutputStream out = replayFile.write("renderQueue.json");
|
||||||
|
OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
||||||
|
new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Timeline.class, new TimelineTypeAdapter())
|
||||||
|
.create()
|
||||||
|
.toJson(renderQueue, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TimelineTypeAdapter extends TypeAdapter<Timeline> {
|
||||||
|
|
||||||
|
private final TimelineSerialization serialization;
|
||||||
|
|
||||||
|
public TimelineTypeAdapter(TimelineSerialization serialization) {
|
||||||
|
this.serialization = serialization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineTypeAdapter(PathingRegistry registry) {
|
||||||
|
this(new TimelineSerialization(registry, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineTypeAdapter() {
|
||||||
|
// TODO need to somehow get rid of the reliance on simplepathing
|
||||||
|
this(new SPTimeline());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter out, Timeline value) throws IOException {
|
||||||
|
out.value(serialization.serialize(Collections.singletonMap("", value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline read(JsonReader in) throws IOException {
|
||||||
|
return serialization.deserialize(in.nextString()).get("");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,41 @@
|
|||||||
package com.replaymod.render.utils;
|
package com.replaymod.render.utils;
|
||||||
|
|
||||||
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
/**
|
/**
|
||||||
* Copies the rgb image (flipped vertically) to the specified position in the target buffer
|
* Copies the rgb image (flipped vertically) to the specified position in the target buffer
|
||||||
* @param buffer Source image
|
* @param source Source image
|
||||||
* @param bufferWidth Source image width
|
|
||||||
* @param xOffset X offset in target image
|
* @param xOffset X offset in target image
|
||||||
* @param yOffset Y offset in target image
|
* @param yOffset Y offset in target image
|
||||||
* @param to Target image
|
* @param to Target image
|
||||||
* @param width Target image width
|
* @param width Target image width
|
||||||
*/
|
*/
|
||||||
public static void openGlBytesToRBG(ByteBuffer buffer, int bufferWidth, int xOffset, int yOffset, ByteBuffer to, int width) {
|
public static void openGlBytesToBitmap(OpenGlFrame source, int xOffset, int yOffset, ByteBuffer to, int width) {
|
||||||
byte[] rowBuf = new byte[bufferWidth * 4];
|
openGlBytesToBitmap(
|
||||||
|
source.getByteBuffer(), source.getSize().getWidth(), source.getBytesPerPixel(),
|
||||||
|
xOffset, yOffset, to, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the rgb image (flipped vertically) to the specified position in the target buffer
|
||||||
|
* @param buffer Source image
|
||||||
|
* @param bufferWidth Source image width
|
||||||
|
* @param bbp Bytes per pixel
|
||||||
|
* @param xOffset X offset in target image
|
||||||
|
* @param yOffset Y offset in target image
|
||||||
|
* @param to Target image
|
||||||
|
* @param width Target image width
|
||||||
|
*/
|
||||||
|
public static void openGlBytesToBitmap(ByteBuffer buffer, int bufferWidth, int bbp, int xOffset, int yOffset, ByteBuffer to, int width) {
|
||||||
|
byte[] rowBuf = new byte[bufferWidth * bbp];
|
||||||
// Copy image flipped vertically to target buffer
|
// Copy image flipped vertically to target buffer
|
||||||
int rows = buffer.remaining() / 4 / bufferWidth;
|
int rows = buffer.remaining() / bbp / bufferWidth;
|
||||||
for (int i = 0; i < rows; i++) {
|
for (int i = 0; i < rows; i++) {
|
||||||
buffer.get(rowBuf);
|
buffer.get(rowBuf);
|
||||||
to.position(((yOffset + rows - i - 1) * width + xOffset) * 4);
|
to.position(((yOffset + rows - i - 1) * width + xOffset) * bbp);
|
||||||
to.put(rowBuf);
|
to.put(rowBuf);
|
||||||
}
|
}
|
||||||
to.rewind();
|
to.rewind();
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
|||||||
/**
|
/**
|
||||||
* Whether we're currently reading packets from the login phase.
|
* Whether we're currently reading packets from the login phase.
|
||||||
*/
|
*/
|
||||||
private boolean loginPhase;
|
private boolean loginPhase = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether we need to restart the current replay. E.g. when jumping backwards in time
|
* Whether we need to restart the current replay. E.g. when jumping backwards in time
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
|||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.embedded.EmbeddedChannel;
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
|
||||||
import net.minecraft.client.network.ClientLoginNetworkHandler;
|
import net.minecraft.client.network.ClientLoginNetworkHandler;
|
||||||
import net.minecraft.util.crash.CrashReport;
|
import net.minecraft.util.crash.CrashReport;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.entity.Entity;
|
||||||
@@ -671,7 +672,10 @@ public class ReplayHandler {
|
|||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
// Send the packets
|
// Send the packets
|
||||||
|
do {
|
||||||
replaySender.sendPacketsTill(targetTime);
|
replaySender.sendPacketsTill(targetTime);
|
||||||
|
targetTime += 500;
|
||||||
|
} while (mc.player == null || mc.currentScreen instanceof DownloadingTerrainScreen);
|
||||||
replaySender.setAsyncMode(true);
|
replaySender.setAsyncMode(true);
|
||||||
replaySender.setReplaySpeed(0);
|
replaySender.setReplaySpeed(0);
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import com.replaymod.replay.gui.screen.GuiModCompatWarning;
|
|||||||
import com.replaymod.replay.handler.GuiHandler;
|
import com.replaymod.replay.handler.GuiHandler;
|
||||||
import com.replaymod.replaystudio.data.Marker;
|
import com.replaymod.replaystudio.data.Marker;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.options.KeyBinding;
|
import net.minecraft.client.options.KeyBinding;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@@ -42,6 +40,7 @@ public class ReplayModReplay implements Module {
|
|||||||
public static ReplayModReplay instance;
|
public static ReplayModReplay instance;
|
||||||
|
|
||||||
private ReplayMod core;
|
private ReplayMod core;
|
||||||
|
public KeyBindingRegistry.Binding keyPlayPause;
|
||||||
|
|
||||||
private final CameraControllerRegistry cameraControllerRegistry = new CameraControllerRegistry();
|
private final CameraControllerRegistry cameraControllerRegistry = new CameraControllerRegistry();
|
||||||
|
|
||||||
@@ -116,7 +115,7 @@ public class ReplayModReplay implements Module {
|
|||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
registry.registerKeyBinding("replaymod.input.playpause", Keyboard.KEY_P, new Runnable() {
|
keyPlayPause = registry.registerKeyBinding("replaymod.input.playpause", Keyboard.KEY_P, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (replayHandler != null) {
|
if (replayHandler != null) {
|
||||||
@@ -162,28 +161,38 @@ public class ReplayModReplay implements Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void startReplay(File file) throws IOException {
|
public void startReplay(File file) throws IOException {
|
||||||
startReplay(new ZipReplayFile(new ReplayStudio(), file));
|
startReplay(core.openReplay(file.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startReplay(ReplayFile replayFile) throws IOException {
|
public void startReplay(ReplayFile replayFile) throws IOException {
|
||||||
startReplay(replayFile, true);
|
startReplay(replayFile, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startReplay(ReplayFile replayFile, boolean checkModCompat) throws IOException {
|
public ReplayHandler startReplay(ReplayFile replayFile, boolean checkModCompat, boolean asyncMode) throws IOException {
|
||||||
if (replayHandler != null) {
|
if (replayHandler != null) {
|
||||||
replayHandler.endReplay();
|
replayHandler.endReplay();
|
||||||
}
|
}
|
||||||
if (checkModCompat) {
|
if (checkModCompat) {
|
||||||
ModCompat.ModInfoDifference modDifference = new ModCompat.ModInfoDifference(replayFile.getModInfo());
|
ModCompat.ModInfoDifference modDifference = new ModCompat.ModInfoDifference(replayFile.getModInfo());
|
||||||
if (!modDifference.getMissing().isEmpty() || !modDifference.getDiffering().isEmpty()) {
|
if (!modDifference.getMissing().isEmpty() || !modDifference.getDiffering().isEmpty()) {
|
||||||
new GuiModCompatWarning(this, replayFile, modDifference).display();
|
GuiModCompatWarning screen = new GuiModCompatWarning(modDifference);
|
||||||
return;
|
screen.loadButton.onClick(() -> {
|
||||||
|
try {
|
||||||
|
startReplay(replayFile, false, asyncMode);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
screen.display();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replayHandler = new ReplayHandler(replayFile, true);
|
replayHandler = new ReplayHandler(replayFile, asyncMode);
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
KeyBinding.updateKeysByCode(); // see Mixin_ContextualKeyBindings
|
KeyBinding.updateKeysByCode(); // see Mixin_ContextualKeyBindings
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
return replayHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forcefullyStopReplay() {
|
public void forcefullyStopReplay() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.replaymod.replay.camera;
|
package com.replaymod.replay.camera;
|
||||||
|
|
||||||
|
import com.replaymod.core.KeyBindingRegistry;
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.core.SettingsRegistry;
|
import com.replaymod.core.SettingsRegistry;
|
||||||
import com.replaymod.core.events.SettingsChangedCallback;
|
import com.replaymod.core.events.SettingsChangedCallback;
|
||||||
@@ -13,7 +14,6 @@ import com.replaymod.replaystudio.util.Location;
|
|||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
import net.minecraft.client.options.KeyBinding;
|
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.entity.mob.MobEntity;
|
import net.minecraft.entity.mob.MobEntity;
|
||||||
import net.minecraft.entity.decoration.ItemFrameEntity;
|
import net.minecraft.entity.decoration.ItemFrameEntity;
|
||||||
@@ -405,6 +405,22 @@ public class CameraEntity
|
|||||||
//$$ }
|
//$$ }
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
//#if MC>=10800
|
||||||
|
@Override
|
||||||
|
public float getSpeed() {
|
||||||
|
Entity view = getRenderViewEntity(this.client);
|
||||||
|
if (view != this && view instanceof AbstractClientPlayerEntity) {
|
||||||
|
return ((AbstractClientPlayerEntity) view).getSpeed();
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
//#else
|
||||||
|
//$$ @Override
|
||||||
|
//$$ public float getFOVMultiplier() {
|
||||||
|
//$$ return 1;
|
||||||
|
//$$ }
|
||||||
|
//#endif
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInvisible() {
|
public boolean isInvisible() {
|
||||||
Entity view = getRenderViewEntity(this.client);
|
Entity view = getRenderViewEntity(this.client);
|
||||||
@@ -585,11 +601,11 @@ public class CameraEntity
|
|||||||
|
|
||||||
handleInputEvents();
|
handleInputEvents();
|
||||||
|
|
||||||
Map<String, KeyBinding> keyBindings = ReplayMod.instance.getKeyBindingRegistry().getKeyBindings();
|
Map<String, KeyBindingRegistry.Binding> keyBindings = ReplayMod.instance.getKeyBindingRegistry().getBindings();
|
||||||
if (keyBindings.get("replaymod.input.rollclockwise").isPressed()) {
|
if (keyBindings.get("replaymod.input.rollclockwise").keyBinding.isPressed()) {
|
||||||
roll += Utils.isCtrlDown() ? 0.2 : 1;
|
roll += Utils.isCtrlDown() ? 0.2 : 1;
|
||||||
}
|
}
|
||||||
if (keyBindings.get("replaymod.input.rollcounterclockwise").isPressed()) {
|
if (keyBindings.get("replaymod.input.rollcounterclockwise").keyBinding.isPressed()) {
|
||||||
roll -= Utils.isCtrlDown() ? 0.2 : 1;
|
roll -= Utils.isCtrlDown() ? 0.2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -250,4 +250,9 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline<GuiMarkerTimeline> im
|
|||||||
markers.add(marker);
|
markers.add(marker);
|
||||||
saveMarkers.accept(markers);
|
saveMarkers.accept(markers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadableDimension getLastSize() {
|
||||||
|
return super.getLastSize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
package com.replaymod.replay.gui.overlay;
|
package com.replaymod.replay.gui.overlay;
|
||||||
|
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.core.events.KeyBindingEventCallback;
|
||||||
|
import com.replaymod.core.events.KeyEventCallback;
|
||||||
import com.replaymod.core.versions.MCVer.Keyboard;
|
import com.replaymod.core.versions.MCVer.Keyboard;
|
||||||
import com.replaymod.replay.ReplayHandler;
|
import com.replaymod.replay.ReplayHandler;
|
||||||
|
import com.replaymod.replay.ReplayModReplay;
|
||||||
import com.replaymod.replay.ReplaySender;
|
import com.replaymod.replay.ReplaySender;
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.RenderInfo;
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
import de.johni0702.minecraft.gui.container.AbstractGuiOverlay;
|
import de.johni0702.minecraft.gui.container.AbstractGuiOverlay;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
import de.johni0702.minecraft.gui.element.GuiElement;
|
import de.johni0702.minecraft.gui.element.GuiElement;
|
||||||
import de.johni0702.minecraft.gui.element.GuiSlider;
|
import de.johni0702.minecraft.gui.element.GuiSlider;
|
||||||
import de.johni0702.minecraft.gui.element.GuiTexturedButton;
|
|
||||||
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||||
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
|
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
@@ -22,30 +25,26 @@ import de.johni0702.minecraft.gui.utils.lwjgl.WritablePoint;
|
|||||||
import net.minecraft.client.options.GameOptions;
|
import net.minecraft.client.options.GameOptions;
|
||||||
import net.minecraft.client.resource.language.I18n;
|
import net.minecraft.client.resource.language.I18n;
|
||||||
|
|
||||||
//#if MC>=11400
|
|
||||||
import com.replaymod.core.events.KeyBindingEventCallback;
|
|
||||||
import com.replaymod.core.events.KeyEventCallback;
|
|
||||||
//#else
|
|
||||||
//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
//$$ import net.minecraftforge.fml.common.gameevent.InputEvent;
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
import static com.replaymod.core.ReplayMod.TEXTURE_SIZE;
|
import static com.replaymod.core.ReplayMod.TEXTURE_SIZE;
|
||||||
|
|
||||||
public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
|
public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
|
||||||
|
|
||||||
|
private final ReplayModReplay mod = ReplayModReplay.instance;
|
||||||
|
|
||||||
public final GuiPanel topPanel = new GuiPanel(this)
|
public final GuiPanel topPanel = new GuiPanel(this)
|
||||||
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.LEFT).setSpacing(5));
|
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.LEFT).setSpacing(5));
|
||||||
public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
|
public final GuiButton playPauseButton = new GuiButton() {
|
||||||
@Override
|
@Override
|
||||||
public GuiElement getTooltip(RenderInfo renderInfo) {
|
public GuiElement getTooltip(RenderInfo renderInfo) {
|
||||||
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
||||||
if (tooltip != null) {
|
if (tooltip != null) {
|
||||||
if (getTextureNormal().getY() == 0) { // Play button
|
String label;
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.unpause");
|
if (getSpriteUV().getY() == 0) { // Play button
|
||||||
|
label = "replaymod.gui.ingame.menu.unpause";
|
||||||
} else { // Pause button
|
} else { // Pause button
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.pause");
|
label = "replaymod.gui.ingame.menu.pause";
|
||||||
}
|
}
|
||||||
|
tooltip.setText(I18n.translate(label) + " (" + mod.keyPlayPause.getBoundKey() + ")");
|
||||||
}
|
}
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}
|
}
|
||||||
@@ -61,6 +60,7 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
|
|||||||
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(5));
|
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(5));
|
||||||
|
|
||||||
private final EventHandler eventHandler = new EventHandler();
|
private final EventHandler eventHandler = new EventHandler();
|
||||||
|
private boolean hidden;
|
||||||
|
|
||||||
public GuiReplayOverlay(final ReplayHandler replayHandler) {
|
public GuiReplayOverlay(final ReplayHandler replayHandler) {
|
||||||
timeline = new GuiMarkerTimeline(replayHandler){
|
timeline = new GuiMarkerTimeline(replayHandler){
|
||||||
@@ -83,7 +83,7 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
playPauseButton.setTexturePosH(new ReadablePoint() {
|
playPauseButton.setSpriteUV(new ReadablePoint() {
|
||||||
@Override
|
@Override
|
||||||
public int getX() {
|
public int getX() {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -156,8 +156,8 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||||
// Do not render overlay when user pressed F1 and we are not currently in some popup
|
// Do not render overlay if all hud, or this one specifically, is hidden and we're not in some popup
|
||||||
if (getMinecraft().options.hudHidden && isAllowUserInput()) {
|
if ((getMinecraft().options.hudHidden || hidden) && isAllowUserInput()) {
|
||||||
// Note that this only applies to when the mouse is visible, otherwise
|
// Note that this only applies to when the mouse is visible, otherwise
|
||||||
// the draw method isn't called in the first place
|
// the draw method isn't called in the first place
|
||||||
return;
|
return;
|
||||||
@@ -176,13 +176,8 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
|
|||||||
//$$ public // All event handlers need to be public in 1.7.10
|
//$$ public // All event handlers need to be public in 1.7.10
|
||||||
//#endif
|
//#endif
|
||||||
class EventHandler extends EventRegistrations {
|
class EventHandler extends EventRegistrations {
|
||||||
//#if MC>=11400
|
|
||||||
{ on(KeyBindingEventCallback.EVENT, this::onKeyBindingEvent); }
|
{ on(KeyBindingEventCallback.EVENT, this::onKeyBindingEvent); }
|
||||||
private void onKeyBindingEvent() {
|
private void onKeyBindingEvent() {
|
||||||
//#else
|
|
||||||
//$$ @SubscribeEvent
|
|
||||||
//$$ public void onKeyBindingEvent(InputEvent.KeyInputEvent event) {
|
|
||||||
//#endif
|
|
||||||
GameOptions gameSettings = getMinecraft().options;
|
GameOptions gameSettings = getMinecraft().options;
|
||||||
while (gameSettings.keyChat.wasPressed() || gameSettings.keyCommand.wasPressed()) {
|
while (gameSettings.keyChat.wasPressed() || gameSettings.keyCommand.wasPressed()) {
|
||||||
if (!isMouseVisible()) {
|
if (!isMouseVisible()) {
|
||||||
@@ -191,20 +186,12 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#if MC>=11400
|
{ on(KeyEventCallback.EVENT, (int key, int scanCode, int action, int modifiers) -> { onKeyInput(key, action); return false; }); }
|
||||||
{ on(KeyEventCallback.EVENT, (int key, int scanCode, int action, int modifiers) -> onKeyInput(key, action)); }
|
|
||||||
private void onKeyInput(int key, int action) {
|
private void onKeyInput(int key, int action) {
|
||||||
if (action != 0) return;
|
if (action != KeyEventCallback.ACTION_PRESS) return;
|
||||||
//#else
|
// Allow F1 to be used to hide the replay gui (e.g. for recording with OBS)
|
||||||
//$$ @SubscribeEvent
|
|
||||||
//$$ public void onKeyInput(InputEvent.KeyInputEvent event) {
|
|
||||||
//$$ if (!Keyboard.getEventKeyState()) return;
|
|
||||||
//$$ int key = Keyboard.getEventKey();
|
|
||||||
//#endif
|
|
||||||
GameOptions gameSettings = getMinecraft().options;
|
|
||||||
// Handle the F1 key binding while the overlay is opened as a gui screen
|
|
||||||
if (isMouseVisible() && key == Keyboard.KEY_F1) {
|
if (isMouseVisible() && key == Keyboard.KEY_F1) {
|
||||||
gameSettings.hudHidden = !gameSettings.hudHidden;
|
hidden = !hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.replaymod.replay.gui.screen;
|
package com.replaymod.replay.gui.screen;
|
||||||
|
|
||||||
import com.replaymod.core.utils.ModCompat;
|
import com.replaymod.core.utils.ModCompat;
|
||||||
import com.replaymod.replay.ReplayModReplay;
|
|
||||||
import com.replaymod.replaystudio.data.ModInfo;
|
import com.replaymod.replaystudio.data.ModInfo;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
|
||||||
import com.replaymod.replaystudio.util.I18n;
|
import com.replaymod.replaystudio.util.I18n;
|
||||||
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
@@ -14,7 +12,6 @@ import de.johni0702.minecraft.gui.layout.CustomLayout;
|
|||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class GuiModCompatWarning extends AbstractGuiScreen<GuiModCompatWarning> {
|
public class GuiModCompatWarning extends AbstractGuiScreen<GuiModCompatWarning> {
|
||||||
@@ -38,7 +35,7 @@ public class GuiModCompatWarning extends AbstractGuiScreen<GuiModCompatWarning>
|
|||||||
content.getListLayout().setSpacing(8);
|
content.getListLayout().setSpacing(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GuiModCompatWarning(ReplayModReplay mod, ReplayFile replayFile, ModCompat.ModInfoDifference difference) {
|
public GuiModCompatWarning(ModCompat.ModInfoDifference difference) {
|
||||||
VerticalLayout.Data data = new VerticalLayout.Data(0.5);
|
VerticalLayout.Data data = new VerticalLayout.Data(0.5);
|
||||||
GuiPanel content = this.content.getListPanel();
|
GuiPanel content = this.content.getListPanel();
|
||||||
content.addElements(data, new GuiLabel().setI18nText("replaymod.gui.modwarning.message1"));
|
content.addElements(data, new GuiLabel().setI18nText("replaymod.gui.modwarning.message1"));
|
||||||
@@ -82,13 +79,6 @@ public class GuiModCompatWarning extends AbstractGuiScreen<GuiModCompatWarning>
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancelButton.onClick(() -> getMinecraft().openScreen(null));
|
cancelButton.onClick(() -> getMinecraft().openScreen(null));
|
||||||
loadButton.onClick(() -> {
|
|
||||||
try {
|
|
||||||
mod.startReplay(replayFile, false);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,7 +4,14 @@ import com.google.common.base.Supplier;
|
|||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
import com.replaymod.render.gui.GuiRenderQueue;
|
||||||
|
import com.replaymod.render.rendering.VideoRenderer;
|
||||||
|
import com.replaymod.render.utils.RenderJob;
|
||||||
|
import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair;
|
||||||
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
import de.johni0702.minecraft.gui.versions.Image;
|
import de.johni0702.minecraft.gui.versions.Image;
|
||||||
|
import net.minecraft.client.font.TextRenderer;
|
||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.util.Formatting;
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
import com.replaymod.core.SettingsRegistry;
|
import com.replaymod.core.SettingsRegistry;
|
||||||
@@ -16,8 +23,6 @@ import com.replaymod.replay.ReplayModReplay;
|
|||||||
import com.replaymod.replay.Setting;
|
import com.replaymod.replay.Setting;
|
||||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
|
||||||
import de.johni0702.minecraft.gui.container.AbstractGuiContainer;
|
import de.johni0702.minecraft.gui.container.AbstractGuiContainer;
|
||||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
@@ -50,11 +55,18 @@ import java.io.InputStream;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.replaymod.replay.ReplayModReplay.LOGGER;
|
import static com.replaymod.replay.ReplayModReplay.LOGGER;
|
||||||
|
import static de.johni0702.minecraft.gui.versions.MCVer.getFontRenderer;
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
import net.minecraft.text.TranslatableText;
|
import net.minecraft.text.TranslatableText;
|
||||||
@@ -65,39 +77,50 @@ import net.minecraft.text.TranslatableText;
|
|||||||
public class GuiReplayViewer extends GuiScreen {
|
public class GuiReplayViewer extends GuiScreen {
|
||||||
private final ReplayModReplay mod;
|
private final ReplayModReplay mod;
|
||||||
|
|
||||||
public final GuiReplayList list = new GuiReplayList(this).onSelectionChanged(new Runnable() {
|
public final GuiReplayList list = new GuiReplayList(this).onSelectionChanged(this::updateButtons).onSelectionDoubleClicked(() -> {
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
replaySpecificButtons.forEach(b -> b.setEnabled(list.getSelected() != null));
|
|
||||||
if (list.getSelected() != null && list.getSelected().incompatible) {
|
|
||||||
loadButton.setDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).onSelectionDoubleClicked(() -> {
|
|
||||||
if (this.loadButton.isEnabled()) {
|
if (this.loadButton.isEnabled()) {
|
||||||
this.loadButton.onClick();
|
this.loadButton.onClick();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public final GuiButton loadButton = new GuiButton().onClick(new Runnable() {
|
public final GuiButton loadButton = new GuiButton().onClick(new Runnable() {
|
||||||
|
private boolean loading = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// Prevent the player from opening the replay twice at the same time
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading = true;
|
||||||
|
loadButton.setDisabled(); // visual hint
|
||||||
|
|
||||||
|
List<GuiReplayEntry> selected = list.getSelected();
|
||||||
|
if (selected.size() == 1) {
|
||||||
try {
|
try {
|
||||||
mod.startReplay(list.getSelected().file);
|
mod.startReplay(selected.get(0).file);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
}
|
||||||
// Disable load button to prevent the player from opening the replay twice at the same time
|
} else {
|
||||||
loadButton.setDisabled();
|
Iterator<Pair<File, List<RenderJob>>> replays = selected.stream()
|
||||||
|
.filter(it -> !it.renderQueue.isEmpty())
|
||||||
|
.map(it -> new Pair<>(it.file, it.renderQueue))
|
||||||
|
.iterator();
|
||||||
|
GuiRenderQueue.processMultipleReplays(GuiReplayViewer.this, mod, replays, () -> {
|
||||||
|
loading = false;
|
||||||
|
updateButtons();
|
||||||
|
display();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).setSize(150, 20).setI18nLabel("replaymod.gui.load").setDisabled();
|
}).setSize(150, 20);
|
||||||
|
|
||||||
public final GuiButton folderButton = new GuiButton().onClick(new Runnable() {
|
public final GuiButton folderButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
File folder = mod.getCore().getReplayFolder();
|
File folder = mod.getCore().getReplayFolder().toFile();
|
||||||
|
|
||||||
MCVer.openFile(folder);
|
MCVer.openFile(folder);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -109,7 +132,7 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
public final GuiButton renameButton = new GuiButton().onClick(new Runnable() {
|
public final GuiButton renameButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final File file = list.getSelected().file;
|
final File file = list.getSelected().get(0).file;
|
||||||
String name = Utils.fileNameToReplayName(file.getName());
|
String name = Utils.fileNameToReplayName(file.getName());
|
||||||
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name);
|
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name);
|
||||||
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
|
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
|
||||||
@@ -165,10 +188,9 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).setSize(73, 20).setI18nLabel("replaymod.gui.rename").setDisabled();
|
}).setSize(73, 20).setI18nLabel("replaymod.gui.rename").setDisabled();
|
||||||
public final GuiButton deleteButton = new GuiButton().onClick(new Runnable() {
|
public final GuiButton deleteButton = new GuiButton().onClick(() -> {
|
||||||
@Override
|
for (GuiReplayEntry entry : list.getSelected()) {
|
||||||
public void run() {
|
String name = entry.name.getText();
|
||||||
String name = list.getSelected().name.getText();
|
|
||||||
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
|
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
|
||||||
new GuiLabel().setI18nText("replaymod.gui.viewer.delete.linea").setColor(Colors.BLACK),
|
new GuiLabel().setI18nText("replaymod.gui.viewer.delete.linea").setColor(Colors.BLACK),
|
||||||
new GuiLabel().setI18nText("replaymod.gui.viewer.delete.lineb", name + Formatting.RESET).setColor(Colors.BLACK)
|
new GuiLabel().setI18nText("replaymod.gui.viewer.delete.lineb", name + Formatting.RESET).setColor(Colors.BLACK)
|
||||||
@@ -178,7 +200,7 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
public void onSuccess(Boolean delete) {
|
public void onSuccess(Boolean delete) {
|
||||||
if (delete) {
|
if (delete) {
|
||||||
try {
|
try {
|
||||||
FileUtils.forceDelete(list.getSelected().file);
|
FileUtils.forceDelete(entry.file);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -194,12 +216,11 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
}
|
}
|
||||||
}).setSize(73, 20).setI18nLabel("replaymod.gui.delete").setDisabled();
|
}).setSize(73, 20).setI18nLabel("replaymod.gui.delete").setDisabled();
|
||||||
|
|
||||||
public final GuiButton settingsButton = new GuiButton().onClick(new Runnable() {
|
public final GuiButton settingsButton = new GuiButton(this)
|
||||||
@Override
|
.setSize(20, 20)
|
||||||
public void run() {
|
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setSpriteUV(20, 0)
|
||||||
new GuiReplaySettings(toMinecraft(), mod.getCore().getSettingsRegistry()).display();
|
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.settings"))
|
||||||
}
|
.onClick(() -> new GuiReplaySettings(toMinecraft(), getMod().getCore().getSettingsRegistry()).display());
|
||||||
}).setSize(150, 20).setI18nLabel("replaymod.gui.settings");
|
|
||||||
|
|
||||||
public final GuiButton cancelButton = new GuiButton().onClick(new Runnable() {
|
public final GuiButton cancelButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -209,11 +230,11 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
}).setSize(73, 20).setI18nLabel("replaymod.gui.cancel");
|
}).setSize(73, 20).setI18nLabel("replaymod.gui.cancel");
|
||||||
|
|
||||||
public final List<GuiButton> replaySpecificButtons = new ArrayList<>();
|
public final List<GuiButton> replaySpecificButtons = new ArrayList<>();
|
||||||
{ replaySpecificButtons.addAll(Arrays.asList(loadButton, renameButton, deleteButton)); }
|
{ replaySpecificButtons.addAll(Arrays.asList(renameButton)); }
|
||||||
public final GuiPanel editorButton = new GuiPanel();
|
public final GuiPanel editorButton = new GuiPanel();
|
||||||
|
|
||||||
public final GuiPanel upperButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5))
|
public final GuiPanel upperButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5))
|
||||||
.addElements(null, loadButton, settingsButton);
|
.addElements(null, loadButton);
|
||||||
public final GuiPanel lowerButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5))
|
public final GuiPanel lowerButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5))
|
||||||
.addElements(null, renameButton, deleteButton, editorButton, cancelButton);
|
.addElements(null, renameButton, deleteButton, editorButton, cancelButton);
|
||||||
public final GuiPanel buttonPanel = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(5))
|
public final GuiPanel buttonPanel = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(5))
|
||||||
@@ -223,7 +244,7 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
list.setFolder(mod.getCore().getReplayFolder());
|
list.setFolder(mod.getCore().getReplayFolder().toFile());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new CrashException(CrashReport.create(e, "Getting replay folder"));
|
throw new CrashException(CrashReport.create(e, "Getting replay folder"));
|
||||||
}
|
}
|
||||||
@@ -237,8 +258,40 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
|
|
||||||
pos(list, 0, 30);
|
pos(list, 0, 30);
|
||||||
size(list, width, y(buttonPanel) - 10 - y(list));
|
size(list, width, y(buttonPanel) - 10 - y(list));
|
||||||
|
|
||||||
|
pos(settingsButton, width - width(settingsButton) - 5, 5);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplayModReplay getMod() {
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateButtons() {
|
||||||
|
List<GuiReplayEntry> selected = list.getSelected();
|
||||||
|
int count = selected.size();
|
||||||
|
|
||||||
|
replaySpecificButtons.forEach(b -> b.setEnabled(count == 1));
|
||||||
|
deleteButton.setEnabled(count > 0);
|
||||||
|
if (count > 1) {
|
||||||
|
Set<RenderJob> jobs = selected.stream().flatMap(entry -> entry.renderQueue.stream()).collect(Collectors.toSet());
|
||||||
|
String[] tooltipLines = jobs.stream().map(RenderJob::getName).toArray(String[]::new);
|
||||||
|
loadButton.setI18nLabel("replaymod.gui.viewer.bulkrender", jobs.size());
|
||||||
|
loadButton.setTooltip(new GuiTooltip().setText(tooltipLines));
|
||||||
|
loadButton.setEnabled(!jobs.isEmpty());
|
||||||
|
|
||||||
|
String[] compatError = VideoRenderer.checkCompat();
|
||||||
|
if (compatError != null) {
|
||||||
|
loadButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loadButton.setI18nLabel("replaymod.gui.load");
|
||||||
|
loadButton.setTooltip(null);
|
||||||
|
loadButton.setEnabled(count == 1 && !selected.get(0).incompatible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final GuiImage DEFAULT_THUMBNAIL = new GuiImage().setTexture(Utils.DEFAULT_THUMBNAIL);
|
private static final GuiImage DEFAULT_THUMBNAIL = new GuiImage().setTexture(Utils.DEFAULT_THUMBNAIL);
|
||||||
@@ -269,10 +322,10 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
acceptButton.setEnabled(list.getSelected() != null);
|
acceptButton.setEnabled(list.getSelected() != null);
|
||||||
}).onSelectionDoubleClicked(() -> {
|
}).onSelectionDoubleClicked(() -> {
|
||||||
close();
|
close();
|
||||||
future.set(list.getSelected().file);
|
future.set(list.getSelected().get(0).file);
|
||||||
});
|
});
|
||||||
acceptButton.onClick(() -> {
|
acceptButton.onClick(() -> {
|
||||||
future.set(list.getSelected().file);
|
future.set(list.getSelected().get(0).file);
|
||||||
close();
|
close();
|
||||||
});
|
});
|
||||||
cancelButton.onClick(() -> {
|
cancelButton.onClick(() -> {
|
||||||
@@ -321,6 +374,10 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
public static class GuiReplayList extends AbstractGuiResourceLoadingList<GuiReplayList, GuiReplayEntry> implements Typeable {
|
public static class GuiReplayList extends AbstractGuiResourceLoadingList<GuiReplayList, GuiReplayEntry> implements Typeable {
|
||||||
private File folder = null;
|
private File folder = null;
|
||||||
|
|
||||||
|
// Not actually a child of this element, we just use it for text manipulation
|
||||||
|
private final GuiTextField filterTextField = new GuiTextField()
|
||||||
|
.setFocused(true);
|
||||||
|
|
||||||
public GuiReplayList(GuiContainer container) {
|
public GuiReplayList(GuiContainer container) {
|
||||||
super(container);
|
super(container);
|
||||||
}
|
}
|
||||||
@@ -332,16 +389,13 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
LOGGER.warn("Failed to list files in {}", folder);
|
LOGGER.warn("Failed to list files in {}", folder);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Map<File, Long> lastModified = new HashMap<>();
|
||||||
|
Arrays.sort(files, Comparator.<File>comparingLong(f -> lastModified.computeIfAbsent(f, File::lastModified)).reversed());
|
||||||
for (final File file : files) {
|
for (final File file : files) {
|
||||||
if (Thread.interrupted()) break;
|
if (Thread.interrupted()) break;
|
||||||
try (ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), file)) {
|
try (ReplayFile replayFile = ReplayMod.instance.openReplay(file.toPath())) {
|
||||||
// TODO add a getThumbBytes method to ReplayStudio
|
final Image thumb = Optional.ofNullable(replayFile.getThumbBytes().orNull()).flatMap(stream -> {
|
||||||
final Image thumb = Optional.ofNullable(replayFile.get("thumb").orNull()).flatMap(stream -> {
|
|
||||||
try (InputStream in = stream) {
|
try (InputStream in = stream) {
|
||||||
int i = 7;
|
|
||||||
while (i > 0) {
|
|
||||||
i -= in.skip(i);
|
|
||||||
}
|
|
||||||
return Optional.of(Image.read(in));
|
return Optional.of(Image.read(in));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -349,9 +403,18 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
}
|
}
|
||||||
}).orElse(null);
|
}).orElse(null);
|
||||||
final ReplayMetaData metaData = replayFile.getMetaData();
|
final ReplayMetaData metaData = replayFile.getMetaData();
|
||||||
|
List<RenderJob> renderQueue = RenderJob.readQueue(replayFile);
|
||||||
|
|
||||||
if (metaData != null) {
|
if (metaData != null) {
|
||||||
results.consume(() -> new GuiReplayEntry(file, metaData, thumb));
|
results.consume(() -> new GuiReplayEntry(file, metaData, thumb, renderQueue) {
|
||||||
|
@Override
|
||||||
|
public ReadableDimension calcMinSize() {
|
||||||
|
if (isFiltered(this)) {
|
||||||
|
return new Dimension(-4, -4);
|
||||||
|
}
|
||||||
|
return super.calcMinSize();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("Could not load Replay File {}", file.getName(), e);
|
LOGGER.error("Could not load Replay File {}", file.getName(), e);
|
||||||
@@ -364,6 +427,14 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
this.folder = folder;
|
this.folder = folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isFiltered(GuiReplayEntry entry) {
|
||||||
|
String filter = filterTextField.getText().toLowerCase();
|
||||||
|
if (filter.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !entry.name.getText().toLowerCase().contains(filter);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
|
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
|
||||||
if (keyCode == Keyboard.KEY_F1) {
|
if (keyCode == Keyboard.KEY_F1) {
|
||||||
@@ -372,9 +443,52 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
reg.save();
|
reg.save();
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean filterHasPriority = !filterTextField.getText().isEmpty();
|
||||||
|
if (filterHasPriority && filterTextField.typeKey(mousePosition, keyCode, keyChar, ctrlDown, shiftDown)) {
|
||||||
|
scrollY(0); // ensure we scroll to top if most entries are filtered
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (super.typeKey(mousePosition, keyCode, keyChar, ctrlDown, shiftDown)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filterHasPriority && filterTextField.typeKey(mousePosition, keyCode, keyChar, ctrlDown, shiftDown)) {
|
||||||
|
scrollY(0); // ensure we scroll to top if most entries are filtered
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||||
|
super.draw(renderer, size, renderInfo);
|
||||||
|
|
||||||
|
String filter = filterTextField.getText();
|
||||||
|
if (!filter.isEmpty()) {
|
||||||
|
boolean anyMatches = getListPanel().calcMinSize().getHeight() > 0;
|
||||||
|
|
||||||
|
TextRenderer fontRenderer = getFontRenderer();
|
||||||
|
int filterTextWidth = fontRenderer.getWidth(filter);
|
||||||
|
int filterTextHeight = fontRenderer.fontHeight;
|
||||||
|
renderer.drawRect(
|
||||||
|
size.getWidth() - 3 - 2 - filterTextWidth - 2,
|
||||||
|
size.getHeight() - 3 - 2 - filterTextHeight - 2,
|
||||||
|
2 + filterTextWidth + 2,
|
||||||
|
2 + filterTextHeight + 2,
|
||||||
|
Colors.WHITE
|
||||||
|
);
|
||||||
|
renderer.drawString(
|
||||||
|
size.getWidth() - 3 - 2 - filterTextWidth,
|
||||||
|
size.getHeight() - 3 - 2 - filterTextHeight,
|
||||||
|
anyMatches ? Colors.BLACK : Colors.DARK_RED,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GuiReplayList getThis() {
|
protected GuiReplayList getThis() {
|
||||||
return this;
|
return this;
|
||||||
@@ -404,15 +518,22 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
return new Dimension(dimension.getWidth() + 2, dimension.getHeight() + 2);
|
return new Dimension(dimension.getWidth() + 2, dimension.getHeight() + 2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
public final GuiImage renderQueueIcon = new GuiImage()
|
||||||
|
.setSize(10, 10)
|
||||||
|
.setTexture(ReplayMod.TEXTURE, 40, 0, 20, 20);
|
||||||
|
|
||||||
private final long dateMillis;
|
private final long dateMillis;
|
||||||
private final boolean incompatible;
|
private final boolean incompatible;
|
||||||
|
private final List<RenderJob> renderQueue;
|
||||||
|
|
||||||
public GuiReplayEntry(File file, ReplayMetaData metaData, Image thumbImage) {
|
public GuiReplayEntry(File file, ReplayMetaData metaData, Image thumbImage, List<RenderJob> renderQueue) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
|
this.renderQueue = renderQueue;
|
||||||
|
|
||||||
name.setText(Formatting.UNDERLINE + Utils.fileNameToReplayName(file.getName()));
|
name.setText(Formatting.UNDERLINE + Utils.fileNameToReplayName(file.getName()));
|
||||||
if (StringUtils.isEmpty(metaData.getServerName())
|
if (!StringUtils.isEmpty(metaData.getCustomServerName())) {
|
||||||
|
server.setText(metaData.getCustomServerName());
|
||||||
|
} else if (StringUtils.isEmpty(metaData.getServerName())
|
||||||
|| !ReplayMod.instance.getSettingsRegistry().get(Setting.SHOW_SERVER_IPS)) {
|
|| !ReplayMod.instance.getSettingsRegistry().get(Setting.SHOW_SERVER_IPS)) {
|
||||||
server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED);
|
server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED);
|
||||||
} else {
|
} else {
|
||||||
@@ -433,6 +554,12 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
duration.setText(Utils.convertSecondsToShortString(metaData.getDuration() / 1000));
|
duration.setText(Utils.convertSecondsToShortString(metaData.getDuration() / 1000));
|
||||||
addElements(null, durationPanel);
|
addElements(null, durationPanel);
|
||||||
|
|
||||||
|
if (!renderQueue.isEmpty()) {
|
||||||
|
renderQueueIcon.setTooltip(new GuiTooltip()
|
||||||
|
.setText(renderQueue.stream().map(RenderJob::getName).toArray(String[]::new)));
|
||||||
|
addElements(null, renderQueueIcon);
|
||||||
|
}
|
||||||
|
|
||||||
setLayout(new CustomLayout<GuiReplayEntry>() {
|
setLayout(new CustomLayout<GuiReplayEntry>() {
|
||||||
@Override
|
@Override
|
||||||
protected void layout(GuiReplayEntry container, int width, int height) {
|
protected void layout(GuiReplayEntry container, int width, int height) {
|
||||||
@@ -442,6 +569,10 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
|
|
||||||
pos(infoPanel, width(thumbnail) + 5, 0);
|
pos(infoPanel, width(thumbnail) + 5, 0);
|
||||||
pos(version, width - width(version), 0);
|
pos(version, width - width(version), 0);
|
||||||
|
|
||||||
|
if (renderQueueIcon.getContainer() != null) {
|
||||||
|
pos(renderQueueIcon, width(thumbnail) - width(renderQueueIcon), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import net.minecraft.client.gui.screen.TitleScreen;
|
|||||||
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
|
|
||||||
//#if MC>=11600
|
//#if MC>=11600
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.text.TranslatableText;
|
import net.minecraft.text.TranslatableText;
|
||||||
|
//#else
|
||||||
|
//$$ import net.minecraft.client.resource.language.I18n;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
@@ -29,6 +30,7 @@ import net.minecraft.client.gui.widget.ButtonWidget;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static com.replaymod.core.versions.MCVer.*;
|
import static com.replaymod.core.versions.MCVer.*;
|
||||||
@@ -152,40 +154,48 @@ public class GuiHandler extends EventRegistrations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (achievements != null && stats != null) {
|
if (achievements != null && stats != null) {
|
||||||
moveAllButtonsDirectlyBelowUpwards(buttonList, achievements.y,
|
moveAllButtonsInRect(buttonList,
|
||||||
achievements.x, stats.x + width(stats));
|
achievements.x, stats.x + width(stats),
|
||||||
|
achievements.y, Integer.MAX_VALUE,
|
||||||
|
-24);
|
||||||
}
|
}
|
||||||
// In 1.13+ Forge, the Options button shares one row with the Open to LAN button
|
// In 1.13+ Forge, the Options button shares one row with the Open to LAN button
|
||||||
//#if MC<11400
|
//#if MC<11400
|
||||||
//$$ if (openToLan != null) {
|
//$$ if (openToLan != null) {
|
||||||
//$$ moveAllButtonsDirectlyBelowUpwards(buttonList, openToLan.y,
|
//$$ moveAllButtonsInRect(buttonList,
|
||||||
//$$ openToLan.x, openToLan.x + openToLan.width);
|
//$$ openToLan.x, openToLan.x + openToLan.width,
|
||||||
|
//$$ openToLan.y, Integer.MAX_VALUE,
|
||||||
|
//$$ -24);
|
||||||
//$$ }
|
//$$ }
|
||||||
//#endif
|
//#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves all buttons that are within a rectangle below a certain y coordinate upwards by 24 units.
|
* Moves all buttons that in any way intersect a rectangle by a given amount on the y axis.
|
||||||
* @param buttons List of buttons
|
* @param buttons List of buttons
|
||||||
* @param belowY The Y limit
|
* @param yStart Top y limit of the rectangle
|
||||||
|
* @param yEnd Bottom y limit of the rectangle
|
||||||
* @param xStart Left x limit of the rectangle
|
* @param xStart Left x limit of the rectangle
|
||||||
* @param xEnd Right x limit of the rectangle
|
* @param xEnd Right x limit of the rectangle
|
||||||
|
* @param moveBy Signed distance to move the buttons
|
||||||
*/
|
*/
|
||||||
private void moveAllButtonsDirectlyBelowUpwards(
|
private void moveAllButtonsInRect(
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
List<AbstractButtonWidget> buttons,
|
List<AbstractButtonWidget> buttons,
|
||||||
//#else
|
//#else
|
||||||
//$$ List<GuiButton> buttons,
|
//$$ List<GuiButton> buttons,
|
||||||
//#endif
|
//#endif
|
||||||
int belowY,
|
|
||||||
int xStart,
|
int xStart,
|
||||||
int xEnd
|
int xEnd,
|
||||||
|
int yStart,
|
||||||
|
int yEnd,
|
||||||
|
int moveBy
|
||||||
) {
|
) {
|
||||||
buttons.stream()
|
buttons.stream()
|
||||||
.filter(button -> button.y >= belowY)
|
|
||||||
.filter(button -> button.x <= xEnd && button.x + width(button) >= xStart)
|
.filter(button -> button.x <= xEnd && button.x + width(button) >= xStart)
|
||||||
.forEach(button -> button.y -= 24);
|
.filter(button -> button.y <= yEnd && button.y + height(button) >= yStart)
|
||||||
|
.forEach(button -> button.y += moveBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
@@ -222,15 +232,35 @@ public class GuiHandler extends EventRegistrations {
|
|||||||
//$$ @SubscribeEvent
|
//$$ @SubscribeEvent
|
||||||
//$$ public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
|
//$$ public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
|
||||||
//$$ Screen guiScreen = getGui(event);
|
//$$ Screen guiScreen = getGui(event);
|
||||||
|
//$$ List<Widget> buttonList = getButtonList(event);
|
||||||
//#endif
|
//#endif
|
||||||
if (!(guiScreen instanceof TitleScreen)) {
|
if (!(guiScreen instanceof TitleScreen)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int x = guiScreen.width / 2 - 100;
|
||||||
|
// We want to position our button below the realms button
|
||||||
|
int y = findButton(buttonList, "menu.online", 14)
|
||||||
|
.map(Optional::of)
|
||||||
|
// or, if someone removed the realms button, we'll alternatively take the multiplayer one
|
||||||
|
.orElse(findButton(buttonList, "menu.multiplayer", 2))
|
||||||
|
// if we found some button, put our button at its position (we'll move it out of the way shortly)
|
||||||
|
.map(it -> it.y)
|
||||||
|
// and if we can't even find that one, then just guess
|
||||||
|
.orElse(guiScreen.height / 4 + 10 + 4 * 24);
|
||||||
|
|
||||||
|
// Move all buttons above or at our one upwards
|
||||||
|
moveAllButtonsInRect(buttonList,
|
||||||
|
x, x + 200,
|
||||||
|
Integer.MIN_VALUE, y,
|
||||||
|
-24);
|
||||||
|
|
||||||
|
// Add our button
|
||||||
InjectedButton button = new InjectedButton(
|
InjectedButton button = new InjectedButton(
|
||||||
guiScreen,
|
guiScreen,
|
||||||
BUTTON_REPLAY_VIEWER,
|
BUTTON_REPLAY_VIEWER,
|
||||||
guiScreen.width / 2 - 100,
|
x,
|
||||||
guiScreen.height / 4 + 10 + 4 * 24,
|
y,
|
||||||
200,
|
200,
|
||||||
20,
|
20,
|
||||||
"replaymod.gui.replayviewer",
|
"replaymod.gui.replayviewer",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public abstract class Mixin_MoveRealmsButton {
|
|||||||
index = 2
|
index = 2
|
||||||
)
|
)
|
||||||
private int adjustRealmsButton(int height) {
|
private int adjustRealmsButton(int height) {
|
||||||
return height - (24 - 10) * 4;
|
return height - 24 * 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endif
|
//#endif
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.replaymod.simplepathing;
|
|||||||
import com.replaymod.core.KeyBindingRegistry;
|
import com.replaymod.core.KeyBindingRegistry;
|
||||||
import com.replaymod.core.Module;
|
import com.replaymod.core.Module;
|
||||||
import com.replaymod.core.ReplayMod;
|
import com.replaymod.core.ReplayMod;
|
||||||
|
import com.replaymod.core.SettingsRegistry;
|
||||||
import com.replaymod.core.events.SettingsChangedCallback;
|
import com.replaymod.core.events.SettingsChangedCallback;
|
||||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||||
import com.replaymod.core.versions.MCVer.Keyboard;
|
import com.replaymod.core.versions.MCVer.Keyboard;
|
||||||
@@ -21,6 +22,7 @@ import com.replaymod.replaystudio.replay.ReplayFile;
|
|||||||
import com.replaymod.simplepathing.SPTimeline.SPPath;
|
import com.replaymod.simplepathing.SPTimeline.SPPath;
|
||||||
import com.replaymod.simplepathing.gui.GuiPathing;
|
import com.replaymod.simplepathing.gui.GuiPathing;
|
||||||
import com.replaymod.simplepathing.preview.PathPreview;
|
import com.replaymod.simplepathing.preview.PathPreview;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import net.minecraft.util.crash.CrashReport;
|
import net.minecraft.util.crash.CrashReport;
|
||||||
import net.minecraft.util.crash.CrashException;
|
import net.minecraft.util.crash.CrashException;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@@ -39,6 +41,9 @@ public class ReplayModSimplePathing extends EventRegistrations implements Module
|
|||||||
public static ReplayModSimplePathing instance;
|
public static ReplayModSimplePathing instance;
|
||||||
|
|
||||||
private ReplayMod core;
|
private ReplayMod core;
|
||||||
|
public KeyBindingRegistry.Binding keyPositionKeyframe;
|
||||||
|
public KeyBindingRegistry.Binding keyTimeKeyframe;
|
||||||
|
public KeyBindingRegistry.Binding keySyncTime;
|
||||||
|
|
||||||
public static Logger LOGGER = LogManager.getLogger();
|
public static Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
@@ -85,11 +90,56 @@ public class ReplayModSimplePathing extends EventRegistrations implements Module
|
|||||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
||||||
if (guiPathing != null) guiPathing.clearKeyframesButtonPressed();
|
if (guiPathing != null) guiPathing.clearKeyframesButtonPressed();
|
||||||
}, true);
|
}, true);
|
||||||
core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
keySyncTime = core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
||||||
if (guiPathing != null) guiPathing.syncTimeButtonPressed();
|
if (guiPathing != null) guiPathing.syncTimeButtonPressed();
|
||||||
}, true);
|
}, true);
|
||||||
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
|
SettingsRegistry settingsRegistry = core.getSettingsRegistry();
|
||||||
if (guiPathing != null) guiPathing.deleteButtonPressed();
|
keySyncTime.registerAutoActivationSupport(settingsRegistry.get(Setting.AUTO_SYNC), active -> {
|
||||||
|
settingsRegistry.set(Setting.AUTO_SYNC, active);
|
||||||
|
settingsRegistry.save();
|
||||||
|
});
|
||||||
|
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () ->
|
||||||
|
guiPathing != null && guiPathing.deleteButtonPressed());
|
||||||
|
keyPositionKeyframe = core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.positionkeyframe", Keyboard.KEY_I, () -> {
|
||||||
|
if (guiPathing != null) guiPathing.toggleKeyframe(SPPath.POSITION, false);
|
||||||
|
}, true);
|
||||||
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.positiononlykeyframe", 0, () -> {
|
||||||
|
if (guiPathing != null) guiPathing.toggleKeyframe(SPPath.POSITION, true);
|
||||||
|
}, true);
|
||||||
|
keyTimeKeyframe = core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.timekeyframe", Keyboard.KEY_O, () -> {
|
||||||
|
if (guiPathing != null) guiPathing.toggleKeyframe(SPPath.TIME, false);
|
||||||
|
}, true);
|
||||||
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.bothkeyframes", 0, () -> {
|
||||||
|
if (guiPathing != null) {
|
||||||
|
guiPathing.toggleKeyframe(SPPath.TIME, false);
|
||||||
|
guiPathing.toggleKeyframe(SPPath.POSITION, false);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_Z, () -> {
|
||||||
|
if (Screen.hasControlDown() && currentTimeline != null) {
|
||||||
|
Timeline timeline = currentTimeline.getTimeline();
|
||||||
|
if (Screen.hasShiftDown()) {
|
||||||
|
if (timeline.peekRedoStack() != null) {
|
||||||
|
timeline.redoLastChange();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (timeline.peekUndoStack() != null) {
|
||||||
|
timeline.undoLastChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_Y, () -> {
|
||||||
|
if (Screen.hasControlDown() && currentTimeline != null) {
|
||||||
|
Timeline timeline = currentTimeline.getTimeline();
|
||||||
|
if (timeline.peekRedoStack() != null) {
|
||||||
|
timeline.redoLastChange();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
public final class Setting<T> extends SettingsRegistry.SettingKeys<T> {
|
public final class Setting<T> extends SettingsRegistry.SettingKeys<T> {
|
||||||
public static final Setting<Boolean> PATH_PREVIEW = make("pathpreview", "pathpreview", true);
|
public static final Setting<Boolean> PATH_PREVIEW = make("pathpreview", "pathpreview", true);
|
||||||
|
public static final Setting<Boolean> AUTO_SYNC = make("autosync", null, true);
|
||||||
|
public static final Setting<Integer> TIMELINE_LENGTH = make("timelineLength", null, 30 * 60);
|
||||||
public static final SettingsRegistry.MultipleChoiceSettingKeys<String> DEFAULT_INTERPOLATION;
|
public static final SettingsRegistry.MultipleChoiceSettingKeys<String> DEFAULT_INTERPOLATION;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -27,6 +29,6 @@ public final class Setting<T> extends SettingsRegistry.SettingKeys<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Setting(String key, String displayString, T defaultValue) {
|
public Setting(String key, String displayString, T defaultValue) {
|
||||||
super("simplepathing", key, "replaymod.gui.settings." + displayString, defaultValue);
|
super("simplepathing", key, displayString == null ? null : "replaymod.gui.settings." + displayString, defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.replaymod.pathing.properties.CameraProperties;
|
|||||||
import com.replaymod.pathing.properties.SpectatorProperty;
|
import com.replaymod.pathing.properties.SpectatorProperty;
|
||||||
import com.replaymod.pathing.properties.TimestampProperty;
|
import com.replaymod.pathing.properties.TimestampProperty;
|
||||||
import com.replaymod.replay.ReplayModReplay;
|
import com.replaymod.replay.ReplayModReplay;
|
||||||
|
import com.replaymod.replay.gui.overlay.GuiMarkerTimeline;
|
||||||
import com.replaymod.replaystudio.pathing.change.Change;
|
import com.replaymod.replaystudio.pathing.change.Change;
|
||||||
import com.replaymod.replaystudio.pathing.path.Keyframe;
|
import com.replaymod.replaystudio.pathing.path.Keyframe;
|
||||||
import com.replaymod.replaystudio.pathing.path.Path;
|
import com.replaymod.replaystudio.pathing.path.Path;
|
||||||
@@ -21,10 +22,15 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Point;
|
import de.johni0702.minecraft.gui.utils.lwjgl.Point;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static com.replaymod.core.versions.MCVer.BufferBuilder_addPosCol;
|
||||||
|
import static com.replaymod.core.versions.MCVer.BufferBuilder_beginPosCol;
|
||||||
|
import static com.replaymod.core.versions.MCVer.Tessellator_getInstance;
|
||||||
|
|
||||||
public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline> implements Draggable {
|
public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline> implements Draggable {
|
||||||
protected static final int KEYFRAME_SIZE = 5;
|
protected static final int KEYFRAME_SIZE = 5;
|
||||||
protected static final int KEYFRAME_TEXTURE_X = 74;
|
protected static final int KEYFRAME_TEXTURE_X = 74;
|
||||||
@@ -101,9 +107,93 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
|
|||||||
}
|
}
|
||||||
renderer.drawTexturedRect(positonX, BORDER_TOP, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
|
renderer.drawTexturedRect(positonX, BORDER_TOP, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
|
||||||
}
|
}
|
||||||
if (keyframe.getValue(TimestampProperty.PROPERTY).isPresent()) {
|
Optional<Integer> timeProperty = keyframe.getValue(TimestampProperty.PROPERTY);
|
||||||
|
if (timeProperty.isPresent()) {
|
||||||
v += KEYFRAME_SIZE;
|
v += KEYFRAME_SIZE;
|
||||||
renderer.drawTexturedRect(positonX, BORDER_TOP + KEYFRAME_SIZE, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
|
renderer.drawTexturedRect(positonX, BORDER_TOP + KEYFRAME_SIZE, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
|
||||||
|
|
||||||
|
GuiMarkerTimeline replayTimeline = gui.overlay.timeline;
|
||||||
|
GuiKeyframeTimeline keyframeTimeline = this;
|
||||||
|
|
||||||
|
ReadableDimension replayTimelineSize = replayTimeline.getLastSize();
|
||||||
|
ReadableDimension keyframeTimelineSize = this.getLastSize();
|
||||||
|
if (replayTimelineSize == null || keyframeTimelineSize == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine absolute positions for both timelines
|
||||||
|
Point replayTimelinePos = new Point(0, 0);
|
||||||
|
Point keyframeTimelinePos = new Point(0, 0);
|
||||||
|
replayTimeline.getContainer().convertFor(replayTimeline, replayTimelinePos);
|
||||||
|
keyframeTimeline.getContainer().convertFor(keyframeTimeline, keyframeTimelinePos);
|
||||||
|
replayTimelinePos.setLocation(-replayTimelinePos.getX(), -replayTimelinePos.getY());
|
||||||
|
keyframeTimelinePos.setLocation(-keyframeTimelinePos.getX(), -keyframeTimelinePos.getY());
|
||||||
|
|
||||||
|
int replayTimelineLeft = replayTimelinePos.getX();
|
||||||
|
int replayTimelineRight = replayTimelinePos.getX() + replayTimelineSize.getWidth();
|
||||||
|
int replayTimelineTop = replayTimelinePos.getY();
|
||||||
|
int replayTimelineBottom = replayTimelinePos.getY() + replayTimelineSize.getHeight();
|
||||||
|
int replayTimelineWidth = replayTimelineRight - replayTimelineLeft - BORDER_LEFT - BORDER_RIGHT;
|
||||||
|
|
||||||
|
int keyframeTimelineLeft = keyframeTimelinePos.getX();
|
||||||
|
int keyframeTimelineTop = keyframeTimelinePos.getY();
|
||||||
|
|
||||||
|
float positionXReplayTimeline = BORDER_LEFT + timeProperty.get() / (float) replayTimeline.getLength() * replayTimelineWidth;
|
||||||
|
float positionXKeyframeTimeline = positonX + KEYFRAME_SIZE / 2f;
|
||||||
|
|
||||||
|
final int color = 0xff0000ff;
|
||||||
|
BufferBuilder_beginPosCol(GL11.GL_LINE_STRIP);
|
||||||
|
|
||||||
|
// Start just below the top border of the replay timeline
|
||||||
|
BufferBuilder_addPosCol(
|
||||||
|
replayTimelineLeft + positionXReplayTimeline,
|
||||||
|
replayTimelineTop + BORDER_TOP,
|
||||||
|
0,
|
||||||
|
color >> 24 & 0xff,
|
||||||
|
color >> 16 & 0xff,
|
||||||
|
color >> 8 & 0xff,
|
||||||
|
color & 0xff
|
||||||
|
);
|
||||||
|
// Draw vertically over the replay timeline, including its bottom border
|
||||||
|
BufferBuilder_addPosCol(
|
||||||
|
replayTimelineLeft + positionXReplayTimeline,
|
||||||
|
replayTimelineBottom,
|
||||||
|
0,
|
||||||
|
color >> 24 & 0xff,
|
||||||
|
color >> 16 & 0xff,
|
||||||
|
color >> 8 & 0xff,
|
||||||
|
color & 0xff
|
||||||
|
);
|
||||||
|
// Now for the important part: connecting to the keyframe timeline
|
||||||
|
BufferBuilder_addPosCol(
|
||||||
|
keyframeTimelineLeft + positionXKeyframeTimeline,
|
||||||
|
keyframeTimelineTop,
|
||||||
|
0,
|
||||||
|
color >> 24 & 0xff,
|
||||||
|
color >> 16 & 0xff,
|
||||||
|
color >> 8 & 0xff,
|
||||||
|
color & 0xff
|
||||||
|
);
|
||||||
|
// And finally another vertical bit (the timeline is already crammed enough, so only the border)
|
||||||
|
BufferBuilder_addPosCol(
|
||||||
|
keyframeTimelineLeft + positionXKeyframeTimeline,
|
||||||
|
keyframeTimelineTop + BORDER_TOP,
|
||||||
|
0,
|
||||||
|
color >> 24 & 0xff,
|
||||||
|
color >> 16 & 0xff,
|
||||||
|
color >> 8 & 0xff,
|
||||||
|
color & 0xff
|
||||||
|
);
|
||||||
|
|
||||||
|
GL11.glEnable(GL11.GL_LINE_SMOOTH);
|
||||||
|
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||||
|
GL11.glPushAttrib(GL11.GL_SCISSOR_BIT);
|
||||||
|
GL11.glDisable(GL11.GL_SCISSOR_TEST);
|
||||||
|
GL11.glLineWidth(1);
|
||||||
|
Tessellator_getInstance().draw();
|
||||||
|
GL11.glPopAttrib();
|
||||||
|
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||||
|
GL11.glDisable(GL11.GL_LINE_SMOOTH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.replaymod.pathing.player.RealtimeTimelinePlayer;
|
|||||||
import com.replaymod.pathing.properties.CameraProperties;
|
import com.replaymod.pathing.properties.CameraProperties;
|
||||||
import com.replaymod.pathing.properties.SpectatorProperty;
|
import com.replaymod.pathing.properties.SpectatorProperty;
|
||||||
import com.replaymod.pathing.properties.TimestampProperty;
|
import com.replaymod.pathing.properties.TimestampProperty;
|
||||||
|
import com.replaymod.render.gui.GuiRenderQueue;
|
||||||
import com.replaymod.render.gui.GuiRenderSettings;
|
import com.replaymod.render.gui.GuiRenderSettings;
|
||||||
import com.replaymod.replay.ReplayHandler;
|
import com.replaymod.replay.ReplayHandler;
|
||||||
import com.replaymod.replay.camera.CameraEntity;
|
import com.replaymod.replay.camera.CameraEntity;
|
||||||
@@ -24,18 +25,17 @@ import com.replaymod.replaystudio.util.EntityPositionTracker;
|
|||||||
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
||||||
import com.replaymod.simplepathing.SPTimeline;
|
import com.replaymod.simplepathing.SPTimeline;
|
||||||
import com.replaymod.simplepathing.SPTimeline.SPPath;
|
import com.replaymod.simplepathing.SPTimeline.SPPath;
|
||||||
|
import com.replaymod.simplepathing.Setting;
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.RenderInfo;
|
import de.johni0702.minecraft.gui.RenderInfo;
|
||||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||||
import de.johni0702.minecraft.gui.element.AbstractGuiClickable;
|
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||||
import de.johni0702.minecraft.gui.element.AbstractGuiElement;
|
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||||
import de.johni0702.minecraft.gui.element.GuiElement;
|
import de.johni0702.minecraft.gui.element.GuiElement;
|
||||||
import de.johni0702.minecraft.gui.element.GuiHorizontalScrollbar;
|
import de.johni0702.minecraft.gui.element.GuiHorizontalScrollbar;
|
||||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||||
import de.johni0702.minecraft.gui.element.GuiTexturedButton;
|
|
||||||
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||||
import de.johni0702.minecraft.gui.element.IGuiClickable;
|
|
||||||
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||||
import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime;
|
import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
@@ -45,10 +45,10 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
|||||||
import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
|
import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
|
||||||
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
||||||
import de.johni0702.minecraft.gui.utils.Colors;
|
import de.johni0702.minecraft.gui.utils.Colors;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
|
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
|
||||||
import de.johni0702.minecraft.gui.utils.lwjgl.WritablePoint;
|
import de.johni0702.minecraft.gui.utils.lwjgl.WritablePoint;
|
||||||
|
import net.minecraft.client.resource.language.I18n;
|
||||||
import net.minecraft.util.crash.CrashReport;
|
import net.minecraft.util.crash.CrashReport;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -80,7 +80,7 @@ import static com.replaymod.simplepathing.ReplayModSimplePathing.LOGGER;
|
|||||||
public class GuiPathing {
|
public class GuiPathing {
|
||||||
private static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
|
public final GuiButton playPauseButton = new GuiButton() {
|
||||||
@Override
|
@Override
|
||||||
public GuiElement getTooltip(RenderInfo renderInfo) {
|
public GuiElement getTooltip(RenderInfo renderInfo) {
|
||||||
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
||||||
@@ -97,61 +97,61 @@ public class GuiPathing {
|
|||||||
}
|
}
|
||||||
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
|
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
|
||||||
|
|
||||||
public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() {
|
public final GuiButton renderButton = new GuiButton().onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!preparePathsForPlayback(false)) return;
|
Timeline timeline = preparePathsForPlayback(false);
|
||||||
|
if (timeline == null) return;
|
||||||
// Clone the timeline passed to the settings gui as it may be stored for later rendering in a queue
|
GuiScreen screen = GuiRenderSettings.createBaseScreen();
|
||||||
SPTimeline spTimeline = mod.getCurrentTimeline();
|
new GuiRenderQueue(screen, replayHandler, () -> preparePathsForPlayback(false)) {
|
||||||
Timeline timeline;
|
@Override
|
||||||
try {
|
protected void close() {
|
||||||
TimelineSerialization serialization = new TimelineSerialization(spTimeline, null);
|
super.close();
|
||||||
String serialized = serialization.serialize(Collections.singletonMap("", spTimeline.getTimeline()));
|
getMinecraft().openScreen(null);
|
||||||
timeline = serialization.deserialize(serialized).get("");
|
|
||||||
} catch (Throwable t) {
|
|
||||||
error(LOGGER, replayHandler.getOverlay(), CrashReport.create(t, "Cloning timeline"), () -> {});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}.open();
|
||||||
new GuiRenderSettings(replayHandler, timeline).display();
|
screen.display();
|
||||||
}
|
}
|
||||||
}).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0)
|
}).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setSpriteUV(40, 0)
|
||||||
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.renderpath"));
|
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.renderpath"));
|
||||||
|
|
||||||
public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton() {
|
public final GuiButton positionKeyframeButton = new GuiButton() {
|
||||||
@Override
|
@Override
|
||||||
public GuiElement getTooltip(RenderInfo renderInfo) {
|
public GuiElement getTooltip(RenderInfo renderInfo) {
|
||||||
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
||||||
if (tooltip != null) {
|
if (tooltip != null) {
|
||||||
if (getTextureNormal().getY() == 40) { // Add keyframe
|
String label;
|
||||||
if (getTextureNormal().getX() == 0) { // Position
|
if (getSpriteUV().getY() == 40) { // Add keyframe
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.addposkeyframe");
|
if (getSpriteUV().getX() == 0) { // Position
|
||||||
|
label = "replaymod.gui.ingame.menu.addposkeyframe";
|
||||||
} else { // Spectator
|
} else { // Spectator
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.addspeckeyframe");
|
label = "replaymod.gui.ingame.menu.addspeckeyframe";
|
||||||
}
|
}
|
||||||
} else { // Remove keyframe
|
} else { // Remove keyframe
|
||||||
if (getTextureNormal().getX() == 0) { // Position
|
if (getSpriteUV().getX() == 0) { // Position
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.removeposkeyframe");
|
label = "replaymod.gui.ingame.menu.removeposkeyframe";
|
||||||
} else { // Spectator
|
} else { // Spectator
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.removespeckeyframe");
|
label = "replaymod.gui.ingame.menu.removespeckeyframe";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tooltip.setText(I18n.translate(label) + " (" + mod.keyPositionKeyframe.getBoundKey() + ")");
|
||||||
}
|
}
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}
|
}
|
||||||
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
|
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
|
||||||
|
|
||||||
public final GuiTexturedButton timeKeyframeButton = new GuiTexturedButton() {
|
public final GuiButton timeKeyframeButton = new GuiButton() {
|
||||||
@Override
|
@Override
|
||||||
public GuiElement getTooltip(RenderInfo renderInfo) {
|
public GuiElement getTooltip(RenderInfo renderInfo) {
|
||||||
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
|
||||||
if (tooltip != null) {
|
if (tooltip != null) {
|
||||||
if (getTextureNormal().getY() == 80) { // Add time keyframe
|
String label;
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.addtimekeyframe");
|
if (getSpriteUV().getY() == 80) { // Add time keyframe
|
||||||
|
label = "replaymod.gui.ingame.menu.addtimekeyframe";
|
||||||
} else { // Remove time keyframe
|
} else { // Remove time keyframe
|
||||||
tooltip.setI18nText("replaymod.gui.ingame.menu.removetimekeyframe");
|
label = "replaymod.gui.ingame.menu.removetimekeyframe";
|
||||||
}
|
}
|
||||||
|
tooltip.setText(I18n.translate(label) + " (" + mod.keyTimeKeyframe.getBoundKey() + ")");
|
||||||
}
|
}
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}
|
}
|
||||||
@@ -161,11 +161,11 @@ public class GuiPathing {
|
|||||||
@Override
|
@Override
|
||||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||||
if (player.isActive()) {
|
if (player.isActive()) {
|
||||||
setCursorPosition((int) player.getTimePassed());
|
setCursorPosition((int) player.getTimePassed()).ensureCursorVisibleWithPadding();
|
||||||
}
|
}
|
||||||
super.draw(renderer, size, renderInfo);
|
super.draw(renderer, size, renderInfo);
|
||||||
}
|
}
|
||||||
}.setSize(Integer.MAX_VALUE, 20).setLength(30 * 60 * 1000).setMarkers();
|
}.setSize(Integer.MAX_VALUE, 20).setMarkers();
|
||||||
|
|
||||||
public final GuiHorizontalScrollbar scrollbar = new GuiHorizontalScrollbar().setSize(Integer.MAX_VALUE, 9);
|
public final GuiHorizontalScrollbar scrollbar = new GuiHorizontalScrollbar().setSize(Integer.MAX_VALUE, 9);
|
||||||
{scrollbar.onValueChanged(new Runnable() {
|
{scrollbar.onValueChanged(new Runnable() {
|
||||||
@@ -179,20 +179,20 @@ public class GuiPathing {
|
|||||||
public final GuiTimelineTime<GuiKeyframeTimeline> timelineTime = new GuiTimelineTime<GuiKeyframeTimeline>()
|
public final GuiTimelineTime<GuiKeyframeTimeline> timelineTime = new GuiTimelineTime<GuiKeyframeTimeline>()
|
||||||
.setTimeline(timeline);
|
.setTimeline(timeline);
|
||||||
|
|
||||||
public final GuiTexturedButton zoomInButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() {
|
public final GuiButton zoomInButton = new GuiButton().setSize(9, 9).onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
zoomTimeline(2d / 3d);
|
zoomTimeline(2d / 3d);
|
||||||
}
|
}
|
||||||
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20)
|
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setSpriteUV(40, 20)
|
||||||
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomin"));
|
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomin"));
|
||||||
|
|
||||||
public final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() {
|
public final GuiButton zoomOutButton = new GuiButton().setSize(9, 9).onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
zoomTimeline(3d / 2d);
|
zoomTimeline(3d / 2d);
|
||||||
}
|
}
|
||||||
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30)
|
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setSpriteUV(40, 30)
|
||||||
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomout"));
|
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomout"));
|
||||||
|
|
||||||
public final GuiPanel zoomButtonPanel = new GuiPanel()
|
public final GuiPanel zoomButtonPanel = new GuiPanel()
|
||||||
@@ -211,58 +211,17 @@ public class GuiPathing {
|
|||||||
pos(scrollbar, 0, y(timeline) + height(timeline) + 1);
|
pos(scrollbar, 0, y(timeline) + height(timeline) + 1);
|
||||||
size(scrollbar, x(zoomButtonPanel) - 2, 9);
|
size(scrollbar, x(zoomButtonPanel) - 2, 9);
|
||||||
}
|
}
|
||||||
}).addElements(null, timelineTime, timeline, scrollbar, zoomButtonPanel);
|
}).addElements(null, timeline, timelineTime, scrollbar, zoomButtonPanel);
|
||||||
|
|
||||||
public final GuiPanel panel = new GuiPanel()
|
public final GuiPanel panel = new GuiPanel()
|
||||||
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5))
|
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5))
|
||||||
.addElements(new HorizontalLayout.Data(0.5),
|
.addElements(new HorizontalLayout.Data(0.5),
|
||||||
playPauseButton, renderButton, positionKeyframeButton, timeKeyframeButton, timelinePanel);
|
playPauseButton, renderButton, positionKeyframeButton, timeKeyframeButton, timelinePanel);
|
||||||
|
|
||||||
/**
|
|
||||||
* IGuiClickable dummy component that is inserted at a high level.
|
|
||||||
* During path playback, this catches all click events and forwards them to the
|
|
||||||
* abort path playback button.
|
|
||||||
* Dragging does not have to be intercepted as every GUI element should only
|
|
||||||
* respond to dragging events after it has received and handled a click event.
|
|
||||||
*/
|
|
||||||
private final IGuiClickable clickCatcher = new AbstractGuiClickable() {
|
|
||||||
@Override
|
|
||||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
|
||||||
if (player.isActive()) {
|
|
||||||
// Make sure the mouse is always visible during path playback
|
|
||||||
// even if the game closes the overlay for some reason (e.g. world change)
|
|
||||||
replayHandler.getOverlay().setMouseVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected AbstractGuiElement getThis() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ReadableDimension calcMinSize() {
|
|
||||||
return new Dimension(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean mouseClick(ReadablePoint position, int button) {
|
|
||||||
if (player.isActive()) {
|
|
||||||
playPauseButton.mouseClick(position, button);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLayer() {
|
|
||||||
return player.isActive() ? 10 : 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ReplayMod core;
|
private final ReplayMod core;
|
||||||
private final ReplayModSimplePathing mod;
|
private final ReplayModSimplePathing mod;
|
||||||
private final ReplayHandler replayHandler;
|
private final ReplayHandler replayHandler;
|
||||||
|
public final GuiReplayOverlay overlay;
|
||||||
private final RealtimeTimelinePlayer player;
|
private final RealtimeTimelinePlayer player;
|
||||||
|
|
||||||
// Whether any error which occured during entity tracker loading has already been shown to the user
|
// Whether any error which occured during entity tracker loading has already been shown to the user
|
||||||
@@ -275,10 +234,12 @@ public class GuiPathing {
|
|||||||
this.core = core;
|
this.core = core;
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
this.replayHandler = replayHandler;
|
this.replayHandler = replayHandler;
|
||||||
|
this.overlay = replayHandler.getOverlay();
|
||||||
this.player = new RealtimeTimelinePlayer(replayHandler);
|
this.player = new RealtimeTimelinePlayer(replayHandler);
|
||||||
final GuiReplayOverlay overlay = replayHandler.getOverlay();
|
|
||||||
|
|
||||||
playPauseButton.setTexturePosH(new ReadablePoint() {
|
timeline.setLength(core.getSettingsRegistry().get(Setting.TIMELINE_LENGTH) * 1000);
|
||||||
|
|
||||||
|
playPauseButton.setSpriteUV(new ReadablePoint() {
|
||||||
@Override
|
@Override
|
||||||
public int getX() {
|
public int getX() {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -300,14 +261,16 @@ public class GuiPathing {
|
|||||||
player.getFuture().cancel(false);
|
player.getFuture().cancel(false);
|
||||||
} else {
|
} else {
|
||||||
boolean ignoreTimeKeyframes = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);
|
boolean ignoreTimeKeyframes = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);
|
||||||
Path timePath = mod.getCurrentTimeline().getTimePath();
|
|
||||||
|
|
||||||
if (!preparePathsForPlayback(ignoreTimeKeyframes)) return;
|
Timeline timeline = preparePathsForPlayback(ignoreTimeKeyframes);
|
||||||
|
if (timeline == null) return;
|
||||||
|
|
||||||
|
Path timePath = new SPTimeline(timeline).getTimePath();
|
||||||
timePath.setActive(!ignoreTimeKeyframes);
|
timePath.setActive(!ignoreTimeKeyframes);
|
||||||
|
|
||||||
// Start from cursor time unless the control key is pressed (then start from beginning)
|
// Start from cursor time unless the control key is pressed (then start from beginning)
|
||||||
int startTime = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)? 0 : GuiPathing.this.timeline.getCursorPosition();
|
int startTime = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)? 0 : GuiPathing.this.timeline.getCursorPosition();
|
||||||
ListenableFuture<Void> future = player.start(mod.getCurrentTimeline().getTimeline(), startTime);
|
ListenableFuture<Void> future = player.start(timeline, startTime);
|
||||||
overlay.setCloseable(false);
|
overlay.setCloseable(false);
|
||||||
overlay.setMouseVisible(true);
|
overlay.setMouseVisible(true);
|
||||||
core.printInfoToChat("replaymod.chat.pathstarted");
|
core.printInfoToChat("replaymod.chat.pathstarted");
|
||||||
@@ -320,21 +283,19 @@ public class GuiPathing {
|
|||||||
core.printInfoToChat("replaymod.chat.pathfinished");
|
core.printInfoToChat("replaymod.chat.pathfinished");
|
||||||
}
|
}
|
||||||
overlay.setCloseable(true);
|
overlay.setCloseable(true);
|
||||||
timePath.setActive(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Throwable t) {
|
public void onFailure(Throwable t) {
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
overlay.setCloseable(true);
|
overlay.setCloseable(true);
|
||||||
timePath.setActive(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
positionKeyframeButton.setTexturePosH(new ReadablePoint() {
|
positionKeyframeButton.setSpriteUV(new ReadablePoint() {
|
||||||
@Override
|
@Override
|
||||||
public int getX() {
|
public int getX() {
|
||||||
SPPath keyframePath = mod.getSelectedPath();
|
SPPath keyframePath = mod.getSelectedPath();
|
||||||
@@ -368,11 +329,11 @@ public class GuiPathing {
|
|||||||
}).onClick(new Runnable() {
|
}).onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
updateKeyframe(SPPath.POSITION);
|
toggleKeyframe(SPPath.POSITION, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
timeKeyframeButton.setTexturePosH(new ReadablePoint() {
|
timeKeyframeButton.setSpriteUV(new ReadablePoint() {
|
||||||
@Override
|
@Override
|
||||||
public int getX() {
|
public int getX() {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -395,17 +356,17 @@ public class GuiPathing {
|
|||||||
}).onClick(new Runnable() {
|
}).onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
updateKeyframe(SPPath.TIME);
|
toggleKeyframe(SPPath.TIME, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
overlay.addElements(null, panel, clickCatcher);
|
overlay.addElements(null, panel);
|
||||||
overlay.setLayout(new CustomLayout<GuiReplayOverlay>(overlay.getLayout()) {
|
overlay.setLayout(new CustomLayout<GuiReplayOverlay>(overlay.getLayout()) {
|
||||||
@Override
|
@Override
|
||||||
protected void layout(GuiReplayOverlay container, int width, int height) {
|
protected void layout(GuiReplayOverlay container, int width, int height) {
|
||||||
|
checkForAutoSync();
|
||||||
pos(panel, 10, y(overlay.topPanel) + height(overlay.topPanel) + 3);
|
pos(panel, 10, y(overlay.topPanel) + height(overlay.topPanel) + 3);
|
||||||
size(panel, width - 20, 40);
|
size(panel, width - 20, 40);
|
||||||
size(clickCatcher, 0, 0);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -459,6 +420,28 @@ public class GuiPathing {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int prevSpeed = -1;
|
||||||
|
private int prevTime = -1;
|
||||||
|
private void checkForAutoSync() {
|
||||||
|
if (!mod.keySyncTime.isAutoActivating()) {
|
||||||
|
prevSpeed = -1;
|
||||||
|
prevTime = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int speed = overlay.speedSlider.getValue();
|
||||||
|
if (prevSpeed != speed && prevSpeed != -1) {
|
||||||
|
syncTimeButtonPressed();
|
||||||
|
}
|
||||||
|
prevSpeed = speed;
|
||||||
|
|
||||||
|
int time = replayHandler.getReplaySender().currentTimeStamp();
|
||||||
|
if (prevTime != time && prevTime != -1 && !player.isActive()) {
|
||||||
|
syncTimeButtonPressed();
|
||||||
|
}
|
||||||
|
prevTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
public void syncTimeButtonPressed() {
|
public void syncTimeButtonPressed() {
|
||||||
// Current replay time
|
// Current replay time
|
||||||
int time = replayHandler.getReplaySender().currentTimeStamp();
|
int time = replayHandler.getReplaySender().currentTimeStamp();
|
||||||
@@ -479,16 +462,18 @@ public class GuiPathing {
|
|||||||
// Cursor time passed
|
// Cursor time passed
|
||||||
int cursorPassed = (int) (timePassed / speed);
|
int cursorPassed = (int) (timePassed / speed);
|
||||||
// Move cursor to new position
|
// Move cursor to new position
|
||||||
timeline.setCursorPosition(keyframeCursor + cursorPassed);
|
timeline.setCursorPosition(keyframeCursor + cursorPassed).ensureCursorVisibleWithPadding();
|
||||||
// Deselect keyframe to allow the user to add a new one right away
|
// Deselect keyframe to allow the user to add a new one right away
|
||||||
mod.setSelected(null, 0);
|
mod.setSelected(null, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteButtonPressed() {
|
public boolean deleteButtonPressed() {
|
||||||
if (mod.getSelectedPath() != null) {
|
if (mod.getSelectedPath() != null) {
|
||||||
updateKeyframe(mod.getSelectedPath());
|
toggleKeyframe(mod.getSelectedPath(), false);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startLoadingEntityTracker() {
|
private void startLoadingEntityTracker() {
|
||||||
@@ -520,8 +505,26 @@ public class GuiPathing {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean preparePathsForPlayback(boolean ignoreTimeKeyframes) {
|
private Timeline preparePathsForPlayback(boolean ignoreTimeKeyframes) {
|
||||||
SPTimeline timeline = mod.getCurrentTimeline();
|
SPTimeline spTimeline = mod.getCurrentTimeline();
|
||||||
|
|
||||||
|
if (!validatePathsForPlayback(spTimeline, ignoreTimeKeyframes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TimelineSerialization serialization = new TimelineSerialization(spTimeline, null);
|
||||||
|
String serialized = serialization.serialize(Collections.singletonMap("", spTimeline.getTimeline()));
|
||||||
|
Timeline timeline = serialization.deserialize(serialized).get("");
|
||||||
|
timeline.getPaths().forEach(Path::updateAll);
|
||||||
|
return timeline;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
error(LOGGER, replayHandler.getOverlay(), CrashReport.create(t, "Cloning timeline"), () -> {});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validatePathsForPlayback(SPTimeline timeline, boolean ignoreTimeKeyframes) {
|
||||||
timeline.getTimeline().getPaths().forEach(Path::updateAll);
|
timeline.getTimeline().getPaths().forEach(Path::updateAll);
|
||||||
|
|
||||||
// Make sure there are at least two position keyframes
|
// Make sure there are at least two position keyframes
|
||||||
@@ -603,14 +606,22 @@ public class GuiPathing {
|
|||||||
/**
|
/**
|
||||||
* Called when either one of the property buttons is pressed.
|
* Called when either one of the property buttons is pressed.
|
||||||
* @param path {@code TIME} for the time property button, {@code POSITION} for the place property button
|
* @param path {@code TIME} for the time property button, {@code POSITION} for the place property button
|
||||||
|
* @param neverSpectator when true, will insert a position keyframe even when currently spectating an entity
|
||||||
*/
|
*/
|
||||||
private void updateKeyframe(SPPath path) {
|
public void toggleKeyframe(SPPath path, boolean neverSpectator) {
|
||||||
LOGGER.debug("Updating keyframe on path {}" + path);
|
LOGGER.debug("Updating keyframe on path {}" + path);
|
||||||
if (!loadEntityTracker(() -> updateKeyframe(path))) return;
|
if (!loadEntityTracker(() -> toggleKeyframe(path, neverSpectator))) return;
|
||||||
|
|
||||||
int time = timeline.getCursorPosition();
|
int time = timeline.getCursorPosition();
|
||||||
SPTimeline timeline = mod.getCurrentTimeline();
|
SPTimeline timeline = mod.getCurrentTimeline();
|
||||||
|
|
||||||
|
if (timeline.getPositionPath().getKeyframes().isEmpty() &&
|
||||||
|
timeline.getTimePath().getKeyframes().isEmpty() &&
|
||||||
|
time > 1000) {
|
||||||
|
String text = I18n.translate("replaymod.gui.ingame.first_keyframe_not_at_start_warning");
|
||||||
|
GuiInfoPopup.open(overlay, text.split("\\\\n"));
|
||||||
|
}
|
||||||
|
|
||||||
switch (path) {
|
switch (path) {
|
||||||
case TIME:
|
case TIME:
|
||||||
if (mod.getSelectedPath() == path) {
|
if (mod.getSelectedPath() == path) {
|
||||||
@@ -640,7 +651,7 @@ public class GuiPathing {
|
|||||||
LOGGER.debug("No position keyframe found -> adding new keyframe");
|
LOGGER.debug("No position keyframe found -> adding new keyframe");
|
||||||
CameraEntity camera = replayHandler.getCameraEntity();
|
CameraEntity camera = replayHandler.getCameraEntity();
|
||||||
int spectatedId = -1;
|
int spectatedId = -1;
|
||||||
if (!replayHandler.isCameraView()) {
|
if (!replayHandler.isCameraView() && !neverSpectator) {
|
||||||
spectatedId = getRenderViewEntity(replayHandler.getOverlay().getMinecraft()).getEntityId();
|
spectatedId = getRenderViewEntity(replayHandler.getOverlay().getMinecraft()).getEntityId();
|
||||||
}
|
}
|
||||||
timeline.addPositionKeyframe(time, Entity_getX(camera), Entity_getY(camera), Entity_getZ(camera),
|
timeline.addPositionKeyframe(time, Entity_getX(camera), Entity_getY(camera), Entity_getZ(camera),
|
||||||
|
|||||||
Submodule src/main/resources/assets/replaymod/lang updated: 8936798bea...b45b1a1f9a
33
src/main/resources/assets/replaymod/replay_gui.license
Normal file
33
src/main/resources/assets/replaymod/replay_gui.license
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
The replay_gui.png file as a whole is governed by the same terms as the entire repository.
|
||||||
|
Some of its individual icons were originally from other projects and under different licenses.
|
||||||
|
This file serves to credit the original author(s) and to comply with the terms of the original license (some parts of
|
||||||
|
which may still apply to the derived replay_gui.png file).
|
||||||
|
|
||||||
|
In particular:
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
The "Settings" icon (three horizontal sliders) is based on ModMenu's settings icon.
|
||||||
|
Original: https://github.com/Prospector/ModMenu/blob/74200fb7baa697b4e865b15a707a560757a7ee8f/src/main/resources/assets/modmenu/textures/gui/configure_button.png
|
||||||
|
|
||||||
|
Copyright (c) 2018-2020 Prospector
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 24 KiB |
@@ -12,12 +12,12 @@
|
|||||||
"Mixin_RegisterDynamicResourcePacks",
|
"Mixin_RegisterDynamicResourcePacks",
|
||||||
//#endif
|
//#endif
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
"MixinKeyboardListener",
|
|
||||||
"MixinMouse",
|
"MixinMouse",
|
||||||
//#endif
|
//#endif
|
||||||
//#if MC<10800
|
//#if MC<10800
|
||||||
//$$ "ResourcePackRepositoryAccessor",
|
//$$ "ResourcePackRepositoryAccessor",
|
||||||
//#endif
|
//#endif
|
||||||
|
"MixinKeyboardListener",
|
||||||
"MixinMinecraft",
|
"MixinMinecraft",
|
||||||
"GuiMainMenuAccessor",
|
"GuiMainMenuAccessor",
|
||||||
"GuiScreenAccessor",
|
"GuiScreenAccessor",
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
"mixins": [],
|
"mixins": [],
|
||||||
"server": [],
|
"server": [],
|
||||||
"client": [
|
"client": [
|
||||||
|
"AddServerScreenAccessor",
|
||||||
"EntityLivingBaseAccessor",
|
"EntityLivingBaseAccessor",
|
||||||
"IntegratedServerAccessor",
|
"IntegratedServerAccessor",
|
||||||
"NetworkManagerAccessor",
|
"NetworkManagerAccessor",
|
||||||
"SPacketSpawnMobAccessor",
|
"SPacketSpawnMobAccessor",
|
||||||
"SPacketSpawnPlayerAccessor",
|
"SPacketSpawnPlayerAccessor",
|
||||||
|
"MixinServerInfo",
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
"MixinDownloadingPackFinder",
|
"MixinDownloadingPackFinder",
|
||||||
"MixinMouseHelper",
|
"MixinMouseHelper",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"Mixin_ForceChunkLoading",
|
"Mixin_ForceChunkLoading",
|
||||||
//#endif
|
//#endif
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
|
"Mixin_PreserveDepthDuringHandRendering",
|
||||||
"MainWindowAccessor",
|
"MainWindowAccessor",
|
||||||
//#endif
|
//#endif
|
||||||
"WorldRendererAccessor",
|
"WorldRendererAccessor",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.4.5
|
2.5.0
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
net.minecraft.potion.EffectInstance net.minecraft.potion.PotionEffect
|
||||||
|
net.minecraft.client.gui.screen.AddServerScreen net.minecraft.client.gui.GuiScreenAddServer
|
||||||
net.minecraft.resources.AbstractResourcePack net.minecraft.client.resources.AbstractResourcePack
|
net.minecraft.resources.AbstractResourcePack net.minecraft.client.resources.AbstractResourcePack
|
||||||
net.minecraft.resources.FolderPack net.minecraft.client.resources.FolderResourcePack
|
net.minecraft.resources.FolderPack net.minecraft.client.resources.FolderResourcePack
|
||||||
net.minecraft.client.resources.DownloadingPackFinder net.minecraft.client.resources.ResourcePackRepository
|
net.minecraft.client.resources.DownloadingPackFinder net.minecraft.client.resources.ResourcePackRepository
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user