Merge branch '1.9.4' into 1.10.2
54790d3Merge branch '1.8' into 1.9.40ba5b0aMerge branch '1.8-shaders' into 1.81658617Merge branch '1.8' into 1.9.40db1b9eFix opening a replay multiple times by quickly double clicking (fixes #25)5014edaRemove javascript comments from mcmod.infoe5eb982Add 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#renderWorld3a28c5bDraw the GuiVideoRenderer on a separate framebuffer that has the actual size of the window instead of MC's framebuffer which may have a different size636ce7bAdd compatibility for GLSL Shaders92eb11aChange video preview to be compatible with Optifine9ff767bFix spectated player having the arm model of the camera playerd82cef3Make sure video folder exists before rendering (fixes #24)cc65b26Replace custom update system with forge update checkerc8b71dcAdd tooltips to ingame buttonsb240f8aFix camera path always playing from the beginning regardless of ctrl key4805e5eFix crash when setting two keyframes to the same time via the edit keyframe gui18f8303Fix crash when moving the last keyframefcd4c07Add error popup when trying to play path with reversed time keyframes Update jGui9c605baUpdate jGui Fix handling of ESC key in popups (fixes #17)721fdc4Allow moving the keyframe timeline cursor by dragging (fixes #18)1851e97Allow deletion of selected keyframes by pressing the DELETE key (fixes #19)d547098Fix markers not being removedb2c7faaFix marker not being deselected when clicking anywhere else4e6b387Update jGui Fix popups being drawn outside of the actually visible element (fixes #21)21bbeb2Fix bitrate field always being active regardless of selected preset (fixes #20)b5a6d8bFix camera player head being visible while in third persondd0537bCenter star between resolution fields in render settings guie35bfcaIncrease spacing between rows in the render settings gui6d0cdfbUpdate jGui (fixes #15)
This commit is contained in:
23
src/main/java/com/replaymod/compat/ReplayModCompat.java
Normal file
23
src/main/java/com/replaymod/compat/ReplayModCompat.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
102
src/main/java/com/replaymod/compat/shaders/ShaderReflection.java
Normal file
102
src/main/java/com/replaymod/compat/shaders/ShaderReflection.java
Normal file
@@ -0,0 +1,102 @@
|
||||
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 {
|
||||
initFrameTimeCounter();
|
||||
|
||||
initIsShadowPass();
|
||||
|
||||
initBeginRender();
|
||||
|
||||
initChunksToUpdateForced();
|
||||
|
||||
initConfigIsShaders();
|
||||
}
|
||||
|
||||
private static void initFrameTimeCounter() {
|
||||
try {
|
||||
shaders_frameTimeCounter = Class.forName("shadersmod.client.Shaders")
|
||||
.getDeclaredField("frameTimeCounter");
|
||||
shaders_frameTimeCounter.setAccessible(true);
|
||||
} catch (ClassNotFoundException ignore) {
|
||||
// no shaders mod installed
|
||||
} catch (NoSuchFieldException e) {
|
||||
// the field wasn't found. Has it been renamed?
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initIsShadowPass() {
|
||||
try {
|
||||
shaders_isShadowPass = Class.forName("shadersmod.client.Shaders")
|
||||
.getDeclaredField("isShadowPass");
|
||||
shaders_isShadowPass.setAccessible(true);
|
||||
} catch (ClassNotFoundException ignore) {
|
||||
// no shaders mod installed
|
||||
} catch (NoSuchFieldException e) {
|
||||
// the field wasn't found. Has it been renamed?
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initBeginRender() {
|
||||
try {
|
||||
shaders_beginRender = Class.forName("shadersmod.client.Shaders")
|
||||
.getDeclaredMethod("beginRender", Minecraft.class, float.class, long.class);
|
||||
shaders_frameTimeCounter.setAccessible(true);
|
||||
} catch (ClassNotFoundException ignore) {
|
||||
// no shaders mod installed
|
||||
} catch (NoSuchMethodException e) {
|
||||
// the method wasn't found. Has it been renamed?
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initChunksToUpdateForced() {
|
||||
try {
|
||||
renderGlobal_chunksToUpdateForced = Class.forName("net.minecraft.client.renderer.RenderGlobal")
|
||||
.getDeclaredField("chunksToUpdateForced");
|
||||
renderGlobal_chunksToUpdateForced.setAccessible(true);
|
||||
} catch (ClassNotFoundException ignore) {
|
||||
// no shaders mod installed
|
||||
} catch (NoSuchFieldException e) {
|
||||
// the field wasn't found. Has it been renamed?
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initConfigIsShaders() {
|
||||
try {
|
||||
config_isShaders = Class.forName("Config")
|
||||
.getDeclaredMethod("isShaders");
|
||||
config_isShaders.setAccessible(true);
|
||||
} catch (ClassNotFoundException ignore) {
|
||||
// no shaders mod installed
|
||||
} catch (NoSuchMethodException e) {
|
||||
// the method wasn't found. Has it been renamed?
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin {
|
||||
Mixins.addConfiguration("mixins.recording.replaymod.json");
|
||||
Mixins.addConfiguration("mixins.render.replaymod.json");
|
||||
Mixins.addConfiguration("mixins.replay.replaymod.json");
|
||||
Mixins.addConfiguration("mixins.compat.shaders.replaymod.json");
|
||||
|
||||
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
|
||||
if (codeSource != null) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.util.List;
|
||||
|
||||
@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 {
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ public class ReplayModExtras {
|
||||
FullBrightness.class,
|
||||
HotkeyButtons.class,
|
||||
LocalizationExtra.class,
|
||||
OpenEyeExtra.class,
|
||||
VersionChecker.class
|
||||
OpenEyeExtra.class
|
||||
);
|
||||
|
||||
private Logger logger;
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -38,6 +38,8 @@ public abstract class MixinRenderGlobal {
|
||||
replayModRender_hook.updateChunks();
|
||||
} while (displayListEntitiesDirty);
|
||||
|
||||
displayListEntitiesDirty = true;
|
||||
|
||||
replayModRender_passThroughSetupTerrain = false;
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.replaymod.replaystudio.pathing.path.Timeline;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.Timer;
|
||||
import org.lwjgl.input.Mouse;
|
||||
@@ -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,12 @@ public class VideoRenderer implements RenderInfo {
|
||||
|
||||
totalFrames = (int) (duration*fps/1000);
|
||||
|
||||
ScaledResolution scaled = new ScaledResolution(mc);
|
||||
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
|
||||
updateDisplaySize();
|
||||
|
||||
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 +236,9 @@ public class VideoRenderer implements RenderInfo {
|
||||
new 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 +258,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);
|
||||
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
|
||||
|
||||
try {
|
||||
gui.toMinecraft().handleInput();
|
||||
} catch (IOException e) {
|
||||
@@ -252,18 +282,17 @@ public class VideoRenderer implements RenderInfo {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
ScaledResolution scaled = new ScaledResolution(mc);
|
||||
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 +314,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;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,7 @@ import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
|
||||
import org.lwjgl.opengl.Display;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
import static net.minecraft.client.renderer.GlStateManager.*;
|
||||
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
||||
@@ -62,7 +59,7 @@ public class ReplayHandler {
|
||||
*/
|
||||
private boolean suppressCameraMovements;
|
||||
|
||||
private final Set<Marker> markers;
|
||||
private final List<Marker> markers;
|
||||
|
||||
private final GuiReplayOverlay overlay;
|
||||
|
||||
@@ -81,7 +78,7 @@ public class ReplayHandler {
|
||||
|
||||
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);
|
||||
|
||||
@@ -188,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;
|
||||
}
|
||||
|
||||
@@ -199,7 +196,7 @@ public class ReplayHandler {
|
||||
*/
|
||||
public void saveMarkers() {
|
||||
try {
|
||||
replayFile.writeMarkers(markers);
|
||||
replayFile.writeMarkers(new HashSet<>(markers));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -101,6 +101,8 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
}).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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -243,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"parent": "replaymod",
|
||||
"screenshots": [],
|
||||
"dependencies": []
|
||||
}/*,
|
||||
},
|
||||
{
|
||||
"modid": "replaymod-online",
|
||||
"name": "Replay Mod - Online",
|
||||
@@ -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": []
|
||||
}
|
||||
]
|
||||
|
||||
12
src/main/resources/mixins.compat.shaders.replaymod.json
Normal file
12
src/main/resources/mixins.compat.shaders.replaymod.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"required": false,
|
||||
"package": "com.replaymod.compat.shaders.mixin",
|
||||
"mixins": [],
|
||||
"server": [],
|
||||
"client": [
|
||||
"MixinShaderEntityRenderer",
|
||||
"MixinShaderRenderChunk",
|
||||
"MixinShaderRenderGlobal"
|
||||
],
|
||||
"refmap": "mixins.replaymod.refmap.json"
|
||||
}
|
||||
Reference in New Issue
Block a user