Compare commits

..

25 Commits

Author SHA1 Message Date
Jonas Herzig
ed566732df Merge branch '1.8' into 1.8.9
91573d9 Update jGui, ReplayStudio and Translations
d71358b Add confirmation dialog before overwriting path presets (fixes #65)
1a55983 Move addCallback method from integration test Utils to main Utils
18c5bcd Be more cooperative with other mods on the ingame menu (fixes #42)
c054fe8 Register keys for simplepathing only once (fixes #63)
f13297c Fix default interpolator handling in keyframe gui and after loading (fixes #64)
391f304 Show error popup if entity tracker fails to load
0e0eaaa Fix entity tracker being set even if it failed loading (fixes #50)
a34bbbc Fix NPE when spectating a player without a camera entity
60879fb Change local class to be an inner class because Srg2Source breaks with it
dfafbec Load translations from github repo, only reload language not all resource packs
8a6ec51 Add missing tooltips to player overview and various missing chat messages
5677fc5 Re-add warning to replay editor
748a91e Fix missing tooltips on fav/like/dislike buttons in replay center
ee24866 Fix upload replay button not being updated when name/tags change
dba085c Move translations into separate repo
3f0e3e7 Fix NPE when receiving Replay|Restrict messages (fixes #16 GH)
40e1d85 Fix crash when saving the last keyframe in the edit gui (fixes #61)
03aada1 Update Mixin to 0.6.8 (fixes #9 GH)
0c1dc65 Fix interpolator being lost when moving keyframe to the end (fixes #62)
fcbbbc9 Add integration test. Run with ./gradlew runIntegrationTest
fe6ded0 Fix movement of keyframe via GuiEditKeyframe not updating selected keyframe
f58fa8f Fix Login GUI not respecting other GUIs opened on startup
4fc3a31 Fix OpenEye being installed into working dir instead of mcDataDir
2017-05-31 09:36:46 +02:00
Jonas Herzig
91573d9487 Update jGui, ReplayStudio and Translations
jGui:
9e84e72 Fix inversion of empty selection

ReplayStudio:
ff12b1d Remove invalid filter from services files
c6a9325 Proper value wrapping in CatmullRomSplineInterpolator (fixes #8 GH)
2017-05-31 09:16:19 +02:00
Jonas Herzig
d71358bcba Add confirmation dialog before overwriting path presets (fixes #65) 2017-05-21 19:34:46 +02:00
Jonas Herzig
1a55983986 Move addCallback method from integration test Utils to main Utils 2017-05-21 19:34:46 +02:00
Jonas Herzig
18c5bcd469 Be more cooperative with other mods on the ingame menu (fixes #42) 2017-05-21 18:59:29 +02:00
Jonas Herzig
c054fe83f6 Register keys for simplepathing only once (fixes #63) 2017-05-21 17:11:15 +02:00
Jonas Herzig
f13297c5a5 Fix default interpolator handling in keyframe gui and after loading (fixes #64) 2017-05-21 16:29:59 +02:00
Jonas Herzig
391f304c5f Show error popup if entity tracker fails to load 2017-05-21 12:42:13 +02:00
Jonas Herzig
0e0eaaa9a7 Fix entity tracker being set even if it failed loading (fixes #50) 2017-05-21 12:27:20 +02:00
Jonas Herzig
a34bbbcd68 Fix NPE when spectating a player without a camera entity 2017-05-21 11:57:30 +02:00
Jonas Herzig
60879fbbed Change local class to be an inner class because Srg2Source breaks with it 2017-04-21 17:24:43 +02:00
Jonas Herzig
dfafbecf35 Load translations from github repo, only reload language not all resource packs 2017-04-21 16:36:47 +02:00
Jonas Herzig
8a6ec5191b Add missing tooltips to player overview and various missing chat messages 2017-04-21 16:36:47 +02:00
Jonas Herzig
5677fc54cb Re-add warning to replay editor 2017-04-21 16:36:47 +02:00
Jonas Herzig
748a91e7b2 Fix missing tooltips on fav/like/dislike buttons in replay center 2017-04-21 16:36:47 +02:00
Jonas Herzig
ee24866f29 Fix upload replay button not being updated when name/tags change 2017-04-21 16:36:47 +02:00
Jonas Herzig
dba085c700 Move translations into separate repo 2017-04-21 16:36:47 +02:00
Jonas Herzig
3f0e3e725e Fix NPE when receiving Replay|Restrict messages (fixes #16 GH) 2017-04-21 13:38:08 +02:00
johni0702
40e1d850da Fix crash when saving the last keyframe in the edit gui (fixes #61) 2017-04-14 17:31:16 +02:00
johni0702
03aada1f5f Update Mixin to 0.6.8 (fixes #9 GH) 2017-04-14 17:31:16 +02:00
johni0702
0c1dc658ad Fix interpolator being lost when moving keyframe to the end (fixes #62) 2017-04-14 17:31:16 +02:00
johni0702
fcbbbc9ea1 Add integration test. Run with ./gradlew runIntegrationTest 2017-04-14 17:31:15 +02:00
johni0702
fe6ded0143 Fix movement of keyframe via GuiEditKeyframe not updating selected keyframe
Selecting a keyframe, double clicking it to open the gui, changing the
timestamp, saving and then clicking the Add/Remove Keyframe button used to
crash the client. This is fixed by updating the selection whenever the time is
changed.
2017-04-13 18:18:17 +02:00
johni0702
f58fa8f9c2 Fix Login GUI not respecting other GUIs opened on startup
Previously the Login GUI would simply replace the currently active GUI.
Now the currently active GUI is stored and then re-opened after login.
2017-04-13 18:12:07 +02:00
johni0702
4fc3a3166c Fix OpenEye being installed into working dir instead of mcDataDir 2017-04-13 18:06:21 +02:00
104 changed files with 2004 additions and 1389 deletions

View File

@@ -7,7 +7,7 @@ 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 setupCIWorkspace
- ./gradlew :build
archive:
image: plugins/s3

View File

@@ -1 +1 @@
eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgY2xvbmU6CiAgICBpbWFnZTogcGx1Z2lucy9naXQKICAgIHRhZ3M6IHRydWUKICBidWlsZDoKICAgIGltYWdlOiBtYXZlbjozLjMuOS1qZGstOAogICAgY29tbWFuZHM6CiAgICAgIC0gY3VybCAtLWZhaWwgLS1jcmVhdGUtZGlycyAtbyAkSE9NRS8uZ3JhZGxlL2luaXQuZC9taXJyb3IuZ3JhZGxlIGh0dHBzOi8vbWF2ZW4uam9obmkwNzAyLmRlL2NvbmZpZy9ncmFkbGUKICAgICAgLSBjdXJsIC0tZmFpbCAtLWNyZWF0ZS1kaXJzIC1vICRIT01FLy5tMi9zZXR0aW5ncy54bWwgaHR0cHM6Ly9tYXZlbi5qb2huaTA3MDIuZGUvY29uZmlnL21hdmVuCiAgICAgIC0gSkFWQV9PUFRTPS1YbXgxNTE2TSAuL2dyYWRsZXcgc2V0dXBEZWNvbXBXb3Jrc3BhY2UKICAgICAgLSAuL2dyYWRsZXcgOmJ1aWxkCiAgYXJjaGl2ZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBhY2w6IHB1YmxpYy1yZWFkCiAgICByZWdpb246ICJ1cy1lYXN0LTEiCiAgICBidWNrZXQ6ICJyZXBsYXltb2QiCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzb3VyY2U6IGJ1aWxkL2xpYnMvKgogICAgc3RyaXBfcHJlZml4OiBidWlsZC9saWJzLwogICAgdGFyZ2V0OiAvCg.nzPW0_TpMIjnFZZ5Ci439QDKlaDo1a3DCH-VCYUgpx8
eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgY2xvbmU6CiAgICBpbWFnZTogcGx1Z2lucy9naXQKICAgIHRhZ3M6IHRydWUKICBidWlsZDoKICAgIGltYWdlOiBtYXZlbjozLjMuOS1qZGstOAogICAgY29tbWFuZHM6CiAgICAgIC0gY3VybCAtLWZhaWwgLS1jcmVhdGUtZGlycyAtbyAkSE9NRS8uZ3JhZGxlL2luaXQuZC9taXJyb3IuZ3JhZGxlIGh0dHBzOi8vbWF2ZW4uam9obmkwNzAyLmRlL2NvbmZpZy9ncmFkbGUKICAgICAgLSBjdXJsIC0tZmFpbCAtLWNyZWF0ZS1kaXJzIC1vICRIT01FLy5tMi9zZXR0aW5ncy54bWwgaHR0cHM6Ly9tYXZlbi5qb2huaTA3MDIuZGUvY29uZmlnL21hdmVuCiAgICAgIC0gLi9ncmFkbGV3IHNldHVwQ0lXb3Jrc3BhY2UKICAgICAgLSAuL2dyYWRsZXcgOmJ1aWxkCiAgYXJjaGl2ZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBhY2w6IHB1YmxpYy1yZWFkCiAgICByZWdpb246ICJ1cy1lYXN0LTEiCiAgICBidWNrZXQ6ICJyZXBsYXltb2QiCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzb3VyY2U6IGJ1aWxkL2xpYnMvKgogICAgc3RyaXBfcHJlZml4OiBidWlsZC9saWJzLwogICAgdGFyZ2V0OiAvCg.riE6lhwfdNUhI7LO2SxEKFOjzjaX40ne7Ae9gyP75KE

4
.gitmodules vendored
View File

@@ -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

View File

@@ -1,7 +1,5 @@
import groovy.json.JsonOutput
import java.util.zip.ZipInputStream
buildscript {
repositories {
mavenCentral()
@@ -20,7 +18,7 @@ buildscript {
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:2.2-SNAPSHOT'
classpath 'org.spongepowered:mixingradle:0.4-SNAPSHOT'
classpath 'org.spongepowered:mixingradle:0.3-SNAPSHOT'
}
}
@@ -36,9 +34,9 @@ archivesBaseName = "replaymod"
minecraft {
coreMod = 'com.replaymod.core.LoadingPlugin'
version = '1.9.4-12.17.0.1976'
version = '1.8.9-11.15.1.1722'
runDir = "eclipse"
mappings = "snapshot_20160518"
mappings = "stable_22"
replace '@MOD_VERSION@', project.version
replace '@MC_VERSION@', project.minecraft.version
}
@@ -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
@@ -138,7 +115,6 @@ jar {
manifest {
attributes 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker',
'TweakOrder': '0',
'FMLCorePluginContainsFMLMod': 'true',
'FMLAT': 'replaymod_at.cfg'
}
}
@@ -189,6 +165,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 +183,30 @@ setupDecompWorkspace.dependsOn copySrg
setupDevWorkspace.dependsOn copySrg
project.tasks.idea.dependsOn copySrg
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

Submodule jGui updated: 26af916257...1e92ad85df

View 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()]));
}
}

View File

@@ -0,0 +1,342 @@
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();
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
addCallback(future, success -> {
FMLCommonHandler.instance().bus().unregister(this);
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) {
FMLCommonHandler.instance().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);
}
}
}
}
FMLCommonHandler.instance().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) {
FMLCommonHandler.instance().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);
}
}
}
}
FMLCommonHandler.instance().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;
}
FMLCommonHandler.instance().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));
}
}
FMLCommonHandler.instance().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);
}
}
});
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
});
}
}

View 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));
});
}
}

View 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();
}
}
}

View 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();
}
}

View File

@@ -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),
});
}
}

View File

@@ -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(),
});
}
}

View File

@@ -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()
});
}
}

View File

@@ -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);
}
});
});
}
}

View File

@@ -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));
});
}
}

View File

@@ -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.GuiSelectWorld;
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(GuiSelectWorld.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());
});
});
});
}
}

View File

@@ -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());
});
}
}

View File

@@ -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));
});
}
}

View File

@@ -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());
}));
}
}

View File

@@ -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));
});
}
}

View File

@@ -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.thePlayer) {
future.set(null);
}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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));
});
}
}
}

View File

@@ -2,7 +2,7 @@ package com.replaymod.compat;
import com.replaymod.compat.optifine.DisableFastRender;
import com.replaymod.compat.shaders.ShaderBeginRender;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.EventBus;
@@ -16,7 +16,7 @@ public class ReplayModCompat {
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
EventBus bus = MinecraftForge.EVENT_BUS;
EventBus bus = FMLCommonHandler.instance().bus();
bus.register(new ShaderBeginRender());
bus.register(new DisableFastRender());
}

View File

@@ -16,6 +16,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
public class KeyBindingRegistry {
private Map<String, KeyBinding> keyBindings = new HashMap<String, KeyBinding>();
@@ -85,7 +86,7 @@ public class KeyBindingRegistry {
CrashReport crashReport = CrashReport.makeCrashReport(cause, "Handling Key Binding");
CrashReportCategory category = crashReport.makeCategory("Key Binding");
category.addCrashSection("Key Binding", keyBinding);
category.setDetail("Handler", runnable::toString);
category.addCrashSectionCallable("Handler", runnable::toString);
throw new ReportedException(crashReport);
}
}
@@ -100,7 +101,12 @@ public class KeyBindingRegistry {
CrashReport crashReport = CrashReport.makeCrashReport(cause, "Handling Raw Key Binding");
CrashReportCategory category = crashReport.makeCategory("Key Binding");
category.addCrashSection("Key Code", keyCode);
category.setDetail("Handler", runnable::toString);
category.addCrashSectionCallable("Handler", new Callable() {
@Override
public Object call() throws Exception {
return runnable;
}
});
throw new ReportedException(crashReport);
}
}

View File

@@ -6,6 +6,7 @@ import com.replaymod.core.gui.GuiReplaySettings;
import com.replaymod.core.gui.RestoreReplayGui;
import com.replaymod.core.handler.MainMenuHandler;
import com.replaymod.core.utils.OpenGLUtils;
import com.replaymod.render.utils.SoundHandler;
import com.replaymod.replaystudio.util.I18n;
import de.johni0702.minecraft.gui.container.GuiScreen;
import lombok.Getter;
@@ -13,11 +14,15 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.ChatStyle;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.*;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
@@ -67,6 +72,8 @@ public class ReplayMod {
@Deprecated
public static Configuration config;
@Deprecated
public static SoundHandler soundHandler = new SoundHandler();
private final KeyBindingRegistry keyBindingRegistry = new KeyBindingRegistry();
private final SettingsRegistry settingsRegistry = new SettingsRegistry();
@@ -109,7 +116,7 @@ public class ReplayMod {
new MainMenuHandler().register();
MinecraftForge.EVENT_BUS.register(keyBindingRegistry);
FMLCommonHandler.instance().bus().register(keyBindingRegistry);
getKeyBindingRegistry().registerKeyBinding("replaymod.input.settings", 0, () -> {
new GuiReplaySettings(null, settingsRegistry).display();
@@ -190,13 +197,13 @@ public class ReplayMod {
private void printToChat(boolean warning, String message, Object... args) {
if (getSettingsRegistry().get(Setting.NOTIFICATIONS)) {
// Some nostalgia: "§8[§6Replay Mod§8]§r Your message goes here"
Style coloredDarkGray = new Style().setColor(TextFormatting.DARK_GRAY);
Style coloredGold = new Style().setColor(TextFormatting.GOLD);
ITextComponent text = new TextComponentString("[").setStyle(coloredDarkGray)
.appendSibling(new TextComponentTranslation("replaymod.title").setStyle(coloredGold))
.appendSibling(new TextComponentString("] "))
.appendSibling(new TextComponentTranslation(message, args).setStyle(new Style()
.setColor(warning ? TextFormatting.RED : TextFormatting.DARK_GREEN)));
ChatStyle coloredDarkGray = new ChatStyle().setColor(EnumChatFormatting.DARK_GRAY);
ChatStyle coloredGold = new ChatStyle().setColor(EnumChatFormatting.GOLD);
IChatComponent text = new ChatComponentText("[").setChatStyle(coloredDarkGray)
.appendSibling(new ChatComponentTranslation("replaymod.title").setChatStyle(coloredGold))
.appendSibling(new ChatComponentText("] "))
.appendSibling(new ChatComponentTranslation(message, args).setChatStyle(new ChatStyle()
.setColor(warning ? EnumChatFormatting.RED : EnumChatFormatting.DARK_GREEN)));
// Send message to chat GUI
// The ingame GUI is initialized at startup, therefore this is possible before the client is connected
mc.ingameGUI.getChatGUI().printChatMessage(text);

View File

@@ -2,8 +2,8 @@ package com.replaymod.core;
import com.replaymod.core.events.SettingsChangedEvent;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.FMLCommonHandler;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -83,7 +83,7 @@ public class SettingsRegistry {
throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported.");
}
settings.put(key, value);
MinecraftForge.EVENT_BUS.post(new SettingsChangedEvent(this, key));
FMLCommonHandler.instance().bus().post(new SettingsChangedEvent(this, key));
}
public void save() {

View File

@@ -6,6 +6,8 @@ import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
/**
* Moves certain buttons on the main menu upwards so we can inject our own.
*/
@@ -16,12 +18,14 @@ public class MainMenuHandler {
@SubscribeEvent
public void onInit(GuiScreenEvent.InitGuiEvent.Post event) {
if (event.getGui() instanceof GuiMainMenu) {
for (GuiButton button : event.getButtonList()) {
if (event.gui instanceof GuiMainMenu) {
@SuppressWarnings("unchecked")
List<GuiButton> buttonList = event.buttonList;
for (GuiButton button : buttonList) {
// Buttons that aren't in a rectangle directly above our space don't need moving
if (button.xPosition + button.width < event.getGui().width / 2 - 100
|| button.xPosition > event.getGui().width / 2 + 100
|| button.yPosition > event.getGui().height / 4 + 10 + 4 * 24) continue;
if (button.xPosition + button.width < event.gui.width / 2 - 100
|| button.xPosition > event.gui.width / 2 + 100
|| button.yPosition > event.gui.height / 4 + 10 + 4 * 24) continue;
// Move button up to make space for two rows of buttons
// and then move back down by 10 to compensate for the space to the exit button that was already there
button.yPosition -= 2 * 24 - 10;

View File

@@ -2,7 +2,7 @@ package com.replaymod.core.utils;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.VertexBuffer;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -35,7 +35,7 @@ public class OpenGLUtils {
float f5 = 1.0F / textureHeight;
Tessellator tessellator = Tessellator.getInstance();
VertexBuffer vertexBuffer = tessellator.getBuffer();
WorldRenderer vertexBuffer = tessellator.getWorldRenderer();
GlStateManager.translate(x+(width/2), y+(width/2), 0);
GlStateManager.rotate(rotation, 0, 0, 1);
vertexBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);

View File

@@ -1,7 +1,7 @@
package com.replaymod.core.utils;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.SPacketCustomPayload;
import net.minecraft.network.play.server.S3FPacketCustomPayload;
/**
* Restrictions set by the server,
@@ -14,7 +14,7 @@ public class Restrictions {
private boolean onlyFirstPerson;
private boolean onlyRecordingPlayer;
public String handle(SPacketCustomPayload packet) {
public String handle(S3FPacketCustomPayload packet) {
PacketBuffer buffer = packet.getBufferData();
while (buffer.isReadable()) {
String name = buffer.readStringFromBuffer(64);

View File

@@ -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;
@@ -135,7 +138,7 @@ public class Utils {
}
public static ResourceLocation getResourceLocationForPlayerUUID(UUID uuid) {
NetworkPlayerInfo info = getMinecraft().getConnection().getPlayerInfo(uuid);
NetworkPlayerInfo info = getMinecraft().getNetHandler().getPlayerInfo(uuid);
ResourceLocation skinLocation;
if (info != null && info.hasLocationSkin()) {
skinLocation = info.getLocationSkin();
@@ -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();

View File

@@ -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);
}
});
}

View File

@@ -8,8 +8,11 @@ import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
public class GuiHandler {
private static final int BUTTON_REPLAY_EDITOR = 17890237;
@@ -20,28 +23,31 @@ public class GuiHandler {
}
public void register() {
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
if (!(event.getGui() instanceof GuiMainMenu)) {
if (!(event.gui instanceof GuiMainMenu)) {
return;
}
GuiButton button = new GuiButton(BUTTON_REPLAY_EDITOR, event.getGui().width / 2 + 2,
event.getGui().height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayeditor"));
@SuppressWarnings("unchecked")
List<GuiButton> buttonList = event.buttonList;
GuiButton button = new GuiButton(BUTTON_REPLAY_EDITOR, event.gui.width / 2 + 2,
event.gui.height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayeditor"));
button.width = button.width / 2 - 2;
event.getButtonList().add(button);
buttonList.add(button);
}
@SubscribeEvent
public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) {
if(!event.getButton().enabled) return;
if(!event.button.enabled) return;
if (event.getGui() instanceof GuiMainMenu) {
if (event.getButton().id == BUTTON_REPLAY_EDITOR) {
new GuiReplayEditor(GuiScreen.wrap(event.getGui()), mod.getCore()).display();
if (event.gui instanceof GuiMainMenu) {
if (event.button.id == BUTTON_REPLAY_EDITOR) {
new GuiReplayEditor(GuiScreen.wrap(event.gui), mod.getCore()).display();
}
}
}

View File

@@ -9,7 +9,7 @@ import de.johni0702.minecraft.gui.element.GuiImage;
import de.johni0702.minecraft.gui.element.IGuiImage;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import net.minecraft.client.settings.GameSettings;
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;
@@ -40,7 +40,7 @@ public class FullBrightness implements Extra {
}
});
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
}
@SubscribeEvent

View File

@@ -17,7 +17,7 @@ import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.LayoutData;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.Dimension;
@@ -35,7 +35,7 @@ public class HotkeyButtons implements Extra {
public void register(ReplayMod mod) throws Exception {
this.mod = mod;
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
}
@SubscribeEvent

View File

@@ -1,106 +1,110 @@
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.client.resources.data.IMetadataSerializer;
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");
public Set getResourceDomains() {
return ImmutableSet.of("replaymod");
}
@Override
public <T extends IMetadataSection> T getPackMetadata(MetadataSerializer metadataSerializer, String metadataSectionName) throws IOException {
public IMetadataSection getPackMetadata(IMetadataSerializer p_135058_1_, String p_135058_2_) throws IOException {
return null;
}

View File

@@ -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));

View File

@@ -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);
}
}
}

View File

@@ -15,6 +15,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.input.Keyboard;
@@ -57,6 +58,7 @@ public class PlayerOverview implements Extra {
}
});
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
RenderManager renderManager = mod.getMinecraft().getRenderManager();

View File

@@ -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;
@@ -16,8 +21,7 @@ import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EnumPlayerModelParts;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.potion.Potion;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
@@ -37,21 +41,22 @@ 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
public void onClick() {
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F));
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.create(BUTTON_SOUND, 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));
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.create(BUTTON_SOUND, 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);
@@ -140,7 +145,7 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable {
}
private static boolean isSpectator(EntityPlayer e) {
return e.isInvisible() && e.getActivePotionEffect(MobEffects.INVISIBILITY) == null;
return e.isInvisible() && e.getActivePotionEffect(Potion.invisibility) == null;
}
private static final class PlayerComparator implements Comparator<EntityPlayer> {

View File

@@ -17,8 +17,8 @@ public class YoutubeUpload implements Extra {
@SubscribeEvent
public void onGuiOpen(GuiScreenEvent.InitGuiEvent.Post event) {
if (GuiScreen.from(event.getGui()) instanceof GuiRenderingDone) {
GuiRenderingDone gui = (GuiRenderingDone) GuiScreen.from(event.getGui());
if (GuiScreen.from(event.gui) instanceof GuiRenderingDone) {
GuiRenderingDone gui = (GuiRenderingDone) GuiScreen.from(event.gui);
// Check if there already is a youtube button
if (gui.actionsPanel.getChildren().stream().anyMatch(it -> it instanceof YoutubeButton)) {
return; // Button already added

View File

@@ -12,8 +12,8 @@ import com.replaymod.replaystudio.replay.ReplayFile;
import com.replaymod.replaystudio.replay.ZipReplayFile;
import com.replaymod.replaystudio.studio.ReplayStudio;
import de.johni0702.minecraft.gui.container.GuiScreen;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
@@ -74,7 +74,7 @@ public class ReplayModOnline {
}
new GuiHandler(this).register();
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
}
@Mod.EventHandler
@@ -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());
}
}
}

View File

@@ -2,8 +2,7 @@ package com.replaymod.online.api.replay.holders;
public enum MinecraftVersion {
MC_1_8("Minecraft 1.8", "1.8"),
MC_1_9_4("Minecraft 1.9.4", "1.9.4");
MC_1_8("Minecraft 1.8", "1.8");
private String niceName, apiName;

View File

@@ -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)

View File

@@ -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"));
}

View File

@@ -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() {

View File

@@ -14,9 +14,11 @@ import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.io.File;
import java.util.List;
public class GuiHandler {
private static final int BUTTON_REPLAY_CENTER = 17890236;
@@ -28,23 +30,26 @@ public class GuiHandler {
}
public void register() {
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
if (!(event.getGui() instanceof GuiMainMenu)) {
if (!(event.gui instanceof GuiMainMenu)) {
return;
}
GuiButton button = new GuiButton(BUTTON_REPLAY_CENTER, event.getGui().width / 2 - 100,
event.getGui().height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replaycenter"));
event.getButtonList().add(button);
@SuppressWarnings("unchecked")
List<GuiButton> buttonList = event.buttonList;
GuiButton button = new GuiButton(BUTTON_REPLAY_CENTER, event.gui.width / 2 - 100,
event.gui.height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replaycenter"));
buttonList.add(button);
}
@SubscribeEvent
public void injectIntoReplayViewer(GuiScreenEvent.InitGuiEvent.Post event) {
AbstractGuiScreen guiScreen = GuiScreen.from(event.getGui());
AbstractGuiScreen guiScreen = GuiScreen.from(event.gui);
if (!(guiScreen instanceof GuiReplayViewer)) {
return;
}
@@ -70,15 +75,15 @@ public class GuiHandler {
@SubscribeEvent
public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) {
if(!event.getButton().enabled) return;
if(!event.button.enabled) return;
if (event.getGui() instanceof GuiMainMenu) {
if (event.getButton().id == BUTTON_REPLAY_CENTER) {
if (event.gui instanceof GuiMainMenu) {
if (event.button.id == BUTTON_REPLAY_CENTER) {
GuiReplayCenter replayCenter = new GuiReplayCenter(mod);
if (mod.isLoggedIn()) {
replayCenter.display();
} else {
new GuiLoginPrompt(mod.getApiClient(), GuiScreen.wrap(event.getGui()), replayCenter, true).display();
new GuiLoginPrompt(mod.getApiClient(), GuiScreen.wrap(event.gui), replayCenter, true).display();
}
}
}

View File

@@ -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() {

View File

@@ -11,7 +11,7 @@ import com.replaymod.replaystudio.pathing.path.Keyframe;
import com.replaymod.replaystudio.pathing.path.Path;
import com.replaymod.replaystudio.pathing.path.Timeline;
import net.minecraft.client.Minecraft;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import javax.annotation.Nullable;
@@ -65,7 +65,7 @@ public abstract class AbstractTimelinePlayer {
}
replayHandler.getReplaySender().setSyncModeAndWait();
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
lastTime = 0;
mc.timer = new ReplayTimer(mc.timer);
mc.timer.timerSpeed = 1;
@@ -87,7 +87,7 @@ public abstract class AbstractTimelinePlayer {
mc.timer = ((ReplayTimer) mc.timer).getWrapped();
replayHandler.getReplaySender().setReplaySpeed(0);
replayHandler.getReplaySender().setAsyncMode(true);
MinecraftForge.EVENT_BUS.unregister(this);
FMLCommonHandler.instance().bus().unregister(this);
return;
}
long time = getTimePassed();

View File

@@ -2,7 +2,7 @@ package com.replaymod.pathing.player;
import com.replaymod.core.utils.WrappedTimer;
import net.minecraft.util.Timer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.Event;
/**
@@ -20,7 +20,7 @@ public class ReplayTimer extends WrappedTimer {
copy(this, state); // Save our current state
super.updateTimer(); // Update current state
copy(state, this); // Restore our old state
MinecraftForge.EVENT_BUS.post(new UpdatedEvent());
FMLCommonHandler.instance().bus().post(new UpdatedEvent());
}
public Timer getWrapped() {

View File

@@ -4,8 +4,10 @@ import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.Restrictions;
import com.replaymod.recording.handler.ConnectionEventHandler;
import com.replaymod.recording.packet.PacketListener;
import net.minecraftforge.common.MinecraftForge;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler;
import net.minecraft.network.NetworkManager;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
@@ -51,12 +53,15 @@ public class ReplayModRecording {
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
EventBus bus = MinecraftForge.EVENT_BUS;
EventBus bus = FMLCommonHandler.instance().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);
}

View File

@@ -40,7 +40,7 @@ public class GuiRecordingOverlay {
*/
@SubscribeEvent
public void renderRecordingIndicator(RenderGameOverlayEvent.Post event) {
if (event.getType() != RenderGameOverlayEvent.ElementType.ALL) return;
if (event.type != RenderGameOverlayEvent.ElementType.ALL) return;
if (settingsRegistry.get(Setting.INDICATOR)) {
FontRenderer fontRenderer = mc.fontRendererObj;
fontRenderer.drawString(I18n.format("replaymod.gui.recording").toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff);

View File

@@ -1,22 +1,24 @@
package com.replaymod.recording.handler;
import com.replaymod.recording.packet.PacketListener;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.network.Packet;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.*;
import net.minecraft.network.play.server.S14PacketEntity.S17PacketEntityLookMove;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.BlockPos;
import net.minecraft.util.MathHelper;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.minecart.MinecartInteractEvent;
import net.minecraftforge.event.entity.player.PlayerSleepInBedEvent;
import net.minecraftforge.event.entity.player.PlayerUseItemEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.ItemPickupEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
@@ -30,24 +32,24 @@ public class RecordingEventHandler {
private final PacketListener packetListener;
private Double lastX, lastY, lastZ;
private ItemStack[] playerItems = new ItemStack[6];
private ItemStack[] playerItems = new ItemStack[5];
private int ticksSinceLastCorrection;
private boolean wasSleeping;
private int lastRiding = -1;
private Integer rotationYawHeadBefore;
private boolean wasHandActive;
private EnumHand lastActiveHand;
public RecordingEventHandler(PacketListener packetListener) {
this.packetListener = packetListener;
}
public void register() {
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
((RecordingEventSender) mc.renderGlobal).setRecordingEventHandler(this);
}
public void unregister() {
FMLCommonHandler.instance().bus().unregister(this);
MinecraftForge.EVENT_BUS.unregister(this);
RecordingEventSender recordingEventSender = ((RecordingEventSender) mc.renderGlobal);
if (recordingEventSender.getRecordingEventHandler() == this) {
@@ -57,7 +59,7 @@ public class RecordingEventHandler {
public void onPlayerJoin() {
try {
packetListener.save(new SPacketSpawnPlayer(mc.thePlayer));
packetListener.save(spawnPlayer(mc.thePlayer));
} catch(Exception e1) {
e1.printStackTrace();
}
@@ -65,28 +67,39 @@ public class RecordingEventHandler {
public void onPlayerRespawn() {
try {
packetListener.save(new SPacketSpawnPlayer(mc.thePlayer));
packetListener.save(spawnPlayer(mc.thePlayer));
} catch(Exception e) {
e.printStackTrace();
}
}
public void onClientSound(SoundEvent sound, SoundCategory category,
double x, double y, double z, float volume, float pitch) {
private S0CPacketSpawnPlayer spawnPlayer(EntityPlayer player) {
try {
// Send to all other players in ServerWorldEventHandler#playSoundToAllNearExcept
packetListener.save(new SPacketSoundEffect(sound, category, x, y, z, volume, pitch));
} catch(Exception e) {
e.printStackTrace();
}
}
S0CPacketSpawnPlayer packet = new S0CPacketSpawnPlayer();
public void onClientEffect(int type, BlockPos pos, int data) {
try {
// Send to all other players in ServerWorldEventHandler#playEvent
packetListener.save(new SPacketEffect(type, pos, data, false));
ByteBuf bb = Unpooled.buffer();
PacketBuffer pb = new PacketBuffer(bb);
pb.writeVarIntToBuffer(player.getEntityId());
pb.writeUuid(EntityPlayer.getUUID(player.getGameProfile()));
pb.writeInt(MathHelper.floor_double(player.posX * 32.0D));
pb.writeInt(MathHelper.floor_double(player.posY * 32.0D));
pb.writeInt(MathHelper.floor_double(player.posZ * 32.0D));
pb.writeByte((byte) ((int) (player.rotationYaw * 256.0F / 360.0F)));
pb.writeByte((byte) ((int) (player.rotationPitch * 256.0F / 360.0F)));
ItemStack itemstack = player.inventory.getCurrentItem();
pb.writeShort(itemstack == null ? 0 : Item.getIdFromItem(itemstack.getItem()));
player.getDataWatcher().writeTo(pb);
packet.readPacketData(pb);
return packet;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
@@ -118,14 +131,19 @@ public class RecordingEventHandler {
lastZ = e.player.posZ;
Packet packet;
if (force || Math.abs(dx) > 8.0 || Math.abs(dy) > 8.0 || Math.abs(dz) > 8.0) {
packet = new SPacketEntityTeleport(e.player);
if(force || Math.abs(dx) > 4.0 || Math.abs(dy) > 4.0 || Math.abs(dz) > 4.0) {
int x = MathHelper.floor_double(e.player.posX * 32.0D);
int y = MathHelper.floor_double(e.player.posY * 32.0D);
int z = MathHelper.floor_double(e.player.posZ * 32.0D);
byte yaw = (byte) ((int) (e.player.rotationYaw * 256.0F / 360.0F));
byte pitch = (byte) ((int) (e.player.rotationPitch * 256.0F / 360.0F));
packet = new S18PacketEntityTeleport(e.player.getEntityId(), x, y, z, yaw, pitch, e.player.onGround);
} else {
byte newYaw = (byte) ((int) (e.player.rotationYaw * 256.0F / 360.0F));
byte newPitch = (byte) ((int) (e.player.rotationPitch * 256.0F / 360.0F));
packet = new SPacketEntity.S17PacketEntityLookMove(e.player.getEntityId(),
(short) Math.round(dx * 4096), (short) Math.round(dy * 4096), (short) Math.round(dz * 4096),
packet = new S17PacketEntityLookMove(e.player.getEntityId(),
(byte) Math.round(dx * 32), (byte) Math.round(dy * 32), (byte) Math.round(dz * 32),
newYaw, newPitch, e.player.onGround);
}
@@ -135,16 +153,37 @@ public class RecordingEventHandler {
int rotationYawHead = ((int)(e.player.rotationYawHead * 256.0F / 360.0F));
if(!Objects.equals(rotationYawHead, rotationYawHeadBefore)) {
packetListener.save(new SPacketEntityHeadLook(e.player, (byte) rotationYawHead));
S19PacketEntityHeadLook head = new S19PacketEntityHeadLook();
ByteBuf bb1 = Unpooled.buffer();
PacketBuffer pb1 = new PacketBuffer(bb1);
pb1.writeVarIntToBuffer(e.player.getEntityId());
pb1.writeByte(rotationYawHead);
head.readPacketData(pb1);
packetListener.save(head);
rotationYawHeadBefore = rotationYawHead;
}
packetListener.save(new SPacketEntityVelocity(e.player.getEntityId(), e.player.motionX, e.player.motionY, e.player.motionZ));
S12PacketEntityVelocity vel = new S12PacketEntityVelocity(e.player.getEntityId(), e.player.motionX, e.player.motionY, e.player.motionZ);
packetListener.save(vel);
//Animation Packets
//Swing Animation
if (e.player.isSwingInProgress && e.player.swingProgressInt == -1) {
packetListener.save(new SPacketAnimation(e.player, e.player.swingingHand == EnumHand.MAIN_HAND ? 0 : 3));
if(e.player.swingProgressInt == 1) {
S0BPacketAnimation pac = new S0BPacketAnimation();
ByteBuf bb = Unpooled.buffer();
PacketBuffer pb = new PacketBuffer(bb);
pb.writeVarIntToBuffer(e.player.getEntityId());
pb.writeByte(0);
pac.readPacketData(pb);
packetListener.save(pac);
}
/*
@@ -168,62 +207,75 @@ public class RecordingEventHandler {
*/
//Inventory Handling
if (playerItems[0] != mc.thePlayer.getHeldItem(EnumHand.MAIN_HAND)) {
playerItems[0] = mc.thePlayer.getHeldItem(EnumHand.MAIN_HAND);
packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.MAINHAND, playerItems[0]));
if(playerItems[0] != mc.thePlayer.getHeldItem()) {
playerItems[0] = mc.thePlayer.getHeldItem();
S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 0, playerItems[0]);
packetListener.save(pee);
}
if (playerItems[1] != mc.thePlayer.getHeldItem(EnumHand.OFF_HAND)) {
playerItems[1] = mc.thePlayer.getHeldItem(EnumHand.OFF_HAND);
packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.OFFHAND, playerItems[1]));
if(playerItems[1] != mc.thePlayer.inventory.armorInventory[0]) {
playerItems[1] = mc.thePlayer.inventory.armorInventory[0];
S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 1, playerItems[1]);
packetListener.save(pee);
}
if (playerItems[2] != mc.thePlayer.inventory.armorInventory[0]) {
playerItems[2] = mc.thePlayer.inventory.armorInventory[0];
packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.FEET, playerItems[2]));
if(playerItems[2] != mc.thePlayer.inventory.armorInventory[1]) {
playerItems[2] = mc.thePlayer.inventory.armorInventory[1];
S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 2, playerItems[2]);
packetListener.save(pee);
}
if (playerItems[3] != mc.thePlayer.inventory.armorInventory[1]) {
playerItems[3] = mc.thePlayer.inventory.armorInventory[1];
packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.LEGS, playerItems[3]));
if(playerItems[3] != mc.thePlayer.inventory.armorInventory[2]) {
playerItems[3] = mc.thePlayer.inventory.armorInventory[2];
S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 3, playerItems[3]);
packetListener.save(pee);
}
if (playerItems[4] != mc.thePlayer.inventory.armorInventory[2]) {
playerItems[4] = mc.thePlayer.inventory.armorInventory[2];
packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.CHEST, playerItems[4]));
}
if (playerItems[5] != mc.thePlayer.inventory.armorInventory[3]) {
playerItems[5] = mc.thePlayer.inventory.armorInventory[3];
packetListener.save(new SPacketEntityEquipment(e.player.getEntityId(), EntityEquipmentSlot.HEAD, playerItems[5]));
if(playerItems[4] != mc.thePlayer.inventory.armorInventory[3]) {
playerItems[4] = mc.thePlayer.inventory.armorInventory[3];
S04PacketEntityEquipment pee = new S04PacketEntityEquipment(e.player.getEntityId(), 4, playerItems[4]);
packetListener.save(pee);
}
//Leaving Ride
if((!mc.thePlayer.isRiding() && lastRiding != -1) ||
(mc.thePlayer.isRiding() && lastRiding != mc.thePlayer.getRidingEntity().getEntityId())) {
(mc.thePlayer.isRiding() && lastRiding != mc.thePlayer.ridingEntity.getEntityId())) {
if(!mc.thePlayer.isRiding()) {
lastRiding = -1;
} else {
lastRiding = mc.thePlayer.getRidingEntity().getEntityId();
lastRiding = mc.thePlayer.ridingEntity.getEntityId();
}
packetListener.save(new SPacketEntityAttach(e.player, e.player.getRidingEntity()));
S1BPacketEntityAttach pea = new S1BPacketEntityAttach();
ByteBuf buf = Unpooled.buffer();
PacketBuffer pbuf = new PacketBuffer(buf);
pbuf.writeInt(e.player.getEntityId());
pbuf.writeInt(lastRiding);
pbuf.writeBoolean(false);
pea.readPacketData(pbuf);
packetListener.save(pea);
}
//Sleeping
if(!mc.thePlayer.isPlayerSleeping() && wasSleeping) {
packetListener.save(new SPacketAnimation(e.player, 2));
wasSleeping = false;
}
S0BPacketAnimation pac = new S0BPacketAnimation();
// Active hand (e.g. eating, drinking, blocking)
if (mc.thePlayer.isHandActive() ^ wasHandActive || mc.thePlayer.getActiveHand() != lastActiveHand) {
wasHandActive = mc.thePlayer.isHandActive();
lastActiveHand = mc.thePlayer.getActiveHand();
EntityDataManager dataManager = new EntityDataManager(null);
int state = (wasHandActive ? 1 : 0) | (lastActiveHand == EnumHand.OFF_HAND ? 2 : 0);
dataManager.register(EntityLiving.HAND_STATES, (byte) state);
packetListener.save(new SPacketEntityMetadata(mc.thePlayer.getEntityId(), dataManager, true));
ByteBuf bb = Unpooled.buffer();
PacketBuffer pb = new PacketBuffer(bb);
pb.writeVarIntToBuffer(e.player.getEntityId());
pb.writeByte(2);
pac.readPacketData(pb);
packetListener.save(pac);
wasSleeping = false;
}
} catch(Exception e1) {
@@ -234,7 +286,27 @@ public class RecordingEventHandler {
@SubscribeEvent
public void onPickupItem(ItemPickupEvent event) {
try {
packetListener.save(new SPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId()));
packetListener.save(new S0DPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId()));
} catch(Exception e) {
e.printStackTrace();
}
}
@SubscribeEvent
public void onStartEating(PlayerUseItemEvent.Start event) {
try {
if(!event.entityPlayer.isEating()) return;
S0BPacketAnimation packet = new S0BPacketAnimation();
ByteBuf bb = Unpooled.buffer();
PacketBuffer pb = new PacketBuffer(bb);
pb.writeVarIntToBuffer(event.entityPlayer.getEntityId());
pb.writeByte(3);
packet.readPacketData(pb);
packetListener.save(packet);
} catch(Exception e) {
e.printStackTrace();
}
@@ -243,11 +315,21 @@ public class RecordingEventHandler {
@SubscribeEvent
public void onSleep(PlayerSleepInBedEvent event) {
try {
if (event.getEntityPlayer() != mc.thePlayer) {
if(event.entityPlayer != mc.thePlayer) {
return;
}
packetListener.save(new SPacketUseBed(event.getEntityPlayer(), event.getPos()));
S0APacketUseBed pub = new S0APacketUseBed();
ByteBuf buf = Unpooled.buffer();
PacketBuffer pbuf = new PacketBuffer(buf);
pbuf.writeVarIntToBuffer(event.entityPlayer.getEntityId());
pbuf.writeBlockPos(event.pos);
pub.readPacketData(pbuf);
packetListener.save(pub);
wasSleeping = true;
@@ -259,13 +341,24 @@ public class RecordingEventHandler {
@SubscribeEvent
public void enterMinecart(MinecartInteractEvent event) {
try {
if(event.getEntity() != mc.thePlayer) {
if(event.player != mc.thePlayer) {
return;
}
packetListener.save(new SPacketEntityAttach(event.getPlayer(), event.getMinecart()));
S1BPacketEntityAttach pea = new S1BPacketEntityAttach();
lastRiding = event.getMinecart().getEntityId();
ByteBuf buf = Unpooled.buffer();
PacketBuffer pbuf = new PacketBuffer(buf);
pbuf.writeInt(event.player.getEntityId());
pbuf.writeInt(event.minecart.getEntityId());
pbuf.writeBoolean(false);
pea.readPacketData(pbuf);
packetListener.save(pea);
lastRiding = event.minecart.getEntityId();
} catch(Exception e) {
e.printStackTrace();
}
@@ -274,7 +367,7 @@ public class RecordingEventHandler {
public void onBlockBreakAnim(int breakerId, BlockPos pos, int progress) {
EntityPlayer thePlayer = mc.thePlayer;
if (thePlayer != null && breakerId == thePlayer.getEntityId()) {
packetListener.save(new SPacketBlockBreakAnim(breakerId, pos, progress));
packetListener.save(new S25PacketBlockBreakAnim(breakerId, pos, progress));
}
}

View File

@@ -4,8 +4,8 @@ import com.replaymod.recording.handler.RecordingEventHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.network.play.server.SPacketPlayerListItem;
import net.minecraft.network.play.server.SPacketRespawn;
import net.minecraft.network.play.server.S07PacketRespawn;
import net.minecraft.network.play.server.S38PacketPlayerListItem;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@@ -36,12 +36,12 @@ public abstract class MixinNetHandlerPlayClient {
* @param ci Callback info
*/
@Inject(method = "handlePlayerListItem", at=@At("HEAD"))
public void recordOwnJoin(SPacketPlayerListItem packet, CallbackInfo ci) {
public void recordOwnJoin(S38PacketPlayerListItem packet, CallbackInfo ci) {
if (gameController.thePlayer == null) return;
RecordingEventHandler handler = getRecordingEventHandler();
if (handler != null && packet.getAction() == SPacketPlayerListItem.Action.ADD_PLAYER) {
for (SPacketPlayerListItem.AddPlayerData data : packet.getEntries()) {
if (handler != null && packet.getAction() == S38PacketPlayerListItem.Action.ADD_PLAYER) {
for (S38PacketPlayerListItem.AddPlayerData data : packet.getEntries()) {
if (data.getProfile() == null || data.getProfile().getId() == null) continue;
// Only add spawn packet for our own player and only if he isn't known yet
if (data.getProfile().getId().equals(Minecraft.getMinecraft().thePlayer.getGameProfile().getId())
@@ -55,12 +55,12 @@ public abstract class MixinNetHandlerPlayClient {
/**
* Record the own player entity respawning.
* We cannot use the {@link net.minecraftforge.event.entity.EntityJoinWorldEvent} because that would also include
* the first spawn which is already handled by {@link #recordOwnJoin(SPacketPlayerListItem, CallbackInfo)}.
* the first spawn which is already handled by {@link #recordOwnJoin(S38PacketPlayerListItem, CallbackInfo)}.
* @param packet The packet
* @param ci Callback info
*/
@Inject(method = "handleRespawn", at=@At("RETURN"))
public void recordOwnRespawn(SPacketRespawn packet, CallbackInfo ci) {
public void recordOwnRespawn(S07PacketRespawn packet, CallbackInfo ci) {
RecordingEventHandler handler = getRecordingEventHandler();
if (handler != null) {
handler.onPlayerRespawn();

View File

@@ -1,28 +0,0 @@
package com.replaymod.recording.mixin;
import com.replaymod.recording.handler.RecordingEventHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerControllerMP;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(PlayerControllerMP.class)
public abstract class MixinPlayerControllerMP implements RecordingEventHandler.RecordingEventSender {
@Shadow
private Minecraft mc;
// Redirects the call to playEvent without the initial player argument to the method with that argument
// The new method will then play it and (if applicable) record it. (See MixinWorldClient)
// This is necessary for the block break event (particles and sound) to be recorded. Otherwise it looks like the
// event was emitted because of a packet (player will be null) and not as it actually was (by the player).
@Redirect(method = "onPlayerDestroyBlock", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/World;playEvent(ILnet/minecraft/util/math/BlockPos;I)V"))
public void replayModRecording_playEvent_fixed(World world, int type, BlockPos pos, int data) {
world.playEvent(mc.thePlayer, type, pos, data);
}
}

View File

@@ -2,7 +2,7 @@ package com.replaymod.recording.mixin;
import com.replaymod.recording.handler.RecordingEventHandler;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;

View File

@@ -1,62 +0,0 @@
package com.replaymod.recording.mixin;
import com.replaymod.recording.handler.RecordingEventHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.profiler.Profiler;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.WorldProvider;
import net.minecraft.world.storage.ISaveHandler;
import net.minecraft.world.storage.WorldInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldClient.class)
public abstract class MixinWorldClient extends World implements RecordingEventHandler.RecordingEventSender {
@Shadow
private Minecraft mc;
protected MixinWorldClient(ISaveHandler saveHandlerIn, WorldInfo info, WorldProvider providerIn, Profiler profilerIn, boolean client) {
super(saveHandlerIn, info, providerIn, profilerIn, client);
}
private RecordingEventHandler replayModRecording_getRecordingEventHandler() {
return ((RecordingEventHandler.RecordingEventSender) mc.renderGlobal).getRecordingEventHandler();
}
// Sounds that are emitted by thePlayer no longer take the long way over the server
// but are instead played directly by the client. The server only sends these sounds to
// other clients so we have to record them manually.
// E.g. Block place sounds
@Inject(method = "playSound", at = @At("HEAD"))
public void replayModRecording_recordClientSound(EntityPlayer player, double x, double y, double z, SoundEvent sound, SoundCategory category,
float volume, float pitch, CallbackInfo ci) {
if (player == mc.thePlayer) {
RecordingEventHandler handler = replayModRecording_getRecordingEventHandler();
if (handler != null) {
handler.onClientSound(sound, category, x, y, z, volume, pitch);
}
}
}
// Same goes for level events (also called effects). E.g. door open, block break, etc.
// These are handled in the World class, so we override the method in WorldClient and add our special handling.
@Override
public void playEvent(EntityPlayer player, int type, BlockPos pos, int data) {
if (player == mc.thePlayer) {
// We caused this event, the server won't send it to us
RecordingEventHandler handler = replayModRecording_getRecordingEventHandler();
if (handler != null) {
handler.onClientEffect(type, pos, data);
}
}
super.playEvent(player, type, pos, data);
}
}

View File

@@ -9,24 +9,21 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.DataWatcher;
import net.minecraft.entity.Entity;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.EnumPacketDirection;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.network.play.server.*;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.ChatComponentText;
import net.minecraftforge.fml.common.network.internal.FMLProxyPacket;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -87,8 +84,8 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
public void save(Packet packet) {
try {
if(packet instanceof SPacketSpawnPlayer) {
UUID uuid = ((SPacketSpawnPlayer) packet).getUniqueId();
if(packet instanceof S0CPacketSpawnPlayer) {
UUID uuid = ((S0CPacketSpawnPlayer) packet).getPlayer();
Set<String> uuids = new HashSet<>(Arrays.asList(metaData.getPlayers()));
uuids.add(uuid.toString());
metaData.setPlayers(uuids.toArray(new String[uuids.size()]));
@@ -154,16 +151,16 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
try {
Packet packet = (Packet) msg;
if(packet instanceof SPacketCollectItem) {
if(packet instanceof S0DPacketCollectItem) {
if(mc.thePlayer != null ||
((SPacketCollectItem) packet).getEntityID() == mc.thePlayer.getEntityId()) {
((S0DPacketCollectItem) packet).getEntityID() == mc.thePlayer.getEntityId()) {
super.channelRead(ctx, msg);
return;
}
}
if (packet instanceof SPacketResourcePackSend) {
save(resourcePackRecorder.handleResourcePack((SPacketResourcePackSend) packet));
if (packet instanceof S48PacketResourcePackSend) {
save(resourcePackRecorder.handleResourcePack((S48PacketResourcePackSend) packet));
return;
}
@@ -176,10 +173,10 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
save(packet);
if (packet instanceof SPacketCustomPayload) {
SPacketCustomPayload p = (SPacketCustomPayload) packet;
if (packet instanceof S3FPacketCustomPayload) {
S3FPacketCustomPayload p = (S3FPacketCustomPayload) packet;
if (Restrictions.PLUGIN_CHANNEL.equals(p.getChannelName())) {
packet = new SPacketDisconnect(new TextComponentString("Please update to view this replay."));
packet = new S40PacketDisconnect(new ChatComponentText("Please update to view this replay."));
save(packet);
}
}
@@ -192,31 +189,27 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
super.channelRead(ctx, msg);
}
private <T> void DataManager_set(EntityDataManager dataManager, EntityDataManager.DataEntry<T> entry) {
dataManager.register(entry.getKey(), entry.getValue());
}
@SuppressWarnings("unchecked")
private byte[] getPacketData(Packet packet) throws IOException {
if (packet instanceof SPacketSpawnMob) {
SPacketSpawnMob p = (SPacketSpawnMob) packet;
if (p.dataManager == null) {
p.dataManager = new EntityDataManager(null);
if (p.getDataManagerEntries() != null) {
for (EntityDataManager.DataEntry<?> entry : p.getDataManagerEntries()) {
DataManager_set(p.dataManager, entry);
if(packet instanceof S0FPacketSpawnMob) {
S0FPacketSpawnMob p = (S0FPacketSpawnMob) packet;
if (p.field_149043_l == null) {
p.field_149043_l = new DataWatcher(null);
if(p.func_149027_c() != null) {
for(DataWatcher.WatchableObject wo : (List<DataWatcher.WatchableObject>) p.func_149027_c()) {
p.field_149043_l.addObject(wo.getDataValueId(), wo.getObject());
}
}
}
}
if (packet instanceof SPacketSpawnPlayer) {
SPacketSpawnPlayer p = (SPacketSpawnPlayer) packet;
if(packet instanceof S0CPacketSpawnPlayer) {
S0CPacketSpawnPlayer p = (S0CPacketSpawnPlayer) packet;
if (p.watcher == null) {
p.watcher = new EntityDataManager(null);
if (p.getDataManagerEntries() != null) {
for (EntityDataManager.DataEntry<?> entry : p.getDataManagerEntries()) {
DataManager_set(p.watcher, entry);
p.watcher = new DataWatcher(null);
if(p.func_148944_c() != null) {
for(DataWatcher.WatchableObject wo : p.func_148944_c()) {
p.watcher.addObject(wo.getDataValueId(), wo.getObject());
}
}
}

View File

@@ -15,8 +15,8 @@ import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.ResourcePackRepository;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.client.CPacketResourcePackStatus;
import net.minecraft.network.play.server.SPacketResourcePackSend;
import net.minecraft.network.play.client.C19PacketResourcePackStatus;
import net.minecraft.network.play.server.S48PacketResourcePackSend;
import net.minecraft.util.HttpUtil;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
@@ -74,9 +74,9 @@ public class ResourcePackRecorder {
}
}
public synchronized SPacketResourcePackSend handleResourcePack(SPacketResourcePackSend packet) {
public synchronized S48PacketResourcePackSend handleResourcePack(S48PacketResourcePackSend packet) {
final int requestId = nextRequestId++;
final NetHandlerPlayClient netHandler = mc.getConnection();
final NetHandlerPlayClient netHandler = mc.getNetHandler();
final NetworkManager netManager = netHandler.getNetworkManager();
final String url = packet.getURL();
final String hash = packet.getHash();
@@ -87,60 +87,60 @@ public class ResourcePackRecorder {
final File levelDir = new File(savesDir, levelName);
if (levelDir.isFile()) {
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.ACCEPTED));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
Futures.addCallback(mc.getResourcePackRepository().setResourcePackInstance(levelDir), new FutureCallback<Object>() {
@Override
public void onSuccess(Object result) {
recordResourcePack(levelDir, requestId);
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
}
@Override
public void onFailure(@Nonnull Throwable throwable) {
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.FAILED_DOWNLOAD));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
}
});
} else {
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.FAILED_DOWNLOAD));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
}
} else {
final ServerData serverData = mc.getCurrentServerData();
if (serverData != null && serverData.getResourceMode() == ServerData.ServerResourceMode.ENABLED) {
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.ACCEPTED));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
downloadResourcePackFuture(requestId, url, hash);
} else if (serverData != null && serverData.getResourceMode() != ServerData.ServerResourceMode.PROMPT) {
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.DECLINED));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
} else {
mc.addScheduledTask(() -> mc.displayGuiScreen(new GuiYesNo((result, id) -> {
if (serverData != null) {
serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED);
}
if (result) {
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.ACCEPTED));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
downloadResourcePackFuture(requestId, url, hash);
} else {
netManager.sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.DECLINED));
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
}
ServerList.saveSingleServer(serverData);
ServerList.func_147414_b(serverData);
mc.displayGuiScreen(null);
}, I18n.format("multiplayer.texturePrompt.line1"), I18n.format("multiplayer.texturePrompt.line2"), 0)));
}
}
return new SPacketResourcePackSend("replay://" + requestId, "");
return new S48PacketResourcePackSend("replay://" + requestId, "");
}
private void downloadResourcePackFuture(int requestId, String url, final String hash) {
Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() {
@Override
public void onSuccess(Object result) {
mc.getConnection().sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
}
@Override
public void onFailure(@Nonnull Throwable throwable) {
mc.getConnection().sendPacket(new CPacketResourcePackStatus(hash, CPacketResourcePackStatus.Action.FAILED_DOWNLOAD));
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
}
});
}

View File

@@ -5,8 +5,8 @@ import com.replaymod.render.utils.RenderJob;
import com.replaymod.replay.events.ReplayCloseEvent;
import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@@ -45,7 +45,7 @@ public class ReplayModRender {
core = ReplayMod.instance;
configuration = new Configuration(event.getSuggestedConfigurationFile());
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
core.getSettingsRegistry().register(Setting.class);
}

View File

@@ -10,7 +10,7 @@ import net.minecraft.client.renderer.chunk.RenderChunk;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ChunkLoadingRenderGlobal {
@@ -27,10 +27,10 @@ public class ChunkLoadingRenderGlobal {
this.renderWorker = new CustomChunkRenderWorker(renderDispatcher, new RegionRenderCacheBuilder());
int workerThreads = renderDispatcher.listThreadedWorkers.size();
PriorityBlockingQueue<ChunkCompileTaskGenerator> queueChunkUpdates = renderDispatcher.queueChunkUpdates;
BlockingQueue<ChunkCompileTaskGenerator> queueChunkUpdates = renderDispatcher.queueChunkUpdates;
workerJailingQueue = new JailingQueue<>(queueChunkUpdates);
renderDispatcher.queueChunkUpdates = workerJailingQueue;
ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null, 0);
ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null);
element.finish();
for (int i = 0; i < workerThreads; i++) {
queueChunkUpdates.add(element);
@@ -68,7 +68,7 @@ public class ChunkLoadingRenderGlobal {
renderDispatcher.updateChunkNow(renderchunk);
renderchunk.clearNeedsUpdate();
renderchunk.setNeedsUpdate(false);
iterator.remove();
}
}

View File

@@ -0,0 +1,47 @@
package com.replaymod.render.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.EffectRenderer;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(EffectRenderer.class)
public abstract class MixinEffectRenderer {
@Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;renderParticle(Lnet/minecraft/client/renderer/WorldRenderer;Lnet/minecraft/entity/Entity;FFFFFF)V"))
private void renderNormalParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks,
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
renderParticle(fx, worldRenderer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
}
@Redirect(method = "renderLitParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;renderParticle(Lnet/minecraft/client/renderer/WorldRenderer;Lnet/minecraft/entity/Entity;FFFFFF)V"))
private void renderLitParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks,
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
renderParticle(fx, worldRenderer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
}
private void renderParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks,
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && handler.omnidirectional) {
// Align all particles towards the camera
double dx = fx.prevPosX + (fx.posX - fx.prevPosX) * partialTicks - view.posX;
double dy = fx.prevPosY + (fx.posY - fx.prevPosY) * partialTicks - view.posY;
double dz = fx.prevPosZ + (fx.posZ - fx.prevPosZ) * partialTicks - view.posZ;
double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
double yaw = -Math.atan2(dx, dz);
rotX = (float) Math.cos(yaw);
rotZ = (float) Math.sin(yaw);
rotXZ = (float) Math.cos(pitch);
rotYZ = (float) (-rotZ * Math.sin(pitch));
rotXY = (float) (rotX * Math.sin(pitch));
}
fx.renderParticle(worldRenderer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
}
}

View File

@@ -12,7 +12,7 @@ import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.MovingObjectPosition;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.ReadableColor;
import org.lwjgl.util.glu.Project;
@@ -118,10 +118,10 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
@Shadow
public abstract void renderHand(float partialTicks, int renderPass);
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/math/RayTraceResult;IF)V"))
private void replayModRender_drawSelectionBox(RenderGlobal instance, EntityPlayer player, RayTraceResult rtr, int alwaysZero, float partialTicks) {
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/MovingObjectPosition;IF)V"))
private void replayModRender_drawSelectionBox(RenderGlobal instance, EntityPlayer player, MovingObjectPosition mop, int alwaysZero, float partialTicks) {
if (replayModRender_handler == null) {
instance.drawSelectionBox(player, rtr, alwaysZero, partialTicks);
instance.drawSelectionBox(player, mop, alwaysZero, partialTicks);
}
}

View File

@@ -1,47 +0,0 @@
package com.replaymod.render.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleManager;
import net.minecraft.client.renderer.VertexBuffer;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ParticleManager.class)
public abstract class MixinParticleManager {
@Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/Particle;renderParticle(Lnet/minecraft/client/renderer/VertexBuffer;Lnet/minecraft/entity/Entity;FFFFFF)V"))
private void renderNormalParticle(Particle particle, VertexBuffer vertexBuffer, Entity view, float partialTicks,
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
renderParticle(particle, vertexBuffer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
}
@Redirect(method = "renderLitParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/Particle;renderParticle(Lnet/minecraft/client/renderer/VertexBuffer;Lnet/minecraft/entity/Entity;FFFFFF)V"))
private void renderLitParticle(Particle particle, VertexBuffer vertexBuffer, Entity view, float partialTicks,
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
renderParticle(particle, vertexBuffer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
}
private void renderParticle(Particle particle, VertexBuffer vertexBuffer, Entity view, float partialTicks,
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && handler.omnidirectional) {
// Align all particles towards the camera
double dx = particle.prevPosX + (particle.posX - particle.prevPosX) * partialTicks - view.posX;
double dy = particle.prevPosY + (particle.posY - particle.prevPosY) * partialTicks - view.posY;
double dz = particle.prevPosZ + (particle.posZ - particle.prevPosZ) * partialTicks - view.posZ;
double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
double yaw = -Math.atan2(dx, dz);
rotX = (float) Math.cos(yaw);
rotZ = (float) Math.sin(yaw);
rotXZ = (float) Math.cos(pitch);
rotYZ = (float) (-rotZ * Math.sin(pitch));
rotXY = (float) (rotX * Math.sin(pitch));
}
particle.renderParticle(vertexBuffer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
}
}

View File

@@ -7,14 +7,15 @@ import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Render.class)
public abstract class MixinRender {
@Inject(method = "renderLivingLabel", at = @At("HEAD"), cancellable = true)
private void replayModRender_areAllNamesHidden(Entity entityIn, String str, double x, double y, double z, int maxDistance, CallbackInfo ci) {
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
private void replayModRender_areAllNamesHidden(Entity entity, CallbackInfoReturnable<Boolean> ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && !handler.getSettings().isRenderNameTags()) {
ci.setReturnValue(false);
ci.cancel();
}
}

View File

@@ -3,13 +3,16 @@ package com.replaymod.render.mixin;
import com.replaymod.render.hooks.ChunkLoadingRenderGlobal;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.RenderChunk;
import net.minecraft.client.renderer.culling.ICamera;
import net.minecraft.entity.Entity;
import net.minecraft.util.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RenderGlobal.class)
public abstract class MixinRenderGlobal {
@@ -34,8 +37,6 @@ public abstract class MixinRenderGlobal {
do {
setupTerrain(viewEntity, partialTicks, camera, replayModRender_hook.nextFrameId(), playerSpectator);
replayModRender_hook.updateChunks();
} while (displayListEntitiesDirty);
displayListEntitiesDirty = true;
@@ -45,6 +46,13 @@ public abstract class MixinRenderGlobal {
}
}
@Inject(method = "isPositionInRenderChunk", at = @At("HEAD"), cancellable = true)
public void replayModRender_isPositionInRenderChunk(BlockPos pos, RenderChunk chunk, CallbackInfoReturnable<Boolean> ci) {
if (replayModRender_hook != null) {
ci.setReturnValue(true);
}
}
@Inject(method = "updateChunks", at = @At("HEAD"), cancellable = true)
public void replayModRender_updateChunks(long finishTimeNano, CallbackInfo ci) {
if (replayModRender_hook != null) {

View File

@@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RenderManager.class)
public abstract class MixinRenderManager {
@@ -19,7 +19,7 @@ public abstract class MixinRenderManager {
private float playerViewY;
@Inject(method = "doRenderEntity", at = @At("HEAD"))
private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfo ci) {
private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && handler.omnidirectional) {
double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));

View File

@@ -0,0 +1,21 @@
package com.replaymod.render.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.EntityLivingBase;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RendererLivingEntity.class)
public abstract class MixinRendererLivingEntity {
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
private void replayModRender_areAllNamesHidden(EntityLivingBase entity, CallbackInfoReturnable<Boolean> ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && !handler.getSettings().isRenderNameTags()) {
ci.setReturnValue(false); //this calls the cancel method
}
}
}

View File

@@ -1,5 +1,6 @@
package com.replaymod.render.rendering;
import com.replaymod.core.ReplayMod;
import com.replaymod.pathing.player.AbstractTimelinePlayer;
import com.replaymod.pathing.player.ReplayTimer;
import com.replaymod.pathing.properties.TimestampProperty;
@@ -13,18 +14,17 @@ import com.replaymod.render.gui.GuiRenderingDone;
import com.replaymod.render.gui.GuiVideoRenderer;
import com.replaymod.render.hooks.ChunkLoadingRenderGlobal;
import com.replaymod.render.metadata.MetadataInjector;
import com.replaymod.render.utils.SoundHandler;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replaystudio.pathing.path.Keyframe;
import com.replaymod.replaystudio.pathing.path.Path;
import com.replaymod.replaystudio.pathing.path.Timeline;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.SoundCategory;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.Timer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.util.Dimension;
@@ -54,7 +54,7 @@ public class VideoRenderer implements RenderInfo {
private int fps;
private boolean mouseWasGrabbed;
private boolean debugInfoWasShown;
private Map<SoundCategory, Float> originalSoundLevels;
private Map originalSoundLevels;
private TimelinePlayer timelinePlayer;
private Future<Void> timelinePlayerFuture;
@@ -90,7 +90,7 @@ public class VideoRenderer implements RenderInfo {
* @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed)
*/
public boolean renderVideo() {
MinecraftForge.EVENT_BUS.post(new ReplayRenderEvent.Pre(this));
FMLCommonHandler.instance().bus().post(new ReplayRenderEvent.Pre(this));
setup();
@@ -136,7 +136,7 @@ public class VideoRenderer implements RenderInfo {
finish();
MinecraftForge.EVENT_BUS.post(new ReplayRenderEvent.Post(this));
FMLCommonHandler.instance().bus().post(new ReplayRenderEvent.Post(this));
return !cancelled;
}
@@ -196,9 +196,9 @@ public class VideoRenderer implements RenderInfo {
for (SoundCategory category : SoundCategory.values()) {
mutedSounds.put(category, 0f);
}
originalSoundLevels = mc.gameSettings.soundLevels;
mutedSounds.put(SoundCategory.MASTER, originalSoundLevels.get(SoundCategory.MASTER));
mc.gameSettings.soundLevels = mutedSounds;
originalSoundLevels = mc.gameSettings.mapSoundLevels;
mutedSounds.put(SoundCategory.MASTER, (Float) originalSoundLevels.get(SoundCategory.MASTER));
mc.gameSettings.mapSoundLevels = mutedSounds;
fps = settings.getFramesPerSecond();
@@ -219,6 +219,9 @@ public class VideoRenderer implements RenderInfo {
updateDisplaySize();
ScaledResolution scaled = new ScaledResolution(mc);
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal);
// Set up our own framebuffer to render the GUI to
@@ -239,13 +242,13 @@ public class VideoRenderer implements RenderInfo {
if (mouseWasGrabbed) {
mc.mouseHelper.grabMouseCursor();
}
mc.gameSettings.soundLevels = originalSoundLevels;
mc.gameSettings.mapSoundLevels = originalSoundLevels;
mc.displayGuiScreen(null);
if (chunkLoadingRenderGlobal != null) {
chunkLoadingRenderGlobal.uninstall();
}
new SoundHandler().playRenderSuccessSound();
ReplayMod.soundHandler.playRenderSuccessSound();
new GuiRenderingDone(ReplayModRender.instance, videoWriter.getVideoFile(), totalFrames, settings).display();

View File

@@ -2,18 +2,15 @@ package com.replaymod.render.utils;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class JailingQueue<T> extends PriorityBlockingQueue<T> {
private final PriorityBlockingQueue<T> delegate;
public class JailingQueue<T> extends AbstractQueue<T> implements BlockingQueue<T> {
private final BlockingQueue<T> delegate;
private final Set<Thread> jailed = new HashSet<Thread>();
public JailingQueue(PriorityBlockingQueue<T> delegate) {
public JailingQueue(BlockingQueue<T> delegate) {
this.delegate = delegate;
}
@@ -62,13 +59,13 @@ public class JailingQueue<T> extends PriorityBlockingQueue<T> {
}
@Override
public void put(T t) {
public void put(T t) throws InterruptedException {
tryAccess();
delegate.put(t);
}
@Override
public boolean offer(T t, long timeout, TimeUnit unit) {
public boolean offer(T t, long timeout, TimeUnit unit) throws InterruptedException {
tryAccess();
return delegate.offer(t, timeout, unit);
}

View File

@@ -143,7 +143,6 @@ public class InputReplayTimer extends WrappedTimer {
// Following are a ton of vanilla keyboard shortcuts, some are removed as they're useless in the
// replay viewer as of now
// TODO Update maybe add new key bindings
// TODO: Translate magic values to Keyboard.KEY_ constants
if (key == 32 && Keyboard.isKeyDown(61) && mc.ingameGUI != null) {

View File

@@ -23,8 +23,8 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.network.EnumPacketDirection;
import net.minecraft.network.NetworkManager;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
import org.lwjgl.opengl.Display;
@@ -76,7 +76,7 @@ public class ReplayHandler {
Preconditions.checkState(mc.isCallingFromMinecraftThread(), "Must be called from Minecraft thread.");
this.replayFile = replayFile;
MinecraftForge.EVENT_BUS.post(new ReplayOpenEvent.Pre(this));
FMLCommonHandler.instance().bus().post(new ReplayOpenEvent.Pre(this));
markers = new ArrayList<>(replayFile.getMarkers().or(Collections.emptySet()));
@@ -87,7 +87,7 @@ public class ReplayHandler {
overlay = new GuiReplayOverlay(this);
overlay.setVisible(true);
MinecraftForge.EVENT_BUS.post(new ReplayOpenEvent.Post(this));
FMLCommonHandler.instance().bus().post(new ReplayOpenEvent.Post(this));
}
void restartedReplay() {
@@ -101,7 +101,7 @@ public class ReplayHandler {
public void endReplay() throws IOException {
Preconditions.checkState(mc.isCallingFromMinecraftThread(), "Must be called from Minecraft thread.");
MinecraftForge.EVENT_BUS.post(new ReplayCloseEvent.Pre(this));
FMLCommonHandler.instance().bus().post(new ReplayCloseEvent.Pre(this));
replaySender.terminateReplay();
@@ -124,7 +124,7 @@ public class ReplayHandler {
ReplayModReplay.instance.replayHandler = null;
MinecraftForge.EVENT_BUS.post(new ReplayCloseEvent.Post(this));
FMLCommonHandler.instance().bus().post(new ReplayCloseEvent.Post(this));
}
private void setup() {
@@ -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;
@@ -328,7 +331,7 @@ public class ReplayHandler {
replaySender.setAsyncMode(true);
replaySender.setReplaySpeed(0);
mc.getConnection().getNetworkManager().processReceivedPackets();
mc.getNetHandler().getNetworkManager().processReceivedPackets();
for (Entity entity : mc.theWorld.loadedEntityList) {
if (entity instanceof EntityOtherPlayerMP) {
EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity;

View File

@@ -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");
}
});
}

View File

@@ -12,22 +12,17 @@ import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityOtherPlayerMP;
import net.minecraft.client.gui.GuiDownloadTerrain;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.network.*;
import net.minecraft.network.play.server.*;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import net.minecraft.world.WorldSettings.GameType;
import net.minecraft.world.WorldType;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
@@ -44,22 +39,30 @@ import java.util.concurrent.Callable;
*/
@Sharable
public class ReplaySender extends ChannelInboundHandlerAdapter {
/**
* Previously packets for the client player were inserted using one fixed entity id (this one).
* This is no longer the case however to provide backwards compatibility, we have to convert
* these old packets to use the normal entity id.
* Need to punch someone? -> CrushedPixel
*/
public static final int LEGACY_ENTITY_ID = Integer.MIN_VALUE + 9001;
/**
* These packets are ignored completely during replay.
*/
private static final List<Class> BAD_PACKETS = Arrays.<Class>asList(
// TODO Update possibly more?
SPacketUpdateHealth.class,
SPacketOpenWindow.class,
SPacketCloseWindow.class,
SPacketSetSlot.class,
SPacketWindowItems.class,
SPacketSignEditorOpen.class,
SPacketStatistics.class,
SPacketSetExperience.class,
SPacketCamera.class,
SPacketPlayerAbilities.class,
SPacketTitle.class);
S06PacketUpdateHealth.class,
S2DPacketOpenWindow.class,
S2EPacketCloseWindow.class,
S2FPacketSetSlot.class,
S30PacketWindowItems.class,
S36PacketSignEditorOpen.class,
S37PacketStatistics.class,
S1FPacketSetExperience.class,
S43PacketCamera.class,
S39PacketPlayerAbilities.class,
S45PacketTitle.class);
private static int TP_DISTANCE_LIMIT = 128;
@@ -168,8 +171,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
this.asyncMode = asyncMode;
this.replayLength = file.getMetaData().getDuration();
MinecraftForge.EVENT_BUS.register(this);
if (asyncMode) {
new Thread(asyncSender, "replaymod-async-sender").start();
}
@@ -238,7 +239,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
*/
public void terminateReplay() {
terminate = true;
MinecraftForge.EVENT_BUS.unregister(this);
try {
channelInactive(ctx);
ctx.channel().pipeline().close();
@@ -247,25 +247,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
}
}
@SubscribeEvent
public void onWorldTick(TickEvent.ClientTickEvent event) {
// Unfortunately the WorldTickEvent doesn't seem to be emitted on the CLIENT side
if (event.phase != TickEvent.Phase.START) return;
// Spawning a player into an empty chunk (which we might do with the recording player)
// prevents it from being moved by teleport packets (it essentially gets stuck) because
// Entity#addedToChunk is not set and it is therefore not updated every tick.
// To counteract this, we need to manually update it's position if it hasn't been added
// to any chunk yet.
if (mc.theWorld != null) {
for (EntityPlayer playerEntity : mc.theWorld.playerEntities) {
if (!playerEntity.addedToChunk && playerEntity instanceof EntityOtherPlayerMP) {
playerEntity.onLivingUpdate();
}
}
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
@@ -293,21 +274,21 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
// Therefore we have to remove all loaded, dead entities manually if we are in sync mode.
// We do this after every SpawnX packet and after the destroy entities packet.
if (!asyncMode && mc.theWorld != null) {
if (p instanceof SPacketSpawnPlayer
|| p instanceof SPacketSpawnObject
|| p instanceof SPacketSpawnMob
|| p instanceof SPacketSpawnGlobalEntity
|| p instanceof SPacketSpawnPainting
|| p instanceof SPacketSpawnExperienceOrb
|| p instanceof SPacketDestroyEntities) {
if (p instanceof S0CPacketSpawnPlayer
|| p instanceof S0EPacketSpawnObject
|| p instanceof S0FPacketSpawnMob
|| p instanceof S2CPacketSpawnGlobalEntity
|| p instanceof S10PacketSpawnPainting
|| p instanceof S11PacketSpawnExperienceOrb
|| p instanceof S13PacketDestroyEntities) {
World world = mc.theWorld;
for (int i = 0; i < world.loadedEntityList.size(); ++i) {
Entity entity = world.loadedEntityList.get(i);
Entity entity = (Entity) world.loadedEntityList.get(i);
if (entity.isDead) {
int chunkX = entity.chunkCoordX;
int chunkY = entity.chunkCoordZ;
if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) {
if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) {
world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity);
}
@@ -345,8 +326,8 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
* @return The processed packet or {@code null} if no packet shall be sent
*/
protected Packet processPacket(Packet p) throws Exception {
if (p instanceof SPacketCustomPayload) {
SPacketCustomPayload packet = (SPacketCustomPayload) p;
if (p instanceof S3FPacketCustomPayload) {
S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p;
if (Restrictions.PLUGIN_CHANNEL.equals(packet.getChannelName())) {
final String unknown = replayHandler.getRestrictions().handle(packet);
if (unknown == null) {
@@ -372,8 +353,8 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
}
}
}
if (p instanceof SPacketDisconnect) {
ITextComponent reason = ((SPacketDisconnect) p).getReason();
if (p instanceof S40PacketDisconnect) {
IChatComponent reason = ((S40PacketDisconnect) p).getReason();
if ("Please update to view this replay.".equals(reason.getUnformattedText())) {
// This version of the mod supports replay restrictions so we are allowed
// to remove this packet.
@@ -383,8 +364,10 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
if(BAD_PACKETS.contains(p.getClass())) return null;
if(p instanceof SPacketResourcePackSend) {
SPacketResourcePackSend packet = (SPacketResourcePackSend) p;
convertLegacyEntityIds(p);
if(p instanceof S48PacketResourcePackSend) {
S48PacketResourcePackSend packet = (S48PacketResourcePackSend) p;
String url = packet.getURL();
if (url.startsWith("replay://")) {
int id = Integer.parseInt(url.substring("replay://".length()));
@@ -403,10 +386,10 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
}
}
if(p instanceof SPacketJoinGame) {
SPacketJoinGame packet = (SPacketJoinGame) p;
if(p instanceof S01PacketJoinGame) {
S01PacketJoinGame packet = (S01PacketJoinGame) p;
allowMovement = true;
int entId = packet.getPlayerId();
int entId = packet.getEntityId();
actualID = entId;
entId = -1789435; // Camera entity id should be negative which is an invalid id and can't be used by servers
int dimension = packet.getDimension();
@@ -414,21 +397,21 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
int maxPlayers = packet.getMaxPlayers();
WorldType worldType = packet.getWorldType();
p = new SPacketJoinGame(entId, GameType.SPECTATOR, false, dimension,
p = new S01PacketJoinGame(entId, GameType.SPECTATOR, false, dimension,
difficulty, maxPlayers, worldType, false);
}
if(p instanceof SPacketRespawn) {
SPacketRespawn respawn = (SPacketRespawn) p;
p = new SPacketRespawn(respawn.getDimensionID(),
if(p instanceof S07PacketRespawn) {
S07PacketRespawn respawn = (S07PacketRespawn) p;
p = new S07PacketRespawn(respawn.getDimensionID(),
respawn.getDifficulty(), respawn.getWorldType(), GameType.SPECTATOR);
allowMovement = true;
}
if(p instanceof SPacketPlayerPosLook) {
if(p instanceof S08PacketPlayerPosLook) {
if(!hasWorldLoaded) hasWorldLoaded = true;
final SPacketPlayerPosLook ppl = (SPacketPlayerPosLook) p;
final S08PacketPlayerPosLook ppl = (S08PacketPlayerPosLook) p;
if (mc.currentScreen instanceof GuiDownloadTerrain) {
// Close the world loading screen manually in case we swallow the packet
@@ -439,10 +422,10 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
CameraEntity cent = replayHandler.getCameraEntity();
for (SPacketPlayerPosLook.EnumFlags relative : ppl.getFlags()) {
if (relative == SPacketPlayerPosLook.EnumFlags.X
|| relative == SPacketPlayerPosLook.EnumFlags.Y
|| relative == SPacketPlayerPosLook.EnumFlags.Z) {
for (Object relative : ppl.func_179834_f()) {
if (relative == S08PacketPlayerPosLook.EnumFlags.X
|| relative == S08PacketPlayerPosLook.EnumFlags.Y
|| relative == S08PacketPlayerPosLook.EnumFlags.Z) {
return null; // At least one of the coordinates is relative, so we don't care
}
}
@@ -474,8 +457,8 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
}.call();
}
if(p instanceof SPacketChangeGameState) {
SPacketChangeGameState pg = (SPacketChangeGameState)p;
if(p instanceof S2BPacketChangeGameState) {
S2BPacketChangeGameState pg = (S2BPacketChangeGameState)p;
int reason = pg.getGameState();
// only allow the following packets:
@@ -490,7 +473,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
}
}
if (p instanceof SPacketChat) {
if (p instanceof S02PacketChat) {
if (!ReplayModReplay.instance.getCore().getSettingsRegistry().get(Setting.SHOW_CHAT)) {
return null;
}
@@ -503,7 +486,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
@SuppressWarnings("unchecked")
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
ctx.attr(NetworkManager.PROTOCOL_ATTRIBUTE_KEY).set(EnumConnectionState.PLAY);
ctx.attr(NetworkManager.attrKeyConnectionState).set(EnumConnectionState.PLAY);
super.channelActive(ctx);
}
@@ -708,10 +691,10 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
protected Packet processPacketAsync(Packet p) {
//If hurrying, ignore some packets, except for short durations
if(desiredTimeStamp - lastTimeStamp > 1000) {
if(p instanceof SPacketParticles) return null;
if(p instanceof S2APacketParticles) return null;
if(p instanceof SPacketSpawnObject) {
SPacketSpawnObject pso = (SPacketSpawnObject)p;
if(p instanceof S0EPacketSpawnObject) {
S0EPacketSpawnObject pso = (S0EPacketSpawnObject)p;
int type = pso.getType();
if(type == 76) { // Firework rocket
return null;
@@ -798,6 +781,68 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
return p; // During synchronous playback everything is sent normally
}
/**
* This is necessary to convert packets from old replays to new replays.
* @param packet The packet to be transformed.
* @see #LEGACY_ENTITY_ID
*/
private void convertLegacyEntityIds(Packet packet) {
if (packet instanceof S0CPacketSpawnPlayer) {
S0CPacketSpawnPlayer p = (S0CPacketSpawnPlayer) packet;
if (p.entityId == LEGACY_ENTITY_ID) {
p.entityId = actualID;
}
} else if (packet instanceof S18PacketEntityTeleport) {
S18PacketEntityTeleport p = (S18PacketEntityTeleport) packet;
if (p.entityId == LEGACY_ENTITY_ID) {
p.entityId = actualID;
}
} else if (packet instanceof S14PacketEntity.S17PacketEntityLookMove) {
S14PacketEntity.S17PacketEntityLookMove p = (S14PacketEntity.S17PacketEntityLookMove) packet;
if (p.entityId == LEGACY_ENTITY_ID) {
p.entityId = actualID;
}
} else if (packet instanceof S19PacketEntityHeadLook) {
S19PacketEntityHeadLook p = (S19PacketEntityHeadLook) packet;
if (p.entityId == LEGACY_ENTITY_ID) {
p.entityId = actualID;
}
} else if (packet instanceof S12PacketEntityVelocity) {
S12PacketEntityVelocity p = (S12PacketEntityVelocity) packet;
if (p.entityID == LEGACY_ENTITY_ID) {
p.entityID = actualID;
}
} else if (packet instanceof S0BPacketAnimation) {
S0BPacketAnimation p = (S0BPacketAnimation) packet;
if (p.entityId == LEGACY_ENTITY_ID) {
p.entityId = actualID;
}
} else if (packet instanceof S04PacketEntityEquipment) {
S04PacketEntityEquipment p = (S04PacketEntityEquipment) packet;
if (p.entityID == LEGACY_ENTITY_ID) {
p.entityID = actualID;
}
} else if (packet instanceof S1BPacketEntityAttach) {
S1BPacketEntityAttach p = (S1BPacketEntityAttach) packet;
if (p.entityId == LEGACY_ENTITY_ID) {
p.entityId = actualID;
}
if (p.vehicleEntityId == LEGACY_ENTITY_ID) {
p.vehicleEntityId = actualID;
}
} else if (packet instanceof S0DPacketCollectItem) {
S0DPacketCollectItem p = (S0DPacketCollectItem) packet;
if (p.entityId == LEGACY_ENTITY_ID) {
p.entityId = actualID;
}
} else if (packet instanceof S13PacketDestroyEntities) {
S13PacketDestroyEntities p = (S13PacketDestroyEntities) packet;
if (p.entityIDs.length == 1 && p.entityIDs[0] == LEGACY_ENTITY_ID) {
p.entityIDs[0] = actualID;
}
}
}
private static final class PacketData {
private final int timestamp;
private final byte[] bytes;

View File

@@ -18,17 +18,16 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.item.EntityItemFrame;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.stats.StatisticsManager;
import net.minecraft.util.EnumHand;
import net.minecraft.stats.StatFileWriter;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
@@ -66,8 +65,9 @@ public class CameraEntity extends EntityPlayerSP {
*/
private final EventHandler eventHandler = new EventHandler();
public CameraEntity(Minecraft mcIn, World worldIn, NetHandlerPlayClient netHandlerPlayClient, StatisticsManager statisticsManager) {
super(mcIn, worldIn, netHandlerPlayClient, statisticsManager);
public CameraEntity(Minecraft mcIn, World worldIn, NetHandlerPlayClient netHandlerPlayClient, StatFileWriter statFileWriter) {
super(mcIn, worldIn, netHandlerPlayClient, statFileWriter);
FMLCommonHandler.instance().bus().register(eventHandler);
MinecraftForge.EVENT_BUS.register(eventHandler);
if (ReplayModReplay.instance.getReplayHandler().getSpectatedUUID() == null) {
cameraController = ReplayModReplay.instance.createCameraController(this);
@@ -289,49 +289,12 @@ public class CameraEntity extends EntityPlayerSP {
}
@Override
public float getCooldownPeriod() {
Entity view = mc.getRenderViewEntity();
if (view != this && view instanceof EntityPlayer) {
return ((EntityPlayer) view).getCooldownPeriod();
}
return 1;
}
@Override
public float getCooledAttackStrength(float adjustTicks) {
Entity view = mc.getRenderViewEntity();
if (view != this && view instanceof EntityPlayer) {
return ((EntityPlayer) view).getCooledAttackStrength(adjustTicks);
}
// Default to 1 as to not render the cooldown indicator (renders for < 1)
return 1;
}
@Override
public EnumHand getActiveHand() {
Entity view = mc.getRenderViewEntity();
if (view != this && view instanceof EntityPlayer) {
return ((EntityPlayer) view).getActiveHand();
}
return super.getActiveHand();
}
@Override
public boolean isHandActive() {
Entity view = mc.getRenderViewEntity();
if (view != this && view instanceof EntityPlayer) {
return ((EntityPlayer) view).isHandActive();
}
return super.isHandActive();
}
@Override
public RayTraceResult rayTrace(double p_174822_1_, float p_174822_3_) {
RayTraceResult pos = super.rayTrace(p_174822_1_, 1f);
public MovingObjectPosition rayTrace(double p_174822_1_, float p_174822_3_) {
MovingObjectPosition pos = super.rayTrace(p_174822_1_, 1f);
// Make sure we can never look at blocks (-> no outline)
if(pos != null && pos.typeOfHit == RayTraceResult.Type.BLOCK) {
pos.typeOfHit = RayTraceResult.Type.MISS;
if(pos != null && pos.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
pos.typeOfHit = MovingObjectPosition.MovingObjectType.MISS;
}
return pos;
@@ -346,6 +309,7 @@ public class CameraEntity extends EntityPlayerSP {
@Override
public void setDead() {
super.setDead();
FMLCommonHandler.instance().bus().unregister(eventHandler);
MinecraftForge.EVENT_BUS.unregister(eventHandler);
}
@@ -403,13 +367,9 @@ public class CameraEntity extends EntityPlayerSP {
@SubscribeEvent
public void preCrosshairRender(RenderGameOverlayEvent.Pre event) {
// The crosshair should only render if targeted entity can actually be spectated
if (event.getType() == RenderGameOverlayEvent.ElementType.CROSSHAIRS) {
if (event.type == RenderGameOverlayEvent.ElementType.CROSSHAIRS) {
event.setCanceled(!canSpectate(mc.pointedEntity));
}
// Hotbar should never be rendered
if (event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR) {
event.setCanceled(true);
}
}
@SubscribeEvent
@@ -440,13 +400,10 @@ public class CameraEntity extends EntityPlayerSP {
if (lastHandRendered != player) {
lastHandRendered = player;
mc.entityRenderer.itemRenderer.prevEquippedProgressMainHand = 1;
mc.entityRenderer.itemRenderer.prevEquippedProgressOffHand = 1;
mc.entityRenderer.itemRenderer.equippedProgressMainHand = 1;
mc.entityRenderer.itemRenderer.equippedProgressOffHand = 1;
mc.entityRenderer.itemRenderer.itemStackMainHand = player.getItemStackFromSlot(EntityEquipmentSlot.MAINHAND);
mc.entityRenderer.itemRenderer.itemStackOffHand = player.getItemStackFromSlot(EntityEquipmentSlot.OFFHAND);
mc.entityRenderer.itemRenderer.prevEquippedProgress = 1;
mc.entityRenderer.itemRenderer.equippedProgress = 1;
mc.entityRenderer.itemRenderer.itemToRender = player.inventory.getCurrentItem();
mc.entityRenderer.itemRenderer.equippedItemSlot = player.inventory.currentItem;
mc.thePlayer.renderArmYaw = mc.thePlayer.prevRenderArmYaw = player.rotationYaw;
mc.thePlayer.renderArmPitch = mc.thePlayer.prevRenderArmPitch = player.rotationPitch;
@@ -457,7 +414,7 @@ public class CameraEntity extends EntityPlayerSP {
@SubscribeEvent
public void onEntityViewRenderEvent(EntityViewRenderEvent.CameraSetup event) {
if (mc.getRenderViewEntity() == CameraEntity.this) {
event.setRoll(roll);
event.roll = roll;
}
}
}

View File

@@ -2,8 +2,8 @@ package com.replaymod.replay.camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3;
import org.lwjgl.Sys;
// TODO: Marius is responsible for this. Please, someone clean it up.
@@ -18,8 +18,8 @@ public class ClassicCameraController implements CameraController {
private double THRESHOLD = MAX_SPEED / 20;
private double DECAY = MAX_SPEED/3;
private Vec3d direction;
private Vec3d dirBefore;
private Vec3 direction;
private Vec3 dirBefore;
private double motion;
private long lastCall = Sys.getTime();
@@ -129,7 +129,7 @@ public class ClassicCameraController implements CameraController {
return;
}
Vec3d movement = direction.normalize();
Vec3 movement = direction.normalize();
double factor = motion * (frac / 1000D);
camera.moveCamera(movement.xCoord * factor, movement.yCoord * factor, movement.zCoord * factor);
@@ -158,7 +158,7 @@ public class ClassicCameraController implements CameraController {
break;
}
Vec3d dbf = direction;
Vec3 dbf = direction;
if(dirBefore != null) {
direction = dirBefore.normalize().add(direction);
@@ -169,12 +169,12 @@ public class ClassicCameraController implements CameraController {
updateMovement();
}
private Vec3d getVectorForRotation(float pitch, float yaw) {
private Vec3 getVectorForRotation(float pitch, float yaw) {
float f2 = MathHelper.cos(-yaw * 0.017453292F - (float) Math.PI);
float f3 = MathHelper.sin(-yaw * 0.017453292F - (float)Math.PI);
float f4 = -MathHelper.cos(-pitch * 0.017453292F);
float f5 = MathHelper.sin(-pitch * 0.017453292F);
return new Vec3d((double)(f3 * f4), (double)f5, (double)(f2 * f4));
return new Vec3((double)(f3 * f4), (double)f5, (double)(f2 * f4));
}
public enum MoveDirection {

View File

@@ -41,9 +41,8 @@ public class SpectatorCameraController implements CameraController {
if (view instanceof EntityPlayer) {
EntityPlayer viewPlayer = (EntityPlayer) view;
camera.inventory = viewPlayer.inventory;
camera.itemStackMainHand = viewPlayer.itemStackMainHand;
camera.swingingHand = viewPlayer.swingingHand;
camera.activeItemStackUseCount = viewPlayer.activeItemStackUseCount;
camera.itemInUse = viewPlayer.itemInUse;
camera.itemInUseCount = viewPlayer.itemInUseCount;
}
}
}

View File

@@ -2,14 +2,14 @@ package com.replaymod.replay.gui.overlay;
import com.replaymod.core.ReplayMod;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replaystudio.data.Marker;
import com.replaymod.replaystudio.util.Location;
import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.RenderInfo;
import de.johni0702.minecraft.gui.element.advanced.AbstractGuiTimeline;
import de.johni0702.minecraft.gui.function.Draggable;
import com.replaymod.replaystudio.data.Marker;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.MathHelper;
import org.lwjgl.util.Point;
import org.lwjgl.util.ReadableDimension;
import org.lwjgl.util.ReadablePoint;

View File

@@ -16,7 +16,7 @@ import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.GameSettings;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import org.lwjgl.input.Keyboard;
@@ -138,9 +138,9 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
public void setVisible(boolean visible) {
if (isVisible() != visible) {
if (visible) {
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
} else {
MinecraftForge.EVENT_BUS.unregister(this);
FMLCommonHandler.instance().bus().unregister(this);
}
}
super.setVisible(visible);

View File

@@ -9,14 +9,15 @@ import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
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;
@@ -33,12 +34,13 @@ public class GuiHandler {
}
public void register() {
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void injectIntoIngameMenu(GuiScreenEvent.InitGuiEvent.Post event) {
if (!(event.getGui() instanceof GuiIngameMenu)) {
if (!(event.gui instanceof GuiIngameMenu)) {
return;
}
@@ -46,7 +48,10 @@ 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;
@SuppressWarnings("unchecked")
List<GuiButton> buttonList = event.buttonList;
for(GuiButton b : new ArrayList<>(buttonList)) {
switch (b.id) {
// Replace "Exit Server" button with "Exit Replay" button
case BUTTON_EXIT_SERVER:
@@ -55,43 +60,69 @@ 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;
}
}
}
@SubscribeEvent
public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
if (!(event.getGui() instanceof GuiMainMenu)) {
if (!(event.gui instanceof GuiMainMenu)) {
return;
}
GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, event.getGui().width / 2 - 100,
event.getGui().height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer"));
@SuppressWarnings("unchecked")
List<GuiButton> buttonList = event.buttonList;
GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, event.gui.width / 2 - 100,
event.gui.height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer"));
button.width = button.width / 2 - 2;
event.getButtonList().add(button);
buttonList.add(button);
}
@SubscribeEvent
public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) {
if(!event.getButton().enabled) return;
if(!event.button.enabled) return;
if (event.getGui() instanceof GuiMainMenu) {
if (event.getButton().id == BUTTON_REPLAY_VIEWER) {
if (event.gui instanceof GuiMainMenu) {
if (event.button.id == BUTTON_REPLAY_VIEWER) {
new GuiReplayViewer(mod).display();
}
}
if (event.getGui() instanceof GuiIngameMenu && mod.getReplayHandler() != null) {
if (event.getButton().id == BUTTON_EXIT_REPLAY) {
event.getButton().enabled = false;
if (event.gui instanceof GuiIngameMenu && mod.getReplayHandler() != null) {
if (event.button.id == BUTTON_EXIT_REPLAY) {
event.button.enabled = false;
mc.displayGuiScreen(new GuiMainMenu());
try {
mod.getReplayHandler().endReplay();

View File

@@ -2,7 +2,7 @@ package com.replaymod.replay.mixin;
import com.replaymod.replay.camera.CameraEntity;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.RenderArmorStand;
import net.minecraft.client.renderer.entity.ArmorStandRenderer;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.player.EntityPlayer;
import org.spongepowered.asm.mixin.Mixin;
@@ -10,8 +10,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RenderArmorStand.class)
public abstract class MixinRenderArmorStand {
@Mixin(ArmorStandRenderer.class)
public abstract class MixinArmorStandRenderer {
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
private void replayModReplay_canRenderInvisibleName(EntityArmorStand entity, CallbackInfoReturnable<Boolean> ci) {
EntityPlayer thePlayer = Minecraft.getMinecraft().thePlayer;

View File

@@ -12,12 +12,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GuiSpectator.class)
public abstract class MixinGuiSpectator {
@Shadow
private Minecraft mc;
private Minecraft field_175268_g;
@Inject(method = "onHotbarSelected", at = @At("HEAD"), cancellable = true)
@Inject(method = "func_175260_a", at = @At("HEAD"), cancellable = true)
public void isInReplay(int i, CallbackInfo ci) {
// Prevent spectator gui from opening while in a replay
if (mc.thePlayer instanceof CameraEntity) {
if (field_175268_g.thePlayer instanceof CameraEntity) {
ci.cancel();
}
}

View File

@@ -1,31 +0,0 @@
package com.replaymod.replay.mixin;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleManager;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Queue;
@Mixin(ParticleManager.class)
public abstract class MixinParticleManager {
@Shadow
private Queue<Particle> queueEntityFX;
/**
* This method additionally clears the queue of particles to be added when the world is changed.
* Otherwise particles from the previous world might show up in this one if they were spawned after
* the last tick in the previous world.
*
* @param world The new world
* @param ci Callback info
*/
@Inject(method = "clearEffects", at = @At("HEAD"))
public void replayModReplay_clearParticleQueue(World world, CallbackInfo ci) {
queueEntityFX.clear();
}
}

View File

@@ -1,12 +1,12 @@
package com.replaymod.replay.mixin;
import com.replaymod.replay.ReplayModReplay;
import com.replaymod.replay.camera.CameraEntity;
import com.replaymod.replay.ReplayModReplay;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.multiplayer.PlayerControllerMP;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.stats.StatisticsManager;
import net.minecraft.stats.StatFileWriter;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -21,12 +21,12 @@ public abstract class MixinPlayerControllerMP {
private Minecraft mc;
@Shadow
private NetHandlerPlayClient connection;
private NetHandlerPlayClient netClientHandler;
@Inject(method = "createClientPlayer", at=@At("HEAD"), cancellable = true)
private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, CallbackInfoReturnable<EntityPlayerSP> ci) {
@Inject(method = "func_178892_a", at=@At("HEAD"), cancellable = true)
private void replayModReplay_createReplayCamera(World worldIn, StatFileWriter statFileWriter, CallbackInfoReturnable<EntityPlayerSP> ci) {
if (ReplayModReplay.instance.getReplayHandler() != null) {
ci.setReturnValue(new CameraEntity(mc, worldIn, connection, statisticsManager));
ci.setReturnValue(new CameraEntity(mc, worldIn, netClientHandler, statFileWriter));
ci.cancel();
}
}

View File

@@ -14,7 +14,6 @@ public abstract class MixinRenderArrow extends Render {
super(renderManager);
}
@SuppressWarnings("unchecked")
@Override
public boolean shouldRender(Entity entity, ICamera camera, double camX, double camY, double camZ) {
// Force arrows to always render, otherwise they stop rendering when you get close to them

View File

@@ -3,7 +3,7 @@ package com.replaymod.replay.mixin;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replay.ReplayModReplay;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.client.renderer.entity.RenderItem;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

View File

@@ -2,7 +2,7 @@ package com.replaymod.replay.mixin;
import com.replaymod.replay.camera.CameraEntity;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.RenderLivingBase;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import org.spongepowered.asm.mixin.Mixin;
@@ -11,8 +11,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RenderLivingBase.class)
public abstract class MixinRenderLivingBase {
@Mixin(RendererLivingEntity.class)
public abstract class MixinRendererLivingEntity {
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
private void replayModReplay_canRenderInvisibleName(EntityLivingBase entity, CallbackInfoReturnable<Boolean> ci) {
EntityPlayer thePlayer = Minecraft.getMinecraft().thePlayer;

View File

@@ -5,8 +5,8 @@ import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.ViewFrustum;
import net.minecraft.client.renderer.chunk.IRenderChunkFactory;
import net.minecraft.client.renderer.chunk.RenderChunk;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.BlockPos;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -31,7 +31,7 @@ public abstract class MixinViewFrustum {
}
/**
* Instead of calling {@link RenderChunk#setOrigin(int, int, int)} we recreate the render chunk
* Instead of calling {@link RenderChunk#setPosition(BlockPos)} we recreate the render chunk
* which seems to solve the problem that chunks are invisible when you leave an area and return
* to it.
* Any better fixes are welcome.
@@ -48,18 +48,17 @@ public abstract class MixinViewFrustum {
int k = this.countChunksX * 16;
for (int l = 0; l < this.countChunksX; ++l) {
int i1 = this.getBaseCoordinate(i, k, l);
int i1 = this.func_178157_a(i, k, l);
for (int j1 = 0; j1 < this.countChunksZ; ++j1) {
int k1 = this.getBaseCoordinate(j, k, j1);
int k1 = this.func_178157_a(j, k, j1);
for (int l1 = 0; l1 < this.countChunksY; ++l1) {
int i2 = l1 * 16;
RenderChunk renderchunk = this.renderChunks[(j1 * this.countChunksY + l1) * this.countChunksX + l];
BlockPos blockpos = new BlockPos(i1, i2, k1);
if (!blockpos.equals(renderchunk.getPosition())) {
// Recreate render chunk instead of setting its position
(renderChunks[(j1 * this.countChunksY + l1) * this.countChunksX + l] =
renderChunkFactory.create(world, renderGlobal, 0)
).setOrigin(blockpos.getX(), blockpos.getY(), blockpos.getZ());
renderChunks[(j1 * this.countChunksY + l1) * this.countChunksX + l] =
renderChunkFactory.makeRenderChunk(world, renderGlobal, blockpos, 0);
}
}
}
@@ -67,5 +66,5 @@ public abstract class MixinViewFrustum {
ci.cancel();
}
@Shadow abstract int getBaseCoordinate(int p_178157_1_, int p_178157_2_, int p_178157_3_);
@Shadow abstract int func_178157_a(int p_178157_1_, int p_178157_2_, int p_178157_3_);
}

View File

@@ -9,11 +9,12 @@ import com.replaymod.simplepathing.SPTimeline.SPPath;
import com.replaymod.simplepathing.gui.GuiPathing;
import com.replaymod.simplepathing.preview.PathPreview;
import lombok.Getter;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
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;
@@ -35,15 +39,28 @@ public class ReplayModSimplePathing {
core.getSettingsRegistry().register(Setting.class);
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
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() {

View File

@@ -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);
@@ -647,7 +658,7 @@ public class SPTimeline implements PathingRegistry {
} catch (IOException e) {
CrashReport crash = CrashReport.makeCrashReport(e, "Serializing interpolator");
CrashReportCategory category = crash.makeCategory("Serializing interpolator");
category.setDetail("Interpolator", interpolator::toString);
category.addCrashSectionCallable("Interpolator", interpolator::toString);
throw new ReportedException(crash);
}

View File

@@ -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

View File

@@ -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;

View File

@@ -8,7 +8,7 @@ import com.replaymod.replay.events.ReplayCloseEvent;
import com.replaymod.replay.events.ReplayOpenEvent;
import com.replaymod.simplepathing.ReplayModSimplePathing;
import com.replaymod.simplepathing.Setting;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.input.Keyboard;
@@ -23,7 +23,7 @@ public class PathPreview {
}
public void register() {
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
ReplayMod core = mod.getCore();
mod.getCore().getKeyBindingRegistry().registerKeyBinding("replaymod.input.pathpreview", Keyboard.KEY_H, () -> {

View File

@@ -17,7 +17,7 @@ import com.replaymod.simplepathing.gui.GuiPathing;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.VertexBuffer;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
@@ -204,7 +204,7 @@ public class PathPreviewRenderer {
if (distanceSquared(view, pos2) > renderDistanceSquared) return;
Tessellator tessellator = Tessellator.getInstance();
VertexBuffer vertexBuffer = tessellator.getBuffer();
WorldRenderer vertexBuffer = tessellator.getWorldRenderer();
vertexBuffer.setTranslation(-view.getLeft(), -view.getMiddle(), -view.getRight());
vertexBuffer.begin(GL11.GL_LINES, DefaultVertexFormats.POSITION_COLOR);
@@ -231,7 +231,7 @@ public class PathPreviewRenderer {
Triple<Double, Double, Double> pos,
Keyframe keyframe) {
Tessellator tessellator = Tessellator.getInstance();
VertexBuffer vertexBuffer = tessellator.getBuffer();
WorldRenderer vertexBuffer = tessellator.getWorldRenderer();
vertexBuffer.setTranslation(0, 0, 0);
mc.renderEngine.bindTexture(TEXTURE);
@@ -280,7 +280,7 @@ public class PathPreviewRenderer {
Triple<Double, Double, Double> pos,
Triple<Float, Float, Float> rot) {
Tessellator tessellator = Tessellator.getInstance();
VertexBuffer vertexBuffer = tessellator.getBuffer();
WorldRenderer vertexBuffer = tessellator.getWorldRenderer();
vertexBuffer.setTranslation(0, 0, 0);
mc.renderEngine.bindTexture(CAMERA_HEAD);

View File

@@ -21,23 +21,23 @@ public net.minecraft.client.Minecraft func_147112_ai()V # middleClickMouse
public net.minecraft.client.Minecraft func_147115_a(Z)V # sendClickBlockToController
# Packets
public net.minecraft.network.play.server.SPacketSpawnMob field_149043_l # dataManager
public net.minecraft.network.play.server.SPacketSpawnPlayer field_148960_i # watcher
public net.minecraft.network.play.server.S0FPacketSpawnMob field_149043_l # dataWatcher
#public net.minecraft.network.play.server.S38PacketPlayerListItem field_179964_d # profile
# public net.minecraft.network.play.server.S1DPacketEntityEffect field_149434_a # entityId
# public net.minecraft.network.play.server.S1CPacketEntityMetadata field_149379_a # entityId
# public net.minecraft.network.play.server.S1CPacketEntityMetadata field_149378_b # list
# public net.minecraft.network.play.server.S0BPacketAnimation field_148981_a # entityId
# public net.minecraft.network.play.server.S02PacketChat field_179842_b # position
# public net.minecraft.network.play.server.S19PacketEntityStatus field_149164_a # entityId
# public net.minecraft.network.play.server.S18PacketEntityTeleport *
# public net.minecraft.network.play.server.S14PacketEntity *
# public net.minecraft.network.play.server.S19PacketEntityHeadLook *
# public net.minecraft.network.play.server.S12PacketEntityVelocity *
# public net.minecraft.network.play.server.S04PacketEntityEquipment *
# public net.minecraft.network.play.server.S1BPacketEntityAttach *
# public net.minecraft.network.play.server.S0DPacketCollectItem *
# public net.minecraft.network.play.server.S13PacketDestroyEntities *
public net.minecraft.network.play.server.S1DPacketEntityEffect field_149434_a # entityId
public net.minecraft.network.play.server.S1CPacketEntityMetadata field_149379_a # entityId
public net.minecraft.network.play.server.S1CPacketEntityMetadata field_149378_b # list
public net.minecraft.network.play.server.S0BPacketAnimation field_148981_a # entityId
public net.minecraft.network.play.server.S02PacketChat field_179842_b # position
public net.minecraft.network.play.server.S19PacketEntityStatus field_149164_a # entityId
public net.minecraft.network.play.server.S0CPacketSpawnPlayer *
public net.minecraft.network.play.server.S18PacketEntityTeleport *
public net.minecraft.network.play.server.S14PacketEntity *
public net.minecraft.network.play.server.S19PacketEntityHeadLook *
public net.minecraft.network.play.server.S12PacketEntityVelocity *
public net.minecraft.network.play.server.S04PacketEntityEquipment *
public net.minecraft.network.play.server.S1BPacketEntityAttach *
public net.minecraft.network.play.server.S0DPacketCollectItem *
public net.minecraft.network.play.server.S13PacketDestroyEntities *
# GuiTextField
public net.minecraft.client.gui.GuiTextField field_146216_j # text
@@ -61,7 +61,7 @@ public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdat
public net.minecraft.client.renderer.RenderGlobal field_72755_R # renderInfos
public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdate
public net.minecraft.client.renderer.RenderGlobal field_72740_G # renderEntitiesStartupCounter
# public net.minecraft.client.renderer.RenderGlobal func_174983_a(Lnet.minecraft.util.BlockPos;Lnet.minecraft.client.renderer.chunk.RenderChunk;)Z # isPositionInRenderChunk
public net.minecraft.client.renderer.RenderGlobal func_174983_a(Lnet.minecraft.util.BlockPos;Lnet.minecraft.client.renderer.chunk.RenderChunk;)Z # isPositionInRenderChunk
public net.minecraft.client.renderer.RenderGlobal$ContainerLocalRenderInformation
#public net.minecraft.client.renderer.RenderGlobal$ContainerLocalRenderInformation field_178553_a # renderChunk
@@ -88,14 +88,14 @@ public net.minecraft.entity.player.EntityPlayer *
public net.minecraft.client.entity.EntityOtherPlayerMP *
# EffectRenderer
# public net.minecraft.client.particle.EffectRenderer field_110737_b # particleTextures
# public net.minecraft.client.particle.EffectRenderer field_78876_b # fxLayers
public net.minecraft.client.particle.EffectRenderer field_110737_b # particleTextures
public net.minecraft.client.particle.EffectRenderer field_78876_b # fxLayers
# IntegratedServer
public net.minecraft.server.integrated.IntegratedServer field_71348_o # isGamePaused
# GameSettings
public net.minecraft.client.settings.GameSettings field_186714_aM # soundLevels
public net.minecraft.client.settings.GameSettings field_151446_aD # mapSoundLevels
# Frustum
public net.minecraft.client.renderer.culling.Frustum field_78552_a # clippingHelper
@@ -117,12 +117,5 @@ public net.minecraft.client.renderer.GlStateManager$FogState
public net.minecraft.client.renderer.GlStateManager$BooleanState
public net.minecraft.client.renderer.GlStateManager$BooleanState *
# EntityLivingBase
public net.minecraft.entity.EntityLivingBase field_184621_as # HAND_STATES
public net.minecraft.entity.EntityLivingBase field_184628_bn # activeItemStackUseCount
# EntityLivingBase
public net.minecraft.client.particle.Particle *
# Example
# public net.minecraft.package.ClassName func_some_id(Ljava/lang/Class;IZS)V # methodName

View File

@@ -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.

Some files were not shown because too many files have changed in this diff Show More