Release 2.6.4
This commit is contained in:
19
build.gradle
19
build.gradle
@@ -245,6 +245,7 @@ dependencies {
|
||||
11701: '1.17.1',
|
||||
11800: '1.18',
|
||||
11801: '1.18.1',
|
||||
11802: '1.18.2',
|
||||
][mcVersion]
|
||||
mappings 'net.fabricmc:yarn:' + [
|
||||
11404: '1.14.4+build.16',
|
||||
@@ -256,6 +257,7 @@ dependencies {
|
||||
11701: '1.17.1+build.29:v2',
|
||||
11800: '1.18+build.1:v2',
|
||||
11801: '1.18.1+build.1:v2',
|
||||
11802: '1.18.2+build.1:v2',
|
||||
][mcVersion]
|
||||
modImplementation 'net.fabricmc:fabric-loader:0.12.5'
|
||||
def fabricApiVersion = [
|
||||
@@ -268,6 +270,7 @@ dependencies {
|
||||
11701: '0.37.1+1.17',
|
||||
11800: '0.43.1+1.18',
|
||||
11801: '0.43.1+1.18',
|
||||
11802: '0.47.9+1.18.2',
|
||||
][mcVersion]
|
||||
def fabricApiModules = [
|
||||
"api-base",
|
||||
@@ -338,7 +341,7 @@ dependencies {
|
||||
|
||||
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) {
|
||||
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'
|
||||
|
||||
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'
|
||||
} else if (mcVersion >= 11700) {
|
||||
modImplementation 'com.terraformersmc:modmenu:2.0.0-beta.7'
|
||||
} else if (mcVersion >= 11602) {
|
||||
modImplementation 'com.terraformersmc:modmenu:1.16.8'
|
||||
} 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 {
|
||||
modImplementation 'io.github.prospector.modmenu:ModMenu:1.6.2-92'
|
||||
modImplementation 'com.terraformersmc:modmenu:1.10.6'
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
## Quick Mode [quickmode] (Minecraft 1.9 and up)
|
||||
## Quick Mode [quickmode]
|
||||

|
||||
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.
|
||||
By default, **Quick Mode** is toggled with `Q`.
|
||||
|
||||
**Quick Mode** is available in ReplayMod for Minecraft 1.9.4 and up.
|
||||
|
||||
## Player Overview [overview]
|
||||

|
||||
The **Player Overview** Screen
|
||||
|
||||
2
jGui
2
jGui
Submodule jGui updated: c79b62a73e...5e41452b0e
@@ -189,6 +189,7 @@ val doRelease by tasks.registering {
|
||||
defaultTasks("bundleJar")
|
||||
|
||||
preprocess {
|
||||
val mc11802 = createNode("1.18.2", 11802, "yarn")
|
||||
val mc11801 = createNode("1.18.1", 11801, "yarn")
|
||||
val mc11701 = createNode("1.17.1", 11701, "yarn")
|
||||
val mc11700 = createNode("1.17", 11700, "yarn")
|
||||
@@ -208,6 +209,7 @@ preprocess {
|
||||
val mc10800 = createNode("1.8", 10800, "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"))
|
||||
mc11701.link(mc11700)
|
||||
mc11700.link(mc11604, file("versions/mapping-fabric-1.17-1.16.4.txt"))
|
||||
|
||||
@@ -32,6 +32,7 @@ val jGuiVersions = listOf(
|
||||
"1.17",
|
||||
"1.17.1",
|
||||
"1.18.1",
|
||||
"1.18.2",
|
||||
)
|
||||
val replayModVersions = listOf(
|
||||
// "1.7.10",
|
||||
@@ -52,6 +53,7 @@ val replayModVersions = listOf(
|
||||
"1.17",
|
||||
"1.17.1",
|
||||
"1.18.1",
|
||||
"1.18.2",
|
||||
)
|
||||
|
||||
rootProject.buildFileName = "root.gradle.kts"
|
||||
|
||||
@@ -9,6 +9,10 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
//#if FABRIC
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
//#endif
|
||||
|
||||
//#if MC>=11400
|
||||
import java.io.InputStream;
|
||||
//#else
|
||||
@@ -34,6 +38,9 @@ public class ReplayModMixinConfigPlugin implements IMixinConfigPlugin {
|
||||
|
||||
private final Logger logger = LogManager.getLogger("replaymod/mixin");
|
||||
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);
|
||||
@@ -49,6 +56,9 @@ public class ReplayModMixinConfigPlugin implements IMixinConfigPlugin {
|
||||
}
|
||||
if (mixinClassName.endsWith("_OF")) return hasOF;
|
||||
if (mixinClassName.endsWith("_NoOF")) return !hasOF;
|
||||
//#if FABRIC
|
||||
if (mixinClassName.endsWith("_Iris")) return hasIris;
|
||||
//#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.nio.file.WatchService;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.replaymod.core.utils.Utils.ensureDirectoryExists;
|
||||
import static com.replaymod.core.versions.MCVer.getMinecraft;
|
||||
|
||||
class SettingsRegistryBackend {
|
||||
@@ -179,7 +180,7 @@ class SettingsRegistryBackend {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
String config = gson.toJson(root);
|
||||
try {
|
||||
Files.createDirectories(configFile.getParent());
|
||||
ensureDirectoryExists(configFile.getParent());
|
||||
Files.write(configFile, config.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.net.URLDecoder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.replaymod.core.utils.Utils.ensureDirectoryExists;
|
||||
|
||||
public class ReplayFoldersService {
|
||||
private final Path mcDir = MinecraftClient.getInstance().runDirectory.toPath();
|
||||
private final SettingsRegistry settings;
|
||||
@@ -19,14 +21,14 @@ public class ReplayFoldersService {
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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 {
|
||||
Files.setAttribute(path, "dos:hidden", true);
|
||||
} catch (UnsupportedOperationException ignored) {
|
||||
|
||||
@@ -56,11 +56,16 @@ public class RestoreReplayGui extends AbstractGuiScreen<RestoreReplayGui> {
|
||||
new GuiLabel().setI18nText("replaymod.gui.restorereplay1"),
|
||||
new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())),
|
||||
new GuiLabel().setI18nText("replaymod.gui.restorereplay3"));
|
||||
|
||||
LOGGER.info("Found partially saved replay, offering recovery: " + file);
|
||||
|
||||
yesButton.onClick(() -> {
|
||||
LOGGER.info("Attempting recovery: " + file);
|
||||
recoverInBackground();
|
||||
parent.display();
|
||||
});
|
||||
noButton.onClick(() -> {
|
||||
LOGGER.info("Recovery rejected, marking for deletion: " + file);
|
||||
try {
|
||||
File tmp = new File(file.getParentFile(), file.getName() + ".tmp");
|
||||
File deleted = new File(file.getParentFile(), file.getName() + ".del");
|
||||
|
||||
@@ -63,6 +63,7 @@ public abstract class MixinMinecraft
|
||||
PostRenderCallback.EVENT.invoker().postRender();
|
||||
}
|
||||
//#else
|
||||
//$$ @Shadow long systemTime;
|
||||
//#if MC>=10904
|
||||
//$$ @Shadow protected abstract void runTickKeyboard() throws IOException;
|
||||
//$$ @Shadow protected abstract void runTickMouse() throws IOException;
|
||||
@@ -80,6 +81,8 @@ public abstract class MixinMinecraft
|
||||
//$$ public void replayModRunTickMouse() {
|
||||
//$$ try {
|
||||
//$$ runTickMouse();
|
||||
//$$ // Update last tick time (MC ignores inputs when there hasn't been a tick in 200ms)
|
||||
//$$ systemTime = Minecraft.getSystemTime();
|
||||
//$$ } catch (IOException e) {
|
||||
//$$ 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)
|
||||
//$$ 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
|
||||
//$$ @Redirect(
|
||||
|
||||
@@ -55,8 +55,13 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
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.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
@@ -166,8 +171,45 @@ public class Utils {
|
||||
|
||||
private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false);
|
||||
|
||||
public static String replayNameToFileName(String replayName) {
|
||||
return REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr";
|
||||
public static Path replayNameToPath(Path folder, String replayName) {
|
||||
// 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) {
|
||||
@@ -356,4 +398,14 @@ public class Utils {
|
||||
configure.accept(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ public class MCVer {
|
||||
|
||||
//#if MC<=11601
|
||||
//$$ 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
|
||||
|
||||
|
||||
@@ -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.ReadableDimension;
|
||||
import net.minecraft.util.crash.CrashReport;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -31,6 +33,8 @@ import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private final Path inputPath;
|
||||
|
||||
private final EditTimeline timeline;
|
||||
@@ -60,6 +64,8 @@ public class GuiEditReplay extends AbstractGuiPopup<GuiEditReplay> {
|
||||
super(container);
|
||||
this.inputPath = inputPath;
|
||||
|
||||
LOGGER.info("Opening replay in editor: " + inputPath);
|
||||
|
||||
try (ReplayFile replayFile = ReplayMod.instance.files.open(inputPath)) {
|
||||
markers = replayFile.getMarkers().or(HashSet::new);
|
||||
timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers);
|
||||
|
||||
@@ -133,7 +133,6 @@ public class MarkerProcessor {
|
||||
for (int i = 1; Files.exists(inputPath); i++) {
|
||||
inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr");
|
||||
}
|
||||
Files.createDirectories(inputPath.getParent());
|
||||
Files.move(path, inputPath);
|
||||
|
||||
try (ReplayFile inputReplayFile = mod.files.open(inputPath)) {
|
||||
|
||||
@@ -25,7 +25,7 @@ public class QuickMode extends EventRegistrations implements Extra {
|
||||
return;
|
||||
}
|
||||
replayHandler.getReplaySender().setSyncModeAndWait();
|
||||
mod.runLater(() -> {
|
||||
mod.runLaterWithoutLock(() -> {
|
||||
replayHandler.ensureQuickModeInitialized(() -> {
|
||||
boolean enabled = !replayHandler.isQuickMode();
|
||||
updateIndicator(replayHandler.getOverlay(), enabled);
|
||||
|
||||
@@ -40,7 +40,7 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable {
|
||||
new GuiLabel().setI18nText("replaymod.gui.advancedscreenshots.resolution"), videoResolutionPanel,
|
||||
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))
|
||||
.addElements(new GridLayout.Data(0, 0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel,
|
||||
|
||||
@@ -155,9 +155,9 @@ public class GuiSavingReplay {
|
||||
|
||||
try {
|
||||
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++) {
|
||||
newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName + " (" + i + ")"));
|
||||
newPath = Utils.replayNameToPath(replaysFolder, newName + " (" + i + ")");
|
||||
}
|
||||
Files.move(path, newPath);
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -127,7 +127,7 @@ public class ConnectionEventHandler {
|
||||
}
|
||||
|
||||
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.writeModInfo(ModCompat.getInstalledNetworkMods());
|
||||
|
||||
@@ -33,7 +33,9 @@ public abstract class MixinMouseHelper {
|
||||
long _p0, double _p1, double _p2,
|
||||
CallbackInfo ci,
|
||||
double _l1,
|
||||
//#if MC>=11400
|
||||
//#if MC>=11802
|
||||
//$$ int yOffsetAccumulated
|
||||
//#elseif MC>=11400
|
||||
float yOffsetAccumulated
|
||||
//#else
|
||||
//$$ double yOffsetAccumulated
|
||||
|
||||
@@ -16,6 +16,10 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
//#if MC>=11802
|
||||
//$$ import net.minecraft.util.registry.RegistryEntry;
|
||||
//#endif
|
||||
|
||||
//#if MC>=11600
|
||||
import net.minecraft.util.registry.RegistryKey;
|
||||
import net.minecraft.world.MutableWorldProperties;
|
||||
@@ -52,7 +56,12 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa
|
||||
//#if MC<11602
|
||||
//$$ RegistryKey<DimensionType> registryKey2,
|
||||
//#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,
|
||||
//#if MC<11602
|
||||
//$$ registryKey2,
|
||||
|
||||
@@ -285,7 +285,6 @@ public class PacketListener extends ChannelInboundHandlerAdapter {
|
||||
for (int i = 1; Files.exists(rawPath); i++) {
|
||||
rawPath = rawPath.resolveSibling(replayName + "." + i + ".mcpr");
|
||||
}
|
||||
Files.createDirectories(rawPath.getParent());
|
||||
replayFile.saveTo(rawPath.toFile());
|
||||
replayFile.close();
|
||||
|
||||
|
||||
@@ -28,9 +28,11 @@ import static org.lwjgl.util.tinyexr.TinyEXR.*;
|
||||
public class EXRWriter implements FrameConsumer<BitmapFrame> {
|
||||
|
||||
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.keepAlpha = keepAlpha;
|
||||
|
||||
Files.createDirectories(outputFolder);
|
||||
}
|
||||
@@ -92,11 +94,15 @@ public class EXRWriter implements FrameConsumer<BitmapFrame> {
|
||||
bgrChannels[(i + 3) % 4] = channel;
|
||||
}
|
||||
}
|
||||
|
||||
int alphaMask = keepAlpha ? 0 : 0xff;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (FloatBuffer channel : bgrChannels) {
|
||||
channel.put(((int) bgra.get() & 0xff) / 255f);
|
||||
}
|
||||
bgrChannels[0].put(((int) bgra.get() & 0xff) / 255f); // b
|
||||
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) {
|
||||
|
||||
@@ -19,9 +19,11 @@ import java.util.Map;
|
||||
public class PNGWriter implements FrameConsumer<BitmapFrame> {
|
||||
|
||||
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.keepAlpha = keepAlpha;
|
||||
|
||||
Files.createDirectories(outputFolder);
|
||||
}
|
||||
@@ -47,6 +49,7 @@ public class PNGWriter implements FrameConsumer<BitmapFrame> {
|
||||
}
|
||||
|
||||
private void withImage(BitmapFrame frame, IOConsumer<Image> consumer) throws IOException {
|
||||
byte alphaMask = (byte) (keepAlpha ? 0 : 0xff);
|
||||
ByteBuffer buffer = frame.getByteBuffer();
|
||||
ReadableDimension size = frame.getSize();
|
||||
int width = size.getWidth();
|
||||
@@ -58,7 +61,7 @@ public class PNGWriter implements FrameConsumer<BitmapFrame> {
|
||||
byte g = buffer.get();
|
||||
byte r = 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);
|
||||
|
||||
@@ -151,6 +151,7 @@ public class RenderSettings {
|
||||
private final File outputFile;
|
||||
|
||||
private final boolean renderNameTags;
|
||||
private final boolean includeAlphaChannel;
|
||||
private final boolean stabilizeYaw;
|
||||
private final boolean stabilizePitch;
|
||||
private final boolean stabilizeRoll;
|
||||
@@ -187,6 +188,7 @@ public class RenderSettings {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
360,
|
||||
180,
|
||||
@@ -209,6 +211,7 @@ public class RenderSettings {
|
||||
int bitRate,
|
||||
File outputFile,
|
||||
boolean renderNameTags,
|
||||
boolean includeAlphaChannel,
|
||||
boolean stabilizeYaw,
|
||||
boolean stabilizePitch,
|
||||
boolean stabilizeRoll,
|
||||
@@ -231,6 +234,7 @@ public class RenderSettings {
|
||||
this.bitRate = bitRate;
|
||||
this.outputFile = outputFile;
|
||||
this.renderNameTags = renderNameTags;
|
||||
this.includeAlphaChannel = includeAlphaChannel;
|
||||
this.stabilizeYaw = stabilizeYaw;
|
||||
this.stabilizePitch = stabilizePitch;
|
||||
this.stabilizeRoll = stabilizeRoll;
|
||||
@@ -256,6 +260,7 @@ public class RenderSettings {
|
||||
bitRate,
|
||||
outputFile,
|
||||
renderNameTags,
|
||||
includeAlphaChannel,
|
||||
stabilizeYaw,
|
||||
stabilizePitch,
|
||||
stabilizeRoll,
|
||||
@@ -415,6 +420,10 @@ public class RenderSettings {
|
||||
return renderNameTags;
|
||||
}
|
||||
|
||||
public boolean isIncludeAlphaChannel() {
|
||||
return includeAlphaChannel;
|
||||
}
|
||||
|
||||
public boolean isStabilizeYaw() {
|
||||
return stabilizeYaw;
|
||||
}
|
||||
@@ -478,6 +487,7 @@ public class RenderSettings {
|
||||
", bitRate=" + bitRate +
|
||||
", outputFile=" + outputFile +
|
||||
", renderNameTags=" + renderNameTags +
|
||||
", includeAlphaChannel=" + includeAlphaChannel +
|
||||
", stabilizeYaw=" + stabilizeYaw +
|
||||
", stabilizePitch=" + stabilizePitch +
|
||||
", stabilizeRoll=" + stabilizeRoll +
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//#if MC>=11600
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.replaymod.render.RenderSettings;
|
||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||
import com.replaymod.render.frame.ODSOpenGlFrame;
|
||||
@@ -16,18 +15,13 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
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 static final String SHADER_PACK_NAME = "assets/replaymod/iris/ods";
|
||||
public static IrisODSFrameCapturer INSTANCE;
|
||||
private final CubicPboOpenGlFrameCapturer left, right;
|
||||
private final String prevShaderPack;
|
||||
private final boolean prevShadersEnabled;
|
||||
private int direction;
|
||||
private boolean isLeftEye;
|
||||
|
||||
@@ -67,13 +61,16 @@ public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
|
||||
right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize);
|
||||
|
||||
INSTANCE = this;
|
||||
prevShaderPack = Iris.getIrisConfig().getShaderPackName().orElse(null);
|
||||
setShaderPack(SHADER_PACK_NAME);
|
||||
IrisConfig irisConfig = Iris.getIrisConfig();
|
||||
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.setShaderPackName(name);
|
||||
irisConfig.setShadersEnabled(enabled);
|
||||
try {
|
||||
irisConfig.save();
|
||||
Iris.reload();
|
||||
@@ -121,7 +118,7 @@ public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
|
||||
left.close();
|
||||
right.close();
|
||||
INSTANCE = null;
|
||||
setShaderPack(prevShaderPack);
|
||||
setShaderPack(prevShaderPack, prevShadersEnabled);
|
||||
}
|
||||
|
||||
private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer {
|
||||
@@ -131,25 +128,8 @@ public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
|
||||
|
||||
@Override
|
||||
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();
|
||||
worldRenderer.renderWorld(partialTicks, null);
|
||||
|
||||
frameBuffer().endWrite();
|
||||
popMatrix();
|
||||
|
||||
return captureFrame(frameId, captureData);
|
||||
return super.renderFrame(frameId, partialTicks, captureData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.replaymod.render.rendering.Channel;
|
||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||
import com.replaymod.render.RenderSettings;
|
||||
@@ -20,12 +19,6 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
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> {
|
||||
private static final Identifier vertexResource = new Identifier("replaymod", "shader/ods.vert");
|
||||
private static final Identifier fragmentResource = new Identifier("replaymod", "shader/ods.frag");
|
||||
@@ -166,25 +159,8 @@ public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
|
||||
|
||||
@Override
|
||||
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());
|
||||
worldRenderer.renderWorld(partialTicks, null);
|
||||
|
||||
frameBuffer().endWrite();
|
||||
popMatrix();
|
||||
|
||||
return captureFrame(frameId, captureData);
|
||||
return super.renderFrame(frameId, partialTicks, captureData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public class GuiExportFailed extends GuiScreen {
|
||||
oldSettings.getBitRate(),
|
||||
oldSettings.getOutputFile(),
|
||||
oldSettings.isRenderNameTags(),
|
||||
oldSettings.isIncludeAlphaChannel(),
|
||||
oldSettings.isStabilizeYaw(),
|
||||
oldSettings.isStabilizePitch(),
|
||||
oldSettings.isStabilizeRoll(),
|
||||
|
||||
48
src/main/java/com/replaymod/render/gui/GuiNoFfmpeg.java
Normal file
48
src/main/java/com/replaymod/render/gui/GuiNoFfmpeg.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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.ReadablePoint;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.NoticeScreen;
|
||||
import net.minecraft.util.crash.CrashReport;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -181,17 +180,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements
|
||||
videoRenderer.renderVideo();
|
||||
} catch (FFmpegWriter.NoFFmpegException e) {
|
||||
LOGGER.error("Rendering video:", e);
|
||||
NoticeScreen errorScreen = new NoticeScreen(
|
||||
//#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);
|
||||
mc.openScreen(new GuiNoFfmpeg(container::display).toMinecraft());
|
||||
return;
|
||||
} catch (FFmpegWriter.FFmpegStartupException e) {
|
||||
int jobsToSkip = jobsDone;
|
||||
@@ -229,7 +218,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements
|
||||
ReplayFile replayFile = null;
|
||||
try {
|
||||
replayFile = mod.getCore().files.open(next.getKey().toPath());
|
||||
replayHandler = mod.startReplay(replayFile, true, false);
|
||||
replayHandler = mod.startReplay(replayFile, false, false);
|
||||
} catch (IOException e) {
|
||||
Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {});
|
||||
container.display(); // Re-show the queue popup and the new error popup
|
||||
|
||||
@@ -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.Dimension;
|
||||
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.util.crash.CrashReport;
|
||||
|
||||
@@ -149,6 +148,9 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
|
||||
public final GuiCheckbox nametagCheckbox = new GuiCheckbox()
|
||||
.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 GuiCheckbox stabilizeYaw = new GuiCheckbox(stabilizePanel)
|
||||
.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);
|
||||
|
||||
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))
|
||||
.addElements(new GridLayout.Data(0, 0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel,
|
||||
@@ -240,17 +242,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
|
||||
videoRenderer.renderVideo();
|
||||
} catch (FFmpegWriter.NoFFmpegException e) {
|
||||
LOGGER.error("Rendering video:", e);
|
||||
NoticeScreen errorScreen = new NoticeScreen(
|
||||
//#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);
|
||||
getMinecraft().openScreen(new GuiNoFfmpeg(getScreen()::display).toMinecraft());
|
||||
} catch (FFmpegWriter.FFmpegStartupException e) {
|
||||
GuiExportFailed.tryToRecover(e, newSettings -> {
|
||||
// Update settings with fixed ffmpeg arguments
|
||||
@@ -533,6 +525,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
|
||||
}
|
||||
outputFileButton.setLabel(this.outputFile.getName());
|
||||
nametagCheckbox.setChecked(settings.isRenderNameTags());
|
||||
alphaCheckbox.setChecked(settings.isIncludeAlphaChannel());
|
||||
stabilizeYaw.setChecked(settings.isStabilizeYaw());
|
||||
stabilizePitch.setChecked(settings.isStabilizePitch());
|
||||
stabilizeRoll.setChecked(settings.isStabilizeRoll());
|
||||
@@ -571,6 +564,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
|
||||
bitRateField.getInteger() << (10 * bitRateUnit.getSelected()),
|
||||
serialize && !userDefinedOutputFileName ? getParentFile(outputFile) : outputFile,
|
||||
nametagCheckbox.isChecked(),
|
||||
alphaCheckbox.isChecked(),
|
||||
stabilizeYaw.isChecked() && (serialize || stabilizeYaw.isEnabled()),
|
||||
stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()),
|
||||
stabilizeRoll.isChecked() && (serialize || stabilizeRoll.isEnabled()),
|
||||
|
||||
@@ -141,7 +141,7 @@ public class GuiVideoRenderer extends GuiScreen implements Tickable {
|
||||
|
||||
@Override
|
||||
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)
|
||||
if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.replaymod.render.Setting;
|
||||
import com.replaymod.render.capturer.CaptureData;
|
||||
import com.replaymod.render.capturer.RenderInfo;
|
||||
import com.replaymod.render.capturer.WorldRenderer;
|
||||
import com.replaymod.render.mixin.GameRendererAccessor;
|
||||
import com.replaymod.replay.ReplayModReplay;
|
||||
import de.johni0702.minecraft.gui.utils.EventRegistrations;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
@@ -81,11 +82,16 @@ public class EntityRendererHandler extends EventRegistrations implements WorldRe
|
||||
//#endif
|
||||
|
||||
if (mc.world != null && mc.player != null) {
|
||||
GameRendererAccessor gameRenderer = (GameRendererAccessor) mc.gameRenderer;
|
||||
Screen orgScreen = mc.currentScreen;
|
||||
boolean orgPauseOnLostFocus = mc.options.pauseOnLostFocus;
|
||||
boolean orgRenderHand = gameRenderer.getRenderHand();
|
||||
try {
|
||||
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
|
||||
if (omnidirectional) {
|
||||
gameRenderer.setRenderHand(false); // makes no sense, we wouldn't even know where to put it
|
||||
}
|
||||
|
||||
//#if MC>=11400
|
||||
mc.gameRenderer.render(partialTicks, finishTimeNano, true);
|
||||
@@ -100,6 +106,7 @@ public class EntityRendererHandler extends EventRegistrations implements WorldRe
|
||||
} finally {
|
||||
mc.currentScreen = orgScreen;
|
||||
mc.options.pauseOnLostFocus = orgPauseOnLostFocus;
|
||||
gameRenderer.setRenderHand(orgRenderHand);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.replaymod.render.mixin;
|
||||
import net.minecraft.client.util.Window;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Window.class)
|
||||
public interface MainWindowAccessor {
|
||||
@@ -14,4 +15,12 @@ public interface MainWindowAccessor {
|
||||
int getFramebufferHeight();
|
||||
@Accessor
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,12 @@ public abstract class Mixin_ChromaKeyColorSky {
|
||||
@Shadow @Final private MinecraftClient client;
|
||||
|
||||
//#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),
|
||||
//$$ cancellable = true)
|
||||
//#elseif MC>=11400 || 10710>=MC
|
||||
|
||||
@@ -4,7 +4,6 @@ package com.replaymod.render.mixin;
|
||||
import com.replaymod.render.capturer.IrisODSFrameCapturer;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -15,14 +14,14 @@ import java.nio.file.Path;
|
||||
@Pseudo
|
||||
@Mixin(value = Iris.class, remap = false)
|
||||
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) {
|
||||
if (IrisODSFrameCapturer.INSTANCE != null && IrisODSFrameCapturer.SHADER_PACK_NAME.equals(name)) {
|
||||
return FabricLoader.getInstance().getModContainer("replaymod")
|
||||
.orElseThrow(() -> new RuntimeException("Failed to get mod container for ReplayMod"))
|
||||
.getRootPath();
|
||||
} else {
|
||||
return Iris.SHADERPACKS_DIRECTORY;
|
||||
return Iris.getShaderpacksDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
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) {
|
||||
return replayModRender_perspective((float) fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
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) {
|
||||
if (replayModRender_getHandler() != null) {
|
||||
Matrix4f offset;
|
||||
|
||||
@@ -120,6 +120,7 @@ public class VideoRenderer implements RenderInfo {
|
||||
|
||||
private Framebuffer guiFramebuffer;
|
||||
private int displayWidth, displayHeight;
|
||||
private int framebufferWidth, framebufferHeight;
|
||||
|
||||
public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException {
|
||||
this.settings = settings;
|
||||
@@ -135,12 +136,12 @@ public class VideoRenderer implements RenderInfo {
|
||||
FrameConsumer<BitmapFrame> frameConsumer;
|
||||
if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.EXR) {
|
||||
//#if MC>=11400
|
||||
frameConsumer = new EXRWriter(settings.getOutputFile().toPath());
|
||||
frameConsumer = new EXRWriter(settings.getOutputFile().toPath(), settings.isIncludeAlphaChannel());
|
||||
//#else
|
||||
//$$ throw new UnsupportedOperationException("EXR requires LWJGL3");
|
||||
//#endif
|
||||
} else if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.PNG) {
|
||||
frameConsumer = new PNGWriter(settings.getOutputFile().toPath());
|
||||
frameConsumer = new PNGWriter(settings.getOutputFile().toPath(), settings.isIncludeAlphaChannel());
|
||||
} else {
|
||||
frameConsumer = new FFmpegWriter(this);
|
||||
}
|
||||
@@ -245,10 +246,10 @@ public class VideoRenderer implements RenderInfo {
|
||||
public float updateForNextFrame() {
|
||||
// because the jGui lib uses Minecraft's displayWidth and displayHeight values, update these temporarily
|
||||
MainWindowAccessor acc = (MainWindowAccessor) (Object) mc.getWindow();
|
||||
int displayWidthBefore = acc.getFramebufferWidth();
|
||||
int displayHeightBefore = acc.getFramebufferHeight();
|
||||
acc.setFramebufferWidth(displayWidth);
|
||||
acc.setFramebufferHeight(displayHeight);
|
||||
int framebufferWidthBefore = acc.getFramebufferWidth();
|
||||
int framebufferHeightBefore = acc.getFramebufferHeight();
|
||||
acc.setFramebufferWidth(framebufferWidth);
|
||||
acc.setFramebufferHeight(framebufferHeight);
|
||||
|
||||
if (!settings.isHighPerformance() || framesDone % fps == 0) {
|
||||
while (drawGui() && paused) {
|
||||
@@ -289,8 +290,8 @@ public class VideoRenderer implements RenderInfo {
|
||||
}
|
||||
|
||||
// change Minecraft's display size back
|
||||
acc.setFramebufferWidth(displayWidthBefore);
|
||||
acc.setFramebufferHeight(displayHeightBefore);
|
||||
acc.setFramebufferWidth(framebufferWidthBefore);
|
||||
acc.setFramebufferHeight(framebufferHeightBefore);
|
||||
|
||||
if (cameraPathExporter != null) {
|
||||
cameraPathExporter.recordFrame(timer.tickDelta);
|
||||
@@ -362,6 +363,7 @@ public class VideoRenderer implements RenderInfo {
|
||||
}
|
||||
|
||||
updateDisplaySize();
|
||||
updateFramebufferSize();
|
||||
|
||||
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
|
||||
//#if MC>=11700
|
||||
//$$ guiFramebuffer = new WindowFramebuffer(displayWidth, displayHeight);
|
||||
//$$ guiFramebuffer = new WindowFramebuffer(framebufferWidth, framebufferHeight);
|
||||
//#else
|
||||
guiFramebuffer = new Framebuffer(displayWidth, displayHeight, true
|
||||
guiFramebuffer = new Framebuffer(framebufferWidth, framebufferHeight, true
|
||||
//#if MC>=11400
|
||||
, false
|
||||
//#endif
|
||||
@@ -425,7 +427,7 @@ public class VideoRenderer implements RenderInfo {
|
||||
}
|
||||
|
||||
// Finally, resize the Minecraft framebuffer to the actual width/height of the window
|
||||
resizeMainWindow(mc, displayWidth, displayHeight);
|
||||
resizeMainWindow(mc, framebufferWidth, framebufferHeight);
|
||||
}
|
||||
|
||||
private void executeTaskQueue() {
|
||||
@@ -483,17 +485,23 @@ public class VideoRenderer implements RenderInfo {
|
||||
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()) {
|
||||
updateDisplaySize();
|
||||
((MainWindowAccessor) (Object) window).invokeUpdateFramebufferSize();
|
||||
}
|
||||
|
||||
// Resize the GUI framebuffer if the display size changed
|
||||
if (framebufferSizeChanged()) {
|
||||
updateFramebufferSize();
|
||||
//#if MC>=11400
|
||||
guiFramebuffer.resize(displayWidth, displayHeight
|
||||
guiFramebuffer.resize(framebufferWidth, framebufferHeight
|
||||
//#if MC>=11400
|
||||
, false
|
||||
//#endif
|
||||
);
|
||||
//#else
|
||||
//$$ guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight);
|
||||
//$$ guiFramebuffer.createBindFramebuffer(framebufferWidth, framebufferHeight);
|
||||
//#endif
|
||||
}
|
||||
|
||||
@@ -594,7 +602,7 @@ public class VideoRenderer implements RenderInfo {
|
||||
guiFramebuffer.endWrite();
|
||||
popMatrix();
|
||||
pushMatrix();
|
||||
guiFramebuffer.draw(displayWidth, displayHeight);
|
||||
guiFramebuffer.draw(framebufferWidth, framebufferHeight);
|
||||
popMatrix();
|
||||
|
||||
//#if MC>=11500
|
||||
@@ -633,12 +641,18 @@ public class VideoRenderer implements RenderInfo {
|
||||
private boolean displaySizeChanged() {
|
||||
int realWidth = mc.getWindow().getWidth();
|
||||
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) {
|
||||
// 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.
|
||||
return false;
|
||||
}
|
||||
return displayWidth != realWidth || displayHeight != realHeight;
|
||||
return framebufferWidth != realWidth || framebufferHeight != realHeight;
|
||||
}
|
||||
|
||||
private void updateDisplaySize() {
|
||||
@@ -646,6 +660,11 @@ public class VideoRenderer implements RenderInfo {
|
||||
displayHeight = mc.getWindow().getHeight();
|
||||
}
|
||||
|
||||
private void updateFramebufferSize() {
|
||||
framebufferWidth = mc.getWindow().getFramebufferWidth();
|
||||
framebufferHeight = mc.getWindow().getFramebufferHeight();
|
||||
}
|
||||
|
||||
public int getFramesDone() {
|
||||
return framesDone;
|
||||
}
|
||||
|
||||
@@ -125,6 +125,9 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
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.replaystudio.util.Utils.readInt;
|
||||
@@ -349,6 +352,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
||||
return;
|
||||
}
|
||||
terminate = true;
|
||||
syncSender.shutdown();
|
||||
events.unregister();
|
||||
try {
|
||||
channelInactive(ctx);
|
||||
@@ -403,53 +407,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
||||
super.channelRead(ctx, 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.
|
||||
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
|
||||
}
|
||||
}
|
||||
maybeRemoveDeadEntities(p);
|
||||
|
||||
//#if MC>=11400
|
||||
if (p instanceof ChunkDataS2CPacket) {
|
||||
@@ -504,6 +462,69 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
||||
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.
|
||||
* @param p The packet to process
|
||||
@@ -627,7 +648,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
||||
//#if MC>=11600
|
||||
//#if MC>=11603
|
||||
packet.getDimensionIds(),
|
||||
//#if MC>=11800
|
||||
//$$ packet.registryManager(),
|
||||
//#else
|
||||
(net.minecraft.util.registry.DynamicRegistryManager.Impl) packet.getRegistryManager(),
|
||||
//#endif
|
||||
packet.getDimensionType(),
|
||||
//#else
|
||||
//$$ 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
|
||||
@@ -1085,6 +1116,10 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
||||
// 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).
|
||||
* 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
|
||||
public void sendPacketsTill(int timestamp) {
|
||||
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 {
|
||||
while (ctx == null && !terminate) { // Make sure channel is ready
|
||||
Thread.sleep(10);
|
||||
@@ -1112,7 +1177,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
||||
loginPhase = true;
|
||||
startFromBeginning = false;
|
||||
nextPacket = null;
|
||||
replayHandler.restartedReplay();
|
||||
ReplayMod.instance.runSync(replayHandler::restartedReplay);
|
||||
}
|
||||
|
||||
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 (p instanceof UnloadChunkS2CPacket) {
|
||||
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) {
|
||||
|
||||
@@ -8,6 +8,10 @@ import com.replaymod.replay.camera.CameraEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.RenderTickCounter;
|
||||
|
||||
//#if MC>=11802
|
||||
//$$ import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
|
||||
//#endif
|
||||
|
||||
//#if MC>=11400
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
//#else
|
||||
@@ -111,6 +115,15 @@ public class InputReplayTimer extends WrappedTimer {
|
||||
//#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
|
||||
return ticksThisFrame;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//#if MC>=10904
|
||||
//#if MC>=10800
|
||||
package com.replaymod.replay;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
@@ -78,7 +78,11 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
|
||||
wrappedBuf.writerIndex(size);
|
||||
PacketByteBuf packetByteBuf = new PacketByteBuf(wrappedBuf);
|
||||
|
||||
//#if MC>=10809
|
||||
Packet<?> mcPacket;
|
||||
//#else
|
||||
//$$ Packet mcPacket;
|
||||
//#endif
|
||||
//#if MC>=11700
|
||||
//$$ mcPacket = NetworkState.PLAY.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId(), packetByteBuf);
|
||||
//#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");
|
||||
} catch (Throwable 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());
|
||||
promise.setException(e);
|
||||
});
|
||||
return;
|
||||
}
|
||||
mod.getCore().runLater(() -> promise.set(null));
|
||||
mod.getCore().runLaterWithoutLock(() -> promise.set(null));
|
||||
}).start();
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ import net.minecraft.entity.LivingEntity;
|
||||
//$$ import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||
//#endif
|
||||
|
||||
//#if MC<10904
|
||||
//#if MC<10800
|
||||
//$$ import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||
//$$ import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
|
||||
//$$ import de.johni0702.minecraft.gui.utils.Colors;
|
||||
@@ -119,11 +119,11 @@ public class ReplayHandler {
|
||||
* Decodes and sends packets into channel.
|
||||
*/
|
||||
private final FullReplaySender fullReplaySender;
|
||||
//#if MC>=10904
|
||||
//#if MC>=10800
|
||||
private final QuickReplaySender quickReplaySender;
|
||||
private boolean quickMode = false;
|
||||
//#else
|
||||
//$$ private static final String QUICK_MODE_MIN_MC = "1.9.4";
|
||||
//$$ private static final String QUICK_MODE_MIN_MC = "1.8";
|
||||
//#endif
|
||||
|
||||
/**
|
||||
@@ -160,7 +160,7 @@ public class ReplayHandler {
|
||||
markers = replayFile.getMarkers().or(Collections.emptySet());
|
||||
|
||||
fullReplaySender = new FullReplaySender(this, replayFile, false);
|
||||
//#if MC>=10904
|
||||
//#if MC>=10800
|
||||
quickReplaySender = new QuickReplaySender(ReplayModReplay.instance, replayFile);
|
||||
//#endif
|
||||
|
||||
@@ -206,7 +206,7 @@ public class ReplayHandler {
|
||||
ReplayClosingCallback.EVENT.invoker().replayClosing(this);
|
||||
|
||||
fullReplaySender.terminateReplay();
|
||||
//#if MC>=10904
|
||||
//#if MC>=10800
|
||||
if (quickMode) {
|
||||
quickReplaySender.unregister();
|
||||
}
|
||||
@@ -308,7 +308,7 @@ public class ReplayHandler {
|
||||
//$$ channel = new EmbeddedChannel(dummyHandler);
|
||||
//$$ channel.pipeline().remove(dummyHandler);
|
||||
//#endif
|
||||
//#if MC>=10904
|
||||
//#if MC>=10800
|
||||
channel.pipeline().addLast("ReplayModReplay_quickReplaySender", quickReplaySender);
|
||||
//#endif
|
||||
channel.pipeline().addLast("ReplayModReplay_replaySender", fullReplaySender);
|
||||
@@ -329,7 +329,7 @@ public class ReplayHandler {
|
||||
}
|
||||
|
||||
public ReplaySender getReplaySender() {
|
||||
//#if MC>=10904
|
||||
//#if MC>=10800
|
||||
return quickMode ? quickReplaySender : fullReplaySender;
|
||||
//#else
|
||||
//$$ return fullReplaySender;
|
||||
@@ -340,7 +340,7 @@ public class ReplayHandler {
|
||||
return overlay;
|
||||
}
|
||||
|
||||
//#if MC>=10904
|
||||
//#if MC>=10800
|
||||
public void ensureQuickModeInitialized(Runnable andThen) {
|
||||
if (Utils.ifMinimalModeDoPopup(overlay, () -> {})) return;
|
||||
ListenableFuture<Void> future = quickReplaySender.getInitializationPromise();
|
||||
@@ -534,7 +534,11 @@ public class ReplayHandler {
|
||||
}
|
||||
|
||||
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) {
|
||||
// Always round to full tick
|
||||
targetTime = targetTime + targetTime % 50;
|
||||
@@ -603,6 +607,7 @@ public class ReplayHandler {
|
||||
// Render our please-wait-screen
|
||||
GuiScreen guiScreen = new GuiScreen();
|
||||
guiScreen.setBackground(AbstractGuiScreen.Background.DIRT);
|
||||
guiScreen.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER));
|
||||
guiScreen.addElements(new HorizontalLayout.Data(0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.pleasewait"));
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ import net.minecraft.stat.StatHandler;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Box;
|
||||
|
||||
//#if MC>=11802
|
||||
//$$ import net.minecraft.tag.TagKey;
|
||||
//#endif
|
||||
|
||||
//#if FABRIC>=1
|
||||
//#else
|
||||
//$$ import net.minecraftforge.client.event.EntityViewRenderEvent;
|
||||
@@ -354,7 +358,13 @@ public class CameraEntity
|
||||
|
||||
//#if MC>=11400
|
||||
@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));
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -95,8 +97,10 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
|
||||
List<GuiReplayEntry> selected = list.getSelected();
|
||||
if (selected.size() == 1) {
|
||||
File file = selected.get(0).file;
|
||||
LOGGER.info("Opening replay in viewer: " + file);
|
||||
try {
|
||||
mod.startReplay(selected.get(0).file);
|
||||
mod.startReplay(file);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -130,8 +134,8 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
public final GuiButton renameButton = new GuiButton().onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final File file = list.getSelected().get(0).file;
|
||||
String name = Utils.fileNameToReplayName(file.getName());
|
||||
final Path path = list.getSelected().get(0).file.toPath();
|
||||
String name = Utils.fileNameToReplayName(path.getFileName().toString());
|
||||
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name);
|
||||
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
|
||||
new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK),
|
||||
@@ -147,16 +151,16 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
}
|
||||
}).onTextChanged(obj -> {
|
||||
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(() -> {
|
||||
// Sanitize their input
|
||||
String newName = nameField.getText().trim();
|
||||
// This file is what they want
|
||||
File targetFile = new File(file.getParentFile(), Utils.replayNameToFileName(newName));
|
||||
Path targetPath = Utils.replayNameToPath(path.getParent(), newName);
|
||||
try {
|
||||
// Finally, try to move it
|
||||
FileUtils.moveFile(file, targetFile);
|
||||
Files.move(path, targetPath);
|
||||
} catch (IOException e) {
|
||||
// We failed (might also be their OS)
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -18,7 +18,7 @@ public abstract class MixinGuiSpectator {
|
||||
//$$ @Inject(method = "func_175260_a", at = @At("HEAD"), cancellable = true)
|
||||
//#endif
|
||||
public void isInReplay(
|
||||
//#if MC>=11400
|
||||
//#if MC>=11400 && MC<11802
|
||||
double i,
|
||||
//#else
|
||||
//$$ int i,
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,49 @@ uniform int direction;
|
||||
|
||||
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() {
|
||||
// Transform to view space
|
||||
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
|
||||
// O := The origin
|
||||
// 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
|
||||
position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0);
|
||||
|
||||
// Rotate for different cubic views
|
||||
float z;
|
||||
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;
|
||||
}
|
||||
// Rotate back into the correct cubic view
|
||||
orient(position, direction);
|
||||
|
||||
// Transform to screen space
|
||||
gl_Position = gl_ProjectionMatrix * position;
|
||||
|
||||
Submodule src/main/resources/assets/replaymod/lang updated: 54ab62bdbb...7614b69e24
@@ -14,10 +14,49 @@ uniform int direction;
|
||||
|
||||
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() {
|
||||
// Transform to view space
|
||||
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
|
||||
// O := The origin
|
||||
// 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
|
||||
position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0);
|
||||
|
||||
// Rotate for different cubic views
|
||||
float z;
|
||||
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;
|
||||
}
|
||||
// Rotate back into the correct cubic view
|
||||
orient(position, direction);
|
||||
|
||||
// Transform to screen space
|
||||
gl_Position = gl_ProjectionMatrix * position;
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
"fabric-resource-loader-v0": "*"
|
||||
},
|
||||
|
||||
"conflicts": {
|
||||
"iris": "<1.1.3"
|
||||
},
|
||||
|
||||
"custom": {
|
||||
"mm:early_risers": [
|
||||
"com.replaymod.core.ReplayModMMLauncher"
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
"SPacketSpawnMobAccessor",
|
||||
"SPacketSpawnPlayerAccessor",
|
||||
"MixinServerInfo",
|
||||
//#if MC>=11400
|
||||
//#if MC>=10800
|
||||
"MixinDownloadingPackFinder",
|
||||
//#endif
|
||||
//#if MC>=11400
|
||||
"MixinMouseHelper",
|
||||
//#endif
|
||||
//#if MC>=10904
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
"Mixin_HideNameTags",
|
||||
"Mixin_HideNameTags_LivingEntity",
|
||||
"Mixin_Omnidirectional_Camera",
|
||||
"Mixin_Omnidirectional_DisableFrustumCulling",
|
||||
"Mixin_Omnidirectional_Rotation",
|
||||
"Mixin_Omnidirectional_SkipHand",
|
||||
"Mixin_PreserveDepthDuringGuiRendering",
|
||||
"Mixin_SkipBlockOutlinesDuringRender",
|
||||
"Mixin_SkipHudDuringRender",
|
||||
@@ -39,6 +37,7 @@
|
||||
"Mixin_PreserveDepthDuringHandRendering",
|
||||
"Mixin_WindowsWorkaroundForTinyEXRNatives",
|
||||
//#endif
|
||||
"GameRendererAccessor",
|
||||
"MainWindowAccessor",
|
||||
"WorldRendererAccessor",
|
||||
//#if MC>=10904
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"client": [
|
||||
"entity_tracking.Mixin_EntityExt",
|
||||
"entity_tracking.Mixin_FixPartialUpdates",
|
||||
"world_border.Mixin_UseReplayTime_ForMovement",
|
||||
"world_border.Mixin_UseReplayTime_ForTexture",
|
||||
"Mixin_FixNPCSkinCaching",
|
||||
//#if MC>=11800
|
||||
//$$ "Mixin_FixEntityNotTracking",
|
||||
@@ -23,6 +25,7 @@
|
||||
"ClientWorldAccessor",
|
||||
"EntityLivingBaseAccessor",
|
||||
//#if MC>=11400
|
||||
"Mixin_ShowSpectatedHand_Iris",
|
||||
"Mixin_ShowSpectatedHand_NoOF",
|
||||
"Mixin_ShowSpectatedHand_OF",
|
||||
//#else
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "ReplayMod resources",
|
||||
//#if MC>=11400
|
||||
"pack_format": 4
|
||||
//#else
|
||||
//#if MC>=11002
|
||||
//$$ "pack_format": 2
|
||||
//#else
|
||||
//$$ "pack_format": 1
|
||||
//#endif
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.6.3
|
||||
2.6.4
|
||||
|
||||
@@ -34,6 +34,11 @@ public class Window implements MainWindowAccessor {
|
||||
mc.displayHeight = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeUpdateFramebufferSize() {
|
||||
// no-op, pre-LWJGL3 MC doesn't differentiate between window and framebuffer size
|
||||
}
|
||||
|
||||
public long getHandle() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.replaymod.render.mixin;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public interface MainWindowAccessor {
|
||||
@@ -14,4 +15,6 @@ public interface MainWindowAccessor {
|
||||
int getFramebufferHeight();
|
||||
@Accessor("displayHeight")
|
||||
void setFramebufferHeight(int value);
|
||||
@Invoker
|
||||
void invokeUpdateFramebufferSize();
|
||||
}
|
||||
|
||||
6
versions/1.12.2/src/main/resources/pack.mcmeta
Normal file
6
versions/1.12.2/src/main/resources/pack.mcmeta
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "ReplayMod resources",
|
||||
"pack_format": 2
|
||||
}
|
||||
}
|
||||
0
versions/1.18.2/.gitkeep
Normal file
0
versions/1.18.2/.gitkeep
Normal file
6
versions/1.9.4/src/main/resources/pack.mcmeta
Normal file
6
versions/1.9.4/src/main/resources/pack.mcmeta
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "ReplayMod resources",
|
||||
"pack_format": 1
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
net.minecraft.client.network.ClientPlayerEntity getUnderwaterVisibility() method_3140()
|
||||
net.minecraft.client.render.GameRenderer getBasicProjectionMatrix() method_22973()
|
||||
|
||||
Reference in New Issue
Block a user