diff --git a/docs/content.md b/docs/content.md old mode 100644 new mode 100755 index 90f9a4f5..f17fb9f2 --- a/docs/content.md +++ b/docs/content.md @@ -1,16 +1,14 @@ # Installation [installing] ## Installing the Replay Mod [replaymod] -The **ReplayMod** requires **Minecraft Forge** to be installed. +### Minecraft 1.14 and above +The **ReplayMod** for Minecraft 1.14 and above requires **Fabric & Fabric API** to be installed. -Depending on your **Replay Mod** version we recommend the following **Forge** versions: +You can find Fabric and the installation instructions [here](https://fabricmc.net/use/). With Fabric, you also need to install the Fabric API by putting it into your mods folder. You can find Fabric API [here](https://www.curseforge.com/minecraft/mc-mods/fabric-api). -- Replay Mod 2.0.0 or later for Minecraft 1.11: Forge [1.11-13.19.1.2189](https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.11.html) -- Replay Mod 2.0.0 or later for Minecraft 1.10.2: Forge [1.10.2-12.18.2.2099](https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.10.2.html) -- Replay Mod 2.0.0 or later for Minecraft 1.9.4: Forge [1.9.4-12.17.0.1976](https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.9.4.html) -- Replay Mod 2.0.0 or later for Minecraft 1.8: Forge [1.8-11.14.4.1563](https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.8.html) -- Replay Mod 1.0.8 or older for Minecraft 1.8: Forge [1.8-11.14.3.1450](http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.8.html) +After installing Fabric & Fabric API, simply put the downloaded ReplayMod .jar file into the `/mods` folder of your Minecraft directory. -Other Forge Versions might, but don't necessarily work. +### Minecraft 1.12.2 and below +For Minecraft 1.12.2 and below it requires **Minecraft Forge** to be installed. If you don't know how to install Forge, follow [this tutorial](https://www.youtube.com/watch?v=4i7-RystzC4). @@ -19,22 +17,12 @@ After installing Forge, simply put the downloaded `ReplayMod.jar` file in the `/ > **Note:** The **Replay Mod** is entirely client side and can not be installed on the servers you play on. ## Installing FFmpeg [ffmpeg] -To use the **Replay Mod**'s [Rendering Feature](#replaying-render), you need to have FFmpeg installed. +To render your creations with **Replay Mod** you will need to have FFmpeg installed. ### Windows [windows] -Download the latest **FFmpeg Static Build** for your architecture from . -Then, extract the downloaded `.7z` file in the folder you want to install FFmpeg in. +Download the latest FFmpeg build from . Make sure to select the latest release in the dropdown next to 'Version'. This will be a number like `4.3` or `4.3.1`. Choose the highest number. -Next, download this **Batch Script** to quickly install FFmpeg: - -> **Note:** If your browser warns you about the download, dismiss the message. It's a simple Batch Script which won't damage your computer. - -Put the downloaded script into the folder where you extracted FFmpeg and run itt by double-clicking it. -If you see a success message in the console, you've sucessfully installed **FFmpeg**! - -Alternatively, starting with ReplayMod 2.0.0-b5, you can also extract the downloaded `.7z` file into a `ffmpeg` folder -(you have to create it, it doesn't exist by default) in your `.minecraft` folder. No need to run any **Batch Script**. -The FFmpeg executable should end up at `.minecraft/ffmpeg/bin/ffmpeg.exe`. +In your `.minecraft` folder, create a `ffmpeg` folder. Extract the downloaded .zip file into this folder. The FFmpeg executable should end up at `.minecraft/ffmpeg/bin/ffmpeg.exe`. ### Mac OSX [mac] On OSX, you can install **FFmpeg** with **[Homebrew](http://brew.sh/)** using `brew install ffmpeg`. @@ -45,34 +33,10 @@ Alternatively, you can download the latest static build from Custom Objects are not available in the 2.0.0 versions. They are expected to return eventually. - -[YouTube](_Xdpg828fbE) - -### Introduction [introduction] -**Custom Objects** are mainly meant for **video creators**. They allow you to add any image into the Replay World and animate its Position, Opacity and much more. - -Instead of using [Motion Tracking](https://en.wikipedia.org/wiki/Match_moving) to add text or pictures to a video, you can directly add these to the rendered file. - -### Adding Assets [assets] -![](img/asset-manager.jpg) -The **Asset Manager** with an example Asset - -In order to create **Custom Objects**, you first have to add **Assets** to your Replay File. -Therefore, open the **Asset Manager** using the `G` key. - -In the **Asset Manager**, you can add **Image Files (.png, .jpg and more)**, so-called **Assets**, to the Replay. -When clicking the **"Add"** button, a File Chooser will show up. Select the image file you want to use. -You can give the Asset a custom name using the Text Input Field in the top right corner. - -All of the Assets you added can be used by **Custom Objects**. - -### Creating Custom Objects [creating] -![](img/object-manager.jpg) -The **Object Manager** with some **Animation Keyframes** - -Using the `F` key, you can open the **Object Manager**. Initially there won't be any Objects in the **Object List**, but you can simply add an Object using the **"Add"** button. - -After doing so, you can name the newly created **Custom Object** using the Text Field in the upper right corner. -To define which **Asset File** (i.e. image) the **Custom Object** should use, select the desired **Asset** from the dropdown beneath the Name Input. - -If you leave the **Object Manager**, you should see the image in the World somwhere near your position. - -### Animating Custom Objects [animating] -![](img/custom-object-animated.gif) -An animated **Custom Image Object**. - -Of course, you don't want the image to stay at that position. - herefore, open the **Object Manager** again and select the **Custom Object** you want to modify. -On the lower half of the screen you will see mutliple **Input Fields**, using which you can modify various settings -(so-called **Transformations**), for example the Object's **Position**, **Scale**, **Orientation** and **Opacity**. - -When editing these values, you'll notice that on the TImeline to the right, Keyframes appear. -You can set and remove Keyframes for specific settings using its "Add Keyframe" button on the very left of the screen. - -The **Object Manager's Keyframe System** is very similar to the **Position Keyframe** and **Time Keyframe** System. - -During a **Camera Path**, the **Custom Objects** will interpolate their **Transformation Values** like **Position Keyframes** are interpolated. -While outside the **Object Manager** you can preview the Objects' position by moving the cursor on the **Keyframe Timeline** - the Objects will interpolate to the position they'll be at that timestamp during the **Camera Path**. - -Once you got the hang of it, you'll be able to quickly animate **Custom Objects**. - ## Rendering [render] With the **Replay Mod**, you can render **Camera Paths** to videos **without** using a screen recording tool like Fraps. To get started, first [set up your Camera Path](#replaying-paths) as described in the previous chapters. @@ -510,29 +418,6 @@ To use **High Performance Rendering**, hold down the `Ctrl` key (`Cmd` key on M but only after one rendered seconds (e.g. every 60 frames when rendering with 60fps) - Resizing the Minecraft Window does not update the Rendering Gui -### Troubleshooting [troubleshooting] - -> If you have trouble with rendering, please first consult the Documentation before asking for help in the Forums. - -#### No FFmpeg installation found [ffmpeg] -![](img/ffmpeg-missing.jpg) -The error screen that is displayed when no FFmpeg installation could be found - -If the **Replay Mod** tells you to install FFmpeg even though you already have, you have to manually tell the **Replay Mod** where your FFmpeg executable is located. - -First, get the full path to your `ffmpeg.exe` (on Windows) or `ffmpeg` executable (on Mac/Linux). -On Windows, this path might look like `C:/ffmpeg/ffmpeg.exe`. - -Then, open the **"Command Line Settings" Tab** in the Render Settings and paste this path into the **left input field** and retry rendering. - -#### Crash while rendering [crash] -If Minecraft crashes after a few frames of rendering, it most likely means the **FFmpeg** didn't like the **Command Line Arguments** you passed. -If you customized the Command Line Arguments manually, re-check them - it's probably your own fault. - -> **Hint:** In your .minecraft folder, you'll find a file called `export.log` which contains information about FFmpeg's rendering process. - -If you did **not** customize the **Command Line Arguments**, you might have entered some insanely high (or low) values e.g. for Bitrate or Video Resolution. Try again with other, more reasonable values. - ## Ambient Lighting [lighting] ![](img/ambient-lighting-comparison.jpg) The same setting, onace with **Ambient Lighting** enabled, once with **Ambient Lighting** disabled @@ -594,84 +479,8 @@ This way, it's even simpler to add structure to your Replays. You can **delete an Event Marker** by clicking it once to select it and then pressing the `DELETE` key. -# Replay Center [center] -The **Replay Center** is where you can **share** your **Minecraft Moments** with others and discover awesome **Replays by other Users**. -To be able to use the **Replay Center**, you need an **account on ReplayMod.com**. - -## Authentication [auth] -![](img/auth-login.jpg) -The **Login Screen** which is displayed upon startup - -![](img/auth-register.jpg) -The **Register Screen** - -When starting Minecraft with the **Replay Mod** installed, you will be asked to login to **ReplayMod.com**. -If you don't want to use the **Replay Center**, you can click the **"Skip" Button** to continue without logging in. - -If you want to browse other users' **Replays** and **share your own Replays** however, you need to **Register an account**. - -> **Note:** By registering an Account on **ReplayMod.com**, you agree to the Website's [Terms of Service](https://www.replaymod.com/legal/terms) - -Please note that you can only create **one ReplayMod.com Account per Minecraft Account**, so choose your username wisely. - -Once you've registered, you're automatically going to be logged in. The Mod **remembers your login** until you manually log out from the **Replay Center**, so it won't ask you upon every startup if you logged in once. - -## Replay Files [files] -![](img/replay-center.jpg) -The **Replay Center** in the Mod - -Once you've logged in, you can use the **"Replay Center" Button** in the Main Menu to access the **Replay Center**. - -In the upper half of the screen, there are **5 Buttons** for **5 Tabs** of the **Replay Center**: -- **Recent** - Shows the most recently uploaded Replays in the **Replay Center** -- **Best** - Shows the Replays with the **best rating and most downloads** -- **Downloaded** - Shows all of the Replays you've downloaded locally -- **Favorited** - Shows all of the Replays you favorited -- **Search** - Allows you to search for specific Replays - -When you've found an insteresting looking Replay, you can download it using the **"Download" Button** in the lower left corner. -After downloading, you'll automatically join the Replay. - -After you've downloaded a Replay, you can rate and favorite it in the **Replay Center** using the respective buttons. - -> You are encouraged to rate Replays after you downloaded them to help promoting the best Replays. - -## Upload Replays [upload] -![](img/replay-upload.jpg) -The **Replay Upload Screen** - -You can share your own **Replay Files** with other users in the **Replay Center**. -Therefore, click the **"Upload" Button** in the **Replay Viewer** after selecting a Replay. - -In the **Replay Upload Screen** there are **6 input fields**: -- **Replay Name** - The Replay's Name in the Replay Center -- **Replay Description** - A description of what happens in the Replay. Try to give the user a good impression why your Replay is worth downloading. -- **Category** - The Replay's Category, one of the following: **Survival**, **Build**, **Minigame**, **Miscellaneous**. If none of the other categories fit, use **Miscellaneous**. -- **Tags** - One or more tags that fit your replay, spearated by comma. **Example tags:** pvp,battle,redstone,creative -- **Hide Server IP** - If you've recorded a Replay on a private Server, you can remove the Server IP by checking the respective checkbox. -- **Thumbnail** - While you can't directly edit the Thumbnail when uploading, it is highly recommended that - you [create a Thumbnail](#replaying-thumbnail) before sharing your Replay. - Replays without a Thumbnail are much less likely to be downloaded by other users. - -When uploading a Replay File, make sure to follow the [Replay Center Rules](https://www.replaymod.com/rules). - # Frequently Asked Questions [faq] -### Do I need a ReplayMod.com Account to use the mod? -You only need a ReplayMod.com Account to access the [Replay Center](#center). -All of the other features are also available offline. - ### For how long can I record? Theoretically, a Replay File can be up to **24 days, 20 hours, 30 minutes and 23 seconds** long - which is a timespan you'll probably never reach. @@ -694,3 +503,48 @@ A Replay in which you travelled around and discovered a lot of terrain is signif An average Replay File of **10 Minutes duration** usually is between **2MB and 10MB large**. Replays recorded on **Minigame Servers** with lots of particle effects and world changes might be larger. + +# Troubleshooting [troubleshooting] +> If you have trouble with rendering, please first consult the Documentation before asking for help in the Discord. + +## No FFmpeg installation found [ffmpeg] +![](img/ffmpeg-missing.jpg) +The error screen that is displayed when no FFmpeg installation could be found + +If you have not installed ffmpeg, please follow the steps provided [here](#installing-ffmpeg) + +If already have installed ffmpeg, you have to manually tell the **Replay Mod** where your FFmpeg executable is located. + +First, get the full path to your `ffmpeg.exe` (on Windows) or `ffmpeg` executable (on Mac/Linux). +On Windows, this path might look like `C:/ffmpeg/ffmpeg.exe`. + +Then, open the **"Command Line Settings" Section** in the Render Settings and paste this path into the **left input field** and retry rendering. + +## Crash while rendering [crash] +If Minecraft crashes after a few frames of rendering, it most likely means the **FFmpeg** didn't like the **Command Line Arguments** you passed. +If you customized the Command Line Arguments manually, re-check them - it's probably your own fault. + +> **Hint:** In your .minecraft folder, you'll find a file called `export.log` which contains information about FFmpeg's rendering process. + +If you did **not** customize the **Command Line Arguments**, you might have entered some insanely high (or low) values e.g. for Bitrate or Video Resolution. Try again with other, more reasonable values. + +## Compatibility with other Mods [compatibility] +### General information [general] +In General, the Replay Mod _should_ be compatible with most Forge and Fabric Mods. + +### Shaders Mod [shaders] +[Karyonix' Shaders Mod](http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/1286604-shaders-mod-updated-by-karyonix) +is no longer compatible with Minecraft Forge starting with 1.9.4. As such it is not compatible with the Replay Mod either. +Please use [Optifine](https://optifine.net/) instead. + +### Custom Main Menu [custom-main-menu] +The [Custom Main Menu](https://www.curseforge.com/minecraft/mc-mods/custom-main-menu) mod is often used in mod packs to customize their Main Menu with a button layout fitting the background image, links to their website / bug tracker and similar. +If you are familiar with it, the button id for the Replay Mod is: **17890234** (text: `replaymod.gui.replayviewer`). + +Due to the nature of this Custom Main Menu mod, buttons added to the Main Menu by 3rd party mods like the **Replay Mod** will not show up by default. +Thus, to access the Replay Viewer/Editor/Center, you need to manually configure the position for those buttons. +To do so, you need to modify the Custom Main Menu configuration file. +Usually, you can find it at `.minecraft/config/CustomMainMenu/mainmenu.json` (if you're using a mod pack launcher, it normally has its own .minecraft folder). +For an explanation of this config file, have a look at Custom Main Menu's page. + +You can find a list of already modified config files [here](https://gist.github.com/Johni0702/3f3fab81dbf7ada83d045d9fe8f345aa). diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 00000000..436a7b88 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,23 @@ +{ + "name": "replaymod-docs", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "array-findindex-polyfill": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/array-findindex-polyfill/-/array-findindex-polyfill-0.1.0.tgz", + "integrity": "sha1-w2JmW+x2RfItejw6rJeT9xw2Iu8=" + }, + "md-jml": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/md-jml/-/md-jml-2.0.1.tgz", + "integrity": "sha1-th99rTm1JPVKCmpthnMcTRLOCDo=" + }, + "string.prototype.endswith": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.endswith/-/string.prototype.endswith-0.2.0.tgz", + "integrity": "sha1-oZwg3uUamHd+mkfhDwm+OTubunU=" + } + } +} diff --git a/jGui b/jGui index 4898b6db..04bcf018 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 4898b6dbc7674177a4a280659b450903a9efac3d +Subproject commit 04bcf018c9404ef02b7112bf49a55af6520125a2 diff --git a/src/main/java/com/replaymod/core/LoadingPlugin.java b/src/main/java/com/replaymod/core/LoadingPlugin.java index b8f2c690..5598ca01 100644 --- a/src/main/java/com/replaymod/core/LoadingPlugin.java +++ b/src/main/java/com/replaymod/core/LoadingPlugin.java @@ -26,7 +26,7 @@ package com.replaymod.core; //$$ public class LoadingPlugin implements IFMLLoadingPlugin { //$$ //$$ public LoadingPlugin() { -//$$ if ((Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment")) { +//$$ if (Launch.blackboard.get("fml.deobfuscatedEnvironment") != Boolean.FALSE) { //$$ // Outside of the dev env, this is the job of the tweaker //$$ MixinBootstrap.init(); //$$ } diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 6e9725c3..14622a87 100644 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -28,6 +28,7 @@ import net.minecraft.text.Style; import net.minecraft.text.LiteralText; import net.minecraft.text.TranslatableText; import net.minecraft.util.Formatting; +import net.minecraft.util.crash.CrashException; import org.apache.commons.io.FileUtils; //#if MC>=11400 @@ -56,6 +57,7 @@ import net.fabricmc.loader.api.FabricLoader; //$$ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; //$$ import net.minecraftforge.versions.mcp.MCPVersion; //#else +//$$ import static com.replaymod.core.versions.MCVer.FML_BUS; //$$ import net.minecraft.client.resources.IResourcePack; //$$ import net.minecraftforge.common.config.Configuration; //#endif @@ -72,14 +74,12 @@ import net.fabricmc.loader.api.FabricLoader; //$$ import net.minecraftforge.fml.common.Mod.Instance; //$$ import net.minecraftforge.fml.common.event.FMLInitializationEvent; //$$ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -//$$ import static com.replaymod.core.versions.MCVer.FORGE_BUS; //#if MC>=10800 //$$ import net.minecraftforge.fml.client.FMLClientHandler; //#else //$$ import com.replaymod.replay.InputReplayTimer; //$$ //$$ import java.util.ArrayDeque; -//$$ import static com.replaymod.core.versions.MCVer.FML_BUS; //#endif //#endif //$$ import net.minecraftforge.fml.common.Mod; @@ -544,6 +544,10 @@ public class ReplayMod implements inRunLater = true; try { runnable.run(); + } catch (CrashException e) { + e.printStackTrace(); + System.err.println(e.getReport().asString()); + mc.setCrashReport(e.getReport()); } finally { inRunLater = false; } @@ -552,11 +556,11 @@ public class ReplayMod implements //#else //$$ if (mc.isCallingFromMinecraftThread() && inRunLater) { //#if MC>=10800 - //$$ FORGE_BUS.register(new Object() { + //$$ FML_BUS.register(new Object() { //$$ @SubscribeEvent //$$ public void onRenderTick(TickEvent.RenderTickEvent event) { //$$ if (event.phase == TickEvent.Phase.START) { - //$$ FORGE_BUS.unregister(this); + //$$ FML_BUS.unregister(this); //$$ defer.run(); //$$ } //$$ } @@ -578,6 +582,10 @@ public class ReplayMod implements //$$ inRunLater = true; //$$ try { //$$ runnable.run(); + //$$ } catch (ReportedException e) { + //$$ e.printStackTrace(); + //$$ System.err.println(e.getCrashReport().getCompleteReport()); + //$$ mc.crashed(e.getCrashReport()); //$$ } finally { //$$ inRunLater = false; //$$ } diff --git a/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java b/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java index df00bccb..1ec58296 100644 --- a/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java +++ b/src/main/java/com/replaymod/core/mixin/MinecraftAccessor.java @@ -8,6 +8,10 @@ import org.spongepowered.asm.mixin.gen.Accessor; import java.util.Queue; +//#if MC>=11400 +import java.util.concurrent.CompletableFuture; +//#endif + //#if MC<11400 //$$ import java.util.concurrent.FutureTask; //#endif @@ -24,6 +28,13 @@ public interface MinecraftAccessor { @Accessor("renderTickCounter") void setTimer(RenderTickCounter value); + //#if MC>=11400 + @Accessor + CompletableFuture getResourceReloadFuture(); + @Accessor + void setResourceReloadFuture(CompletableFuture value); + //#endif + //#if MC>=11400 @Accessor Queue getRenderTaskQueue(); diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index 5e680fc5..a1996d90 100644 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -104,17 +104,14 @@ public class RecordingEventHandler extends EventRegistrations { } //#endif - public void onPlayerJoin() { + public void spawnRecordingPlayer() { try { - packetListener.save(new PlayerSpawnS2CPacket(mc.player)); - } catch(Exception e1) { - e1.printStackTrace(); - } - } - - public void onPlayerRespawn() { - try { - packetListener.save(new PlayerSpawnS2CPacket(mc.player)); + ClientPlayerEntity player = mc.player; + assert player != null; + packetListener.save(new PlayerSpawnS2CPacket(player)); + //#if MC>=11500 + packetListener.save(new EntityTrackerUpdateS2CPacket(player.getEntityId(), player.getDataTracker(), true)); + //#endif lastX = lastY = lastZ = null; } catch(Exception e) { e.printStackTrace(); diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java index 497a18ec..f739ce52 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java @@ -81,7 +81,7 @@ public abstract class MixinNetHandlerPlayClient { // Only add spawn packet for our own player and only if he isn't known yet if (data.getUuid().equals(mcStatic.player.getGameProfile().getId()) && !this.playerListEntries.containsKey(data.getUuid())) { - handler.onPlayerJoin(); + handler.spawnRecordingPlayer(); } } } catch (IOException e) { @@ -96,7 +96,7 @@ public abstract class MixinNetHandlerPlayClient { //$$ public void recordOwnJoin(S01PacketJoinGame packet, CallbackInfo ci) { //$$ RecordingEventHandler handler = getRecordingEventHandler(); //$$ if (handler != null) { - //$$ handler.onPlayerJoin(); + //$$ handler.spawnRecordingPlayer(); //$$ } //$$ } //#endif @@ -116,7 +116,7 @@ public abstract class MixinNetHandlerPlayClient { public void recordOwnRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) { RecordingEventHandler handler = getRecordingEventHandler(); if (handler != null) { - handler.onPlayerRespawn(); + handler.spawnRecordingPlayer(); } } } diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 754e4553..75c105a3 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -95,10 +95,9 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { } // Update export arguments to match new Preset exportArguments.setText(newPreset.getValue()); - // If the user hasn't changed the output file by themselves, - if (!outputFileManuallySet) { - // generate a new output file name with updated file extension - outputFile = generateOutputFile(newPreset); + // Update output file ending + if (outputFile != null) { + outputFile = conformExtension(outputFile, newPreset); outputFileButton.setLabel(outputFile.getName()); } updateInputs(); @@ -125,15 +124,20 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { public final GuiButton outputFileButton = new GuiButton().setMinSize(new Dimension(0, 20)).onClick(new Runnable() { @Override public void run() { + GuiFileChooserPopup popup = GuiFileChooserPopup.openSaveGui(GuiRenderSettings.this, "replaymod.gui.save", + encodingPresetDropdown.getSelectedValue().getFileExtension()); + popup.setFolder(outputFile.getParentFile()); + popup.setFileName(outputFile.getName()); Futures.addCallback( - GuiFileChooserPopup.openSaveGui(GuiRenderSettings.this, "replaymod.gui.save", - encodingPresetDropdown.getSelectedValue().getFileExtension()).getFuture(), + popup.getFuture(), new FutureCallback() { @Override public void onSuccess(@Nullable File result) { if (result != null) { + if (!result.getName().equals(outputFile.getName())) { + userDefinedOutputFileName = true; + } outputFile = result; - outputFileManuallySet = true; outputFileButton.setLabel(result.getName()); } } @@ -318,7 +322,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { private final ReplayHandler replayHandler; private final Timeline timeline; private File outputFile; - private boolean outputFileManuallySet; + private boolean userDefinedOutputFileName; public GuiRenderSettings(ReplayHandler replayHandler, Timeline timeline) { this.replayHandler = replayHandler; @@ -500,14 +504,19 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { bitRateField.setValue(settings.getBitRate()); bitRateUnit.setSelected(0); } - if (settings.getOutputFile() == null) { - outputFile = generateOutputFile(settings.getEncodingPreset()); - outputFileManuallySet = false; + File savedOutputFile = settings.getOutputFile(); + if (savedOutputFile == null || !savedOutputFile.getParentFile().exists()) { + this.outputFile = generateOutputFile(settings.getEncodingPreset()); + userDefinedOutputFileName = false; + } else if (savedOutputFile.exists()) { + String name = generateOutputFile(settings.getEncodingPreset()).getName(); + this.outputFile = new File(savedOutputFile.isDirectory() ? savedOutputFile : savedOutputFile.getParentFile(), name); + userDefinedOutputFileName = false; } else { - outputFile = settings.getOutputFile(); - outputFileManuallySet = true; + this.outputFile = conformExtension(savedOutputFile, settings.getEncodingPreset()); + userDefinedOutputFileName = true; } - outputFileButton.setLabel(outputFile.getName()); + outputFileButton.setLabel(this.outputFile.getName()); nametagCheckbox.setChecked(settings.isRenderNameTags()); stabilizeYaw.setChecked(settings.isStabilizeYaw()); stabilizePitch.setChecked(settings.isStabilizePitch()); @@ -543,7 +552,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { videoHeight.getInteger(), frameRateSlider.getValue() + 10, bitRateField.getInteger() << (10 * bitRateUnit.getSelected()), - serialize ? null : outputFile, + serialize && !userDefinedOutputFileName ? outputFile.getParentFile() : outputFile, nametagCheckbox.isChecked(), stabilizeYaw.isChecked() && (serialize || stabilizeYaw.isEnabled()), stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()), @@ -564,6 +573,14 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { return new File(folder, fileName + "." + encodingPreset.getFileExtension()); } + protected File conformExtension(File file, RenderSettings.EncodingPreset preset) { + String name = file.getName(); + if (name.contains(".")) { + name = name.substring(0, name.lastIndexOf('.')); + } + return new File(file.getParentFile(), name + "." + preset.getFileExtension()); + } + protected Path getSettingsPath() { return ReplayModRender.instance.getRenderSettingsPath(); } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 3a959c75..de488519 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -44,7 +44,9 @@ import org.lwjgl.opengl.GL11; //#if MC>=11400 import com.replaymod.render.mixin.MainWindowAccessor; +import net.minecraft.client.gui.screen.Screen; import org.lwjgl.glfw.GLFW; +import java.util.concurrent.CompletableFuture; //#else //$$ import net.minecraft.client.gui.ScaledResolution; //$$ import org.lwjgl.input.Mouse; @@ -219,7 +221,15 @@ public class VideoRenderer implements RenderInfo { //#endif if (!settings.isHighPerformance() || framesDone % fps == 0) { - drawGui(); + while (drawGui() && paused) { + try { + //noinspection BusyWait + Thread.sleep(50); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } } // Updating the timer will cause the timeline player to update the game state @@ -398,6 +408,20 @@ public class VideoRenderer implements RenderInfo { private void executeTaskQueue() { //#if MC>=11400 + while (true) { + while (mc.overlay != null) { + drawGui(); + ((MinecraftMethodAccessor) mc).replayModExecuteTaskQueue(); + } + + CompletableFuture resourceReloadFuture = ((MinecraftAccessor) mc).getResourceReloadFuture(); + if (resourceReloadFuture != null) { + ((MinecraftAccessor) mc).setResourceReloadFuture(null); + mc.reloadResources().thenRun(() -> resourceReloadFuture.complete(null)); + continue; + } + break; + } ((MCVer.MinecraftMethodAccessor) mc).replayModExecuteTaskQueue(); //#else //$$ Queue> scheduledTasks = ((MinecraftAccessor) mc).getScheduledTasks(); @@ -430,14 +454,14 @@ public class VideoRenderer implements RenderInfo { //#endif } - public void drawGui() { + public boolean drawGui() { do { //#if MC>=11400 if (GLFW.glfwWindowShouldClose(getWindow(mc).getHandle()) || ((MinecraftAccessor) mc).getCrashReporter() != null) { //#else //$$ if (Display.isCloseRequested() || ((MinecraftAccessor) mc).getCrashReporter() != null) { //#endif - return; + return false; } // Resize the GUI framebuffer if the display size changed @@ -510,12 +534,26 @@ public class VideoRenderer implements RenderInfo { int mouseX = (int) mc.mouse.getX() * getWindow(mc).getScaledWidth() / displayWidth; int mouseY = (int) mc.mouse.getY() * getWindow(mc).getScaledHeight() / displayHeight; - gui.toMinecraft().tick(); - gui.toMinecraft().render( - //#if MC>=11600 - //$$ new MatrixStack(), - //#endif - mouseX, mouseY, 0); + if (mc.overlay != null) { + Screen orgScreen = mc.currentScreen; + try { + mc.currentScreen = gui.toMinecraft(); + mc.overlay.render( + //#if MC>=11600 + //$$ new MatrixStack(), + //#endif + mouseX, mouseY, 0); + } finally { + mc.currentScreen = orgScreen; + } + } else { + gui.toMinecraft().tick(); + gui.toMinecraft().render( + //#if MC>=11600 + //$$ new MatrixStack(), + //#endif + mouseX, mouseY, 0); + } //#else //$$ int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth; //$$ int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1; @@ -558,15 +596,9 @@ public class VideoRenderer implements RenderInfo { //$$ Mouse.setGrabbed(false); //$$ } //#endif - if (paused) { - try { - Thread.sleep(50); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } - } - } while (paused && !hasFailed() && !cancelled); + + return !hasFailed() && !cancelled; + } while (true); } private boolean displaySizeChanged() { diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index da575bea..4e8cd82a 100644 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -70,11 +70,14 @@ import net.minecraft.network.packet.s2c.play.EntitySpawnGlobalS2CPacket; //#endif //#if MC>=11400 +import com.replaymod.core.versions.MCVer; +import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerActionResponseS2CPacket; import net.minecraft.network.packet.s2c.play.OpenContainerS2CPacket; import net.minecraft.network.packet.s2c.play.OpenWrittenBookS2CPacket; import net.minecraft.entity.EntityType; import net.minecraft.text.TranslatableText; +import net.minecraft.world.chunk.light.LightingProvider; //#else //$$ import net.minecraft.client.resources.I18n; //$$ import net.minecraft.entity.Entity; @@ -459,6 +462,24 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#endif } } + + //#if MC>=11400 + if (p instanceof ChunkDataS2CPacket) { + Runnable doLightUpdates = () -> { + if (mc.world != null) { + LightingProvider provider = mc.world.getChunkManager().getLightingProvider(); + while (provider.hasUpdates()) { + provider.doLightUpdates(Integer.MAX_VALUE, true, true); + } + } + }; + if (MCVer.isOnMainThread()) { + doLightUpdates.run(); + } else { + MCVer.scheduleOnMainThread(doLightUpdates); + } + } + //#endif } } catch (Exception e) { // We'd rather not have a failure parsing one packet screw up the whole replay process diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 7ee18cf5..5fa12715 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -284,7 +284,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe //$$ public void onTick(TickEvent.ClientTickEvent event) { //$$ if (event.phase != TickEvent.Phase.START) return; //#endif - if (!asyncMode) return; + if (!asyncMode || paused()) return; long now = System.currentTimeMillis(); long realTimePassed = now - lastAsyncUpdateTime; diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index 975ab97f..c64f4e27 100644 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -578,7 +578,7 @@ public class CameraEntity private void handleInputEvents() { if (this.client.options.keyAttack.wasPressed() || this.client.options.keyUse.wasPressed()) { - if (canSpectate(this.client.targetedEntity)) { + if (this.client.currentScreen == null && canSpectate(this.client.targetedEntity)) { ReplayModReplay.instance.getReplayHandler().spectateEntity( //#if MC<=10710 //$$ (EntityLivingBase) diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java index 705916be..a9bf7f44 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java @@ -100,7 +100,7 @@ public class GuiPathing { public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() { @Override public void run() { - if (!preparePathsForPlayback()) return; + if (!preparePathsForPlayback(false)) return; // Clone the timeline passed to the settings gui as it may be stored for later rendering in a queue SPTimeline spTimeline = mod.getCurrentTimeline(); @@ -299,11 +299,12 @@ public class GuiPathing { if (player.isActive()) { player.getFuture().cancel(false); } else { + boolean ignoreTimeKeyframes = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT); Path timePath = mod.getCurrentTimeline().getTimePath(); - if (!preparePathsForPlayback()) return; + if (!preparePathsForPlayback(ignoreTimeKeyframes)) return; - timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)); + timePath.setActive(!ignoreTimeKeyframes); // 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 future = player.start(mod.getCurrentTimeline().getTimeline(), startTime); @@ -519,10 +520,20 @@ public class GuiPathing { }).start(); } - private boolean preparePathsForPlayback() { + private boolean preparePathsForPlayback(boolean ignoreTimeKeyframes) { SPTimeline timeline = mod.getCurrentTimeline(); timeline.getTimeline().getPaths().forEach(Path::updateAll); + // Make sure there are at least two position keyframes + if (timeline.getPositionPath().getSegments().isEmpty()) { + GuiInfoPopup.open(replayHandler.getOverlay(), "replaymod.chat.morekeyframes"); + return false; + } + + if (ignoreTimeKeyframes) { + return true; + } + // Make sure time keyframes's values are monotonically increasing int lastTime = 0; for (Keyframe keyframe : timeline.getTimePath().getKeyframes()) { @@ -538,9 +549,8 @@ public class GuiPathing { lastTime = time; } - // Make sure there are at least two position- and two time-keyframes - if (timeline.getPositionPath().getSegments().isEmpty() - || timeline.getTimePath().getSegments().isEmpty()) { + // Make sure there are at least two time keyframes + if (timeline.getTimePath().getSegments().isEmpty()) { GuiInfoPopup.open(replayHandler.getOverlay(), "replaymod.chat.morekeyframes"); return false; } diff --git a/version.txt b/version.txt index 2bf1c1cc..f90b1afc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.1 +2.3.2