Compare commits

..

5 Commits

Author SHA1 Message Date
johni0702
24c58cbbe7 Merge branch '1.8' into 1.8.9
1ffeb01 Update jGui
c122d30 Merge branch '1.8-path-segments' into 1.8
27fd25a Fix crash when server responds with garbage to language query (fixes #51)
db6b0ff Fix / by 0 when two spectator keyframes are closer than 50ms (fixes #60)
d61d0ac Fix hand of invisible spectated player being visible (fixes #59)
f7460f9 Re-enable time path after path playback (fixes #55)
bdbffd5 Add Catmull-Rom spline interpolator to simplepathing
5cbfd28 Generate interpolator settings from InterpolatorType enum
6095aef Fix loading and storing of default interpolator with settings
2c64030 Refactor simple pathing timeline logic into dedicated class with unit tests Fix logic for user-set interpolators
cd16211 Register new Property to imply that the path segment following a keyframe has a user-set interpolator instead of the default Modified Keyframe GUI to allow for custom Path interpolator
2017-01-29 11:25:30 +01:00
johni0702
3f52336852 Revert "Fix AbstractMethodError caused by FG2.0 not supporting lambdas (fixes #58)"
This reverts commit 70e3e54038.

As of c6293bc, this branch uses FG2.2 which fully supports lambdas.
2017-01-24 13:29:09 +01:00
johni0702
c6293bcbb0 Update to FG2.2 2017-01-24 13:27:57 +01:00
johni0702
c8126edbb2 Merge '1.8' into 1.8.9
64898ce Fix NPE when saving recorded resource pack
70e3e54 Fix AbstractMethodError caused by FG2.0 not supporting lambdas (fixes #58)
3f2b3d6 Fix replacing source file in Replay Editor (fixes #57)
d2def94 Fix duration of replays corrupted because of JVM/OS crash
4deb374 Enable validation on focus change for all number input fields
5302bca Fix id and text of Replay Editor button in docs
057edcc Manually inject root cert for cross-signed LetsEncrypt cert in requests
2017-01-24 13:25:53 +01:00
johni0702
01acbc6f1f Update to 1.8.9 2017-01-14 22:52:53 +01:00
148 changed files with 2330 additions and 3321 deletions

6
.gitmodules vendored
View File

@@ -2,6 +2,6 @@
path = jGui
url = https://github.com/ReplayMod/jGui
[submodule "src/main/resources/assets/replaymod/lang"]
path = src/main/resources/assets/replaymod/lang
url = https://github.com/ReplayMod/Translations
[submodule "ReplayStudio"]
path = ReplayStudio
url = https://github.com/ReplayMod/ReplayStudio

1
ReplayStudio Submodule

Submodule ReplayStudio added at c615476693

View File

@@ -1,5 +1,7 @@
import groovy.json.JsonOutput
import java.util.zip.ZipInputStream
buildscript {
repositories {
mavenCentral()
@@ -7,17 +9,23 @@ buildscript {
name = "forge"
url = "http://files.minecraftforge.net/maven"
}
maven {
name = "sponge"
url = "https://repo.spongepowered.org/maven"
}
maven {
name = "sonatype"
url = "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:1.2-SNAPSHOT'
classpath 'net.minecraftforge.gradle:ForgeGradle:2.2-SNAPSHOT'
classpath 'org.spongepowered:mixingradle:0.3-SNAPSHOT'
}
}
apply plugin: 'forge'
apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'org.spongepowered.mixin'
sourceCompatibility = 1.8
targetCompatibility = 1.8
@@ -26,22 +34,13 @@ version = gitDescribe()
group= "com.replaymod"
archivesBaseName = "replaymod"
ext {
mixinConfigs = [
'compat.shaders', 'extras.playeroverview', 'recording', 'render', 'replay'
].collect {"mixins.${it}.replaymod.json"}
mixinSrg = new File(project.buildDir, 'tmp/mixins/mixins.srg')
mixinRefMap = new File(project.buildDir, 'tmp/mixins/mixins.replaymod.refmap.json')
}
minecraft {
version = '1.7.10-10.13.4.1558-1.7.10'
coreMod = 'com.replaymod.core.LoadingPlugin'
version = '1.8.9-11.15.1.1722'
runDir = "eclipse"
mappings = "stable_12"
mappings = "stable_22"
replace '@MOD_VERSION@', project.version
// Includes intentional whitespace to stop Forge from declaring the mod to be compatible with
// a newer srg-compatible MC version (that may be using a different protocol version)
replace '@MC_VERSION@', "[ ${project.minecraft.version} ]"
replace '@MC_VERSION@', project.minecraft.version
}
repositories {
@@ -50,7 +49,7 @@ repositories {
url = "http://repo.spongepowered.org/maven/"
}
maven {
url 'https://jitpack.io'
url 'https://repo.spacehq.org/content/repositories/snapshots/'
}
}
@@ -62,7 +61,7 @@ configurations {
dependencies {
compile 'org.projectlombok:lombok:1.16.4'
compile 'org.spongepowered:mixin:0.6.8-SNAPSHOT'
compile 'org.spongepowered:mixin:0.6.4-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'
@@ -72,22 +71,24 @@ dependencies {
shade 'org.aspectj:aspectjrt:1.8.2'
shade 'com.github.replaymod:ReplayStudio:181140c'
compile project(':ReplayStudio')
testCompile 'junit:junit:4.11'
}
tasks['idea'].dependsOn ':ReplayStudio:preshadowJar'
jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
dependsOn configurations.compile
dependsOn configurations.shade
from project.mixinRefMap
dependsOn ':ReplayStudio:shadowJar'
def shade = {files(
configurations.compile.findAll {it.name.startsWith 'mixin-'}
+ configurations.shade
+ getTasks().getByPath(':ReplayStudio:shadowJar').outputs.files
)}
def noticeDir = file("$buildDir/NOTICE")
@@ -102,6 +103,27 @@ 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
@@ -116,8 +138,6 @@ jar {
manifest {
attributes 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker',
'TweakOrder': '0',
'MixinConfigs': project.mixinConfigs.join(','),
'FMLCorePlugin': 'com.replaymod.core.LoadingPlugin',
'FMLAT': 'replaymod_at.cfg'
}
}
@@ -157,8 +177,6 @@ processResources
sourceSets {
main {
// Required so resources are loaded correctly in dev env
output.resourcesDir = output.classesDir
// Directly include jGui into main build
// This could also be done by including it as a gradle subproject, however
// doing so caused classpath issues on some IntelliJ versions.
@@ -168,13 +186,7 @@ sourceSets {
resources {
srcDir 'jGui/src/main/resources'
}
}
integrationTest {
compileClasspath += main.runtimeClasspath + main.output
java {
srcDir file('src/integration-test/java')
}
resources.srcDir file('src/integration-test/resources')
refMap = "mixins.replaymod.refmap.json"
}
}
@@ -183,49 +195,10 @@ task copySrg(type: Copy, dependsOn: 'genSrgs') {
into 'build'
}
reobf.doFirst {
if (project.mixinSrg.exists()) {
addExtraSrgFile project.mixinSrg
}
}
compileJava.dependsOn copySrg
compileJava {
options.compilerArgs += [
"-AoutSrgFile=${project.mixinSrg.canonicalPath}",
"-AoutRefMapFile=${project.mixinRefMap.canonicalPath}",
"-AreobfSrgFile=${project.file('build/mcp-srg.srg').canonicalPath}"
]
}
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: 042e1c6e50...b4be91dbd9

View File

@@ -0,0 +1 @@
include 'ReplayStudio'

View File

@@ -1,30 +0,0 @@
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

@@ -1,371 +0,0 @@
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 cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
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 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();
FMLCommonHandler.instance().bus().register(new GuiEventHandler(timeout, stackTrace, onClosed));
}
public class GuiEventHandler {
final net.minecraft.client.gui.GuiScreen currentScreen = mc.currentScreen;
private final int timeout;
private final StackTraceElement[] stackTrace;
private final Runnable onClosed;
int framesPassed;
public GuiEventHandler(int timeout, StackTraceElement[] stackTrace, Runnable onClosed) {
this.timeout = timeout;
this.stackTrace = stackTrace;
this.onClosed = onClosed;
}
@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);
}
}
}
}
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();
FMLCommonHandler.instance().bus().register(new PopupEventHandler(timeout, stackTrace, onClosed));
}
public class PopupEventHandler {
private final int timeout;
private final StackTraceElement[] stackTrace;
private final Runnable onClosed;
private final AbstractGuiPopup popup;
int framesPassed;
public PopupEventHandler(int timeout, StackTraceElement[] stackTrace, Runnable onClosed) {
this.timeout = timeout;
this.stackTrace = stackTrace;
this.onClosed = onClosed;
this.popup = getPopup(mc.currentScreen);
if (this.popup == null) {
throw new IllegalStateException("No popup found.");
}
}
@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);
}
}
}
}
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();
FMLCommonHandler.instance().bus().register(new ExpectGuiEventHandler<T>(guiClass, stackTrace, onOpen));
}
public class ExpectGuiEventHandler<T> {
private final Class<T> guiClass;
private final StackTraceElement[] stackTrace;
private final Consumer<T> onOpen;
net.minecraft.client.gui.GuiScreen currentScreen;
int framesPassed;
public ExpectGuiEventHandler(Class<T> guiClass, StackTraceElement[] stackTrace, Consumer<T> onOpen) {
this.guiClass = guiClass;
this.stackTrace = stackTrace;
this.onOpen = onOpen;
}
@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;
}
}
future.setException(new UnexpectedGuiException(guiClass, stackTrace,
foundGui == null ? currentScreen : foundGui));
}
}
class UnexpectedGuiException extends Exception {
UnexpectedGuiException(Class<?> guiClass, StackTraceElement[] stackTrace, Object foundGui) {
super("Expected instance of " + guiClass + " but found " + foundGui);
setStackTrace(Arrays.copyOfRange(stackTrace, 2, stackTrace.length));
}
}
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

@@ -1,41 +0,0 @@
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

@@ -1,73 +0,0 @@
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 cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import net.minecraft.client.Minecraft;
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

@@ -1,35 +0,0 @@
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

@@ -1,9 +0,0 @@
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

@@ -1,21 +0,0 @@
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

@@ -1,59 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -1,28 +0,0 @@
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

@@ -1,25 +0,0 @@
package com.replaymod.extra;
import com.replaymod.core.AbstractTask;
import com.replaymod.extras.OpenEyeExtra;
import cpw.mods.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

@@ -1,14 +0,0 @@
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

@@ -1,33 +0,0 @@
package com.replaymod.recording;
import com.replaymod.core.AbstractTask;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
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;
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");
MinecraftForge.EVENT_BUS.register(new EventHandler());
});
});
});
}
public class EventHandler {
@SubscribeEvent
public void onRenderIngame(RenderGameOverlayEvent.Pre event) {
MinecraftForge.EVENT_BUS.unregister(this);
runLater(() -> future.set(null));
}
}
}

View File

@@ -1,36 +0,0 @@
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

@@ -1,16 +0,0 @@
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

@@ -1,33 +0,0 @@
package com.replaymod.replay;
import com.replaymod.core.AbstractTask;
import com.replaymod.replay.gui.screen.GuiReplayViewer;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import de.johni0702.minecraft.gui.function.Clickable;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
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);
MinecraftForge.EVENT_BUS.register(new EventHandler());
}));
}
public class EventHandler {
@SubscribeEvent
public void onRenderIngame(RenderGameOverlayEvent.Pre event) {
MinecraftForge.EVENT_BUS.unregister(this);
runLater(() -> future.set(null));
}
}
}

View File

@@ -1,15 +0,0 @@
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

@@ -1,42 +0,0 @@
package com.replaymod.replay;
import com.replaymod.core.AbstractTask;
import com.replaymod.extras.playeroverview.PlayerOverviewGui;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.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.renderViewEntity == mc.thePlayer) {
future.set(null);
}
}
}
}

View File

@@ -1,33 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -1,17 +1,15 @@
package com.replaymod.compat;
import com.replaymod.compat.bettersprinting.DisableBetterSprinting;
import com.replaymod.compat.optifine.DisableFastRender;
import com.replaymod.compat.oranges17animations.HideInvisibleEntities;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.eventhandler.EventBus;
import com.replaymod.compat.shaders.ShaderBeginRender;
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;
@Mod(modid = ReplayModCompat.MOD_ID,
version = "@MOD_VERSION@",
acceptedMinecraftVersions = "@MC_VERSION@",
acceptableRemoteVersions = "*",
useMetadata = true)
public class ReplayModCompat {
public static final String MOD_ID = "replaymod-compat";
@@ -19,9 +17,8 @@ public class ReplayModCompat {
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
EventBus bus = FMLCommonHandler.instance().bus();
bus.register(new ShaderBeginRender());
bus.register(new DisableFastRender());
bus.register(new HideInvisibleEntities());
DisableBetterSprinting.register();
}
}

View File

@@ -1,97 +0,0 @@
package com.replaymod.compat.bettersprinting;
import com.replaymod.replay.ReplayModReplay;
import com.replaymod.replay.events.ReplayChatMessageEvent;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import cpw.mods.fml.common.versioning.Restriction;
import cpw.mods.fml.common.versioning.VersionRange;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerControllerMP;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.IWorldAccess;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.common.MinecraftForge;
import java.util.Collections;
/**
* Old Better Sprinting versions replace the vanilla player with their own, overridden instance (replacing the camera entity).
*
* See: https://github.com/chylex/Better-Sprinting/blob/1.8/src/main/java/chylex/bettersprinting/client/player/impl/LogicImplOverride.java
*/
public class DisableBetterSprinting {
private static final VersionRange OLD_VERSION = VersionRange.newRange(null,
Collections.singletonList(new Restriction(null, false, new DefaultArtifactVersion("2.0.0"), false)));
private static final String LOGIC_CLASS_NAME = "chylex.bettersprinting.client.player.impl.LogicImplOverride";
private static final String CONTROLLER_OVERRIDE_CLASS_NAME = LOGIC_CLASS_NAME + ".PlayerControllerMPOverride";
public static void register() {
Loader.instance().getModList().stream()
.filter(mod -> mod.getModId().equalsIgnoreCase("bettersprinting"))
.findFirst()
.map(ModContainer::getProcessedVersion).filter(OLD_VERSION::containsVersion)
.ifPresent($_ -> MinecraftForge.EVENT_BUS.register(new DisableBetterSprinting()));
}
private DisableBetterSprinting() {}
private final Minecraft mc = Minecraft.getMinecraft();
private PlayerControllerMP originalController;
private BetterSprintingWorldAccess worldAccessHook = new BetterSprintingWorldAccess();
@SubscribeEvent(priority = EventPriority.HIGH)
public void beforeGuiOpenEvent(GuiOpenEvent event) {
if (ReplayModReplay.instance.getReplayHandler() != null && mc.theWorld != null) {
// During replay, get ready to revert BetterSprinting's overwritten playerController
originalController = mc.playerController;
mc.theWorld.addWorldAccess(worldAccessHook);
}
}
@SubscribeEvent(priority = EventPriority.LOW)
public void afterGuiOpenEvent(GuiOpenEvent event) {
if (ReplayModReplay.instance.getReplayHandler() != null && mc.theWorld != null) {
mc.theWorld.removeWorldAccess(worldAccessHook);
}
}
@SubscribeEvent
public void onReplayChatMessage(ReplayChatMessageEvent event) {
// Suppress this message if it's the Better Sprinting warning message
for (StackTraceElement elem : Thread.currentThread().getStackTrace()) {
if (LOGIC_CLASS_NAME.equals(elem.getClassName())) {
event.setCanceled(true);
return;
}
}
}
private class BetterSprintingWorldAccess implements IWorldAccess {
@Override
public void onEntityDestroy(Entity entityIn) {
if (mc.playerController != null && mc.playerController.getClass().getName().equals(CONTROLLER_OVERRIDE_CLASS_NAME)) {
// Someone has secretly swapped out the player controller and is about to substitute their own player entity.
// This is the right time to destroy their plan.
mc.playerController = originalController;
}
}
@Override public void markBlockForUpdate(int x, int y, int z) {}
@Override public void markBlockForRenderUpdate(int p_147588_1_, int p_147588_2_, int p_147588_3_) {}
@Override public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2) {}
@Override public void playSound(String soundName, double x, double y, double z, float volume, float pitch) {}
@Override public void playSoundToNearExcept(EntityPlayer except, String soundName, double x, double y, double z, float volume, float pitch) {}
@Override public void spawnParticle(String p_180442_1_, double p_180442_2_, double p_180442_3_, double p_180442_5_, double p_180442_7_, double p_180442_9_, double p_180442_11_) {}
@Override public void onEntityCreate(Entity p_72703_1_) {}
@Override public void playRecord(String recordName, int x, int y, int z) {}
@Override public void broadcastSound(int p_180440_1_, int x, int y, int z, int p_180440_3_) {}
@Override public void playAuxSFX(EntityPlayer p_72706_1_, int p_72706_2_, int p_72706_3_, int p_72706_4_, int p_72706_5_, int p_72706_6_) {}
@Override public void destroyBlockPartially(int p_147587_1_, int p_147587_2_, int p_147587_3_, int p_147587_4_, int p_147587_5_) {}
@Override public void onStaticEntitiesChanged() {}
}
}

View File

@@ -1,11 +1,10 @@
package com.replaymod.compat.optifine;
import com.replaymod.render.events.ReplayRenderEvent;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.Minecraft;
import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
// TODO 1.7.10: Is this still necessary (probably, but needs checking)?
public class DisableFastRender {
private final Minecraft mc = Minecraft.getMinecraft();

View File

@@ -1,28 +0,0 @@
package com.replaymod.compat.oranges17animations;
import com.replaymod.replay.camera.CameraEntity;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.Minecraft;
import net.minecraftforge.client.event.RenderLivingEvent;
/**
* Orange seems to have copied vast parts of the RendererLivingEntity into their ArmorAnimation class which cancels the RenderLivingEvent.Pre and calls its own code instead.
* This breaks our mixin which assures that, even though the camera is in spectator mode, it cannot see invisible entities.
*
* To fix this issue, we simply cancel the RenderLivingEvent.Pre before it gets to ArmorAnimation if the entity is invisible.
*/
public class HideInvisibleEntities {
private final Minecraft mc = Minecraft.getMinecraft();
private final boolean modLoaded = Loader.isModLoaded("animations");
@SubscribeEvent(priority = EventPriority.HIGH)
public void preRenderLiving(RenderLivingEvent.Pre event) {
if (modLoaded) {
if (mc.thePlayer instanceof CameraEntity && event.entity.isInvisible()) {
event.setCanceled(true);
}
}
}
}

View File

@@ -0,0 +1,39 @@
package com.replaymod.compat.shaders;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import java.lang.reflect.InvocationTargetException;
public class ShaderBeginRender {
private final Minecraft mc = Minecraft.getMinecraft();
/**
* Invokes Shaders#beginRender when rendering a video,
* as this would usually get called by EntityRenderer#renderWorld,
* which we're not calling during rendering.
*/
@SubscribeEvent
public void onRenderTickStart(TickEvent.RenderTickEvent event) {
if (event.phase != TickEvent.Phase.START) return;
if (ShaderReflection.shaders_beginRender == null) return;
if (ShaderReflection.config_isShaders == null) return;
try {
// check if video is being rendered
if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null)
return;
// check if Shaders are enabled
if (!(boolean) (ShaderReflection.config_isShaders.invoke(null))) return;
ShaderReflection.shaders_beginRender.invoke(null, mc, mc.timer.elapsedPartialTicks, 0);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,53 @@
package com.replaymod.compat.shaders;
import net.minecraft.client.Minecraft;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ShaderReflection {
// Shaders.frameTimeCounter
public static Field shaders_frameTimeCounter;
// Shaders.isShadowPass
public static Field shaders_isShadowPass;
// Shaders.beginRender()
public static Method shaders_beginRender;
// RenderGlobal.chunksToUpdateForced (Optifine only)
public static Field renderGlobal_chunksToUpdateForced;
// Config.isShaders() (Optifine only)
public static Method config_isShaders;
static {
try {
shaders_frameTimeCounter = Class.forName("shadersmod.client.Shaders")
.getDeclaredField("frameTimeCounter");
shaders_frameTimeCounter.setAccessible(true);
shaders_isShadowPass = Class.forName("shadersmod.client.Shaders")
.getDeclaredField("isShadowPass");
shaders_isShadowPass.setAccessible(true);
shaders_beginRender = Class.forName("shadersmod.client.Shaders")
.getDeclaredMethod("beginRender", Minecraft.class, float.class, long.class);
shaders_beginRender.setAccessible(true);
renderGlobal_chunksToUpdateForced = Class.forName("net.minecraft.client.renderer.RenderGlobal")
.getDeclaredField("chunksToUpdateForced");
renderGlobal_chunksToUpdateForced.setAccessible(true);
config_isShaders = Class.forName("Config")
.getDeclaredMethod("isShaders");
config_isShaders.setAccessible(true);
} catch (ClassNotFoundException ignore) {
// no shaders mod installed
} catch (NoSuchMethodException | NoSuchFieldException e) {
// the method wasn't found. Has it been renamed?
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,28 @@
package com.replaymod.compat.shaders.mixin;
import com.replaymod.compat.shaders.ShaderReflection;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replay.ReplayModReplay;
import net.minecraft.client.renderer.EntityRenderer;
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;
@Mixin(EntityRenderer.class)
public abstract class MixinShaderEntityRenderer {
@Inject(method = "renderWorldPass", at = @At("HEAD"))
private void replayModCompat_updateShaderFrameTimeCounter(CallbackInfo ignore) {
if (ReplayModReplay.instance.getReplayHandler() == null) return;
if (ShaderReflection.shaders_frameTimeCounter == null) return;
ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler();
float timestamp = replayHandler.getReplaySender().currentTimeStamp() / 1000f % 3600f;
try {
ShaderReflection.shaders_frameTimeCounter.set(null, timestamp);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,28 @@
package com.replaymod.compat.shaders.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.chunk.RenderChunk;
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(RenderChunk.class)
public abstract class MixinShaderRenderChunk {
private final Minecraft mc = Minecraft.getMinecraft();
/**
* Changes the RenderChunk#isPlayerUpdate method that Optifine adds
* to always return true while rendering so no chunks are being added
* to a separate rendering queue
*/
@Inject(method = "isPlayerUpdate", at = @At("HEAD"), cancellable = true)
private void replayModCompat_disableIsPlayerUpdate(CallbackInfoReturnable<Boolean> ci) {
if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null) return;
ci.setReturnValue(true);
}
}

View File

@@ -0,0 +1,40 @@
package com.replaymod.compat.shaders.mixin;
import com.replaymod.compat.shaders.ShaderReflection;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.culling.ICamera;
import net.minecraft.entity.Entity;
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(RenderGlobal.class)
public abstract class MixinShaderRenderGlobal {
private final Minecraft mc = Minecraft.getMinecraft();
@Shadow
public boolean displayListEntitiesDirty;
@Inject(method = "setupTerrain", at = @At("HEAD"), cancellable = true)
public void replayModCompat_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera,
int frameCount, boolean playerSpectator, CallbackInfo ci) {
if (((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler() == null) return;
if (ShaderReflection.shaders_isShadowPass == null) return;
// when called by the shadow pass, displayListEntitiesDirty can't be set to false, as no chunk updates
// are being processed. As it's being set to true by ChunkLoadingRenderGlobal#updateChunks, we have to
// set it to false manually to exit the loop imposed by MixinRenderGlobal#replayModRender_setupTerrain.
try {
if ((boolean) ShaderReflection.shaders_isShadowPass.get(null) == true) {
displayListEntitiesDirty = false;
}
} catch (IllegalAccessException ignore) {}
}
}

View File

@@ -1,50 +0,0 @@
package com.replaymod.compat.shaders.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replay.ReplayModReplay;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Pseudo
@Mixin(targets = "shadersmod/client/Shaders", remap = false)
public abstract class MixinShaders {
@Shadow static Minecraft mc;
@Shadow static long systemTime;
@Shadow static long lastSystemTime;
@Shadow static long diffSystemTime;
@Shadow static int frameCounter;
@Shadow static float frameTime;
@Shadow static float frameTimeCounter;
@Redirect(method = "beginRender", at = @At(value = "INVOKE", target = "Ljava/lang/System;currentTimeMillis()J"))
private static long replayModCompat_currentTimeMillis() {
ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler();
if (replayHandler != null) {
systemTime = replayHandler.getReplaySender().currentTimeStamp();
// We need to manipulate all the other previous-frame-time-based variables too
lastSystemTime = 0; // Draw all frames as if they were the first ones
// diffSystemTime will be set to 0 by Shaders
// frameTime will be set to 0 by Shaders
frameTimeCounter = systemTime / 1000f; // will be %= 3600f by Shaders
// Set frameCounter only if rendering is in progress
EntityRendererHandler entityRendererHandler =
((EntityRendererHandler.IEntityRenderer) mc.entityRenderer).replayModRender_getHandler();
if (entityRendererHandler != null) {
frameCounter = entityRendererHandler.getRenderInfo().getFramesDone();
}
return systemTime;
}
return System.currentTimeMillis();
}
}

View File

@@ -1,94 +0,0 @@
package com.replaymod.core;
import net.minecraft.launchwrapper.IClassTransformer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Insert glGetError checks after all calls to any method in any GL*, ARB* and EXT* classes
*/
public class GLErrorTransformer implements IClassTransformer {
private static final String GL_CLASS = "org.lwjgl.opengl.GL";
private static final String GL = GL_CLASS.replace('.', '/');
private static final String ARB_CLASS = "org.lwjgl.opengl.ARB";
private static final String ARB = ARB_CLASS.replace('.', '/');
private static final String EXT_CLASS = "org.lwjgl.opengl.EXT";
private static final String EXT = EXT_CLASS.replace('.', '/');
private static final String GLErrorTransformer_CLASS = GLErrorTransformer.class.getName();
private static final String GLErrorTransformer = GLErrorTransformer_CLASS.replace('.', '/');
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
// Ignore (anonymous) inner classes of this transformer
if (name.startsWith(GLErrorTransformer_CLASS)) return basicClass;
ClassReader reader = new ClassReader(basicClass);
ClassWriter writer = new ClassWriter(reader, 0);
reader.accept(new ClassVisitor(Opcodes.ASM5, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM5, mv) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
super.visitMethodInsn(opcode, owner, name, desc, itf);
if (owner.startsWith(GL) || owner.startsWith(ARB) || owner.startsWith(EXT)) {
visitLdcInsn(owner.replace('/', '.'));
visitLdcInsn(name);
super.visitMethodInsn(Opcodes.INVOKESTATIC, GLErrorTransformer, "glErrorCheck", "(Ljava/lang/String;Ljava/lang/String;)V", false);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 2, maxLocals);
}
};
}
}, 0);
return writer.toByteArray();
}
private static boolean inGlBegin;
@SuppressWarnings("unused") // Called via ASM
public static void glErrorCheck(String cls, String method) {
if (method.equals("glBegin") && cls.equals(GL_CLASS + "11")) {
if (inGlBegin) {
// glBegin within glBegin
throw new GLError(GL11.GL_INVALID_OPERATION, cls, method);
}
inGlBegin = true;
return;
}
if (inGlBegin && method.equals("glEnd") && cls.equals(GL_CLASS + "11")) {
inGlBegin = false;
}
if (inGlBegin) {
return; // Cannot call glGetError in between of glBegin and glEnd
}
int err = GL11.glGetError();
if (err != 0) {
GLError e = new GLError(err, cls, method);
if ("true".equals(System.getProperty("replaymod.glerrors.throw", "true"))) {
throw e;
} else {
e.printStackTrace();
}
}
}
public static class GLError extends RuntimeException {
private GLError(int err, String cls, String method) {
super(GLU.gluErrorString(err) + " (" + err + ")");
StackTraceElement[] stack = getStackTrace();
stack[0] = new StackTraceElement(cls, method, stack[0].getFileName(), stack[0].getLineNumber());
setStackTrace(stack);
}
}
}

View File

@@ -1,105 +0,0 @@
package com.replaymod.core;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.HashSet;
import java.util.Set;
/**
* Redirect all calls to GL11.glEnable and GL11.glDisable to the GLStateTracker (excluding that class itself).
*/
public class GLStateTrackerTransformer implements IClassTransformer {
private static final String GL11_CLASS = "org.lwjgl.opengl.GL11";
private static final String GL11 = GL11_CLASS.replace('.', '/');
private static final String glEnable = "glEnable";
private static final String glDisable = "glDisable";
private static final String GLStateTracker_CLASS = "com.replaymod.render.hooks.GLStateTracker";
private static final String GLStateTracker = GLStateTracker_CLASS.replace('.', '/');
private static final String hook_glEnable = "hook_glEnable";
private static final String hook_glDisable = "hook_glDisable";
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
// Ignore (anonymous) inner classes of this transformer
if (name.startsWith(GLStateTrackerTransformer.class.getName())) return basicClass;
// Ignore the state tracker itself
if (name.equals(GLStateTracker_CLASS)) return basicClass;
ClassReader reader = new ClassReader(basicClass);
Set<Method> eligibleMethods = findEligibleMethods(reader);
if (eligibleMethods.isEmpty()) {
return basicClass;
}
ClassWriter writer = new ClassWriter(reader, 0);
reader.accept(new ClassVisitor(Opcodes.ASM5, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (!eligibleMethods.contains(new Method(name, desc))) {
return mv;
}
return new MethodVisitor(Opcodes.ASM5, mv) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (owner.equals(GL11)) {
if (name.equals(glEnable)) {
owner = GLStateTracker;
name = hook_glEnable;
} else if (name.equals(glDisable)) {
owner = GLStateTracker;
name = hook_glDisable;
}
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
};
}
}, 0);
return writer.toByteArray();
}
private Set<Method> findEligibleMethods(ClassReader reader) {
Set<Method> eligibleMethods = new HashSet<>();
reader.accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, String methodName, String methodDesc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (owner.equals(GL11) && (name.equals(glEnable) || name.equals(glDisable))) {
eligibleMethods.add(new Method(methodName, methodDesc));
}
}
};
}
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return eligibleMethods;
}
private static class Method {
private final String name, desc;
private Method(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Method method = (Method) o;
return name.equals(method.name) && desc.equals(method.desc);
}
@Override
public int hashCode() {
return name.hashCode() ^ desc.hashCode();
}
}
}

View File

@@ -2,14 +2,14 @@ package com.replaymod.core;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.InputEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.util.ReportedException;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.lwjgl.input.Keyboard;
import java.util.Collection;
@@ -64,7 +64,7 @@ public class KeyBindingRegistry {
public void handleRepeatedKeyBindings() {
for (Map.Entry<KeyBinding, Collection<Runnable>> entry : repeatedKeyBindingHandlers.asMap().entrySet()) {
if (entry.getKey().getIsKeyPressed()) {
if (entry.getKey().isKeyDown()) {
invokeKeyBindingHandlers(entry.getKey(), entry.getValue());
}
}

View File

@@ -1,7 +1,7 @@
package com.replaymod.core;
import cpw.mods.fml.relauncher.CoreModManager;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import net.minecraftforge.fml.relauncher.CoreModManager;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import org.apache.logging.log4j.LogManager;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.Mixins;
@@ -10,8 +10,6 @@ import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class LoadingPlugin implements IFMLLoadingPlugin {
@@ -22,7 +20,6 @@ public class LoadingPlugin implements IFMLLoadingPlugin {
Mixins.addConfiguration("mixins.render.replaymod.json");
Mixins.addConfiguration("mixins.replay.replaymod.json");
Mixins.addConfiguration("mixins.compat.shaders.replaymod.json");
Mixins.addConfiguration("mixins.extras.playeroverview.replaymod.json");
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
if (codeSource != null) {
@@ -32,8 +29,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin {
if (file.isFile()) {
// This forces forge to reexamine the jar file for FML mods
// Should eventually be handled by Mixin itself, maybe?
CoreModManager.getLoadedCoremods().remove(file.getName());
CoreModManager.getReparseableCoremods().add(file.getName());
CoreModManager.getIgnoredMods().remove(file.getName());
}
} catch (URISyntaxException e) {
e.printStackTrace();
@@ -46,12 +42,8 @@ public class LoadingPlugin implements IFMLLoadingPlugin {
@Override
public String[] getASMTransformerClass() {
List<String> transformers = new ArrayList<>();
if ("true".equals(System.getProperty("replaymod.glerrors", "false"))) {
transformers.add(GLErrorTransformer.class.getName());
}
transformers.add(GLStateTrackerTransformer.class.getName());
return transformers.stream().toArray(String[]::new);
return new String[]{
};
}
@Override

View File

@@ -7,24 +7,11 @@ 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.replay.InputReplayTimer;
import com.replaymod.replaystudio.util.I18n;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.EventBus;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import de.johni0702.minecraft.gui.container.GuiScreen;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
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;
@@ -34,19 +21,27 @@ import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.ResourceLocation;
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;
import net.minecraftforge.fml.common.Mod.Instance;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Queue;
@Mod(modid = ReplayMod.MOD_ID,
useMetadata = true,
version = "@MOD_VERSION@",
acceptedMinecraftVersions = "@MC_VERSION@",
acceptableRemoteVersions = "*",
updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json",
guiFactory = "com.replaymod.core.gui.GuiFactory")
public class ReplayMod {
@@ -59,10 +54,10 @@ public class ReplayMod {
private static String parseMinecraftVersion() {
CrashReport crashReport = new CrashReport("", new Throwable());
@SuppressWarnings("unchecked")
List<CrashReportCategory.Entry> list = crashReport.getCategory().field_85077_c;
List<CrashReportCategory.Entry> list = crashReport.getCategory().children;
for (CrashReportCategory.Entry entry : list) {
if ("Minecraft Version".equals(entry.func_85089_a())) {
return entry.func_85090_b();
if ("Minecraft Version".equals(entry.getKey())) {
return entry.getValue();
}
}
return "Unknown";
@@ -121,7 +116,6 @@ public class ReplayMod {
new MainMenuHandler().register();
FMLCommonHandler.instance().bus().register(this);
FMLCommonHandler.instance().bus().register(keyBindingRegistry);
getKeyBindingRegistry().registerKeyBinding("replaymod.input.settings", 0, () -> {
@@ -133,155 +127,10 @@ public class ReplayMod {
public void postInit(FMLPostInitializationEvent event) throws IOException {
settingsRegistry.save(); // Save default values to disk
/* TODO 1.7.10: MC crashes when setting render distance to > 16
if(!FMLClientHandler.instance().hasOptifine())
GameSettings.Options.RENDER_DISTANCE.setValueMax(64f);
*/
if (System.getProperty("replaymod.render.file") != null) {
final File file = new File(System.getProperty("replaymod.render.file"));
if (!file.exists()) {
throw new IOException("File \"" + file.getPath() + "\" not found.");
}
final String path = System.getProperty("replaymod.render.path");
String type = System.getProperty("replaymod.render.type");
String bitrate = System.getProperty("replaymod.render.bitrate");
String fps = System.getProperty("replaymod.render.fps");
String waitForChunks = System.getProperty("replaymod.render.waitforchunks");
String linearMovement = System.getProperty("replaymod.render.linearmovement");
String skyColor = System.getProperty("replaymod.render.skycolor");
String width = System.getProperty("replaymod.render.width");
String height = System.getProperty("replaymod.render.height");
String exportCommand = System.getProperty("replaymod.render.exportcommand");
String exportCommandArgs = System.getProperty("replaymod.render.exportcommandargs");
/*
final RenderOptions options = new RenderOptions();
if (bitrate != null) {
options.setBitrate(bitrate);
}
if (fps != null) {
options.setFps(Integer.parseInt(fps));
}
if (waitForChunks != null) {
options.setWaitForChunks(Boolean.parseBoolean(waitForChunks));
}
if (linearMovement != null) {
options.setLinearMovement(Boolean.parseBoolean(linearMovement));
}
if (skyColor != null) {
if (skyColor.startsWith("0x")) {
options.setSkyColor(Integer.parseInt(skyColor.substring(2), 16));
} else {
options.setSkyColor(Integer.parseInt(skyColor));
}
}
if (width != null) {
options.setWidth(Integer.parseInt(width));
}
if (height != null) {
options.setHeight(Integer.parseInt(height));
}
if (exportCommand != null) {
options.setExportCommand(exportCommand);
}
if (exportCommandArgs != null) {
options.setExportCommandArgs(exportCommandArgs);
} else {
options.setExportCommandArgs(EncodingPreset.MP4DEFAULT.getCommandLineArgs());
}
options.setOutputFile(new File(String.valueOf(System.currentTimeMillis())));
Pipelines.Preset pipelinePreset = Pipelines.Preset.DEFAULT;
if (type != null) {
String[] parts = type.split(":");
type = parts[0].toUpperCase();
if ("NORMAL".equals(type) || "DEFAULT".equals(type)) {
pipelinePreset = Pipelines.Preset.DEFAULT;
} else if ("STEREO".equals(type) || "STEREOSCOPIC".equals(type)) {
pipelinePreset = Pipelines.Preset.STEREOSCOPIC;
} else if ("CUBIC".equals(type)) {
if (parts.length < 2) {
throw new IllegalArgumentException("Cubic renderer requires boolean for whether it's stable.");
}
pipelinePreset = Pipelines.Preset.CUBIC;
} else if ("EQUIRECTANGULAR".equals(type)) {
if (parts.length < 2) {
throw new IllegalArgumentException("Equirectangular renderer requires boolean for whether it's stable.");
}
pipelinePreset = Pipelines.Preset.EQUIRECTANGULAR;
} else if ("ODS".equals(type)) {
if (parts.length < 2) {
throw new IllegalArgumentException("ODS renderer requires boolean for whether it's stable.");
}
pipelinePreset = Pipelines.Preset.ODS;
} else {
throw new IllegalArgumentException("Unknown type: " + parts[0]);
}
}
options.setMode(pipelinePreset);
*/
@SuppressWarnings("unchecked")
Queue<ListenableFutureTask> tasks = mc.scheduledTasks;
synchronized (mc.scheduledTasks) {
tasks.add(ListenableFutureTask.create(new Runnable() {
@Override
public void run() {
String renderDistance = System.getProperty("replaymod.render.mc.renderdistance");
String clouds = System.getProperty("replaymod.render.mc.clouds");
if (renderDistance != null) {
mc.gameSettings.renderDistanceChunks = Integer.parseInt(renderDistance);
}
if (clouds != null) {
mc.gameSettings.clouds = Boolean.parseBoolean(clouds);
}
System.out.println("Loading replay...");
// TODO
// try {
// ReplayHandler.startReplay(file, false);
// } catch (Throwable t) {
// t.printStackTrace();
// FMLCommonHandler.instance().exitJava(1, false);
// }
//
// int index = 0;
// if (path != null) {
// for (KeyframeSet set : ReplayHandler.getKeyframeRepository()) {
// if (set.getName().equals(path)) {
// break;
// }
// index++;
// }
// if (index >= ReplayHandler.getKeyframeRepository().length) {
// throw new IllegalArgumentException("No path named \"" + path + "\".");
// }
// } else if (ReplayHandler.getKeyframeRepository().length == 0) {
// throw new IllegalArgumentException("Replay file has no paths defined.");
// }
// ReplayHandler.useKeyframePresetFromRepository(index);
//
// System.out.println("Rendering started...");
// try {
// ReplayProcess.startReplayProcess(options, true);
// } catch (Throwable t) {
// t.printStackTrace();
// FMLCommonHandler.instance().exitJava(1, false);
// }
// if (mc.hasCrashed) {
// System.out.println(mc.crashReporter.getCompleteReport());
// }
// System.out.println("Rendering done. Shutting down...");
// mc.shutdown();
}
}, null));
}
testIfMoeshAndExitMinecraft();
}
testIfMoeshAndExitMinecraft();
runLater(() -> {
// Cleanup deleted corrupted replays
@@ -317,53 +166,9 @@ public class ReplayMod {
});
}
/**
* Set when the currently running code has been scheduled by runLater.
* If this is the case, subsequent calls to runLater have to be delayed until all scheduled tasks have been
* processed, otherwise a livelock may occur.
*/
private boolean inRunLater = false;
// 1.7.10: Cannot use MC's because it is processed only during ticks (so not at all when replay is paused)
private final Queue<ListenableFutureTask<?>> scheduledTasks = new ArrayDeque<>();
public void runLater(Runnable runnable) {
if (mc.isCallingFromMinecraftThread() && inRunLater) {
EventBus bus = FMLCommonHandler.instance().bus();
bus.register(new RunLaterHelper(runnable));
return;
}
synchronized (scheduledTasks) {
scheduledTasks.add(ListenableFutureTask.create(() -> {
inRunLater = true;
try {
runnable.run();
} finally {
inRunLater = false;
}
}, null));
}
}
// in 1.7.10 apparently events can't be delivered to anonymous classes
@RequiredArgsConstructor
public class RunLaterHelper {
private final Runnable runnable;
@SubscribeEvent
public void onRenderTick(TickEvent.RenderTickEvent event) {
if (event.phase == TickEvent.Phase.START) {
runLater(runnable);
FMLCommonHandler.instance().bus().unregister(this);
}
}
}
@SubscribeEvent
public void runScheduledTasks(InputReplayTimer.RunScheduledTasks event) {
synchronized (scheduledTasks) {
while (!scheduledTasks.isEmpty()) {
scheduledTasks.poll().run();
}
synchronized (mc.scheduledTasks) {
mc.scheduledTasks.add(ListenableFutureTask.create(runnable, null));
}
}

View File

@@ -1,9 +1,9 @@
package com.replaymod.core;
import com.replaymod.core.events.SettingsChangedEvent;
import cpw.mods.fml.common.FMLCommonHandler;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.FMLCommonHandler;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

View File

@@ -1,9 +1,9 @@
package com.replaymod.core.events;
import com.replaymod.core.SettingsRegistry;
import cpw.mods.fml.common.eventhandler.Event;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraftforge.fml.common.eventhandler.Event;
@RequiredArgsConstructor
public class SettingsChangedEvent extends Event {

View File

@@ -1,9 +1,9 @@
package com.replaymod.core.gui;
import com.replaymod.core.ReplayMod;
import cpw.mods.fml.client.IModGuiFactory;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.IModGuiFactory;
import java.util.Set;

View File

@@ -1,10 +1,10 @@
package com.replaymod.core.handler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;

View File

@@ -1,9 +1,10 @@
package com.replaymod.core.utils;
import com.replaymod.replaystudio.data.ModInfo;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.registry.GameData;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.registry.GameData;
import java.util.*;
import java.util.function.Function;
@@ -16,19 +17,14 @@ public class ModCompat {
Map<String, ModContainer> ignoreCaseMap = Loader.instance().getModList().stream()
.collect(Collectors.toMap(m -> m.getModId().toLowerCase(), Function.identity()));
return Stream.concat(
((Set<String>) GameData.getBlockRegistry().getKeys()).stream(),
((Set<String>) GameData.getItemRegistry().getKeys()).stream()
).map(ModCompat::getResourceDomain).filter(s -> !s.equals("minecraft")).distinct()
((Set<ResourceLocation>) GameData.getBlockRegistry().getKeys()).stream(),
((Set<ResourceLocation>) GameData.getItemRegistry().getKeys()).stream()
).map(ResourceLocation::getResourceDomain).filter(s -> !s.equals("minecraft")).distinct()
.map(String::toLowerCase).map(ignoreCaseMap::get).filter(mod -> mod != null)
.map(mod -> new ModInfo(mod.getModId(), mod.getName(), mod.getVersion()))
.collect(Collectors.toList());
}
private static String getResourceDomain(String name) {
if (!name.contains(":")) return null; // Still using old names without namespace, can't do anything, ignore
return name.split(":", 2)[0];
}
public static final class ModInfoDifference {
private final Set<ModInfo> missing = new HashSet<>();
private final Map<ModInfo, String> differing = new HashMap<>();

View File

@@ -1,6 +1,9 @@
package com.replaymod.core.utils;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -26,21 +29,22 @@ public class OpenGLUtils {
}
public static void drawRotatedRectWithCustomSizedTexture(int x, int y, float rotation, float u, float v, int width, int height, float textureWidth, float textureHeight) {
GL11.glPushMatrix();
GlStateManager.pushMatrix();
float f4 = 1.0F / textureWidth;
float f5 = 1.0F / textureHeight;
Tessellator tessellator = Tessellator.instance;
GL11.glTranslatef(x+(width/2), y+(width/2), 0);
GL11.glRotatef(rotation, 0, 0, 1);
tessellator.startDrawingQuads();
tessellator.addVertexWithUV(-width / 2, height / 2, 0.0D, (double) (u * f4), (double) ((v + (float) height) * f5));
tessellator.addVertexWithUV(width/2, height/2, 0.0D, (double)((u + (float)width) * f4), (double)((v + (float)height) * f5));
tessellator.addVertexWithUV(width/2, -height/2, 0.0D, (double)((u + (float)width) * f4), (double)(v * f5));
tessellator.addVertexWithUV(-width/2, -height/2, 0.0D, (double)(u * f4), (double)(v * f5));
Tessellator tessellator = Tessellator.getInstance();
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);
vertexBuffer.pos(-width / 2, height / 2, 0.0D).tex((double) (u * f4), (double) ((v + (float) height) * f5)).endVertex();
vertexBuffer.pos(width/2, height/2, 0.0D).tex((double)((u + (float)width) * f4), (double)((v + (float)height) * f5)).endVertex();
vertexBuffer.pos(width/2, -height/2, 0.0D).tex((double)((u + (float)width) * f4), (double)(v * f5)).endVertex();
vertexBuffer.pos(-width/2, -height/2, 0.0D).tex((double)(u * f4), (double)(v * f5)).endVertex();
tessellator.draw();
GL11.glPopMatrix();
GlStateManager.popMatrix();
}
}

View File

@@ -1,11 +1,8 @@
package com.replaymod.core.utils;
import io.netty.buffer.Unpooled;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.S3FPacketCustomPayload;
import java.io.IOException;
/**
* Restrictions set by the server,
* @see <a href="https://gist.github.com/Johni0702/2547c463e51f65f312cb">Replay Restrictions Gist</a>
@@ -17,8 +14,8 @@ public class Restrictions {
private boolean onlyFirstPerson;
private boolean onlyRecordingPlayer;
public String handle(S3FPacketCustomPayload packet) throws IOException {
PacketBuffer buffer = new PacketBuffer(Unpooled.wrappedBuffer(packet.func_149168_d()));
public String handle(S3FPacketCustomPayload packet) {
PacketBuffer buffer = packet.getBufferData();
while (buffer.isReadable()) {
String name = buffer.readStringFromBuffer(64);
boolean active = buffer.readBoolean();

View File

@@ -1,9 +1,7 @@
package com.replaymod.core.utils;
import com.google.common.net.PercentEscaper;
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;
@@ -17,21 +15,16 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import lombok.SneakyThrows;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.client.resources.DefaultPlayerSkin;
import net.minecraft.crash.CrashReport;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.Logger;
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;
@@ -41,8 +34,6 @@ import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -54,7 +45,6 @@ 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;
@@ -144,48 +134,21 @@ public class Utils {
return mail.matches("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$");
}
private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false);
public static String replayNameToFileName(String replayName) {
return REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr";
}
@SneakyThrows(UnsupportedEncodingException.class)
public static String fileNameToReplayName(String fileName) {
String baseName = FilenameUtils.getBaseName(fileName);
try {
return URLDecoder.decode(baseName, Charsets.UTF_8.name());
} catch (IllegalArgumentException e) {
return baseName;
}
}
public static ResourceLocation getResourceLocationForPlayerUUID(UUID uuid) {
EntityPlayer player = Minecraft.getMinecraft().theWorld.getPlayerEntityByUUID(uuid);
if (player == null || !(player instanceof AbstractClientPlayer)) {
return AbstractClientPlayer.locationStevePng;
NetworkPlayerInfo info = getMinecraft().getNetHandler().getPlayerInfo(uuid);
ResourceLocation skinLocation;
if (info != null && info.hasLocationSkin()) {
skinLocation = info.getLocationSkin();
} else {
skinLocation = DefaultPlayerSkin.getDefaultSkin(uuid);
}
return ((AbstractClientPlayer) player).getLocationSkin();
return skinLocation;
}
public static boolean isCtrlDown() {
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

@@ -27,7 +27,7 @@ public class WrappedTimer extends Timer {
to.elapsedPartialTicks = from.elapsedPartialTicks;
to.lastSyncSysClock = from.lastSyncSysClock;
to.lastSyncHRClock = from.lastSyncHRClock;
to.field_74285_i = from.field_74285_i;
to.counter = from.counter;
to.timeSyncAdjustment = from.timeSyncAdjustment;
}
}

View File

@@ -3,15 +3,14 @@ package com.replaymod.editor;
import com.replaymod.core.ReplayMod;
import com.replaymod.editor.handler.GuiHandler;
import com.replaymod.online.Setting;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
@Mod(modid = ReplayModEditor.MOD_ID,
version = "@MOD_VERSION@",
acceptedMinecraftVersions = "@MC_VERSION@",
acceptableRemoteVersions = "*",
useMetadata = true)
public class ReplayModEditor {
public static final String MOD_ID = "replaymod-editor";

View File

@@ -36,6 +36,7 @@ import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.crash.CrashReport;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.lwjgl.util.ReadableDimension;
import javax.annotation.Nullable;
@@ -58,9 +59,6 @@ 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<>();
@@ -86,18 +84,15 @@ 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, y(tabButtons) + height(tabButtons) + 10);
pos(currentTabPanel, 10, 50);
size(currentTabPanel, width - 20, y(buttonPanel) - 10 - y(currentTabPanel));
}
pos(tabButtons, 10, 20);
size(tabButtons, width - 20, 20);
}
});
}
@@ -124,7 +119,7 @@ public class GuiReplayEditor extends GuiScreen {
}
public void save(File inputFile, PacketStream.FilterInfo...filters) {
save(Utils.fileNameToReplayName(inputFile.getName()), (outputFile) -> {
save(FilenameUtils.getBaseName(inputFile.getName()), (outputFile) -> {
Studio studio = new ReplayStudio();
File tmpDir = null;
try {
@@ -219,7 +214,7 @@ public class GuiReplayEditor extends GuiScreen {
Utils.error(LOGGER, GuiReplayEditor.this, CrashReport.makeCrashReport(e, "Getting replay folder"), null);
return;
}
File targetFile = new File(replayFolder, Utils.replayNameToFileName(name));
File targetFile = new File(replayFolder, name + ".mcpr");
if (targetFile.exists()) {
LOGGER.trace("Selected file already exists, asking for confirmation");
Futures.addCallback(GuiYesNoPopup.open(GuiReplayEditor.this,

View File

@@ -20,6 +20,7 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import net.minecraft.client.resources.I18n;
import net.minecraft.crash.CrashReport;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.lwjgl.util.Dimension;
@@ -51,7 +52,7 @@ public class GuiTrimPanel extends GuiPanel {
public final GuiDropdownMenu<File> inputReplays = new GuiDropdownMenu<File>(this)
.setMinSize(new Dimension(200, 20)).onSelection(i -> updateSelectedReplay())
.setToString(f -> f == NO_REPLAY ? "" : Utils.fileNameToReplayName(f.getName()));
.setToString(f -> f == NO_REPLAY ? "" : FilenameUtils.getBaseName(f.getName()));
public final GuiNumberField startHour = newGuiNumberField();
public final GuiNumberField startMin = newGuiNumberField();

View File

@@ -2,14 +2,14 @@ package com.replaymod.editor.handler;
import com.replaymod.editor.ReplayModEditor;
import com.replaymod.editor.gui.GuiReplayEditor;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import de.johni0702.minecraft.gui.container.GuiScreen;
import net.minecraft.client.gui.GuiButton;
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;

View File

@@ -5,13 +5,13 @@ import com.replaymod.replay.ReplayHandler;
import com.replaymod.replay.ReplayModReplay;
import com.replaymod.replay.events.ReplayOpenEvent;
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
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.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.lwjgl.input.Keyboard;
public class FullBrightness implements Extra {

View File

@@ -4,8 +4,6 @@ import com.replaymod.core.KeyBindingRegistry;
import com.replaymod.core.ReplayMod;
import com.replaymod.replay.events.ReplayOpenEvent;
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.RenderInfo;
import de.johni0702.minecraft.gui.container.GuiContainer;
@@ -19,6 +17,8 @@ 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.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;

View File

@@ -1,106 +1,102 @@
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.IMetadataSerializer;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.ConnectException;
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 static final String ZIP_FILE_URL = "https://github.com/ReplayMod/Translations/archive/master.zip";
private static final String LANG_PREFIX = "Translations-master/";
private ReplayModOnline module;
@Override
public void register(ReplayMod mod) throws Exception {
final Minecraft mc = mod.getMinecraft();
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());
this.module = ReplayModOnline.instance;
// Add lang files as resource pack
mc.addScheduledTask(() -> {
final Minecraft mc = mod.getMinecraft();
Thread localizedResourcePackLoader = new Thread(new Runnable() {
@Override
public void run() {
try {
@SuppressWarnings("unchecked")
List<IResourcePack> defaultResourcePacks = mc.defaultResourcePacks;
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);
defaultResourcePacks.add(new LocalizedResourcePack(module.getApiClient()));
mc.addScheduledTask(new Runnable() {
@Override
public void run() {
mc.refreshResources();
}
});
} catch(Exception e) {
e.printStackTrace();
}
}
}, "localizedResourcePackLoader");
localizedResourcePackLoader.setDaemon(true);
localizedResourcePackLoader.start();
}
@RequiredArgsConstructor
public static class LocalizedResourcePack implements IResourcePack {
private final Pattern LANG_PATTERN = Pattern.compile("^lang/([.+].lang)$");
private final Map<String, byte[]> languages;
public LocalizedResourcePack(Map<String, byte[]> languages) {
this.languages = languages;
}
private final ApiClient apiClient;
private Map<String, String> availableLanguages = new HashMap<>();
private boolean websiteAvailable = true;
@Override
public InputStream getInputStream(ResourceLocation loc) {
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);
}
}
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));
return null;
}
@Override
public boolean resourceExists(ResourceLocation loc) {
// Assumes that getInputStream returns a ByteArrayInputStream that doesn't need to be closed
return getInputStream(loc) != null;
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;
}
@Override
public Set getResourceDomains() {
return ImmutableSet.of("replaymod");
return ImmutableSet.of("minecraft", "replaymod");
}
@Override

View File

@@ -2,7 +2,6 @@ package com.replaymod.extras;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.Setting;
import cpw.mods.fml.common.Loader;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
@@ -15,6 +14,7 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraftforge.fml.common.Loader;
import org.apache.commons.io.FileUtils;
import javax.net.ssl.HttpsURLConnection;
@@ -43,7 +43,7 @@ public class OpenEyeExtra implements Extra {
}
}
public class OfferGui extends AbstractGuiScreen<OfferGui> {
private 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(mod.getMinecraft().mcDataDir, "mods/" + Loader.MC_VERSION + "/OpenEye.jar");
File targetFile = new File("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 {
}
}
public static final class GuiPopup extends AbstractGuiPopup<GuiPopup> {
private static final class GuiPopup extends AbstractGuiPopup<GuiPopup> {
GuiPopup(GuiContainer container) {
super(container);
popup.addElements(null, new GuiIndicator().setColor(Colors.BLACK));

View File

@@ -4,21 +4,17 @@ import com.replaymod.core.ReplayMod;
import com.replaymod.extras.playeroverview.PlayerOverview;
import com.replaymod.extras.urischeme.UriSchemeExtra;
import com.replaymod.extras.youtube.YoutubeUpload;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Mod(modid = ReplayModExtras.MOD_ID,
version = "@MOD_VERSION@",
acceptedMinecraftVersions = "@MC_VERSION@",
acceptableRemoteVersions = "*",
useMetadata = true)
public class ReplayModExtras {
public static final String MOD_ID = "replaymod-extras";
@@ -36,13 +32,11 @@ public class ReplayModExtras {
OpenEyeExtra.class
);
private final Map<Class<? extends Extra>, Extra> instances = new HashMap<>();
public static Logger LOGGER;
private Logger logger;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
LOGGER = event.getModLog();
logger = event.getModLog();
}
@Mod.EventHandler
@@ -51,14 +45,9 @@ public class ReplayModExtras {
try {
Extra extra = cls.newInstance();
extra.register(ReplayMod.instance);
instances.put(cls, extra);
} catch (Throwable t) {
LOGGER.warn("Failed to load extra " + cls.getName() + ": ", t);
logger.warn("Failed to load extra " + cls.getName() + ": ", t);
}
}
}
public <T extends Extra> Optional<T> get(Class<T> cls) {
return Optional.ofNullable(instances.get(cls)).map(cls::cast);
}
}

View File

@@ -1,6 +1,7 @@
package com.replaymod.extras.playeroverview;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.Utils;
import com.replaymod.extras.Extra;
@@ -8,23 +9,18 @@ import com.replaymod.replay.ReplayModReplay;
import com.replaymod.replay.camera.CameraEntity;
import com.replaymod.replay.events.ReplayCloseEvent;
import com.replaymod.replay.events.ReplayOpenEvent;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.RenderPlayer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.input.Keyboard;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.*;
public class PlayerOverview implements Extra {
private ReplayModReplay module;
@@ -41,10 +37,12 @@ public class PlayerOverview implements Extra {
public void run() {
if (module.getReplayHandler() != null) {
@SuppressWarnings("unchecked")
List<EntityPlayer> players = mod.getMinecraft().theWorld.playerEntities;
players = players.stream()
.filter(it -> !(it instanceof CameraEntity)) // Exclude the camera entity
.collect(Collectors.toList());
List<EntityPlayer> players = mod.getMinecraft().theWorld.getPlayers(EntityPlayer.class, new Predicate() {
@Override
public boolean apply(Object input) {
return !(input instanceof CameraEntity); // Exclude the camera entity
}
});
if (!Utils.isCtrlDown()) {
// Hide all players that have an UUID v2 (commonly used for NPCs)
Iterator<EntityPlayer> iter = players.iterator();
@@ -62,6 +60,12 @@ public class PlayerOverview implements Extra {
FMLCommonHandler.instance().bus().register(this);
MinecraftForge.EVENT_BUS.register(this);
RenderManager renderManager = mod.getMinecraft().getRenderManager();
@SuppressWarnings("unchecked")
Map<String, RenderPlayer> skinMap = renderManager.skinMap;
skinMap.put("default", new PlayerRenderHook(this, renderManager, false));
skinMap.put("slim", new PlayerRenderHook(this, renderManager, true));
}
public boolean isHidden(UUID uuid) {
@@ -94,19 +98,12 @@ public class PlayerOverview implements Extra {
@SubscribeEvent
public void oRenderHand(RenderHandEvent event) {
Entity view = module.getCore().getMinecraft().renderViewEntity;
Entity view = module.getCore().getMinecraft().getRenderViewEntity();
if (view != null && isHidden(view.getUniqueID())) {
event.setCanceled(true);
}
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void preRenderPlayer(RenderPlayerEvent.Pre event) {
if (isHidden(event.entityPlayer.getUniqueID())) {
event.setCanceled(true);
}
}
public boolean isSavingEnabled() {
return savingEnabled;
}

View File

@@ -4,15 +4,10 @@ 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.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.container.*;
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;
@@ -20,6 +15,7 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout;
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.potion.Potion;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.util.Dimension;
@@ -40,22 +36,21 @@ 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.createPositionedSoundRecord(BUTTON_SOUND, 1.0F));
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.create(BUTTON_SOUND, 1.0F));
playersScrollable.forEach(IGuiCheckbox.class).setChecked(true);
}
}.setLabel("").setChecked(true).setTooltip(new GuiTooltip().setI18nText("replaymod.gui.playeroverview.showall"));
}.setLabel("").setChecked(true);
public final GuiCheckbox uncheckAll = new GuiCheckbox(contentPanel){
@Override
public void onClick() {
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.createPositionedSoundRecord(BUTTON_SOUND, 1.0F));
getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.create(BUTTON_SOUND, 1.0F));
playersScrollable.forEach(IGuiCheckbox.class).setChecked(false);
}
}.setLabel("").setChecked(false).setTooltip(new GuiTooltip().setI18nText("replaymod.gui.playeroverview.hideall"));
}.setLabel("").setChecked(false);
{
setBackground(Background.NONE);
@@ -95,10 +90,12 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable {
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
renderer.bindTexture(texture);
renderer.drawTexturedRect(0, 0, 8, 8, 16, 16, 8, 8, 64, 64);
renderer.drawTexturedRect(0, 0, 40, 8, size.getWidth(), size.getHeight(), 8, 8, 64, 64);
if (p.isWearing(EnumPlayerModelParts.HAT)) {
renderer.drawTexturedRect(0, 0, 40, 8, size.getWidth(), size.getHeight(), 8, 8, 64, 64);
}
}
}.setSize(16, 16),
new GuiLabel().setText(p.getDisplayName()).setColor(isSpectator(p) ? Colors.DKGREY : Colors.WHITE)
new GuiLabel().setText(p.getName()).setColor(isSpectator(p) ? Colors.DKGREY : Colors.WHITE)
).onClick(new Runnable() {
@Override
public void run() {
@@ -150,7 +147,7 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable {
public int compare(EntityPlayer o1, EntityPlayer o2) {
if (isSpectator(o1) && !isSpectator(o2)) return 1;
if (isSpectator(o2) && !isSpectator(o1)) return -1;
return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
return o1.getName().compareToIgnoreCase(o2.getName());
}
}
}

View File

@@ -0,0 +1,20 @@
package com.replaymod.extras.playeroverview;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.renderer.culling.ICamera;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.RenderPlayer;
public class PlayerRenderHook extends RenderPlayer {
private final PlayerOverview extra;
public PlayerRenderHook(PlayerOverview extra, RenderManager renderManager, boolean useSmallArms) {
super(renderManager, useSmallArms);
this.extra = extra;
}
@Override
public boolean shouldRender(AbstractClientPlayer entity, ICamera camera, double camX, double camY, double camZ) {
return !extra.isHidden(entity.getUniqueID()) && super.shouldRender(entity, camera, camX, camY, camZ);
}
}

View File

@@ -1,39 +0,0 @@
package com.replaymod.extras.playeroverview.mixin;
import com.replaymod.extras.ReplayModExtras;
import com.replaymod.extras.playeroverview.PlayerOverview;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.renderer.entity.RenderPlayer;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import java.util.UUID;
/**
* This mixin prevents players that are hidden in the PlayerOverview from being rendered.
*
* Cancelling the RenderPlayerEvent.Pre is insufficient because it affects neither the shadows nor the fire texture.
* See: https://github.com/MinecraftForge/MinecraftForge/issues/2987
*
* The previous solution was to overwrite the RenderPlayer instances which has been dropped in favor of this one
* because it is less compatible with other mods whereas this one should be fine as long as no other mod completely
* overwrites the methods we modify in this mixin.
* One example of the previous solution breaking is when used with VanillaEnhancements because it replaces the
* RenderManager with a new custom one which in turn will reset our registered RenderPlayer instances because
* it does so after we have already registered with the old RenderManager.
*/
@Mixin(value = RenderPlayer.class, priority = 1200)
public abstract class MixinRenderPlayer extends RendererLivingEntity {
public MixinRenderPlayer(ModelBase p_i1261_1_, float p_i1261_2_) {
super(p_i1261_1_, p_i1261_2_);
}
@Override
public void doRenderShadowAndFire(Entity entity, double d1, double d2, double d3, float f1, float f2) {
UUID uuid = entity.getUniqueID();
if (!ReplayModExtras.instance.get(PlayerOverview.class).map(po -> po.isHidden(uuid)).orElse(false)) {
super.doRenderShadowAndFire(entity, d1, d2, d3, f1, f2);
}
}
}

View File

@@ -25,7 +25,6 @@ public class LinuxUriScheme extends UriScheme {
"Icon=" + iconFile.getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\"") + "\n" +
"Type=Application\n" +
"Terminal=false\n" +
"NoDisplay=true\n" +
"MimeType=x-scheme-handler/replaymod;";
FileOutputStream out = new FileOutputStream(file);

View File

@@ -3,11 +3,11 @@ package com.replaymod.extras.youtube;
import com.replaymod.core.ReplayMod;
import com.replaymod.extras.Extra;
import com.replaymod.render.gui.GuiRenderingDone;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
public class YoutubeUpload implements Extra {
@Override

View File

@@ -36,8 +36,6 @@ import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.replaymod.extras.ReplayModExtras.LOGGER;
public class YoutubeUploader {
private static final String CLIENT_ID = "743126594724-mfe7pj1k7e47uu5pk4503c8st9vj9ibu.apps.googleusercontent.com";
private static final String CLIENT_SECRET = "gMwcy3mRYCRamCIjJIYP7rqc";
@@ -139,7 +137,7 @@ public class YoutubeUploader {
CommandLine commandLine = new CommandLine(settings.getExportCommand());
commandLine.addArguments(args);
LOGGER.info("Re-encoding for ODS with {} {}", settings.getExportCommand(), args);
System.out.println("Re-encoding for ODS with " + settings.getExportCommand() + args);
Process process = new ProcessBuilder(commandLine.toStrings()).directory(videoFile.getParentFile()).start();
final AtomicBoolean active = new AtomicBoolean(true);
@@ -151,7 +149,7 @@ public class YoutubeUploader {
char c = (char) in.read();
if (c == '\r') {
String str = sb.toString();
LOGGER.debug("[FFmpeg] {}", str);
System.out.println(str);
if (str.startsWith("frame=")) {
str = str.substring(6).trim();
str = str.substring(0, str.indexOf(' '));

View File

@@ -11,14 +11,14 @@ import com.replaymod.replay.events.ReplayCloseEvent;
import com.replaymod.replaystudio.replay.ReplayFile;
import com.replaymod.replaystudio.replay.ZipReplayFile;
import com.replaymod.replaystudio.studio.ReplayStudio;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import de.johni0702.minecraft.gui.container.GuiScreen;
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;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.apache.logging.log4j.Logger;
import java.io.File;
@@ -29,7 +29,6 @@ import static net.minecraft.client.Minecraft.getMinecraft;
@Mod(modid = ReplayModOnline.MOD_ID,
version = "@MOD_VERSION@",
acceptedMinecraftVersions = "@MC_VERSION@",
acceptableRemoteVersions = "*",
useMetadata = true)
public class ReplayModOnline {
public static final String MOD_ID = "replaymod-online";
@@ -41,7 +40,7 @@ public class ReplayModOnline {
private ReplayModReplay replayModule;
public static Logger LOGGER;
private Logger logger;
private ApiClient apiClient;
@@ -54,7 +53,7 @@ public class ReplayModOnline {
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
LOGGER = event.getModLog();
logger = event.getModLog();
core = ReplayMod.instance;
replayModule = ReplayModReplay.instance;
@@ -70,7 +69,7 @@ public class ReplayModOnline {
public void init(FMLInitializationEvent event) {
if (!getDownloadsFolder().exists()){
if (!getDownloadsFolder().mkdirs()) {
LOGGER.warn("Failed to create downloads folder: " + getDownloadsFolder());
logger.warn("Failed to create downloads folder: " + getDownloadsFolder());
}
}
@@ -83,10 +82,7 @@ public class ReplayModOnline {
// Initial login prompt
if (!core.getSettingsRegistry().get(Setting.SKIP_LOGIN_PROMPT)) {
if (!isLoggedIn()) {
core.runLater(() -> {
GuiScreen parent = GuiScreen.wrap(getMinecraft().currentScreen);
new GuiLoginPrompt(apiClient, parent, parent, false).display();
});
new GuiLoginPrompt(apiClient, null, null, false).display();
}
}
}
@@ -100,7 +96,7 @@ public class ReplayModOnline {
}
public Logger getLogger() {
return LOGGER;
return logger;
}
public ApiClient getApiClient() {

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);
public GuiButton cancelButton = new GuiButton(this).setI18nLabel("replaymod.gui.cancel").setSize(150, 20);
private 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,7 +28,6 @@ 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;
@@ -37,6 +36,8 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import de.johni0702.minecraft.gui.utils.Consumer;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.core.helpers.Strings;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
@@ -49,8 +50,6 @@ import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import static com.replaymod.online.ReplayModOnline.LOGGER;
public class GuiReplayCenter extends GuiScreen {
private final ReplayModOnline mod;
private final ApiClient apiClient;
@@ -110,7 +109,6 @@ 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);
@@ -125,22 +123,10 @@ 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"));
}
@@ -336,7 +322,7 @@ public class GuiReplayCenter extends GuiScreen {
}
});
} catch (Exception e) {
LOGGER.error("Could not load Replay File {}", fileInfo.getId(), e);
FMLLog.getLogger().error("Could not load Replay File " + fileInfo.getId(), e);
}
}
}
@@ -417,7 +403,7 @@ public class GuiReplayCenter extends GuiScreen {
this.downloaded = downloaded;
ReplayMetaData metaData = fileInfo.getMetadata();
name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(fileInfo.getName()));
name.setText(ChatFormatting.UNDERLINE + FilenameUtils.getBaseName(fileInfo.getName()));
author.setI18nText("replaymod.gui.center.author",
"" + ChatFormatting.GRAY + ChatFormatting.ITALIC, fileInfo.getOwner());
if (Strings.isEmpty(metaData.getServerName())) {

View File

@@ -2,7 +2,6 @@ package com.replaymod.online.gui;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.replaymod.core.utils.Utils;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
@@ -28,7 +27,7 @@ public class GuiSaveModifiedReplay extends GuiScreen {
@Override
public void run() {
String resultName = name.getText().trim().replace("[^a-zA-Z0-9\\.\\- ]", "_");
final File resultFile = new File(file.getParentFile(), Utils.replayNameToFileName(resultName));
final File resultFile = new File(file.getParentFile(), resultName + ".mcpr");
if (resultFile.exists()) {
Futures.addCallback(GuiYesNoPopup.open(GuiSaveModifiedReplay.this,
new GuiLabel().setI18nText("replaymod.gui.replaymodified.warning1", resultName).setColor(Colors.BLACK),

View File

@@ -17,12 +17,7 @@ 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.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.*;
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
import de.johni0702.minecraft.gui.element.advanced.GuiTextArea;
@@ -38,6 +33,7 @@ import net.minecraft.client.settings.KeyBinding;
import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
@@ -134,7 +130,7 @@ public class GuiUploadReplay extends GuiScreen {
}
// Apply to gui
name.setText(Utils.fileNameToReplayName(file.getName()));
name.setText(FilenameUtils.getBaseName(file.getName()));
int secs = metaData.getDuration() / 1000;
durationLabel.setI18nText("replaymod.gui.upload.duration", secs / 60, secs % 60);
hideServerIP.setEnabled(!metaData.isSingleplayer());
@@ -238,8 +234,6 @@ public class GuiUploadReplay extends GuiScreen {
});
validateInputs();
name.onTextChanged(s -> validateInputs());
tags.onTextChanged(s -> validateInputs());
}
public void validateInputs() {

View File

@@ -5,8 +5,6 @@ import com.replaymod.online.gui.GuiLoginPrompt;
import com.replaymod.online.gui.GuiReplayCenter;
import com.replaymod.online.gui.GuiUploadReplay;
import com.replaymod.replay.gui.screen.GuiReplayViewer;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
@@ -16,6 +14,8 @@ 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;

View File

@@ -4,7 +4,6 @@ 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;
@@ -12,11 +11,7 @@ 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.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.container.*;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.GuiTextField;
@@ -46,16 +41,9 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable {
public final GuiButton overwriteButton = new GuiButton(buttonPanel).onClick(new Runnable() {
@Override
public void run() {
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);
timelines.put(selectedEntry.name, currentTimeline);
overwriteButton.setDisabled();
save();
}
}).setSize(75, 20).setI18nLabel("replaymod.gui.overwrite").setDisabled();
public final GuiButton saveAsButton = new GuiButton(buttonPanel).onClick(new Runnable() {

View File

@@ -10,9 +10,9 @@ 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 cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.Minecraft;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import javax.annotation.Nullable;
import java.util.Iterator;

View File

@@ -3,7 +3,6 @@ package com.replaymod.pathing.player;
import com.google.common.util.concurrent.ListenableFuture;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replaystudio.pathing.path.Timeline;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
/**
* Timeline player using the system time.
@@ -33,7 +32,6 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer {
}
@Override
@SubscribeEvent // 1.7.10 doesn't yet deal properly with inherited, overridden event handlers
public void onTick(ReplayTimer.UpdatedEvent event) {
if (secondFrame) {
secondFrame = false;

View File

@@ -1,9 +1,9 @@
package com.replaymod.pathing.player;
import com.replaymod.core.utils.WrappedTimer;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.Event;
import net.minecraft.util.Timer;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.Event;
/**
* Wrapper around the current timer that prevents the timer from advancing by itself.

View File

@@ -8,7 +8,6 @@ import com.replaymod.replaystudio.pathing.property.PropertyPart;
import com.replaymod.replaystudio.pathing.property.PropertyParts;
import lombok.NonNull;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.world.World;
import java.io.IOException;
@@ -37,9 +36,7 @@ public class SpectatorProperty extends AbstractProperty<Integer> {
// Lookup entity by id, returns null if an entity with the id does not exists
Entity target = world.getEntityByID(value);
// Spectate entity, when called with null, returns to camera
if (target instanceof EntityLivingBase) {
handler.spectateEntity((EntityLivingBase) target);
}
handler.spectateEntity(target);
}
@Override

View File

@@ -4,22 +4,19 @@ import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.Restrictions;
import com.replaymod.recording.handler.ConnectionEventHandler;
import com.replaymod.recording.packet.PacketListener;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.EventBus;
import cpw.mods.fml.common.network.NetworkRegistry;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraft.network.NetworkManager;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.EventBus;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import org.apache.logging.log4j.Logger;
import org.lwjgl.input.Keyboard;
@Mod(modid = ReplayModRecording.MOD_ID,
version = "@MOD_VERSION@",
acceptedMinecraftVersions = "@MC_VERSION@",
acceptableRemoteVersions = "*",
useMetadata = true)
public class ReplayModRecording {
public static final String MOD_ID = "replaymod-recording";
@@ -57,12 +54,9 @@ public class ReplayModRecording {
EventBus bus = FMLCommonHandler.instance().bus();
bus.register(connectionEventHandler = new ConnectionEventHandler(logger, core));
NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler());
NetworkRegistry.INSTANCE.newSimpleChannel(Restrictions.PLUGIN_CHANNEL);
}
@ChannelHandler.Sharable
private static class RestrictionsChannelHandler extends ChannelDuplexHandler {}
public void initiateRecording(NetworkManager networkManager) {
connectionEventHandler.onConnectedToServerEvent(networkManager);
}

View File

@@ -2,14 +2,14 @@ package com.replaymod.recording.gui;
import com.replaymod.core.SettingsRegistry;
import com.replaymod.recording.Setting;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import static com.replaymod.core.ReplayMod.TEXTURE;
import static com.replaymod.core.ReplayMod.TEXTURE_SIZE;
@@ -45,7 +45,8 @@ public class GuiRecordingOverlay {
FontRenderer fontRenderer = mc.fontRendererObj;
fontRenderer.drawString(I18n.format("replaymod.gui.recording").toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff);
mc.renderEngine.bindTexture(TEXTURE);
GL11.glEnable(GL11.GL_ALPHA_TEST);
GlStateManager.resetColor();
GlStateManager.enableAlpha();
Gui.drawModalRectWithCustomSizedTexture(10, 10, 58, 20, 16, 16, TEXTURE_SIZE, TEXTURE_SIZE);
}
}

View File

@@ -2,7 +2,6 @@ package com.replaymod.recording.handler;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.ModCompat;
import com.replaymod.core.utils.Utils;
import com.replaymod.recording.Setting;
import com.replaymod.recording.gui.GuiRecordingOverlay;
import com.replaymod.recording.packet.PacketListener;
@@ -10,10 +9,11 @@ import com.replaymod.replaystudio.replay.ReplayFile;
import com.replaymod.replaystudio.replay.ReplayMetaData;
import com.replaymod.replaystudio.replay.ZipReplayFile;
import com.replaymod.replaystudio.studio.ReplayStudio;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.network.FMLNetworkEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.network.NetworkManager;
import net.minecraft.world.WorldType;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent;
import org.apache.logging.log4j.Logger;
import java.io.File;
@@ -46,6 +46,10 @@ public class ConnectionEventHandler {
try {
boolean local = networkManager.isLocalChannel();
if (local) {
if (mc.getIntegratedServer().getEntityWorld().getWorldType() == WorldType.DEBUG_WORLD) {
logger.info("Debug World recording is not supported.");
return;
}
if(!core.getSettingsRegistry().get(Setting.RECORD_SINGLEPLAYER)) {
logger.info("Singleplayer Recording is disabled");
return;
@@ -70,7 +74,7 @@ public class ConnectionEventHandler {
File folder = core.getReplayFolder();
String name = sdf.format(Calendar.getInstance().getTime());
File currentFile = new File(folder, Utils.replayNameToFileName(name));
File currentFile = new File(folder, name + ".mcpr");
ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), currentFile);
replayFile.writeModInfo(ModCompat.getInstalledNetworkMods());
@@ -91,14 +95,14 @@ public class ConnectionEventHandler {
guiOverlay.register();
core.printInfoToChat("replaymod.chat.recordingstarted");
} catch(Throwable e) {
} catch(Exception e) {
e.printStackTrace();
core.printWarningToChat("replaymod.chat.recordingfailed");
}
}
@SubscribeEvent
public void onDisconnectedFromServerEvent(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) {
public void onDisconnectedFromServerEvent(ClientDisconnectionFromServerEvent event) {
if (packetListener != null) {
guiOverlay.unregister();
guiOverlay = null;

View File

@@ -1,8 +1,8 @@
package com.replaymod.recording.handler;
import cpw.mods.fml.common.network.handshake.FMLHandshakeMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import net.minecraftforge.fml.common.network.handshake.FMLHandshakeMessage;
/**
* Filters out all handshake packets that were sent for recording but must
@@ -17,7 +17,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
public class FMLHandshakeFilter extends SimpleChannelInboundHandler<FMLHandshakeMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FMLHandshakeMessage msg) throws Exception {
if (!(msg instanceof FMLHandshakeMessage.ModIdData)) {
if (!(msg instanceof FMLHandshakeMessage.RegistryData)) {
// Pass on everything but RegistryData messages
ctx.fireChannelRead(msg);
}

View File

@@ -1,34 +1,28 @@
package com.replaymod.recording.handler;
import com.replaymod.recording.packet.PacketListener;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.S04PacketEntityEquipment;
import net.minecraft.network.play.server.S0APacketUseBed;
import net.minecraft.network.play.server.S0BPacketAnimation;
import net.minecraft.network.play.server.S0CPacketSpawnPlayer;
import net.minecraft.network.play.server.S0DPacketCollectItem;
import net.minecraft.network.play.server.S12PacketEntityVelocity;
import net.minecraft.network.play.server.*;
import net.minecraft.network.play.server.S14PacketEntity.S17PacketEntityLookMove;
import net.minecraft.network.play.server.S18PacketEntityTeleport;
import net.minecraft.network.play.server.S19PacketEntityHeadLook;
import net.minecraft.network.play.server.S1BPacketEntityAttach;
import net.minecraft.network.play.server.S25PacketBlockBreakAnim;
import net.minecraft.server.integrated.IntegratedServer;
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;
import net.minecraftforge.fml.common.gameevent.TickEvent.PlayerTickEvent;
import java.util.Objects;
@@ -80,11 +74,37 @@ public class RecordingEventHandler {
}
private S0CPacketSpawnPlayer spawnPlayer(EntityPlayer player) {
return new S0CPacketSpawnPlayer(player);
try {
S0CPacketSpawnPlayer packet = new S0CPacketSpawnPlayer();
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;
}
}
@SubscribeEvent
public void onPlayerTick(TickEvent.PlayerTickEvent e) {
public void onPlayerTick(PlayerTickEvent e) {
try {
if(e.player != mc.thePlayer) return;
@@ -92,7 +112,7 @@ public class RecordingEventHandler {
if(lastX == null || lastY == null || lastZ == null) {
force = true;
lastX = e.player.posX;
lastY = e.player.boundingBox.minY;
lastY = e.player.posY;
lastZ = e.player.posZ;
}
@@ -103,28 +123,28 @@ public class RecordingEventHandler {
}
double dx = e.player.posX - lastX;
double dy = e.player.boundingBox.minY - lastY;
double dy = e.player.posY - lastY;
double dz = e.player.posZ - lastZ;
lastX = e.player.posX;
lastY = e.player.boundingBox.minY;
lastY = e.player.posY;
lastZ = e.player.posZ;
Packet packet;
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.boundingBox.minY * 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);
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 S17PacketEntityLookMove(e.player.getEntityId(),
(byte) Math.round(dx * 32), (byte) Math.round(dy * 32), (byte) Math.round(dz * 32),
newYaw, newPitch);
newYaw, newPitch, e.player.onGround);
}
packetListener.save(packet);
@@ -133,7 +153,16 @@ public class RecordingEventHandler {
int rotationYawHead = ((int)(e.player.rotationYawHead * 256.0F / 360.0F));
if(!Objects.equals(rotationYawHead, rotationYawHeadBefore)) {
packetListener.save(new S19PacketEntityHeadLook(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;
}
@@ -144,7 +173,17 @@ public class RecordingEventHandler {
//Animation Packets
//Swing Animation
if(e.player.swingProgressInt == 1) {
packetListener.save(new S0BPacketAnimation(e.player, 0));
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);
}
/*
@@ -224,7 +263,17 @@ public class RecordingEventHandler {
//Sleeping
if(!mc.thePlayer.isPlayerSleeping() && wasSleeping) {
packetListener.save(new S0BPacketAnimation(e.player, 2));
S0BPacketAnimation pac = new S0BPacketAnimation();
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;
}
@@ -235,7 +284,7 @@ public class RecordingEventHandler {
}
@SubscribeEvent
public void onPickupItem(PlayerEvent.ItemPickupEvent event) {
public void onPickupItem(ItemPickupEvent event) {
try {
packetListener.save(new S0DPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId()));
} catch(Exception e) {
@@ -245,33 +294,80 @@ public class RecordingEventHandler {
@SubscribeEvent
public void onStartEating(PlayerUseItemEvent.Start event) {
if(!event.entityPlayer.isEating()) return;
packetListener.save(new S0BPacketAnimation(event.entityPlayer, 3));
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();
}
}
@SubscribeEvent
public void onSleep(PlayerSleepInBedEvent event) {
if(event.entityPlayer != mc.thePlayer) {
return;
}
try {
if(event.entityPlayer != mc.thePlayer) {
return;
}
packetListener.save(new S0APacketUseBed(event.entityPlayer, event.x, event.y, event.z));
wasSleeping = true;
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;
} catch(Exception e) {
e.printStackTrace();
}
}
@SubscribeEvent
public void enterMinecart(MinecartInteractEvent event) {
if(event.player != mc.thePlayer) {
return;
try {
if(event.player != mc.thePlayer) {
return;
}
S1BPacketEntityAttach pea = new S1BPacketEntityAttach();
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();
}
packetListener.save(new S1BPacketEntityAttach(0, event.player, event.minecart));
lastRiding = event.minecart.getEntityId();
}
public void onBlockBreakAnim(int breakerId, int x, int y, int z, int progress) {
public void onBlockBreakAnim(int breakerId, BlockPos pos, int progress) {
EntityPlayer thePlayer = mc.thePlayer;
if (thePlayer != null && breakerId == thePlayer.getEntityId()) {
packetListener.save(new S25PacketBlockBreakAnim(breakerId, x, y, z, progress));
packetListener.save(new S25PacketBlockBreakAnim(breakerId, pos, progress));
}
}

View File

@@ -1,10 +1,8 @@
package com.replaymod.recording.mixin;
import com.replaymod.recording.ReplayModRecording;
import cpw.mods.fml.common.network.FMLNetworkEvent;
import net.minecraft.client.network.NetHandlerLoginClient;
import net.minecraft.network.NetworkManager;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@@ -15,15 +13,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public abstract class MixinNetHandlerLoginClient {
@Shadow
private @Final NetworkManager field_147393_d;
private NetworkManager networkManager;
/**
* Starts the recording right before switching into PLAY state.
* We cannot use the {@link FMLNetworkEvent.ClientConnectedToServerEvent}
* We cannot use the {@link net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientConnectedToServerEvent}
* as it only fires after the forge handshake.
*/
@Inject(method = "handleLoginSuccess", at=@At("HEAD"))
public void replayModRecording_initiateRecording(CallbackInfo cb) {
ReplayModRecording.instance.initiateRecording(field_147393_d);
ReplayModRecording.instance.initiateRecording(networkManager);
}
}

View File

@@ -3,20 +3,27 @@ package com.replaymod.recording.mixin;
import com.replaymod.recording.handler.RecordingEventHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.network.play.server.S01PacketJoinGame;
import net.minecraft.client.network.NetworkPlayerInfo;
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;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.UUID;
@Mixin(NetHandlerPlayClient.class)
public abstract class MixinNetHandlerPlayClient {
@Shadow
private Minecraft gameController;
@Shadow
private Map<UUID, NetworkPlayerInfo> playerInfoMap;
public RecordingEventHandler getRecordingEventHandler() {
return ((RecordingEventHandler.RecordingEventSender) gameController.renderGlobal).getRecordingEventHandler();
}
@@ -24,22 +31,31 @@ public abstract class MixinNetHandlerPlayClient {
/**
* Record the own player entity joining the world.
* We cannot use the {@link net.minecraftforge.event.entity.EntityJoinWorldEvent} because the entity id
* of the player is set afterwards
* of the player is set afterwards and the tablist entry might not yet be sent.
* @param packet The packet
* @param ci Callback info
*/
@Inject(method = "handleJoinGame", at=@At("RETURN"))
public void recordOwnJoin(S01PacketJoinGame packet, CallbackInfo ci) {
@Inject(method = "handlePlayerListItem", at=@At("HEAD"))
public void recordOwnJoin(S38PacketPlayerListItem packet, CallbackInfo ci) {
if (gameController.thePlayer == null) return;
RecordingEventHandler handler = getRecordingEventHandler();
if (handler != null) {
handler.onPlayerJoin();
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())
&& !playerInfoMap.containsKey(data.getProfile().getId())) {
handler.onPlayerJoin();
}
}
}
}
/**
* 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(S01PacketJoinGame, CallbackInfo)}.
* the first spawn which is already handled by {@link #recordOwnJoin(S38PacketPlayerListItem, CallbackInfo)}.
* @param packet The packet
* @param ci Callback info
*/

View File

@@ -1,11 +1,11 @@
package com.replaymod.recording.mixin;
import com.replaymod.recording.handler.FMLHandshakeFilter;
import cpw.mods.fml.common.network.handshake.FMLHandshakeCodec;
import cpw.mods.fml.common.network.handshake.NetworkDispatcher;
import cpw.mods.fml.relauncher.Side;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.embedded.EmbeddedChannel;
import net.minecraftforge.fml.common.network.handshake.FMLHandshakeCodec;
import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
import net.minecraftforge.fml.relauncher.Side;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;

View File

@@ -2,6 +2,7 @@ package com.replaymod.recording.mixin;
import com.replaymod.recording.handler.RecordingEventHandler;
import net.minecraft.client.renderer.RenderGlobal;
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;
@@ -22,10 +23,10 @@ public abstract class MixinRenderGlobal implements RecordingEventHandler.Recordi
return recordingEventHandler;
}
@Inject(method = "destroyBlockPartially", at = @At("HEAD"))
public void saveBlockBreakProgressPacket(int breakerId, int x, int y, int z, int progress, CallbackInfo info) {
@Inject(method = "sendBlockBreakProgress", at = @At("HEAD"))
public void saveBlockBreakProgressPacket(int breakerId, BlockPos pos, int progress, CallbackInfo info) {
if (recordingEventHandler != null) {
recordingEventHandler.onBlockBreakAnim(breakerId, x, y, z, progress);
recordingEventHandler.onBlockBreakAnim(breakerId, pos, progress);
}
}
}

View File

@@ -4,7 +4,6 @@ import com.replaymod.core.utils.Restrictions;
import com.replaymod.replaystudio.data.Marker;
import com.replaymod.replaystudio.replay.ReplayFile;
import com.replaymod.replaystudio.replay.ReplayMetaData;
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
@@ -13,10 +12,12 @@ 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.play.server.*;
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;
@@ -84,7 +85,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
public void save(Packet packet) {
try {
if(packet instanceof S0CPacketSpawnPlayer) {
UUID uuid = ((S0CPacketSpawnPlayer) packet).field_148955_b.getId();
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()]));
@@ -152,23 +153,20 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
if(packet instanceof S0DPacketCollectItem) {
if(mc.thePlayer != null ||
((S0DPacketCollectItem) packet).func_149353_d() == mc.thePlayer.getEntityId()) {
((S0DPacketCollectItem) packet).getEntityID() == mc.thePlayer.getEntityId()) {
super.channelRead(ctx, msg);
return;
}
}
if (packet instanceof S3FPacketCustomPayload) {
S3FPacketCustomPayload p = (S3FPacketCustomPayload) packet;
if ("MC|RPack".equals(p.func_149169_c())) {
save(resourcePackRecorder.handleResourcePack(p));
return;
}
if (packet instanceof S48PacketResourcePackSend) {
save(resourcePackRecorder.handleResourcePack((S48PacketResourcePackSend) packet));
return;
}
if (packet instanceof FMLProxyPacket) {
// This packet requires special handling
save(((FMLProxyPacket) packet).toS3FPacket());
((FMLProxyPacket) packet).toS3FPackets().forEach(this::save);
super.channelRead(ctx, msg);
return;
}
@@ -177,7 +175,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
if (packet instanceof S3FPacketCustomPayload) {
S3FPacketCustomPayload p = (S3FPacketCustomPayload) packet;
if (Restrictions.PLUGIN_CHANNEL.equals(p.func_149169_c())) {
if (Restrictions.PLUGIN_CHANNEL.equals(p.getChannelName())) {
packet = new S40PacketDisconnect(new ChatComponentText("Please update to view this replay."));
save(packet);
}
@@ -207,17 +205,17 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
if(packet instanceof S0CPacketSpawnPlayer) {
S0CPacketSpawnPlayer p = (S0CPacketSpawnPlayer) packet;
if (p.field_148960_i == null) {
p.field_148960_i = new DataWatcher(null);
if (p.watcher == null) {
p.watcher = new DataWatcher(null);
if(p.func_148944_c() != null) {
for(DataWatcher.WatchableObject wo : (List<DataWatcher.WatchableObject>) p.func_148944_c()) {
p.field_148960_i.addObject(wo.getDataValueId(), wo.getObject());
for(DataWatcher.WatchableObject wo : p.func_148944_c()) {
p.watcher.addObject(wo.getDataValueId(), wo.getObject());
}
}
}
}
Integer packetId = (Integer) EnumConnectionState.PLAY.func_150755_b().inverse().get(packet.getClass());
Integer packetId = EnumConnectionState.PLAY.getPacketId(EnumPacketDirection.CLIENTBOUND, packet);
if (packetId == null) {
throw new IOException("Unknown packet type:" + packet.getClass());
}
@@ -235,7 +233,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
}
public void addMarker() {
Entity view = Minecraft.getMinecraft().renderViewEntity;
Entity view = Minecraft.getMinecraft().getRenderViewEntity();
int timestamp = (int) (System.currentTimeMillis() - startTime);
Marker marker = new Marker();

View File

@@ -2,23 +2,27 @@ package com.replaymod.recording.packet;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.replaymod.replaystudio.replay.ReplayFile;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreenWorking;
import net.minecraft.client.gui.GuiYesNo;
import net.minecraft.client.gui.GuiYesNoCallback;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.ServerData.ServerResourceMode;
import net.minecraft.client.multiplayer.ServerList;
import net.minecraft.client.resources.FileResourcePack;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.ResourcePackRepository;
import net.minecraft.network.play.server.S3FPacketCustomPayload;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.client.C19PacketResourcePackStatus;
import net.minecraft.network.play.server.S48PacketResourcePackSend;
import net.minecraft.util.HttpUtil;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
@@ -70,75 +74,140 @@ public class ResourcePackRecorder {
}
}
public synchronized S3FPacketCustomPayload handleResourcePack(S3FPacketCustomPayload packet) {
public synchronized S48PacketResourcePackSend handleResourcePack(S48PacketResourcePackSend packet) {
final int requestId = nextRequestId++;
final String url = new String(packet.func_149168_d(), Charsets.UTF_8);
final NetHandlerPlayClient netHandler = mc.getNetHandler();
final NetworkManager netManager = netHandler.getNetworkManager();
final String url = packet.getURL();
final String hash = packet.getHash();
ServerData serverData = mc.getCurrentServerData();
ServerResourceMode resourceMode = serverData == null ? ServerResourceMode.PROMPT : serverData.getResourceMode();
if (resourceMode == ServerResourceMode.ENABLED) {
downloadResourcePack(requestId, url);
mc.getResourcePackRepository().obtainResourcePack(url);
} else if (resourceMode == ServerResourceMode.PROMPT) {
// Lambdas MUST NOT be used with methods that need re-obfuscation in FG prior to 2.2 (will result in AbstractMethodError)
//noinspection Convert2Lambda
mc.displayGuiScreen(new GuiYesNo(new GuiYesNoCallback() {
@Override
public void confirmClicked(boolean result, int id) {
if (url.startsWith("level://")) {
String levelName = url.substring("level://".length());
File savesDir = new File(mc.mcDataDir, "saves");
final File levelDir = new File(savesDir, levelName);
if (levelDir.isFile()) {
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 C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
}
@Override
public void onFailure(@Nonnull Throwable throwable) {
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
}
});
} else {
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 C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
downloadResourcePackFuture(requestId, url, hash);
} else if (serverData != null && serverData.getResourceMode() != ServerData.ServerResourceMode.PROMPT) {
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
} else {
mc.addScheduledTask(() -> mc.displayGuiScreen(new GuiYesNo((result, id) -> {
if (serverData != null) {
serverData.setResourceMode(ServerResourceMode.ENABLED);
ServerList.func_147414_b(serverData);
serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED);
}
mc.displayGuiScreen(null);
if (result) {
downloadResourcePack(requestId, url);
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.ACCEPTED));
downloadResourcePackFuture(requestId, url, hash);
} else {
netManager.sendPacket(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.DECLINED));
}
}
}, I18n.format("multiplayer.texturePrompt.line1"), I18n.format("multiplayer.texturePrompt.line2"), 0));
ServerList.func_147414_b(serverData);
mc.displayGuiScreen(null);
}, I18n.format("multiplayer.texturePrompt.line1"), I18n.format("multiplayer.texturePrompt.line2"), 0)));
}
}
return new S3FPacketCustomPayload(packet.func_149169_c(), ("replay://" + requestId).getBytes(Charsets.UTF_8));
return new S48PacketResourcePackSend("replay://" + requestId, "");
}
private void downloadResourcePack(final int requestId, String url) {
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.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.SUCCESSFULLY_LOADED));
}
@Override
public void onFailure(@Nonnull Throwable throwable) {
mc.getNetHandler().addToSendQueue(new C19PacketResourcePackStatus(hash, C19PacketResourcePackStatus.Action.FAILED_DOWNLOAD));
}
});
}
private ListenableFuture downloadResourcePack(final int requestId, String url, String hash) {
final ResourcePackRepository repo = mc.mcResourcePackRepository;
String fileName;
if (hash.matches("^[a-f0-9]{40}$")) {
fileName = hash;
} else {
fileName = url.substring(url.lastIndexOf("/") + 1);
String fileName = url.substring(url.lastIndexOf("/") + 1);
if (fileName.contains("?")) {
fileName = fileName.substring(0, fileName.indexOf("?"));
}
if (fileName.contains("?")) {
fileName = fileName.substring(0, fileName.indexOf("?"));
if (!fileName.endsWith(".zip")) {
return Futures.immediateFailedFuture(new IllegalArgumentException("Invalid filename; must end in .zip"));
}
fileName = "legacy_" + fileName.replaceAll("\\W", "");
}
if (!fileName.endsWith(".zip")) {
return;
}
final File file = new File(repo.dirServerResourcepacks, fileName);
repo.lock.lock();
try {
repo.clearResourcePack();
fileName = fileName.replaceAll("\\W", "");
if (file.exists() && hash.length() == 40) {
try {
String fileHash = Hashing.sha1().hashBytes(Files.toByteArray(file)).toString();
if (fileHash.equals(hash)) {
recordResourcePack(file, requestId);
return repo.setResourcePackInstance(file);
}
File file = new File(repo.field_148534_e, fileName);
HashMap<String, String> hashmap = new HashMap<>();
hashmap.put("X-Minecraft-Username", mc.getSession().getUsername());
hashmap.put("X-Minecraft-UUID", mc.getSession().getPlayerID());
hashmap.put("X-Minecraft-Version", "1.7.10");
GuiScreenWorking guiScreen = new GuiScreenWorking();
Minecraft.getMinecraft().displayGuiScreen(guiScreen);
repo.func_148529_f();
repo.field_148533_g = true;
// Lambdas MUST NOT be used with methods that need re-obfuscation in FG prior to 2.2 (will result in AbstractMethodError)
//noinspection Convert2Lambda
HttpUtil.downloadResourcePack(file, url, new HttpUtil.DownloadListener() {
public void onDownloadComplete(File file) {
if (repo.field_148533_g) {
repo.field_148533_g = false;
repo.field_148532_f = new FileResourcePack(file);
Minecraft.getMinecraft().scheduleResourcesRefresh();
recordResourcePack(file, requestId);
logger.warn("File " + file + " had wrong hash (expected " + hash + ", found " + fileHash + "). Deleting it.");
FileUtils.deleteQuietly(file);
} catch (IOException ioexception) {
logger.warn("File " + file + " couldn\'t be hashed. Deleting it.", ioexception);
FileUtils.deleteQuietly(file);
}
}
}, hashmap, 50*1024*1024, guiScreen, Minecraft.getMinecraft().getProxy());
final GuiScreenWorking guiScreen = new GuiScreenWorking();
final Minecraft mc = Minecraft.getMinecraft();
Futures.getUnchecked(mc.addScheduledTask(() -> mc.displayGuiScreen(guiScreen)));
Map<String, String> sessionInfo = Minecraft.getSessionInfo();
repo.downloadingPacks = HttpUtil.downloadResourcePack(file, url, sessionInfo, 50 * 1024 * 1024, guiScreen, mc.getProxy());
Futures.addCallback(repo.downloadingPacks, new FutureCallback<Object>() {
@Override
public void onSuccess(Object value) {
recordResourcePack(file, requestId);
repo.setResourcePackInstance(file);
}
@Override
public void onFailure(@Nonnull Throwable throwable) {
throwable.printStackTrace();
}
});
return repo.downloadingPacks;
} finally {
repo.lock.unlock();
}
}
}

View File

@@ -3,13 +3,13 @@ package com.replaymod.render;
import com.replaymod.core.ReplayMod;
import com.replaymod.render.utils.RenderJob;
import com.replaymod.replay.events.ReplayCloseEvent;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException;
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;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.Logger;
@@ -21,7 +21,6 @@ import java.util.List;
@Mod(modid = ReplayModRender.MOD_ID,
version = "@MOD_VERSION@",
acceptedMinecraftVersions = "@MC_VERSION@",
acceptableRemoteVersions = "*",
useMetadata = true)
public class ReplayModRender {
public static final String MOD_ID = "replaymod-render";

View File

@@ -2,14 +2,13 @@ package com.replaymod.render;
import com.replaymod.render.frame.RGBFrame;
import com.replaymod.render.rendering.FrameConsumer;
import com.replaymod.render.rendering.VideoRenderer;
import com.replaymod.render.utils.ByteBufferPool;
import com.replaymod.render.utils.StreamPipe;
import cpw.mods.fml.common.versioning.ComparableVersion;
import net.minecraft.client.Minecraft;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.util.Util;
import net.minecraftforge.fml.common.versioning.ComparableVersion;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
@@ -33,7 +32,6 @@ import static org.apache.commons.lang3.Validate.isTrue;
public class VideoWriter implements FrameConsumer<RGBFrame> {
private final VideoRenderer renderer;
private final RenderSettings settings;
private final Process process;
private final OutputStream outputStream;
@@ -43,9 +41,8 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
private ByteArrayOutputStream ffmpegLog = new ByteArrayOutputStream(4096);
public VideoWriter(final VideoRenderer renderer) throws IOException {
this.renderer = renderer;
this.settings = renderer.getRenderSettings();
public VideoWriter(final RenderSettings settings) throws IOException {
this.settings = settings;
File outputFolder = settings.getOutputFile().getParentFile();
FileUtils.forceMkdir(outputFolder);
@@ -60,13 +57,9 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
.replace("%FILTERS%", settings.getVideoFilters());
String executable = settings.getExportCommand().isEmpty() ? findFFmpeg() : settings.getExportCommand();
LOGGER.info("Starting {} with args: {}", executable, commandArgs);
System.out.println("Starting " + executable + " with args: " + commandArgs);
String[] cmdline = new CommandLine(executable).addArguments(commandArgs).toStrings();
try {
process = new ProcessBuilder(cmdline).directory(outputFolder).start();
} catch (IOException e) {
throw new NoFFmpegException(e);
}
process = new ProcessBuilder(cmdline).directory(outputFolder).start();
File exportLogFile = new File(Minecraft.getMinecraft().mcDataDir, "export.log");
OutputStream exportLogOut = new TeeOutputStream(new FileOutputStream(exportLogFile), ffmpegLog);
new StreamPipe(process.getInputStream(), exportLogOut).start();
@@ -159,16 +152,6 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
if (aborted) {
return;
}
try {
// Check whether this is a failure right at the beginning of the rendering process
// or at some later point (ffmpeg won't print the output file until the first frame
// has been written to stdin, so we can't already check for invalid args in <init>).
getVideoFile();
} catch (FFmpegStartupException e) {
// Possibly invalid ffmpeg arguments
renderer.setFailure(e);
return;
}
CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame");
CrashReportCategory exportDetails = report.makeCategory("Export details");
exportDetails.addCrashSection("Export command", settings.getExportCommand());
@@ -190,7 +173,7 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
aborted = true;
}
public File getVideoFile() throws FFmpegStartupException {
public File getVideoFile() {
String log = ffmpegLog.toString();
for (String line : log.split("\n")) {
if (line.startsWith("Output #0")) {
@@ -198,30 +181,6 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
return new File(settings.getOutputFile().getParentFile(), fileName);
}
}
throw new FFmpegStartupException(settings, log);
}
public static class NoFFmpegException extends IOException {
public NoFFmpegException(Throwable cause) {
super(cause);
}
}
public static class FFmpegStartupException extends IOException {
private final RenderSettings settings;
private final String log;
public FFmpegStartupException(RenderSettings settings, String log) {
this.settings = settings;
this.log = log;
}
public RenderSettings getSettings() {
return settings;
}
public String getLog() {
return log;
}
throw new IllegalStateException("No output file found.");
}
}

View File

@@ -4,10 +4,10 @@ import com.replaymod.render.RenderSettings;
import com.replaymod.render.frame.CubicOpenGlFrame;
import com.replaymod.render.frame.ODSOpenGlFrame;
import com.replaymod.render.frame.OpenGlFrame;
import com.replaymod.render.hooks.GLStateTracker;
import com.replaymod.render.rendering.FrameCapturer;
import com.replaymod.render.shader.Program;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException;
import net.minecraft.util.ResourceLocation;
@@ -15,13 +15,9 @@ import org.lwjgl.util.ReadableDimension;
import java.io.IOException;
import static net.minecraft.client.renderer.GlStateManager.*;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
private static final ResourceLocation vertexResource = new ResourceLocation("replaymod", "shader/ods.vert");
@@ -32,8 +28,8 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
private final Program.Uniform directionVariable;
private final Program.Uniform leftEyeVariable;
private final GLStateTracker.EnabledState[] previousStates = new GLStateTracker.EnabledState[2];
private final GLStateTracker.EnabledState previousFogState;
private final BooleanState[] previousStates = new BooleanState[3];
private final BooleanState previousFogState;
private final Minecraft mc = Minecraft.getMinecraft();
@@ -46,11 +42,6 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
return renderInfo.getFrameSize();
}
@Override
public int getFramesDone() {
return renderInfo.getFramesDone();
}
@Override
public int getTotalFrames() {
return renderInfo.getTotalFrames();
@@ -82,14 +73,15 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
setTexture("lightMap", 1);
linkState(0, "textureEnabled");
linkState(1, "lightMapEnabled");
linkState(2, "hurtTextureEnabled");
final Program.Uniform uniform = shaderProgram.getUniformVariable("fogEnabled");
previousFogState = GLStateTracker.getInstance().fog;
uniform.set(previousFogState.isEnabled());
GLStateTracker.getInstance().fog = new GLStateTracker.EnabledState() {
previousFogState = GlStateManager.fogState.fog;
uniform.set(previousFogState.currentState);
GlStateManager.fogState.fog = new BooleanState(previousFogState.capability) {
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
uniform.set(enabled);
public void setState(boolean state) {
super.setState(state);
uniform.set(state);
}
};
shaderProgram.stopUsing();
@@ -104,13 +96,13 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
private void linkState(int id, String var) {
final Program.Uniform uniform = shaderProgram.getUniformVariable(var);
previousStates[id] = GLStateTracker.getInstance().texture[id];
uniform.set(previousStates[id].isEnabled());
GLStateTracker.getInstance().texture[id] = new GLStateTracker.EnabledState() {
previousStates[id] = GlStateManager.textureState[id].texture2DState;
uniform.set(previousStates[id].currentState);
GlStateManager.textureState[id].texture2DState = new BooleanState(previousStates[id].capability) {
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
uniform.set(enabled);
public void setState(boolean state) {
super.setState(state);
uniform.set(state);
}
};
}
@@ -140,10 +132,10 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
left.close();
right.close();
shaderProgram.delete();
for (int i = 0; i < previousStates.length; i++) {
GLStateTracker.getInstance().texture[i] = previousStates[i];
for (int i = 0; i < 3; i++) {
GlStateManager.textureState[i].texture2DState = previousStates[i];
}
GLStateTracker.getInstance().fog = previousFogState;
GlStateManager.fogState.fog = previousFogState;
}
private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer {
@@ -155,17 +147,17 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) {
resize(getFrameWidth(), getFrameHeight());
glPushMatrix();
pushMatrix();
frameBuffer().bindFramebuffer(true);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
enableTexture2D();
directionVariable.set(captureData.ordinal());
worldRenderer.renderWorld(partialTicks, null);
frameBuffer().unbindFramebuffer();
glPopMatrix();
popMatrix();
return captureFrame(frameId, captureData);
}

View File

@@ -15,9 +15,9 @@ import org.lwjgl.util.WritableDimension;
import java.io.IOException;
import java.nio.ByteBuffer;
import static net.minecraft.client.renderer.GlStateManager.*;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
public abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData> implements FrameCapturer<F> {
protected final WorldRenderer worldRenderer;
@@ -76,16 +76,16 @@ public abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData
protected OpenGlFrame renderFrame(int frameId, float partialTicks, D captureData) {
resize(getFrameWidth(), getFrameHeight());
GL11.glPushMatrix();
pushMatrix();
frameBuffer().bindFramebuffer(true);
GL11.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GL11.glEnable(GL_TEXTURE_2D);
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
enableTexture2D();
worldRenderer.renderWorld(partialTicks, captureData);
frameBuffer().unbindFramebuffer();
GL11.glPopMatrix();
popMatrix();
return captureFrame(frameId, captureData);
}

View File

@@ -6,8 +6,6 @@ import org.lwjgl.util.ReadableDimension;
public interface RenderInfo {
ReadableDimension getFrameSize();
int getFramesDone();
int getTotalFrames();
float updateForNextFrame();

View File

@@ -1,9 +1,9 @@
package com.replaymod.render.events;
import com.replaymod.render.rendering.VideoRenderer;
import cpw.mods.fml.common.eventhandler.Event;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraftforge.fml.common.eventhandler.Event;
@RequiredArgsConstructor
public abstract class ReplayRenderEvent extends Event {

View File

@@ -1,109 +0,0 @@
package com.replaymod.render.gui;
import com.replaymod.render.RenderSettings;
import com.replaymod.render.VideoWriter;
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.GuiElement;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.util.ReportedException;
import java.util.Arrays;
import java.util.function.Consumer;
import static com.replaymod.render.ReplayModRender.LOGGER;
public class GuiExportFailed extends GuiScreen {
public static GuiExportFailed tryToRecover(VideoWriter.FFmpegStartupException e, Consumer<RenderSettings> doRestart) {
// Always log the error first
LOGGER.error("Rendering video:", e);
RenderSettings settings = e.getSettings();
// Check whether the user has configured some custom ffmpeg arguments
if (settings.getEncodingPreset().getValue().equals(settings.getExportArguments())) {
// If they haven't, then this is probably a faulty ffmpeg installation and there's nothing we can do
CrashReport crashReport = CrashReport.makeCrashReport(e, "Exporting video");
CrashReportCategory details = crashReport.makeCategory("Export details");
details.addCrashSection("Settings", settings);
details.addCrashSection("FFmpeg log", e.getLog());
throw new ReportedException(crashReport);
} else {
// If they have, ask them whether it was intentional
GuiExportFailed gui = new GuiExportFailed(e, doRestart);
gui.display();
return gui;
}
}
private final GuiLabel logLabel = new GuiLabel(this)
.setI18nText("replaymod.gui.rendering.error.ffmpeglog");
private final GuiVerticalList logList = new GuiVerticalList(this).setDrawShadow(true);
private final GuiButton resetButton = new GuiButton().setI18nLabel("gui.yes").setSize(100, 20);
private final GuiButton abortButton = new GuiButton().setI18nLabel("gui.no").setSize(100, 20);
private final GuiPanel info = new GuiPanel(this)
.setLayout(new VerticalLayout().setSpacing(4))
.addElements(new VerticalLayout.Data(0.5),
new GuiLabel().setI18nText("replaymod.gui.rendering.error.ffmpegargs.1"),
new GuiLabel().setI18nText("replaymod.gui.rendering.error.ffmpegargs.2"),
new GuiLabel(),
new GuiPanel().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5))
.addElements(null, resetButton, abortButton)
);
{
setLayout(new CustomLayout<GuiScreen>() {
@Override
protected void layout(GuiScreen container, int width, int height) {
pos(info, width/2 - width(info)/2, (height/2 - height(info) - 30) / 2 + 30);
pos(logLabel, width/2 - width(logLabel)/2, height/2 + 4);
pos(logList, 10, y(logLabel) + height(logLabel) + 4);
size(logList, width - 10 - x(logList), height - 10 - y(logList));
}
});
setTitle(new GuiLabel().setI18nText("replaymod.gui.rendering.error.title"));
setBackground(Background.DIRT);
}
public GuiExportFailed(VideoWriter.FFmpegStartupException e, Consumer<RenderSettings> doRestart) {
logList.getListPanel().addElements(null,
Arrays.stream(e.getLog().replace("\t", " ").split("\n"))
.map(l -> new GuiLabel().setText(l))
.toArray(GuiElement[]::new));
resetButton.onClick(() -> {
RenderSettings oldSettings = e.getSettings();
doRestart.accept(new RenderSettings(
oldSettings.getRenderMethod(),
oldSettings.getEncodingPreset(),
oldSettings.getVideoWidth(),
oldSettings.getVideoHeight(),
oldSettings.getFramesPerSecond(),
oldSettings.getBitRate(),
oldSettings.getOutputFile(),
oldSettings.isRenderNameTags(),
oldSettings.isStabilizeYaw(),
oldSettings.isStabilizePitch(),
oldSettings.isStabilizeRoll(),
oldSettings.getChromaKeyingColor(),
oldSettings.isInject360Metadata(),
oldSettings.getAntiAliasing(),
oldSettings.getExportCommand(),
oldSettings.getEncodingPreset().getValue(),
oldSettings.isHighPerformance()
));
});
abortButton.onClick(() -> {
// Assume they know what they're doing
getMinecraft().displayGuiScreen(null);
});
}
}

View File

@@ -1,11 +1,8 @@
package com.replaymod.render.gui;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.replaymod.core.utils.Utils;
import com.replaymod.render.ReplayModRender;
import com.replaymod.render.VideoWriter;
import com.replaymod.render.rendering.VideoRenderer;
import com.replaymod.render.utils.RenderJob;
import com.replaymod.replay.ReplayHandler;
@@ -14,7 +11,6 @@ import com.replaymod.replaystudio.util.I18n;
import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.RenderInfo;
import de.johni0702.minecraft.gui.container.AbstractGuiClickableContainer;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiVerticalList;
@@ -29,10 +25,12 @@ import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
import static com.replaymod.render.ReplayModRender.LOGGER;
@@ -64,8 +62,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
renameButton, removeButton),
closeButton);
private final AbstractGuiScreen container;
private final ReplayHandler replayHandler;
private final GuiContainer container;
private Entry selectedEntry;
{
@@ -87,10 +84,9 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
}).addElements(null, title, list, buttonPanel);
}
public GuiRenderQueue(AbstractGuiScreen container, GuiRenderSettings guiRenderSettings, ReplayHandler replayHandler, Timeline timeline) {
public GuiRenderQueue(GuiContainer container, GuiRenderSettings guiRenderSettings, ReplayHandler replayHandler, Timeline timeline) {
super(container);
this.container = container;
this.replayHandler = replayHandler;
LOGGER.trace("Opening render queue popup");
setBackgroundColor(Colors.DARK_TRANSPARENT);
@@ -192,46 +188,30 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> {
renderButton.onClick(() -> {
LOGGER.trace("Render button clicked");
processQueue(queue);
// Close all GUIs (so settings in GuiRenderSettings are saved)
getMinecraft().displayGuiScreen(null);
// Start rendering
for (RenderJob renderJob : queue) {
LOGGER.info("Starting render job {}", renderJob);
try {
VideoRenderer videoRenderer = new VideoRenderer(renderJob.getSettings(), replayHandler, renderJob.getTimeline());
videoRenderer.renderVideo();
} catch (IOException e) {
LOGGER.error("Rendering video:", e);
GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"),
I18n.format("replaymod.gui.rendering.error.message"));
getMinecraft().displayGuiScreen(errorScreen);
return;
} catch (Throwable t) {
CrashReport crashReport = CrashReport.makeCrashReport(t, "Rendering video");
throw new ReportedException(crashReport);
}
}
});
updateButtons();
}
private void processQueue(Iterable<RenderJob> queue) {
// Close all GUIs (so settings in GuiRenderSettings are saved)
getMinecraft().displayGuiScreen(null);
// Start rendering
int jobsDone = 0;
for (RenderJob renderJob : queue) {
LOGGER.info("Starting render job {}", renderJob);
try {
VideoRenderer videoRenderer = new VideoRenderer(renderJob.getSettings(), replayHandler, renderJob.getTimeline());
videoRenderer.renderVideo();
} catch (VideoWriter.NoFFmpegException e) {
LOGGER.error("Rendering video:", e);
GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"),
I18n.format("replaymod.gui.rendering.error.message"));
getMinecraft().displayGuiScreen(errorScreen);
return;
} catch (VideoWriter.FFmpegStartupException e) {
int jobsToSkip = jobsDone;
GuiExportFailed.tryToRecover(e, newSettings -> {
// Update current job with fixed ffmpeg arguments
renderJob.setSettings(newSettings);
// Restart queue, skipping the already completed jobs
processQueue(Iterables.skip(queue, jobsToSkip));
});
return;
} catch (Throwable t) {
Utils.error(LOGGER, this, CrashReport.makeCrashReport(t, "Rendering video"), () -> {});
container.display(); // Re-show the queue popup and the new error popup
return;
}
jobsDone++;
}
}
@Override
public void open() {
super.open();

View File

@@ -7,7 +7,6 @@ import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.replaymod.render.RenderSettings;
import com.replaymod.render.ReplayModRender;
import com.replaymod.render.VideoWriter;
import com.replaymod.render.rendering.VideoRenderer;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replaystudio.pathing.path.Timeline;
@@ -30,6 +29,7 @@ import de.johni0702.minecraft.gui.utils.Utils;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import org.lwjgl.util.Color;
@@ -39,13 +39,11 @@ import org.lwjgl.util.ReadableDimension;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import static com.replaymod.core.utils.Utils.error;
import static com.replaymod.render.ReplayModRender.LOGGER;
public class GuiRenderSettings extends GuiScreen implements Closeable {
public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT);
public final GuiVerticalList settingsList = new GuiVerticalList(contentPanel).setDrawSlider(true);
@@ -201,21 +199,15 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
try {
VideoRenderer videoRenderer = new VideoRenderer(save(false), replayHandler, timeline);
videoRenderer.renderVideo();
} catch (VideoWriter.NoFFmpegException e) {
LOGGER.error("Rendering video:", e);
} catch (IOException e) {
e.printStackTrace();
GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"),
I18n.format("replaymod.gui.rendering.error.message"));
getMinecraft().displayGuiScreen(errorScreen);
} catch (VideoWriter.FFmpegStartupException e) {
GuiExportFailed.tryToRecover(e, newSettings -> {
// Update settings with fixed ffmpeg arguments
exportArguments.setText(newSettings.getExportArguments());
// Restart rendering, this will also save the changed ffmpeg arguments
renderButton.onClick();
});
} catch (Throwable t) {
error(LOGGER, GuiRenderSettings.this, CrashReport.makeCrashReport(t, "Rendering video"), () -> {});
display(); // Re-show the render settings gui and the new error popup
CrashReport crashReport = CrashReport.makeCrashReport(t, "Rendering video");
throw new ReportedException(crashReport);
}
}
}).setSize(100, 20).setI18nLabel("replaymod.gui.render");

View File

@@ -0,0 +1,104 @@
package com.replaymod.render.hooks;
import com.replaymod.render.utils.JailingQueue;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.ChunkRenderWorker;
import net.minecraft.client.renderer.chunk.RenderChunk;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
public class ChunkLoadingRenderGlobal {
private final RenderGlobal hooked;
private final ChunkRenderDispatcher renderDispatcher;
private final JailingQueue<ChunkCompileTaskGenerator> workerJailingQueue;
private final CustomChunkRenderWorker renderWorker;
private int frame;
@SuppressWarnings("unchecked")
public ChunkLoadingRenderGlobal(RenderGlobal renderGlobal) {
this.hooked = renderGlobal;
this.renderDispatcher = renderGlobal.renderDispatcher;
this.renderWorker = new CustomChunkRenderWorker(renderDispatcher, new RegionRenderCacheBuilder());
int workerThreads = renderDispatcher.listThreadedWorkers.size();
BlockingQueue<ChunkCompileTaskGenerator> queueChunkUpdates = renderDispatcher.queueChunkUpdates;
workerJailingQueue = new JailingQueue<>(queueChunkUpdates);
renderDispatcher.queueChunkUpdates = workerJailingQueue;
ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null);
element.finish();
for (int i = 0; i < workerThreads; i++) {
queueChunkUpdates.add(element);
}
// Temporary workaround for dead lock, will be replaced by a new (ShaderMod compatible) mechanism later
//noinspection StatementWithEmptyBody
while (renderDispatcher.runChunkUploads(0)) {}
workerJailingQueue.jail(workerThreads);
renderDispatcher.queueChunkUpdates = queueChunkUpdates;
try {
Field hookField = RenderGlobal.class.getField("replayModRender_hook");
hookField.set(hooked, this);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
public void updateChunks() {
while (renderDispatcher.runChunkUploads(0)) {
hooked.displayListEntitiesDirty = true;
}
while (!renderDispatcher.queueChunkUpdates.isEmpty()) {
try {
renderWorker.processTask(renderDispatcher.queueChunkUpdates.poll());
} catch (InterruptedException ignored) { }
}
Iterator<RenderChunk> iterator = hooked.chunksToUpdate.iterator();
while (iterator.hasNext()) {
RenderChunk renderchunk = iterator.next();
renderDispatcher.updateChunkNow(renderchunk);
renderchunk.setNeedsUpdate(false);
iterator.remove();
}
}
public void uninstall() {
workerJailingQueue.freeAll();
try {
Field hookField = RenderGlobal.class.getField("replayModRender_hook");
hookField.set(hooked, null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
public int nextFrameId() {
return frame++;
}
/**
* Custom ChunkRenderWorker class providing access to the protected processTask method
*/
private static class CustomChunkRenderWorker extends ChunkRenderWorker {
public CustomChunkRenderWorker(ChunkRenderDispatcher p_i46202_1_, RegionRenderCacheBuilder p_i46202_2_) {
super(p_i46202_1_, p_i46202_2_);
}
@Override
protected void processTask(ChunkCompileTaskGenerator p_178474_1_) throws InterruptedException {
super.processTask(p_178474_1_);
}
}
}

View File

@@ -2,12 +2,11 @@ package com.replaymod.render.hooks;
import com.replaymod.render.RenderSettings;
import com.replaymod.render.capturer.CaptureData;
import com.replaymod.render.capturer.RenderInfo;
import com.replaymod.render.capturer.WorldRenderer;
import cpw.mods.fml.common.FMLCommonHandler;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraftforge.fml.common.FMLCommonHandler;
import java.io.IOException;
@@ -17,16 +16,12 @@ public class EntityRendererHandler implements WorldRenderer {
@Getter
protected final RenderSettings settings;
@Getter
private final RenderInfo renderInfo;
public CaptureData data;
public boolean omnidirectional;
public EntityRendererHandler(RenderSettings settings, RenderInfo renderInfo) {
public EntityRendererHandler(RenderSettings settings) {
this.settings = settings;
this.renderInfo = renderInfo;
((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(this);
}
@@ -42,11 +37,11 @@ public class EntityRendererHandler implements WorldRenderer {
mc.entityRenderer.updateLightmap(partialTicks);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glAlphaFunc(516, 0.5F);
GlStateManager.enableDepth();
GlStateManager.enableAlpha();
GlStateManager.alphaFunc(516, 0.5F);
mc.entityRenderer.renderWorld(partialTicks, finishTimeNano);
mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano);
FMLCommonHandler.instance().onRenderTickEnd(partialTicks);
}

View File

@@ -1,63 +0,0 @@
package com.replaymod.render.hooks;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
/**
* Tracks OpenGL state for use in the ODS shader.
* All methods should be called from the GL mixins only.
*/
public class GLStateTracker {
private static ThreadLocal<GLStateTracker> stateTracker = ThreadLocal.withInitial(GLStateTracker::new);
public static GLStateTracker getInstance() {
return stateTracker.get();
}
public int activeTexture;
public EnabledState fog = new EnabledState();
public EnabledState[] texture = new EnabledState[32];
{
for (int i = 0; i < texture.length; i++) {
texture[i] = new EnabledState();
}
}
public void updateActiveTexture(int magic) {
this.activeTexture = magic - GL13.GL_TEXTURE0;
}
public void updateEnabledState(int magic, boolean enabled) {
switch (magic) {
case GL11.GL_FOG: fog.setEnabled(enabled); break;
case GL11.GL_TEXTURE_2D: texture[activeTexture].setEnabled(enabled); break;
}
}
public static class EnabledState {
private boolean enabled;
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
}
// Called via ASM (see GLStateTrackerTransformer)
@Deprecated
@SuppressWarnings("unused")
public static void hook_glEnable(int magic) {
GL11.glEnable(magic);
getInstance().updateEnabledState(magic, true);
}
// Called via ASM (see GLStateTrackerTransformer)
@Deprecated
@SuppressWarnings("unused")
public static void hook_glDisable(int magic) {
GL11.glDisable(magic);
getInstance().updateEnabledState(magic, false);
}
}

View File

@@ -4,6 +4,7 @@ import com.coremedia.iso.IsoFile;
import com.coremedia.iso.boxes.*;
import com.google.common.primitives.Bytes;
import com.googlecode.mp4parser.BasicContainer;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
@@ -12,8 +13,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import static com.replaymod.render.ReplayModRender.LOGGER;
public class MetadataInjector {
private static final String STITCHING_SOFTWARE = "Minecraft ReplayMod";
@@ -97,7 +96,7 @@ public class MetadataInjector {
videoFileOutputStream = new FileOutputStream(videoFile);
tempIsoFile.getBox(videoFileOutputStream.getChannel());
} catch(Exception e) {
LOGGER.error("360 Degree Metadata couldn't be injected", e);
FMLLog.getLogger().error("360 Degree Metadata couldn't be injected", e);
} finally {
IOUtils.closeQuietly(tempIsoFile);
IOUtils.closeQuietly(videoFileOutputStream);

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