Release 2.6.4

This commit is contained in:
Jonas Herzig
2022-03-09 07:09:00 +01:00
66 changed files with 711 additions and 339 deletions

View File

@@ -245,6 +245,7 @@ dependencies {
11701: '1.17.1', 11701: '1.17.1',
11800: '1.18', 11800: '1.18',
11801: '1.18.1', 11801: '1.18.1',
11802: '1.18.2',
][mcVersion] ][mcVersion]
mappings 'net.fabricmc:yarn:' + [ mappings 'net.fabricmc:yarn:' + [
11404: '1.14.4+build.16', 11404: '1.14.4+build.16',
@@ -256,6 +257,7 @@ dependencies {
11701: '1.17.1+build.29:v2', 11701: '1.17.1+build.29:v2',
11800: '1.18+build.1:v2', 11800: '1.18+build.1:v2',
11801: '1.18.1+build.1:v2', 11801: '1.18.1+build.1:v2',
11802: '1.18.2+build.1:v2',
][mcVersion] ][mcVersion]
modImplementation 'net.fabricmc:fabric-loader:0.12.5' modImplementation 'net.fabricmc:fabric-loader:0.12.5'
def fabricApiVersion = [ def fabricApiVersion = [
@@ -268,6 +270,7 @@ dependencies {
11701: '0.37.1+1.17', 11701: '0.37.1+1.17',
11800: '0.43.1+1.18', 11800: '0.43.1+1.18',
11801: '0.43.1+1.18', 11801: '0.43.1+1.18',
11802: '0.47.9+1.18.2',
][mcVersion] ][mcVersion]
def fabricApiModules = [ def fabricApiModules = [
"api-base", "api-base",
@@ -338,7 +341,7 @@ dependencies {
shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8'
shadow "com.github.ReplayMod:ReplayStudio:70f59ef", shadeExclusions shadow "com.github.ReplayMod:ReplayStudio:b5539d1", shadeExclusions
implementation(FABRIC ? dependencies.project(path: jGui.path, configuration: "namedElements") : jGui) { implementation(FABRIC ? dependencies.project(path: jGui.path, configuration: "namedElements") : jGui) {
transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those
@@ -346,21 +349,27 @@ dependencies {
shadow 'com.github.ReplayMod:lwjgl-utils:27dcd66' shadow 'com.github.ReplayMod:lwjgl-utils:27dcd66'
if (FABRIC) { if (FABRIC) {
if (mcVersion >= 11800) { if (mcVersion >= 11802) {
modImplementation 'com.terraformersmc:modmenu:3.1.0'
} else if (mcVersion >= 11800) {
modImplementation 'com.terraformersmc:modmenu:3.0.0' modImplementation 'com.terraformersmc:modmenu:3.0.0'
} else if (mcVersion >= 11700) { } else if (mcVersion >= 11700) {
modImplementation 'com.terraformersmc:modmenu:2.0.0-beta.7' modImplementation 'com.terraformersmc:modmenu:2.0.0-beta.7'
} else if (mcVersion >= 11602) { } else if (mcVersion >= 11602) {
modImplementation 'com.terraformersmc:modmenu:1.16.8' modImplementation 'com.terraformersmc:modmenu:1.16.8'
} else if (mcVersion >= 11600) { } else if (mcVersion >= 11600) {
modImplementation 'io.github.prospector:modmenu:1.14.0+build.24' modImplementation('com.terraformersmc:modmenu:1.14.15') {
exclude module: 'fabric-resource-loader-v0' // inappropriate version for 1.16.1
}
} else { } else {
modImplementation 'io.github.prospector.modmenu:ModMenu:1.6.2-92' modImplementation 'com.terraformersmc:modmenu:1.10.6'
} }
} }
if (mcVersion >= 11600) { if (mcVersion >= 11600) {
modCompileOnly 'com.github.IrisShaders:Iris:1.0.0' modCompileOnly("com.github.IrisShaders:Iris:1.18.x~v1.2.0") {
transitive = false // we do not want to upgrade our libs, we only need this to compile our mixins
}
} }
testImplementation 'junit:junit:4.11' testImplementation 'junit:junit:4.11'

View File

@@ -465,7 +465,7 @@ If you have a Replay in a dark setting (for example at nighttime, or in a cave)
This works as a replacement for the **Night Vision Potion Effect**, without the side effect of a weird sky color. This works as a replacement for the **Night Vision Potion Effect**, without the side effect of a weird sky color.
## Quick Mode [quickmode] (Minecraft 1.9 and up) ## Quick Mode [quickmode]
![](img/quickmode-icon.jpg) ![](img/quickmode-icon.jpg)
In **Quick Mode**, this clock symbol is displayed in the lower right corner of the screen. In **Quick Mode**, this clock symbol is displayed in the lower right corner of the screen.
@@ -473,8 +473,6 @@ When you first enable **Quick Mode** in a replay, an internal reference of certa
As a side effect, certain features like particles and second skin layers will not be rendered in the preview. As a side effect, certain features like particles and second skin layers will not be rendered in the preview.
By default, **Quick Mode** is toggled with `Q`. By default, **Quick Mode** is toggled with `Q`.
**Quick Mode** is available in ReplayMod for Minecraft 1.9.4 and up.
## Player Overview [overview] ## Player Overview [overview]
![](img/player-overview.jpg) ![](img/player-overview.jpg)
The **Player Overview** Screen The **Player Overview** Screen

2
jGui

Submodule jGui updated: c79b62a73e...5e41452b0e

View File

@@ -189,6 +189,7 @@ val doRelease by tasks.registering {
defaultTasks("bundleJar") defaultTasks("bundleJar")
preprocess { preprocess {
val mc11802 = createNode("1.18.2", 11802, "yarn")
val mc11801 = createNode("1.18.1", 11801, "yarn") val mc11801 = createNode("1.18.1", 11801, "yarn")
val mc11701 = createNode("1.17.1", 11701, "yarn") val mc11701 = createNode("1.17.1", 11701, "yarn")
val mc11700 = createNode("1.17", 11700, "yarn") val mc11700 = createNode("1.17", 11700, "yarn")
@@ -208,6 +209,7 @@ preprocess {
val mc10800 = createNode("1.8", 10800, "srg") val mc10800 = createNode("1.8", 10800, "srg")
val mc10710 = createNode("1.7.10", 10710, "srg") val mc10710 = createNode("1.7.10", 10710, "srg")
mc11802.link(mc11801)
mc11801.link(mc11701, file("versions/mapping-fabric-1.18.1-1.17.1.txt")) mc11801.link(mc11701, file("versions/mapping-fabric-1.18.1-1.17.1.txt"))
mc11701.link(mc11700) mc11701.link(mc11700)
mc11700.link(mc11604, file("versions/mapping-fabric-1.17-1.16.4.txt")) mc11700.link(mc11604, file("versions/mapping-fabric-1.17-1.16.4.txt"))

View File

@@ -32,6 +32,7 @@ val jGuiVersions = listOf(
"1.17", "1.17",
"1.17.1", "1.17.1",
"1.18.1", "1.18.1",
"1.18.2",
) )
val replayModVersions = listOf( val replayModVersions = listOf(
// "1.7.10", // "1.7.10",
@@ -52,6 +53,7 @@ val replayModVersions = listOf(
"1.17", "1.17",
"1.17.1", "1.17.1",
"1.18.1", "1.18.1",
"1.18.2",
) )
rootProject.buildFileName = "root.gradle.kts" rootProject.buildFileName = "root.gradle.kts"

View File

@@ -9,6 +9,10 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
//#if FABRIC
import net.fabricmc.loader.api.FabricLoader;
//#endif
//#if MC>=11400 //#if MC>=11400
import java.io.InputStream; import java.io.InputStream;
//#else //#else
@@ -34,6 +38,9 @@ public class ReplayModMixinConfigPlugin implements IMixinConfigPlugin {
private final Logger logger = LogManager.getLogger("replaymod/mixin"); private final Logger logger = LogManager.getLogger("replaymod/mixin");
private final boolean hasOF = hasClass("optifine.OptiFineForgeTweaker") || hasClass("me.modmuss50.optifabric.mod.Optifabric"); private final boolean hasOF = hasClass("optifine.OptiFineForgeTweaker") || hasClass("me.modmuss50.optifabric.mod.Optifabric");
//#if FABRIC
private final boolean hasIris = FabricLoader.getInstance().isModLoaded("iris");
//#endif
{ {
logger.debug("hasOF: " + hasOF); logger.debug("hasOF: " + hasOF);
@@ -49,6 +56,9 @@ public class ReplayModMixinConfigPlugin implements IMixinConfigPlugin {
} }
if (mixinClassName.endsWith("_OF")) return hasOF; if (mixinClassName.endsWith("_OF")) return hasOF;
if (mixinClassName.endsWith("_NoOF")) return !hasOF; if (mixinClassName.endsWith("_NoOF")) return !hasOF;
//#if FABRIC
if (mixinClassName.endsWith("_Iris")) return hasIris;
//#endif
return true; return true;
} }

View File

@@ -22,6 +22,7 @@ import java.nio.file.WatchService;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.replaymod.core.utils.Utils.ensureDirectoryExists;
import static com.replaymod.core.versions.MCVer.getMinecraft; import static com.replaymod.core.versions.MCVer.getMinecraft;
class SettingsRegistryBackend { class SettingsRegistryBackend {
@@ -179,7 +180,7 @@ class SettingsRegistryBackend {
Gson gson = new GsonBuilder().setPrettyPrinting().create(); Gson gson = new GsonBuilder().setPrettyPrinting().create();
String config = gson.toJson(root); String config = gson.toJson(root);
try { try {
Files.createDirectories(configFile.getParent()); ensureDirectoryExists(configFile.getParent());
Files.write(configFile, config.getBytes(StandardCharsets.UTF_8)); Files.write(configFile, config.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();

View File

@@ -10,6 +10,8 @@ import java.net.URLDecoder;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static com.replaymod.core.utils.Utils.ensureDirectoryExists;
public class ReplayFoldersService { public class ReplayFoldersService {
private final Path mcDir = MinecraftClient.getInstance().runDirectory.toPath(); private final Path mcDir = MinecraftClient.getInstance().runDirectory.toPath();
private final SettingsRegistry settings; private final SettingsRegistry settings;
@@ -19,14 +21,14 @@ public class ReplayFoldersService {
} }
public Path getReplayFolder() throws IOException { public Path getReplayFolder() throws IOException {
return Files.createDirectories(mcDir.resolve(settings.get(Setting.RECORDING_PATH))); return ensureDirectoryExists(mcDir.resolve(settings.get(Setting.RECORDING_PATH)));
} }
/** /**
* Folder into which replay backups are saved before the MarkerProcessor is unleashed. * Folder into which replay backups are saved before the MarkerProcessor is unleashed.
*/ */
public Path getRawReplayFolder() throws IOException { public Path getRawReplayFolder() throws IOException {
return Files.createDirectories(getReplayFolder().resolve("raw")); return ensureDirectoryExists(getReplayFolder().resolve("raw"));
} }
/** /**
@@ -34,7 +36,7 @@ public class ReplayFoldersService {
* Distinct from the main folder, so they cannot be opened while they are still saving. * Distinct from the main folder, so they cannot be opened while they are still saving.
*/ */
public Path getRecordingFolder() throws IOException { public Path getRecordingFolder() throws IOException {
return Files.createDirectories(getReplayFolder().resolve("recording")); return ensureDirectoryExists(getReplayFolder().resolve("recording"));
} }
/** /**
@@ -42,7 +44,7 @@ public class ReplayFoldersService {
* Distinct from the recording folder cause people kept confusing them with recordings. * Distinct from the recording folder cause people kept confusing them with recordings.
*/ */
public Path getCacheFolder() throws IOException { public Path getCacheFolder() throws IOException {
Path path = Files.createDirectories(mcDir.resolve(settings.get(Setting.CACHE_PATH))); Path path = ensureDirectoryExists(mcDir.resolve(settings.get(Setting.CACHE_PATH)));
try { try {
Files.setAttribute(path, "dos:hidden", true); Files.setAttribute(path, "dos:hidden", true);
} catch (UnsupportedOperationException ignored) { } catch (UnsupportedOperationException ignored) {

View File

@@ -56,11 +56,16 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
new GuiLabel().setI18nText("replaymod.gui.restorereplay1"), new GuiLabel().setI18nText("replaymod.gui.restorereplay1"),
new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())), new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())),
new GuiLabel().setI18nText("replaymod.gui.restorereplay3")); new GuiLabel().setI18nText("replaymod.gui.restorereplay3"));
LOGGER.info("Found partially saved replay, offering recovery: " + file);
yesButton.onClick(() -> { yesButton.onClick(() -> {
LOGGER.info("Attempting recovery: " + file);
recoverInBackground(); recoverInBackground();
parent.display(); parent.display();
}); });
noButton.onClick(() -> { noButton.onClick(() -> {
LOGGER.info("Recovery rejected, marking for deletion: " + file);
try { try {
File tmp = new File(file.getParentFile(), file.getName() + ".tmp"); File tmp = new File(file.getParentFile(), file.getName() + ".tmp");
File deleted = new File(file.getParentFile(), file.getName() + ".del"); File deleted = new File(file.getParentFile(), file.getName() + ".del");

View File

@@ -63,6 +63,7 @@ public abstract class MixinMinecraft
PostRenderCallback.EVENT.invoker().postRender(); PostRenderCallback.EVENT.invoker().postRender();
} }
//#else //#else
//$$ @Shadow long systemTime;
//#if MC>=10904 //#if MC>=10904
//$$ @Shadow protected abstract void runTickKeyboard() throws IOException; //$$ @Shadow protected abstract void runTickKeyboard() throws IOException;
//$$ @Shadow protected abstract void runTickMouse() throws IOException; //$$ @Shadow protected abstract void runTickMouse() throws IOException;
@@ -80,6 +81,8 @@ public abstract class MixinMinecraft
//$$ public void replayModRunTickMouse() { //$$ public void replayModRunTickMouse() {
//$$ try { //$$ try {
//$$ runTickMouse(); //$$ runTickMouse();
//$$ // Update last tick time (MC ignores inputs when there hasn't been a tick in 200ms)
//$$ systemTime = Minecraft.getSystemTime();
//$$ } catch (IOException e) { //$$ } catch (IOException e) {
//$$ e.printStackTrace(); //$$ e.printStackTrace();
//$$ } //$$ }
@@ -94,7 +97,12 @@ public abstract class MixinMinecraft
//$$ //$$
//$$ @Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;sendClickBlockToController(Z)V"), cancellable = true) //$$ @Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;sendClickBlockToController(Z)V"), cancellable = true)
//$$ private void doEarlyReturnFromRunTick(CallbackInfo ci) { //$$ private void doEarlyReturnFromRunTick(CallbackInfo ci) {
//$$ if (earlyReturn) ci.cancel(); //$$ if (earlyReturn) {
//$$ ci.cancel();
//$$
//$$ // Update last tick time (MC ignores inputs when there hasn't been a tick in 200ms)
//$$ systemTime = Minecraft.getSystemTime();
//$$ }
//$$ } //$$ }
//#endif //#endif
//$$ @Redirect( //$$ @Redirect(

View File

@@ -55,8 +55,13 @@ import javax.net.ssl.TrustManagerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
@@ -166,8 +171,45 @@ public class Utils {
private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false); private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false);
public static String replayNameToFileName(String replayName) { public static Path replayNameToPath(Path folder, String replayName) {
return REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr"; // If we can, prefer directly using the replay name as the file name
if (isUsable(folder, replayName + ".mcpr")) {
return folder.resolve(replayName + ".mcpr");
} else {
// otherwise, fall back to percent encoding
return folder.resolve(REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr");
}
}
/**
* Checks whether a given file name is actually usable with the file system / operating system at the given folder.
*/
private static boolean isUsable(Path folder, String fileName) {
Path path = folder.resolve(fileName);
if (Files.exists(path)) {
return true; // if it already exits, it's definitely usable
}
// Otherwise, there's no sure way to know, so we just gotta try
try (OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE_NEW)) {
outputStream.flush();
} catch (IOException e) {
return false;
}
// Looking good, but now we gotta clean up that mess (and Anti-Virus / Cloud Sync are know to lock them)
int attempts = 0;
while (true) {
try {
Files.delete(path);
return true;
} catch (IOException e) {
if (attempts++ > 100) {
LOGGER.warn("Repeatedly failed to clean up temporary test file at " + path + ": ", e);
return false; // while we were able to use it, it's taken now and we can't get it back
}
}
}
} }
public static String fileNameToReplayName(String fileName) { public static String fileNameToReplayName(String fileName) {
@@ -356,4 +398,14 @@ public class Utils {
configure.accept(instance); configure.accept(instance);
return instance; return instance;
} }
/**
* Like {@link Files#createDirectories(Path, FileAttribute[])} but doesn't explode if it's a symlink.
*/
public static Path ensureDirectoryExists(Path path) throws IOException {
// Who in their right mind thought the default behavior of throwing when the target is a link to a directory
// was the preferred behavior?! Everyone has to fall for this at least once to learn it...
// https://bugs.openjdk.java.net/browse/JDK-8130464
return Files.createDirectories(Files.exists(path) ? path.toRealPath() : path);
}
} }

View File

@@ -304,7 +304,7 @@ public class MCVer {
//#if MC<=11601 //#if MC<=11601
//$$ public static Vec3d getTrackedPosition(Entity entity) { //$$ public static Vec3d getTrackedPosition(Entity entity) {
//$$ return new Vec3d(entity.trackedX, entity.trackedY, entity.trackedZ); //$$ return new Vec3d(entity.trackedX / 4096.0, entity.trackedY / 4096.0, entity.trackedZ / 4096.0);
//$$ } //$$ }
//#endif //#endif

View File

@@ -21,6 +21,8 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.lwjgl.Color; import de.johni0702.minecraft.gui.utils.lwjgl.Color;
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
import net.minecraft.util.crash.CrashReport; import net.minecraft.util.crash.CrashReport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
@@ -31,6 +33,8 @@ import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> { public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
private static final Logger LOGGER = LogManager.getLogger();
private final Path inputPath; private final Path inputPath;
private final EditTimeline timeline; private final EditTimeline timeline;
@@ -60,6 +64,8 @@ public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
super(container); super(container);
this.inputPath = inputPath; this.inputPath = inputPath;
LOGGER.info("Opening replay in editor: " + inputPath);
try (ReplayFile replayFile = ReplayMod.instance.files.open(inputPath)) { try (ReplayFile replayFile = ReplayMod.instance.files.open(inputPath)) {
markers = replayFile.getMarkers().or(HashSet::new); markers = replayFile.getMarkers().or(HashSet::new);
timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers); timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers);

View File

@@ -133,7 +133,6 @@ public class MarkerProcessor {
for (int i = 1; Files.exists(inputPath); i++) { for (int i = 1; Files.exists(inputPath); i++) {
inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr"); inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr");
} }
Files.createDirectories(inputPath.getParent());
Files.move(path, inputPath); Files.move(path, inputPath);
try (ReplayFile inputReplayFile = mod.files.open(inputPath)) { try (ReplayFile inputReplayFile = mod.files.open(inputPath)) {

View File

@@ -25,7 +25,7 @@ public class QuickMode extends EventRegistrations implements Extra {
return; return;
} }
replayHandler.getReplaySender().setSyncModeAndWait(); replayHandler.getReplaySender().setSyncModeAndWait();
mod.runLater(() -> { mod.runLaterWithoutLock(() -> {
replayHandler.ensureQuickModeInitialized(() -> { replayHandler.ensureQuickModeInitialized(() -> {
boolean enabled = !replayHandler.isQuickMode(); boolean enabled = !replayHandler.isQuickMode();
updateIndicator(replayHandler.getOverlay(), enabled); updateIndicator(replayHandler.getOverlay(), enabled);

View File

@@ -40,7 +40,7 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable {
new GuiLabel().setI18nText("replaymod.gui.advancedscreenshots.resolution"), videoResolutionPanel, new GuiLabel().setI18nText("replaymod.gui.advancedscreenshots.resolution"), videoResolutionPanel,
new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton); new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton);
resetChildren(advancedPanel).addElements(null, nametagCheckbox, new GuiPanel().setLayout( resetChildren(advancedPanel).addElements(null, nametagCheckbox, alphaCheckbox , new GuiPanel().setLayout(
new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15)) new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15))
.addElements(new GridLayout.Data(0, 0.5), .addElements(new GridLayout.Data(0, 0.5),
new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel,

View File

@@ -155,9 +155,9 @@ public class GuiSavingReplay {
try { try {
Path replaysFolder = core.folders.getReplayFolder(); Path replaysFolder = core.folders.getReplayFolder();
Path newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName)); Path newPath = Utils.replayNameToPath(replaysFolder, newName);
for (int i = 1; Files.exists(newPath); i++) { for (int i = 1; Files.exists(newPath); i++) {
newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName + " (" + i + ")")); newPath = Utils.replayNameToPath(replaysFolder, newName + " (" + i + ")");
} }
Files.move(path, newPath); Files.move(path, newPath);
} catch (IOException e) { } catch (IOException e) {

View File

@@ -127,7 +127,7 @@ public class ConnectionEventHandler {
} }
String name = sdf.format(Calendar.getInstance().getTime()); String name = sdf.format(Calendar.getInstance().getTime());
Path outputPath = core.folders.getRecordingFolder().resolve(Utils.replayNameToFileName(name)); Path outputPath = Utils.replayNameToPath(core.folders.getRecordingFolder(), name);
ReplayFile replayFile = core.files.open(outputPath); ReplayFile replayFile = core.files.open(outputPath);
replayFile.writeModInfo(ModCompat.getInstalledNetworkMods()); replayFile.writeModInfo(ModCompat.getInstalledNetworkMods());

View File

@@ -33,7 +33,9 @@ public abstract class MixinMouseHelper {
long _p0, double _p1, double _p2, long _p0, double _p1, double _p2,
CallbackInfo ci, CallbackInfo ci,
double _l1, double _l1,
//#if MC>=11400 //#if MC>=11802
//$$ int yOffsetAccumulated
//#elseif MC>=11400
float yOffsetAccumulated float yOffsetAccumulated
//#else //#else
//$$ double yOffsetAccumulated //$$ double yOffsetAccumulated

View File

@@ -16,6 +16,10 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
//#if MC>=11802
//$$ import net.minecraft.util.registry.RegistryEntry;
//#endif
//#if MC>=11600 //#if MC>=11600
import net.minecraft.util.registry.RegistryKey; import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.MutableWorldProperties; import net.minecraft.world.MutableWorldProperties;
@@ -52,7 +56,12 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa
//#if MC<11602 //#if MC<11602
//$$ RegistryKey<DimensionType> registryKey2, //$$ RegistryKey<DimensionType> registryKey2,
//#endif //#endif
DimensionType dimensionType, Supplier<Profiler> profiler, boolean bl, boolean bl2, long l) { //#if MC>=11802
//$$ RegistryEntry<DimensionType> dimensionType,
//#else
DimensionType dimensionType,
//#endif
Supplier<Profiler> profiler, boolean bl, boolean bl2, long l) {
super(mutableWorldProperties, registryKey, super(mutableWorldProperties, registryKey,
//#if MC<11602 //#if MC<11602
//$$ registryKey2, //$$ registryKey2,

View File

@@ -285,7 +285,6 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
for (int i = 1; Files.exists(rawPath); i++) { for (int i = 1; Files.exists(rawPath); i++) {
rawPath = rawPath.resolveSibling(replayName + "." + i + ".mcpr"); rawPath = rawPath.resolveSibling(replayName + "." + i + ".mcpr");
} }
Files.createDirectories(rawPath.getParent());
replayFile.saveTo(rawPath.toFile()); replayFile.saveTo(rawPath.toFile());
replayFile.close(); replayFile.close();

View File

@@ -28,9 +28,11 @@ import static org.lwjgl.util.tinyexr.TinyEXR.*;
public class EXRWriter implements FrameConsumer<BitmapFrame> { public class EXRWriter implements FrameConsumer<BitmapFrame> {
private final Path outputFolder; private final Path outputFolder;
private final boolean keepAlpha;
public EXRWriter(Path outputFolder) throws IOException { public EXRWriter(Path outputFolder, boolean keepAlpha) throws IOException {
this.outputFolder = outputFolder; this.outputFolder = outputFolder;
this.keepAlpha = keepAlpha;
Files.createDirectories(outputFolder); Files.createDirectories(outputFolder);
} }
@@ -92,11 +94,15 @@ public class EXRWriter implements FrameConsumer<BitmapFrame> {
bgrChannels[(i + 3) % 4] = channel; bgrChannels[(i + 3) % 4] = channel;
} }
} }
int alphaMask = keepAlpha ? 0 : 0xff;
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
for (FloatBuffer channel : bgrChannels) { bgrChannels[0].put(((int) bgra.get() & 0xff) / 255f); // b
channel.put(((int) bgra.get() & 0xff) / 255f); bgrChannels[1].put(((int) bgra.get() & 0xff) / 255f); // g
} bgrChannels[2].put(((int) bgra.get() & 0xff) / 255f); // r
bgrChannels[3].put(((int) bgra.get() & 0xff | alphaMask) / 255f); // a
} }
} }
if (depthFrame != null && depthChannel != null) { if (depthFrame != null && depthChannel != null) {

View File

@@ -19,9 +19,11 @@ import java.util.Map;
public class PNGWriter implements FrameConsumer<BitmapFrame> { public class PNGWriter implements FrameConsumer<BitmapFrame> {
private final Path outputFolder; private final Path outputFolder;
private final boolean keepAlpha;
public PNGWriter(Path outputFolder) throws IOException { public PNGWriter(Path outputFolder, boolean keepAlpha) throws IOException {
this.outputFolder = outputFolder; this.outputFolder = outputFolder;
this.keepAlpha = keepAlpha;
Files.createDirectories(outputFolder); Files.createDirectories(outputFolder);
} }
@@ -47,6 +49,7 @@ public class PNGWriter implements FrameConsumer<BitmapFrame> {
} }
private void withImage(BitmapFrame frame, IOConsumer<Image> consumer) throws IOException { private void withImage(BitmapFrame frame, IOConsumer<Image> consumer) throws IOException {
byte alphaMask = (byte) (keepAlpha ? 0 : 0xff);
ByteBuffer buffer = frame.getByteBuffer(); ByteBuffer buffer = frame.getByteBuffer();
ReadableDimension size = frame.getSize(); ReadableDimension size = frame.getSize();
int width = size.getWidth(); int width = size.getWidth();
@@ -58,7 +61,7 @@ public class PNGWriter implements FrameConsumer<BitmapFrame> {
byte g = buffer.get(); byte g = buffer.get();
byte r = buffer.get(); byte r = buffer.get();
byte a = buffer.get(); byte a = buffer.get();
image.setRGBA(x, y, r, g, b, a); image.setRGBA(x, y, r, g, b, a | alphaMask);
} }
} }
consumer.accept(image); consumer.accept(image);

View File

@@ -151,6 +151,7 @@ public class RenderSettings {
private final File outputFile; private final File outputFile;
private final boolean renderNameTags; private final boolean renderNameTags;
private final boolean includeAlphaChannel;
private final boolean stabilizeYaw; private final boolean stabilizeYaw;
private final boolean stabilizePitch; private final boolean stabilizePitch;
private final boolean stabilizeRoll; private final boolean stabilizeRoll;
@@ -187,6 +188,7 @@ public class RenderSettings {
false, false,
false, false,
false, false,
false,
null, null,
360, 360,
180, 180,
@@ -209,6 +211,7 @@ public class RenderSettings {
int bitRate, int bitRate,
File outputFile, File outputFile,
boolean renderNameTags, boolean renderNameTags,
boolean includeAlphaChannel,
boolean stabilizeYaw, boolean stabilizeYaw,
boolean stabilizePitch, boolean stabilizePitch,
boolean stabilizeRoll, boolean stabilizeRoll,
@@ -231,6 +234,7 @@ public class RenderSettings {
this.bitRate = bitRate; this.bitRate = bitRate;
this.outputFile = outputFile; this.outputFile = outputFile;
this.renderNameTags = renderNameTags; this.renderNameTags = renderNameTags;
this.includeAlphaChannel = includeAlphaChannel;
this.stabilizeYaw = stabilizeYaw; this.stabilizeYaw = stabilizeYaw;
this.stabilizePitch = stabilizePitch; this.stabilizePitch = stabilizePitch;
this.stabilizeRoll = stabilizeRoll; this.stabilizeRoll = stabilizeRoll;
@@ -256,6 +260,7 @@ public class RenderSettings {
bitRate, bitRate,
outputFile, outputFile,
renderNameTags, renderNameTags,
includeAlphaChannel,
stabilizeYaw, stabilizeYaw,
stabilizePitch, stabilizePitch,
stabilizeRoll, stabilizeRoll,
@@ -415,6 +420,10 @@ public class RenderSettings {
return renderNameTags; return renderNameTags;
} }
public boolean isIncludeAlphaChannel() {
return includeAlphaChannel;
}
public boolean isStabilizeYaw() { public boolean isStabilizeYaw() {
return stabilizeYaw; return stabilizeYaw;
} }
@@ -478,6 +487,7 @@ public class RenderSettings {
", bitRate=" + bitRate + ", bitRate=" + bitRate +
", outputFile=" + outputFile + ", outputFile=" + outputFile +
", renderNameTags=" + renderNameTags + ", renderNameTags=" + renderNameTags +
", includeAlphaChannel=" + includeAlphaChannel +
", stabilizeYaw=" + stabilizeYaw + ", stabilizeYaw=" + stabilizeYaw +
", stabilizePitch=" + stabilizePitch + ", stabilizePitch=" + stabilizePitch +
", stabilizeRoll=" + stabilizeRoll + ", stabilizeRoll=" + stabilizeRoll +

View File

@@ -1,7 +1,6 @@
//#if MC>=11600 //#if MC>=11600
package com.replaymod.render.capturer; package com.replaymod.render.capturer;
import com.mojang.blaze3d.platform.GlStateManager;
import com.replaymod.render.RenderSettings; import com.replaymod.render.RenderSettings;
import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.CubicOpenGlFrame;
import com.replaymod.render.frame.ODSOpenGlFrame; import com.replaymod.render.frame.ODSOpenGlFrame;
@@ -16,18 +15,13 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static com.replaymod.core.versions.MCVer.popMatrix;
import static com.replaymod.core.versions.MCVer.pushMatrix;
import static com.replaymod.core.versions.MCVer.resizeMainWindow;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> { public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
public static final String SHADER_PACK_NAME = "assets/replaymod/iris/ods"; public static final String SHADER_PACK_NAME = "assets/replaymod/iris/ods";
public static IrisODSFrameCapturer INSTANCE; public static IrisODSFrameCapturer INSTANCE;
private final CubicPboOpenGlFrameCapturer left, right; private final CubicPboOpenGlFrameCapturer left, right;
private final String prevShaderPack; private final String prevShaderPack;
private final boolean prevShadersEnabled;
private int direction; private int direction;
private boolean isLeftEye; private boolean isLeftEye;
@@ -67,13 +61,16 @@ public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize); right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize);
INSTANCE = this; INSTANCE = this;
prevShaderPack = Iris.getIrisConfig().getShaderPackName().orElse(null); IrisConfig irisConfig = Iris.getIrisConfig();
setShaderPack(SHADER_PACK_NAME); prevShaderPack = irisConfig.getShaderPackName().orElse(null);
prevShadersEnabled = irisConfig.areShadersEnabled();
setShaderPack(SHADER_PACK_NAME, true);
} }
private static void setShaderPack(String name) { private static void setShaderPack(String name, boolean enabled) {
IrisConfig irisConfig = Iris.getIrisConfig(); IrisConfig irisConfig = Iris.getIrisConfig();
irisConfig.setShaderPackName(name); irisConfig.setShaderPackName(name);
irisConfig.setShadersEnabled(enabled);
try { try {
irisConfig.save(); irisConfig.save();
Iris.reload(); Iris.reload();
@@ -121,7 +118,7 @@ public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
left.close(); left.close();
right.close(); right.close();
INSTANCE = null; INSTANCE = null;
setShaderPack(prevShaderPack); setShaderPack(prevShaderPack, prevShadersEnabled);
} }
private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer { private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer {
@@ -131,25 +128,8 @@ public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
@Override @Override
protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) {
resizeMainWindow(mc, getFrameWidth(), getFrameHeight());
pushMatrix();
frameBuffer().beginWrite(true);
GlStateManager.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
//#if MC>=11400
, false
//#endif
);
GlStateManager.enableTexture();
direction = captureData.ordinal(); direction = captureData.ordinal();
worldRenderer.renderWorld(partialTicks, null); return super.renderFrame(frameId, partialTicks, captureData);
frameBuffer().endWrite();
popMatrix();
return captureFrame(frameId, captureData);
} }
} }
} }

View File

@@ -1,6 +1,5 @@
package com.replaymod.render.capturer; package com.replaymod.render.capturer;
import com.mojang.blaze3d.platform.GlStateManager;
import com.replaymod.render.rendering.Channel; import com.replaymod.render.rendering.Channel;
import de.johni0702.minecraft.gui.utils.EventRegistrations; import de.johni0702.minecraft.gui.utils.EventRegistrations;
import com.replaymod.render.RenderSettings; import com.replaymod.render.RenderSettings;
@@ -20,12 +19,6 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static com.replaymod.core.versions.MCVer.popMatrix;
import static com.replaymod.core.versions.MCVer.pushMatrix;
import static com.replaymod.core.versions.MCVer.resizeMainWindow;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> { public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
private static final Identifier vertexResource = new Identifier("replaymod", "shader/ods.vert"); private static final Identifier vertexResource = new Identifier("replaymod", "shader/ods.vert");
private static final Identifier fragmentResource = new Identifier("replaymod", "shader/ods.frag"); private static final Identifier fragmentResource = new Identifier("replaymod", "shader/ods.frag");
@@ -166,25 +159,8 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
@Override @Override
protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) {
resizeMainWindow(mc, getFrameWidth(), getFrameHeight());
pushMatrix();
frameBuffer().beginWrite(true);
GlStateManager.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
//#if MC>=11400
, false
//#endif
);
GlStateManager.enableTexture();
directionVariable.set(captureData.ordinal()); directionVariable.set(captureData.ordinal());
worldRenderer.renderWorld(partialTicks, null); return super.renderFrame(frameId, partialTicks, captureData);
frameBuffer().endWrite();
popMatrix();
return captureFrame(frameId, captureData);
} }
} }
} }

View File

@@ -90,6 +90,7 @@ public class GuiExportFailed extends GuiScreen {
oldSettings.getBitRate(), oldSettings.getBitRate(),
oldSettings.getOutputFile(), oldSettings.getOutputFile(),
oldSettings.isRenderNameTags(), oldSettings.isRenderNameTags(),
oldSettings.isIncludeAlphaChannel(),
oldSettings.isStabilizeYaw(), oldSettings.isStabilizeYaw(),
oldSettings.isStabilizePitch(), oldSettings.isStabilizePitch(),
oldSettings.isStabilizeRoll(), oldSettings.isStabilizeRoll(),

View File

@@ -0,0 +1,48 @@
package com.replaymod.render.gui;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.container.GuiScreen;
import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import java.net.URI;
import static com.replaymod.core.versions.MCVer.openURL;
import static de.johni0702.minecraft.gui.versions.MCVer.setClipboardString;
public class GuiNoFfmpeg extends GuiScreen {
private static final String LINK = "https://www.replaymod.com/docs/#installing-ffmpeg";
private final GuiLabel message = new GuiLabel()
.setI18nText("replaymod.gui.rendering.error.message");
private final GuiLabel link = new GuiLabel()
.setText(LINK);
private final GuiButton openLinkButton = new GuiButton()
.setI18nLabel("chat.link.open")
.setSize(100, 20)
.onClick(() -> openURL(URI.create(LINK)));
private final GuiButton copyToClipboardButton = new GuiButton()
.setI18nLabel("chat.copy")
.setSize(100, 20)
.onClick(() -> setClipboardString(LINK));
private final GuiButton backButton = new GuiButton()
.setI18nLabel("gui.back")
.setSize(100, 20);
private final GuiPanel buttons = new GuiPanel()
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(4))
.addElements(null, openLinkButton, copyToClipboardButton, backButton);
{
setBackground(Background.DIRT);
setTitle(new GuiLabel().setI18nText("replaymod.gui.rendering.error.title"));
setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(30));
addElements(new VerticalLayout.Data(0.5), message, link, buttons);
}
public GuiNoFfmpeg(Runnable goBack) {
backButton.onClick(goBack);
}
}

View File

@@ -37,7 +37,6 @@ import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.NoticeScreen;
import net.minecraft.util.crash.CrashReport; import net.minecraft.util.crash.CrashReport;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -181,17 +180,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements
videoRenderer.renderVideo(); videoRenderer.renderVideo();
} catch (FFmpegWriter.NoFFmpegException e) { } catch (FFmpegWriter.NoFFmpegException e) {
LOGGER.error("Rendering video:", e); LOGGER.error("Rendering video:", e);
NoticeScreen errorScreen = new NoticeScreen( mc.openScreen(new GuiNoFfmpeg(container::display).toMinecraft());
//#if MC>=11400
container::display,
new TranslatableText("replaymod.gui.rendering.error.title"),
new TranslatableText("replaymod.gui.rendering.error.message")
//#else
//$$ I18n.format("replaymod.gui.rendering.error.title"),
//$$ I18n.format("replaymod.gui.rendering.error.message")
//#endif
);
mc.openScreen(errorScreen);
return; return;
} catch (FFmpegWriter.FFmpegStartupException e) { } catch (FFmpegWriter.FFmpegStartupException e) {
int jobsToSkip = jobsDone; int jobsToSkip = jobsDone;
@@ -229,7 +218,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements
ReplayFile replayFile = null; ReplayFile replayFile = null;
try { try {
replayFile = mod.getCore().files.open(next.getKey().toPath()); replayFile = mod.getCore().files.open(next.getKey().toPath());
replayHandler = mod.startReplay(replayFile, true, false); replayHandler = mod.startReplay(replayFile, false, false);
} catch (IOException e) { } catch (IOException e) {
Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {}); Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {});
container.display(); // Re-show the queue popup and the new error popup container.display(); // Re-show the queue popup and the new error popup

View File

@@ -30,7 +30,6 @@ import de.johni0702.minecraft.gui.utils.Utils;
import de.johni0702.minecraft.gui.utils.lwjgl.Color; import de.johni0702.minecraft.gui.utils.lwjgl.Color;
import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension;
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
import net.minecraft.client.gui.screen.NoticeScreen;
import net.minecraft.client.resource.language.I18n; import net.minecraft.client.resource.language.I18n;
import net.minecraft.util.crash.CrashReport; import net.minecraft.util.crash.CrashReport;
@@ -149,6 +148,9 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
public final GuiCheckbox nametagCheckbox = new GuiCheckbox() public final GuiCheckbox nametagCheckbox = new GuiCheckbox()
.setI18nLabel("replaymod.gui.rendersettings.nametags"); .setI18nLabel("replaymod.gui.rendersettings.nametags");
public final GuiCheckbox alphaCheckbox = new GuiCheckbox()
.setI18nLabel("replaymod.gui.rendersettings.includealpha");
public final GuiPanel stabilizePanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(10)); public final GuiPanel stabilizePanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(10));
public final GuiCheckbox stabilizeYaw = new GuiCheckbox(stabilizePanel) public final GuiCheckbox stabilizeYaw = new GuiCheckbox(stabilizePanel)
.setI18nLabel("replaymod.gui.yaw"); .setI18nLabel("replaymod.gui.yaw");
@@ -188,7 +190,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
.setSize(200, 20).setValues(RenderSettings.AntiAliasing.values()).setSelected(RenderSettings.AntiAliasing.NONE); .setSize(200, 20).setValues(RenderSettings.AntiAliasing.values()).setSelected(RenderSettings.AntiAliasing.NONE);
public final GuiPanel advancedPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(15)) public final GuiPanel advancedPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(15))
.addElements(null, nametagCheckbox, new GuiPanel().setLayout( .addElements(null, nametagCheckbox, alphaCheckbox, new GuiPanel().setLayout(
new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15)) new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15))
.addElements(new GridLayout.Data(0, 0.5), .addElements(new GridLayout.Data(0, 0.5),
new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel,
@@ -240,17 +242,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
videoRenderer.renderVideo(); videoRenderer.renderVideo();
} catch (FFmpegWriter.NoFFmpegException e) { } catch (FFmpegWriter.NoFFmpegException e) {
LOGGER.error("Rendering video:", e); LOGGER.error("Rendering video:", e);
NoticeScreen errorScreen = new NoticeScreen( getMinecraft().openScreen(new GuiNoFfmpeg(getScreen()::display).toMinecraft());
//#if MC>=11400
getScreen()::display,
new TranslatableText("replaymod.gui.rendering.error.title"),
new TranslatableText("replaymod.gui.rendering.error.message")
//#else
//$$ I18n.format("replaymod.gui.rendering.error.title"),
//$$ I18n.format("replaymod.gui.rendering.error.message")
//#endif
);
getMinecraft().openScreen(errorScreen);
} catch (FFmpegWriter.FFmpegStartupException e) { } catch (FFmpegWriter.FFmpegStartupException e) {
GuiExportFailed.tryToRecover(e, newSettings -> { GuiExportFailed.tryToRecover(e, newSettings -> {
// Update settings with fixed ffmpeg arguments // Update settings with fixed ffmpeg arguments
@@ -533,6 +525,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
} }
outputFileButton.setLabel(this.outputFile.getName()); outputFileButton.setLabel(this.outputFile.getName());
nametagCheckbox.setChecked(settings.isRenderNameTags()); nametagCheckbox.setChecked(settings.isRenderNameTags());
alphaCheckbox.setChecked(settings.isIncludeAlphaChannel());
stabilizeYaw.setChecked(settings.isStabilizeYaw()); stabilizeYaw.setChecked(settings.isStabilizeYaw());
stabilizePitch.setChecked(settings.isStabilizePitch()); stabilizePitch.setChecked(settings.isStabilizePitch());
stabilizeRoll.setChecked(settings.isStabilizeRoll()); stabilizeRoll.setChecked(settings.isStabilizeRoll());
@@ -571,6 +564,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
bitRateField.getInteger() << (10 * bitRateUnit.getSelected()), bitRateField.getInteger() << (10 * bitRateUnit.getSelected()),
serialize && !userDefinedOutputFileName ? getParentFile(outputFile) : outputFile, serialize && !userDefinedOutputFileName ? getParentFile(outputFile) : outputFile,
nametagCheckbox.isChecked(), nametagCheckbox.isChecked(),
alphaCheckbox.isChecked(),
stabilizeYaw.isChecked() && (serialize || stabilizeYaw.isEnabled()), stabilizeYaw.isChecked() && (serialize || stabilizeYaw.isEnabled()),
stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()), stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()),
stabilizeRoll.isChecked() && (serialize || stabilizeRoll.isEnabled()), stabilizeRoll.isChecked() && (serialize || stabilizeRoll.isEnabled()),

View File

@@ -141,7 +141,7 @@ public class GuiVideoRenderer extends GuiScreen implements Tickable {
@Override @Override
public void tick() { public void tick() {
long current = System.currentTimeMillis(); long current = System.nanoTime() / 1_000_000;
//first, update the total render time (only if rendering is not paused and has already started) //first, update the total render time (only if rendering is not paused and has already started)
if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) { if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) {

View File

@@ -8,6 +8,7 @@ import com.replaymod.render.Setting;
import com.replaymod.render.capturer.CaptureData; import com.replaymod.render.capturer.CaptureData;
import com.replaymod.render.capturer.RenderInfo; import com.replaymod.render.capturer.RenderInfo;
import com.replaymod.render.capturer.WorldRenderer; import com.replaymod.render.capturer.WorldRenderer;
import com.replaymod.render.mixin.GameRendererAccessor;
import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.ReplayModReplay;
import de.johni0702.minecraft.gui.utils.EventRegistrations; import de.johni0702.minecraft.gui.utils.EventRegistrations;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@@ -81,11 +82,16 @@ public class EntityRendererHandler extends EventRegistrations implements WorldRe
//#endif //#endif
if (mc.world != null && mc.player != null) { if (mc.world != null && mc.player != null) {
GameRendererAccessor gameRenderer = (GameRendererAccessor) mc.gameRenderer;
Screen orgScreen = mc.currentScreen; Screen orgScreen = mc.currentScreen;
boolean orgPauseOnLostFocus = mc.options.pauseOnLostFocus; boolean orgPauseOnLostFocus = mc.options.pauseOnLostFocus;
boolean orgRenderHand = gameRenderer.getRenderHand();
try { try {
mc.currentScreen = null; // do not want to render the current screen (that'd just be the progress gui) mc.currentScreen = null; // do not want to render the current screen (that'd just be the progress gui)
mc.options.pauseOnLostFocus = false; // do not want the pause menu to open if the window is unfocused mc.options.pauseOnLostFocus = false; // do not want the pause menu to open if the window is unfocused
if (omnidirectional) {
gameRenderer.setRenderHand(false); // makes no sense, we wouldn't even know where to put it
}
//#if MC>=11400 //#if MC>=11400
mc.gameRenderer.render(partialTicks, finishTimeNano, true); mc.gameRenderer.render(partialTicks, finishTimeNano, true);
@@ -100,6 +106,7 @@ public class EntityRendererHandler extends EventRegistrations implements WorldRe
} finally { } finally {
mc.currentScreen = orgScreen; mc.currentScreen = orgScreen;
mc.options.pauseOnLostFocus = orgPauseOnLostFocus; mc.options.pauseOnLostFocus = orgPauseOnLostFocus;
gameRenderer.setRenderHand(orgRenderHand);
} }
} }

View File

@@ -0,0 +1,13 @@
package com.replaymod.render.mixin;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(GameRenderer.class)
public interface GameRendererAccessor {
@Accessor
boolean getRenderHand();
@Accessor
void setRenderHand(boolean value);
}

View File

@@ -3,6 +3,7 @@ package com.replaymod.render.mixin;
import net.minecraft.client.util.Window; import net.minecraft.client.util.Window;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Window.class) @Mixin(Window.class)
public interface MainWindowAccessor { public interface MainWindowAccessor {
@@ -14,4 +15,12 @@ public interface MainWindowAccessor {
int getFramebufferHeight(); int getFramebufferHeight();
@Accessor @Accessor
void setFramebufferHeight(int value); void setFramebufferHeight(int value);
// FIXME preprocessor should be able to infer this mapping
// FIXME preprocessor should be able to remap this one when the mapping is given manually
//#if MC>=11500
@Invoker
//#else
//$$ @Invoker("method_4483")
//#endif
void invokeUpdateFramebufferSize();
} }

View File

@@ -21,7 +21,12 @@ public abstract class Mixin_ChromaKeyColorSky {
@Shadow @Final private MinecraftClient client; @Shadow @Final private MinecraftClient client;
//#if MC>=11800 //#if MC>=11800
//$$ @Inject(method = "renderSky(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/util/math/Matrix4f;FLjava/lang/Runnable;)V", //$$ @Inject(
//#if MC>=11802
//$$ method = "renderSky(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/util/math/Matrix4f;FLnet/minecraft/client/render/Camera;ZLjava/lang/Runnable;)V",
//#else
//$$ method = "renderSky(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/util/math/Matrix4f;FLjava/lang/Runnable;)V",
//#endif
//$$ at = @At(value = "INVOKE", target = "Ljava/lang/Runnable;run()V", remap = false, shift = At.Shift.AFTER), //$$ at = @At(value = "INVOKE", target = "Ljava/lang/Runnable;run()V", remap = false, shift = At.Shift.AFTER),
//$$ cancellable = true) //$$ cancellable = true)
//#elseif MC>=11400 || 10710>=MC //#elseif MC>=11400 || 10710>=MC

View File

@@ -4,7 +4,6 @@ package com.replaymod.render.mixin;
import com.replaymod.render.capturer.IrisODSFrameCapturer; import com.replaymod.render.capturer.IrisODSFrameCapturer;
import net.coderbot.iris.Iris; import net.coderbot.iris.Iris;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@@ -15,14 +14,14 @@ import java.nio.file.Path;
@Pseudo @Pseudo
@Mixin(value = Iris.class, remap = false) @Mixin(value = Iris.class, remap = false)
public class Mixin_LoadIrisOdsShaderPack { public class Mixin_LoadIrisOdsShaderPack {
@Redirect(method = "loadExternalShaderpack", at = @At(value = "FIELD", opcode = Opcodes.GETSTATIC, target = "Lnet/coderbot/iris/Iris;SHADERPACKS_DIRECTORY:Ljava/nio/file/Path;")) @Redirect(method = "loadExternalShaderpack", at = @At(value = "INVOKE", target = "Lnet/coderbot/iris/Iris;getShaderpacksDirectory()Ljava/nio/file/Path;"))
private static Path loadReplayModOdsPack(String name) { private static Path loadReplayModOdsPack(String name) {
if (IrisODSFrameCapturer.INSTANCE != null && IrisODSFrameCapturer.SHADER_PACK_NAME.equals(name)) { if (IrisODSFrameCapturer.INSTANCE != null && IrisODSFrameCapturer.SHADER_PACK_NAME.equals(name)) {
return FabricLoader.getInstance().getModContainer("replaymod") return FabricLoader.getInstance().getModContainer("replaymod")
.orElseThrow(() -> new RuntimeException("Failed to get mod container for ReplayMod")) .orElseThrow(() -> new RuntimeException("Failed to get mod container for ReplayMod"))
.getRootPath(); .getRootPath();
} else { } else {
return Iris.SHADERPACKS_DIRECTORY; return Iris.getShaderpacksDirectory();
} }
} }
} }

View File

@@ -9,7 +9,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(GameRenderer.class) @Mixin(GameRenderer.class)
public abstract class Mixin_Omnidirectional_Camera implements EntityRendererHandler.IEntityRenderer { public abstract class Mixin_Omnidirectional_Camera implements EntityRendererHandler.IEntityRenderer {
@Redirect(method = "method_22973", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/Matrix4f;viewboxMatrix(DFFF)Lnet/minecraft/util/math/Matrix4f;")) @Redirect(method = "getBasicProjectionMatrix", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/Matrix4f;viewboxMatrix(DFFF)Lnet/minecraft/util/math/Matrix4f;"))
private Matrix4f replayModRender_perspective$0(double fovY, float aspect, float zNear, float zFar) { private Matrix4f replayModRender_perspective$0(double fovY, float aspect, float zNear, float zFar) {
return replayModRender_perspective((float) fovY, aspect, zNear, zFar); return replayModRender_perspective((float) fovY, aspect, zNear, zFar);
} }

View File

@@ -1,37 +0,0 @@
package com.replaymod.render.mixin;
import com.replaymod.core.versions.MCVer;
import com.replaymod.render.hooks.EntityRendererHandler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
//#if MC>=10800
import net.minecraft.client.render.Frustum;
//#else
//$$ import net.minecraft.client.renderer.culling.Frustrum;
//#endif
//#if MC>=10800
@Mixin(Frustum.class)
//#else
//$$ @Mixin(Frustrum.class)
//#endif
public abstract class Mixin_Omnidirectional_DisableFrustumCulling {
//#if MC>=11500
@Inject(method = "isAnyCornerVisible", at = @At("HEAD"), cancellable = true)
//#else
//$$ @Inject(method = "intersects", at = @At("HEAD"), cancellable = true)
//#endif
public void intersects(CallbackInfoReturnable<Boolean> ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().gameRenderer).replayModRender_getHandler();
if (handler != null && handler.omnidirectional) {
// Note the following used to be true but for simplicity non-ODS omnidirectional is the same now too.
// Normally the camera is always facing the direction of the omnidirectional image face that is currently
// getting rendered. With ODS however, the camera is always facing forwards and the turning happens in the
// vertex shader (non-trivial due to stereo). As such, all chunks need to be rendered all the time for ODS.
ci.setReturnValue(true);
}
}
}

View File

@@ -1,34 +0,0 @@
package com.replaymod.render.mixin;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.util.math.MatrixStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GameRenderer.class)
public abstract class Mixin_Omnidirectional_SkipHand implements EntityRendererHandler.IEntityRenderer {
@Inject(method = "renderHand", at = @At("HEAD"), cancellable = true)
private void replayModRender_renderSpectatorHand(
//#if MC>=11500
MatrixStack matrixStack,
//#endif
//#if MC>=11400
Camera camera,
//#endif
float partialTicks,
//#if MC<11400
//$$ int renderPass,
//#endif
CallbackInfo ci
) {
EntityRendererHandler handler = replayModRender_getHandler();
if (handler != null && handler.omnidirectional) {
// No spectator hands during 360° view, we wouldn't even know where to put it
ci.cancel();
}
}
}

View File

@@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(GameRenderer.class) @Mixin(GameRenderer.class)
public abstract class Mixin_Stereoscopic_Camera implements EntityRendererHandler.IEntityRenderer { public abstract class Mixin_Stereoscopic_Camera implements EntityRendererHandler.IEntityRenderer {
@Inject(method = "method_22973", at = @At("RETURN"), cancellable = true) @Inject(method = "getBasicProjectionMatrix", at = @At("RETURN"), cancellable = true)
private void replayModRender_setupStereoscopicProjection(CallbackInfoReturnable<Matrix4f> ci) { private void replayModRender_setupStereoscopicProjection(CallbackInfoReturnable<Matrix4f> ci) {
if (replayModRender_getHandler() != null) { if (replayModRender_getHandler() != null) {
Matrix4f offset; Matrix4f offset;

View File

@@ -120,6 +120,7 @@ public class VideoRenderer implements RenderInfo {
private Framebuffer guiFramebuffer; private Framebuffer guiFramebuffer;
private int displayWidth, displayHeight; private int displayWidth, displayHeight;
private int framebufferWidth, framebufferHeight;
public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException { public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException {
this.settings = settings; this.settings = settings;
@@ -135,12 +136,12 @@ public class VideoRenderer implements RenderInfo {
FrameConsumer<BitmapFrame> frameConsumer; FrameConsumer<BitmapFrame> frameConsumer;
if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.EXR) { if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.EXR) {
//#if MC>=11400 //#if MC>=11400
frameConsumer = new EXRWriter(settings.getOutputFile().toPath()); frameConsumer = new EXRWriter(settings.getOutputFile().toPath(), settings.isIncludeAlphaChannel());
//#else //#else
//$$ throw new UnsupportedOperationException("EXR requires LWJGL3"); //$$ throw new UnsupportedOperationException("EXR requires LWJGL3");
//#endif //#endif
} else if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.PNG) { } else if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.PNG) {
frameConsumer = new PNGWriter(settings.getOutputFile().toPath()); frameConsumer = new PNGWriter(settings.getOutputFile().toPath(), settings.isIncludeAlphaChannel());
} else { } else {
frameConsumer = new FFmpegWriter(this); frameConsumer = new FFmpegWriter(this);
} }
@@ -245,10 +246,10 @@ public class VideoRenderer implements RenderInfo {
public float updateForNextFrame() { public float updateForNextFrame() {
// because the jGui lib uses Minecraft's displayWidth and displayHeight values, update these temporarily // because the jGui lib uses Minecraft's displayWidth and displayHeight values, update these temporarily
MainWindowAccessor acc = (MainWindowAccessor) (Object) mc.getWindow(); MainWindowAccessor acc = (MainWindowAccessor) (Object) mc.getWindow();
int displayWidthBefore = acc.getFramebufferWidth(); int framebufferWidthBefore = acc.getFramebufferWidth();
int displayHeightBefore = acc.getFramebufferHeight(); int framebufferHeightBefore = acc.getFramebufferHeight();
acc.setFramebufferWidth(displayWidth); acc.setFramebufferWidth(framebufferWidth);
acc.setFramebufferHeight(displayHeight); acc.setFramebufferHeight(framebufferHeight);
if (!settings.isHighPerformance() || framesDone % fps == 0) { if (!settings.isHighPerformance() || framesDone % fps == 0) {
while (drawGui() && paused) { while (drawGui() && paused) {
@@ -289,8 +290,8 @@ public class VideoRenderer implements RenderInfo {
} }
// change Minecraft's display size back // change Minecraft's display size back
acc.setFramebufferWidth(displayWidthBefore); acc.setFramebufferWidth(framebufferWidthBefore);
acc.setFramebufferHeight(displayHeightBefore); acc.setFramebufferHeight(framebufferHeightBefore);
if (cameraPathExporter != null) { if (cameraPathExporter != null) {
cameraPathExporter.recordFrame(timer.tickDelta); cameraPathExporter.recordFrame(timer.tickDelta);
@@ -362,6 +363,7 @@ public class VideoRenderer implements RenderInfo {
} }
updateDisplaySize(); updateDisplaySize();
updateFramebufferSize();
gui.toMinecraft().init(mc, mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight()); gui.toMinecraft().init(mc, mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight());
@@ -369,9 +371,9 @@ public class VideoRenderer implements RenderInfo {
// Set up our own framebuffer to render the GUI to // Set up our own framebuffer to render the GUI to
//#if MC>=11700 //#if MC>=11700
//$$ guiFramebuffer = new WindowFramebuffer(displayWidth, displayHeight); //$$ guiFramebuffer = new WindowFramebuffer(framebufferWidth, framebufferHeight);
//#else //#else
guiFramebuffer = new Framebuffer(displayWidth, displayHeight, true guiFramebuffer = new Framebuffer(framebufferWidth, framebufferHeight, true
//#if MC>=11400 //#if MC>=11400
, false , false
//#endif //#endif
@@ -425,7 +427,7 @@ public class VideoRenderer implements RenderInfo {
} }
// Finally, resize the Minecraft framebuffer to the actual width/height of the window // Finally, resize the Minecraft framebuffer to the actual width/height of the window
resizeMainWindow(mc, displayWidth, displayHeight); resizeMainWindow(mc, framebufferWidth, framebufferHeight);
} }
private void executeTaskQueue() { private void executeTaskQueue() {
@@ -483,17 +485,23 @@ public class VideoRenderer implements RenderInfo {
return false; return false;
} }
// Resize the GUI framebuffer if the display size changed // Check if display size has changes and force recalculate GUI framebuffer size.
if (displaySizeChanged()) { if (displaySizeChanged()) {
updateDisplaySize(); updateDisplaySize();
((MainWindowAccessor) (Object) window).invokeUpdateFramebufferSize();
}
// Resize the GUI framebuffer if the display size changed
if (framebufferSizeChanged()) {
updateFramebufferSize();
//#if MC>=11400 //#if MC>=11400
guiFramebuffer.resize(displayWidth, displayHeight guiFramebuffer.resize(framebufferWidth, framebufferHeight
//#if MC>=11400 //#if MC>=11400
, false , false
//#endif //#endif
); );
//#else //#else
//$$ guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight); //$$ guiFramebuffer.createBindFramebuffer(framebufferWidth, framebufferHeight);
//#endif //#endif
} }
@@ -594,7 +602,7 @@ public class VideoRenderer implements RenderInfo {
guiFramebuffer.endWrite(); guiFramebuffer.endWrite();
popMatrix(); popMatrix();
pushMatrix(); pushMatrix();
guiFramebuffer.draw(displayWidth, displayHeight); guiFramebuffer.draw(framebufferWidth, framebufferHeight);
popMatrix(); popMatrix();
//#if MC>=11500 //#if MC>=11500
@@ -633,12 +641,18 @@ public class VideoRenderer implements RenderInfo {
private boolean displaySizeChanged() { private boolean displaySizeChanged() {
int realWidth = mc.getWindow().getWidth(); int realWidth = mc.getWindow().getWidth();
int realHeight = mc.getWindow().getHeight(); int realHeight = mc.getWindow().getHeight();
return displayWidth != realWidth || displayHeight != realHeight;
}
private boolean framebufferSizeChanged() {
int realWidth = mc.getWindow().getFramebufferWidth();
int realHeight = mc.getWindow().getFramebufferHeight();
if (realWidth == 0 || realHeight == 0) { if (realWidth == 0 || realHeight == 0) {
// These can be zero on Windows if minimized. // These can be zero on Windows if minimized.
// Creating zero-sized framebuffers however will throw an error, so we never want to switch to zero values. // Creating zero-sized framebuffers however will throw an error, so we never want to switch to zero values.
return false; return false;
} }
return displayWidth != realWidth || displayHeight != realHeight; return framebufferWidth != realWidth || framebufferHeight != realHeight;
} }
private void updateDisplaySize() { private void updateDisplaySize() {
@@ -646,6 +660,11 @@ public class VideoRenderer implements RenderInfo {
displayHeight = mc.getWindow().getHeight(); displayHeight = mc.getWindow().getHeight();
} }
private void updateFramebufferSize() {
framebufferWidth = mc.getWindow().getFramebufferWidth();
framebufferHeight = mc.getWindow().getFramebufferHeight();
}
public int getFramesDone() { public int getFramesDone() {
return framesDone; return framesDone;
} }

View File

@@ -125,6 +125,9 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.replaymod.core.versions.MCVer.*; import static com.replaymod.core.versions.MCVer.*;
import static com.replaymod.replaystudio.util.Utils.readInt; import static com.replaymod.replaystudio.util.Utils.readInt;
@@ -349,6 +352,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
return; return;
} }
terminate = true; terminate = true;
syncSender.shutdown();
events.unregister(); events.unregister();
try { try {
channelInactive(ctx); channelInactive(ctx);
@@ -403,53 +407,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
super.channelRead(ctx, p); super.channelRead(ctx, p);
} }
// If we do not give minecraft time to tick, there will be dead entity artifacts left in the world maybeRemoveDeadEntities(p);
// Therefore we have to remove all loaded, dead entities manually if we are in sync mode.
// We do this after every SpawnX packet and after the destroy entities packet.
if (!asyncMode && mc.world != null) {
if (p instanceof PlayerSpawnS2CPacket
|| p instanceof EntitySpawnS2CPacket
|| p instanceof MobSpawnS2CPacket
//#if MC<11600
//$$ || p instanceof EntitySpawnGlobalS2CPacket
//#endif
|| p instanceof PaintingSpawnS2CPacket
|| p instanceof ExperienceOrbSpawnS2CPacket
|| p instanceof EntitiesDestroyS2CPacket) {
ClientWorld world = mc.world;
//#if MC>=11700
//$$ // From the looks of it, this has now been resolved (thanks to EntityChangeListener)
//#elseif MC>=11400
// Note: Not sure if it's still required but there's this really handy method anyway
world.finishRemovingEntities();
//#else
//$$ Iterator<Entity> iter = world.loadedEntityList.iterator();
//$$ while (iter.hasNext()) {
//$$ Entity entity = iter.next();
//$$ if (entity.isDead) {
//$$ int chunkX = entity.chunkCoordX;
//$$ int chunkY = entity.chunkCoordZ;
//$$
//#if MC>=11400
//$$ if (entity.addedToChunk && world.getChunkProvider().provideChunk(chunkX, chunkY, false, false) != null) {
//#else
//#if MC>=10904
//$$ if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) {
//#else
//$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) {
//#endif
//#endif
//$$ world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity);
//$$ }
//$$
//$$ iter.remove();
//$$ world.onEntityRemoved(entity);
//$$ }
//$$
//$$ }
//#endif
}
}
//#if MC>=11400 //#if MC>=11400
if (p instanceof ChunkDataS2CPacket) { if (p instanceof ChunkDataS2CPacket) {
@@ -504,6 +462,69 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
return p; return p;
} }
// If we do not give minecraft time to tick, there will be dead entity artifacts left in the world
// Therefore we have to remove all loaded, dead entities manually if we are in sync mode.
// We do this after every SpawnX packet and after the destroy entities packet.
private void maybeRemoveDeadEntities(Packet packet) {
if (asyncMode) {
return; // MC should have enough time to tick
}
boolean relevantPacket = packet instanceof PlayerSpawnS2CPacket
|| packet instanceof EntitySpawnS2CPacket
|| packet instanceof MobSpawnS2CPacket
//#if MC<11600
//$$ || packet instanceof EntitySpawnGlobalS2CPacket
//#endif
|| packet instanceof PaintingSpawnS2CPacket
|| packet instanceof ExperienceOrbSpawnS2CPacket
|| packet instanceof EntitiesDestroyS2CPacket;
if (!relevantPacket) {
return; // don't want to do it too often, only when there's likely to be a dead entity
}
mc.send(() -> {
ClientWorld world = mc.world;
if (world != null) {
removeDeadEntities(world);
}
});
}
private void removeDeadEntities(ClientWorld world) {
//#if MC>=11700
//$$ // From the looks of it, this has now been resolved (thanks to EntityChangeListener)
//#elseif MC>=11400
// Note: Not sure if it's still required but there's this really handy method anyway
world.finishRemovingEntities();
//#else
//$$ Iterator<Entity> iter = world.loadedEntityList.iterator();
//$$ while (iter.hasNext()) {
//$$ Entity entity = iter.next();
//$$ if (entity.isDead) {
//$$ int chunkX = entity.chunkCoordX;
//$$ int chunkY = entity.chunkCoordZ;
//$$
//#if MC>=11400
//$$ if (entity.addedToChunk && world.getChunkProvider().provideChunk(chunkX, chunkY, false, false) != null) {
//#else
//#if MC>=10904
//$$ if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) {
//#else
//$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) {
//#endif
//#endif
//$$ world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity);
//$$ }
//$$
//$$ iter.remove();
//$$ world.onEntityRemoved(entity);
//$$ }
//$$
//$$ }
//#endif
}
/** /**
* Process a packet and return the result. * Process a packet and return the result.
* @param p The packet to process * @param p The packet to process
@@ -627,7 +648,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
//#if MC>=11600 //#if MC>=11600
//#if MC>=11603 //#if MC>=11603
packet.getDimensionIds(), packet.getDimensionIds(),
//#if MC>=11800
//$$ packet.registryManager(),
//#else
(net.minecraft.util.registry.DynamicRegistryManager.Impl) packet.getRegistryManager(), (net.minecraft.util.registry.DynamicRegistryManager.Impl) packet.getRegistryManager(),
//#endif
packet.getDimensionType(), packet.getDimensionType(),
//#else //#else
//$$ packet.method_29443(), //$$ packet.method_29443(),
@@ -824,7 +849,13 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
} }
} }
return asyncMode ? processPacketAsync(p) : processPacketSync(p); if (asyncMode) {
return processPacketAsync(p);
} else {
Packet fp = p;
mc.send(() -> processPacketSync(fp));
return p;
}
} }
@Override @Override
@@ -1085,6 +1116,10 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
// Synchronous packet processing // // Synchronous packet processing //
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
// Even in sync mode, we send from another thread because mods may rely on that
private final ExecutorService syncSender = Executors.newSingleThreadExecutor(runnable ->
new Thread(runnable, "replaymod-sync-sender"));
/** /**
* Sends all packets until the specified timestamp is reached (inclusive). * Sends all packets until the specified timestamp is reached (inclusive).
* If the timestamp is smaller than the last packet sent, the replay is restarted from the beginning. * If the timestamp is smaller than the last packet sent, the replay is restarted from the beginning.
@@ -1093,6 +1128,36 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
@Override @Override
public void sendPacketsTill(int timestamp) { public void sendPacketsTill(int timestamp) {
Preconditions.checkState(!asyncMode, "This method cannot be used in async mode. Use jumpToTime(int) instead."); Preconditions.checkState(!asyncMode, "This method cannot be used in async mode. Use jumpToTime(int) instead.");
// Submit our target to the sender thread and track its progress
AtomicBoolean doneSending = new AtomicBoolean();
syncSender.submit(() -> {
try {
doSendPacketsTill(timestamp);
} finally {
doneSending.set(true);
}
});
// Drain the task queue while we are sending (in case a mod blocks the io thread waiting for the main thread)
while (!doneSending.get()) {
executeTaskQueue();
// Wait until the sender thread has made progress
try {
//noinspection BusyWait
Thread.sleep(0, 100_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// Everything has been sent, drain the queue one last time
executeTaskQueue();
}
private void doSendPacketsTill(int timestamp) {
try { try {
while (ctx == null && !terminate) { // Make sure channel is ready while (ctx == null && !terminate) { // Make sure channel is ready
Thread.sleep(10); Thread.sleep(10);
@@ -1112,7 +1177,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
loginPhase = true; loginPhase = true;
startFromBeginning = false; startFromBeginning = false;
nextPacket = null; nextPacket = null;
replayHandler.restartedReplay(); ReplayMod.instance.runSync(replayHandler::restartedReplay);
} }
if (replayIn == null) { if (replayIn == null) {
@@ -1159,7 +1224,30 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
} }
} }
protected Packet processPacketSync(Packet p) { private void executeTaskQueue() {
//#if MC>=11400
((MCVer.MinecraftMethodAccessor) mc).replayModExecuteTaskQueue();
//#else
//$$ java.util.Queue<java.util.concurrent.FutureTask<?>> scheduledTasks = ((MinecraftAccessor) mc).getScheduledTasks();
//$$
//$$ // Live-lock detection: if we already hold the lock, then the sender thread will never be able to queue its
//$$ // tasks
//$$ if (Thread.holdsLock(scheduledTasks)) {
//$$ throw new IllegalStateException("Task queue already locked. " +
//$$ "You may want to use `Scheduler.runLaterWithoutLock` to run while the lock is not taken.");
//$$ }
//$$
//$$ //noinspection SynchronizationOnLocalVariableOrMethodParameter
//$$ synchronized (scheduledTasks) {
//$$ while (!scheduledTasks.isEmpty()) {
//$$ scheduledTasks.poll().run();
//$$ }
//$$ }
//#endif
ReplayMod.instance.runTasks();
}
protected void processPacketSync(Packet p) {
//#if MC>=10904 //#if MC>=10904
if (p instanceof UnloadChunkS2CPacket) { if (p instanceof UnloadChunkS2CPacket) {
UnloadChunkS2CPacket packet = (UnloadChunkS2CPacket) p; UnloadChunkS2CPacket packet = (UnloadChunkS2CPacket) p;
@@ -1280,7 +1368,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
} }
} }
} }
return p; // During synchronous playback everything is sent normally
} }
private void forcePositionForVehicleAndSelf(Entity entity) { private void forcePositionForVehicleAndSelf(Entity entity) {

View File

@@ -8,6 +8,10 @@ import com.replaymod.replay.camera.CameraEntity;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderTickCounter; import net.minecraft.client.render.RenderTickCounter;
//#if MC>=11802
//$$ import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
//#endif
//#if MC>=11400 //#if MC>=11400
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
//#else //#else
@@ -111,6 +115,15 @@ public class InputReplayTimer extends WrappedTimer {
//#endif //#endif
//$$ } //$$ }
//#endif //#endif
//#if MC>=11802
//$$ // As of 1.18.2, this screen always stays open for at least two seconds, and requires ticking to close.
//$$ // Thanks, but we'll have none of that (at least while in a replay).
//$$ if (mc.currentScreen instanceof DownloadingTerrainScreen) {
//$$ mc.currentScreen.close();
//$$ }
//#endif
} }
//#if MC>=11600 //#if MC>=11600
return ticksThisFrame; return ticksThisFrame;

View File

@@ -1,4 +1,4 @@
//#if MC>=10904 //#if MC>=10800
package com.replaymod.replay; package com.replaymod.replay;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
@@ -78,7 +78,11 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
wrappedBuf.writerIndex(size); wrappedBuf.writerIndex(size);
PacketByteBuf packetByteBuf = new PacketByteBuf(wrappedBuf); PacketByteBuf packetByteBuf = new PacketByteBuf(wrappedBuf);
//#if MC>=10809
Packet<?> mcPacket; Packet<?> mcPacket;
//#else
//$$ Packet mcPacket;
//#endif
//#if MC>=11700 //#if MC>=11700
//$$ mcPacket = NetworkState.PLAY.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId(), packetByteBuf); //$$ mcPacket = NetworkState.PLAY.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId(), packetByteBuf);
//#elseif MC>=11500 //#elseif MC>=11500
@@ -137,13 +141,13 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
LOGGER.info("Initialized quick replay sender in " + (System.currentTimeMillis() - start) + "ms"); LOGGER.info("Initialized quick replay sender in " + (System.currentTimeMillis() - start) + "ms");
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Initializing quick replay sender:", e); LOGGER.error("Initializing quick replay sender:", e);
mod.getCore().runLater(() -> { mod.getCore().runLaterWithoutLock(() -> {
mod.getCore().printWarningToChat("Error initializing quick replay sender: %s", e.getLocalizedMessage()); mod.getCore().printWarningToChat("Error initializing quick replay sender: %s", e.getLocalizedMessage());
promise.setException(e); promise.setException(e);
}); });
return; return;
} }
mod.getCore().runLater(() -> promise.set(null)); mod.getCore().runLaterWithoutLock(() -> promise.set(null));
}).start(); }).start();
return promise; return promise;
} }

View File

@@ -69,7 +69,7 @@ import net.minecraft.entity.LivingEntity;
//$$ import io.netty.channel.ChannelOutboundHandlerAdapter; //$$ import io.netty.channel.ChannelOutboundHandlerAdapter;
//#endif //#endif
//#if MC<10904 //#if MC<10800
//$$ import de.johni0702.minecraft.gui.element.GuiLabel; //$$ import de.johni0702.minecraft.gui.element.GuiLabel;
//$$ import de.johni0702.minecraft.gui.popup.GuiInfoPopup; //$$ import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
//$$ import de.johni0702.minecraft.gui.utils.Colors; //$$ import de.johni0702.minecraft.gui.utils.Colors;
@@ -119,11 +119,11 @@ public class ReplayHandler {
* Decodes and sends packets into channel. * Decodes and sends packets into channel.
*/ */
private final FullReplaySender fullReplaySender; private final FullReplaySender fullReplaySender;
//#if MC>=10904 //#if MC>=10800
private final QuickReplaySender quickReplaySender; private final QuickReplaySender quickReplaySender;
private boolean quickMode = false; private boolean quickMode = false;
//#else //#else
//$$ private static final String QUICK_MODE_MIN_MC = "1.9.4"; //$$ private static final String QUICK_MODE_MIN_MC = "1.8";
//#endif //#endif
/** /**
@@ -160,7 +160,7 @@ public class ReplayHandler {
markers = replayFile.getMarkers().or(Collections.emptySet()); markers = replayFile.getMarkers().or(Collections.emptySet());
fullReplaySender = new FullReplaySender(this, replayFile, false); fullReplaySender = new FullReplaySender(this, replayFile, false);
//#if MC>=10904 //#if MC>=10800
quickReplaySender = new QuickReplaySender(ReplayModReplay.instance, replayFile); quickReplaySender = new QuickReplaySender(ReplayModReplay.instance, replayFile);
//#endif //#endif
@@ -206,7 +206,7 @@ public class ReplayHandler {
ReplayClosingCallback.EVENT.invoker().replayClosing(this); ReplayClosingCallback.EVENT.invoker().replayClosing(this);
fullReplaySender.terminateReplay(); fullReplaySender.terminateReplay();
//#if MC>=10904 //#if MC>=10800
if (quickMode) { if (quickMode) {
quickReplaySender.unregister(); quickReplaySender.unregister();
} }
@@ -308,7 +308,7 @@ public class ReplayHandler {
//$$ channel = new EmbeddedChannel(dummyHandler); //$$ channel = new EmbeddedChannel(dummyHandler);
//$$ channel.pipeline().remove(dummyHandler); //$$ channel.pipeline().remove(dummyHandler);
//#endif //#endif
//#if MC>=10904 //#if MC>=10800
channel.pipeline().addLast("ReplayModReplay_quickReplaySender", quickReplaySender); channel.pipeline().addLast("ReplayModReplay_quickReplaySender", quickReplaySender);
//#endif //#endif
channel.pipeline().addLast("ReplayModReplay_replaySender", fullReplaySender); channel.pipeline().addLast("ReplayModReplay_replaySender", fullReplaySender);
@@ -329,7 +329,7 @@ public class ReplayHandler {
} }
public ReplaySender getReplaySender() { public ReplaySender getReplaySender() {
//#if MC>=10904 //#if MC>=10800
return quickMode ? quickReplaySender : fullReplaySender; return quickMode ? quickReplaySender : fullReplaySender;
//#else //#else
//$$ return fullReplaySender; //$$ return fullReplaySender;
@@ -340,7 +340,7 @@ public class ReplayHandler {
return overlay; return overlay;
} }
//#if MC>=10904 //#if MC>=10800
public void ensureQuickModeInitialized(Runnable andThen) { public void ensureQuickModeInitialized(Runnable andThen) {
if (Utils.ifMinimalModeDoPopup(overlay, () -> {})) return; if (Utils.ifMinimalModeDoPopup(overlay, () -> {})) return;
ListenableFuture<Void> future = quickReplaySender.getInitializationPromise(); ListenableFuture<Void> future = quickReplaySender.getInitializationPromise();
@@ -534,7 +534,11 @@ public class ReplayHandler {
} }
public void doJump(int targetTime, boolean retainCameraPosition) { public void doJump(int targetTime, boolean retainCameraPosition) {
//#if MC>=10904 if (!getReplaySender().isAsyncMode()) {
return; // path playback, rendering, etc. -> no jumping allowed
}
//#if MC>=10800
if (getReplaySender() == quickReplaySender) { if (getReplaySender() == quickReplaySender) {
// Always round to full tick // Always round to full tick
targetTime = targetTime + targetTime % 50; targetTime = targetTime + targetTime % 50;
@@ -603,6 +607,7 @@ public class ReplayHandler {
// Render our please-wait-screen // Render our please-wait-screen
GuiScreen guiScreen = new GuiScreen(); GuiScreen guiScreen = new GuiScreen();
guiScreen.setBackground(AbstractGuiScreen.Background.DIRT); guiScreen.setBackground(AbstractGuiScreen.Background.DIRT);
guiScreen.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER));
guiScreen.addElements(new HorizontalLayout.Data(0.5), guiScreen.addElements(new HorizontalLayout.Data(0.5),
new GuiLabel().setI18nText("replaymod.gui.pleasewait")); new GuiLabel().setI18nText("replaymod.gui.pleasewait"));

View File

@@ -31,6 +31,10 @@ import net.minecraft.stat.StatHandler;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.Box; import net.minecraft.util.math.Box;
//#if MC>=11802
//$$ import net.minecraft.tag.TagKey;
//#endif
//#if FABRIC>=1 //#if FABRIC>=1
//#else //#else
//$$ import net.minecraftforge.client.event.EntityViewRenderEvent; //$$ import net.minecraftforge.client.event.EntityViewRenderEvent;
@@ -354,7 +358,13 @@ public class CameraEntity
//#if MC>=11400 //#if MC>=11400
@Override @Override
public boolean isSubmergedIn(Tag<Fluid> fluid) { public boolean isSubmergedIn(
//#if MC>=11802
//$$ TagKey<Fluid> fluid
//#else
Tag<Fluid> fluid
//#endif
) {
return falseUnlessSpectating(entity -> entity.isSubmergedIn(fluid)); return falseUnlessSpectating(entity -> entity.isSubmergedIn(fluid));
} }

View File

@@ -49,6 +49,8 @@ import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -95,8 +97,10 @@ public class GuiReplayViewer extends GuiScreen {
List<GuiReplayEntry> selected = list.getSelected(); List<GuiReplayEntry> selected = list.getSelected();
if (selected.size() == 1) { if (selected.size() == 1) {
File file = selected.get(0).file;
LOGGER.info("Opening replay in viewer: " + file);
try { try {
mod.startReplay(selected.get(0).file); mod.startReplay(file);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -130,8 +134,8 @@ public class GuiReplayViewer extends GuiScreen {
public final GuiButton renameButton = new GuiButton().onClick(new Runnable() { public final GuiButton renameButton = new GuiButton().onClick(new Runnable() {
@Override @Override
public void run() { public void run() {
final File file = list.getSelected().get(0).file; final Path path = list.getSelected().get(0).file.toPath();
String name = Utils.fileNameToReplayName(file.getName()); String name = Utils.fileNameToReplayName(path.getFileName().toString());
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name); final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name);
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this, final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK), new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK),
@@ -147,16 +151,16 @@ public class GuiReplayViewer extends GuiScreen {
} }
}).onTextChanged(obj -> { }).onTextChanged(obj -> {
popup.getYesButton().setEnabled(!nameField.getText().isEmpty() popup.getYesButton().setEnabled(!nameField.getText().isEmpty()
&& !new File(file.getParentFile(), Utils.replayNameToFileName(nameField.getText())).exists()); && Files.notExists(Utils.replayNameToPath(path.getParent(), nameField.getText())));
}); });
popup.onAccept(() -> { popup.onAccept(() -> {
// Sanitize their input // Sanitize their input
String newName = nameField.getText().trim(); String newName = nameField.getText().trim();
// This file is what they want // This file is what they want
File targetFile = new File(file.getParentFile(), Utils.replayNameToFileName(newName)); Path targetPath = Utils.replayNameToPath(path.getParent(), newName);
try { try {
// Finally, try to move it // Finally, try to move it
FileUtils.moveFile(file, targetFile); Files.move(path, targetPath);
} catch (IOException e) { } catch (IOException e) {
// We failed (might also be their OS) // We failed (might also be their OS)
e.printStackTrace(); e.printStackTrace();

View File

@@ -18,7 +18,7 @@ public abstract class MixinGuiSpectator {
//$$ @Inject(method = "func_175260_a", at = @At("HEAD"), cancellable = true) //$$ @Inject(method = "func_175260_a", at = @At("HEAD"), cancellable = true)
//#endif //#endif
public void isInReplay( public void isInReplay(
//#if MC>=11400 //#if MC>=11400 && MC<11802
double i, double i,
//#else //#else
//$$ int i, //$$ int i,

View File

@@ -0,0 +1,35 @@
//#if MC>=11400
package com.replaymod.replay.mixin;
import com.replaymod.replay.camera.CameraEntity;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.world.GameMode;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import static com.replaymod.core.versions.MCVer.getMinecraft;
@Pseudo
@Mixin(targets = "net.coderbot.iris.pipeline.HandRenderer", remap = false)
public abstract class Mixin_ShowSpectatedHand_Iris {
@Redirect(
method = "*",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;getCurrentGameMode()Lnet/minecraft/world/GameMode;",
remap = true
)
)
private GameMode getGameMode(ClientPlayerInteractionManager interactionManager) {
ClientPlayerEntity camera = getMinecraft().player;
if (camera instanceof CameraEntity) {
// alternative doesn't really matter, the caller only checks for equality to SPECTATOR
return camera.isSpectator() ? GameMode.SPECTATOR : GameMode.SURVIVAL;
}
return interactionManager.getCurrentGameMode();
}
}
//#endif

View File

@@ -0,0 +1,38 @@
package com.replaymod.replay.mixin.world_border;
import com.replaymod.core.versions.MCVer;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replay.ReplayModReplay;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Normally Minecraft's world border movement is based off real time;
* this redirect ensures that it is synced with the time in the Replay instead.
*/
//#if MC>=11400
// FIXME: preprocessor should be able to remap between fabric and forge
//#if FABRIC
@Mixin(targets = "net.minecraft.world.border.WorldBorder.MovingArea")
//#else
//$$ @Mixin(targets = "net.minecraft.world.border.WorldBorder.MovingBorderInfo")
//#endif
//#else
//$$ @Mixin(net.minecraft.world.border.WorldBorder.class)
//#endif
public class Mixin_UseReplayTime_ForMovement {
//#if MC>=11400
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;getMeasuringTimeMs()J"))
//#else
//$$ @Redirect(method = "*", at = @At(value = "INVOKE", target = "Ljava/lang/System;currentTimeMillis()J"))
//#endif
private long getWorldBorderTime() {
ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler();
if (replayHandler != null) {
return replayHandler.getReplaySender().currentTimeStamp();
}
return MCVer.milliTime();
}
}

View File

@@ -0,0 +1,29 @@
package com.replaymod.replay.mixin.world_border;
import com.replaymod.core.versions.MCVer;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replay.ReplayModReplay;
import net.minecraft.client.render.WorldRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Normally Minecraft's world border texture animation is based off real time;
* this redirect ensures that it is synced with the time in the Replay instead.
*/
@Mixin(WorldRenderer.class)
public class Mixin_UseReplayTime_ForTexture {
//#if MC>=11400
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;getMeasuringTimeMs()J"))
//#else
//$$ @Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J"))
//#endif
private long getWorldBorderTime() {
ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler();
if (replayHandler != null) {
return replayHandler.getReplaySender().currentTimeStamp();
}
return MCVer.milliTime();
}
}

View File

@@ -9,10 +9,49 @@ uniform int direction;
const float eyeDistance = 0.14; const float eyeDistance = 0.14;
void orient(vec4 position, int orientation) {
float z;
if (orientation == 0) { // LEFT
z = position.z;
position.z = position.x;
position.x = -z;
} else if (orientation == 1) { // RIGHT
z = position.z;
position.z = -position.x;
position.x = z;
} else if (orientation == 2) { // FRONT
// No changes required
} else if (orientation == 3) { // BACK
position.x = -position.x;
position.z = -position.z;
} else if (orientation == 4) { // TOP
z = position.z;
position.z = -position.y;
position.y = z;
} else if (orientation == 5) { // BOTTOM
z = position.z;
position.z = position.y;
position.y = -z;
}
}
void orientInverse(vec4 position, int orientation) {
if (orientation < 2) {
orient(position, 1 - orientation); // LEFT and RIGHT flip
} else if (orientation < 4) {
orient(position, orientation); // FRONT and BACK are their own inverses
} else {
orient(position, (1 - (orientation - 4)) + 4); // TOP and BOTTOM flip
}
}
void main() { void main() {
// Transform to view space // Transform to view space
vec4 position = gl_ModelViewMatrix * gl_Vertex; vec4 position = gl_ModelViewMatrix * gl_Vertex;
// Undo the camera rotation, so we always apply our stereo effect looking in the same direction
orientInverse(position, direction);
// Distort for ODS // Distort for ODS
// O := The origin // O := The origin
// P := The current vertex/point // P := The current vertex/point
@@ -34,30 +73,8 @@ void main() {
// Calculate the vector between O and T and finally move the vertex by that vector // Calculate the vector between O and T and finally move the vertex by that vector
position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0); position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0);
// Rotate for different cubic views // Rotate back into the correct cubic view
float z; orient(position, direction);
if (direction == 0) { // LEFT
z = position.z;
position.z = position.x;
position.x = -z;
} else if (direction == 1) { // RIGHT
z = position.z;
position.z = -position.x;
position.x = z;
} else if (direction == 2) { // FRONT
// No changes required
} else if (direction == 3) { // BACK
position.x = -position.x;
position.z = -position.z;
} else if (direction == 4) { // TOP
z = position.z;
position.z = -position.y;
position.y = z;
} else if (direction == 5) { // BOTTOM
z = position.z;
position.z = position.y;
position.y = -z;
}
// Transform to screen space // Transform to screen space
gl_Position = gl_ProjectionMatrix * position; gl_Position = gl_ProjectionMatrix * position;

View File

@@ -14,10 +14,49 @@ uniform int direction;
const float eyeDistance = 0.14; const float eyeDistance = 0.14;
void orient(vec4 position, int orientation) {
float z;
if (orientation == 0) { // LEFT
z = position.z;
position.z = position.x;
position.x = -z;
} else if (orientation == 1) { // RIGHT
z = position.z;
position.z = -position.x;
position.x = z;
} else if (orientation == 2) { // FRONT
// No changes required
} else if (orientation == 3) { // BACK
position.x = -position.x;
position.z = -position.z;
} else if (orientation == 4) { // TOP
z = position.z;
position.z = -position.y;
position.y = z;
} else if (orientation == 5) { // BOTTOM
z = position.z;
position.z = position.y;
position.y = -z;
}
}
void orientInverse(vec4 position, int orientation) {
if (orientation < 2) {
orient(position, 1 - orientation); // LEFT and RIGHT flip
} else if (orientation < 4) {
orient(position, orientation); // FRONT and BACK are their own inverses
} else {
orient(position, (1 - (orientation - 4)) + 4); // TOP and BOTTOM flip
}
}
void main() { void main() {
// Transform to view space // Transform to view space
vec4 position = gl_ModelViewMatrix * gl_Vertex; vec4 position = gl_ModelViewMatrix * gl_Vertex;
// Undo the camera rotation, so we always apply our stereo effect looking in the same direction
orientInverse(position, direction);
// Distort for ODS // Distort for ODS
// O := The origin // O := The origin
// P := The current vertex/point // P := The current vertex/point
@@ -39,30 +78,8 @@ void main() {
// Calculate the vector between O and T and finally move the vertex by that vector // Calculate the vector between O and T and finally move the vertex by that vector
position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0); position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0);
// Rotate for different cubic views // Rotate back into the correct cubic view
float z; orient(position, direction);
if (direction == 0) { // LEFT
z = position.z;
position.z = position.x;
position.x = -z;
} else if (direction == 1) { // RIGHT
z = position.z;
position.z = -position.x;
position.x = z;
} else if (direction == 2) { // FRONT
// No changes required
} else if (direction == 3) { // BACK
position.x = -position.x;
position.z = -position.z;
} else if (direction == 4) { // TOP
z = position.z;
position.z = -position.y;
position.y = z;
} else if (direction == 5) { // BOTTOM
z = position.z;
position.z = position.y;
position.y = -z;
}
// Transform to screen space // Transform to screen space
gl_Position = gl_ProjectionMatrix * position; gl_Position = gl_ProjectionMatrix * position;

View File

@@ -47,6 +47,10 @@
"fabric-resource-loader-v0": "*" "fabric-resource-loader-v0": "*"
}, },
"conflicts": {
"iris": "<1.1.3"
},
"custom": { "custom": {
"mm:early_risers": [ "mm:early_risers": [
"com.replaymod.core.ReplayModMMLauncher" "com.replaymod.core.ReplayModMMLauncher"

View File

@@ -11,8 +11,10 @@
"SPacketSpawnMobAccessor", "SPacketSpawnMobAccessor",
"SPacketSpawnPlayerAccessor", "SPacketSpawnPlayerAccessor",
"MixinServerInfo", "MixinServerInfo",
//#if MC>=11400 //#if MC>=10800
"MixinDownloadingPackFinder", "MixinDownloadingPackFinder",
//#endif
//#if MC>=11400
"MixinMouseHelper", "MixinMouseHelper",
//#endif //#endif
//#if MC>=10904 //#if MC>=10904

View File

@@ -14,9 +14,7 @@
"Mixin_HideNameTags", "Mixin_HideNameTags",
"Mixin_HideNameTags_LivingEntity", "Mixin_HideNameTags_LivingEntity",
"Mixin_Omnidirectional_Camera", "Mixin_Omnidirectional_Camera",
"Mixin_Omnidirectional_DisableFrustumCulling",
"Mixin_Omnidirectional_Rotation", "Mixin_Omnidirectional_Rotation",
"Mixin_Omnidirectional_SkipHand",
"Mixin_PreserveDepthDuringGuiRendering", "Mixin_PreserveDepthDuringGuiRendering",
"Mixin_SkipBlockOutlinesDuringRender", "Mixin_SkipBlockOutlinesDuringRender",
"Mixin_SkipHudDuringRender", "Mixin_SkipHudDuringRender",
@@ -39,6 +37,7 @@
"Mixin_PreserveDepthDuringHandRendering", "Mixin_PreserveDepthDuringHandRendering",
"Mixin_WindowsWorkaroundForTinyEXRNatives", "Mixin_WindowsWorkaroundForTinyEXRNatives",
//#endif //#endif
"GameRendererAccessor",
"MainWindowAccessor", "MainWindowAccessor",
"WorldRendererAccessor", "WorldRendererAccessor",
//#if MC>=10904 //#if MC>=10904

View File

@@ -7,6 +7,8 @@
"client": [ "client": [
"entity_tracking.Mixin_EntityExt", "entity_tracking.Mixin_EntityExt",
"entity_tracking.Mixin_FixPartialUpdates", "entity_tracking.Mixin_FixPartialUpdates",
"world_border.Mixin_UseReplayTime_ForMovement",
"world_border.Mixin_UseReplayTime_ForTexture",
"Mixin_FixNPCSkinCaching", "Mixin_FixNPCSkinCaching",
//#if MC>=11800 //#if MC>=11800
//$$ "Mixin_FixEntityNotTracking", //$$ "Mixin_FixEntityNotTracking",
@@ -23,6 +25,7 @@
"ClientWorldAccessor", "ClientWorldAccessor",
"EntityLivingBaseAccessor", "EntityLivingBaseAccessor",
//#if MC>=11400 //#if MC>=11400
"Mixin_ShowSpectatedHand_Iris",
"Mixin_ShowSpectatedHand_NoOF", "Mixin_ShowSpectatedHand_NoOF",
"Mixin_ShowSpectatedHand_OF", "Mixin_ShowSpectatedHand_OF",
//#else //#else

View File

@@ -1,14 +1,6 @@
{ {
"pack": { "pack": {
"description": "ReplayMod resources", "description": "ReplayMod resources",
//#if MC>=11400
"pack_format": 4 "pack_format": 4
//#else
//#if MC>=11002
//$$ "pack_format": 2
//#else
//$$ "pack_format": 1
//#endif
//#endif
} }
} }

View File

@@ -1 +1 @@
2.6.3 2.6.4

View File

@@ -34,6 +34,11 @@ public class Window implements MainWindowAccessor {
mc.displayHeight = value; mc.displayHeight = value;
} }
@Override
public void invokeUpdateFramebufferSize() {
// no-op, pre-LWJGL3 MC doesn't differentiate between window and framebuffer size
}
public long getHandle() { public long getHandle() {
return 0; return 0;
} }

View File

@@ -3,6 +3,7 @@ package com.replaymod.render.mixin;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Minecraft.class) @Mixin(Minecraft.class)
public interface MainWindowAccessor { public interface MainWindowAccessor {
@@ -14,4 +15,6 @@ public interface MainWindowAccessor {
int getFramebufferHeight(); int getFramebufferHeight();
@Accessor("displayHeight") @Accessor("displayHeight")
void setFramebufferHeight(int value); void setFramebufferHeight(int value);
@Invoker
void invokeUpdateFramebufferSize();
} }

View File

@@ -0,0 +1,6 @@
{
"pack": {
"description": "ReplayMod resources",
"pack_format": 2
}
}

0
versions/1.18.2/.gitkeep Normal file
View File

View File

@@ -0,0 +1,6 @@
{
"pack": {
"description": "ReplayMod resources",
"pack_format": 1
}
}

View File

@@ -1 +1,2 @@
net.minecraft.client.network.ClientPlayerEntity getUnderwaterVisibility() method_3140() net.minecraft.client.network.ClientPlayerEntity getUnderwaterVisibility() method_3140()
net.minecraft.client.render.GameRenderer getBasicProjectionMatrix() method_22973()