Compare commits

...

37 Commits

Author SHA1 Message Date
johni0702
e20dfa6d7f Merge branch '1.8' into 1.9.4
682fb4b Download OpenEye via http because of outdated java versions
0c48d6a Make ShaderReflection init fail fast, not printing out unneeded exceptions
2016-11-11 18:14:02 +01:00
johni0702
682fb4bfdc Download OpenEye via http because of outdated java versions
The Java version shipped with the default Minecraft launcher for Windows and
OSX, is quite old and does not contain the necessary root certificates for
properly accepting LetsEncrypt's certificates.
This can be reversed once the Minecraft launcher ships at least 1.8.0_101.
2016-11-11 18:12:07 +01:00
johni0702
0c48d6a1dd Make ShaderReflection init fail fast, not printing out unneeded exceptions 2016-11-11 18:10:19 +01:00
johni0702
54790d33f9 Merge branch '1.8' into 1.9.4
0ba5b0a Merge branch '1.8-shaders' into 1.8
e5eb982 Add Optifine compatibility to rendering by disabling their separate chunk loading queue Add compatibility to shaders that utilize the Shadow Map by manually setting displayListEntitiesDirty to false Call Shaders.beginRender in an Event Handler to avoid calling all of EntityRenderer#renderWorld
3a28c5b Draw the GuiVideoRenderer on a separate framebuffer that has the actual size of the window instead of MC's framebuffer which may have a different size
636ce7b Add compatibility for GLSL Shaders
92eb11a Change video preview to be compatible with Optifine
2016-11-10 21:51:28 +01:00
johni0702
0ba5b0aaad Merge branch '1.8-shaders' into 1.8 2016-11-10 21:22:08 +01:00
johni0702
165861764c Merge branch '1.8' into 1.9.4
0db1b9e Fix opening a replay multiple times by quickly double clicking (fixes #25)
9ff767b Fix spectated player having the arm model of the camera player
d82cef3 Make sure video folder exists before rendering (fixes #24)
cc65b26 Replace custom update system with forge update checker
c8b71dc Add tooltips to ingame buttons
b240f8a Fix camera path always playing from the beginning regardless of ctrl key
4805e5e Fix crash when setting two keyframes to the same time via the edit keyframe gui
18f8303 Fix crash when moving the last keyframe
fcd4c07 Add error popup when trying to play path with reversed time keyframes Update jGui
9c605ba Update jGui Fix handling of ESC key in popups (fixes #17)
721fdc4 Allow moving the keyframe timeline cursor by dragging (fixes #18)
1851e97 Allow deletion of selected keyframes by pressing the DELETE key (fixes #19)
d547098 Fix markers not being removed
b2c7faa Fix marker not being deselected when clicking anywhere else
4e6b387 Update jGui Fix popups being drawn outside of the actually visible element (fixes #21)
21bbeb2 Fix bitrate field always being active regardless of selected preset (fixes #20)
b5a6d8b Fix camera player head being visible while in third person
dd0537b Center star between resolution fields in render settings gui
e35bfca Increase spacing between rows in the render settings gui
6d0cdfb Update jGui (fixes #15)
2016-11-10 21:02:55 +01:00
johni0702
0db1b9ef5a Fix opening a replay multiple times by quickly double clicking (fixes #25) 2016-11-10 20:52:07 +01:00
johni0702
5014edafe0 Remove javascript comments from mcmod.info 2016-11-10 20:32:24 +01:00
CrushedPixel
e5eb982b98 Add Optifine compatibility to rendering by disabling their separate chunk loading queue
Add compatibility to shaders that utilize the Shadow Map by manually setting displayListEntitiesDirty to false
Call Shaders.beginRender in an Event Handler to avoid calling all of EntityRenderer#renderWorld
2016-11-09 21:11:28 +01:00
CrushedPixel
3a28c5ba6f Draw the GuiVideoRenderer on a separate framebuffer that has the actual size of the window instead of MC's framebuffer which may have a different size 2016-11-09 20:07:43 +01:00
CrushedPixel
636ce7bdd4 Add compatibility for GLSL Shaders 2016-11-09 19:50:36 +01:00
johni0702
92eb11a617 Change video preview to be compatible with Optifine 2016-11-09 19:04:46 +01:00
johni0702
9ff767b054 Fix spectated player having the arm model of the camera player 2016-11-09 10:23:58 +01:00
johni0702
d82cef3687 Make sure video folder exists before rendering (fixes #24) 2016-11-06 13:43:28 +01:00
johni0702
cc65b26c31 Replace custom update system with forge update checker 2016-10-23 15:52:51 +02:00
johni0702
c8b71dc2cc Add tooltips to ingame buttons 2016-10-23 15:07:24 +02:00
johni0702
b240f8a276 Fix camera path always playing from the beginning regardless of ctrl key 2016-10-23 14:45:14 +02:00
johni0702
4805e5e9f5 Fix crash when setting two keyframes to the same time via the edit keyframe gui
The save button is now disabled if the configured time is already occupied.
2016-10-23 14:06:17 +02:00
johni0702
18f83039af Fix crash when moving the last keyframe 2016-10-23 13:49:56 +02:00
johni0702
fcd4c0788e Add error popup when trying to play path with reversed time keyframes
Update jGui
2016-10-23 13:46:00 +02:00
johni0702
9c605baa7e Update jGui
Fix handling of ESC key in popups (fixes #17)

This commit changes the behavior of the escape key from closing the current
gui screen to only closing the current layer of the gui screen. E.g. pressing
ESC closes the popup but not the gui screen, it used to close both
2016-10-22 23:15:20 +02:00
johni0702
721fdc4d86 Allow moving the keyframe timeline cursor by dragging (fixes #18) 2016-10-22 22:52:27 +02:00
johni0702
1851e97346 Allow deletion of selected keyframes by pressing the DELETE key (fixes #19) 2016-10-22 22:33:11 +02:00
johni0702
d547098ce8 Fix markers not being removed
Markers were saved in a (Hash)Set even though they are mutable. Reducing the
exposed interface to Collection allows changing the interal implementation to
ArrayList which solves the problem.
2016-10-22 22:14:16 +02:00
johni0702
b2c7faac6f Fix marker not being deselected when clicking anywhere else 2016-10-22 21:22:36 +02:00
johni0702
4e6b387f97 Update jGui
Fix popups being drawn outside of the actually visible element (fixes #21)
2016-10-22 21:11:23 +02:00
johni0702
21bbeb2235 Fix bitrate field always being active regardless of selected preset (fixes #20) 2016-10-22 21:11:06 +02:00
johni0702
b5a6d8b307 Fix camera player head being visible while in third person 2016-10-22 16:10:03 +02:00
johni0702
dd0537b8fd Center star between resolution fields in render settings gui 2016-10-19 13:45:19 +02:00
johni0702
e35bfcaa58 Increase spacing between rows in the render settings gui 2016-10-19 13:43:03 +02:00
johni0702
6d0cdfbcb4 Update jGui (fixes #15) 2016-10-18 11:02:46 +02:00
johni0702
21ac84e990 Merge branch '1.8' into 1.9.4
a804d6b Properly register replay restrictions plugin channel
45191da Add GUI that offers the user to install OpenEye
9de63ce Add gradle tasks for automating the release process
2a7542a Update jGui
2016-10-17 13:05:25 +02:00
johni0702
a804d6b8a3 Properly register replay restrictions plugin channel 2016-10-16 11:16:20 +02:00
johni0702
45191daad4 Add GUI that offers the user to install OpenEye 2016-10-07 22:55:58 +02:00
johni0702
9de63ce9b1 Add gradle tasks for automating the release process 2016-10-06 19:56:25 +02:00
johni0702
2a7542aeb8 Update jGui
Never use decimal point regardless of user locale (fixes #13)
Make popups disable user input and show the mouse when opened on an overlay (fixes #11)
Fix de-focusing of text fields when focusing another text field (fixes #14)
2016-10-06 15:19:20 +02:00
johni0702
8e6eec879f Fix name tag hiding during redering (fixes #10) 2016-09-24 17:11:25 +02:00
43 changed files with 820 additions and 212 deletions

View File

@@ -1,3 +1,5 @@
import groovy.json.JsonOutput
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
buildscript { buildscript {
@@ -196,4 +198,85 @@ setupDecompWorkspace.dependsOn copySrg
setupDevWorkspace.dependsOn copySrg setupDevWorkspace.dependsOn copySrg
project.tasks.idea.dependsOn copySrg project.tasks.idea.dependsOn copySrg
def generateVersionsJson() {
// List all tags
def stdout = new ByteArrayOutputStream()
project.exec {
commandLine 'git', 'for-each-ref', '--sort=taggerdate', '--format=%(refname:short)', 'refs/tags'
standardOutput = stdout
}
def versions = stdout.toString().tokenize('\n').reverse()
def mcVersions = versions.collect {it.substring(0, it.indexOf('-'))}.unique().reverse()
def root = [
homepage: 'https://www.replaymod.com/download/',
promos: [:]
]
mcVersions.forEach { mcVersion ->
def mcVersionRoot = [:]
def latest
def recommended
versions.forEach {
def (thisMcVersion, modVersion, preVersion) = it.tokenize('-')
if (thisMcVersion == mcVersion) {
mcVersionRoot[it] = "See https://github.com/ReplayMod/ReplayMod/releases/$it"
if (latest == null) latest = it
if (preVersion == null) {
if (recommended == null) recommended = it
}
}
}
root[mcVersion] = mcVersionRoot
root.promos[mcVersion + '-latest'] = latest
if (recommended != null) {
root.promos[mcVersion + '-recommended'] = recommended
}
}
root
}
task doRelease() {
doLast {
// Parse version
def version = project.releaseVersion as String
if (project.version.endsWith('*')) {
throw new InvalidUserDataException('Git working tree is dirty. Make sure to commit all changes.')
}
def (mcVersion, modVersion, preVersion) = version.tokenize('-')
preVersion = preVersion != null && preVersion.startsWith('b') ? preVersion.substring(1) : null
// Create new tag
if (preVersion != null) {
project.exec {
commandLine 'git', 'tag',
'-m', "Pre-release $preVersion of $modVersion for Minecraft $mcVersion",
"$mcVersion-$modVersion-b$preVersion"
}
} else {
project.exec {
commandLine 'git', 'tag',
'-m', "Release $modVersion for Minecraft $mcVersion",
"$mcVersion-$modVersion"
}
}
// Switch to master branch to update versions.json
project.exec { commandLine 'git', 'checkout', 'master' }
// Generate versions.json content
def versionsRoot = generateVersionsJson()
def versionsJson = JsonOutput.prettyPrint(JsonOutput.toJson(versionsRoot))
// Write versions.json
new File('versions.json').write(versionsJson)
// Commit changes
project.exec { commandLine 'git', 'add', 'versions.json' }
project.exec { commandLine 'git', 'commit', '-m', "Update versions.json for $version" }
// Return to previous branch
project.exec { commandLine 'git', 'checkout', '-' }
}
}
defaultTasks 'build' defaultTasks 'build'

2
jGui

Submodule jGui updated: 87f7f27009...7849fb3bb7

View File

@@ -0,0 +1,23 @@
package com.replaymod.compat;
import com.replaymod.compat.shaders.ShaderBeginRender;
import com.replaymod.core.ReplayMod;
import net.minecraftforge.common.MinecraftForge;
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, useMetadata = true)
public class ReplayModCompat {
public static final String MOD_ID = "replaymod-compat";
@Mod.Instance(ReplayMod.MOD_ID)
private static ReplayMod core;
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
EventBus bus = MinecraftForge.EVENT_BUS;
bus.register(new ShaderBeginRender());
}
}

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

@@ -19,6 +19,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin {
Mixins.addConfiguration("mixins.recording.replaymod.json"); Mixins.addConfiguration("mixins.recording.replaymod.json");
Mixins.addConfiguration("mixins.render.replaymod.json"); Mixins.addConfiguration("mixins.render.replaymod.json");
Mixins.addConfiguration("mixins.replay.replaymod.json"); Mixins.addConfiguration("mixins.replay.replaymod.json");
Mixins.addConfiguration("mixins.compat.shaders.replaymod.json");
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
if (codeSource != null) { if (codeSource != null) {

View File

@@ -34,6 +34,7 @@ import java.util.List;
@Mod(modid = ReplayMod.MOD_ID, @Mod(modid = ReplayMod.MOD_ID,
useMetadata = true, useMetadata = true,
updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json",
guiFactory = "com.replaymod.core.gui.GuiFactory") guiFactory = "com.replaymod.core.gui.GuiFactory")
public class ReplayMod { public class ReplayMod {

View File

@@ -0,0 +1,142 @@
package com.replaymod.extras;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.Setting;
import de.johni0702.minecraft.gui.container.AbstractGuiScreen;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.function.Tickable;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraftforge.fml.common.Loader;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
public class OpenEyeExtra implements Extra {
private static final String DOWNLOAD_URL = "http://www.replaymod.com/dl/openeye/" + Loader.MC_VERSION;
private static final Setting<Boolean> ASK_FOR_OPEN_EYE = new Setting<>("advanced", "askForOpenEye", null, true);
private ReplayMod mod;
@Override
public void register(ReplayMod mod) throws Exception {
this.mod = mod;
mod.getSettingsRegistry().register(ASK_FOR_OPEN_EYE);
if (!Loader.isModLoaded("OpenEye") && mod.getSettingsRegistry().get(ASK_FOR_OPEN_EYE)) {
mod.runLater(() -> new OfferGui(GuiScreen.wrap(mod.getMinecraft().currentScreen)).display());
}
}
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),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye1"),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye2"),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye3"),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye4"),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye5"),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye6"),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye7"),
new GuiLabel().setI18nText("replaymod.gui.offeropeneye8"));
public final GuiPanel buttonPanel = new GuiPanel()
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5));
public final GuiPanel contentPanel = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(20))
.addElements(new VerticalLayout.Data(0.5), textPanel, buttonPanel);
public final GuiButton yesButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.yes");
public final GuiButton noButton = new GuiButton(buttonPanel).setSize(150, 20).setI18nLabel("gui.no");
public OfferGui(GuiScreen parent) {
this.parent = parent;
yesButton.onClick(() -> {
GuiPopup popup = new GuiPopup(OfferGui.this);
new Thread(() -> {
try {
File targetFile = new File("mods/" + Loader.MC_VERSION, "OpenEye.jar");
FileUtils.forceMkdir(targetFile.getParentFile());
ReadableByteChannel in = Channels.newChannel(new URL(DOWNLOAD_URL).openStream());
FileChannel out = new FileOutputStream(targetFile).getChannel();
out.transferFrom(in, 0, Long.MAX_VALUE);
} catch (Throwable e) {
e.printStackTrace();
} finally {
mod.runLater(() -> {
popup.close();
parent.display();
});
}
}).start();
});
noButton.onClick(() -> {
mod.getSettingsRegistry().set(ASK_FOR_OPEN_EYE, false);
mod.getSettingsRegistry().save();
parent.display();
});
setLayout(new CustomLayout<OfferGui>() {
@Override
protected void layout(OfferGui container, int width, int height) {
pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2);
}
});
}
@Override
protected OfferGui getThis() {
return this;
}
}
private static final class GuiPopup extends AbstractGuiPopup<GuiPopup> {
GuiPopup(GuiContainer container) {
super(container);
popup.addElements(null, new GuiIndicator().setColor(Colors.BLACK));
setBackgroundColor(Colors.DARK_TRANSPARENT);
open();
}
@Override
public void close() {
super.close();
}
@Override
protected GuiPopup getThis() {
return this;
}
private static final class GuiIndicator extends GuiLabel implements Tickable {
private int tick;
@Override
public void tick() {
tick++;
setText(new String[]{
"Ooooo",
"oOooo",
"ooOoo",
"oooOo",
"ooooO",
"oooOo",
"ooOoo",
"oOooo",
}[tick / 5 % 8]);
}
}
}
}

View File

@@ -29,7 +29,7 @@ public class ReplayModExtras {
FullBrightness.class, FullBrightness.class,
HotkeyButtons.class, HotkeyButtons.class,
LocalizationExtra.class, LocalizationExtra.class,
VersionChecker.class OpenEyeExtra.class
); );
private Logger logger; private Logger logger;

View File

@@ -1,66 +0,0 @@
package com.replaymod.extras;
import com.replaymod.core.ReplayMod;
import com.replaymod.online.ReplayModOnline;
import net.minecraft.client.gui.Gui;
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.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.awt.*;
import java.util.List;
public class VersionChecker implements Extra {
@Mod.Instance(ReplayModOnline.MOD_ID)
private static ReplayModOnline module;
@Override
public void register(ReplayMod mod) throws Exception {
final String currentVersion = mod.getVersion();
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean upToDate = module.getApiClient().isVersionUpToDate(currentVersion);
if (!upToDate) {
MinecraftForge.EVENT_BUS.register(VersionChecker.this);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}, "ReplayMod-VersionChecker").start();
}
@SubscribeEvent
public void onDrawScreen(GuiScreenEvent.DrawScreenEvent.Post event) {
if (!(event.getGui() instanceof GuiMainMenu)) {
return;
}
int width = Math.max(100, event.getGui().width / 2 - 100 - 10);
@SuppressWarnings("unchecked") List<String> lines =
event.getGui().mc.fontRendererObj.listFormattedStringToWidth(I18n.format("replaymod.gui.outdated"), width);
int maxLineWidth = 0;
for(String line : lines) {
int lineWidth = event.getGui().mc.fontRendererObj.getStringWidth(line);
if(lineWidth > maxLineWidth) {
maxLineWidth = lineWidth;
}
}
Gui.drawRect(2, 77, 5 + maxLineWidth + 3, 80 + (lines.size() * 10), 0x80FF0000);
int i = 0;
for(String line : lines) {
event.getGui().mc.fontRendererObj.drawStringWithShadow(line, 5, 80 + (i * 10), Color.WHITE.getRGB());
i++;
}
}
}

View File

@@ -11,15 +11,18 @@ import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.GuiTextField; import de.johni0702.minecraft.gui.element.GuiTextField;
import de.johni0702.minecraft.gui.element.GuiToggleButton; import de.johni0702.minecraft.gui.element.GuiToggleButton;
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu; import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
import de.johni0702.minecraft.gui.function.Typeable;
import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.I18n;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.ReadablePoint;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearch> { public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearch> implements Typeable {
private final GuiReplayCenter replayCenter; private final GuiReplayCenter replayCenter;
private final ApiClient apiClient; private final ApiClient apiClient;
public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.center.search.filters").setColor(Colors.BLACK); public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.center.search.filters").setColor(Colors.BLACK);
@@ -100,4 +103,13 @@ public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearc
protected GuiReplayCenterSearch getThis() { protected GuiReplayCenterSearch getThis() {
return this; return this;
} }
@Override
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
if (keyCode == Keyboard.KEY_ESCAPE) {
cancelButton.onClick();
return true;
}
return false;
}
} }

View File

@@ -24,6 +24,7 @@ public abstract class AbstractTimelinePlayer {
private final Minecraft mc = Minecraft.getMinecraft(); private final Minecraft mc = Minecraft.getMinecraft();
private final ReplayHandler replayHandler; private final ReplayHandler replayHandler;
private Timeline timeline; private Timeline timeline;
protected long startOffset;
private long lastTime; private long lastTime;
private long lastTimestamp; private long lastTimestamp;
private ListenableFuture<Void> future; private ListenableFuture<Void> future;
@@ -33,6 +34,11 @@ public abstract class AbstractTimelinePlayer {
this.replayHandler = replayHandler; this.replayHandler = replayHandler;
} }
public ListenableFuture<Void> start(Timeline timeline, long from) {
startOffset = from;
return start(timeline);
}
public ListenableFuture<Void> start(Timeline timeline) { public ListenableFuture<Void> start(Timeline timeline) {
this.timeline = timeline; this.timeline = timeline;

View File

@@ -46,6 +46,6 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer {
@Override @Override
public long getTimePassed() { public long getTimePassed() {
return firstFrame ? 0 : System.currentTimeMillis() - startTime; return startOffset + (firstFrame ? 0 : System.currentTimeMillis() - startTime);
} }
} }

View File

@@ -1,6 +1,7 @@
package com.replaymod.recording; package com.replaymod.recording;
import com.replaymod.core.ReplayMod; import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.Restrictions;
import com.replaymod.recording.handler.ConnectionEventHandler; import com.replaymod.recording.handler.ConnectionEventHandler;
import com.replaymod.recording.packet.PacketListener; import com.replaymod.recording.packet.PacketListener;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
@@ -8,6 +9,7 @@ import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.EventBus; import net.minecraftforge.fml.common.eventhandler.EventBus;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.lwjgl.input.Keyboard; import org.lwjgl.input.Keyboard;
@@ -44,5 +46,7 @@ public class ReplayModRecording {
public void init(FMLInitializationEvent event) { public void init(FMLInitializationEvent event) {
EventBus bus = MinecraftForge.EVENT_BUS; EventBus bus = MinecraftForge.EVENT_BUS;
bus.register(connectionEventHandler = new ConnectionEventHandler(logger, core)); bus.register(connectionEventHandler = new ConnectionEventHandler(logger, core));
NetworkRegistry.INSTANCE.newSimpleChannel(Restrictions.PLUGIN_CHANNEL);
} }
} }

View File

@@ -8,6 +8,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory; import net.minecraft.crash.CrashReportCategory;
import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.CommandLine;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream; import org.apache.commons.io.output.TeeOutputStream;
import org.lwjgl.util.ReadableDimension; import org.lwjgl.util.ReadableDimension;
@@ -34,6 +35,7 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
this.settings = settings; this.settings = settings;
File outputFolder = settings.getOutputFile().getParentFile(); File outputFolder = settings.getOutputFile().getParentFile();
FileUtils.forceMkdir(outputFolder);
String fileName = settings.getOutputFile().getName(); String fileName = settings.getOutputFile().getName();
commandArgs = settings.getExportArguments() commandArgs = settings.getExportArguments()

View File

@@ -6,11 +6,11 @@ import com.replaymod.render.frame.ODSOpenGlFrame;
import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.frame.OpenGlFrame;
import com.replaymod.render.rendering.FrameCapturer; import com.replaymod.render.rendering.FrameCapturer;
import com.replaymod.render.shader.Program; import com.replaymod.render.shader.Program;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException; import net.minecraft.util.ReportedException;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension; import org.lwjgl.util.ReadableDimension;
import java.io.IOException; import java.io.IOException;
@@ -31,6 +31,8 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
private final BooleanState[] previousStates = new BooleanState[3]; private final BooleanState[] previousStates = new BooleanState[3];
private final BooleanState previousFogState; private final BooleanState previousFogState;
private final Minecraft mc = Minecraft.getMinecraft();
public ODSFrameCapturer(WorldRenderer worldRenderer, final RenderInfo renderInfo, int frameSize) { public ODSFrameCapturer(WorldRenderer worldRenderer, final RenderInfo renderInfo, int frameSize) {
RenderInfo fakeInfo = new RenderInfo() { RenderInfo fakeInfo = new RenderInfo() {
private int call; private int call;
@@ -143,6 +145,8 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
@Override @Override
protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) {
resize(getFrameWidth(), getFrameHeight());
pushMatrix(); pushMatrix();
frameBuffer().bindFramebuffer(true); frameBuffer().bindFramebuffer(true);
@@ -150,7 +154,7 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
enableTexture2D(); enableTexture2D();
directionVariable.set(captureData.ordinal()); directionVariable.set(captureData.ordinal());
worldRenderer.renderWorld(new Dimension(getFrameWidth(), getFrameHeight()), partialTicks, null); worldRenderer.renderWorld(partialTicks, null);
frameBuffer().unbindFramebuffer(); frameBuffer().unbindFramebuffer();
popMatrix(); popMatrix();

View File

@@ -4,6 +4,7 @@ import com.replaymod.render.frame.OpenGlFrame;
import com.replaymod.render.rendering.Frame; import com.replaymod.render.rendering.Frame;
import com.replaymod.render.rendering.FrameCapturer; import com.replaymod.render.rendering.FrameCapturer;
import com.replaymod.render.utils.ByteBufferPool; import com.replaymod.render.utils.ByteBufferPool;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.shader.Framebuffer; import net.minecraft.client.shader.Framebuffer;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
@@ -24,6 +25,8 @@ public abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData
protected int framesDone; protected int framesDone;
private Framebuffer frameBuffer; private Framebuffer frameBuffer;
private final Minecraft mc = Minecraft.getMinecraft();
public OpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) { public OpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
this.renderInfo = renderInfo; this.renderInfo = renderInfo;
@@ -56,7 +59,7 @@ public abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData
protected Framebuffer frameBuffer() { protected Framebuffer frameBuffer() {
if (frameBuffer == null) { if (frameBuffer == null) {
frameBuffer = new Framebuffer(getFrameWidth(), getFrameHeight(), true); frameBuffer = Minecraft.getMinecraft().getFramebuffer();
} }
return frameBuffer; return frameBuffer;
} }
@@ -71,13 +74,15 @@ public abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData
} }
protected OpenGlFrame renderFrame(int frameId, float partialTicks, D captureData) { protected OpenGlFrame renderFrame(int frameId, float partialTicks, D captureData) {
resize(getFrameWidth(), getFrameHeight());
pushMatrix(); pushMatrix();
frameBuffer().bindFramebuffer(true); frameBuffer().bindFramebuffer(true);
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
enableTexture2D(); enableTexture2D();
worldRenderer.renderWorld(frameSize, partialTicks, captureData); worldRenderer.renderWorld(partialTicks, captureData);
frameBuffer().unbindFramebuffer(); frameBuffer().unbindFramebuffer();
popMatrix(); popMatrix();
@@ -102,11 +107,18 @@ public abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData
return new OpenGlFrame(frameId, new Dimension(getFrameWidth(), getFrameHeight()), buffer); return new OpenGlFrame(frameId, new Dimension(getFrameWidth(), getFrameHeight()), buffer);
} }
protected void resize(int width, int height) {
if (width != mc.displayWidth || height != mc.displayHeight) {
setWindowSize(width, height);
}
}
private void setWindowSize(int width, int height) {
mc.resize(width, height);
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
worldRenderer.close(); worldRenderer.close();
if (frameBuffer != null) {
frameBuffer.deleteFramebuffer();
}
} }
} }

View File

@@ -1,10 +1,8 @@
package com.replaymod.render.capturer; package com.replaymod.render.capturer;
import org.lwjgl.util.ReadableDimension;
import java.io.Closeable; import java.io.Closeable;
public interface WorldRenderer extends Closeable { public interface WorldRenderer extends Closeable {
void renderWorld(ReadableDimension displaySize, float partialTicks, CaptureData data); void renderWorld(float partialTicks, CaptureData data);
void setOmnidirectional(boolean omnidirectional); void setOmnidirectional(boolean omnidirectional);
} }

View File

@@ -91,7 +91,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
}).setSize(122, 20).setSteps(110); }).setSize(122, 20).setSteps(110);
public final GuiPanel videoResolutionPanel = new GuiPanel() public final GuiPanel videoResolutionPanel = new GuiPanel()
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(2)) .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(2))
.addElements(null, videoWidth, new GuiLabel().setText("*"), videoHeight); .addElements(new HorizontalLayout.Data(0.5), videoWidth, new GuiLabel().setText("*"), videoHeight);
public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20); public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20);
public final GuiDropdownMenu<String> bitRateUnit = new GuiDropdownMenu<String>() public final GuiDropdownMenu<String> bitRateUnit = new GuiDropdownMenu<String>()
@@ -130,7 +130,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
new GuiPanel().addElements(null, bitRateField, bitRateUnit).setLayout(new HorizontalLayout()), new GuiPanel().addElements(null, bitRateField, bitRateUnit).setLayout(new HorizontalLayout()),
frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)), frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)),
new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton) new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton)
.setLayout(new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(3)); .setLayout(new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(5));
public final GuiCheckbox nametagCheckbox = new GuiCheckbox() public final GuiCheckbox nametagCheckbox = new GuiCheckbox()
.setI18nLabel("replaymod.gui.rendersettings.nametags"); .setI18nLabel("replaymod.gui.rendersettings.nametags");
@@ -284,6 +284,15 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
videoHeight.setTextColor(Colors.RED); videoHeight.setTextColor(Colors.RED);
} }
// Enable/Disable bitrate input field and dropdown
if (encodingPresetDropdown.getSelectedValue().hasBitrateSetting()) {
bitRateField.setEnabled();
bitRateUnit.setEnabled();
} else {
bitRateField.setDisabled();
bitRateUnit.setDisabled();
}
// Enable/Disable camera stabilization checkboxes // Enable/Disable camera stabilization checkboxes
switch (renderMethodDropdown.getSelectedValue()) { switch (renderMethodDropdown.getSelectedValue()) {
case CUBIC: case CUBIC:

View File

@@ -14,7 +14,6 @@ import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.I18n;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.lwjgl.util.Dimension; import org.lwjgl.util.Dimension;
@@ -23,8 +22,6 @@ import org.lwjgl.util.ReadablePoint;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import static net.minecraft.client.renderer.GlStateManager.bindTexture;
public class GuiVideoRenderer extends GuiScreen { public class GuiVideoRenderer extends GuiScreen {
private static final ResourceLocation NO_PREVIEW_TEXTURE = new ResourceLocation("replaymod", "logo.jpg"); private static final ResourceLocation NO_PREVIEW_TEXTURE = new ResourceLocation("replaymod", "logo.jpg");
@@ -232,13 +229,7 @@ public class GuiVideoRenderer extends GuiScreen {
final int videoHeight = videoSize.getHeight(); final int videoHeight = videoSize.getHeight();
if (previewTexture == null) { if (previewTexture == null) {
previewTexture = new DynamicTexture(videoWidth, videoHeight) { previewTexture = new DynamicTexture(videoWidth, videoHeight);
@Override
public void updateDynamicTexture() {
bindTexture(getGlTextureId());
TextureUtil.uploadTextureSub(0, getTextureData(), videoWidth, videoHeight, 0, 0, true, false, false);
}
};
} }
if (previewTextureDirty) { if (previewTextureDirty) {
@@ -273,7 +264,10 @@ public class GuiVideoRenderer extends GuiScreen {
buffer.mark(); buffer.mark();
synchronized (this) { synchronized (this) {
int[] data = previewTexture.getTextureData(); int[] data = previewTexture.getTextureData();
for (int i = 0; i < data.length; i++) { // Optifine changes the texture data array to be three times as long (for use by shaders),
// we only want to initialize the first third which is why we use the length of the buffer instead
// of the length of the data array
for (int i = 0; buffer.remaining() > 0; i++) {
data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff);
} }
previewTextureDirty = true; previewTextureDirty = true;

View File

@@ -5,14 +5,11 @@ import com.replaymod.render.capturer.CaptureData;
import com.replaymod.render.capturer.WorldRenderer; import com.replaymod.render.capturer.WorldRenderer;
import lombok.Getter; import lombok.Getter;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.culling.ClippingHelper; import net.minecraft.client.renderer.GlStateManager;
import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.FMLCommonHandler;
import org.lwjgl.util.ReadableDimension;
import java.io.IOException; import java.io.IOException;
import static net.minecraft.client.renderer.GlStateManager.*;
public class EntityRendererHandler implements WorldRenderer { public class EntityRendererHandler implements WorldRenderer {
public final Minecraft mc = Minecraft.getMinecraft(); public final Minecraft mc = Minecraft.getMinecraft();
@@ -29,37 +26,20 @@ public class EntityRendererHandler implements WorldRenderer {
((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(this); ((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(this);
} }
public void withDisplaySize(int displayWidth, int displayHeight, Runnable runnable) {
final int prevWidth = mc.displayWidth;
final int prevHeight = mc.displayHeight;
mc.displayWidth = displayWidth;
mc.displayHeight = displayHeight;
runnable.run();
mc.displayWidth = prevWidth;
mc.displayHeight = prevHeight;
}
@Override @Override
public void renderWorld(ReadableDimension displaySize, final float partialTicks, CaptureData data) { public void renderWorld(final float partialTicks, CaptureData data) {
this.data = data; this.data = data;
withDisplaySize(displaySize.getWidth(), displaySize.getHeight(), new Runnable() {
@Override
public void run() {
renderWorld(partialTicks, 0); renderWorld(partialTicks, 0);
} }
});
}
public void renderWorld(float partialTicks, long finishTimeNano) { public void renderWorld(float partialTicks, long finishTimeNano) {
FMLCommonHandler.instance().onRenderTickStart(partialTicks); FMLCommonHandler.instance().onRenderTickStart(partialTicks);
mc.entityRenderer.updateLightmap(partialTicks); mc.entityRenderer.updateLightmap(partialTicks);
enableDepth(); GlStateManager.enableDepth();
enableAlpha(); GlStateManager.enableAlpha();
alphaFunc(516, 0.5F); GlStateManager.alphaFunc(516, 0.5F);
mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano); mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano);
@@ -76,13 +56,6 @@ public class EntityRendererHandler implements WorldRenderer {
this.omnidirectional = omnidirectional; this.omnidirectional = omnidirectional;
} }
public static final class NoCullingClippingHelper extends ClippingHelper {
@Override
public boolean isBoxInFrustum(double p_78553_1_, double p_78553_3_, double p_78553_5_, double p_78553_7_, double p_78553_9_, double p_78553_11_) {
return true;
}
}
public interface GluPerspective { public interface GluPerspective {
void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar); void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar);
} }

View File

@@ -9,7 +9,6 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderGlobal; import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.settings.GameSettings; import net.minecraft.client.settings.GameSettings;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@@ -49,14 +48,6 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
} }
} }
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/culling/Frustum;setPosition(DDD)V"))
public void replayModRender_createNoCullingFrustum(Frustum frustum, double x, double y, double z) {
if (replayModRender_handler != null) {
frustum.clippingHelper = new EntityRendererHandler.NoCullingClippingHelper();
}
frustum.setPosition(x, y, z);
}
@Inject(method = "orientCamera", at = @At("HEAD")) @Inject(method = "orientCamera", at = @At("HEAD"))
private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) { private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) {
if (replayModRender_handler != null) { if (replayModRender_handler != null) {

View File

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

View File

@@ -38,6 +38,8 @@ public abstract class MixinRenderGlobal {
replayModRender_hook.updateChunks(); replayModRender_hook.updateChunks();
} while (displayListEntitiesDirty); } while (displayListEntitiesDirty);
displayListEntitiesDirty = true;
replayModRender_passThroughSetupTerrain = false; replayModRender_passThroughSetupTerrain = false;
ci.cancel(); ci.cancel();
} }

View File

@@ -1,21 +0,0 @@
package com.replaymod.render.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.RenderLiving;
import net.minecraft.entity.EntityLiving;
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(RenderLiving.class)
public abstract class MixinRenderLiving {
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
private void replayModRender_areAllNamesHidden(EntityLiving entity, CallbackInfoReturnable<Boolean> ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && !handler.getSettings().isRenderNameTags()) {
ci.setReturnValue(false); //this calls the cancel method
}
}
}

View File

@@ -19,6 +19,7 @@ import com.replaymod.replaystudio.pathing.path.Timeline;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundCategory;
import net.minecraft.util.Timer; import net.minecraft.util.Timer;
import org.lwjgl.input.Mouse; import org.lwjgl.input.Mouse;
@@ -62,6 +63,9 @@ public class VideoRenderer implements RenderInfo {
private boolean paused; private boolean paused;
private boolean cancelled; private boolean cancelled;
private Framebuffer guiFramebuffer;
private int displayWidth, displayHeight;
public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException { public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException {
this.settings = settings; this.settings = settings;
this.replayHandler = replayHandler; this.replayHandler = replayHandler;
@@ -130,6 +134,13 @@ public class VideoRenderer implements RenderInfo {
@Override @Override
public float updateForNextFrame() { public float updateForNextFrame() {
// because the jGui lib uses Minecraft's displayWidth and displayHeight values, update these temporarily
int displayWidthBefore = mc.displayWidth;
int displayHeightBefore = mc.displayHeight;
mc.displayWidth = displayWidth;
mc.displayHeight = displayHeight;
if (!settings.isHighPerformance() || framesDone % fps == 0) { if (!settings.isHighPerformance() || framesDone % fps == 0) {
drawGui(); drawGui();
} }
@@ -142,6 +153,10 @@ public class VideoRenderer implements RenderInfo {
tick(); tick();
} }
// change Minecraft's display size back
mc.displayWidth = displayWidthBefore;
mc.displayHeight = displayHeightBefore;
framesDone++; framesDone++;
return mc.timer.renderPartialTicks; return mc.timer.renderPartialTicks;
} }
@@ -192,10 +207,12 @@ public class VideoRenderer implements RenderInfo {
totalFrames = (int) (duration*fps/1000); totalFrames = (int) (duration*fps/1000);
ScaledResolution scaled = new ScaledResolution(mc); updateDisplaySize();
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal); chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal);
// Set up our own framebuffer to render the GUI to
guiFramebuffer = new Framebuffer(displayWidth, displayHeight, true);
} }
private void finish() { private void finish() {
@@ -219,6 +236,9 @@ public class VideoRenderer implements RenderInfo {
new SoundHandler().playRenderSuccessSound(); new SoundHandler().playRenderSuccessSound();
new GuiRenderingDone(ReplayModRender.instance, videoWriter.getVideoFile(), totalFrames, settings).display(); new GuiRenderingDone(ReplayModRender.instance, videoWriter.getVideoFile(), totalFrames, settings).display();
// Finally, resize the Minecraft framebuffer to the actual width/height of the window
mc.resize(displayWidth, displayHeight);
} }
private void tick() { private void tick() {
@@ -238,12 +258,22 @@ public class VideoRenderer implements RenderInfo {
public void drawGui() { public void drawGui() {
do { do {
// Resize the GUI framebuffer if the display size changed
if (!settings.isHighPerformance() && displaySizeChanged()) {
updateDisplaySize();
guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight);
}
pushMatrix(); pushMatrix();
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
enableTexture2D(); enableTexture2D();
mc.getFramebuffer().bindFramebuffer(true); guiFramebuffer.bindFramebuffer(true);
mc.entityRenderer.setupOverlayRendering(); mc.entityRenderer.setupOverlayRendering();
ScaledResolution scaled = new ScaledResolution(mc);
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
try { try {
gui.toMinecraft().handleInput(); gui.toMinecraft().handleInput();
} catch (IOException e) { } catch (IOException e) {
@@ -252,18 +282,17 @@ public class VideoRenderer implements RenderInfo {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
ScaledResolution scaled = new ScaledResolution(mc);
int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth; int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth;
int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1; int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1;
gui.toMinecraft().drawScreen(mouseX, mouseY, 0); gui.toMinecraft().drawScreen(mouseX, mouseY, 0);
mc.getFramebuffer().unbindFramebuffer(); guiFramebuffer.unbindFramebuffer();
popMatrix(); popMatrix();
pushMatrix(); pushMatrix();
mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); guiFramebuffer.framebufferRender(displayWidth, displayHeight);
popMatrix(); popMatrix();
// if not in high performance mode, update the gui size if screen size changed // if not in high performance mode, update the gui size if screen size changed
// otherwise just swap the progress gui to screen // otherwise just swap the progress gui to screen
if (settings.isHighPerformance()) { if (settings.isHighPerformance()) {
@@ -285,6 +314,15 @@ public class VideoRenderer implements RenderInfo {
} while (paused); } while (paused);
} }
private boolean displaySizeChanged() {
return displayWidth != Display.getWidth() || displayHeight != Display.getHeight();
}
private void updateDisplaySize() {
displayWidth = Display.getWidth();
displayHeight = Display.getHeight();
}
public int getFramesDone() { public int getFramesDone() {
return framesDone; return framesDone;
} }

View File

@@ -29,10 +29,7 @@ import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.*;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import static net.minecraft.client.renderer.GlStateManager.*; import static net.minecraft.client.renderer.GlStateManager.*;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
@@ -62,7 +59,7 @@ public class ReplayHandler {
*/ */
private boolean suppressCameraMovements; private boolean suppressCameraMovements;
private final Set<Marker> markers; private final List<Marker> markers;
private final GuiReplayOverlay overlay; private final GuiReplayOverlay overlay;
@@ -81,7 +78,7 @@ public class ReplayHandler {
MinecraftForge.EVENT_BUS.post(new ReplayOpenEvent.Pre(this)); MinecraftForge.EVENT_BUS.post(new ReplayOpenEvent.Pre(this));
markers = new HashSet<>(replayFile.getMarkers().or(Collections.<Marker>emptySet())); markers = new ArrayList<>(replayFile.getMarkers().or(Collections.emptySet()));
replaySender = new ReplaySender(this, replayFile, asyncMode); replaySender = new ReplaySender(this, replayFile, asyncMode);
@@ -188,9 +185,9 @@ public class ReplayHandler {
/** /**
* Returns all markers. * Returns all markers.
* When changed, {@link #saveMarkers()} should be called to save the changes. * When changed, {@link #saveMarkers()} should be called to save the changes.
* @return Set of markers * @return Collection of markers in no particular order
*/ */
public Set<Marker> getMarkers() { public Collection<Marker> getMarkers() {
return markers; return markers;
} }
@@ -199,7 +196,7 @@ public class ReplayHandler {
*/ */
public void saveMarkers() { public void saveMarkers() {
try { try {
replayFile.writeMarkers(markers); replayFile.writeMarkers(new HashSet<>(markers));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -10,6 +10,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.settings.KeyBinding; import net.minecraft.client.settings.KeyBinding;
@@ -237,6 +238,14 @@ public class CameraEntity extends EntityPlayerSP {
return ReplayModReplay.instance.getReplayHandler().isCameraView(); // Make sure we're treated as spectator return ReplayModReplay.instance.getReplayHandler().isCameraView(); // Make sure we're treated as spectator
} }
@Override
public boolean shouldRenderInPass(int pass) {
// Never render the camera
// This is necessary to hide the player head in third person mode and to not
// cause any unwanted shadows when rendering with shaders.
return false;
}
@Override @Override
public ResourceLocation getLocationSkin() { public ResourceLocation getLocationSkin() {
Entity view = mc.getRenderViewEntity(); Entity view = mc.getRenderViewEntity();
@@ -246,6 +255,15 @@ public class CameraEntity extends EntityPlayerSP {
return super.getLocationSkin(); return super.getLocationSkin();
} }
@Override
public String getSkinType() {
Entity view = mc.getRenderViewEntity();
if (view != this && view instanceof AbstractClientPlayer) {
return ((AbstractClientPlayer) view).getSkinType();
}
return super.getSkinType();
}
@Override @Override
public float getSwingProgress(float renderPartialTicks) { public float getSwingProgress(float renderPartialTicks) {
Entity view = mc.getRenderViewEntity(); Entity view = mc.getRenderViewEntity();

View File

@@ -5,14 +5,17 @@ import com.replaymod.replay.ReplayHandler;
import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.element.*; import de.johni0702.minecraft.gui.element.*;
import de.johni0702.minecraft.gui.function.Typeable;
import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.data.Marker;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.ReadablePoint;
public class GuiEditMarkerPopup extends AbstractGuiPopup<GuiEditMarkerPopup> { public class GuiEditMarkerPopup extends AbstractGuiPopup<GuiEditMarkerPopup> implements Typeable {
private final ReplayHandler replayHandler; private final ReplayHandler replayHandler;
private final Marker marker; private final Marker marker;
@@ -107,4 +110,13 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup<GuiEditMarkerPopup> {
protected GuiEditMarkerPopup getThis() { protected GuiEditMarkerPopup getThis() {
return this; return this;
} }
@Override
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
if (keyCode == Keyboard.KEY_ESCAPE) {
cancelButton.onClick();
return true;
}
return false;
}
} }

View File

@@ -133,6 +133,8 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline<GuiMarkerTimeline> im
replayHandler.doJump(marker.getTime(), false); replayHandler.doJump(marker.getTime(), false);
} }
return true; return true;
} else {
selectedMarker = null;
} }
return super.mouseClick(position, button); return super.mouseClick(position, button);
} }

View File

@@ -7,8 +7,10 @@ import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.RenderInfo; import de.johni0702.minecraft.gui.RenderInfo;
import de.johni0702.minecraft.gui.container.AbstractGuiOverlay; import de.johni0702.minecraft.gui.container.AbstractGuiOverlay;
import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.element.GuiElement;
import de.johni0702.minecraft.gui.element.GuiSlider; import de.johni0702.minecraft.gui.element.GuiSlider;
import de.johni0702.minecraft.gui.element.GuiTexturedButton; import de.johni0702.minecraft.gui.element.GuiTexturedButton;
import de.johni0702.minecraft.gui.element.GuiTooltip;
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline; import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
@@ -27,8 +29,20 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
public final GuiPanel topPanel = new GuiPanel(this) public final GuiPanel topPanel = new GuiPanel(this)
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.LEFT).setSpacing(5)); .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.LEFT).setSpacing(5));
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (getTextureNormal().getY() == 0) { // Play button
tooltip.setI18nText("replaymod.gui.ingame.menu.unpause");
} else { // Pause button
tooltip.setI18nText("replaymod.gui.ingame.menu.pause");
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiSlider speedSlider = new GuiSlider().setSize(100, 20).setSteps(37); // 0.0 is not included public final GuiSlider speedSlider = new GuiSlider().setSize(100, 20).setSteps(37); // 0.0 is not included
public final GuiMarkerTimeline timeline; public final GuiMarkerTimeline timeline;

View File

@@ -101,6 +101,8 @@ public class GuiReplayViewer extends GuiScreen {
}).onSelectionDoubleClicked(() -> { }).onSelectionDoubleClicked(() -> {
if (this.loadButton.isEnabled()) { if (this.loadButton.isEnabled()) {
this.loadButton.onClick(); this.loadButton.onClick();
// Disable load button to prevent the player from opening the replay twice at the same time
this.loadButton.setDisabled();
} }
}).setDrawShadow(true).setDrawSlider(true); }).setDrawShadow(true).setDrawSlider(true);

View File

@@ -13,16 +13,20 @@ import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel; import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.GuiNumberField; import de.johni0702.minecraft.gui.element.GuiNumberField;
import de.johni0702.minecraft.gui.element.IGuiLabel; import de.johni0702.minecraft.gui.element.IGuiLabel;
import de.johni0702.minecraft.gui.function.Typeable;
import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import de.johni0702.minecraft.gui.utils.Consumer;
import org.apache.commons.lang3.tuple.Triple; import org.apache.commons.lang3.tuple.Triple;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.ReadablePoint;
import static de.johni0702.minecraft.gui.utils.Utils.link; import static de.johni0702.minecraft.gui.utils.Utils.link;
public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends AbstractGuiPopup<T> { public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends AbstractGuiPopup<T> implements Typeable {
private static GuiNumberField newGuiNumberField() { private static GuiNumberField newGuiNumberField() {
return new GuiNumberField().setPrecision(0).setValidateOnFocusChange(true); return new GuiNumberField().setPrecision(0).setValidateOnFocusChange(true);
} }
@@ -68,9 +72,10 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
this.path = path; this.path = path;
long time = keyframe.getTime(); long time = keyframe.getTime();
timeMinField.setValue(time / 1000 / 60); Consumer<String> updateSaveButtonState = s -> saveButton.setEnabled(canSave());
timeSecField.setValue(time / 1000 % 60); timeMinField.setValue(time / 1000 / 60).onTextChanged(updateSaveButtonState);
timeMSecField.setValue(time % 1000); timeSecField.setValue(time / 1000 % 60).onTextChanged(updateSaveButtonState);
timeMSecField.setValue(time % 1000).onTextChanged(updateSaveButtonState);
title.setI18nText("replaymod.gui.editkeyframe.title." + type); title.setI18nText("replaymod.gui.editkeyframe.title." + type);
saveButton.onClick(() -> { saveButton.onClick(() -> {
@@ -84,6 +89,23 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
}); });
} }
private boolean canSave() {
long newTime = (timeMinField.getInteger() * 60 + timeSecField.getInteger()) * 1000 + timeMSecField.getInteger();
if (newTime != keyframe.getTime() && path.getKeyframe(newTime) != null) {
return false;
}
return true;
}
@Override
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
if (keyCode == Keyboard.KEY_ESCAPE) {
cancelButton.onClick();
return true;
}
return false;
}
@Override @Override
public void open() { public void open() {
super.open(); super.open();

View File

@@ -121,10 +121,31 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
|| !segment.getInterpolator().getKeyframeProperties().contains(SpectatorProperty.PROPERTY)) { || !segment.getInterpolator().getKeyframeProperties().contains(SpectatorProperty.PROPERTY)) {
continue; // Not a spectator segment continue; // Not a spectator segment
} }
drawQuadOnSegment(renderer, visibleWidth, segment, BORDER_TOP + 1, 0xFF0088FF);
}
// Draw red quads on time path segments that would require time going backwards
for (PathSegment segment : mod.getCurrentTimeline().getPaths().get(GuiPathing.TIME_PATH).getSegments()) {
long startTimestamp = segment.getStartKeyframe().getValue(TimestampProperty.PROPERTY).orElseThrow(IllegalStateException::new);
long endTimestamp = segment.getEndKeyframe().getValue(TimestampProperty.PROPERTY).orElseThrow(IllegalStateException::new);
if (endTimestamp >= startTimestamp) {
continue; // All is fine, time is not moving backwards
}
drawQuadOnSegment(renderer, visibleWidth, segment, BORDER_TOP + KEYFRAME_SIZE + 1, 0xFFFF0000);
}
super.drawTimelineCursor(renderer, size);
}
private void drawQuadOnSegment(GuiRenderer renderer, int visibleWidth, PathSegment segment, int y, int color) {
int startTime = getOffset();
int visibleTime = (int) (getZoom() * getLength());
int endTime = getOffset() + visibleTime;
long startFrameTime = segment.getStartKeyframe().getTime(); long startFrameTime = segment.getStartKeyframe().getTime();
long endFrameTime = segment.getEndKeyframe().getTime(); long endFrameTime = segment.getEndKeyframe().getTime();
if (startFrameTime >= endTime || endFrameTime <= startTime) { if (startFrameTime >= endTime || endFrameTime <= startTime) {
continue; // Segment out of display range return; // Segment out of display range
} }
double relativeStart = startFrameTime - startTime; double relativeStart = startFrameTime - startTime;
@@ -132,13 +153,10 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
int startX = BORDER_LEFT + Math.max(0, (int) (relativeStart / visibleTime * visibleWidth) + KEYFRAME_SIZE / 2 + 1); int startX = BORDER_LEFT + Math.max(0, (int) (relativeStart / visibleTime * visibleWidth) + KEYFRAME_SIZE / 2 + 1);
int endX = BORDER_LEFT + Math.min(visibleWidth, (int) (relativeEnd / visibleTime * visibleWidth) - KEYFRAME_SIZE / 2); int endX = BORDER_LEFT + Math.min(visibleWidth, (int) (relativeEnd / visibleTime * visibleWidth) - KEYFRAME_SIZE / 2);
if (startX < endX) { if (startX < endX) {
renderer.drawRect(startX + 1, BORDER_TOP + 1, endX - startX - 2, KEYFRAME_SIZE - 2, 0xFF0088FF); renderer.drawRect(startX + 1, y, endX - startX - 2, KEYFRAME_SIZE - 2, color);
} }
} }
super.drawTimelineCursor(renderer, size);
}
/** /**
* Returns the keyframe at the specified position. * Returns the keyframe at the specified position.
* @param position The raw position * @param position The raw position
@@ -243,6 +261,15 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
@Override @Override
public boolean mouseDrag(ReadablePoint position, int button, long timeSinceLastCall) { public boolean mouseDrag(ReadablePoint position, int button, long timeSinceLastCall) {
if (!dragging) { if (!dragging) {
if (button == 0) {
// Left click, the user might try to move the cursor by clicking and holding
int time = getTimeAt(position.getX(), position.getY());
if (time != -1) {
// and they are still on the timeline, so update the time appropriately
setCursorPosition(time);
return true;
}
}
return false; return false;
} }

View File

@@ -38,6 +38,7 @@ import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@@ -69,22 +70,69 @@ public class GuiPathing {
private static final Logger logger = LogManager.getLogger(); private static final Logger logger = LogManager.getLogger();
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (player.isActive()) {
tooltip.setI18nText("replaymod.gui.ingame.menu.pausepath");
} else if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) {
tooltip.setI18nText("replaymod.gui.ingame.menu.playpathfromstart");
} else {
tooltip.setI18nText("replaymod.gui.ingame.menu.playpath");
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() { public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() {
@Override @Override
public void run() { public void run() {
preparePathsForPlayback(); if (!preparePathsForPlayback()) return;
new GuiRenderSettings(replayHandler, mod.getCurrentTimeline()).display(); new GuiRenderSettings(replayHandler, mod.getCurrentTimeline()).display();
} }
}).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0); }).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0)
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.renderpath"));
public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (getTextureNormal().getY() == 40) { // Add keyframe
if (getTextureNormal().getX() == 0) { // Position
tooltip.setI18nText("replaymod.gui.ingame.menu.addposkeyframe");
} else { // Spectator
tooltip.setI18nText("replaymod.gui.ingame.menu.addspeckeyframe");
}
} else { // Remove keyframe
if (getTextureNormal().getX() == 0) { // Position
tooltip.setI18nText("replaymod.gui.ingame.menu.removeposkeyframe");
} else { // Spectator
tooltip.setI18nText("replaymod.gui.ingame.menu.removespeckeyframe");
}
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiTexturedButton timeKeyframeButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton timeKeyframeButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (getTextureNormal().getY() == 80) { // Add time keyframe
tooltip.setI18nText("replaymod.gui.ingame.menu.addtimekeyframe");
} else { // Remove time keyframe
tooltip.setI18nText("replaymod.gui.ingame.menu.removetimekeyframe");
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(this){ public final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(this){
@Override @Override
@@ -113,14 +161,16 @@ public class GuiPathing {
public void run() { public void run() {
zoomTimeline(2d / 3d); zoomTimeline(2d / 3d);
} }
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20); }).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20)
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomin"));
public final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() { public final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() {
@Override @Override
public void run() { public void run() {
zoomTimeline(3d / 2d); zoomTimeline(3d / 2d);
} }
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30); }).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30)
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomout"));
public final GuiPanel zoomButtonPanel = new GuiPanel() public final GuiPanel zoomButtonPanel = new GuiPanel()
.setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(2)) .setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(2))
@@ -231,10 +281,12 @@ public class GuiPathing {
Timeline timeline = mod.getCurrentTimeline(); Timeline timeline = mod.getCurrentTimeline();
Path timePath = timeline.getPaths().get(TIME_PATH); Path timePath = timeline.getPaths().get(TIME_PATH);
preparePathsForPlayback(); if (!preparePathsForPlayback()) return;
timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)); timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT));
ListenableFuture<Void> future = player.start(timeline); // Start from cursor time unless the control key is pressed (then start from beginning)
int startTime = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)? 0 : GuiPathing.this.timeline.getCursorPosition();
ListenableFuture<Void> future = player.start(timeline, startTime);
overlay.setCloseable(false); overlay.setCloseable(false);
overlay.setMouseVisible(true); overlay.setMouseVisible(true);
Futures.addCallback(future, new FutureCallback<Void>() { Futures.addCallback(future, new FutureCallback<Void>() {
@@ -403,6 +455,15 @@ public class GuiPathing {
}); });
}); });
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
if (!overlay.isVisible()) {
return;
}
if (mod.getSelectedKeyframe() != null) {
updateKeyframe(mod.getSelectedKeyframe().getValue(TimestampProperty.PROPERTY).isPresent());
}
});
// Start loading entity tracker // Start loading entity tracker
entityTrackerFuture = SettableFuture.create(); entityTrackerFuture = SettableFuture.create();
new Thread(() -> { new Thread(() -> {
@@ -429,10 +490,27 @@ public class GuiPathing {
}).start(); }).start();
} }
private void preparePathsForPlayback() { private boolean preparePathsForPlayback() {
Timeline timeline = mod.getCurrentTimeline(); Timeline timeline = mod.getCurrentTimeline();
timeline.getPaths().get(TIME_PATH).updateAll(); timeline.getPaths().get(TIME_PATH).updateAll();
timeline.getPaths().get(POSITION_PATH).updateAll(); timeline.getPaths().get(POSITION_PATH).updateAll();
// Make sure time keyframes's values are monotonically increasing
int lastTime = 0;
for (Keyframe keyframe : timeline.getPaths().get(TIME_PATH).getKeyframes()) {
int time = keyframe.getValue(TimestampProperty.PROPERTY).orElseThrow(IllegalStateException::new);
if (time < lastTime) {
// We are going backwards in time
GuiInfoPopup.open(replayHandler.getOverlay(),
"replaymod.error.negativetime1",
"replaymod.error.negativetime2",
"replaymod.error.negativetime3");
return false;
}
lastTime = time;
}
return true;
} }
public void zoomTimeline(double factor) { public void zoomTimeline(double factor) {

View File

@@ -485,6 +485,9 @@ replaymod.gui.objects=Custom Objects
#Errors #Errors
replaymod.error.unknownrestriction1=This replay cannot be played with your current version. replaymod.error.unknownrestriction1=This replay cannot be played with your current version.
replaymod.error.unknownrestriction2=It tried to enforce %s which is unknown. replaymod.error.unknownrestriction2=It tried to enforce %s which is unknown.
replaymod.error.negativetime1=Some of your time keyframes are out of order.
replaymod.error.negativetime2=Going backwards in time is not supported.
replaymod.error.negativetime3=The invalid parts are marked in red.
#Replay Mod Incompatibility Warning #Replay Mod Incompatibility Warning
replaymod.gui.modwarning.title=Incompatibility detected replaymod.gui.modwarning.title=Incompatibility detected
@@ -497,3 +500,13 @@ replaymod.gui.modwarning.missing=Missing Mods
replaymod.gui.modwarning.version=Different Mod Versions replaymod.gui.modwarning.version=Different Mod Versions
replaymod.gui.modwarning.version.expected=Expected replaymod.gui.modwarning.version.expected=Expected
replaymod.gui.modwarning.version.found=Found replaymod.gui.modwarning.version.found=Found
#OpenEye
replaymod.gui.offeropeneye1=Do you want to install OpenEye?
replaymod.gui.offeropeneye2=OpenEye is a mod that collects anonymous information
replaymod.gui.offeropeneye3=about the mods being run and any crashes that happen.
replaymod.gui.offeropeneye4=
replaymod.gui.offeropeneye5=OpenEye can be disabled at any time and is completely optional.
replaymod.gui.offeropeneye6=More information is available at https://openeye.openmods.info/faq
replaymod.gui.offeropeneye7=Clicking Yes will download OpenEye and place it in your mods folder.
replaymod.gui.offeropeneye8=It will then be active the next time you start Minecraft.

View File

@@ -48,7 +48,7 @@
"parent": "replaymod", "parent": "replaymod",
"screenshots": [], "screenshots": [],
"dependencies": [] "dependencies": []
}/*, },
{ {
"modid": "replaymod-online", "modid": "replaymod-online",
"name": "Replay Mod - Online", "name": "Replay Mod - Online",
@@ -116,5 +116,22 @@
"parent": "replaymod", "parent": "replaymod",
"screenshots": [], "screenshots": [],
"dependencies": [] "dependencies": []
}*/ },
{
"modid": "replaymod-compat",
"name": "Replay Mod - Compatibility",
"description": "Compatibility Module of the ReplayMod - Adds compatibility with other mods",
"version": "${version}",
"mcversion": "${mcversion}",
"url": "https://replaymod.com",
"updateUrl": "https://replaymod.com/download",
"authorList": [
"CrushedPixel",
"johni0702"
],
"logoFile": "replaymod_logo.png",
"parent": "replaymod",
"screenshots": [],
"dependencies": []
}
] ]

View File

@@ -0,0 +1,12 @@
{
"required": false,
"package": "com.replaymod.compat.shaders.mixin",
"mixins": [],
"server": [],
"client": [
"MixinShaderEntityRenderer",
"MixinShaderRenderChunk",
"MixinShaderRenderGlobal"
],
"refmap": "mixins.replaymod.refmap.json"
}

View File

@@ -6,7 +6,6 @@
"MixinParticleManager", "MixinParticleManager",
"MixinRender", "MixinRender",
"MixinRenderGlobal", "MixinRenderGlobal",
"MixinRenderLiving",
"MixinRenderManager" "MixinRenderManager"
], ],
"server": [], "server": [],