Merge branch '1.11' into 1.11.2
2a47f57Merge branch '1.10.2' into 1.1196b2904Merge branch '1.9.4' into 1.10.26c8c779Merge branch '1.8.9' into 1.9.4aa16e42Add workaround for setupCIWorkspace not being sufficiented56673Merge branch '1.8' into 1.8.991573d9Update jGui, ReplayStudio and Translationsd71358bAdd confirmation dialog before overwriting path presets (fixes #65)1a55983Move addCallback method from integration test Utils to main Utils18c5bcdBe more cooperative with other mods on the ingame menu (fixes #42)c054fe8Register keys for simplepathing only once (fixes #63)f13297cFix default interpolator handling in keyframe gui and after loading (fixes #64)391f304Show error popup if entity tracker fails to load0e0eaaaFix entity tracker being set even if it failed loading (fixes #50)a34bbbcFix NPE when spectating a player without a camera entity60879fbChange local class to be an inner class because Srg2Source breaks with itdfafbecLoad translations from github repo, only reload language not all resource packs8a6ec51Add missing tooltips to player overview and various missing chat messages5677fc5Re-add warning to replay editor748a91eFix missing tooltips on fav/like/dislike buttons in replay centeree24866Fix upload replay button not being updated when name/tags changedba085cMove translations into separate repo3f0e3e7Fix NPE when receiving Replay|Restrict messages (fixes #16 GH)40e1d85Fix crash when saving the last keyframe in the edit gui (fixes #61)03aada1Update Mixin to 0.6.8 (fixes #9 GH)0c1dc65Fix interpolator being lost when moving keyframe to the end (fixes #62)fcbbbc9Add integration test. Run with ./gradlew runIntegrationTestfe6ded0Fix movement of keyframe via GuiEditKeyframe not updating selected keyframef58fa8fFix Login GUI not respecting other GUIs opened on startup4fc3a31Fix OpenEye being installed into working dir instead of mcDataDir
This commit is contained in:
@@ -7,7 +7,6 @@ pipeline:
|
||||
commands:
|
||||
- curl --fail --create-dirs -o $HOME/.gradle/init.d/mirror.gradle https://maven.johni0702.de/config/gradle
|
||||
- curl --fail --create-dirs -o $HOME/.m2/settings.xml https://maven.johni0702.de/config/maven
|
||||
- JAVA_OPTS=-Xmx1516M ./gradlew setupDecompWorkspace
|
||||
- ./gradlew :build
|
||||
archive:
|
||||
image: plugins/s3
|
||||
|
||||
@@ -1 +1 @@
|
||||
eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgY2xvbmU6CiAgICBpbWFnZTogcGx1Z2lucy9naXQKICAgIHRhZ3M6IHRydWUKICBidWlsZDoKICAgIGltYWdlOiBtYXZlbjozLjMuOS1qZGstOAogICAgY29tbWFuZHM6CiAgICAgIC0gY3VybCAtLWZhaWwgLS1jcmVhdGUtZGlycyAtbyAkSE9NRS8uZ3JhZGxlL2luaXQuZC9taXJyb3IuZ3JhZGxlIGh0dHBzOi8vbWF2ZW4uam9obmkwNzAyLmRlL2NvbmZpZy9ncmFkbGUKICAgICAgLSBjdXJsIC0tZmFpbCAtLWNyZWF0ZS1kaXJzIC1vICRIT01FLy5tMi9zZXR0aW5ncy54bWwgaHR0cHM6Ly9tYXZlbi5qb2huaTA3MDIuZGUvY29uZmlnL21hdmVuCiAgICAgIC0gSkFWQV9PUFRTPS1YbXgxNTE2TSAuL2dyYWRsZXcgc2V0dXBEZWNvbXBXb3Jrc3BhY2UKICAgICAgLSAuL2dyYWRsZXcgOmJ1aWxkCiAgYXJjaGl2ZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBhY2w6IHB1YmxpYy1yZWFkCiAgICByZWdpb246ICJ1cy1lYXN0LTEiCiAgICBidWNrZXQ6ICJyZXBsYXltb2QiCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzb3VyY2U6IGJ1aWxkL2xpYnMvKgogICAgc3RyaXBfcHJlZml4OiBidWlsZC9saWJzLwogICAgdGFyZ2V0OiAvCg.nzPW0_TpMIjnFZZ5Ci439QDKlaDo1a3DCH-VCYUgpx8
|
||||
eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgY2xvbmU6CiAgICBpbWFnZTogcGx1Z2lucy9naXQKICAgIHRhZ3M6IHRydWUKICBidWlsZDoKICAgIGltYWdlOiBtYXZlbjozLjMuOS1qZGstOAogICAgY29tbWFuZHM6CiAgICAgIC0gY3VybCAtLWZhaWwgLS1jcmVhdGUtZGlycyAtbyAkSE9NRS8uZ3JhZGxlL2luaXQuZC9taXJyb3IuZ3JhZGxlIGh0dHBzOi8vbWF2ZW4uam9obmkwNzAyLmRlL2NvbmZpZy9ncmFkbGUKICAgICAgLSBjdXJsIC0tZmFpbCAtLWNyZWF0ZS1kaXJzIC1vICRIT01FLy5tMi9zZXR0aW5ncy54bWwgaHR0cHM6Ly9tYXZlbi5qb2huaTA3MDIuZGUvY29uZmlnL21hdmVuCiAgICAgIC0gLi9ncmFkbGV3IDpidWlsZAogIGFyY2hpdmU6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgYWNsOiBwdWJsaWMtcmVhZAogICAgcmVnaW9uOiAidXMtZWFzdC0xIgogICAgYnVja2V0OiAicmVwbGF5bW9kIgogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc291cmNlOiBidWlsZC9saWJzLyoKICAgIHN0cmlwX3ByZWZpeDogYnVpbGQvbGlicy8KICAgIHRhcmdldDogLwo.Z6rpaLplJKQ4pzB8AjCnplgci42qEu7L4LeS0fGm4SY
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -5,3 +5,7 @@
|
||||
[submodule "ReplayStudio"]
|
||||
path = ReplayStudio
|
||||
url = https://github.com/ReplayMod/ReplayStudio
|
||||
|
||||
[submodule "src/main/resources/assets/replaymod/lang"]
|
||||
path = src/main/resources/assets/replaymod/lang
|
||||
url = https://github.com/ReplayMod/Translations
|
||||
|
||||
Submodule ReplayStudio updated: f778f556a7...d066d7e210
107
build.gradle
107
build.gradle
@@ -1,7 +1,5 @@
|
||||
import groovy.json.JsonOutput
|
||||
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -61,7 +59,7 @@ configurations {
|
||||
dependencies {
|
||||
compile 'org.projectlombok:lombok:1.16.4'
|
||||
|
||||
compile 'org.spongepowered:mixin:0.6.4-SNAPSHOT'
|
||||
compile 'org.spongepowered:mixin:0.6.8-SNAPSHOT'
|
||||
shade 'com.googlecode.mp4parser:isoparser:1.1.7'
|
||||
shade 'org.apache.commons:commons-exec:1.3'
|
||||
shade 'com.google.apis:google-api-services-youtube:v3-rev178-1.22.0'
|
||||
@@ -103,27 +101,6 @@ jar {
|
||||
}
|
||||
from noticeDir
|
||||
|
||||
def langDir = file("$buildDir/languages")
|
||||
doFirst {
|
||||
try {
|
||||
langDir.deleteDir()
|
||||
langDir.mkdirs()
|
||||
def dir = new File(langDir, 'assets/replaymod/lang/')
|
||||
dir.mkdirs()
|
||||
def zip = new ZipInputStream(new URL('http://replaymod.com/api/grab_languages').openStream())
|
||||
def e;
|
||||
while ((e = zip.nextEntry) != null) {
|
||||
new File(dir, e.getName()) << zip
|
||||
zip.closeEntry()
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
from (langDir) {
|
||||
exclude '**/en_US.lang'
|
||||
}
|
||||
|
||||
from ({shade().collect { it.isDirectory() ? it : zipTree(it) }}) {
|
||||
exclude '**/NOTICE*'
|
||||
// exclude everything taken in from jGui for running the mod in a dev environment
|
||||
@@ -189,6 +166,13 @@ sourceSets {
|
||||
}
|
||||
refMap = "mixins.replaymod.refmap.json"
|
||||
}
|
||||
integrationTest {
|
||||
compileClasspath += main.runtimeClasspath + main.output
|
||||
java {
|
||||
srcDir file('src/integration-test/java')
|
||||
}
|
||||
resources.srcDir file('src/integration-test/resources')
|
||||
}
|
||||
}
|
||||
|
||||
task copySrg(type: Copy, dependsOn: 'genSrgs') {
|
||||
@@ -200,6 +184,81 @@ setupDecompWorkspace.dependsOn copySrg
|
||||
setupDevWorkspace.dependsOn copySrg
|
||||
project.tasks.idea.dependsOn copySrg
|
||||
|
||||
|
||||
import org.objectweb.asm.*
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
import static org.objectweb.asm.Opcodes.ASM5
|
||||
|
||||
// MC binaries were complied with a java version that produces invalid class files under certain circumstances
|
||||
// This causes setupCIWorkspace to be insufficient for compiling.
|
||||
// Related JDK bug: https://bugs.openjdk.java.net/browse/JDK-8066725
|
||||
// As a workaround, to use setupCIWorkspace on Drone, we modify the bin jar in-place and remove all parameter annotations.
|
||||
// WARNING: This piece of code ignores any and all gradle conventions and will probably fail horribly when run outside
|
||||
// of a single-use environment (e.g. Drone). Use setupDecompWorkspace for normal use.
|
||||
tasks.deobfMcMCP.doLast {
|
||||
println "Applying RuntimeInvisibleParameterAnnotations workaround..."
|
||||
File jar = getOutJar()
|
||||
File tmp = new File((File) getTemporaryDir(), "workaround.jar")
|
||||
tmp.withOutputStream {
|
||||
new ZipOutputStream(it).withStream { dst ->
|
||||
new ZipFile(jar).withCloseable { src ->
|
||||
src.entries().each {
|
||||
if (it.name.startsWith("net/minecraft/") && it.name.endsWith(".class")) {
|
||||
def cw = new ClassWriter(0)
|
||||
def cv = new ClassVisitor(ASM5, cw) {
|
||||
@Override
|
||||
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
return new MethodVisitor(ASM5, cv.visitMethod(access, name, desc, signature, exceptions)) {
|
||||
@Override
|
||||
AnnotationVisitor visitParameterAnnotation(int parameter, String pdesc, boolean visible) {
|
||||
return null // Strip all parameter annotations
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
new ClassReader(src.getInputStream(it)).accept(cv, 0)
|
||||
dst.putNextEntry(new ZipEntry(it.name))
|
||||
dst.write(cw.toByteArray())
|
||||
} else {
|
||||
dst.putNextEntry(it)
|
||||
dst.write(src.getInputStream(it).bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jar.delete()
|
||||
tmp.renameTo(jar)
|
||||
}
|
||||
|
||||
task runIntegrationTest(type: JavaExec, dependsOn: ["makeStart", "jar"]) {
|
||||
main = 'GradleStart'
|
||||
standardOutput = System.out
|
||||
errorOutput = System.err
|
||||
workingDir file(minecraft.runDir)
|
||||
|
||||
def testDir = new File(minecraft.runDir, "integration-test")
|
||||
doFirst {
|
||||
testDir.deleteDir()
|
||||
testDir.mkdirs()
|
||||
}
|
||||
|
||||
doLast {
|
||||
testDir.deleteDir()
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
def runClient = tasks.getByName("runClient")
|
||||
runIntegrationTest.jvmArgs = runClient.jvmArgs + "-Dfml.noGrab=true"
|
||||
runIntegrationTest.args = runClient.args + "--gameDir" + testDir.canonicalPath
|
||||
runIntegrationTest.classpath runClient.classpath + sourceSets.integrationTest.output
|
||||
}
|
||||
}
|
||||
|
||||
def generateVersionsJson() {
|
||||
// List all tags
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
|
||||
2
jGui
2
jGui
Submodule jGui updated: 97566db8ed...bd3fa93d7d
30
src/integration-test/java/IntegrationTest.java
Normal file
30
src/integration-test/java/IntegrationTest.java
Normal file
@@ -0,0 +1,30 @@
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entry point for running integration tests from within the IDE.
|
||||
* This is <b>not</b> called when running integration tests directly from gradle.
|
||||
*/
|
||||
public class IntegrationTest {
|
||||
public static void main(String[] args) throws Throwable {
|
||||
// Prevent MC from grabbing our mouse during the test (it's not needed)
|
||||
System.setProperty("fml.noGrab", "true");
|
||||
|
||||
// Make sure the test folder exists and is fresh
|
||||
File gameDir = new File(System.getProperty("user.dir"), "integration-test");
|
||||
if (gameDir.exists()) {
|
||||
FileUtils.forceDelete(gameDir);
|
||||
}
|
||||
FileUtils.forceMkdir(gameDir);
|
||||
|
||||
// Set game dir to test folder and call regular entry point
|
||||
List<String> argsList = new ArrayList<>(Arrays.asList(args));
|
||||
argsList.add("--gameDir");
|
||||
argsList.add(gameDir.getCanonicalPath());
|
||||
GradleStart.main(argsList.toArray(new String[argsList.size()]));
|
||||
}
|
||||
}
|
||||
340
src/integration-test/java/com/replaymod/core/AbstractTask.java
Normal file
340
src/integration-test/java/com/replaymod/core/AbstractTask.java
Normal file
@@ -0,0 +1,340 @@
|
||||
package com.replaymod.core;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import de.johni0702.minecraft.gui.container.AbstractGuiOverlay;
|
||||
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
|
||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||
import de.johni0702.minecraft.gui.container.GuiOverlay;
|
||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||
import de.johni0702.minecraft.gui.element.GuiElement;
|
||||
import de.johni0702.minecraft.gui.element.GuiTexturedButton;
|
||||
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
||||
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.settings.KeyBinding;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static com.replaymod.core.ReplayModIntegrationTest.LOGGER;
|
||||
import static com.replaymod.core.utils.Utils.addCallback;
|
||||
|
||||
public abstract class AbstractTask implements Task {
|
||||
public static Task create(Consumer<AbstractTask> init) {
|
||||
return new AbstractTask() {
|
||||
@Override
|
||||
protected void init() {
|
||||
init.consume(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final Minecraft mc = Minecraft.getMinecraft();
|
||||
public final ReplayMod core = ReplayMod.instance;
|
||||
public SettableFuture<Void> future;
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Void> execute() {
|
||||
future = SettableFuture.create();
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
addCallback(future, success -> {
|
||||
MinecraftForge.EVENT_BUS.unregister(this);
|
||||
}, error -> {});
|
||||
|
||||
init();
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
protected void init() {}
|
||||
|
||||
protected void runLater(Runnable runnable) {
|
||||
core.runLater(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable t) {
|
||||
future.setException(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void expectGuiClosed(Runnable onClosed) {
|
||||
expectGuiClosed0(10, onClosed);
|
||||
}
|
||||
|
||||
public void expectGuiClosed(int timeout, Runnable onClosed) {
|
||||
expectGuiClosed0(timeout, onClosed);
|
||||
}
|
||||
|
||||
private void expectGuiClosed0(int timeout, Runnable onClosed) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
class EventHandler {
|
||||
final net.minecraft.client.gui.GuiScreen currentScreen = mc.currentScreen;
|
||||
int framesPassed;
|
||||
|
||||
@SubscribeEvent
|
||||
public void onGuiOpen(TickEvent.RenderTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.START) return;
|
||||
if (currentScreen != mc.currentScreen) {
|
||||
MinecraftForge.EVENT_BUS.unregister(this);
|
||||
onClosed.run();
|
||||
} else {
|
||||
if (framesPassed < timeout) {
|
||||
framesPassed++;
|
||||
} else {
|
||||
Object gui = (gui = GuiScreen.from(currentScreen)) == null ? currentScreen : gui;
|
||||
Exception e = new TimeoutException("Timeout while waiting for " + gui + " to be closed.");
|
||||
e.setStackTrace(Arrays.copyOfRange(stackTrace, 3, stackTrace.length));
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
}
|
||||
|
||||
public void expectPopupClosed(Runnable onClosed) {
|
||||
expectPopupClosed0(10, onClosed);
|
||||
}
|
||||
|
||||
public void expectPopupClosed(int timeout, Runnable onClosed) {
|
||||
expectPopupClosed0(timeout, onClosed);
|
||||
}
|
||||
|
||||
private void expectPopupClosed0(int timeout, Runnable onClosed) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
AbstractGuiPopup popup = getPopup(mc.currentScreen);
|
||||
if (popup == null) {
|
||||
throw new IllegalStateException("No popup found.");
|
||||
}
|
||||
class EventHandler {
|
||||
int framesPassed;
|
||||
|
||||
@SubscribeEvent
|
||||
public void onGuiOpen(TickEvent.RenderTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.START) return;
|
||||
if (getPopup(mc.currentScreen) != popup) {
|
||||
MinecraftForge.EVENT_BUS.unregister(this);
|
||||
onClosed.run();
|
||||
} else {
|
||||
if (framesPassed < timeout) {
|
||||
framesPassed++;
|
||||
} else {
|
||||
Exception e = new TimeoutException("Timeout while waiting for " + popup + " to be closed.");
|
||||
e.setStackTrace(Arrays.copyOfRange(stackTrace, 3, stackTrace.length));
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
}
|
||||
|
||||
private AbstractGuiPopup getPopup(net.minecraft.client.gui.GuiScreen minecraft) {
|
||||
GuiContainer<?> container = GuiOverlay.from(minecraft);
|
||||
if (container == null) {
|
||||
container = GuiScreen.from(minecraft);
|
||||
}
|
||||
if (container != null) {
|
||||
while (container.getContainer() != null) {
|
||||
container = container.getContainer();
|
||||
}
|
||||
GuiElement popup = Iterables.getLast(container.getChildren());
|
||||
if (popup instanceof AbstractGuiPopup) {
|
||||
return (AbstractGuiPopup) popup;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> void expectGui(Class<T> guiClass, Consumer<T> onOpen) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
class EventHandler {
|
||||
net.minecraft.client.gui.GuiScreen currentScreen;
|
||||
int framesPassed;
|
||||
|
||||
@SubscribeEvent
|
||||
public void onGuiOpen(TickEvent.RenderTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.START) return;
|
||||
if (currentScreen != mc.currentScreen) {
|
||||
currentScreen = mc.currentScreen;
|
||||
framesPassed = 0;
|
||||
}
|
||||
if (framesPassed < 10) {
|
||||
framesPassed++;
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftForge.EVENT_BUS.unregister(this);
|
||||
|
||||
Object foundGui = null;
|
||||
if (AbstractGuiScreen.class.isAssignableFrom(guiClass)) {
|
||||
AbstractGuiScreen guiScreen = GuiScreen.from(currentScreen);
|
||||
if (guiClass.isInstance(guiScreen)) {
|
||||
onOpen.consume(guiClass.cast(guiScreen));
|
||||
return;
|
||||
}
|
||||
foundGui = guiScreen;
|
||||
} else if (AbstractGuiOverlay.class.isAssignableFrom(guiClass)) {
|
||||
AbstractGuiOverlay guiScreen = GuiOverlay.from(currentScreen);
|
||||
if (guiClass.isInstance(guiScreen)) {
|
||||
onOpen.consume(guiClass.cast(guiScreen));
|
||||
return;
|
||||
}
|
||||
foundGui = guiScreen;
|
||||
} else if (AbstractGuiPopup.class.isAssignableFrom(guiClass)) {
|
||||
AbstractGuiPopup popup = getPopup(currentScreen);
|
||||
if (guiClass.isInstance(popup)) {
|
||||
onOpen.consume(guiClass.cast(popup));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (guiClass.isInstance(currentScreen)) {
|
||||
onOpen.consume(guiClass.cast(currentScreen));
|
||||
return;
|
||||
}
|
||||
}
|
||||
class UnexpectedGuiException extends Exception {
|
||||
UnexpectedGuiException(Object foundGui) {
|
||||
super("Expected instance of " + guiClass + " but found " + foundGui);
|
||||
setStackTrace(Arrays.copyOfRange(stackTrace, 2, stackTrace.length));
|
||||
}
|
||||
}
|
||||
future.setException(new UnexpectedGuiException(foundGui == null ? currentScreen : foundGui));
|
||||
}
|
||||
}
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
}
|
||||
|
||||
private void clickNow(int x, int y) {
|
||||
try {
|
||||
Method method = net.minecraft.client.gui.GuiScreen.class
|
||||
.getDeclaredMethod("mouseClicked", int.class, int.class, int.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(mc.currentScreen, x, y, 0);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void click(int x, int y) {
|
||||
runLater(() -> {
|
||||
LOGGER.info("Clicking at {}/{}", x, y);
|
||||
clickNow(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
private void dragNow(int x, int y) {
|
||||
try {
|
||||
Method method = net.minecraft.client.gui.GuiScreen.class
|
||||
.getDeclaredMethod("mouseClickMove", int.class, int.class, int.class, long.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(mc.currentScreen, x, y, 0, 0);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void drag(int x, int y) {
|
||||
runLater(() -> {
|
||||
LOGGER.info("Dragging to {}/{}", x, y);
|
||||
dragNow(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
public void click(GuiButton button) {
|
||||
runLater(() -> {
|
||||
if (!button.isEnabled()) {
|
||||
future.setException(new IllegalStateException("Button is disabled: " + button.getLabel()));
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Clicking button {}", button.getLabel());
|
||||
button.onClick();
|
||||
});
|
||||
}
|
||||
|
||||
public void click(GuiTexturedButton button) {
|
||||
runLater(() -> {
|
||||
if (!button.isEnabled()) {
|
||||
future.setException(new IllegalStateException("Button is disabled: " + button.getTexture()));
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Clicking textured button {}", button.getTexture());
|
||||
button.onClick();
|
||||
});
|
||||
}
|
||||
|
||||
public void click(String buttonText) {
|
||||
runLater(() -> {
|
||||
LOGGER.info("Clicking button {}", buttonText);
|
||||
try {
|
||||
Field field = net.minecraft.client.gui.GuiScreen.class.getDeclaredField("buttonList");
|
||||
field.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<net.minecraft.client.gui.GuiButton> buttonList = (List)
|
||||
field.get(mc.currentScreen);
|
||||
|
||||
net.minecraft.client.gui.GuiButton button = null;
|
||||
for (net.minecraft.client.gui.GuiButton guiButton : buttonList) {
|
||||
if (guiButton.displayString.equals(buttonText)) {
|
||||
button = guiButton;
|
||||
}
|
||||
}
|
||||
if (button == null) {
|
||||
future.setException(new NoSuchElementException("No button with label: " + buttonText));
|
||||
return;
|
||||
}
|
||||
|
||||
clickNow(button.xPosition + 5, button.yPosition + 5);
|
||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||
future.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void type(String string) {
|
||||
for (char c : string.toCharArray()) {
|
||||
press(c, Keyboard.getKeyIndex(String.valueOf(c).toUpperCase()));
|
||||
}
|
||||
}
|
||||
|
||||
public void press(int keyCode) {
|
||||
String keyName = Keyboard.getKeyName(keyCode);
|
||||
char character = keyName.length() == 1 ? keyName.charAt(0) : '\0';
|
||||
press(character, keyCode);
|
||||
}
|
||||
|
||||
public void press(char character, int keyCode) {
|
||||
runLater(() -> {
|
||||
LOGGER.info("Pressing key {}", Keyboard.getKeyName(keyCode));
|
||||
if (mc.currentScreen == null || mc.currentScreen.allowUserInput) {
|
||||
KeyBinding.onTick(keyCode);
|
||||
FMLCommonHandler.instance().fireKeyInput();
|
||||
}
|
||||
if (mc.currentScreen != null) {
|
||||
try {
|
||||
Method method = net.minecraft.client.gui.GuiScreen.class
|
||||
.getDeclaredMethod("keyTyped", char.class, int.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(mc.currentScreen, character, keyCode);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.replaymod.core;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import static com.replaymod.core.ReplayModIntegrationTest.LOGGER;
|
||||
import static com.replaymod.core.utils.Utils.addCallback;
|
||||
|
||||
public class CompositeTask implements Task {
|
||||
private SettableFuture<Void> future;
|
||||
protected final Task[] children;
|
||||
|
||||
public CompositeTask(Task[] children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Void> execute() {
|
||||
future = SettableFuture.create();
|
||||
executeChild(0);
|
||||
return future;
|
||||
}
|
||||
|
||||
private void executeChild(int childIndex) {
|
||||
if (future.isDone()) return;
|
||||
if (childIndex < children.length) {
|
||||
ReplayMod.instance.runLater(() -> {
|
||||
try {
|
||||
Task task = children[childIndex];
|
||||
LOGGER.info("Running task {}", task);
|
||||
ListenableFuture<Void> childFuture = task.execute();
|
||||
addCallback(childFuture, done -> executeChild(childIndex + 1), err -> future.setException(err));
|
||||
} catch (Throwable t) {
|
||||
future.setException(t);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
future.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.replaymod.core;
|
||||
|
||||
import com.replaymod.core.regression.RegressionTest60;
|
||||
import com.replaymod.core.regression.RegressionTest61;
|
||||
import com.replaymod.core.regression.RegressionTest62;
|
||||
import com.replaymod.extra.DownloadOpenEye;
|
||||
import com.replaymod.online.SkipLogin;
|
||||
import com.replaymod.recording.CreateSPWorld;
|
||||
import com.replaymod.recording.ExitSPWorld;
|
||||
import com.replaymod.replay.ExitReplay;
|
||||
import com.replaymod.replay.LoadReplay;
|
||||
import com.replaymod.replay.OpenReplayViewer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import static com.replaymod.core.AbstractTask.mc;
|
||||
import static com.replaymod.core.ReplayModIntegrationTest.MOD_ID;
|
||||
import static com.replaymod.core.utils.Utils.addCallback;
|
||||
|
||||
/**
|
||||
* Helper mod that initiates the integration tests.
|
||||
*/
|
||||
@Mod(modid = MOD_ID)
|
||||
public class ReplayModIntegrationTest {
|
||||
public static final String MOD_ID = "replaymod-integration-test";
|
||||
|
||||
public static Logger LOGGER;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void init(FMLPreInitializationEvent event) {
|
||||
LOGGER = event.getModLog();
|
||||
|
||||
// Make sure the game window doesn't have to remain in focus during the test
|
||||
mc.gameSettings.pauseOnLostFocus = false;
|
||||
|
||||
runTasks(
|
||||
new SkipLogin(),
|
||||
new DownloadOpenEye(),
|
||||
new CreateSPWorld(),
|
||||
new Wait(5000),
|
||||
new ExitSPWorld(),
|
||||
new OpenReplayViewer(),
|
||||
new LoadReplay(),
|
||||
|
||||
new RegressionTest60(),
|
||||
new RegressionTest61(),
|
||||
new RegressionTest62(),
|
||||
|
||||
// new AbstractTask() {}, // Uncomment to not exit on success (useful for writing more tests)
|
||||
new ExitReplay()
|
||||
);
|
||||
}
|
||||
|
||||
private void runTasks(Task... tests) {
|
||||
addCallback(new CompositeTask(tests).execute(), success -> {
|
||||
if (!Minecraft.getMinecraft().hasCrashed) {
|
||||
LOGGER.info("===================================================");
|
||||
LOGGER.info("= ALL TESTS PASSED =");
|
||||
LOGGER.info("===================================================");
|
||||
FMLCommonHandler.instance().exitJava(0, false);
|
||||
}
|
||||
}, error -> {
|
||||
LOGGER.error("Failed task:", error);
|
||||
LOGGER.error("===================================================");
|
||||
LOGGER.error("= TEST FAILED =");
|
||||
LOGGER.error("===================================================");
|
||||
FMLCommonHandler.instance().exitJava(1, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
35
src/integration-test/java/com/replaymod/core/Task.java
Normal file
35
src/integration-test/java/com/replaymod/core/Task.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.replaymod.core;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
public interface Task {
|
||||
ListenableFuture<Void> execute();
|
||||
|
||||
static Task click(int x, int y) {
|
||||
return AbstractTask.create(task -> {
|
||||
task.click(x, y);
|
||||
task.runLater(() -> task.future.set(null));
|
||||
});
|
||||
}
|
||||
|
||||
static Task drag(int x, int y) {
|
||||
return AbstractTask.create(task -> {
|
||||
task.drag(x, y);
|
||||
task.runLater(() -> task.future.set(null));
|
||||
});
|
||||
}
|
||||
|
||||
static Task pressKey(int keyCode) {
|
||||
return AbstractTask.create(task -> {
|
||||
task.press(keyCode);
|
||||
task.runLater(() -> task.future.set(null));
|
||||
});
|
||||
}
|
||||
|
||||
static Task pressKey(char character, int keyCode) {
|
||||
return AbstractTask.create(task -> {
|
||||
task.press(character, keyCode);
|
||||
task.runLater(() -> task.future.set(null));
|
||||
});
|
||||
}
|
||||
}
|
||||
9
src/integration-test/java/com/replaymod/core/Utils.java
Normal file
9
src/integration-test/java/com/replaymod/core/Utils.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.replaymod.core;
|
||||
|
||||
public class Utils {
|
||||
public static void times(int x, Runnable runnable) {
|
||||
for (int i = 0; i < x; i++) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/integration-test/java/com/replaymod/core/Wait.java
Normal file
21
src/integration-test/java/com/replaymod/core/Wait.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.replaymod.core;
|
||||
|
||||
public class Wait extends AbstractTask {
|
||||
private final int duration;
|
||||
|
||||
public Wait(int duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(duration);
|
||||
runLater(() -> future.set(null));
|
||||
} catch (InterruptedException e) {
|
||||
runLater(() -> future.setException(e));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.replaymod.core.regression;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.core.CompositeTask;
|
||||
import com.replaymod.core.Task;
|
||||
import com.replaymod.replay.SpectatePlayer;
|
||||
import com.replaymod.replay.overlay.OverlayGui;
|
||||
import com.replaymod.simplepathing.GuiPathingTasks;
|
||||
import com.replaymod.simplepathing.gui.GuiEditKeyframe;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import static com.replaymod.core.AbstractTask.mc;
|
||||
import static com.replaymod.core.Utils.times;
|
||||
|
||||
/**
|
||||
* Regression test: #60 Crash in path preview when two spectator keyframes are closer than 50ms
|
||||
*/
|
||||
public class RegressionTest60 extends CompositeTask {
|
||||
public RegressionTest60() {
|
||||
super(new Task[]{
|
||||
new SpectatePlayer(),
|
||||
OverlayGui.whileOpened(
|
||||
// Place first spectator keyframe
|
||||
new GuiPathingTasks.ClickPositionKeyframeButton(),
|
||||
// Place second spectator keyframe
|
||||
Task.click(130, 50),
|
||||
new GuiPathingTasks.ClickPositionKeyframeButton(),
|
||||
// Move second keyframe to 20ms on the keyframe timeline
|
||||
AbstractTask.create(task -> {
|
||||
// Double click keyframe
|
||||
task.click(130, 50);
|
||||
task.click(130, 50);
|
||||
task.expectGui(GuiEditKeyframe.Spectator.class, gui -> {
|
||||
// Set ms field to 20
|
||||
task.click(mc.currentScreen.width / 2 + 80, mc.currentScreen.height / 2);
|
||||
times(4, () -> task.press(Keyboard.KEY_BACK));
|
||||
task.type("20");
|
||||
// Set other fields to 0
|
||||
times(2, () -> {
|
||||
task.press(Keyboard.KEY_TAB);
|
||||
times(4, () -> task.press(Keyboard.KEY_BACK));
|
||||
task.press(Keyboard.KEY_0);
|
||||
});
|
||||
task.click(gui.saveButton);
|
||||
task.expectPopupClosed(() -> task.future.set(null));
|
||||
});
|
||||
}),
|
||||
// Place second spectator keyframe
|
||||
new GuiPathingTasks.ClickPositionKeyframeButton()
|
||||
),
|
||||
// Stop spectating player
|
||||
new SpectatePlayer.End(),
|
||||
// Enable path preview
|
||||
Task.pressKey(Keyboard.KEY_H),
|
||||
new GuiPathingTasks.ClearKeyframeTimeline(),
|
||||
Task.pressKey(Keyboard.KEY_H),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.replaymod.core.regression;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.core.CompositeTask;
|
||||
import com.replaymod.core.Task;
|
||||
import com.replaymod.replay.overlay.OverlayGui;
|
||||
import com.replaymod.simplepathing.GuiPathingTasks;
|
||||
import com.replaymod.simplepathing.gui.GuiEditKeyframe;
|
||||
|
||||
/**
|
||||
* Regression test: #61 NPE when saving in edit keyframe gui of last keyframe
|
||||
*/
|
||||
public class RegressionTest61 extends CompositeTask {
|
||||
public RegressionTest61() {
|
||||
super(new Task[]{
|
||||
OverlayGui.whileOpened(
|
||||
// Place keyframe
|
||||
Task.click(130, 50),
|
||||
new GuiPathingTasks.ClickPositionKeyframeButton(),
|
||||
AbstractTask.create(task -> {
|
||||
// Double click keyframe
|
||||
task.click(130, 50);
|
||||
task.click(130, 50);
|
||||
task.expectGui(GuiEditKeyframe.Position.class, gui -> {
|
||||
task.click(gui.saveButton);
|
||||
task.expectPopupClosed(() -> task.future.set(null));
|
||||
});
|
||||
}),
|
||||
// Place second spectator keyframe
|
||||
new GuiPathingTasks.ClickPositionKeyframeButton()
|
||||
),
|
||||
new GuiPathingTasks.ClearKeyframeTimeline(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.replaymod.core.regression;
|
||||
|
||||
import com.replaymod.core.CompositeTask;
|
||||
import com.replaymod.core.Task;
|
||||
import com.replaymod.replay.overlay.OverlayGui;
|
||||
import com.replaymod.simplepathing.GuiPathingTasks;
|
||||
|
||||
/**
|
||||
* Regression test: #62 Swapping the only two existing keyframes causes NPE
|
||||
*/
|
||||
public class RegressionTest62 extends CompositeTask {
|
||||
public RegressionTest62() {
|
||||
super(new Task[]{
|
||||
OverlayGui.whileOpened(
|
||||
// Place first keyframe
|
||||
Task.click(130, 50),
|
||||
new GuiPathingTasks.ClickPositionKeyframeButton(),
|
||||
// Place second keyframe
|
||||
Task.click(150, 50),
|
||||
new GuiPathingTasks.ClickPositionKeyframeButton(),
|
||||
// Move first keyframe past second keyframe
|
||||
Task.click(130,50),
|
||||
Task.drag(170,50)
|
||||
),
|
||||
new GuiPathingTasks.ClearKeyframeTimeline()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.replaymod.extra;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.extras.OpenEyeExtra;
|
||||
import net.minecraftforge.fml.common.Loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
|
||||
public class DownloadOpenEye extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
expectGui(OpenEyeExtra.OfferGui.class, offerGui -> {
|
||||
click(offerGui.yesButton);
|
||||
expectGuiClosed(20 * 1000, () -> {
|
||||
File targetFile = new File(mc.mcDataDir, "mods/" + Loader.MC_VERSION + "/OpenEye.jar");
|
||||
if (!targetFile.exists()) {
|
||||
future.setException(new NoSuchFileException(targetFile.getAbsolutePath()));
|
||||
} else {
|
||||
future.set(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.replaymod.online;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.online.gui.GuiLoginPrompt;
|
||||
|
||||
public class SkipLogin extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
expectGui(GuiLoginPrompt.class, gui -> {
|
||||
click(gui.cancelButton);
|
||||
expectGuiClosed(() -> future.set(null));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.replaymod.recording;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import net.minecraft.client.gui.GuiCreateWorld;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
import net.minecraft.client.gui.GuiWorldSelection;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
public class CreateSPWorld extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
expectGui(GuiMainMenu.class, mainMenu -> {
|
||||
click("Singleplayer");
|
||||
expectGui(GuiWorldSelection.class, selectWorld -> {
|
||||
click("Create New World");
|
||||
expectGui(GuiCreateWorld.class, createWorld -> {
|
||||
click("Create New World");
|
||||
class EventHandler {
|
||||
@SubscribeEvent
|
||||
public void onRenderIngame(RenderGameOverlayEvent.Pre event) {
|
||||
MinecraftForge.EVENT_BUS.unregister(this);
|
||||
runLater(() -> future.set(null));
|
||||
}
|
||||
}
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.replaymod.recording;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import net.minecraft.client.gui.GuiIngameMenu;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
import org.apache.commons.io.filefilter.DirectoryFileFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ExitSPWorld extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
mc.displayInGameMenu();
|
||||
expectGui(GuiIngameMenu.class, ingameMenu -> {
|
||||
click("Save and Quit to Title");
|
||||
expectGui(GuiMainMenu.class, mainMenu -> new Thread(() -> {
|
||||
try {
|
||||
while (true) {
|
||||
String[] dirs = core.getReplayFolder().list(DirectoryFileFilter.DIRECTORY);
|
||||
if (dirs == null) {
|
||||
future.setException(new NullPointerException("dirs is null"));
|
||||
return;
|
||||
}
|
||||
if (dirs.length == 0) {
|
||||
runLater(() -> future.set(null));
|
||||
return;
|
||||
}
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}).start());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.replaymod.replay;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import net.minecraft.client.gui.GuiIngameMenu;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
|
||||
public class ExitReplay extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
mc.displayInGameMenu();
|
||||
expectGui(GuiIngameMenu.class, ingameMenu -> {
|
||||
click("Exit Replay");
|
||||
expectGui(GuiMainMenu.class, mainMenu -> future.set(null));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.replaymod.replay;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.replay.gui.screen.GuiReplayViewer;
|
||||
import de.johni0702.minecraft.gui.function.Clickable;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import org.lwjgl.util.Point;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
public class LoadReplay extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
expectGui(GuiReplayViewer.class, replayViewer -> runLater(() -> {
|
||||
ReadableDimension size = replayViewer.getMaxSize();
|
||||
// Select first entry
|
||||
replayViewer.forEach(Clickable.class).mouseClick(new Point(size.getWidth() / 2, 40), 0);
|
||||
// Load first replay
|
||||
click(replayViewer.loadButton);
|
||||
|
||||
class EventHandler {
|
||||
@SubscribeEvent
|
||||
public void onRenderIngame(RenderGameOverlayEvent.Pre event) {
|
||||
MinecraftForge.EVENT_BUS.unregister(this);
|
||||
runLater(() -> future.set(null));
|
||||
}
|
||||
}
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.replaymod.replay;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.replay.gui.screen.GuiReplayViewer;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
|
||||
public class OpenReplayViewer extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
expectGui(GuiMainMenu.class, mainMenu -> {
|
||||
click("Replay Viewer");
|
||||
expectGui(GuiReplayViewer.class, replayViewer -> future.set(null));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.replaymod.replay;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.extras.playeroverview.PlayerOverviewGui;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class SpectatePlayer extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
press(Keyboard.KEY_B);
|
||||
expectGui(PlayerOverviewGui.class, overview -> {
|
||||
click(overview.getMaxSize().getWidth() / 2 - 60, 60);
|
||||
press(Keyboard.KEY_ESCAPE);
|
||||
expectGuiClosed(() -> future.set(null));
|
||||
});
|
||||
}
|
||||
|
||||
public static class End extends AbstractTask {
|
||||
private int timeout;
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
runLater(() -> press(Keyboard.KEY_LSHIFT));
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTick(TickEvent.RenderTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.START) return;
|
||||
if (timeout++ > 20) {
|
||||
future.setException(new TimeoutException("Camera hasn't stopped spectating."));
|
||||
return;
|
||||
}
|
||||
if (mc.getRenderViewEntity() == mc.player) {
|
||||
future.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.replaymod.replay.overlay;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.core.CompositeTask;
|
||||
import com.replaymod.core.Task;
|
||||
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
public class OverlayGui {
|
||||
public static Task whileOpened(Task...tasks) {
|
||||
Task[] nTasks = new Task[tasks.length + 2];
|
||||
System.arraycopy(tasks, 0, nTasks, 1, tasks.length);
|
||||
nTasks[0] = new Open();
|
||||
nTasks[tasks.length + 1] = new Close();
|
||||
return new CompositeTask(nTasks);
|
||||
}
|
||||
|
||||
public static class Open extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
press(Keyboard.KEY_T);
|
||||
expectGui(GuiReplayOverlay.class, done -> future.set(null));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Close extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
press(Keyboard.KEY_ESCAPE);
|
||||
expectGuiClosed(() -> future.set(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.replaymod.simplepathing;
|
||||
|
||||
import com.replaymod.core.AbstractTask;
|
||||
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
|
||||
import com.replaymod.simplepathing.gui.GuiPathing;
|
||||
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
public abstract class GuiPathingTasks extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
expectGui(GuiReplayOverlay.class, ign -> init0(ReplayModSimplePathing.instance.getGuiPathing()));
|
||||
}
|
||||
|
||||
protected abstract void init0(GuiPathing guiPathing);
|
||||
|
||||
public static class ClickPositionKeyframeButton extends GuiPathingTasks {
|
||||
@Override
|
||||
protected void init0(GuiPathing guiPathing) {
|
||||
click(guiPathing.positionKeyframeButton);
|
||||
runLater(() -> future.set(null));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClearKeyframeTimeline extends AbstractTask {
|
||||
@Override
|
||||
protected void init() {
|
||||
press(Keyboard.KEY_C);
|
||||
expectGui(GuiYesNoPopup.class, popup -> {
|
||||
click(popup.getYesButton());
|
||||
expectGuiClosed(() -> future.set(null));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.replaymod.core.utils;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.AbstractGuiScrollable;
|
||||
@@ -25,6 +26,7 @@ import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -45,6 +47,7 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static net.minecraft.client.Minecraft.getMinecraft;
|
||||
|
||||
@@ -149,6 +152,20 @@ public class Utils {
|
||||
return Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL);
|
||||
}
|
||||
|
||||
public static <T> void addCallback(ListenableFuture<T> future, Consumer<T> onSuccess, Consumer<Throwable> onFailure) {
|
||||
Futures.addCallback(future, new FutureCallback<T>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable T result) {
|
||||
onSuccess.accept(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nonnull Throwable t) {
|
||||
onFailure.accept(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static GuiInfoPopup error(Logger logger, GuiContainer container, CrashReport crashReport, Runnable onClose) {
|
||||
// Convert crash report to string
|
||||
String crashReportStr = crashReport.getCompleteReport();
|
||||
|
||||
@@ -59,6 +59,9 @@ public class GuiReplayEditor extends GuiScreen {
|
||||
public GuiButton currentTabButton;
|
||||
public GuiPanel currentTabPanel;
|
||||
|
||||
public final GuiLabel warningLabel = new GuiLabel(this).setColor(Colors.RED)
|
||||
.setI18nText("replaymod.gui.editor.disclaimer");
|
||||
|
||||
public final GuiPanel tabButtons = new GuiPanel(this).setLayout(new GridLayout().setSpacingX(5));
|
||||
public final List<GuiPanel> tabPanels = new ArrayList<>();
|
||||
|
||||
@@ -84,15 +87,18 @@ public class GuiReplayEditor extends GuiScreen {
|
||||
// Move all inactive panels aside
|
||||
tabPanels.forEach(e -> pos(e, Integer.MIN_VALUE, Integer.MIN_VALUE));
|
||||
|
||||
pos(warningLabel, 10, 22);
|
||||
size(warningLabel, width - 20, 10);
|
||||
|
||||
pos(tabButtons, 10, y(warningLabel) + height(warningLabel) + 2);
|
||||
size(tabButtons, width - 20, 20);
|
||||
|
||||
pos(buttonPanel, width - 10 - width(buttonPanel), height - 10 - height(buttonPanel));
|
||||
|
||||
if (currentTabPanel != null) {
|
||||
pos(currentTabPanel, 10, 50);
|
||||
pos(currentTabPanel, 10, y(tabButtons) + height(tabButtons) + 10);
|
||||
size(currentTabPanel, width - 20, y(buttonPanel) - 10 - y(currentTabPanel));
|
||||
}
|
||||
|
||||
pos(tabButtons, 10, 20);
|
||||
size(tabButtons, width - 20, 20);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,102 +1,106 @@
|
||||
package com.replaymod.extras;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.online.ReplayModOnline;
|
||||
import com.replaymod.online.api.ApiClient;
|
||||
import com.replaymod.online.api.ApiException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IResourcePack;
|
||||
import net.minecraft.client.resources.data.IMetadataSection;
|
||||
import net.minecraft.client.resources.data.MetadataSerializer;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import static com.replaymod.extras.ReplayModExtras.LOGGER;
|
||||
|
||||
public class LocalizationExtra implements Extra {
|
||||
private ReplayModOnline module;
|
||||
private static final String ZIP_FILE_URL = "https://github.com/ReplayMod/Translations/archive/master.zip";
|
||||
private static final String LANG_PREFIX = "Translations-master/";
|
||||
|
||||
@Override
|
||||
public void register(ReplayMod mod) throws Exception {
|
||||
this.module = ReplayModOnline.instance;
|
||||
|
||||
final Minecraft mc = mod.getMinecraft();
|
||||
Thread localizedResourcePackLoader = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (Boolean.parseBoolean(System.getProperty("replaymod.offline", "false"))) {
|
||||
return;
|
||||
}
|
||||
Thread localizedResourcePackLoader = new Thread(() -> {
|
||||
try {
|
||||
// Download zip of lang files
|
||||
LOGGER.debug("Downloading languages from {}", ZIP_FILE_URL);
|
||||
Map<String, byte[]> languages = new HashMap<>();
|
||||
try (InputStream urlIn = new URL(ZIP_FILE_URL).openStream();
|
||||
ZipInputStream in = new ZipInputStream(urlIn)) {
|
||||
ZipEntry entry;
|
||||
while ((entry = in.getNextEntry()) != null) {
|
||||
String name = entry.getName();
|
||||
if (!name.startsWith(LANG_PREFIX) || !name.endsWith(".lang")) {
|
||||
continue;
|
||||
}
|
||||
name = name.substring(LANG_PREFIX.length());
|
||||
languages.put(name, IOUtils.toByteArray(in));
|
||||
LOGGER.debug("Added language file {}", name);
|
||||
}
|
||||
}
|
||||
LOGGER.debug("Downloaded {} languages", languages.size());
|
||||
|
||||
// Add lang files as resource pack
|
||||
mc.addScheduledTask(() -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<IResourcePack> defaultResourcePacks = mc.defaultResourcePacks;
|
||||
defaultResourcePacks.add(new LocalizedResourcePack(module.getApiClient()));
|
||||
mc.addScheduledTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mc.refreshResources();
|
||||
}
|
||||
});
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
defaultResourcePacks.add(new LocalizedResourcePack(languages));
|
||||
mc.getLanguageManager().onResourceManagerReload(mc.getResourceManager());
|
||||
LOGGER.debug("Added language files to resource packs and reloaded LanguageManager");
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Loading localized resource pack:", t);
|
||||
}
|
||||
}, "localizedResourcePackLoader");
|
||||
localizedResourcePackLoader.setDaemon(true);
|
||||
localizedResourcePackLoader.start();
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class LocalizedResourcePack implements IResourcePack {
|
||||
private final ApiClient apiClient;
|
||||
private Map<String, String> availableLanguages = new HashMap<>();
|
||||
private boolean websiteAvailable = true;
|
||||
private final Pattern LANG_PATTERN = Pattern.compile("^lang/([.+].lang)$");
|
||||
private final Map<String, byte[]> languages;
|
||||
|
||||
public LocalizedResourcePack(Map<String, byte[]> languages) {
|
||||
this.languages = languages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(ResourceLocation loc) {
|
||||
if(!loc.getResourcePath().endsWith(".lang")) return null;
|
||||
String langcode = loc.getResourcePath().split("/")[1].split("\\.")[0];
|
||||
if(availableLanguages.containsKey(langcode)) return new ByteArrayInputStream(availableLanguages.get(langcode).getBytes(Charsets.UTF_8));
|
||||
if (!"replaymod".equals(loc.getResourceDomain())) return null;
|
||||
Matcher matcher = LANG_PATTERN.matcher(loc.getResourcePath());
|
||||
if (matcher.matches()) {
|
||||
byte[] bytes = languages.get(matcher.group());
|
||||
if (bytes != null) {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resourceExists(ResourceLocation loc) {
|
||||
if(!(loc.getResourcePath().endsWith(".lang"))) return false;
|
||||
String langcode = loc.getResourcePath().split("/")[1].split("\\.")[0];
|
||||
if(availableLanguages.containsKey(langcode)) return true;
|
||||
if(!websiteAvailable) return false;
|
||||
try {
|
||||
if (Boolean.parseBoolean(System.getProperty("replaymod.offline", "false"))) {
|
||||
return false;
|
||||
}
|
||||
String lang = apiClient.getTranslation(langcode);
|
||||
String prop = StringEscapeUtils.unescapeHtml4(lang);
|
||||
availableLanguages.put(langcode, prop);
|
||||
return true;
|
||||
} catch (ApiException e) {
|
||||
if (e.getError() == null || e.getError().getId() != 16) { // This language has not been translated
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch(ConnectException ce) {
|
||||
websiteAvailable = false;
|
||||
ce.printStackTrace();
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
// Assumes that getInputStream returns a ByteArrayInputStream that doesn't need to be closed
|
||||
return getInputStream(loc) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getResourceDomains() {
|
||||
return ImmutableSet.of("minecraft", "replaymod");
|
||||
return ImmutableSet.of("replaymod");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,7 +43,7 @@ public class OpenEyeExtra implements Extra {
|
||||
}
|
||||
}
|
||||
|
||||
private class OfferGui extends AbstractGuiScreen<OfferGui> {
|
||||
public class OfferGui extends AbstractGuiScreen<OfferGui> {
|
||||
public final GuiScreen parent;
|
||||
public final GuiPanel textPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(3))
|
||||
.addElements(new VerticalLayout.Data(0.5),
|
||||
@@ -69,7 +69,7 @@ public class OpenEyeExtra implements Extra {
|
||||
GuiPopup popup = new GuiPopup(OfferGui.this);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
File targetFile = new File("mods/" + Loader.MC_VERSION, "OpenEye.jar");
|
||||
File targetFile = new File(mod.getMinecraft().mcDataDir, "mods/" + Loader.MC_VERSION + "/OpenEye.jar");
|
||||
FileUtils.forceMkdir(targetFile.getParentFile());
|
||||
|
||||
HttpsURLConnection connection = (HttpsURLConnection) new URL(DOWNLOAD_URL).openConnection();
|
||||
@@ -107,7 +107,7 @@ public class OpenEyeExtra implements Extra {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GuiPopup extends AbstractGuiPopup<GuiPopup> {
|
||||
public static final class GuiPopup extends AbstractGuiPopup<GuiPopup> {
|
||||
GuiPopup(GuiContainer container) {
|
||||
super(container);
|
||||
popup.addElements(null, new GuiIndicator().setColor(Colors.BLACK));
|
||||
|
||||
@@ -32,11 +32,11 @@ public class ReplayModExtras {
|
||||
OpenEyeExtra.class
|
||||
);
|
||||
|
||||
private Logger logger;
|
||||
public static Logger LOGGER;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
logger = event.getModLog();
|
||||
LOGGER = event.getModLog();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
@@ -46,7 +46,7 @@ public class ReplayModExtras {
|
||||
Extra extra = cls.newInstance();
|
||||
extra.register(ReplayMod.instance);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Failed to load extra " + cls.getName() + ": ", t);
|
||||
LOGGER.warn("Failed to load extra " + cls.getName() + ": ", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,15 @@ import com.replaymod.core.utils.Utils;
|
||||
import com.replaymod.replay.ReplayModReplay;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.*;
|
||||
import de.johni0702.minecraft.gui.container.GuiClickable;
|
||||
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.GuiVerticalList;
|
||||
import de.johni0702.minecraft.gui.element.GuiCheckbox;
|
||||
import de.johni0702.minecraft.gui.element.GuiImage;
|
||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||
import de.johni0702.minecraft.gui.element.IGuiCheckbox;
|
||||
import de.johni0702.minecraft.gui.function.Closeable;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
@@ -37,6 +42,7 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable {
|
||||
public final GuiVerticalList playersScrollable = new GuiVerticalList(contentPanel)
|
||||
.setDrawSlider(true).setDrawShadow(true);
|
||||
public final GuiCheckbox saveCheckbox = new GuiCheckbox(contentPanel)
|
||||
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.playeroverview.remembersettings.description"))
|
||||
.setI18nLabel("replaymod.gui.playeroverview.remembersettings");
|
||||
public final GuiCheckbox checkAll = new GuiCheckbox(contentPanel){
|
||||
@Override
|
||||
@@ -44,14 +50,14 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable {
|
||||
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
playersScrollable.forEach(IGuiCheckbox.class).setChecked(true);
|
||||
}
|
||||
}.setLabel("").setChecked(true);
|
||||
}.setLabel("").setChecked(true).setTooltip(new GuiTooltip().setI18nText("replaymod.gui.playeroverview.showall"));
|
||||
public final GuiCheckbox uncheckAll = new GuiCheckbox(contentPanel){
|
||||
@Override
|
||||
public void onClick() {
|
||||
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
playersScrollable.forEach(IGuiCheckbox.class).setChecked(false);
|
||||
}
|
||||
}.setLabel("").setChecked(false);
|
||||
}.setLabel("").setChecked(false).setTooltip(new GuiTooltip().setI18nText("replaymod.gui.playeroverview.hideall"));
|
||||
|
||||
{
|
||||
setBackground(Background.NONE);
|
||||
|
||||
@@ -82,7 +82,7 @@ public class ReplayModOnline {
|
||||
// Initial login prompt
|
||||
if (!core.getSettingsRegistry().get(Setting.SKIP_LOGIN_PROMPT)) {
|
||||
if (!isLoggedIn()) {
|
||||
new GuiLoginPrompt(apiClient, null, null, false).display();
|
||||
core.runLater(() -> new GuiLoginPrompt(apiClient, GuiScreen.wrap(getMinecraft().currentScreen), null, false).display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class GuiLoginPrompt extends AbstractGuiScreen<GuiLoginPrompt> {
|
||||
private GuiLabel noAccountLabel = new GuiLabel(this).setI18nText("replaymod.gui.login.noacc");
|
||||
private GuiLabel statusLabel = new GuiLabel(this);
|
||||
private GuiButton loginButton = new GuiButton(this).setI18nLabel("replaymod.gui.login").setSize(150, 20).setEnabled(false);
|
||||
private GuiButton cancelButton = new GuiButton(this).setI18nLabel("replaymod.gui.cancel").setSize(150, 20);
|
||||
public GuiButton cancelButton = new GuiButton(this).setI18nLabel("replaymod.gui.cancel").setSize(150, 20);
|
||||
private GuiButton registerButton = new GuiButton(this).setI18nLabel("replaymod.gui.register").setSize(150, 20);
|
||||
private GuiTextField username = new GuiTextField(this).setSize(145, 20).setMaxLength(16).setFocused(true);
|
||||
private GuiPasswordField password = new GuiPasswordField(this).setSize(145, 20)
|
||||
|
||||
@@ -28,6 +28,7 @@ import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||
import de.johni0702.minecraft.gui.element.GuiImage;
|
||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||
import de.johni0702.minecraft.gui.element.IGuiButton;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiResourceLoadingList;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
@@ -109,6 +110,7 @@ public class GuiReplayCenter extends GuiScreen {
|
||||
public void run() {
|
||||
GuiReplayEntry selected = list.getSelected();
|
||||
replayButtonPanel.forEach(IGuiButton.class).setEnabled(selected != null);
|
||||
replayButtonPanel.forEach(IGuiButton.class).setTooltip(null);
|
||||
if (selected != null) {
|
||||
int replayId = selected.fileInfo.getId();
|
||||
boolean favorited = favoritedReplays.contains(replayId);
|
||||
@@ -123,10 +125,22 @@ public class GuiReplayCenter extends GuiScreen {
|
||||
favoriteButton.setI18nLabel("replaymod.gui.center." + (favorited ? "unfavorite" : "favorite"));
|
||||
// Only allow button usage for either unfavorite or favorite after they've actually downloaded it
|
||||
favoriteButton.setEnabled(favorited || selected.downloaded);
|
||||
if (favoriteButton.isEnabled()) {
|
||||
favoriteButton.setTooltip(null);
|
||||
} else {
|
||||
favoriteButton.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.center.downloadrequired"));
|
||||
}
|
||||
|
||||
// Similar for like/dislike buttons
|
||||
likeButton.setEnabled(selected.downloaded);
|
||||
dislikeButton.setEnabled(selected.downloaded);
|
||||
if (likeButton.isEnabled()) {
|
||||
likeButton.setTooltip(null);
|
||||
dislikeButton.setTooltip(null);
|
||||
} else {
|
||||
likeButton.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.center.downloadrequired"));
|
||||
dislikeButton.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.center.downloadrequired"));
|
||||
}
|
||||
likeButton.setI18nLabel("replaymod.gui." + (liked ? "removelike" : "like"));
|
||||
dislikeButton.setI18nLabel("replaymod.gui." + (disliked ? "removedislike" : "dislike"));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,12 @@ import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||
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.element.*;
|
||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||
import de.johni0702.minecraft.gui.element.GuiCheckbox;
|
||||
import de.johni0702.minecraft.gui.element.GuiImage;
|
||||
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.GuiDropdownMenu;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiTextArea;
|
||||
@@ -234,6 +239,8 @@ public class GuiUploadReplay extends GuiScreen {
|
||||
});
|
||||
|
||||
validateInputs();
|
||||
name.onTextChanged(s -> validateInputs());
|
||||
tags.onTextChanged(s -> validateInputs());
|
||||
}
|
||||
|
||||
public void validateInputs() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.core.utils.Utils;
|
||||
import com.replaymod.replay.ReplayModReplay;
|
||||
import com.replaymod.replaystudio.pathing.PathingRegistry;
|
||||
import com.replaymod.replaystudio.pathing.path.Path;
|
||||
@@ -11,7 +12,11 @@ import com.replaymod.replaystudio.pathing.path.Timeline;
|
||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.*;
|
||||
import de.johni0702.minecraft.gui.container.AbstractGuiClickableContainer;
|
||||
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.GuiVerticalList;
|
||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||
import de.johni0702.minecraft.gui.element.GuiTextField;
|
||||
@@ -41,9 +46,16 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
||||
public final GuiButton overwriteButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
timelines.put(selectedEntry.name, currentTimeline);
|
||||
overwriteButton.setDisabled();
|
||||
save();
|
||||
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
||||
new GuiLabel().setI18nText("replaymod.gui.keyframerepo.overwrite").setColor(Colors.BLACK)
|
||||
).setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no");
|
||||
Utils.addCallback(popup.getFuture(), doIt -> {
|
||||
if (doIt) {
|
||||
timelines.put(selectedEntry.name, currentTimeline);
|
||||
overwriteButton.setDisabled();
|
||||
save();
|
||||
}
|
||||
}, Throwable::printStackTrace);
|
||||
}
|
||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.overwrite").setDisabled();
|
||||
public final GuiButton saveAsButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.core.utils.Restrictions;
|
||||
import com.replaymod.recording.handler.ConnectionEventHandler;
|
||||
import com.replaymod.recording.packet.PacketListener;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraft.network.NetworkManager;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
@@ -54,9 +56,12 @@ public class ReplayModRecording {
|
||||
EventBus bus = MinecraftForge.EVENT_BUS;
|
||||
bus.register(connectionEventHandler = new ConnectionEventHandler(logger, core));
|
||||
|
||||
NetworkRegistry.INSTANCE.newSimpleChannel(Restrictions.PLUGIN_CHANNEL);
|
||||
NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler());
|
||||
}
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private static class RestrictionsChannelHandler extends ChannelDuplexHandler {}
|
||||
|
||||
public void initiateRecording(NetworkManager networkManager) {
|
||||
connectionEventHandler.onConnectedToServerEvent(networkManager);
|
||||
}
|
||||
|
||||
@@ -209,6 +209,9 @@ public class ReplayHandler {
|
||||
*/
|
||||
public void spectateEntity(Entity e) {
|
||||
CameraEntity cameraEntity = getCameraEntity();
|
||||
if (cameraEntity == null) {
|
||||
return; // Cannot spectate if we have no camera
|
||||
}
|
||||
if (e == null || e == cameraEntity) {
|
||||
spectating = null;
|
||||
e = cameraEntity;
|
||||
|
||||
@@ -6,7 +6,11 @@ import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.core.utils.ModCompat;
|
||||
import com.replaymod.replay.camera.*;
|
||||
import com.replaymod.replay.camera.CameraController;
|
||||
import com.replaymod.replay.camera.CameraControllerRegistry;
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import com.replaymod.replay.camera.ClassicCameraController;
|
||||
import com.replaymod.replay.camera.VanillaCameraController;
|
||||
import com.replaymod.replay.gui.overlay.GuiMarkerTimeline;
|
||||
import com.replaymod.replay.gui.screen.GuiModCompatWarning;
|
||||
import com.replaymod.replay.handler.GuiHandler;
|
||||
@@ -99,6 +103,7 @@ public class ReplayModReplay {
|
||||
@Override
|
||||
public void onSuccess(NoGuiScreenshot result) {
|
||||
try {
|
||||
core.printInfoToChat("replaymod.chat.savingthumb");
|
||||
replayHandler.getReplayFile().writeThumb(result.getImage());
|
||||
core.printInfoToChat("replaymod.chat.savedthumb");
|
||||
} catch (IOException e) {
|
||||
@@ -109,6 +114,7 @@ public class ReplayModReplay {
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
core.printWarningToChat("replaymod.chat.failedthumb");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GuiHandler {
|
||||
private static final int BUTTON_EXIT_SERVER = 1;
|
||||
private static final int BUTTON_RETURN_TO_GAME = 4;
|
||||
private static final int BUTTON_ACHIEVEMENTS = 5;
|
||||
private static final int BUTTON_STATS = 6;
|
||||
private static final int BUTTON_OPEN_TO_LAN = 7;
|
||||
@@ -46,7 +46,9 @@ public class GuiHandler {
|
||||
// Pause replay when menu is opened
|
||||
mod.getReplayHandler().getReplaySender().setReplaySpeed(0);
|
||||
|
||||
for(GuiButton b : new ArrayList<>(event.getButtonList())) {
|
||||
GuiButton achievements = null, stats = null, openToLan = null;
|
||||
List<GuiButton> buttonList = event.getButtonList();
|
||||
for(GuiButton b : new ArrayList<>(buttonList)) {
|
||||
switch (b.id) {
|
||||
// Replace "Exit Server" button with "Exit Replay" button
|
||||
case BUTTON_EXIT_SERVER:
|
||||
@@ -55,15 +57,39 @@ public class GuiHandler {
|
||||
break;
|
||||
// Remove "Achievements", "Stats" and "Open to LAN" buttons
|
||||
case BUTTON_ACHIEVEMENTS:
|
||||
buttonList.remove(achievements = b);
|
||||
break;
|
||||
case BUTTON_STATS:
|
||||
buttonList.remove(stats = b);
|
||||
break;
|
||||
case BUTTON_OPEN_TO_LAN:
|
||||
event.getButtonList().remove(b);
|
||||
}
|
||||
// Move all buttons except the "Return to game" button upwards
|
||||
if (b.id != BUTTON_RETURN_TO_GAME) {
|
||||
b.yPosition -= 48;
|
||||
buttonList.remove(openToLan = b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (achievements != null && stats != null) {
|
||||
moveAllButtonsDirectlyBelowUpwards(buttonList, achievements.yPosition,
|
||||
achievements.xPosition, stats.xPosition + stats.width);
|
||||
}
|
||||
if (openToLan != null) {
|
||||
moveAllButtonsDirectlyBelowUpwards(buttonList, openToLan.yPosition,
|
||||
openToLan.xPosition, openToLan.xPosition + openToLan.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all buttons that are within a rectangle below a certain y coordinate upwards by 24 units.
|
||||
* @param buttons List of buttons
|
||||
* @param belowY The Y limit
|
||||
* @param xStart Left x limit of the rectangle
|
||||
* @param xEnd Right x limit of the rectangle
|
||||
*/
|
||||
private void moveAllButtonsDirectlyBelowUpwards(List<GuiButton> buttons, int belowY, int xStart, int xEnd) {
|
||||
for (GuiButton button : buttons) {
|
||||
if (button.yPosition >= belowY && button.xPosition <= xEnd && button.xPosition + button.width >= xStart) {
|
||||
button.yPosition -= 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
@Mod(modid = ReplayModSimplePathing.MOD_ID,
|
||||
version = "@MOD_VERSION@",
|
||||
@@ -22,6 +23,9 @@ import org.apache.logging.log4j.Logger;
|
||||
public class ReplayModSimplePathing {
|
||||
public static final String MOD_ID = "replaymod-simplepathing";
|
||||
|
||||
@Mod.Instance(MOD_ID)
|
||||
public static ReplayModSimplePathing instance;
|
||||
|
||||
private ReplayMod core;
|
||||
|
||||
public static Logger LOGGER;
|
||||
@@ -39,11 +43,24 @@ public class ReplayModSimplePathing {
|
||||
|
||||
PathPreview pathPreview = new PathPreview(this);
|
||||
pathPreview.register();
|
||||
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.keyframerepository", Keyboard.KEY_X, () -> {
|
||||
if (guiPathing != null) guiPathing.keyframeRepoButtonPressed();
|
||||
});
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
||||
if (guiPathing != null) guiPathing.clearKeyframesButtonPressed();
|
||||
});
|
||||
core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
||||
if (guiPathing != null) guiPathing.syncTimeButtonPressed();
|
||||
});
|
||||
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
|
||||
if (guiPathing != null) guiPathing.deleteButtonPressed();
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void postReplayOpen(ReplayOpenEvent.Post event) {
|
||||
currentTimeline = new SPTimeline();
|
||||
clearCurrentTimeline();
|
||||
guiPathing = new GuiPathing(core, this, event.getReplayHandler());
|
||||
}
|
||||
|
||||
@@ -82,11 +99,11 @@ public class ReplayModSimplePathing {
|
||||
public void setCurrentTimeline(SPTimeline newTimeline) {
|
||||
selectedPath = null;
|
||||
currentTimeline = newTimeline;
|
||||
updateDefaultInterpolatorType();
|
||||
}
|
||||
|
||||
public void clearCurrentTimeline() {
|
||||
setCurrentTimeline(new SPTimeline());
|
||||
updateDefaultInterpolatorType();
|
||||
}
|
||||
|
||||
public SPTimeline getCurrentTimeline() {
|
||||
|
||||
@@ -71,7 +71,7 @@ public class SPTimeline implements PathingRegistry {
|
||||
|
||||
@Getter
|
||||
private EntityPositionTracker entityTracker;
|
||||
private InterpolatorType defaultInterpolatorType = InterpolatorType.fromString("invalid string returns default");
|
||||
private InterpolatorType defaultInterpolatorType;
|
||||
|
||||
public SPTimeline() {
|
||||
this(createInitialTimeline());
|
||||
@@ -340,13 +340,15 @@ public class SPTimeline implements PathingRegistry {
|
||||
Optional<Interpolator> firstInterpolator =
|
||||
path.getSegments().stream().findFirst().map(PathSegment::getInterpolator);
|
||||
|
||||
// The interpolator of the previous segment
|
||||
Optional<Interpolator> interpolatorBefore =
|
||||
path.getSegments().stream().filter(s -> s.getEndKeyframe() == keyframe).findFirst().map(PathSegment::getInterpolator);
|
||||
|
||||
// The interpolator of the following segment
|
||||
Optional<Interpolator> interpolatorAfter =
|
||||
path.getSegments().stream().filter(s -> s.getStartKeyframe() == keyframe).findFirst().map(PathSegment::getInterpolator);
|
||||
// The interpolator that will be lost once we remove the old keyframe and has to be restored afterwards
|
||||
Optional<Interpolator> lostInterpolator = path.getSegments().stream().filter(s -> {
|
||||
// If this is the last keyframe,
|
||||
if (Iterables.getLast(path.getKeyframes()) == keyframe) {
|
||||
return s.getEndKeyframe() == keyframe; // the previous interpolator will be lost
|
||||
} else { // otherwise
|
||||
return s.getStartKeyframe() == keyframe; // the following interpolator will be lost
|
||||
}
|
||||
}).findFirst().map(PathSegment::getInterpolator);
|
||||
|
||||
// First remove the old keyframe
|
||||
Change removeChange = create(path, keyframe);
|
||||
@@ -369,18 +371,27 @@ public class SPTimeline implements PathingRegistry {
|
||||
Keyframe newKf = path.getKeyframe(newTime);
|
||||
if (Iterables.getLast(path.getKeyframes()) != newKf) { // Unless this is the last keyframe
|
||||
// the interpolator of the following segment has been lost and needs to be restored
|
||||
restoreInterpolatorChange = interpolatorAfter.<Change>flatMap(interpolator ->
|
||||
restoreInterpolatorChange = lostInterpolator.<Change>flatMap(interpolator ->
|
||||
path.getSegments().stream().filter(s -> s.getStartKeyframe() == newKf).findFirst().map(segment ->
|
||||
SetInterpolator.create(segment, interpolator)
|
||||
)
|
||||
).orElseGet(CombinedChange::create);
|
||||
} else { // If it is the last keyframe however,
|
||||
// the interpolator of the previous segment has been lost and needs to be restored
|
||||
restoreInterpolatorChange = interpolatorBefore.<Change>flatMap(interpolator ->
|
||||
path.getSegments().stream().filter(s -> s.getEndKeyframe() == newKf).findFirst().map(segment ->
|
||||
SetInterpolator.create(segment, interpolator)
|
||||
)
|
||||
).orElseGet(CombinedChange::create);
|
||||
restoreInterpolatorChange = path.getSegments().stream().filter(s -> s.getEndKeyframe() == newKf)
|
||||
.findFirst().flatMap(segment -> lostInterpolator.map(interpolator -> {
|
||||
// additionally, if the interpolation of this keyframe was set to explicit, that property
|
||||
// has to be transferred to the start keyframe of the new segment
|
||||
if (newKf.getValue(ExplicitInterpolationProperty.PROPERTY).isPresent()) {
|
||||
return CombinedChange.create(
|
||||
SetInterpolator.create(segment, interpolator),
|
||||
UpdateKeyframeProperties.create(path, segment.getStartKeyframe())
|
||||
.setValue(ExplicitInterpolationProperty.PROPERTY, ObjectUtils.NULL).done()
|
||||
);
|
||||
} else {
|
||||
return SetInterpolator.create(segment, interpolator);
|
||||
}
|
||||
})).orElseGet(CombinedChange::create);
|
||||
}
|
||||
restoreInterpolatorChange.apply(timeline);
|
||||
|
||||
|
||||
@@ -109,6 +109,9 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
|
||||
if (newTime != time) {
|
||||
change = CombinedChange.createFromApplied(change,
|
||||
gui.getMod().getCurrentTimeline().moveKeyframe(path, time, newTime));
|
||||
if (gui.getMod().getSelectedPath() == path && gui.getMod().getSelectedTime() == time) {
|
||||
gui.getMod().setSelected(path, newTime);
|
||||
}
|
||||
}
|
||||
gui.getMod().getCurrentTimeline().getTimeline().pushChange(change);
|
||||
close();
|
||||
@@ -252,6 +255,10 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
|
||||
xField.getDouble(), yField.getDouble(), zField.getDouble(),
|
||||
yawField.getFloat(), pitchField.getFloat(), rollField.getFloat()
|
||||
);
|
||||
if (interpolationPanel.getSettingsPanel() == null) {
|
||||
// The last keyframe doesn't have interpolator settings because there is no segment following it
|
||||
return positionChange;
|
||||
}
|
||||
Interpolator interpolator = interpolationPanel.getSettingsPanel().createInterpolator();
|
||||
if (interpolationPanel.getInterpolatorType() == InterpolatorType.DEFAULT) {
|
||||
return CombinedChange.createFromApplied(positionChange, timeline.setInterpolatorToDefault(time),
|
||||
@@ -303,6 +310,7 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
|
||||
dropdown.setSelected(type); // trigger the callback once to display settings panel
|
||||
} else {
|
||||
setSettingsPanel(InterpolatorType.DEFAULT);
|
||||
type = InterpolatorType.DEFAULT;
|
||||
}
|
||||
if (getInterpolatorTypeNoDefault(type).getInterpolatorClass().isInstance(interpolator)) {
|
||||
//noinspection unchecked
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.replaymod.simplepathing.gui;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.core.utils.Utils;
|
||||
import com.replaymod.pathing.gui.GuiKeyframeRepository;
|
||||
import com.replaymod.pathing.player.RealtimeTimelinePlayer;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
@@ -255,6 +257,7 @@ public class GuiPathing {
|
||||
}
|
||||
};
|
||||
|
||||
private final ReplayMod core;
|
||||
private final ReplayModSimplePathing mod;
|
||||
private final ReplayHandler replayHandler;
|
||||
private final RealtimeTimelinePlayer player;
|
||||
@@ -264,6 +267,7 @@ public class GuiPathing {
|
||||
private SettableFuture<Void> entityTrackerFuture;
|
||||
|
||||
public GuiPathing(final ReplayMod core, final ReplayModSimplePathing mod, final ReplayHandler replayHandler) {
|
||||
this.core = core;
|
||||
this.mod = mod;
|
||||
this.replayHandler = replayHandler;
|
||||
this.player = new RealtimeTimelinePlayer(replayHandler);
|
||||
@@ -300,9 +304,15 @@ public class GuiPathing {
|
||||
ListenableFuture<Void> future = player.start(mod.getCurrentTimeline().getTimeline(), startTime);
|
||||
overlay.setCloseable(false);
|
||||
overlay.setMouseVisible(true);
|
||||
core.printInfoToChat("replaymod.chat.pathstarted");
|
||||
Futures.addCallback(future, new FutureCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Void result) {
|
||||
if (future.isCancelled()) {
|
||||
core.printInfoToChat("replaymod.chat.pathinterrupted");
|
||||
} else {
|
||||
core.printInfoToChat("replaymod.chat.pathfinished");
|
||||
}
|
||||
overlay.setCloseable(true);
|
||||
timePath.setActive(true);
|
||||
}
|
||||
@@ -393,94 +403,90 @@ public class GuiPathing {
|
||||
}
|
||||
});
|
||||
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.keyframerepository", Keyboard.KEY_X, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!overlay.isVisible()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
GuiKeyframeRepository gui = new GuiKeyframeRepository(
|
||||
mod.getCurrentTimeline(), replayHandler.getReplayFile(), mod.getCurrentTimeline().getTimeline());
|
||||
Futures.addCallback(gui.getFuture(), new FutureCallback<Timeline>() {
|
||||
@Override
|
||||
public void onSuccess(Timeline result) {
|
||||
if (result != null) {
|
||||
mod.setCurrentTimeline(new SPTimeline(result));
|
||||
}
|
||||
}
|
||||
startLoadingEntityTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
gui.display();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
||||
GuiYesNoPopup popup = GuiYesNoPopup.open(overlay,
|
||||
new GuiLabel().setI18nText("replaymod.gui.clearcallback.title").setColor(Colors.BLACK)
|
||||
).setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no");
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
public void keyframeRepoButtonPressed() {
|
||||
try {
|
||||
GuiKeyframeRepository gui = new GuiKeyframeRepository(
|
||||
mod.getCurrentTimeline(), replayHandler.getReplayFile(), mod.getCurrentTimeline().getTimeline());
|
||||
Futures.addCallback(gui.getFuture(), new FutureCallback<Timeline>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean delete) {
|
||||
if (delete) {
|
||||
mod.clearCurrentTimeline();
|
||||
if (entityTracker != null) {
|
||||
mod.getCurrentTimeline().setEntityTracker(entityTracker);
|
||||
}
|
||||
public void onSuccess(Timeline result) {
|
||||
if (result != null) {
|
||||
mod.setCurrentTimeline(new SPTimeline(result));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
});
|
||||
gui.display();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
||||
// Current replay time
|
||||
int time = replayHandler.getReplaySender().currentTimeStamp();
|
||||
// Position of the cursor
|
||||
int cursor = timeline.getCursorPosition();
|
||||
// Get the last time keyframe before the cursor
|
||||
mod.getCurrentTimeline().getTimePath().getKeyframes().stream()
|
||||
.filter(it -> it.getTime() <= cursor).reduce((__, last) -> last).ifPresent(keyframe -> {
|
||||
// Cursor position at the keyframe
|
||||
int keyframeCursor = (int) keyframe.getTime();
|
||||
// Replay time at the keyframe
|
||||
// This is a keyframe from the time path, so it _should_ always have a time property
|
||||
int keyframeTime = keyframe.getValue(TimestampProperty.PROPERTY).get();
|
||||
// Replay time passed
|
||||
int timePassed = time - keyframeTime;
|
||||
// Speed (set to 1 when shift is held)
|
||||
double speed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) ? 1 : overlay.getSpeedSliderValue();
|
||||
// Cursor time passed
|
||||
int cursorPassed = (int) (timePassed / speed);
|
||||
// Move cursor to new position
|
||||
timeline.setCursorPosition(keyframeCursor + cursorPassed);
|
||||
// Deselect keyframe to allow the user to add a new one right away
|
||||
mod.setSelected(null, 0);
|
||||
});
|
||||
});
|
||||
|
||||
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
|
||||
if (!overlay.isVisible()) {
|
||||
return;
|
||||
public void clearKeyframesButtonPressed() {
|
||||
GuiYesNoPopup popup = GuiYesNoPopup.open(replayHandler.getOverlay(),
|
||||
new GuiLabel().setI18nText("replaymod.gui.clearcallback.title").setColor(Colors.BLACK)
|
||||
).setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no");
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean delete) {
|
||||
if (delete) {
|
||||
mod.clearCurrentTimeline();
|
||||
if (entityTracker != null) {
|
||||
mod.getCurrentTimeline().setEntityTracker(entityTracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mod.getSelectedPath() != null) {
|
||||
updateKeyframe(mod.getSelectedPath());
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void syncTimeButtonPressed() {
|
||||
// Current replay time
|
||||
int time = replayHandler.getReplaySender().currentTimeStamp();
|
||||
// Position of the cursor
|
||||
int cursor = timeline.getCursorPosition();
|
||||
// Get the last time keyframe before the cursor
|
||||
mod.getCurrentTimeline().getTimePath().getKeyframes().stream()
|
||||
.filter(it -> it.getTime() <= cursor).reduce((__, last) -> last).ifPresent(keyframe -> {
|
||||
// Cursor position at the keyframe
|
||||
int keyframeCursor = (int) keyframe.getTime();
|
||||
// Replay time at the keyframe
|
||||
// This is a keyframe from the time path, so it _should_ always have a time property
|
||||
int keyframeTime = keyframe.getValue(TimestampProperty.PROPERTY).get();
|
||||
// Replay time passed
|
||||
int timePassed = time - keyframeTime;
|
||||
// Speed (set to 1 when shift is held)
|
||||
double speed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) ? 1 : replayHandler.getOverlay().getSpeedSliderValue();
|
||||
// Cursor time passed
|
||||
int cursorPassed = (int) (timePassed / speed);
|
||||
// Move cursor to new position
|
||||
timeline.setCursorPosition(keyframeCursor + cursorPassed);
|
||||
// Deselect keyframe to allow the user to add a new one right away
|
||||
mod.setSelected(null, 0);
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteButtonPressed() {
|
||||
if (mod.getSelectedPath() != null) {
|
||||
updateKeyframe(mod.getSelectedPath());
|
||||
}
|
||||
}
|
||||
|
||||
private void startLoadingEntityTracker() {
|
||||
Preconditions.checkState(entityTrackerFuture == null);
|
||||
// Start loading entity tracker
|
||||
entityTrackerFuture = SettableFuture.create();
|
||||
new Thread(() -> {
|
||||
@@ -493,12 +499,13 @@ public class GuiPathing {
|
||||
}
|
||||
});
|
||||
logger.info("Loaded entity tracker in " + (System.currentTimeMillis() - start) + "ms");
|
||||
} catch (IOException e) {
|
||||
} catch (Throwable e) {
|
||||
logger.error("Loading entity tracker:", e);
|
||||
mod.getCore().runLater(() -> {
|
||||
mod.getCore().printWarningToChat("Error loading entity tracker: %s", e.getLocalizedMessage());
|
||||
entityTrackerFuture.setException(e);
|
||||
});
|
||||
return;
|
||||
}
|
||||
entityTracker = tracker;
|
||||
mod.getCore().runLater(() -> {
|
||||
@@ -557,7 +564,9 @@ public class GuiPathing {
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nonnull Throwable t) {
|
||||
popup.close();
|
||||
String message = "Failed to load entity tracker, pathing will be unavailable.";
|
||||
GuiReplayOverlay overlay = replayHandler.getOverlay();
|
||||
Utils.error(LOGGER, overlay, CrashReport.makeCrashReport(t, message), popup::close);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
|
||||
1
src/main/resources/assets/replaymod/lang
Submodule
1
src/main/resources/assets/replaymod/lang
Submodule
Submodule src/main/resources/assets/replaymod/lang added at da6f6f8d12
@@ -1,539 +0,0 @@
|
||||
replaymod.title=Replay Mod
|
||||
|
||||
#Website API translations
|
||||
replaymod.api.parammissing=Required parameter missing: %1$s
|
||||
replaymod.api.invalidvalue=Invalid value passed for parameter %1$s - Possible values: %2$s
|
||||
replaymod.api.invalidlogin=Invalid login data provided
|
||||
replaymod.api.invalidauthkey=Invalid authentication key provided
|
||||
replaymod.api.notenoughspace=Not enough space left to upload this file. Consider purchasing Premium on ReplayMod.com
|
||||
replaymod.api.invalidfile=This file is invalid
|
||||
replaymod.api.fileduplicate=This file has already been uploaded
|
||||
replaymod.api.filenoexist=This file does not exist
|
||||
replaymod.api.nopermissions=You don't have permission to access this file
|
||||
replaymod.api.alreadyrated=You already rated this file
|
||||
replaymod.api.usernoexist=This user does not exist
|
||||
replaymod.api.invalidcategory=Invalid file category
|
||||
replaymod.api.nofilesuploaded=No files uploaded
|
||||
replaymod.api.toomanyvalues=Too many values passed for %1$s
|
||||
replaymod.api.noparameters=At least one parameter required. Parameters: %1$s
|
||||
replaymod.api.invalidfilename=File name must be between 5 and 30 characters long
|
||||
replaymod.api.unknownlang=This language has not been translated
|
||||
replaymod.api.zippingerror=An error occured while zipping the language files
|
||||
replaymod.api.usernameexists=This username is already taken
|
||||
replaymod.api.mailexists=This Email address is already linked to an account
|
||||
replaymod.api.invalidmail=Invalid Email Address
|
||||
replaymod.api.nopermissions=You don't have permission to do this
|
||||
replaymod.api.authfailed=Authentication to the Minecraft Session Servers failed
|
||||
replaymod.api.mcuserexists=A ReplayMod account is already associated with this Minecraft account
|
||||
replaymod.api.filetoobig=The uploaded file is too big
|
||||
replaymod.api.usernametoolong=The username is too long
|
||||
replaymod.api.descriptiontoolong=The description is too long
|
||||
replaymod.api.passwordlength=The password has to be between 5 and 1024 characters long
|
||||
replaymod.api.suspended=Your account has been suspended
|
||||
|
||||
#All of the chat messages
|
||||
replaymod.chat.recordingstarted=Recording started
|
||||
replaymod.chat.recordingfailed=Failed to start recording
|
||||
|
||||
replaymod.chat.savingthumb=Saving Thumbnail...
|
||||
replaymod.chat.savedthumb=Thumbnail has been successfully saved
|
||||
replaymod.chat.failedthumb=Thumbnail could not be saved
|
||||
|
||||
replaymod.chat.addedmarker=Event Marker has been added
|
||||
|
||||
#Chat messages displayed in Replay Viewer
|
||||
replaymod.chat.morekeyframes=At least 2 position keyframes and 2 time keyframes required
|
||||
replaymod.chat.negativetime=Camera Paths can't play time backwards. Please consult your nearest time lord for assistance.
|
||||
replaymod.chat.pathstarted=Camera Path started
|
||||
replaymod.chat.pathfinished=Camera Path finished
|
||||
replaymod.chat.pathinterrupted=Camera Path canceled
|
||||
|
||||
#Replay Categories
|
||||
replaymod.category=Category
|
||||
replaymod.category.survival=Survival
|
||||
replaymod.category.minigame=Minigame
|
||||
replaymod.category.build=Build
|
||||
replaymod.category.misc=Miscellaneous
|
||||
|
||||
#Common GUI-related strings
|
||||
replaymod.gui.replay=Replay
|
||||
replaymod.gui.login=Login
|
||||
replaymod.gui.logout=Logout
|
||||
replaymod.gui.cancel=Cancel
|
||||
replaymod.gui.username=Username
|
||||
replaymod.gui.password=Password
|
||||
replaymod.gui.back=Back
|
||||
replaymod.gui.duration=Duration
|
||||
replaymod.gui.load=Load
|
||||
replaymod.gui.download=Download
|
||||
replaymod.gui.like=Like
|
||||
replaymod.gui.dislike=Dislike
|
||||
replaymod.gui.removelike=Remove Like
|
||||
replaymod.gui.removedislike=Remove Dislike
|
||||
replaymod.gui.save=Save
|
||||
replaymod.gui.upload=Upload
|
||||
replaymod.gui.rename=Rename
|
||||
replaymod.gui.remove=Remove
|
||||
replaymod.gui.add=Add
|
||||
replaymod.gui.start=Start
|
||||
replaymod.gui.end=End
|
||||
replaymod.gui.delete=Delete
|
||||
replaymod.gui.recording=Recording
|
||||
replaymod.gui.speed=Speed
|
||||
replaymod.gui.register=Register
|
||||
replaymod.gui.mail=Email Address
|
||||
replaymod.gui.loading=Loading
|
||||
replaymod.gui.pleasewait=Please wait
|
||||
replaymod.gui.render=Render
|
||||
replaymod.gui.iphidden=Server IP Hidden
|
||||
replaymod.gui.overwrite=Overwrite
|
||||
replaymod.gui.saveas=Save as ...
|
||||
replaymod.gui.done=Done
|
||||
replaymod.gui.close=Close
|
||||
replaymod.gui.notagain=Don't show again
|
||||
|
||||
replaymod.gui.renderdonetitle=Video rendered
|
||||
replaymod.gui.openfolder=Open Video Folder
|
||||
replaymod.gui.youtubeupload=Upload to YouTube
|
||||
replaymod.gui.renderdone1=Your video was successfully rendered.
|
||||
replaymod.gui.renderdone2=How would you like to proceed?
|
||||
replaymod.gui.videotitle=Title
|
||||
replaymod.gui.videodescription=Description
|
||||
replaymod.gui.videotags=Tags,Tags,Tags
|
||||
replaymod.gui.videothumbnail=Video Thumbnail
|
||||
replaymod.gui.videovisibility.private=Private
|
||||
replaymod.gui.videovisibility.unlisted=Unlisted
|
||||
replaymod.gui.videovisibility.public=Public
|
||||
replaymod.gui.ytuploadprogress.auth=[1/4] Authorization
|
||||
replaymod.gui.ytuploadprogress.prepare_video=[2/4] Preparing video: %%d%%%%
|
||||
replaymod.gui.ytuploadprogress.upload=[3/4] Uploading: %%d%%%%
|
||||
replaymod.gui.ytuploadprogress.cleanup=[4/4] Cleanup
|
||||
replaymod.gui.ytuploadprogress.done=Done: %s
|
||||
replaymod.gui.titleempty=Title cannot be empty
|
||||
replaymod.gui.videothumbnailtoolarge=Thumbnail size exceeds 2MB
|
||||
replaymod.gui.videothumbnailformat=Thumbnail has to be either JPEG or PNG format
|
||||
|
||||
replaymod.gui.original=Original
|
||||
replaymod.gui.modified=Modified
|
||||
|
||||
replaymod.gui.outdated=There is a newer Replay Mod version available. Please download it from replaymod.com
|
||||
|
||||
replaymod.gui.hours=h
|
||||
replaymod.gui.minutes=min
|
||||
replaymod.gui.seconds=sec
|
||||
replaymod.gui.milliseconds=ms
|
||||
|
||||
replaymod.gui.pitch=Pitch
|
||||
replaymod.gui.yaw=Yaw
|
||||
replaymod.gui.roll=Roll
|
||||
|
||||
replaymod.gui.camera=Camera
|
||||
replaymod.gui.position=Position
|
||||
|
||||
replaymod.gui.unknownerror=An unknown error occured
|
||||
|
||||
replaymod.gui.exit=Exit Replay
|
||||
replaymod.gui.java=Java 1.7 or newer required
|
||||
replaymod.gui.morereplays=At least one Replay required
|
||||
|
||||
replaymod.gui.loggedin=LOGGED IN
|
||||
replaymod.gui.loggedout=LOGGED OUT
|
||||
|
||||
replaymod.gui.mainmenu=Main Menu
|
||||
replaymod.gui.settings=Settings
|
||||
|
||||
replaymod.gui.keyframerepo.delete=Are you sure you want to delete these keyframes?
|
||||
|
||||
replaymod.gui.restorereplay1=It seems like Minecraft has not quit normally.
|
||||
replaymod.gui.restorereplay2=The Replay "%1$s" was not saved correctly.
|
||||
replaymod.gui.restorereplay3=Do you wish to recover it?
|
||||
|
||||
replaymod.gui.loadentitytracker=Loading entity positions: %%d%%%%
|
||||
|
||||
#Only change these if it's neccessary
|
||||
replaymod.gui.replayviewer=Replay Viewer
|
||||
replaymod.gui.replaycenter=Replay Center
|
||||
replaymod.gui.replaysettings=Replay Settings
|
||||
replaymod.gui.replayeditor=Replay Editor
|
||||
|
||||
#Login GUI
|
||||
replaymod.gui.login.title=Login to ReplayMod.com
|
||||
replaymod.gui.login.logging=Logging in...
|
||||
replaymod.gui.login.incorrect=Incorrect username or password
|
||||
replaymod.gui.login.connectionerror=Could not connect to ReplayMod.com
|
||||
|
||||
replaymod.gui.login.skip=Skip
|
||||
|
||||
replaymod.gui.register.title=Register on ReplayMod.com
|
||||
replaymod.gui.register.confirmpw=Confirm Password
|
||||
|
||||
replaymod.gui.register.disclaimer=By registering an account, you agree to the Replay Mod's Terms of Service: https://replaymod.com/legal/terms
|
||||
|
||||
replaymod.gui.register.error.nomatch=Passwords don't match
|
||||
replaymod.gui.register.error.shortusername=Username has to be at least 5 characters long
|
||||
replaymod.gui.register.error.invalidname=Username may only contain letters and numbers
|
||||
replaymod.gui.register.error.shortpw=Password has to be at least 5 characters long
|
||||
replaymod.gui.register.error.longpw=Password has to be at most 1024 characters long
|
||||
replaymod.gui.register.error.authfailed=Could not authenticate your Minecraft account
|
||||
|
||||
#Replay Viewer GUI
|
||||
replaymod.gui.viewer.rename.title=Rename Replay
|
||||
replaymod.gui.viewer.rename.name=Replay Name
|
||||
replaymod.gui.viewer.replayfolder=Open Replay Folder...
|
||||
replaymod.gui.viewer.noauth=Log in to upload a Replay
|
||||
replaymod.gui.viewer.alreadyuploaded=This Replay has already been uploaded
|
||||
|
||||
replaymod.gui.viewer.delete.linea=Are you sure you want to delete this replay?
|
||||
replaymod.gui.viewer.delete.failed1=Your OS did not allow us to move the replay. There is
|
||||
replaymod.gui.viewer.delete.failed2=nothing we can do about this. You have to do it yourself.
|
||||
replaymod.gui.viewer.delete.lineb='%1$s' will be lost forever! (a long time!)
|
||||
|
||||
replaymod.gui.viewer.download.title=Downloading Replay File...
|
||||
replaymod.gui.viewer.download.message=Please wait while "%1$s" is being downloaded.
|
||||
|
||||
replaymod.gui.viewer.chooser.title=Choose Replay File
|
||||
replaymod.gui.viewer.chooser.message=You have a modified version of "%1$s" on your computer. Which Replay do you want to load?
|
||||
|
||||
replaymod.gui.login.noacc=Don't have an account yet?
|
||||
|
||||
#Replay Center GUI
|
||||
replaymod.gui.center.logoutcallback=Do you really want to log out?
|
||||
replaymod.gui.center.top.recent=Recent
|
||||
replaymod.gui.center.top.best=Best
|
||||
replaymod.gui.center.top.downloaded=Downloaded
|
||||
replaymod.gui.center.top.favorited=Favorited
|
||||
replaymod.gui.center.top.search=Search
|
||||
|
||||
replaymod.gui.center.search.filters=Search Filters
|
||||
replaymod.gui.center.search.gametype=Gametype
|
||||
replaymod.gui.center.search.order=Sort by
|
||||
replaymod.gui.center.search.order.best=Best
|
||||
replaymod.gui.center.search.order.recent=Recent
|
||||
replaymod.gui.center.search.name=Replay Name
|
||||
replaymod.gui.center.search.server=Server IP (Multiplayer only)
|
||||
replaymod.gui.center.search.category=Filter by Category
|
||||
replaymod.gui.center.search.version=Filter by Version
|
||||
|
||||
replaymod.gui.center.downloadrequired=Download the Replay to rate or favorite it
|
||||
|
||||
replaymod.gui.center.favorite=Favorite
|
||||
replaymod.gui.center.unfavorite=Unfavorite
|
||||
replaymod.gui.center.favorites=Favorites
|
||||
|
||||
replaymod.gui.center.author=by %1$s%2$s
|
||||
|
||||
#Upload GUI
|
||||
replaymod.gui.upload.title=Upload File
|
||||
replaymod.gui.upload.start=Start Upload
|
||||
replaymod.gui.upload.cancel=Cancel Upload
|
||||
replaymod.gui.upload.tagshint=Tags separated by comma
|
||||
replaymod.gui.upload.namehint=Replay Title
|
||||
replaymod.gui.upload.descriptionhint=Description
|
||||
replaymod.gui.upload.duration=Duration: %02dm%02ds
|
||||
replaymod.gui.upload.uploading=Uploading...
|
||||
replaymod.gui.upload.success=File has been successfully uploaded
|
||||
replaymod.gui.upload.canceled=Upload has been canceled
|
||||
replaymod.gui.upload.hideip2=Hide Server IP (%s)
|
||||
replaymod.gui.upload.nothumbnail=Missing Thumbnail. Please create a Thumbnail in the Replay using the %1$s key.
|
||||
replaymod.gui.upload.tryagain=Try again?
|
||||
|
||||
replaymod.gui.upload.error.name.length=The Replay Name has to be between 5 and 30 characters long
|
||||
replaymod.gui.upload.error.name=The Replay Name may not contain special characters
|
||||
replaymod.gui.upload.error.tags=Tags may only contain letters and are separated using a comma
|
||||
|
||||
#Replay Editor
|
||||
replaymod.gui.editor.replayfile=Replay File
|
||||
|
||||
replaymod.gui.editor.savemode.override=Replace Source File
|
||||
replaymod.gui.editor.savemode.newfile=Save to new File
|
||||
|
||||
replaymod.gui.editor.trim.title=Trim Replay
|
||||
replaymod.gui.editor.connect.title=Connect Replays
|
||||
replaymod.gui.editor.modify.title=Modify Replay
|
||||
|
||||
replaymod.gui.editor.trim.description=Removes the beginning and end of a Replay File, only keeping the Replay between the given timestamps
|
||||
replaymod.gui.editor.connect.description=Connects multiple Replays in the specified order
|
||||
replaymod.gui.editor.modify.description=Provides several filters to modify Replay Files
|
||||
|
||||
replaymod.gui.editor.trim.marker=Select Event Marker
|
||||
|
||||
replaymod.gui.editor.progress.title=Editing Replay File...
|
||||
replaymod.gui.editor.progress.pleasewait=Please wait while the Replay is being edited.
|
||||
|
||||
replaymod.gui.editor.progress.status.initializing=Initializing
|
||||
replaymod.gui.editor.progress.status.writing.raw=Rewriting Replay...
|
||||
replaymod.gui.editor.progress.status.writing.final=Writing File to disk...
|
||||
replaymod.gui.editor.progress.status.finished=Finished Editing!
|
||||
|
||||
replaymod.gui.editor.disclaimer=The Replay Editor is an experimental feature and may contain bugs.
|
||||
|
||||
#Cancel Replay GUI
|
||||
replaymod.gui.cancelrender.title=Cancel Rendering
|
||||
replaymod.gui.cancelrender.message=Are you sure that you want to cancel the current rendering process?
|
||||
#Saving Replay GUI
|
||||
replaymod.gui.replaysaving.title=Saving Replay File...
|
||||
replaymod.gui.replaysaving.message=Please wait while your recent Replay is being saved.
|
||||
replaymod.gui.replaymodified.message=Replay was modified. Would you like to save the changes?
|
||||
replaymod.gui.replaymodified.yes=Save Modified Replay
|
||||
replaymod.gui.replaymodified.no=Discard Changes
|
||||
replaymod.gui.replaymodified.warning1=A Replay named "%1$s" already exists.
|
||||
replaymod.gui.replaymodified.warning2=Are you sure you want to replace it?
|
||||
|
||||
#Player Overview GUI
|
||||
replaymod.gui.playeroverview.visible=Visible
|
||||
replaymod.gui.playeroverview.hideall=Hide all
|
||||
replaymod.gui.playeroverview.showall=Show all
|
||||
replaymod.gui.playeroverview.spectate=Spectate Player
|
||||
replaymod.gui.playeroverview.remembersettings=Remember Hidden Players
|
||||
replaymod.gui.playeroverview.remembersettings.description=Saves Player Visibility in Replay File
|
||||
|
||||
#Replay Mod Settings GUI
|
||||
replaymod.gui.settings.title=Replay Mod Settings
|
||||
|
||||
replaymod.gui.settings.interpolation.linear=Linear
|
||||
replaymod.gui.settings.interpolation.cubic=Cubic
|
||||
replaymod.gui.settings.interpolation.catmullrom=Catmull Rom
|
||||
|
||||
replaymod.gui.settings.bitrate=Video Bitrate
|
||||
replaymod.gui.settings.framerate=Video Framerate
|
||||
replaymod.gui.settings.notifications=Enable Notifications
|
||||
replaymod.gui.settings.recordserver=Record Server
|
||||
replaymod.gui.settings.recordsingleplayer=Record Singleplayer
|
||||
replaymod.gui.settings.indicator=Recording Indicator
|
||||
replaymod.gui.settings.lighting=Ambient Lighting
|
||||
replaymod.gui.settings.forcechunks=Force Render Chunks
|
||||
replaymod.gui.settings.resources=Server Resource Packs
|
||||
replaymod.gui.settings.interpolation=Path Interpolation
|
||||
replaymod.gui.settings.linearinterpolation=Linear Path Interpolation
|
||||
replaymod.gui.settings.pathpreview=Show Path Preview
|
||||
replaymod.gui.settings.keyframecleancallback=Clear Confirmation
|
||||
replaymod.gui.settings.renderinvisible=Render invisible Entities
|
||||
replaymod.gui.settings.camera=Camera
|
||||
replaymod.gui.settings.showchat=Show Chat
|
||||
replaymod.gui.settings.interpolator=Default Interpolator
|
||||
|
||||
replaymod.gui.settings.warning.linea=WARNING: Recording settings will be
|
||||
replaymod.gui.settings.warning.lineb=applied the next time you join a world.
|
||||
|
||||
#Replay Mod Keybindings
|
||||
replaymod.input.lighting=Toggle Lighting
|
||||
replaymod.input.thumbnail=Capture Thumbnail
|
||||
replaymod.input.playeroverview=Player Overview
|
||||
replaymod.input.clearkeyframes=Clear Keyframes
|
||||
replaymod.input.synctimeline=Synchronize Timeline
|
||||
replaymod.input.keyframerepository=Open Keyframe Presets
|
||||
replaymod.input.rollclockwise=Roll Clockwise
|
||||
replaymod.input.rollcounterclockwise=Roll Counterclockwise
|
||||
replaymod.input.resettilt=Reset Camera Tilt
|
||||
replaymod.input.playpause=Play/Pause Replay
|
||||
replaymod.input.marker=Add Event Marker
|
||||
replaymod.input.pathpreview=Toggle Path Preview
|
||||
replaymod.input.assetmanager=Asset Manager
|
||||
replaymod.input.objectmanager=Object Manager
|
||||
replaymod.input.interpolation=Toggle Interpolation
|
||||
replaymod.input.settings=ReplayMod Settings
|
||||
|
||||
# CameraControllers
|
||||
replaymod.camera.classic=Classic
|
||||
replaymod.camera.vanilla=Vanilla-ish
|
||||
|
||||
#Keyframe Presets GUI
|
||||
replaymod.gui.keyframerepository.title=Keyframe Repository
|
||||
replaymod.gui.keyframerepository.presets=Keyframe Presets
|
||||
replaymod.gui.keyframerepository.positionkeyframes=Position Keyframes
|
||||
replaymod.gui.keyframerepository.timekeyframes=Time Keyframes
|
||||
|
||||
replaymod.gui.keyframerepository.savecurrent=Save current Path
|
||||
|
||||
replaymod.gui.keyframerepository.noentries=No Presets available
|
||||
|
||||
replaymod.gui.keyframerepository.preset.defaultname=New Keyframe Preset
|
||||
replaymod.gui.keyframerepository.duplicate=This Preset already exists
|
||||
|
||||
#Edit Keyframe GUI
|
||||
replaymod.gui.editkeyframe.title.pos=Edit Position Keyframe
|
||||
replaymod.gui.editkeyframe.title.time=Edit Time Keyframe
|
||||
replaymod.gui.editkeyframe.title.marker=Edit Event Marker
|
||||
replaymod.gui.editkeyframe.title.spec=Edit Spectator Keyframe
|
||||
replaymod.gui.editkeyframe.xpos=X Position
|
||||
replaymod.gui.editkeyframe.ypos=Y Position
|
||||
replaymod.gui.editkeyframe.zpos=Z Position
|
||||
replaymod.gui.editkeyframe.campitch=Camera Pitch
|
||||
replaymod.gui.editkeyframe.camyaw=Camera Yaw
|
||||
replaymod.gui.editkeyframe.camroll=Camera Roll
|
||||
replaymod.gui.editkeyframe.timelineposition=Timeline Position
|
||||
replaymod.gui.editkeyframe.markername=Event Marker Name
|
||||
replaymod.gui.editkeyframe.timestamp=Time Index
|
||||
|
||||
replaymod.gui.editkeyframe.spec.method=Spectating Method
|
||||
replaymod.gui.editkeyframe.spec.method.firstperson=First Person
|
||||
replaymod.gui.editkeyframe.spec.method.shoulder=Shoulder Camera
|
||||
|
||||
replaymod.gui.editkeyframe.spec.method.shoulder.distance=Camera Distance
|
||||
replaymod.gui.editkeyframe.spec.method.shoulder.pitch=Pitch Offset
|
||||
replaymod.gui.editkeyframe.spec.method.shoulder.yaw=Rotation Angle
|
||||
replaymod.gui.editkeyframe.spec.method.shoulder.smoothness=Path Smoothness
|
||||
|
||||
replaymod.gui.editkeyframe.interpolator=Interpolator
|
||||
replaymod.gui.editkeyframe.interpolator.default.name=Default Interpolator
|
||||
replaymod.gui.editkeyframe.interpolator.default.desc=Uses the default interpolator defined in the Replay Mod Settings.
|
||||
replaymod.gui.editkeyframe.interpolator.catmullrom.name=Catmull Rom Spline Interpolator
|
||||
replaymod.gui.editkeyframe.interpolator.catmullrom.desc=Calculates a catmull rom spline with customizable alpha value for precise tightness and smoothness of the camera path.
|
||||
replaymod.gui.editkeyframe.interpolator.catmullrom.alpha=Alpha:
|
||||
replaymod.gui.editkeyframe.interpolator.cubic.name=Cubic Spline Interpolator
|
||||
replaymod.gui.editkeyframe.interpolator.cubic.desc=Calculates a cubic equation matrix for all points for a smooth camera path.
|
||||
replaymod.gui.editkeyframe.interpolator.linear.name=Linear Interpolator
|
||||
replaymod.gui.editkeyframe.interpolator.linear.desc=Draws straight lines between the keyframes.
|
||||
|
||||
#Render Settings GUI
|
||||
replaymod.gui.rendersettings.title=Rendering Options
|
||||
replaymod.gui.rendersettings.renderer=Rendering Method
|
||||
|
||||
replaymod.gui.rendersettings.renderer.default=Default Rendering
|
||||
replaymod.gui.rendersettings.renderer.tiled=Tiled Rendering
|
||||
replaymod.gui.rendersettings.renderer.stereoscopic=Stereoscopic Rendering
|
||||
replaymod.gui.rendersettings.renderer.cubic=Cubic Rendering
|
||||
replaymod.gui.rendersettings.renderer.equirectangular=Equirectangular Rendering
|
||||
replaymod.gui.rendersettings.renderer.ods=ODS Rendering
|
||||
|
||||
replaymod.gui.rendersettings.renderer.default.description=Renders the video in the specified resolution. Fastest Rendering Option.
|
||||
replaymod.gui.rendersettings.renderer.stereoscopic.description=Renders the video as a stereoscopic (side-by-side) 3D movie, usable by different 3D technologies. The image for one eye is half the width of the video
|
||||
replaymod.gui.rendersettings.renderer.cubic.description=Renders the video with a 360 degree panoramic view, using Cubic Projection. This is usable by several 360 degree video players (and the Oculus Rift), for example VR Player.
|
||||
replaymod.gui.rendersettings.renderer.equirectangular.description=Renders the video with a 360 degree panoramic view, using Equirectangular Projection. This is usable by YouTube's new 360 degree video function, and several video players (and the Oculus Rift), for example VR Player.
|
||||
replaymod.gui.rendersettings.renderer.ods.description=Renders the video with a stereoscopic 3D 360 degree panoramic view, also called VR Video. This is usable by YouTube's new VR video function.
|
||||
|
||||
replaymod.gui.rendersettings.customresolution=Video Resolution
|
||||
replaymod.gui.rendersettings.customresolution.warning.cubic=Cubic Rendering requires an aspect ratio of 4:3
|
||||
replaymod.gui.rendersettings.customresolution.warning.equirectangular=Equirectangular Rendering requires an aspect ratio of 2:1
|
||||
replaymod.gui.rendersettings.customresolution.warning.yuv420=For this preset, the width and height values need to be even numbers
|
||||
replaymod.gui.rendersettings.customresolution.warning.ods=ODS Rendering requires an aspect ratio of 1:1
|
||||
replaymod.gui.rendersettings.resolution=Video Resolution
|
||||
replaymod.gui.rendersettings.interpolation=Path Interpolation
|
||||
replaymod.gui.rendersettings.forcechunks=Force Render Chunks
|
||||
replaymod.gui.rendersettings.framerate=Video Framerate
|
||||
replaymod.gui.rendersettings.quality=Video Quality
|
||||
replaymod.gui.rendersettings.chromakey=Chroma Keying
|
||||
replaymod.gui.rendersettings.skycolor=Sky Color
|
||||
replaymod.gui.rendersettings.nametags=Render Name Tags
|
||||
replaymod.gui.rendersettings.outputfile=Output File
|
||||
replaymod.gui.rendersettings.360metadata=Inject 360° Metadata
|
||||
replaymod.gui.rendersettings.360metadata.error=360° Metadata can only be added to Equirectangular videos in MP4 format
|
||||
|
||||
replaymod.gui.rendersettings.command=Command
|
||||
replaymod.gui.rendersettings.arguments=Command Line Arguments
|
||||
replaymod.gui.rendersettings.ffmpeg.description=If you are an advanced user, you can customize the command line parameters used to export the video. For more information, check http://replaymod.com/docs
|
||||
|
||||
replaymod.gui.rendersettings.stabilizecamera=Stabilize Camera
|
||||
replaymod.gui.rendersettings.exportyoutube=Export for YouTube
|
||||
|
||||
replaymod.gui.rendersettings.video=Video Settings
|
||||
replaymod.gui.rendersettings.advanced=Advanced Settings
|
||||
replaymod.gui.rendersettings.commandline=Command Line Settings
|
||||
|
||||
replaymod.gui.rendersettings.presets=Encoding Presets
|
||||
replaymod.gui.rendersettings.presets.mp4.custom=MP4 - Custom Bitrate
|
||||
replaymod.gui.rendersettings.presets.mp4.high=MP4 - High Quality
|
||||
replaymod.gui.rendersettings.presets.mp4.default=MP4 - Default Quality
|
||||
replaymod.gui.rendersettings.presets.mp4.potato=MP4 - Potato Quality
|
||||
replaymod.gui.rendersettings.presets.webm.custom=WEBM - Custom Bitrate
|
||||
replaymod.gui.rendersettings.presets.mkv.lossless=MKV - Lossless
|
||||
replaymod.gui.rendersettings.presets.png=PNG Sequence
|
||||
|
||||
replaymod.gui.rendersettings.antialiasing=Anti-Aliasing
|
||||
replaymod.gui.rendersettings.antialiasing.none=None
|
||||
replaymod.gui.rendersettings.antialiasing.x2=2x
|
||||
replaymod.gui.rendersettings.antialiasing.x4=4x
|
||||
replaymod.gui.rendersettings.antialiasing.x8=8x
|
||||
|
||||
#Render Queue GUI
|
||||
replaymod.gui.renderqueue.title=Render Queue
|
||||
replaymod.gui.renderqueue.open=Open Queue
|
||||
replaymod.gui.renderqueue.jobname=Job Name:
|
||||
replaymod.gui.renderqueue.add=Add current configuration
|
||||
|
||||
#Rendering GUI
|
||||
replaymod.gui.rendering.title=Rendering Video
|
||||
replaymod.gui.rendering.pause=Pause Rendering
|
||||
replaymod.gui.rendering.resume=Resume Rendering
|
||||
replaymod.gui.rendering.cancel=Cancel Rendering
|
||||
replaymod.gui.rendering.cancel.callback=Are you sure?
|
||||
replaymod.gui.rendering.preview=Show Preview (Performance might suffer)
|
||||
replaymod.gui.rendering.progress=Frames rendered: %1$d / %2$d
|
||||
replaymod.gui.rendering.timetaken=Render Time
|
||||
replaymod.gui.rendering.timeleft=Time Left
|
||||
|
||||
#Render Errors
|
||||
replaymod.gui.rendering.error.title=Rendering Failed
|
||||
replaymod.gui.rendering.error.optifine=Please uninstall Optifine before rendering.
|
||||
replaymod.gui.rendering.error.message=To render a video, you need to have ffmpeg installed. http://ffmpeg.org
|
||||
|
||||
#Ingame Menu
|
||||
replaymod.gui.ingame.menu.addposkeyframe=Add Position Keyframe
|
||||
replaymod.gui.ingame.menu.removeposkeyframe=Remove Position Keyframe
|
||||
replaymod.gui.ingame.menu.addspeckeyframe=Add Spectator Keyframe
|
||||
replaymod.gui.ingame.menu.removespeckeyframe=Remove Spectator Keyframe
|
||||
replaymod.gui.ingame.menu.addtimekeyframe=Add Time Keyframe
|
||||
replaymod.gui.ingame.menu.removetimekeyframe=Remove Time Keyframe
|
||||
replaymod.gui.ingame.menu.pause=Pause Replay
|
||||
replaymod.gui.ingame.menu.unpause=Unpause Replay
|
||||
replaymod.gui.ingame.menu.renderpath=Render Camera Path
|
||||
replaymod.gui.ingame.menu.playpath=Play Camera Path from Cursor Position
|
||||
replaymod.gui.ingame.menu.playpathfromstart=Play Camera Path from Start
|
||||
replaymod.gui.ingame.menu.pausepath=Pause Camera Path
|
||||
replaymod.gui.ingame.menu.zoomin=Zoom in
|
||||
replaymod.gui.ingame.menu.zoomout=Zoom out
|
||||
|
||||
replaymod.gui.ingame.unnamedmarker=Unnamed Event Marker
|
||||
|
||||
#Clear Keyframe Callback
|
||||
replaymod.gui.clearcallback.title=Clear all Keyframes?
|
||||
replaymod.gui.clearcallback.message=This Callback can be disabled in the Replay Settings.
|
||||
|
||||
#Asset Manager Gui
|
||||
replaymod.gui.assets.title=Asset Manager
|
||||
replaymod.gui.assets.filechooser=Asset File
|
||||
replaymod.gui.assets.defaultname=New Asset
|
||||
replaymod.gui.assets.emptylist=No Assets available
|
||||
replaymod.gui.assets.namehint=Asset Name
|
||||
replaymod.gui.assets.changefile=Change Asset File
|
||||
replaymod.gui.assets.noselection=No Asset selected
|
||||
|
||||
#Object Manager Gui
|
||||
replaymod.gui.objects.properties.anchor=Anchor Point
|
||||
replaymod.gui.objects.properties.position=Position
|
||||
replaymod.gui.objects.properties.scale=Scale
|
||||
replaymod.gui.objects.properties.orientation=Orientation
|
||||
replaymod.gui.objects.properties.opacity=Opacity
|
||||
replaymod.gui.objects.properties.name=Object Name
|
||||
replaymod.gui.objects.empty=No Objects added
|
||||
replaymod.gui.objects.defaultname=New Object
|
||||
replaymod.gui.objects=Custom Objects
|
||||
|
||||
#Errors
|
||||
replaymod.error.unknownrestriction1=This replay cannot be played with your current version.
|
||||
replaymod.error.unknownrestriction2=It tried to enforce %s which is unknown.
|
||||
replaymod.error.negativetime1=Some of your time keyframes are out of order.
|
||||
replaymod.error.negativetime2=Going backwards in time is not supported.
|
||||
replaymod.error.negativetime3=The invalid parts are marked in red.
|
||||
|
||||
#Replay Mod Incompatibility Warning
|
||||
replaymod.gui.modwarning.title=Incompatibility detected
|
||||
replaymod.gui.modwarning.message1=A possible incompatibility between your current
|
||||
replaymod.gui.modwarning.message2=Minecraft version and this Replay has been detected.
|
||||
replaymod.gui.modwarning.message3=Loading this Replay may result in errors or a crash.
|
||||
replaymod.gui.modwarning.name=Mod Name:
|
||||
replaymod.gui.modwarning.id=Mod Id:
|
||||
replaymod.gui.modwarning.missing=Missing Mods
|
||||
replaymod.gui.modwarning.version=Different Mod Versions
|
||||
replaymod.gui.modwarning.version.expected=Expected
|
||||
replaymod.gui.modwarning.version.found=Found
|
||||
|
||||
#OpenEye
|
||||
replaymod.gui.offeropeneye1=Do you want to install OpenEye?
|
||||
replaymod.gui.offeropeneye2=OpenEye is a mod that collects anonymous information
|
||||
replaymod.gui.offeropeneye3=about the mods being run and any crashes that happen.
|
||||
replaymod.gui.offeropeneye4=
|
||||
replaymod.gui.offeropeneye5=OpenEye can be disabled at any time and is completely optional.
|
||||
replaymod.gui.offeropeneye6=More information is available at https://openeye.openmods.info/faq
|
||||
replaymod.gui.offeropeneye7=Clicking Yes will download OpenEye and place it in your mods folder.
|
||||
replaymod.gui.offeropeneye8=It will then be active the next time you start Minecraft.
|
||||
@@ -473,6 +473,27 @@ public class SPTimelineTest {
|
||||
impl.setInterpolatorToDefault(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveKeyframeSimple() {
|
||||
addPosition(0, 0);
|
||||
addPosition(1, 1);
|
||||
setInterpolator(0, new LinearInterpolator(), 1);
|
||||
assertIsLinear(0);
|
||||
|
||||
impl.moveKeyframe(SPPath.POSITION, 0, 2);
|
||||
assertValidInterpolators(SPPath.POSITION, 1);
|
||||
assertIsLinear(0);
|
||||
impl.moveKeyframe(SPPath.POSITION, 2, 0);
|
||||
assertValidInterpolators(SPPath.POSITION, 1);
|
||||
assertIsLinear(0);
|
||||
|
||||
addPosition(2, 1);
|
||||
impl.moveKeyframe(SPPath.POSITION, 0, 3);
|
||||
assertValidInterpolators(SPPath.POSITION, 1);
|
||||
assertIsLinear(0);
|
||||
assertIsLinear(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveKeyframe() {
|
||||
addPosition(1, 0);
|
||||
|
||||
Reference in New Issue
Block a user