diff --git a/README.md b/README.md index c462bf7d..f05cdaea 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ to access private fields is a good indication that this has happened). You can build the mod by running `./gradlew :build`. You can then find the final jar files in `build/libs/`. ### IntelliJ -For the initial setup run `./gradlew preshadowJar idea genIntellijRuns`. +For the initial setup run `./gradlew idea genIntellijRuns`. You also need to enable the Mixin annotation processor: 1. Go to File -> Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors 2. Tick "Enable annotation processing" @@ -20,8 +20,8 @@ You also need to enable the Mixin annotation processor: 4. Set the name to "reobfSrgFile" and the value to "$path/build/mcp-srg.srg" where you replace $path with the full path to the folder containing the gradlew file -Whenever you switch to another branch, you can either just run `./gradlew preshadowJar idea` or instead run -`./gradlew preshadowJar copySrg` and then refresh the gradle project from within IntelliJ. +Whenever you switch to another branch, you can either just run `./gradlew idea` or instead run +`./gradlew copySrg` and then refresh the gradle project from within IntelliJ. ### Eclipse diff --git a/build.gradle b/build.gradle index e50e7cce..5e402c5f 100755 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ dependencies { shade 'org.aspectj:aspectjrt:1.8.2' - shade 'com.github.replaymod:ReplayStudio:2df8d8b' + shade 'com.github.replaymod:ReplayStudio:a2168ca' testCompile 'junit:junit:4.11' } diff --git a/gradle/reprod/checksums.txt b/gradle/reprod/checksums.txt index 9138086a..16bd608c 100644 --- a/gradle/reprod/checksums.txt +++ b/gradle/reprod/checksums.txt @@ -14,8 +14,8 @@ f98b3e07c6b6a32dd6f9f70f5cef4ac9be42a9a6c850ceb8f33767da94706635 /com/github/joh 03e3dff7f9b3ac6cd3b87684fd36a2f6996a4b164a2f8fab9c22c01cb7bfb84b /com/github/johni0702/gradle-reproducible-builds-plugin/a524ada/gradle-reproducible-builds-plugin-a524ada.pom 75ccc0e24d37ed5b8916e1bcffad1e067c2b1cfa21b635287f968db5c3c1963a /com/github/jponge/lzma-java/1.3/lzma-java-1.3.jar fbcba81b21a8bc3c0761e4798fa05285444b43d681c03c9af49f9b2e1ecb0184 /com/github/jponge/lzma-java/1.3/lzma-java-1.3.pom -f38a7028bbac3f6c6b15cb40d533c96c6e8fdb54fc93d922cfa1931b25fa8c5c /com/github/replaymod/ReplayStudio/2df8d8b/ReplayStudio-2df8d8b.jar -1075c4246911339023a15bc962d6408925b959656443cad3193bbe7ca3dae5fb /com/github/replaymod/ReplayStudio/2df8d8b/ReplayStudio-2df8d8b.pom +04d7ceaf1e83006d2d93518884353a7856399669332d1174f2572e3d6ec03cd3 /com/github/replaymod/ReplayStudio/a2168ca/ReplayStudio-a2168ca.jar +0703ef825f3e3cd0943f6955fd12da923eb255ed94fe421cbb207b2dcfd61ba4 /com/github/replaymod/ReplayStudio/a2168ca/ReplayStudio-a2168ca.pom c6ebd74cd6108b8c372c95b842725ee5bd6d3303ba26cb00f18bc92f80b47956 /com/github/tony19/named-regexp/0.2.3/named-regexp-0.2.3.jar 7cdd89a892927998beab59398ef073232588f6e83e411cf4f0905833bf300487 /com/github/tony19/named-regexp/0.2.3/named-regexp-0.2.3.pom 47c625c83a8cf97b8bbdff2acde923ff8fd3174e62aabcfc5d1b86692594ffba /com/google/api-client/google-api-client/1.22.0/google-api-client-1.22.0.jar diff --git a/jGui b/jGui index 1e92ad85..c5aa3236 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 1e92ad85df9c4282d88e017e433a189a76bea433 +Subproject commit c5aa32364bafe4e48f9f84c4b57d418d941b6c2f diff --git a/src/main/java/com/replaymod/compat/ReplayModCompat.java b/src/main/java/com/replaymod/compat/ReplayModCompat.java index 9347cd2f..a5a23fea 100644 --- a/src/main/java/com/replaymod/compat/ReplayModCompat.java +++ b/src/main/java/com/replaymod/compat/ReplayModCompat.java @@ -7,16 +7,26 @@ import com.replaymod.compat.shaders.ShaderBeginRender; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.fml.common.eventhandler.EventBus; +import org.apache.logging.log4j.Logger; @Mod(modid = ReplayModCompat.MOD_ID, version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModCompat { public static final String MOD_ID = "replaymod-compat"; + public static Logger LOGGER; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent event) { + LOGGER = event.getModLog(); + } + @Mod.EventHandler public void init(FMLInitializationEvent event) { EventBus bus = FMLCommonHandler.instance().bus(); diff --git a/src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java b/src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java index 6d8b4f48..89e5926b 100644 --- a/src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java +++ b/src/main/java/com/replaymod/compat/bettersprinting/DisableBetterSprinting.java @@ -20,6 +20,8 @@ import net.minecraftforge.fml.common.versioning.VersionRange; import java.util.Collections; +import static com.replaymod.compat.ReplayModCompat.LOGGER; + /** * Old Better Sprinting versions replace the vanilla player with their own, overridden instance (replacing the camera entity). * @@ -29,7 +31,7 @@ public class DisableBetterSprinting { private static final VersionRange OLD_VERSION = VersionRange.newRange(null, Collections.singletonList(new Restriction(null, false, new DefaultArtifactVersion("2.0.0"), false))); private static final String LOGIC_CLASS_NAME = "chylex.bettersprinting.client.player.impl.LogicImplOverride"; - private static final String CONTROLLER_OVERRIDE_CLASS_NAME = LOGIC_CLASS_NAME + ".PlayerControllerMPOverride"; + private static final String CONTROLLER_OVERRIDE_CLASS_NAME = LOGIC_CLASS_NAME + "$PlayerControllerMPOverride"; public static void register() { Loader.instance().getModList().stream() @@ -39,7 +41,9 @@ public class DisableBetterSprinting { .ifPresent($_ -> MinecraftForge.EVENT_BUS.register(new DisableBetterSprinting())); } - private DisableBetterSprinting() {} + private DisableBetterSprinting() { + LOGGER.info("BetterSprinting workaround enabled"); + } private final Minecraft mc = Minecraft.getMinecraft(); private PlayerControllerMP originalController; @@ -66,6 +70,7 @@ public class DisableBetterSprinting { // Suppress this message if it's the Better Sprinting warning message for (StackTraceElement elem : Thread.currentThread().getStackTrace()) { if (LOGIC_CLASS_NAME.equals(elem.getClassName())) { + LOGGER.info("BetterSprinting warning message suppressed."); event.setCanceled(true); return; } @@ -78,6 +83,7 @@ public class DisableBetterSprinting { if (mc.playerController != null && mc.playerController.getClass().getName().equals(CONTROLLER_OVERRIDE_CLASS_NAME)) { // Someone has secretly swapped out the player controller and is about to substitute their own player entity. // This is the right time to destroy their plan. + LOGGER.info("Preventing player controller {} from being replaced by BetterSprinting with {}.", originalController, mc.playerController); mc.playerController = originalController; } } diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index cd3ee038..83a5c369 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -45,6 +45,7 @@ import java.util.List; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json", guiFactory = "com.replaymod.core.gui.GuiFactory") public class ReplayMod { diff --git a/src/main/java/com/replaymod/editor/ReplayModEditor.java b/src/main/java/com/replaymod/editor/ReplayModEditor.java index 7749f5c0..ddc9b61c 100644 --- a/src/main/java/com/replaymod/editor/ReplayModEditor.java +++ b/src/main/java/com/replaymod/editor/ReplayModEditor.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.Logger; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModEditor { public static final String MOD_ID = "replaymod-editor"; diff --git a/src/main/java/com/replaymod/extras/ReplayModExtras.java b/src/main/java/com/replaymod/extras/ReplayModExtras.java index 93ed038a..3dd7a033 100644 --- a/src/main/java/com/replaymod/extras/ReplayModExtras.java +++ b/src/main/java/com/replaymod/extras/ReplayModExtras.java @@ -1,6 +1,7 @@ package com.replaymod.extras; import com.replaymod.core.ReplayMod; +import com.replaymod.extras.advancedscreenshots.AdvancedScreenshots; import com.replaymod.extras.playeroverview.PlayerOverview; import com.replaymod.extras.urischeme.UriSchemeExtra; import com.replaymod.extras.youtube.YoutubeUpload; @@ -19,6 +20,7 @@ import java.util.Optional; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModExtras { public static final String MOD_ID = "replaymod-extras"; @@ -27,6 +29,7 @@ public class ReplayModExtras { public static ReplayModExtras instance; private static final List> builtin = Arrays.asList( + AdvancedScreenshots.class, PlayerOverview.class, UriSchemeExtra.class, YoutubeUpload.class, @@ -43,6 +46,7 @@ public class ReplayModExtras { @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { LOGGER = event.getModLog(); + ReplayMod.instance.getSettingsRegistry().register(Setting.class); } @Mod.EventHandler diff --git a/src/main/java/com/replaymod/extras/Setting.java b/src/main/java/com/replaymod/extras/Setting.java new file mode 100644 index 00000000..2f69e4c1 --- /dev/null +++ b/src/main/java/com/replaymod/extras/Setting.java @@ -0,0 +1,8 @@ +package com.replaymod.extras; + +import com.replaymod.core.SettingsRegistry; + +public final class Setting { + public static final SettingsRegistry.SettingKey SKIP_POST_SCREENSHOT_GUI = + new SettingsRegistry.SettingKeys<>("advanced", "skipPostScreenshotGui", null, false); +} diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java new file mode 100644 index 00000000..a3e7b36b --- /dev/null +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java @@ -0,0 +1,41 @@ +package com.replaymod.extras.advancedscreenshots; + +import com.replaymod.core.ReplayMod; +import com.replaymod.extras.Extra; +import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiControls; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.lwjgl.input.Keyboard; + +public class AdvancedScreenshots implements Extra { + + private ReplayMod mod; + + private final Minecraft mc = Minecraft.getMinecraft(); + + @Override + public void register(ReplayMod mod) throws Exception { + this.mod = mod; + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onDispatchKeypresses(ReplayDispatchKeypressesEvent.Pre event) { + int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() : Keyboard.getEventKey(); + + // all the conditions required to trigger a screenshot condensed in a single if statement + if (keyCode != 0 && !Keyboard.isRepeatEvent() + && (!(mc.currentScreen instanceof GuiControls) || ((GuiControls) mc.currentScreen).time <= mc.getSystemTime() - 20L) + && Keyboard.getEventKeyState() + && keyCode == mc.gameSettings.keyBindScreenshot.getKeyCode()) { + + ReplayMod.instance.runLater(() -> { + new GuiCreateScreenshot(mod).display(); + }); + + event.setCanceled(true); + } + } +} diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java new file mode 100644 index 00000000..3ef09234 --- /dev/null +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java @@ -0,0 +1,94 @@ +package com.replaymod.extras.advancedscreenshots; + +import com.replaymod.core.ReplayMod; +import com.replaymod.render.RenderSettings; +import com.replaymod.render.gui.GuiRenderSettings; +import com.replaymod.replay.ReplayModReplay; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.function.Loadable; +import de.johni0702.minecraft.gui.layout.GridLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import net.minecraft.crash.CrashReport; +import net.minecraft.util.ScreenShotHelper; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.File; +import java.util.ArrayList; + +import static com.replaymod.core.utils.Utils.error; +import static com.replaymod.render.ReplayModRender.LOGGER; + +public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable { + + private final ReplayMod mod; + + public GuiCreateScreenshot(ReplayMod mod) { + super(null, null); + + this.mod = mod; + + resetChildren(settingsList.getListPanel()).addElements(new VerticalLayout.Data(0.5), + new GuiLabel().setI18nText("replaymod.gui.advancedscreenshots.title"), mainPanel, new GuiPanel(), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.advanced"), advancedPanel, new GuiPanel()); + + resetChildren(mainPanel).addElements(new GridLayout.Data(1, 0.5), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.renderer"), renderMethodDropdown, + new GuiLabel().setI18nText("replaymod.gui.advancedscreenshots.resolution"), videoResolutionPanel, + new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton); + + resetChildren(advancedPanel).addElements(null, nametagCheckbox, new GuiPanel().setLayout( + new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15)) + .addElements(new GridLayout.Data(0, 0.5), + new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, + chromaKeyingCheckbox, chromaKeyingColor)); + + exportArguments.setText(""); // To disable any preset-based checks + buttonPanel.removeElement(queueButton); + renderButton.setI18nLabel("replaymod.gui.advancedscreenshots.create").onClick(() -> { + // Closing this GUI ensures that settings are saved + getMinecraft().displayGuiScreen(null); + + mod.runLater(() -> { + try { + RenderSettings settings = save(false); + + boolean success = new ScreenshotRenderer(settings).renderScreenshot(); + if (success) { + new GuiUploadScreenshot(ReplayModReplay.instance.getReplayHandler().getOverlay(), mod, + settings).open(); + } + + } catch (Throwable t) { + error(LOGGER, GuiCreateScreenshot.this, CrashReport.makeCrashReport(t, "Rendering video"), () -> {}); + display(); // Re-show the render settings gui and the new error popup + } + }); + }); + } + + private > T resetChildren(T container) { + new ArrayList<>(container.getChildren()).forEach(container::removeElement); + return container; + } + + @Override + public void load() { + // pause replay when opening this gui + ReplayModReplay.instance.getReplayHandler().getReplaySender().setReplaySpeed(0); + } + + @Override + protected File generateOutputFile(RenderSettings.EncodingPreset encodingPreset) { + File screenshotFolder = new File(getMinecraft().mcDataDir, "screenshots"); + return ScreenShotHelper.getTimestampedPNGFileForDirectory(screenshotFolder); + } + + @Override + protected Property getConfigProperty(Configuration configuration) { + return configuration.get("screenshotsettings", "settings", "{}", + "Last state of the screenshot settings GUI. Internal use only."); + } +} \ No newline at end of file diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java new file mode 100644 index 00000000..4c2cf396 --- /dev/null +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java @@ -0,0 +1,128 @@ +package com.replaymod.extras.advancedscreenshots; + +import com.replaymod.core.ReplayMod; +import com.replaymod.core.SettingsRegistry; +import com.replaymod.extras.Setting; +import com.replaymod.render.RenderSettings; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.element.GuiCheckbox; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; +import org.lwjgl.util.ReadableColor; + +import java.awt.*; +import java.io.IOException; +import java.net.URI; + +public class GuiUploadScreenshot extends AbstractGuiPopup { + + public final ReplayMod mod; + + public final RenderSettings renderSettings; + + public final GuiLabel successLabel = new GuiLabel() + .setI18nText("replaymod.gui.advancedscreenshots.finished.description") + .setColor(ReadableColor.BLACK); + + public final GuiLabel veerLabel = new GuiLabel() + .setI18nText("replaymod.gui.advancedscreenshots.finished.description.veer") + .setColor(ReadableColor.BLACK); + + public final GuiButton veerUploadButton = new GuiButton() + .setSize(150, 20) + .setI18nLabel("replaymod.gui.advancedscreenshots.finished.upload.veer"); + + public final GuiButton showOnDiskButton = new GuiButton() + .setSize(150, 20) + .setI18nLabel("replaymod.gui.advancedscreenshots.finished.showfile"); + + public final GuiButton closeButton = new GuiButton() + .setSize(150, 20) + .setI18nLabel("replaymod.gui.close"); + + public final GuiCheckbox neverOpenCheckbox = new GuiCheckbox(); + + public final GuiLabel neverOpenLabel = new GuiLabel() + .setI18nText("replaymod.gui.notagain") + .setColor(ReadableColor.BLACK); + + public final GuiPanel checkboxPanel = GuiPanel.builder() + .layout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(5)) + .with(neverOpenCheckbox, new HorizontalLayout.Data(0.5)) + .with(neverOpenLabel, new HorizontalLayout.Data(0.5)) + .build(); + + public GuiUploadScreenshot(GuiContainer container, ReplayMod mod, RenderSettings renderSettings) { + super(container); + this.mod = mod; + this.renderSettings = renderSettings; + + boolean veer = renderSettings.getRenderMethod() == RenderSettings.RenderMethod.EQUIRECTANGULAR; + + if (renderSettings.getRenderMethod() == RenderSettings.RenderMethod.EQUIRECTANGULAR) { + successLabel.setI18nText("replaymod.gui.advancedscreenshots.finished.description.360"); + } + + if (veer) { + veerUploadButton.onClick(() -> { + try { + Desktop.getDesktop().browse(URI.create("https://veer.tv/upload")); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + showOnDiskButton.onClick(() -> { + try { + Desktop.getDesktop().browse(renderSettings.getOutputFile().toURI()); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + closeButton.onClick(() -> { + if (neverOpenCheckbox.isChecked()) { + SettingsRegistry settingsRegistry = mod.getSettingsRegistry(); + settingsRegistry.set(Setting.SKIP_POST_SCREENSHOT_GUI, true); + settingsRegistry.save(); + } + close(); + }); + + popup.addElements(new VerticalLayout.Data(0.5), successLabel); + + if (veer) { + popup.addElements(new VerticalLayout.Data(0.5), + veerLabel, + veerUploadButton); + } + + popup.addElements(new VerticalLayout.Data(0.5), + successLabel, + showOnDiskButton, + closeButton); + + popup.addElements(new VerticalLayout.Data(1), + checkboxPanel); + + popup.setLayout(new VerticalLayout().setSpacing(5)); + } + + @Override + protected void open() { + if (mod.getSettingsRegistry().get(Setting.SKIP_POST_SCREENSHOT_GUI)) { + return; + } + super.open(); + } + + @Override + protected GuiUploadScreenshot getThis() { + return this; + } +} diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java new file mode 100644 index 00000000..6fe7af62 --- /dev/null +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java @@ -0,0 +1,66 @@ +package com.replaymod.extras.advancedscreenshots; + +import com.replaymod.render.RenderSettings; +import com.replaymod.render.capturer.RenderInfo; +import com.replaymod.render.hooks.ChunkLoadingRenderGlobal; +import com.replaymod.render.rendering.Pipelines; +import lombok.RequiredArgsConstructor; +import net.minecraft.client.Minecraft; +import net.minecraft.crash.CrashReport; +import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; + +@RequiredArgsConstructor +public class ScreenshotRenderer implements RenderInfo { + + private final Minecraft mc = Minecraft.getMinecraft(); + + private final RenderSettings settings; + + public boolean renderScreenshot() throws Throwable { + try { + int displayWidthBefore = mc.displayWidth; + int displayHeightBefore = mc.displayHeight; + + boolean hideGUIBefore = mc.gameSettings.hideGUI; + mc.gameSettings.hideGUI = true; + + ChunkLoadingRenderGlobal clrg = new ChunkLoadingRenderGlobal(mc.renderGlobal); + + Pipelines.newPipeline(settings.getRenderMethod(),this, + new ScreenshotWriter(settings.getOutputFile())).run(); + + clrg.uninstall(); + + mc.gameSettings.hideGUI = hideGUIBefore; + mc.resize(displayWidthBefore, displayHeightBefore); + return true; + } catch (OutOfMemoryError e) { + e.printStackTrace(); + CrashReport report = CrashReport.makeCrashReport(e, "Creating Equirectangular Screenshot"); + Minecraft.getMinecraft().crashed(report); + } + return false; + } + + @Override + public ReadableDimension getFrameSize() { + return new Dimension(settings.getVideoWidth(), settings.getVideoHeight()); + } + + @Override + public int getTotalFrames() { + // render 2 frames, because only the second contains all frames fully loaded + return 2; + } + + @Override + public float updateForNextFrame() { + return mc.timer.renderPartialTicks; + } + + @Override + public RenderSettings getRenderSettings() { + return settings; + } +} diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java new file mode 100644 index 00000000..fcabe95c --- /dev/null +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java @@ -0,0 +1,65 @@ +package com.replaymod.extras.advancedscreenshots; + +import com.replaymod.core.ReplayMod; +import com.replaymod.core.utils.Utils; +import com.replaymod.extras.ReplayModExtras; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.rendering.FrameConsumer; +import com.replaymod.replay.ReplayModReplay; +import net.minecraft.client.Minecraft; +import net.minecraft.crash.CrashReport; +import org.lwjgl.util.ReadableDimension; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class ScreenshotWriter implements FrameConsumer { + + private final File outputFile; + + public ScreenshotWriter(File outputFile) { + this.outputFile = outputFile; + } + + @Override + public void consume(RGBFrame frame) { + // skip the first frame, in which not all chunks are properly loaded + if (frame.getFrameId() == 0) return; + + try { + final ReadableDimension frameSize = frame.getSize(); + + BufferedImage img = new BufferedImage(frameSize.getWidth(), frameSize.getHeight(), BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < frameSize.getHeight(); y++) { + for (int x = 0; x < frameSize.getWidth(); x++) { + byte r = frame.getByteBuffer().get(); + byte g = frame.getByteBuffer().get(); + byte b = frame.getByteBuffer().get(); + + int color = ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); + img.setRGB(x, y, color); + } + } + + outputFile.getParentFile().mkdirs(); + ImageIO.write(img, "PNG", outputFile); + } catch (OutOfMemoryError e) { + e.printStackTrace(); + CrashReport report = CrashReport.makeCrashReport(e, "Exporting frame"); + Minecraft.getMinecraft().crashed(report); + } catch (Throwable t) { + CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame"); + + ReplayMod.instance.runLater(() -> Utils.error(ReplayModExtras.LOGGER, + ReplayModReplay.instance.getReplayHandler().getOverlay(), + report, null)); + } + } + + @Override + public void close() throws IOException { + + } +} diff --git a/src/main/java/com/replaymod/online/ReplayModOnline.java b/src/main/java/com/replaymod/online/ReplayModOnline.java index 44092066..cf1d7f6e 100644 --- a/src/main/java/com/replaymod/online/ReplayModOnline.java +++ b/src/main/java/com/replaymod/online/ReplayModOnline.java @@ -30,6 +30,7 @@ import static net.minecraft.client.Minecraft.getMinecraft; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModOnline { public static final String MOD_ID = "replaymod-online"; diff --git a/src/main/java/com/replaymod/online/gui/GuiRegister.java b/src/main/java/com/replaymod/online/gui/GuiRegister.java index 574a6c05..08c78f29 100644 --- a/src/main/java/com/replaymod/online/gui/GuiRegister.java +++ b/src/main/java/com/replaymod/online/gui/GuiRegister.java @@ -110,7 +110,7 @@ public class GuiRegister extends AbstractGuiScreen { @Override public void run() { String status = null; - if (usernameInput.getText().length() < 5) { + if (usernameInput.getText().length() < 1) { status = "replaymod.gui.register.error.shortusername"; } else if(!Patterns.ALPHANUMERIC_UNDERSCORE.matcher(usernameInput.getText().trim()).matches()) { status = "replaymod.gui.register.error.invalidname"; diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index b2458985..c8d68dd6 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -3,6 +3,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.handler.GuiHandler; import com.replaymod.recording.packet.PacketListener; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; @@ -20,6 +21,7 @@ import org.lwjgl.input.Keyboard; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModRecording { public static final String MOD_ID = "replaymod-recording"; @@ -57,6 +59,8 @@ public class ReplayModRecording { EventBus bus = FMLCommonHandler.instance().bus(); bus.register(connectionEventHandler = new ConnectionEventHandler(logger, core)); + new GuiHandler(core).register(); + NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler()); } diff --git a/src/main/java/com/replaymod/recording/handler/GuiHandler.java b/src/main/java/com/replaymod/recording/handler/GuiHandler.java new file mode 100644 index 00000000..dc6869d4 --- /dev/null +++ b/src/main/java/com/replaymod/recording/handler/GuiHandler.java @@ -0,0 +1,52 @@ +package com.replaymod.recording.handler; + +import com.replaymod.core.ReplayMod; +import com.replaymod.core.SettingsRegistry; +import com.replaymod.recording.Setting; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.container.VanillaGuiScreen; +import de.johni0702.minecraft.gui.element.GuiCheckbox; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import net.minecraft.client.gui.GuiMultiplayer; +import net.minecraft.client.gui.GuiSelectWorld; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +public class GuiHandler { + + private final ReplayMod mod; + + public GuiHandler(ReplayMod mod) { + this.mod = mod; + } + + public void register() { + MinecraftForge.EVENT_BUS.register(this); + } + + @SubscribeEvent + public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { + if (event.gui instanceof GuiSelectWorld || event.gui instanceof GuiMultiplayer) { + SettingsRegistry settingsRegistry = mod.getSettingsRegistry(); + boolean sp = event.gui instanceof GuiSelectWorld; + Setting setting = sp ? Setting.RECORD_SINGLEPLAYER : Setting.RECORD_SERVER; + + GuiCheckbox recordingCheckbox = new GuiCheckbox() + .setI18nLabel("replaymod.gui.settings.record" + (sp ? "singleplayer" : "server")) + .setChecked(settingsRegistry.get(setting)); + recordingCheckbox.onClick(() -> { + settingsRegistry.set(setting, recordingCheckbox.isChecked()); + settingsRegistry.save(); + }); + + VanillaGuiScreen.setup(event.gui).setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + //size(recordingCheckbox, 200, 20); + pos(recordingCheckbox, width - width(recordingCheckbox) - 5, 5); + } + }).addElements(null, recordingCheckbox); + } + } +} diff --git a/src/main/java/com/replaymod/render/ReplayModRender.java b/src/main/java/com/replaymod/render/ReplayModRender.java index 6b7f3668..3a5852de 100644 --- a/src/main/java/com/replaymod/render/ReplayModRender.java +++ b/src/main/java/com/replaymod/render/ReplayModRender.java @@ -22,6 +22,7 @@ import java.util.List; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModRender { public static final String MOD_ID = "replaymod-render"; diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index f712dd0d..a07f801e 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -427,7 +427,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { ); } - private File generateOutputFile(RenderSettings.EncodingPreset encodingPreset) { + protected File generateOutputFile(RenderSettings.EncodingPreset encodingPreset) { String fileName = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date()); File folder = ReplayModRender.instance.getVideoFolder(); return new File(folder, fileName + "." + encodingPreset.getFileExtension()); @@ -447,7 +447,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { config.save(); } - private Property getConfigProperty(Configuration configuration) { + protected Property getConfigProperty(Configuration configuration) { return configuration.get("rendersettings", "settings", "{}", "Last state of the render settings GUI. Internal use only."); } diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java index 7f5cae0a..07b90620 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java @@ -37,6 +37,7 @@ public abstract class MixinRenderGlobal { do { setupTerrain(viewEntity, partialTicks, camera, replayModRender_hook.nextFrameId(), playerSpectator); + replayModRender_hook.updateChunks(); } while (displayListEntitiesDirty); displayListEntitiesDirty = true; diff --git a/src/main/java/com/replaymod/replay/InputReplayTimer.java b/src/main/java/com/replaymod/replay/InputReplayTimer.java index da1bd5fb..250ba766 100644 --- a/src/main/java/com/replaymod/replay/InputReplayTimer.java +++ b/src/main/java/com/replaymod/replay/InputReplayTimer.java @@ -3,6 +3,7 @@ package com.replaymod.replay; import com.replaymod.core.utils.WrappedTimer; import com.replaymod.replay.camera.CameraController; import com.replaymod.replay.camera.CameraEntity; +import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.settings.GameSettings; @@ -11,6 +12,7 @@ import net.minecraft.crash.CrashReport; import net.minecraft.util.ReportedException; import net.minecraft.util.Timer; import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.FMLCommonHandler; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; @@ -122,7 +124,9 @@ public class InputReplayTimer extends WrappedTimer { } // Twitch, screenshot, fullscreen, etc. (stuff that works everywhere) - mc.dispatchKeypresses(); + if (!MinecraftForge.EVENT_BUS.post(new ReplayDispatchKeypressesEvent.Pre())) { + mc.dispatchKeypresses(); + } if (pressed) { // This might be subject to change as vanilla shaders are still kinda unused in 1.8 diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index d6ef677d..a19741ca 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -36,6 +36,7 @@ import java.util.Optional; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModReplay { public static final String MOD_ID = "replaymod-replay"; @@ -194,6 +195,10 @@ public class ReplayModReplay { replayHandler = new ReplayHandler(replayFile, true); } + public void forcefullyStopReplay() { + replayHandler = null; + } + public ReplayMod getCore() { return core; } diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index 3d2a8c36..e3094a4f 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -5,8 +5,12 @@ import com.google.common.io.Files; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Restrictions; import com.replaymod.replay.camera.CameraEntity; +import com.replaymod.replaystudio.io.ReplayInputStream; +import com.replaymod.replaystudio.io.ReplayOutputStream; import com.replaymod.replaystudio.replay.ReplayFile; +import com.replaymod.replaystudio.studio.ReplayStudio; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler.Sharable; @@ -108,10 +112,10 @@ public class ReplaySender extends ChannelDuplexHandler { protected ChannelHandlerContext ctx; /** - * The data input stream from which new packets are read. + * The replay input stream from which new packets are read. * When accessing this stream make sure to synchronize on {@code this} as it's used from multiple threads. */ - protected DataInputStream dis; + protected ReplayInputStream replayIn; /** * The next packet that should be sent. @@ -574,8 +578,8 @@ public class ReplaySender extends ChannelDuplexHandler { REPLAY_LOOP: while (!terminate) { synchronized (ReplaySender.this) { - if (dis == null) { - dis = new DataInputStream(replayFile.getPacketData()); + if (replayIn == null) { + replayIn = replayFile.getPacketData(); } // Packet loop while (true) { @@ -602,7 +606,7 @@ public class ReplaySender extends ChannelDuplexHandler { // Read the next packet if we don't already have one if (nextPacket == null) { - nextPacket = new PacketData(dis); + nextPacket = new PacketData(replayIn); } int nextTimeStamp = nextPacket.timestamp; @@ -659,9 +663,9 @@ public class ReplaySender extends ChannelDuplexHandler { nextPacket = null; lastPacketSent = System.currentTimeMillis(); replayHandler.restartedReplay(); - if (dis != null) { - dis.close(); - dis = null; + if (replayIn != null) { + replayIn.close(); + replayIn = null; } } } @@ -750,17 +754,17 @@ public class ReplaySender extends ChannelDuplexHandler { if (timestamp < lastTimeStamp) { // Restart the replay if we need to go backwards in time hasWorldLoaded = false; lastTimeStamp = 0; - if (dis != null) { - dis.close(); - dis = null; + if (replayIn != null) { + replayIn.close(); + replayIn = null; } startFromBeginning = false; nextPacket = null; replayHandler.restartedReplay(); } - if (dis == null) { - dis = new DataInputStream(replayFile.getPacketData()); + if (replayIn == null) { + replayIn = replayFile.getPacketData(); } while (true) { // Send packets @@ -772,7 +776,7 @@ public class ReplaySender extends ChannelDuplexHandler { nextPacket = null; } else { // Otherwise read one from the input stream - pd = new PacketData(dis); + pd = new PacketData(replayIn); } int nextTimeStamp = pd.timestamp; @@ -787,7 +791,7 @@ public class ReplaySender extends ChannelDuplexHandler { } catch (EOFException eof) { // Shit! We hit the end before finishing our job! What shall we do now? // well, let's just pretend we're done... - dis = null; + replayIn = null; break; } catch (IOException e) { e.printStackTrace(); @@ -915,13 +919,32 @@ public class ReplaySender extends ChannelDuplexHandler { } private static final class PacketData { + private static final ByteBuf byteBuf = Unpooled.buffer(); + private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); + private static final ReplayOutputStream encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut); private final int timestamp; private final byte[] bytes; - public PacketData(DataInputStream in) throws IOException { - timestamp = in.readInt(); - bytes = new byte[in.readInt()]; - in.readFully(bytes); + public PacketData(ReplayInputStream in) throws IOException { + com.replaymod.replaystudio.PacketData data = in.readPacket(); + timestamp = (int) data.getTime(); + // We need to re-encode MCProtocolLib packets, so we can later decode them as NMS packets + // The main reason we aren't reading them as NMS packets is that we want ReplayStudio to be able + // to apply ViaVersion (and potentially other magic) to it. + synchronized (encoder) { + byteBuf.markReaderIndex(); // Mark the current reader and writer index (should be at start) + byteBuf.markWriterIndex(); + + encoder.write(data); // Re-encode packet, data will end up in byteBuf + encoder.flush(); + + byteBuf.skipBytes(8); // Skip packet length & timestamp + bytes = new byte[byteBuf.readableBytes()]; // Create bytes array of sufficient size + byteBuf.readBytes(bytes); // Read all data into bytes + + byteBuf.resetReaderIndex(); // Reset reader & writer index for next use + byteBuf.resetWriterIndex(); + } } } } diff --git a/src/main/java/com/replaymod/replay/Setting.java b/src/main/java/com/replaymod/replay/Setting.java index aa2a6256..a16e42e7 100644 --- a/src/main/java/com/replaymod/replay/Setting.java +++ b/src/main/java/com/replaymod/replay/Setting.java @@ -4,6 +4,7 @@ import com.replaymod.core.SettingsRegistry; public final class Setting extends SettingsRegistry.SettingKeys { public static final Setting SHOW_CHAT = make("showChat", "showchat", true); + public static final Setting SHOW_SERVER_IPS = new Setting<>("showServerIPs", true); public static final SettingsRegistry.MultipleChoiceSettingKeys CAMERA = new SettingsRegistry.MultipleChoiceSettingKeys<>( "replay", "camera", "replaymod.gui.settings.camera", "replaymod.camera.classic"); @@ -15,4 +16,8 @@ public final class Setting extends SettingsRegistry.SettingKeys { public Setting(String key, String displayString, T defaultValue) { super("replay", key, "replaymod.gui.settings." + displayString, defaultValue); } + + public Setting(String key, T defaultValue) { + super("replay", key, null, defaultValue); + } } diff --git a/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java b/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java new file mode 100644 index 00000000..d3547127 --- /dev/null +++ b/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java @@ -0,0 +1,10 @@ +package com.replaymod.replay.events; + +import net.minecraftforge.fml.common.eventhandler.Cancelable; +import net.minecraftforge.fml.common.eventhandler.Event; + +public abstract class ReplayDispatchKeypressesEvent extends Event { + + @Cancelable + public static class Pre extends ReplayDispatchKeypressesEvent {} +} diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index e9bd1d4e..6ce4bb39 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -5,9 +5,12 @@ import com.google.common.base.Supplier; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.mojang.realmsclient.gui.ChatFormatting; +import com.replaymod.core.ReplayMod; +import com.replaymod.core.SettingsRegistry; import com.replaymod.core.gui.GuiReplaySettings; import com.replaymod.core.utils.Utils; import com.replaymod.replay.ReplayModReplay; +import com.replaymod.replay.Setting; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; import com.replaymod.replaystudio.replay.ZipReplayFile; @@ -18,6 +21,7 @@ import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiScreen; import de.johni0702.minecraft.gui.element.*; import de.johni0702.minecraft.gui.element.advanced.GuiResourceLoadingList; +import de.johni0702.minecraft.gui.function.Typeable; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; @@ -34,8 +38,10 @@ import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.helpers.Strings; import org.lwjgl.Sys; +import org.lwjgl.input.Keyboard; import org.lwjgl.util.Dimension; import org.lwjgl.util.ReadableDimension; +import org.lwjgl.util.ReadablePoint; import java.awt.*; import java.awt.image.BufferedImage; @@ -47,7 +53,7 @@ import java.util.Date; import static com.replaymod.replay.ReplayModReplay.LOGGER; -public class GuiReplayViewer extends GuiScreen { +public class GuiReplayViewer extends GuiScreen implements Typeable { private final ReplayModReplay mod; public final GuiResourceLoadingList list = new GuiResourceLoadingList(this).onSelectionChanged(new Runnable() { @@ -272,6 +278,18 @@ public class GuiReplayViewer extends GuiScreen { } private final GuiImage defaultThumbnail = new GuiImage().setTexture(Utils.DEFAULT_THUMBNAIL); + + @Override + public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) { + if (keyCode == Keyboard.KEY_F1) { + SettingsRegistry reg = ReplayMod.instance.getSettingsRegistry(); + reg.set(Setting.SHOW_SERVER_IPS, !reg.get(Setting.SHOW_SERVER_IPS)); + reg.save(); + list.load(); + } + return false; + } + public class GuiReplayEntry extends AbstractGuiContainer implements Comparable { public final File file; public final GuiLabel name = new GuiLabel(); @@ -303,7 +321,8 @@ public class GuiReplayViewer extends GuiScreen { this.file = file; name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(file.getName())); - if (Strings.isEmpty(metaData.getServerName())) { + if (Strings.isEmpty(metaData.getServerName()) + || !ReplayMod.instance.getSettingsRegistry().get(Setting.SHOW_SERVER_IPS)) { server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED); } else { server.setText(metaData.getServerName()); diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java index f05d2971..cad20d5a 100644 --- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java @@ -16,6 +16,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import static com.replaymod.replay.ReplayModReplay.LOGGER; + public class GuiHandler { private static final int BUTTON_EXIT_SERVER = 1; private static final int BUTTON_ACHIEVEMENTS = 5; @@ -102,6 +104,20 @@ public class GuiHandler { return; } + if (mod.getReplayHandler() != null) { + // Something went terribly wrong and we ended up in the main menu with the replay still active. + // To prevent players from joining live servers and using the CameraEntity, try to stop the replay now. + try { + mod.getReplayHandler().endReplay(); + } catch (IOException e) { + LOGGER.error("Trying to stop broken replay: ", e); + } finally { + if (mod.getReplayHandler() != null) { + mod.forcefullyStopReplay(); + } + } + } + @SuppressWarnings("unchecked") List buttonList = event.buttonList; GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, event.gui.width / 2 - 100, diff --git a/src/main/java/com/replaymod/restrictions/ReplayModRestrictions.java b/src/main/java/com/replaymod/restrictions/ReplayModRestrictions.java new file mode 100644 index 00000000..9a0fbd28 --- /dev/null +++ b/src/main/java/com/replaymod/restrictions/ReplayModRestrictions.java @@ -0,0 +1,95 @@ +package com.replaymod.restrictions; + +import com.replaymod.core.SettingsRegistry; +import com.replaymod.core.utils.Restrictions; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.EventBus; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; +import net.minecraftforge.fml.common.network.FMLEmbeddedChannel; +import net.minecraftforge.fml.common.network.FMLOutboundHandler; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.minecraftforge.fml.common.network.internal.FMLProxyPacket; +import net.minecraftforge.fml.relauncher.Side; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Mod(modid = ReplayModRestrictions.MOD_ID, + version = "@MOD_VERSION@", + acceptedMinecraftVersions = "@MC_VERSION@", + acceptableRemoteVersions = "*", + serverSideOnly = true, + useMetadata = true) +public class ReplayModRestrictions { + public static final String MOD_ID = "replaymod-restrictions"; + + public static Logger LOGGER; + + private final SettingsRegistry settingsRegistry = new SettingsRegistry(); + private FMLEmbeddedChannel channel; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent event) { + LOGGER = event.getModLog(); + + Configuration config = new Configuration(event.getSuggestedConfigurationFile()); + config.load(); + settingsRegistry.setConfiguration(config); + + settingsRegistry.register(Setting.class); + } + + @Mod.EventHandler + public void init(FMLInitializationEvent event) { + EventBus bus = FMLCommonHandler.instance().bus(); + bus.register(this); + + channel = NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler()).get(Side.SERVER); + } + + @Mod.EventHandler + public void postInit(FMLPostInitializationEvent event) throws IOException { + settingsRegistry.save(); // Save default values to disk + } + + @ChannelHandler.Sharable + private static class RestrictionsChannelHandler extends ChannelDuplexHandler {} + + @SubscribeEvent + public void onUserJoin(PlayerEvent.PlayerLoggedInEvent event) { + Map restrictions = new HashMap<>(); + if (settingsRegistry.get(Setting.NO_XRAY)) restrictions.put("no_xray", new byte[0]); + if (settingsRegistry.get(Setting.NO_NOCLIP)) restrictions.put("no_noclip", new byte[0]); + if (settingsRegistry.get(Setting.ONLY_FIRST_PERSON)) restrictions.put("only_first_person", new byte[0]); + if (settingsRegistry.get(Setting.ONLY_RECORDING_PLAYER)) restrictions.put("only_recording_player", new byte[0]); + if (settingsRegistry.get(Setting.HIDE_COORDINATES)) restrictions.put("hide_coordinates", new byte[0]); + + PacketBuffer buf = new PacketBuffer(Unpooled.buffer()); + for (Map.Entry e : restrictions.entrySet()) { + byte[] bytes = e.getKey().getBytes(); + buf.writeByte(bytes.length); + buf.writeBytes(bytes); + buf.writeByte(1); + buf.writeBytes(e.getValue()); + } + + FMLProxyPacket packet = new FMLProxyPacket(buf, Restrictions.PLUGIN_CHANNEL); + + channel.attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.PLAYER); + channel.attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(event.player); + channel.writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } +} diff --git a/src/main/java/com/replaymod/restrictions/Setting.java b/src/main/java/com/replaymod/restrictions/Setting.java new file mode 100644 index 00000000..7a36e936 --- /dev/null +++ b/src/main/java/com/replaymod/restrictions/Setting.java @@ -0,0 +1,19 @@ +package com.replaymod.restrictions; + +import com.replaymod.core.SettingsRegistry; + +public final class Setting extends SettingsRegistry.SettingKeys { + public static final Setting NO_XRAY = make("no_xray", false); + public static final Setting NO_NOCLIP = make("no_noclip", false); + public static final Setting ONLY_FIRST_PERSON = make("only_first_person", false); + public static final Setting ONLY_RECORDING_PLAYER = make("only_recording_player", false); + public static final Setting HIDE_COORDINATES = make("hide_coordinates", false); + + private static Setting make(String key, T defaultValue) { + return new Setting<>("restrictions", key, defaultValue); + } + + public Setting(String category, String key, T defaultValue) { + super(category, key, null, defaultValue); + } +} diff --git a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java index ecb1e436..86cb94ca 100644 --- a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java +++ b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java @@ -2,8 +2,10 @@ package com.replaymod.simplepathing; import com.replaymod.core.ReplayMod; import com.replaymod.core.events.SettingsChangedEvent; +import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.events.ReplayCloseEvent; import com.replaymod.replay.events.ReplayOpenEvent; +import com.replaymod.replay.gui.overlay.GuiReplayOverlay; import com.replaymod.replaystudio.pathing.path.Keyframe; import com.replaymod.simplepathing.SPTimeline.SPPath; import com.replaymod.simplepathing.gui.GuiPathing; @@ -20,6 +22,7 @@ import org.lwjgl.input.Keyboard; version = "@MOD_VERSION@", acceptedMinecraftVersions = "@MC_VERSION@", acceptableRemoteVersions = "*", + clientSideOnly = true, useMetadata = true) public class ReplayModSimplePathing { public static final String MOD_ID = "replaymod-simplepathing"; @@ -81,20 +84,34 @@ public class ReplayModSimplePathing { } } + private GuiReplayOverlay getReplayOverlay() { + return ReplayModReplay.instance.getReplayHandler().getOverlay(); + } + private SPTimeline currentTimeline; - @Getter private SPPath selectedPath; @Getter private long selectedTime; + public SPPath getSelectedPath() { + if (getReplayOverlay().timeline.getSelectedMarker() != null) { + selectedPath = null; + selectedTime = 0; + } + return selectedPath; + } + public boolean isSelected(Keyframe keyframe) { - return selectedPath != null && currentTimeline.getKeyframe(selectedPath, selectedTime) == keyframe; + return getSelectedPath() != null && currentTimeline.getKeyframe(selectedPath, selectedTime) == keyframe; } public void setSelected(SPPath path, long time) { selectedPath = path; selectedTime = time; + if (selectedPath != null) { + getReplayOverlay().timeline.setSelectedMarker(null); + } } public void setCurrentTimeline(SPTimeline newTimeline) { diff --git a/src/main/resources/META-INF/replaymod_at.cfg b/src/main/resources/META-INF/replaymod_at.cfg index 59d3b95e..08652131 100644 --- a/src/main/resources/META-INF/replaymod_at.cfg +++ b/src/main/resources/META-INF/replaymod_at.cfg @@ -117,5 +117,8 @@ public net.minecraft.client.renderer.GlStateManager$FogState public net.minecraft.client.renderer.GlStateManager$BooleanState public net.minecraft.client.renderer.GlStateManager$BooleanState * +# ScreenShotHelper +public net.minecraft.util.ScreenShotHelper func_74290_a(Ljava/io/File;)Ljava/io/File; # getTimestampedPNGFileForDirectory + # Example # public net.minecraft.package.ClassName func_some_id(Ljava/lang/Class;IZS)V # methodName diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 8d77fe06..692a6765 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 8d77fe0697691f0b682f422e268b40383468f31d +Subproject commit 692a6765560bd96f02c9364798395914470c2c9e diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 065f181d..3cd255eb 100755 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -134,6 +134,22 @@ "screenshots": [], "dependencies": [] }, + { + "modid": "replaymod-restrictions", + "name": "Replay Mod - Restrictions", + "description": "Server-side Restrictions Module of the ReplayMod - Restrict what clients can do in replays", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "https://replaymod.com", + "updateUrl": "https://replaymod.com/download", + "authorList": [ + "CrushedPixel", + "johni0702" + ], + "logoFile": "replaymod_logo.png", + "screenshots": [], + "dependencies": [] + }, { "modid": "replaymod-compat", "name": "Replay Mod - Compatibility", diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index 539d1c55..58617e59 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -1,7 +1,9 @@ { "required": true, "package": "com.replaymod.render.mixin", - "mixins": [ + "mixins": [], + "server": [], + "client": [ "MixinEffectRenderer", "MixinEntityRenderer", "MixinFrustum", @@ -10,8 +12,6 @@ "MixinRenderGlobal", "MixinRenderManager" ], - "server": [], - "client": [], "compatibilityLevel": "JAVA_8", "minVersion": "0.6.11", "refmap": "mixins.replaymod.refmap.json" diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json index e92257fb..c3471556 100644 --- a/src/main/resources/mixins.replay.replaymod.json +++ b/src/main/resources/mixins.replay.replaymod.json @@ -1,7 +1,9 @@ { "required": true, "package": "com.replaymod.replay.mixin", - "mixins": [ + "mixins": [], + "server": [], + "client": [ "MixinGuiSpectator", "MixinPlayerControllerMP", "MixinRenderArrow", @@ -12,8 +14,6 @@ "MixinViewFrustum", "MixinWorldClient" ], - "server": [], - "client": [], "compatibilityLevel": "JAVA_8", "minVersion": "0.6.11", "refmap": "mixins.replaymod.refmap.json"