Compare commits

..

39 Commits

Author SHA1 Message Date
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
0ba5b0aaad Merge branch '1.8-shaders' into 1.8 2016-11-10 21:22:08 +01:00
johni0702
0db1b9ef5a Fix opening a replay multiple times by quickly double clicking (fixes #25) 2016-11-10 20:52:07 +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
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
c93a3e384c Open Replay on double click (fixes #9) 2016-09-23 21:51:22 +02:00
johni0702
8bb5d11cb4 Update ReplayStudio
Fix wrap around of interpolated property parts (fixes #4)
2016-09-20 13:38:37 +02:00
johni0702
f3142a714c Pause replay after path playback (fixes #5) 2016-09-19 18:01:01 +02:00
johni0702
3abaa1bcbb Cleanup deleted corrupted replays after two days (fixes #1) 2016-09-19 17:49:30 +02:00
johni0702
4b47f2cbf8 Add compatibility checking to ReplayViewer and ReplayCenter (fixes #7) 2016-09-19 17:24:47 +02:00
johni0702
ada460c384 Deselect keyframe when left clicking empty space on timeline (fixes #6) 2016-09-18 13:20:28 +02:00
johni0702
ad62060368 Apply render settings only when their checkbox is enabled (fixes #3) 2016-09-18 12:53:29 +02:00
johni0702
82246a5f48 Deselect Keyframe when timeline is changed (fixes #2) 2016-09-18 11:37:36 +02:00
42 changed files with 877 additions and 194 deletions

View File

@@ -1,3 +1,5 @@
import groovy.json.JsonOutput
import java.util.zip.ZipInputStream
buildscript {
@@ -195,4 +197,85 @@ setupDecompWorkspace.dependsOn copySrg
setupDevWorkspace.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'

2
jGui

Submodule jGui updated: aa68206b5e...163cfe2f8a

View File

@@ -0,0 +1,23 @@
package com.replaymod.compat;
import com.replaymod.compat.shaders.ShaderBeginRender;
import com.replaymod.core.ReplayMod;
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, 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 = FMLCommonHandler.instance().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 {
MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.recording.replaymod.json");
MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.render.replaymod.json");
MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.replay.replaymod.json");
MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.compat.shaders.replaymod.json");
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
if (codeSource != null) {

View File

@@ -35,6 +35,7 @@ import java.util.Queue;
@Mod(modid = ReplayMod.MOD_ID,
useMetadata = true,
updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json",
guiFactory = "com.replaymod.core.gui.GuiFactory")
public class ReplayMod {
@@ -268,6 +269,22 @@ public class ReplayMod {
}
runLater(() -> {
// Cleanup deleted corrupted replays
try {
File[] files = getReplayFolder().listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory() && file.getName().endsWith(".mcpr.del")) {
if (file.lastModified() + 2 * 24 * 60 * 60 * 1000 < System.currentTimeMillis()) {
FileUtils.deleteDirectory(file);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
// Restore corrupted replays
try {
File[] files = getReplayFolder().listFiles();

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,
HotkeyButtons.class,
LocalizationExtra.class,
VersionChecker.class
OpenEyeExtra.class
);
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.gui instanceof GuiMainMenu)) {
return;
}
int width = Math.max(100, event.gui.width / 2 - 100 - 10);
@SuppressWarnings("unchecked") List<String> lines =
event.gui.mc.fontRendererObj.listFormattedStringToWidth(I18n.format("replaymod.gui.outdated"), width);
int maxLineWidth = 0;
for(String line : lines) {
int lineWidth = event.gui.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.gui.mc.fontRendererObj.drawStringWithShadow(line, 5, 80 + (i * 10), Color.WHITE.getRGB());
i++;
}
}
}

View File

@@ -20,6 +20,7 @@ import com.replaymod.online.api.replay.pagination.FavoritedFilePagination;
import com.replaymod.online.api.replay.pagination.Pagination;
import com.replaymod.online.api.replay.pagination.SearchPagination;
import com.replaymod.replaystudio.replay.ReplayMetaData;
import com.replaymod.replaystudio.studio.ReplayStudio;
import de.johni0702.minecraft.gui.container.AbstractGuiContainer;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
@@ -115,6 +116,9 @@ public class GuiReplayCenter extends GuiScreen {
boolean disliked = dislikedReplays.contains(replayId);
loadButton.setI18nLabel(selected.downloaded ? "replaymod.gui.load" : "replaymod.gui.download");
if (selected.incompatible) {
loadButton.setDisabled();
}
favoriteButton.setI18nLabel("replaymod.gui.center." + (favorited ? "unfavorite" : "favorite"));
// Only allow button usage for either unfavorite or favorite after they've actually downloaded it
@@ -339,6 +343,7 @@ public class GuiReplayCenter extends GuiScreen {
public final GuiLabel author = new GuiLabel();
public final GuiLabel date = new GuiLabel().setColor(Colors.LIGHT_GRAY);
public final GuiLabel server = new GuiLabel().setColor(Colors.LIGHT_GRAY);
public final GuiLabel version = new GuiLabel().setColor(Colors.RED);
public final GuiLabel category = new GuiLabel().setColor(Colors.GREY);
public final GuiPanel stats = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(3));
public final GuiLabel favorites = new GuiLabel(stats).setColor(Colors.ORANGE);
@@ -353,9 +358,10 @@ public class GuiReplayCenter extends GuiScreen {
pos(category, 0, y(server) + height(server) + 3);
pos(date, width - width(date), y(author));
pos(version, width - width(version), y(server));
pos(stats, width - width(stats), y(category));
}
}).addElements(null, name, author, date, server, category, stats);
}).addElements(null, name, author, date, server, version, category, stats);
public final GuiImage thumbnail;
public final GuiLabel duration = new GuiLabel();
public final GuiPanel durationPanel = new GuiPanel().setBackgroundColor(Colors.HALF_TRANSPARENT)
@@ -389,6 +395,7 @@ public class GuiReplayCenter extends GuiScreen {
private final long dateMillis;
private final int sortId;
private final boolean downloaded;
private final boolean incompatible;
public GuiReplayEntry(FileInfo fileInfo, BufferedImage thumbImage, int sortId, boolean downloaded) {
this.fileInfo = fileInfo;
@@ -404,6 +411,10 @@ public class GuiReplayCenter extends GuiScreen {
} else {
server.setText(metaData.getServerName());
}
incompatible = !new ReplayStudio().isCompatible(fileInfo.getMetadata().getFileFormatVersion());
if (incompatible) {
version.setText("Minecraft " + fileInfo.getMetadata().getMcVersion());
}
dateMillis = metaData.getDate();
date.setText(new SimpleDateFormat().format(new Date(dateMillis)));
if (thumbImage == null) {

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.GuiToggleButton;
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.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.client.resources.I18n;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.ReadablePoint;
import java.util.ArrayList;
import java.util.List;
public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearch> {
public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearch> implements Typeable {
private final GuiReplayCenter replayCenter;
private final ApiClient apiClient;
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() {
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 ReplayHandler replayHandler;
private Timeline timeline;
protected long startOffset;
private long lastTime;
private long lastTimestamp;
private ListenableFuture<Void> future;
@@ -33,6 +34,11 @@ public abstract class AbstractTimelinePlayer {
this.replayHandler = replayHandler;
}
public ListenableFuture<Void> start(Timeline timeline, long from) {
startOffset = from;
return start(timeline);
}
public ListenableFuture<Void> start(Timeline timeline) {
this.timeline = timeline;
@@ -79,6 +85,7 @@ public abstract class AbstractTimelinePlayer {
public void onTick(ReplayTimer.UpdatedEvent event) {
if (future.isDone()) {
mc.timer = ((ReplayTimer) mc.timer).getWrapped();
replayHandler.getReplaySender().setReplaySpeed(0);
replayHandler.getReplaySender().setAsyncMode(true);
FMLCommonHandler.instance().bus().unregister(this);
return;

View File

@@ -46,6 +46,6 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer {
@Override
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;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.utils.Restrictions;
import com.replaymod.recording.handler.ConnectionEventHandler;
import com.replaymod.recording.packet.PacketListener;
import net.minecraftforge.fml.common.FMLCommonHandler;
@@ -8,6 +9,7 @@ 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;
@@ -44,5 +46,7 @@ public class ReplayModRecording {
public void init(FMLInitializationEvent event) {
EventBus bus = FMLCommonHandler.instance().bus();
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.CrashReportCategory;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.lwjgl.util.ReadableDimension;
@@ -34,6 +35,7 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
this.settings = settings;
File outputFolder = settings.getOutputFile().getParentFile();
FileUtils.forceMkdir(outputFolder);
String fileName = settings.getOutputFile().getName();
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.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;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
import java.io.IOException;
@@ -31,6 +31,8 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
private final BooleanState[] previousStates = new BooleanState[3];
private final BooleanState previousFogState;
private final Minecraft mc = Minecraft.getMinecraft();
public ODSFrameCapturer(WorldRenderer worldRenderer, final RenderInfo renderInfo, int frameSize) {
RenderInfo fakeInfo = new RenderInfo() {
private int call;
@@ -143,6 +145,8 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
@Override
protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) {
resize(getFrameWidth(), getFrameHeight());
pushMatrix();
frameBuffer().bindFramebuffer(true);
@@ -150,7 +154,7 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
enableTexture2D();
directionVariable.set(captureData.ordinal());
worldRenderer.renderWorld(new Dimension(getFrameWidth(), getFrameHeight()), partialTicks, null);
worldRenderer.renderWorld(partialTicks, null);
frameBuffer().unbindFramebuffer();
popMatrix();

View File

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

View File

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

View File

@@ -91,7 +91,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
}).setSize(122, 20).setSteps(110);
public final GuiPanel videoResolutionPanel = new GuiPanel()
.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 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()),
frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)),
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()
.setI18nLabel("replaymod.gui.rendersettings.nametags");
@@ -186,7 +186,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
// Closing this GUI ensures that settings are saved
getMinecraft().displayGuiScreen(null);
try {
VideoRenderer videoRenderer = new VideoRenderer(save(true), replayHandler, timeline);
VideoRenderer videoRenderer = new VideoRenderer(save(false), replayHandler, timeline);
videoRenderer.renderVideo();
} catch (IOException e) {
e.printStackTrace();
@@ -284,6 +284,15 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
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
switch (renderMethodDropdown.getSelectedValue()) {
case CUBIC:
@@ -378,7 +387,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
updateInputs();
}
public RenderSettings save(boolean saveOutputFile) {
public RenderSettings save(boolean serialize) {
return new RenderSettings(
renderMethodDropdown.getSelectedValue(),
encodingPresetDropdown.getSelectedValue(),
@@ -386,13 +395,13 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
videoHeight.getInteger(),
frameRateSlider.getValue() + 10,
bitRateField.getInteger() << (10 * bitRateUnit.getSelected()),
saveOutputFile ? outputFile : null,
serialize ? null : outputFile,
nametagCheckbox.isChecked(),
stabilizeYaw.isChecked(),
stabilizePitch.isChecked(),
stabilizeRoll.isChecked(),
stabilizeYaw.isChecked() && (serialize || stabilizeYaw.isEnabled()),
stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()),
stabilizeRoll.isChecked() && (serialize || stabilizeRoll.isEnabled()),
chromaKeyingCheckbox.isChecked() ? chromaKeyingColor.getColor() : null,
inject360Metadata.isChecked(),
inject360Metadata.isChecked() && (serialize || inject360Metadata.isEnabled()),
exportCommand.getText(),
exportArguments.getText(),
net.minecraft.client.gui.GuiScreen.isCtrlKeyDown()
@@ -412,7 +421,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
@Override
public void close() {
RenderSettings settings = save(false);
RenderSettings settings = save(true);
String json = new Gson().toJson(settings);
Configuration config = ReplayModRender.instance.getConfiguration();
getConfigProperty(config).set(json);

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.HorizontalLayout;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.util.Dimension;
@@ -23,8 +22,6 @@ import org.lwjgl.util.ReadablePoint;
import java.nio.ByteBuffer;
import static net.minecraft.client.renderer.GlStateManager.bindTexture;
public class GuiVideoRenderer extends GuiScreen {
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();
if (previewTexture == null) {
previewTexture = new DynamicTexture(videoWidth, videoHeight) {
@Override
public void updateDynamicTexture() {
bindTexture(getGlTextureId());
TextureUtil.uploadTextureSub(0, getTextureData(), videoWidth, videoHeight, 0, 0, true, false, false);
}
};
previewTexture = new DynamicTexture(videoWidth, videoHeight);
}
if (previewTextureDirty) {
@@ -273,7 +264,10 @@ public class GuiVideoRenderer extends GuiScreen {
buffer.mark();
synchronized (this) {
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);
}
previewTextureDirty = true;

View File

@@ -5,14 +5,11 @@ import com.replaymod.render.capturer.CaptureData;
import com.replaymod.render.capturer.WorldRenderer;
import lombok.Getter;
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 org.lwjgl.util.ReadableDimension;
import java.io.IOException;
import static net.minecraft.client.renderer.GlStateManager.*;
public class EntityRendererHandler implements WorldRenderer {
public final Minecraft mc = Minecraft.getMinecraft();
@@ -29,27 +26,10 @@ public class EntityRendererHandler implements WorldRenderer {
((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
public void renderWorld(ReadableDimension displaySize, final float partialTicks, CaptureData data) {
public void renderWorld(final float partialTicks, CaptureData 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) {
@@ -57,9 +37,9 @@ public class EntityRendererHandler implements WorldRenderer {
mc.entityRenderer.updateLightmap(partialTicks);
enableDepth();
enableAlpha();
alphaFunc(516, 0.5F);
GlStateManager.enableDepth();
GlStateManager.enableAlpha();
GlStateManager.alphaFunc(516, 0.5F);
mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano);
@@ -76,13 +56,6 @@ public class EntityRendererHandler implements WorldRenderer {
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 {
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.GlStateManager;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.entity.Entity;
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"))
private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) {
if (replayModRender_handler != null) {

View File

@@ -39,6 +39,8 @@ public abstract class MixinRenderGlobal {
setupTerrain(viewEntity, partialTicks, camera, replayModRender_hook.nextFrameId(), playerSpectator);
} while (displayListEntitiesDirty);
displayListEntitiesDirty = true;
replayModRender_passThroughSetupTerrain = false;
ci.cancel();
}

View File

@@ -20,6 +20,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.SoundCategory;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.util.Timer;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
@@ -62,6 +63,9 @@ public class VideoRenderer implements RenderInfo {
private boolean paused;
private boolean cancelled;
private Framebuffer guiFramebuffer;
private int displayWidth, displayHeight;
public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException {
this.settings = settings;
this.replayHandler = replayHandler;
@@ -130,6 +134,13 @@ public class VideoRenderer implements RenderInfo {
@Override
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) {
drawGui();
}
@@ -142,6 +153,10 @@ public class VideoRenderer implements RenderInfo {
tick();
}
// change Minecraft's display size back
mc.displayWidth = displayWidthBefore;
mc.displayHeight = displayHeightBefore;
framesDone++;
return mc.timer.renderPartialTicks;
}
@@ -192,10 +207,15 @@ public class VideoRenderer implements RenderInfo {
totalFrames = (int) (duration*fps/1000);
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
updateDisplaySize();
ScaledResolution scaled = new ScaledResolution(mc, displayWidth, displayHeight);
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal);
// Set up our own framebuffer to render the GUI to
guiFramebuffer = new Framebuffer(displayWidth, displayHeight, true);
}
private void finish() {
@@ -219,6 +239,9 @@ public class VideoRenderer implements RenderInfo {
ReplayMod.soundHandler.playRenderSuccessSound();
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() {
@@ -238,12 +261,22 @@ public class VideoRenderer implements RenderInfo {
public void drawGui() {
do {
// Resize the GUI framebuffer if the display size changed
if (!settings.isHighPerformance() && displaySizeChanged()) {
updateDisplaySize();
guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight);
}
pushMatrix();
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
enableTexture2D();
mc.getFramebuffer().bindFramebuffer(true);
guiFramebuffer.bindFramebuffer(true);
mc.entityRenderer.setupOverlayRendering();
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
try {
gui.toMinecraft().handleInput();
} catch (IOException e) {
@@ -252,18 +285,17 @@ public class VideoRenderer implements RenderInfo {
throw new RuntimeException(e);
}
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth;
int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1;
gui.toMinecraft().drawScreen(mouseX, mouseY, 0);
mc.getFramebuffer().unbindFramebuffer();
guiFramebuffer.unbindFramebuffer();
popMatrix();
pushMatrix();
mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight);
guiFramebuffer.framebufferRender(displayWidth, displayHeight);
popMatrix();
// if not in high performance mode, update the gui size if screen size changed
// otherwise just swap the progress gui to screen
if (settings.isHighPerformance()) {
@@ -285,6 +317,15 @@ public class VideoRenderer implements RenderInfo {
} while (paused);
}
private boolean displaySizeChanged() {
return displayWidth != Display.getWidth() || displayHeight != Display.getHeight();
}
private void updateDisplaySize() {
displayWidth = Display.getWidth();
displayHeight = Display.getHeight();
}
public int getFramesDone() {
return framesDone;
}

View File

@@ -59,7 +59,7 @@ public class ReplayHandler {
*/
private boolean suppressCameraMovements;
private final Set<Marker> markers;
private final List<Marker> markers;
private final GuiReplayOverlay overlay;
@@ -78,7 +78,7 @@ public class ReplayHandler {
FMLCommonHandler.instance().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);
@@ -185,9 +185,9 @@ public class ReplayHandler {
/**
* Returns all markers.
* 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;
}
@@ -196,7 +196,7 @@ public class ReplayHandler {
*/
public void saveMarkers() {
try {
replayFile.writeMarkers(markers);
replayFile.writeMarkers(new HashSet<>(markers));
} catch (IOException e) {
e.printStackTrace();
}

View File

@@ -10,6 +10,7 @@ import lombok.Getter;
import lombok.Setter;
import net.minecraft.block.material.Material;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.network.NetHandlerPlayClient;
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
}
@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
public ResourceLocation getLocationSkin() {
Entity view = mc.getRenderViewEntity();
@@ -246,6 +255,15 @@ public class CameraEntity extends EntityPlayerSP {
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
public float getSwingProgress(float renderPartialTicks) {
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.GuiPanel;
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.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors;
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 Marker marker;
@@ -107,4 +110,13 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup<GuiEditMarkerPopup> {
protected GuiEditMarkerPopup getThis() {
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);
}
return true;
} else {
selectedMarker = null;
}
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.container.AbstractGuiOverlay;
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.GuiTexturedButton;
import de.johni0702.minecraft.gui.element.GuiTooltip;
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
@@ -27,8 +29,20 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
public final GuiPanel topPanel = new GuiPanel(this)
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.LEFT).setSpacing(5));
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20)
.setTexture(ReplayMod.TEXTURE, TEXTURE_SIZE);
public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
@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 GuiMarkerTimeline timeline;

View File

@@ -6,7 +6,12 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.mojang.realmsclient.gui.ChatFormatting;
import com.replaymod.core.gui.GuiReplaySettings;
import com.replaymod.core.utils.Utils;
import com.replaymod.replay.ReplayModReplay;
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 de.johni0702.minecraft.gui.container.AbstractGuiContainer;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
@@ -20,11 +25,6 @@ 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 com.replaymod.replaystudio.replay.ReplayFile;
import com.replaymod.replaystudio.replay.ReplayMetaData;
import com.replaymod.replaystudio.replay.ZipReplayFile;
import com.replaymod.replaystudio.studio.ReplayStudio;
import com.replaymod.core.utils.Utils;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.Util;
@@ -54,6 +54,9 @@ public class GuiReplayViewer extends GuiScreen {
@Override
public void run() {
replayButtonPanel.forEach(IGuiButton.class).setEnabled(list.getSelected() != null);
if (list.getSelected() != null && list.getSelected().incompatible) {
loadButton.setDisabled();
}
}
}).onLoad(new Consumer<Consumer<Supplier<GuiReplayEntry>>> () {
@Override
@@ -95,6 +98,12 @@ public class GuiReplayViewer extends GuiScreen {
e.printStackTrace();
}
}
}).onSelectionDoubleClicked(() -> {
if (this.loadButton.isEnabled()) {
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);
public final GuiButton loadButton = new GuiButton().onClick(new Runnable() {
@@ -270,6 +279,7 @@ public class GuiReplayViewer extends GuiScreen {
public final GuiLabel date = new GuiLabel().setColor(Colors.LIGHT_GRAY);
public final GuiPanel infoPanel = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(2))
.addElements(null, name, server, date);
public final GuiLabel version = new GuiLabel(this).setColor(Colors.RED);
public final GuiImage thumbnail;
public final GuiLabel duration = new GuiLabel();
public final GuiPanel durationPanel = new GuiPanel().setBackgroundColor(Colors.HALF_TRANSPARENT)
@@ -287,6 +297,7 @@ public class GuiReplayViewer extends GuiScreen {
});
private final long dateMillis;
private final boolean incompatible;
public GuiReplayEntry(File file, ReplayMetaData metaData, BufferedImage thumbImage) {
this.file = file;
@@ -297,6 +308,10 @@ public class GuiReplayViewer extends GuiScreen {
} else {
server.setText(metaData.getServerName());
}
incompatible = !new ReplayStudio().isCompatible(metaData.getFileFormatVersion());
if (incompatible) {
version.setText("Minecraft " + metaData.getMcVersion());
}
dateMillis = metaData.getDate();
date.setText(new SimpleDateFormat().format(new Date(dateMillis)));
if (thumbImage == null) {
@@ -316,6 +331,7 @@ public class GuiReplayViewer extends GuiScreen {
y(durationPanel, height(thumbnail) - height(durationPanel));
pos(infoPanel, width(thumbnail) + 5, 0);
pos(version, width - width(version), 0);
}
@Override

View File

@@ -84,6 +84,9 @@ public class ReplayModSimplePathing implements PathingRegistry {
}
public void setCurrentTimeline(Timeline currentTimeline) {
if (this.currentTimeline != currentTimeline) {
selectedKeyframe = null;
}
this.currentTimeline = currentTimeline;
}

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.GuiNumberField;
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.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors;
import de.johni0702.minecraft.gui.utils.Consumer;
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;
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() {
return new GuiNumberField().setPrecision(0).setValidateOnFocusChange(true);
}
@@ -68,9 +72,10 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
this.path = path;
long time = keyframe.getTime();
timeMinField.setValue(time / 1000 / 60);
timeSecField.setValue(time / 1000 % 60);
timeMSecField.setValue(time % 1000);
Consumer<String> updateSaveButtonState = s -> saveButton.setEnabled(canSave());
timeMinField.setValue(time / 1000 / 60).onTextChanged(updateSaveButtonState);
timeSecField.setValue(time / 1000 % 60).onTextChanged(updateSaveButtonState);
timeMSecField.setValue(time % 1000).onTextChanged(updateSaveButtonState);
title.setI18nText("replaymod.gui.editkeyframe.title." + type);
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
public void open() {
super.open();

View File

@@ -121,24 +121,42 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
|| !segment.getInterpolator().getKeyframeProperties().contains(SpectatorProperty.PROPERTY)) {
continue; // Not a spectator segment
}
long startFrameTime = segment.getStartKeyframe().getTime();
long endFrameTime = segment.getEndKeyframe().getTime();
if (startFrameTime >= endTime || endFrameTime <= startTime) {
continue; // Segment out of display range
}
drawQuadOnSegment(renderer, visibleWidth, segment, BORDER_TOP + 1, 0xFF0088FF);
}
double relativeStart = startFrameTime - startTime;
double relativeEnd = endFrameTime - startTime;
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);
if (startX < endX) {
renderer.drawRect(startX + 1, BORDER_TOP + 1, endX - startX - 2, KEYFRAME_SIZE - 2, 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 endFrameTime = segment.getEndKeyframe().getTime();
if (startFrameTime >= endTime || endFrameTime <= startTime) {
return; // Segment out of display range
}
double relativeStart = startFrameTime - startTime;
double relativeEnd = endFrameTime - startTime;
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);
if (startX < endX) {
renderer.drawRect(startX + 1, y, endX - startX - 2, KEYFRAME_SIZE - 2, color);
}
}
/**
* Returns the keyframe at the specified position.
* @param position The raw position
@@ -208,6 +226,7 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
// Clicked on timeline but not on any keyframe
if (button == 0) { // Left click
setCursorPosition(time);
gui.getMod().setSelectedKeyframe(null);
} else if (button == 1) { // Right click
if (pathKeyframePair.getLeft() != null) {
// Apply the value of the clicked path at the clicked position
@@ -242,6 +261,15 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
@Override
public boolean mouseDrag(ReadablePoint position, int button, long timeSinceLastCall) {
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;
}

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.VerticalLayout;
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.utils.Colors;
import net.minecraft.entity.Entity;
@@ -69,22 +70,69 @@ public class GuiPathing {
private static final Logger logger = LogManager.getLogger();
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20)
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
@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() {
@Override
public void run() {
preparePathsForPlayback();
if (!preparePathsForPlayback()) return;
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)
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton() {
@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)
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
public final GuiTexturedButton timeKeyframeButton = new GuiTexturedButton() {
@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){
@Override
@@ -113,14 +161,16 @@ public class GuiPathing {
public void run() {
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() {
@Override
public void run() {
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()
.setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(2))
@@ -231,10 +281,12 @@ public class GuiPathing {
Timeline timeline = mod.getCurrentTimeline();
Path timePath = timeline.getPaths().get(TIME_PATH);
preparePathsForPlayback();
if (!preparePathsForPlayback()) return;
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.setMouseVisible(true);
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
entityTrackerFuture = SettableFuture.create();
new Thread(() -> {
@@ -429,10 +490,27 @@ public class GuiPathing {
}).start();
}
private void preparePathsForPlayback() {
private boolean preparePathsForPlayback() {
Timeline timeline = mod.getCurrentTimeline();
timeline.getPaths().get(TIME_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) {

View File

@@ -485,6 +485,9 @@ replaymod.gui.objects=Custom Objects
#Errors
replaymod.error.unknownrestriction1=This replay cannot be played with your current version.
replaymod.error.unknownrestriction2=It tried to enforce %s which is unknown.
replaymod.error.negativetime1=Some of your time keyframes are out of order.
replaymod.error.negativetime2=Going backwards in time is not supported.
replaymod.error.negativetime3=The invalid parts are marked in red.
#Replay Mod Incompatibility Warning
replaymod.gui.modwarning.title=Incompatibility detected
@@ -496,4 +499,14 @@ replaymod.gui.modwarning.id=Mod Id:
replaymod.gui.modwarning.missing=Missing Mods
replaymod.gui.modwarning.version=Different Mod Versions
replaymod.gui.modwarning.version.expected=Expected
replaymod.gui.modwarning.version.found=Found
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

@@ -116,5 +116,22 @@
"parent": "replaymod",
"screenshots": [],
"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"
}