From 832413dda030671a2fd6c864187c3fee85dcff98 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 12 Jun 2018 22:21:39 +0200 Subject: [PATCH 01/65] Set default memory for gradle JVM (fixes #32) See: https://github.com/MinecraftForge/MinecraftForge/pull/3361 --- gradle.properties | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 gradle.properties diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..e9b9fd5a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +# This is required to provide enough memory for the Minecraft decompilation process. +org.gradle.jvmargs=-Xmx3G From 8314803cda88a81ee16969e5ab89dabbd131c52e Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 12 Jun 2018 22:24:57 +0200 Subject: [PATCH 02/65] Use "-dirty" for marking dirty builds because Windows doesn't like "*" --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e4e25ae6..4809b885 100755 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ if (releaseCommit == currentCommit) { version = "$latestVersion-$diff-g${currentCommit.substring(0, 7)}" } if (gitDescribe().endsWith('*')) { - version = "$version*" + version = "$version-dirty" } group= "com.replaymod" From edc54ce6d457194d59867d37c74d8ca3dd629b30 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 18 Jul 2018 21:53:58 +0200 Subject: [PATCH 03/65] Add quick replay mode Allows very quick forwards and backwards jumping at the cost of only replaying entity positions and block updates. Still needs to be backported from 1.12.2 and contains FIXMEs --- .../replaymod/replay/FullReplaySender.java | 1143 ++++++++++++++++ .../replaymod/replay/QuickReplaySender.java | 520 ++++++++ .../com/replaymod/replay/ReplayHandler.java | 59 +- .../com/replaymod/replay/ReplayModReplay.java | 6 + .../com/replaymod/replay/ReplaySender.java | 1147 +---------------- .../replay/gui/overlay/GuiReplayOverlay.java | 2 +- 6 files changed, 1731 insertions(+), 1146 deletions(-) create mode 100755 src/main/java/com/replaymod/replay/FullReplaySender.java create mode 100644 src/main/java/com/replaymod/replay/QuickReplaySender.java mode change 100755 => 100644 src/main/java/com/replaymod/replay/ReplaySender.java diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java new file mode 100755 index 00000000..c91316fc --- /dev/null +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -0,0 +1,1143 @@ +package com.replaymod.replay; + +import com.google.common.base.Preconditions; +import com.google.common.io.Files; +import com.replaymod.core.ReplayMod; +import com.replaymod.core.utils.Restrictions; +import com.replaymod.replay.camera.CameraEntity; +import com.replaymod.replaystudio.io.ReplayInputStream; +import com.replaymod.replaystudio.io.ReplayOutputStream; +import com.replaymod.replaystudio.replay.ReplayFile; +import com.replaymod.replaystudio.studio.ReplayStudio; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityOtherPlayerMP; +import net.minecraft.client.gui.GuiDownloadTerrain; +import net.minecraft.client.gui.GuiErrorScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.network.*; +import net.minecraft.network.play.server.*; +import net.minecraft.world.EnumDifficulty; +import net.minecraft.world.World; +import net.minecraft.world.WorldType; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraftforge.common.MinecraftForge; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +//#if MC>=11200 +import com.replaymod.core.utils.WrappedTimer; +//#endif +//#if MC>=11002 +import net.minecraft.world.GameType; +//#else +//$$ import net.minecraft.world.WorldSettings.GameType; +//#endif +//#if MC>=10904 +import net.minecraft.util.text.ITextComponent; +//#else +//$$ import net.minecraft.util.IChatComponent; +//#endif + +//#if MC>=10800 +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +//#else +//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; +//$$ import cpw.mods.fml.common.gameevent.TickEvent; +//$$ import org.apache.commons.io.Charsets; +//#endif + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static com.replaymod.core.versions.MCVer.*; + +/** + * Sends replay packets to netty channels. + * Even though {@link Sharable}, this should never be added to multiple pipes at once, it may however be re-added when + * the replay restart from the beginning. + */ +@Sharable +public class FullReplaySender extends ChannelDuplexHandler implements ReplaySender { + /** + * These packets are ignored completely during replay. + */ + private static final List BAD_PACKETS = Arrays.asList( + //#if MC>=11200 + SPacketRecipeBook.class, + SPacketAdvancementInfo.class, + SPacketSelectAdvancementsTab.class, + //#endif + //#if MC>=10904 + // TODO Update possibly more? + SPacketUpdateHealth.class, + SPacketOpenWindow.class, + SPacketCloseWindow.class, + SPacketSetSlot.class, + SPacketWindowItems.class, + SPacketSignEditorOpen.class, + SPacketStatistics.class, + SPacketSetExperience.class, + SPacketCamera.class, + SPacketPlayerAbilities.class, + SPacketTitle.class + //#else + //#if MC>=10800 + //$$ S43PacketCamera.class, + //$$ S45PacketTitle.class, + //#endif + //$$ S06PacketUpdateHealth.class, + //$$ S2DPacketOpenWindow.class, + //$$ S2EPacketCloseWindow.class, + //$$ S2FPacketSetSlot.class, + //$$ S30PacketWindowItems.class, + //$$ S36PacketSignEditorOpen.class, + //$$ S37PacketStatistics.class, + //$$ S1FPacketSetExperience.class, + //$$ S39PacketPlayerAbilities.class + //#endif + ); + + private static int TP_DISTANCE_LIMIT = 128; + + /** + * The replay handler responsible for the current replay. + */ + private final ReplayHandler replayHandler; + + /** + * Whether to work in async mode. + * + * When in async mode, a separate thread send packets and waits according to their delays. + * This is default in normal playback mode. + * + * When in sync mode, no packets will be sent until {@link #sendPacketsTill(int)} is called. + * This is used during path playback and video rendering. + */ + protected boolean asyncMode; + + /** + * Timestamp of the last packet sent in milliseconds since the start. + */ + protected int lastTimeStamp; + + /** + * @see #currentTimeStamp() + */ + protected int currentTimeStamp; + + /** + * The replay file. + */ + protected ReplayFile replayFile; + + /** + * The channel handler context used to send packets to minecraft. + */ + protected ChannelHandlerContext ctx; + + /** + * The replay input stream from which new packets are read. + * When accessing this stream make sure to synchronize on {@code this} as it's used from multiple threads. + */ + protected ReplayInputStream replayIn; + + /** + * The next packet that should be sent. + * This is required as some actions such as jumping to a specified timestamp have to peek at the next packet. + */ + protected PacketData nextPacket; + + /** + * Whether we need to restart the current replay. E.g. when jumping backwards in time + */ + protected boolean startFromBeginning = true; + + /** + * Whether to terminate the replay. This only has an effect on the async mode and is {@code true} during sync mode. + */ + protected boolean terminate; + + /** + * The speed of the replay. 1 is normal, 2 is twice as fast, 0.5 is half speed and 0 is frozen + */ + protected double replaySpeed = 1f; + + /** + * Whether the world has been loaded and the dirt-screen should go away. + */ + protected boolean hasWorldLoaded; + + /** + * The minecraft instance. + */ + protected Minecraft mc = Minecraft.getMinecraft(); + + /** + * The total length of this replay in milliseconds. + */ + protected final int replayLength; + + /** + * Our actual entity id that the server gave to us. + */ + protected int actualID = -1; + + /** + * Whether to allow (process) the next player movement packet. + */ + protected boolean allowMovement; + + /** + * Directory to which resource packs are extracted. + */ + private final File tempResourcePackFolder = Files.createTempDir(); + + /** + * Create a new replay sender. + * @param file The replay file + * @param asyncMode {@code true} for async mode, {@code false} otherwise + * @see #asyncMode + */ + public FullReplaySender(ReplayHandler replayHandler, ReplayFile file, boolean asyncMode) throws IOException { + this.replayHandler = replayHandler; + this.replayFile = file; + this.asyncMode = asyncMode; + this.replayLength = file.getMetaData().getDuration(); + + MinecraftForge.EVENT_BUS.register(this); + + if (asyncMode) { + new Thread(asyncSender, "replaymod-async-sender").start(); + } + } + + /** + * Set whether this replay sender operates in async mode. + * When in async mode, it will send packets timed from a separate thread. + * When not in async mode, it will send packets when {@link #sendPacketsTill(int)} is called. + * @param asyncMode {@code true} to enable async mode + */ + @Override + public void setAsyncMode(boolean asyncMode) { + if (this.asyncMode == asyncMode) return; + this.asyncMode = asyncMode; + if (asyncMode) { + this.terminate = false; + new Thread(asyncSender, "replaymod-async-sender").start(); + } else { + this.terminate = true; + } + } + + @Override + public boolean isAsyncMode() { + return asyncMode; + } + + /** + * Set whether this replay sender to operate in sync mode. + * When in sync mode, it will send packets when {@link #sendPacketsTill(int)} is called. + * This call will block until the async worker thread has stopped. + */ + @Override + public void setSyncModeAndWait() { + if (!this.asyncMode) return; + this.asyncMode = false; + this.terminate = true; + synchronized (this) { + // This will wait for the worker thread to leave the synchronized code part + } + } + + /** + * Return a fake {@link Minecraft#getSystemTime()} value that respects slowdown/speedup/pause and works in both, + * sync and async mode. + * Note: For sync mode this returns the last value passed to {@link #sendPacketsTill(int)}. + * @return The timestamp in milliseconds since the start of the replay + */ + @Override + public int currentTimeStamp() { + if (asyncMode) { + int timePassed = (int) (System.currentTimeMillis() - lastPacketSent); + return lastTimeStamp + (int) (timePassed * getReplaySpeed()); + } else { + return lastTimeStamp; + } + } + + /** + * Terminate this replay sender. + */ + public void terminateReplay() { + terminate = true; + MinecraftForge.EVENT_BUS.unregister(this); + try { + channelInactive(ctx); + ctx.channel().pipeline().close(); + FileUtils.deleteDirectory(tempResourcePackFolder); + } catch(Exception e) { + e.printStackTrace(); + } + } + + @SubscribeEvent + public void onWorldTick(TickEvent.ClientTickEvent event) { + // Unfortunately the WorldTickEvent doesn't seem to be emitted on the CLIENT side + if (event.phase != TickEvent.Phase.START) return; + + // Spawning a player into an empty chunk (which we might do with the recording player) + // prevents it from being moved by teleport packets (it essentially gets stuck) because + // Entity#addedToChunk is not set and it is therefore not updated every tick. + // To counteract this, we need to manually update it's position if it hasn't been added + // to any chunk yet. + if (world(mc) != null) { + for (EntityPlayer playerEntity : playerEntities(world(mc))) { + if (!playerEntity.addedToChunk && playerEntity instanceof EntityOtherPlayerMP) { + playerEntity.onLivingUpdate(); + } + } + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) + throws Exception { + // When in async mode and the replay sender shut down, then don't send packets + if(terminate && asyncMode) { + return; + } + + // When a packet is sent directly, perform no filtering + if(msg instanceof Packet) { + super.channelRead(ctx, msg); + } + + if (msg instanceof byte[]) { + try { + Packet p = deserializePacket((byte[]) msg); + + if (p != null) { + p = processPacket(p); + if (p != null) { + 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 && world(mc) != null) { + //#if MC>=10904 + if (p instanceof SPacketSpawnPlayer + || p instanceof SPacketSpawnObject + || p instanceof SPacketSpawnMob + || p instanceof SPacketSpawnGlobalEntity + || p instanceof SPacketSpawnPainting + || p instanceof SPacketSpawnExperienceOrb + || p instanceof SPacketDestroyEntities) { + //#else + //$$ if (p instanceof S0CPacketSpawnPlayer + //$$ || p instanceof S0EPacketSpawnObject + //$$ || p instanceof S0FPacketSpawnMob + //$$ || p instanceof S2CPacketSpawnGlobalEntity + //$$ || p instanceof S10PacketSpawnPainting + //$$ || p instanceof S11PacketSpawnExperienceOrb + //$$ || p instanceof S13PacketDestroyEntities) { + //#endif + World world = world(mc); + for (int i = 0; i < world.loadedEntityList.size(); ++i) { + Entity entity = loadedEntityList(world).get(i); + if (entity.isDead) { + int chunkX = entity.chunkCoordX; + int chunkY = entity.chunkCoordZ; + + //#if MC>=10904 + if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) { + //#else + //$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) { + //#endif + world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity); + } + + world.loadedEntityList.remove(i--); + world.onEntityRemoved(entity); + } + + } + } + } + } + } catch (Exception e) { + // We'd rather not have a failure parsing one packet screw up the whole replay process + e.printStackTrace(); + } + } + + } + + private Packet deserializePacket(byte[] bytes) throws IOException, IllegalAccessException, InstantiationException { + ByteBuf bb = Unpooled.wrappedBuffer(bytes); + PacketBuffer pb = new PacketBuffer(bb); + + int i = readVarInt(pb); + + //#if MC>=10800 + Packet p = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, i); + //#else + //$$ Packet p = Packet.generatePacket(EnumConnectionState.PLAY.func_150755_b(), i); + //#endif + p.readPacketData(pb); + + return p; + } + + /** + * Process a packet and return the result. + * @param p The packet to process + * @return The processed packet or {@code null} if no packet shall be sent + */ + protected Packet processPacket(Packet p) throws Exception { + //#if MC>=10904 + if (p instanceof SPacketCustomPayload) { + SPacketCustomPayload packet = (SPacketCustomPayload) p; + //#else + //$$ if (p instanceof S3FPacketCustomPayload) { + //$$ S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; + //#endif + //#if MC>=10800 + String channelName = packet.getChannelName(); + //#else + //$$ String channelName = packet.func_149169_c(); + //#endif + if (Restrictions.PLUGIN_CHANNEL.equals(channelName)) { + final String unknown = replayHandler.getRestrictions().handle(packet); + if (unknown == null) { + return null; + } else { + // Failed to parse options, make sure that under no circumstances further packets are parsed + terminateReplay(); + // Then end replay and show error GUI + mc.addScheduledTask(new Runnable() { + @Override + public void run() { + try { + replayHandler.endReplay(); + } catch (IOException e) { + e.printStackTrace(); + } + mc.displayGuiScreen(new GuiErrorScreen( + I18n.format("replaymod.error.unknownrestriction1"), + I18n.format("replaymod.error.unknownrestriction2", unknown) + )); + } + }); + } + } + } + //#if MC>=10904 + if (p instanceof SPacketDisconnect) { + ITextComponent reason = ((SPacketDisconnect) p).getReason(); + //#else + //$$ if (p instanceof S40PacketDisconnect) { + //#if MC>=10809 + //$$ IChatComponent reason = ((S40PacketDisconnect) p).getReason(); + //#else + //$$ IChatComponent reason = ((S40PacketDisconnect) p).func_149165_c(); + //#endif + //#endif + if ("Please update to view this replay.".equals(reason.getUnformattedText())) { + // This version of the mod supports replay restrictions so we are allowed + // to remove this packet. + return null; + } + } + + if(BAD_PACKETS.contains(p.getClass())) return null; + + //#if MC>=10904 + if (p instanceof SPacketCustomPayload) { + SPacketCustomPayload packet = (SPacketCustomPayload) p; + //#else + //$$ if (p instanceof S3FPacketCustomPayload) { + //$$ S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; + //#endif + //#if MC>=10800 + String channelName = packet.getChannelName(); + //#else + //$$ String channelName = packet.func_149169_c(); + //#endif + if ("MC|BOpen".equals(channelName)) { + return null; + } + //#if MC>=10800 + } + + //#if MC>=10904 + if(p instanceof SPacketResourcePackSend) { + SPacketResourcePackSend packet = (SPacketResourcePackSend) p; + String url = packet.getURL(); + //#else + //$$ if(p instanceof S48PacketResourcePackSend) { + //$$ S48PacketResourcePackSend packet = (S48PacketResourcePackSend) p; + //#if MC>=10809 + //$$ String url = packet.getURL(); + //#else + //$$ String url = packet.func_179783_a(); + //#endif + //#endif + if (url.startsWith("replay://")) { + //#else + //$$ String url; + //$$ if ("MC|RPack".equals(channelName) && + //$$ (url = new String(packet.func_149168_d(), Charsets.UTF_8)).startsWith("replay://")) { + //#endif + int id = Integer.parseInt(url.substring("replay://".length())); + Map index = replayFile.getResourcePackIndex(); + if (index != null) { + String hash = index.get(id); + if (hash != null) { + File file = new File(tempResourcePackFolder, hash + ".zip"); + if (!file.exists()) { + IOUtils.copy(replayFile.getResourcePack(hash).get(), new FileOutputStream(file)); + } + setServerResourcePack(mc.getResourcePackRepository(), file); + } + } + return null; + } + } + + //#if MC>=10904 + if(p instanceof SPacketJoinGame) { + SPacketJoinGame packet = (SPacketJoinGame) p; + int entId = packet.getPlayerId(); + //#else + //$$ if(p instanceof S01PacketJoinGame) { + //$$ S01PacketJoinGame packet = (S01PacketJoinGame) p; + //#if MC>=10800 + //$$ int entId = packet.getEntityId(); + //#else + //$$ int entId = packet.func_149197_c(); + //#endif + //#endif + allowMovement = true; + actualID = entId; + entId = -1789435; // Camera entity id should be negative which is an invalid id and can't be used by servers + //#if MC>=10800 + int dimension = packet.getDimension(); + EnumDifficulty difficulty = packet.getDifficulty(); + int maxPlayers = packet.getMaxPlayers(); + WorldType worldType = packet.getWorldType(); + + //#if MC>=10904 + p = new SPacketJoinGame(entId, GameType.SPECTATOR, false, dimension, + difficulty, maxPlayers, worldType, false); + //#else + //$$ p = new S01PacketJoinGame(entId, GameType.SPECTATOR, false, dimension, + //$$ difficulty, maxPlayers, worldType, false); + //#endif + //#else + //$$ int dimension = packet.func_149194_f(); + //$$ EnumDifficulty difficulty = packet.func_149192_g(); + //$$ int maxPlayers = packet.func_149193_h(); + //$$ WorldType worldType = packet.func_149196_i(); + //$$ + //$$ p = new S01PacketJoinGame(entId, GameType.ADVENTURE, false, dimension, + //$$ difficulty, maxPlayers, worldType); + //#endif + } + + //#if MC>=10904 + if(p instanceof SPacketRespawn) { + SPacketRespawn respawn = (SPacketRespawn) p; + p = new SPacketRespawn(respawn.getDimensionID(), + respawn.getDifficulty(), respawn.getWorldType(), GameType.SPECTATOR); + //#else + //$$ if(p instanceof S07PacketRespawn) { + //$$ S07PacketRespawn respawn = (S07PacketRespawn) p; + //#if MC>=10809 + //$$ p = new S07PacketRespawn(respawn.getDimensionID(), + //$$ respawn.getDifficulty(), respawn.getWorldType(), GameType.SPECTATOR); + //#else + //$$ p = new S07PacketRespawn(respawn.func_149082_c(), + //$$ respawn.func_149081_d(), respawn.func_149080_f(), + //#if MC>=10800 + //$$ GameType.SPECTATOR); + //#else + //$$ GameType.ADVENTURE); + //#endif + //#endif + //#endif + + allowMovement = true; + } + + //#if MC>=10904 + if(p instanceof SPacketPlayerPosLook) { + final SPacketPlayerPosLook ppl = (SPacketPlayerPosLook) p; + //#else + //$$ if(p instanceof S08PacketPlayerPosLook) { + //$$ final S08PacketPlayerPosLook ppl = (S08PacketPlayerPosLook) p; + //#endif + if(!hasWorldLoaded) hasWorldLoaded = true; + + if (mc.currentScreen instanceof GuiDownloadTerrain) { + // Close the world loading screen manually in case we swallow the packet + mc.displayGuiScreen(null); + } + + if(replayHandler.shouldSuppressCameraMovements()) return null; + + CameraEntity cent = replayHandler.getCameraEntity(); + + //#if MC>=10800 + //#if MC>=10904 + for (SPacketPlayerPosLook.EnumFlags relative : ppl.getFlags()) { + if (relative == SPacketPlayerPosLook.EnumFlags.X + || relative == SPacketPlayerPosLook.EnumFlags.Y + || relative == SPacketPlayerPosLook.EnumFlags.Z) { + //#else + //$$ for (Object relative : ppl.func_179834_f()) { + //$$ if (relative == S08PacketPlayerPosLook.EnumFlags.X + //$$ || relative == S08PacketPlayerPosLook.EnumFlags.Y + //$$ || relative == S08PacketPlayerPosLook.EnumFlags.Z) { + //#endif + return null; // At least one of the coordinates is relative, so we don't care + } + } + //#endif + + if(cent != null) { + //#if MC>=10809 + if(!allowMovement && !((Math.abs(cent.posX - ppl.getX()) > TP_DISTANCE_LIMIT) || + (Math.abs(cent.posZ - ppl.getZ()) > TP_DISTANCE_LIMIT))) { + //#else + //$$ if(!allowMovement && !((Math.abs(cent.posX - ppl.func_148932_c()) > TP_DISTANCE_LIMIT) || + //$$ (Math.abs(cent.posZ - ppl.func_148933_e()) > TP_DISTANCE_LIMIT))) { + //#endif + return null; + } else { + allowMovement = false; + } + } + + new Runnable() { + @Override + @SuppressWarnings("unchecked") + public void run() { + if (world(mc) == null || !mc.isCallingFromMinecraftThread()) { + ReplayMod.instance.runLater(this); + return; + } + + CameraEntity cent = replayHandler.getCameraEntity(); + //#if MC>=10809 + cent.setCameraPosition(ppl.getX(), ppl.getY(), ppl.getZ()); + //#else + //$$ cent.setCameraPosition(ppl.func_148932_c(), ppl.func_148928_d(), ppl.func_148933_e()); + //#endif + } + }.run(); + } + + //#if MC>=10904 + if(p instanceof SPacketChangeGameState) { + SPacketChangeGameState pg = (SPacketChangeGameState)p; + int reason = pg.getGameState(); + //#else + //$$ if(p instanceof S2BPacketChangeGameState) { + //$$ S2BPacketChangeGameState pg = (S2BPacketChangeGameState)p; + //#if MC>=10809 + //$$ int reason = pg.getGameState(); + //#else + //$$ int reason = pg.func_149138_c(); + //#endif + //#endif + + // only allow the following packets: + // 1 - End raining + // 2 - Begin raining + // + // The following values are to control sky color (e.g. if thunderstorm) + // 7 - Fade value + // 8 - Fade time + if(!(reason == 1 || reason == 2 || reason == 7 || reason == 8)) { + return null; + } + } + + //#if MC>=10904 + if (p instanceof SPacketChat) { + //#else + //$$ if (p instanceof S02PacketChat) { + //#endif + if (!ReplayModReplay.instance.getCore().getSettingsRegistry().get(Setting.SHOW_CHAT)) { + return null; + } + } + + return asyncMode ? processPacketAsync(p) : processPacketSync(p); + } + + @Override + @SuppressWarnings("unchecked") + public void channelActive(ChannelHandlerContext ctx) throws Exception { + this.ctx = ctx; + //#if MC>=10904 + ctx.channel().attr(NetworkManager.PROTOCOL_ATTRIBUTE_KEY).set(EnumConnectionState.PLAY); + //#else + //#if MC>=10800 + //$$ ctx.attr(NetworkManager.attrKeyConnectionState).set(EnumConnectionState.PLAY); + //#endif + //#endif + super.channelActive(ctx); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + // The embedded channel's event loop will consider every thread to be in it and as such provides no + // guarantees that only one thread is using the pipeline at any one time. + // For reading the replay sender (either sync or async) is the only thread ever writing. + // For writing it may very well happen that multiple threads want to use the pipline at the same time. + // It's unclear whether the EmbeddedChannel is supposed to be thread-safe (the behavior of the event loop + // does suggest that). However it seems like it either isn't (likely) or there is a race condition. + // See: https://www.replaymod.com/forum/thread/1752#post8045 (https://paste.replaymod.com/lotacatuwo) + // To work around this issue, we just outright drop all write/flush requests (they aren't needed anyway). + // This still leaves channel handlers upstream with the threading issue but they all seem to cope well with it. + promise.setSuccess(); + } + + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + // See write method above + } + + /** + * Returns the speed of the replay. 1 being normal speed, 0.5 half and 2 twice as fast. + * If 0 is returned, the replay is paused. + * @return speed multiplier + */ + @Override + public double getReplaySpeed() { + if(!paused()) return replaySpeed; + else return 0; + } + + /** + * Set the speed of the replay. 1 being normal speed, 0.5 half and 2 twice as fast. + * The speed may not be set to 0 nor to negative values. + * @param d Speed multiplier + */ + @Override + public void setReplaySpeed(final double d) { + if(d != 0) this.replaySpeed = d; + //#if MC>=11200 + mc.timer.tickLength = WrappedTimer.DEFAULT_MS_PER_TICK / (float) d; + //#else + //$$ mc.timer.timerSpeed = (float) d; + //#endif + } + + ///////////////////////////////////////////////////////// + // Asynchronous packet processing // + ///////////////////////////////////////////////////////// + + /** + * The real time at which the last packet was sent in milliseconds. + */ + private long lastPacketSent; + + /** + * There is no waiting performed until a packet with at least this timestamp is reached (but not yet sent). + * If this is -1, then timing is normal. + */ + private long desiredTimeStamp = -1; + + /** + * Runnable which performs timed dispatching of packets from the input stream. + */ + private Runnable asyncSender = new Runnable() { + public void run() { + try { + while (ctx == null && !terminate) { + Thread.sleep(10); + } + REPLAY_LOOP: + while (!terminate) { + synchronized (FullReplaySender.this) { + if (replayIn == null) { + replayIn = replayFile.getPacketData(); + } + // Packet loop + while (true) { + try { + // When playback is paused and the world has loaded (we don't want any dirt-screens) we sleep + while (paused() && hasWorldLoaded) { + // Unless we are going to terminate, restart or jump + if (terminate || startFromBeginning || desiredTimeStamp != -1) { + break; + } + Thread.sleep(10); + } + + if (terminate) { + break REPLAY_LOOP; + } + + if (startFromBeginning) { + // In case we need to restart from the beginning + // break out of the loop sending all packets which will + // cause the replay to be restarted by the outer loop + break; + } + + // Read the next packet if we don't already have one + if (nextPacket == null) { + nextPacket = new PacketData(replayIn); + } + + int nextTimeStamp = nextPacket.timestamp; + + // If we aren't jumping and the world has already been loaded (no dirt-screens) then wait + // the required amount to get proper packet timing + if (!isHurrying() && hasWorldLoaded) { + // How much time should have passed + int timeWait = (int) Math.round((nextTimeStamp - lastTimeStamp) / replaySpeed); + // How much time did pass + long timeDiff = System.currentTimeMillis() - lastPacketSent; + // How much time we need to wait to make up for the difference + long timeToSleep = Math.max(0, timeWait - timeDiff); + + Thread.sleep(timeToSleep); + lastPacketSent = System.currentTimeMillis(); + } + + // Process packet + channelRead(ctx, nextPacket.bytes); + nextPacket = null; + + lastTimeStamp = nextTimeStamp; + + // In case we finished jumping + // We need to check that we aren't planing to restart so we don't accidentally run this + // code before we actually restarted + if (isHurrying() && lastTimeStamp > desiredTimeStamp && !startFromBeginning) { + desiredTimeStamp = -1; + + replayHandler.moveCameraToTargetPosition(); + + // Pause after jumping + setReplaySpeed(0); + } + } catch (EOFException eof) { + // Reached end of file + // Pause the replay which will cause it to freeze before getting restarted + setReplaySpeed(0); + // Then wait until the user tells us to continue + while (paused() && hasWorldLoaded && desiredTimeStamp == -1 && !terminate) { + Thread.sleep(10); + } + break; + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Restart the replay. + hasWorldLoaded = false; + lastTimeStamp = 0; + startFromBeginning = false; + nextPacket = null; + lastPacketSent = System.currentTimeMillis(); + replayHandler.restartedReplay(); + if (replayIn != null) { + replayIn.close(); + replayIn = null; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + + /** + * Return whether this replay sender is currently rushing. When rushing, all packets are sent without waiting until + * a specified timestamp is passed. + * @return {@code true} if currently rushing, {@code false} otherwise + */ + public boolean isHurrying() { + return desiredTimeStamp != -1; + } + + /** + * Cancels the hurrying. + */ + public void stopHurrying() { + desiredTimeStamp = -1; + } + + /** + * Return the timestamp to which this replay sender is currently rushing. All packets with an lower or equal + * timestamp will be sent out without any sleeping. + * @return The timestamp in milliseconds since the start of the replay + */ + public long getDesiredTimestamp() { + return desiredTimeStamp; + } + + /** + * Jumps to the specified timestamp when in async mode by rushing all packets until one with a timestamp greater + * than the specified timestamp is found. + * If the timestamp has already passed, this causes the replay to restart and then rush all packets. + * @param millis Timestamp in milliseconds since the start of the replay + */ + @Override + public void jumpToTime(int millis) { + Preconditions.checkState(asyncMode, "Can only jump in async mode. Use sendPacketsTill(int) instead."); + if(millis < lastTimeStamp && !isHurrying()) { + startFromBeginning = true; + } + + desiredTimeStamp = millis; + } + + protected Packet processPacketAsync(Packet p) { + //If hurrying, ignore some packets, except for short durations + if(desiredTimeStamp - lastTimeStamp > 1000) { + //#if MC>=10904 + if(p instanceof SPacketParticles) return null; + + if(p instanceof SPacketSpawnObject) { + SPacketSpawnObject pso = (SPacketSpawnObject)p; + int type = pso.getType(); + //#else + //$$ if(p instanceof S2APacketParticles) return null; + //$$ + //$$ if(p instanceof S0EPacketSpawnObject) { + //$$ S0EPacketSpawnObject pso = (S0EPacketSpawnObject)p; + //#if MC>=10809 + //$$ int type = pso.getType(); + //#else + //$$ int type = pso.func_148993_l(); + //#endif + //#endif + if(type == 76) { // Firework rocket + return null; + } + } + } + return p; + } + + ///////////////////////////////////////////////////////// + // Synchronous packet processing // + ///////////////////////////////////////////////////////// + + /** + * 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. + * @param timestamp The timestamp in milliseconds since the beginning of this replay + */ + @Override + public void sendPacketsTill(int timestamp) { + Preconditions.checkState(!asyncMode, "This method cannot be used in async mode. Use jumpToTime(int) instead."); + try { + while (ctx == null && !terminate) { // Make sure channel is ready + Thread.sleep(10); + } + + synchronized (this) { + if (timestamp == lastTimeStamp) { // Do nothing if we're already there + return; + } + if (timestamp < lastTimeStamp) { // Restart the replay if we need to go backwards in time + hasWorldLoaded = false; + lastTimeStamp = 0; + if (replayIn != null) { + replayIn.close(); + replayIn = null; + } + startFromBeginning = false; + nextPacket = null; + replayHandler.restartedReplay(); + } + + if (replayIn == null) { + replayIn = replayFile.getPacketData(); + } + + while (true) { // Send packets + try { + PacketData pd; + if (nextPacket != null) { + // If there is still a packet left from before, use it first + pd = nextPacket; + nextPacket = null; + } else { + // Otherwise read one from the input stream + pd = new PacketData(replayIn); + } + + int nextTimeStamp = pd.timestamp; + if (nextTimeStamp > timestamp) { + // We are done sending all packets + nextPacket = pd; + break; + } + + // Process packet + channelRead(ctx, pd.bytes); + } catch (EOFException eof) { + // Shit! We hit the end before finishing our job! What shall we do now? + // well, let's just pretend we're done... + replayIn = null; + break; + } catch (IOException e) { + e.printStackTrace(); + } + } + + // This might be required if we change to async mode anytime soon + lastPacketSent = System.currentTimeMillis(); + lastTimeStamp = timestamp; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected Packet processPacketSync(Packet p) { + //#if MC>=10904 + if (p instanceof SPacketUnloadChunk) { + SPacketUnloadChunk packet = (SPacketUnloadChunk) p; + int x = packet.getX(); + int z = packet.getZ(); + //#else + //#if MC>=10809 + //$$ if (p instanceof S21PacketChunkData && ((S21PacketChunkData) p).getExtractedSize() == 0) { + //$$ S21PacketChunkData packet = (S21PacketChunkData) p; + //$$ int x = packet.getChunkX(); + //$$ int z = packet.getChunkZ(); + //#else + //$$ if (p instanceof S21PacketChunkData && ((S21PacketChunkData) p).func_149276_g() == 0) { + //$$ S21PacketChunkData packet = (S21PacketChunkData) p; + //$$ int x = packet.func_149273_e(); + //$$ int z = packet.func_149271_f(); + //#endif + //#endif + // If the chunk is getting unloaded, we will have to forcefully update the position of all entities + // within. Otherwise, if there wasn't a game tick recently, there may be entities that have moved + // out of the chunk by now but are still registered in it. If we do not update those, they will get + // unloaded even though they shouldn't. + // Note: This is only half of the truth. Entities may be removed by chunk-unloading, see else-case below. + // To make things worse, it seems like players were never supposed to be unloaded this way because + // they will remain glitched in the World#playerEntities list. + World world = world(mc); + IChunkProvider chunkProvider = world.getChunkProvider(); + // Get the chunk that will be unloaded + Chunk chunk = chunkProvider.provideChunk(x, z); + if (!chunk.isEmpty()) { + List entitiesInChunk = new ArrayList<>(); + // Gather all entities in that chunk + for (Collection entityList : getEntityLists(chunk)) { + entitiesInChunk.addAll(entityList); + } + for (Entity entity : entitiesInChunk) { + // Skip interpolation of position updates coming from server + // (See: newX in EntityLivingBase or otherPlayerMPX in EntityOtherPlayerMP) + // Needs to be called at least 4 times thanks to + // EntityOtherPlayerMP#otherPlayerMPPosRotationIncrements (max vanilla value is 3) + for (int i = 0; i < 4; i++) { + entity.onUpdate(); + } + + // Check whether the entity has left the chunk + int chunkX = floor(entity.posX / 16); + int chunkZ = floor(entity.posZ / 16); + if (entity.chunkCoordX != chunkX || entity.chunkCoordZ != chunkZ) { + // Entity has left the chunk + chunk.removeEntityAtIndex(entity, entity.chunkCoordY); + //#if MC>=10904 + Chunk newChunk = chunkProvider.getLoadedChunk(chunkX, chunkZ); + //#else + //$$ Chunk newChunk = chunkProvider.chunkExists(chunkX, chunkZ) + //$$ ? chunkProvider.provideChunk(chunkX, chunkZ) : null; + //#endif + if (newChunk != null) { + newChunk.addEntity(entity); + } else { + // Entity has left all loaded chunks + entity.addedToChunk = false; + } + } else { + // When entities remain in a chunk that's to be unloaded, they'll only be added to a unload + // queue and remain loaded as before until the next tick (which during jumping is way off). + // So, if they are re-spawned with the same entity id, MC actually cleans up the old entity and + // then adds the new one but leaves the unload queue as is. + // Finally, on the next tick the legitimate entity will be unloaded because it's part of the + // unload queue (entities .equals based purely on their id). However, the old entity object + // is used to determine the chunk the entity is removed from and in this case that'll allow the + // legitimate entity to remain registered in a loaded chunk, causing them to still be rendered. + // + // The usual removal-due-to-chunk-unload process will, without touching the entityList, call + // onEntityRemoved. In that method WorldClient checks to see whether the entity is still in the + // entityList (which it is) and then adds it to the entitySpawnQueue. + // As the final result the entity will remain loaded. + // To get the same result without ticking, we just remove the entity from the to-be-unloaded + // chunk but keep it loaded otherwise. They won't be rendered because they're not part of any + // chunk and will be removed properly if the server decides to re-spawn the entity. + chunk.removeEntityAtIndex(entity, entity.chunkCoordY); + entity.addedToChunk = false; + } + } + } + } + return p; // During synchronous playback everything is sent normally + } + + private static final class PacketData { + private static final ByteBuf byteBuf = Unpooled.buffer(); + private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); + private static final ReplayOutputStream encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut); + private final int timestamp; + private final byte[] bytes; + + public PacketData(ReplayInputStream in) throws IOException { + com.replaymod.replaystudio.PacketData data = in.readPacket(); + timestamp = (int) data.getTime(); + // We need to re-encode MCProtocolLib packets, so we can later decode them as NMS packets + // The main reason we aren't reading them as NMS packets is that we want ReplayStudio to be able + // to apply ViaVersion (and potentially other magic) to it. + synchronized (encoder) { + byteBuf.markReaderIndex(); // Mark the current reader and writer index (should be at start) + byteBuf.markWriterIndex(); + + encoder.write(data); // Re-encode packet, data will end up in byteBuf + encoder.flush(); + + byteBuf.skipBytes(8); // Skip packet length & timestamp + bytes = new byte[byteBuf.readableBytes()]; // Create bytes array of sufficient size + byteBuf.readBytes(bytes); // Read all data into bytes + + byteBuf.resetReaderIndex(); // Reset reader & writer index for next use + byteBuf.resetWriterIndex(); + } + } + } +} diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java new file mode 100644 index 00000000..fb299866 --- /dev/null +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -0,0 +1,520 @@ +package com.replaymod.replay; + +import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; +import com.github.steveice10.mc.protocol.data.game.chunk.BlockStorage; +import com.github.steveice10.mc.protocol.data.game.chunk.Column; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; +import com.github.steveice10.mc.protocol.data.game.world.WorldType; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockChangeRecord; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityDestroyPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityTeleportPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnMobPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnObjectPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPaintingPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMultiBlockChangePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket; +import com.google.common.base.Throwables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import com.replaymod.core.utils.WrappedTimer; +import com.replaymod.replaystudio.PacketData; +import com.replaymod.replaystudio.io.ReplayInputStream; +import com.replaymod.replaystudio.io.ReplayOutputStream; +import com.replaymod.replaystudio.replay.ReplayFile; +import com.replaymod.replaystudio.studio.ReplayStudio; +import com.replaymod.replaystudio.util.Location; +import com.replaymod.replaystudio.util.PacketUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; +import net.minecraft.client.Minecraft; +import net.minecraft.network.EnumConnectionState; +import net.minecraft.network.EnumPacketDirection; +import net.minecraft.network.Packet; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.UUID; +import java.util.function.Consumer; + +import static com.replaymod.core.versions.MCVer.FML_BUS; +import static com.replaymod.replay.ReplayModReplay.LOGGER; + +/** + * Sends only chunk updates and entity position updates but tries to do so as quickly as possible. + * To do so, it performs an initial analysis of the replay, scanning all of its packets and storing entity positions + * and chunk states while doing so. + * This allows it to later jump to any time by doing a diff from the current time (including backwards jumping). + */ +@ChannelHandler.Sharable +public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySender { + private final Minecraft mc = Minecraft.getMinecraft(); + + private final ReplayModReplay mod; + private final ReplayFile replayFile; + private ChannelHandlerContext ctx; + + private int currentTimeStamp; + private double replaySpeed = 1; + + /** + * Whether async mode is enabled. + * Async mode is emulated by registering an event handler on client tick. + */ + private boolean asyncMode; + private long lastAsyncUpdateTime; + + private ListenableFuture initPromise; + + private TreeMap> thingSpawnsT = new TreeMap<>(); + private ListMultimap thingSpawns = Multimaps.newListMultimap(thingSpawnsT, ArrayList::new); + private TreeMap> thingDespawnsT = new TreeMap<>(); + private ListMultimap thingDespawns = Multimaps.newListMultimap(thingDespawnsT, ArrayList::new); + private List activeThings = new LinkedList<>(); + + public QuickReplaySender(ReplayModReplay mod, ReplayFile replayFile) { + this.mod = mod; + this.replayFile = replayFile; + } + + public void register() { + FML_BUS.register(this); + } + + public void unregister() { + FML_BUS.unregister(this); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + + public ListenableFuture initialize(Consumer progress) { + if (initPromise != null) { + return initPromise; + } + SettableFuture promise = SettableFuture.create(); + initPromise = promise; + new Thread(() -> { + try { + long start = System.currentTimeMillis(); + analyseReplay(progress); + 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().printWarningToChat("Error initializing quick replay sender: %s", e.getLocalizedMessage()); + promise.setException(e); + }); + return; + } + mod.getCore().runLater(() -> promise.set(null)); + }).start(); + return promise; + } + + public void ensureInitialized(Runnable body) { + if (initPromise == null) { + // TODO progress popup + initialize(progress -> {}); + } + Futures.addCallback(initPromise, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + body.run(); + } + + @Override + public void onFailure(Throwable t) { + // Error already printed by initialize method + } + }); + } + + public void restart() { + activeThings.clear(); + currentTimeStamp = 0; + ctx.fireChannelRead(toMC(new ServerRespawnPacket(0, Difficulty.NORMAL, GameMode.SPECTATOR, WorldType.DEFAULT))); + ctx.fireChannelRead(toMC(new ServerPlayerPositionRotationPacket(0, 0, 0, 0, 0, 0))); + } + + @Override + public int currentTimeStamp() { + return currentTimeStamp; + } + + @Override + public void setReplaySpeed(double factor) { + if (factor != 0) { + if (paused() && asyncMode) { + lastAsyncUpdateTime = System.currentTimeMillis(); // TODO test this + } + this.replaySpeed = factor; + } + //#if MC>=11200 + mc.timer.tickLength = WrappedTimer.DEFAULT_MS_PER_TICK / (float) factor; + //#else + //$$ mc.timer.timerSpeed = (float) factor; + //#endif + } + + @Override + public double getReplaySpeed() { + return replaySpeed; + } + + @Override + public boolean isAsyncMode() { + return asyncMode; + } + + @Override + public void setAsyncMode(boolean async) { + if (this.asyncMode == async) return; + ensureInitialized(() -> { + this.asyncMode = async; + if (async) { + lastAsyncUpdateTime = System.currentTimeMillis(); + } + }); + } + + @Override + public void setSyncModeAndWait() { + setAsyncMode(false); + // No waiting required, we emulated async mode via tick events + } + + @Override + public void jumpToTime(int value) { + sendPacketsTill(value); + } + + @SubscribeEvent + public void onTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.START) return; + if (!asyncMode) return; + + long now = System.currentTimeMillis(); + long realTimePassed = now - lastAsyncUpdateTime; + lastAsyncUpdateTime = now; + int replayTimePassed = (int) (realTimePassed * replaySpeed); + sendPacketsTill(currentTimeStamp + replayTimePassed); + } + + private void analyseReplay(Consumer progress) { + ReplayStudio studio = new ReplayStudio(); + PacketUtils.registerAllMovementRelated(studio); + studio.setParsing(ServerSpawnMobPacket.class, true); + studio.setParsing(ServerSpawnObjectPacket.class, true); + studio.setParsing(ServerSpawnPaintingPacket.class, true); + studio.setParsing(ServerSpawnPlayerPacket.class, true); + studio.setParsing(ServerEntityDestroyPacket.class, true); + studio.setParsing(ServerChunkDataPacket.class, true); + studio.setParsing(ServerUnloadChunkPacket.class, true); + studio.setParsing(ServerBlockChangePacket.class, true); + studio.setParsing(ServerMultiBlockChangePacket.class, true); + studio.setParsing(ServerPlayerListEntryPacket.class, true); + + Map playerListEntries = new HashMap<>(); + Map activeEntities = new HashMap<>(); + Map activeChunks = new HashMap<>(); + + try (ReplayInputStream in = replayFile.getPacketData(studio)) { + double duration = replayFile.getMetaData().getDuration(); + PacketData packetData; + while ((packetData = in.readPacket()) != null) { + com.github.steveice10.packetlib.packet.Packet packet = packetData.getPacket(); + int time = (int) packetData.getTime(); + progress.accept(time / duration); + Integer entityId = PacketUtils.getEntityId(packet); + if (packet instanceof ServerSpawnMobPacket + || packet instanceof ServerSpawnObjectPacket + || packet instanceof ServerSpawnPaintingPacket) { + Entity entity = new Entity(entityId, Collections.singletonList(toMC(packet))); + entity.spawnTime = time; + thingSpawns.put(time, entity); + Entity prev = activeEntities.put(entityId, entity); + if (prev != null) { + prev.despawnTime = time; + thingDespawns.put(time, prev); + } + } else if (packet instanceof ServerSpawnPlayerPacket) { + ServerPlayerListEntryPacket listEntryPacket = new ServerPlayerListEntryPacket( + PlayerListEntryAction.ADD_PLAYER, + new PlayerListEntry[]{ + playerListEntries.get(((ServerSpawnPlayerPacket) packet).getUUID()) + } + ); + Entity entity = new Entity(entityId, Arrays.asList(toMC(listEntryPacket), toMC(packet))); + entity.spawnTime = time; + thingSpawns.put(time, entity); + Entity prev = activeEntities.put(entityId, entity); + if (prev != null) { + prev.despawnTime = time; + thingDespawns.put(time, prev); + } + } else if (packet instanceof ServerEntityDestroyPacket) { + for (int id : ((ServerEntityDestroyPacket) packet).getEntityIds()) { + Entity entity = activeEntities.remove(id); + if (entity != null) { + entity.despawnTime = time; + thingDespawns.put(time, entity); + } + } + } else if (packet instanceof ServerChunkDataPacket) { + Column column = ((ServerChunkDataPacket) packet).getColumn(); + Chunk chunk = new Chunk(column); + chunk.spawnTime = time; + thingSpawns.put(time, chunk); + Chunk prev = activeChunks.put(coordToLong(column.getX(), column.getZ()), chunk); + if (prev != null) { + prev.currentBlockState = null; // free memory because we no longer need it + prev.despawnTime = time; + thingDespawns.put(time, prev); + } + } else if (packet instanceof ServerUnloadChunkPacket) { + ServerUnloadChunkPacket p = (ServerUnloadChunkPacket) packet; + Chunk prev = activeChunks.remove(coordToLong(p.getX(), p.getZ())); + if (prev != null) { + prev.currentBlockState = null; // free memory because we no longer need it + prev.despawnTime = time; + thingDespawns.put(time, prev); + } + } else if (packet instanceof ServerBlockChangePacket || packet instanceof ServerMultiBlockChangePacket) { + for (BlockChangeRecord record : + packet instanceof ServerBlockChangePacket + ? new BlockChangeRecord[]{ ((ServerBlockChangePacket) packet).getRecord() } + : ((ServerMultiBlockChangePacket) packet).getRecords()) { + Position pos = record.getPosition(); + Chunk chunk = activeChunks.get(coordToLong(pos.getX() / 16, pos.getZ() / 16)); + if (chunk != null) { + BlockStorage blockStorage = chunk.currentBlockState[pos.getY() / 16]; + int x = Math.floorMod(pos.getX(), 16), y = Math.floorMod(pos.getY(), 16), z = Math.floorMod(pos.getZ(), 16); + BlockState prevState = blockStorage.get(x, y, z); + BlockState newState = record.getBlock(); + blockStorage.set(x, y, z, newState); + chunk.blocks.put(time, new BlockChange(pos, prevState, newState)); + } + } + } else if (packet instanceof ServerPlayerListEntryPacket) { + ServerPlayerListEntryPacket p = (ServerPlayerListEntryPacket) packet; + if (p.getAction() == PlayerListEntryAction.ADD_PLAYER) { + for (PlayerListEntry entry : p.getEntries()) { + playerListEntries.put(entry.getProfile().getId(), entry); + } + } + } else if (packet instanceof ServerRespawnPacket) { + // FIXME + } + if (entityId != null) { + Entity entity = activeEntities.get(entityId); + if (entity != null) { + Location current = entity.locations.isEmpty() ? null : entity.locations.lastEntry().getValue(); + Location updated = PacketUtils.updateLocation(current, packet); + if (updated != null) { + entity.locations.put(time, updated); + } + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void sendPacketsTill(int replayTime) { + ensureInitialized(() -> { + if (replayTime > currentTimeStamp) { + activeThings.removeIf(thing -> { + if (thing.despawnTime < replayTime) { + thing.despawnPackets.forEach(ctx::fireChannelRead); + return true; + } else { + return false; + } + }); + thingSpawnsT.subMap(currentTimeStamp, false, replayTime, true).values() + .forEach(things -> things.forEach(thing -> { + if (thing.despawnTime > replayTime) { + thing.spawnPackets.forEach(ctx::fireChannelRead); + activeThings.add(thing); + } + })); + activeThings.forEach(thing -> thing.play(currentTimeStamp, replayTime, ctx::fireChannelRead)); + } else { + activeThings.removeIf(thing -> { + if (thing.spawnTime > replayTime) { + thing.despawnPackets.forEach(ctx::fireChannelRead); + return true; + } else { + return false; + } + }); + thingDespawnsT.subMap(replayTime, false, currentTimeStamp, true).values() + .forEach(things -> things.forEach(thing -> { + if (thing.spawnTime <= replayTime) { + thing.spawnPackets.forEach(ctx::fireChannelRead); + activeThings.add(thing); + } + })); + activeThings.forEach(thing -> thing.rewind(currentTimeStamp, replayTime, ctx::fireChannelRead)); + } + currentTimeStamp = replayTime; + }); + } + + private static final ByteBuf byteBuf = Unpooled.buffer(); + private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); + private static final PacketBuffer packetBuf = new PacketBuffer(byteBuf); + private static final ReplayOutputStream encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut); + + private static Packet toMC(com.github.steveice10.packetlib.packet.Packet packet) { + // We need to re-encode MCProtocolLib packets, so we can then decode them as NMS packets + // The main reason we aren't reading them as NMS packets is that we want ReplayStudio to be able + // to apply ViaVersion (and potentially other magic) to it. + synchronized (encoder) { + int readerIndex = byteBuf.readerIndex(); // Mark the current reader and writer index (should be at start) + int writerIndex = byteBuf.writerIndex(); + try { + encoder.write(0, packet); // Re-encode packet, data will end up in byteBuf + encoder.flush(); + + byteBuf.skipBytes(8); // Skip packet length & timestamp + + int packetId = packetBuf.readVarInt(); + Packet mcPacket = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, packetId); + mcPacket.readPacketData(packetBuf); + return mcPacket; + } catch (Exception e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } finally { + byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use + byteBuf.writerIndex(writerIndex); + } + } + } + + private static long coordToLong(int x, int z) { + return (long)x << 32 | (long)z & 0xFFFFFFFFL; + } + + private static abstract class TrackedThing { + List> spawnPackets; + List> despawnPackets; + int spawnTime; + int despawnTime = Integer.MAX_VALUE; + + public abstract void play(int currentTimeStamp, int replayTime, Consumer> send); + public abstract void rewind(int currentTimeStamp, int replayTime, Consumer> send); + } + + private static class Entity extends TrackedThing { + private int id; + private NavigableMap locations = new TreeMap<>(); + + private Entity(int entityId, List> spawnPackets) { + this.id = entityId; + this.spawnPackets = spawnPackets; + this.despawnPackets = Collections.singletonList(toMC(new ServerEntityDestroyPacket(entityId))); + } + + @Override + public void play(int currentTimeStamp, int replayTime, Consumer> send) { + Map.Entry lastUpdate = locations.floorEntry(replayTime); + if (lastUpdate != null && lastUpdate.getKey() > currentTimeStamp) { + Location l = lastUpdate.getValue(); + send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false))); + } + } + + @Override + public void rewind(int currentTimeStamp, int replayTime, Consumer> send) { + Map.Entry lastUpdate = locations.floorEntry(replayTime); + if (lastUpdate != null && !lastUpdate.getKey().equals(locations.floorKey(currentTimeStamp))) { + Location l = lastUpdate.getValue(); + send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false))); + } + } + } + + private static class Chunk extends TrackedThing { + private TreeMap> blocksT = new TreeMap<>(); + private ListMultimap blocks = Multimaps.newListMultimap(blocksT, LinkedList::new); // LinkedList to allow .descendingIterator + private BlockStorage[] currentBlockState = new BlockStorage[16]; + + private Chunk(Column column) { + this.spawnPackets = Collections.singletonList(toMC(new ServerChunkDataPacket(column))); + this.despawnPackets = Collections.singletonList(toMC(new ServerUnloadChunkPacket(column.getX(), column.getZ()))); + com.github.steveice10.mc.protocol.data.game.chunk.Chunk[] chunks = column.getChunks(); + for (int i = 0; i < currentBlockState.length; i++) { + currentBlockState[i] = chunks[i] == null ? new BlockStorage() : chunks[i].getBlocks(); + } + } + + @Override + public void play(int currentTimeStamp, int replayTime, Consumer> send) { + blocksT.subMap(currentTimeStamp, false, replayTime, true).values() + .forEach(updates -> updates.forEach(update -> { + send.accept(toMC(new ServerBlockChangePacket(new BlockChangeRecord(update.pos, update.to)))); + })); + } + + @Override + public void rewind(int currentTimeStamp, int replayTime, Consumer> send) { + if (currentTimeStamp >= despawnTime) { + play(spawnTime, replayTime, send); + return; + } + blocksT.subMap(replayTime, false, currentTimeStamp, true).descendingMap().values() + .forEach(updates -> + ((LinkedList) updates).descendingIterator().forEachRemaining(update -> + send.accept(toMC(new ServerBlockChangePacket(new BlockChangeRecord(update.pos, update.from)))))); + } + } + + private static class BlockChange { + private Position pos; + private BlockState from; + private BlockState to; + + private BlockChange(Position pos, BlockState from, BlockState to) { + this.pos = pos; + this.from = from; + this.to = to; + } + } +} diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 6052a8c7..33ba056b 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -30,7 +30,6 @@ import java.util.*; import com.mojang.authlib.GameProfile; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.network.EnumPacketDirection; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher; @@ -67,7 +66,9 @@ public class ReplayHandler { /** * Decodes and sends packets into channel. */ - private final ReplaySender replaySender; + private final FullReplaySender fullReplaySender; + private final QuickReplaySender quickReplaySender; + private boolean quickMode = false; /** * Currently active replay restrictions. @@ -85,6 +86,8 @@ public class ReplayHandler { private EmbeddedChannel channel; + private int replayDuration; + /** * The position at which the camera should be located after the next jump. */ @@ -96,11 +99,14 @@ public class ReplayHandler { Preconditions.checkState(mc.isCallingFromMinecraftThread(), "Must be called from Minecraft thread."); this.replayFile = replayFile; + replayDuration = replayFile.getMetaData().getDuration(); + FML_BUS.post(new ReplayOpenEvent.Pre(this)); markers = new ArrayList<>(replayFile.getMarkers().or(Collections.emptySet())); - replaySender = new ReplaySender(this, replayFile, false); + fullReplaySender = new FullReplaySender(this, replayFile, false); + quickReplaySender = new QuickReplaySender(ReplayModReplay.instance, replayFile); setup(); @@ -109,7 +115,7 @@ public class ReplayHandler { FML_BUS.post(new ReplayOpenEvent.Post(this)); - replaySender.setAsyncMode(asyncMode); + fullReplaySender.setAsyncMode(asyncMode); } void restartedReplay() { @@ -131,7 +137,8 @@ public class ReplayHandler { FML_BUS.post(new ReplayCloseEvent.Pre(this)); - replaySender.terminateReplay(); + fullReplaySender.terminateReplay(); + quickReplaySender.unregister(); replayFile.save(); replayFile.close(); @@ -185,7 +192,8 @@ public class ReplayHandler { NetworkDispatcher networkDispatcher = new NetworkDispatcher(networkManager); channel.attr(NetworkDispatcher.FML_DISPATCHER).set(networkDispatcher); - channel.pipeline().addFirst("ReplayModReplay_replaySender", replaySender); + channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); + channel.pipeline().addFirst("ReplayModReplay_quickReplaySender", quickReplaySender); channel.pipeline().addLast("packet_handler", networkManager); channel.pipeline().fireChannelActive(); networkDispatcher.clientToServerHandshake(); @@ -194,7 +202,7 @@ public class ReplayHandler { //$$ NetworkDispatcher networkDispatcher = new NetworkDispatcher(networkManager); //$$ channel.attr(NetworkDispatcher.FML_DISPATCHER).set(networkDispatcher); //$$ - //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", replaySender); + //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); //$$ channel.pipeline().addAfter("ReplayModReplay_replaySender", "fml:packet_handler", networkDispatcher); //$$ channel.pipeline().fireChannelActive(); //#endif @@ -225,7 +233,7 @@ public class ReplayHandler { //$$ ChannelOutboundHandlerAdapter dummyHandler = new ChannelOutboundHandlerAdapter(); //$$ channel = new EmbeddedChannel(dummyHandler); //$$ channel.pipeline().remove(dummyHandler); - //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", replaySender); + //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); //$$ channel.pipeline().addAfter("ReplayModReplay_replaySender", "packet_handler", networkManager); //$$ channel.pipeline().fireChannelActive(); //$$ @@ -247,13 +255,40 @@ public class ReplayHandler { } public ReplaySender getReplaySender() { - return replaySender; + return quickMode ? quickReplaySender : fullReplaySender; } public GuiReplayOverlay getOverlay() { return overlay; } + public void setQuickMode(boolean quickMode) { + if (quickMode == this.quickMode) return; + if (quickMode && !fullReplaySender.isAsyncMode()) return; // Cannot activate quick mode when already in sync mode + this.quickMode = quickMode; + if (quickMode) { + fullReplaySender.setSyncModeAndWait(); + quickReplaySender.register(); + quickReplaySender.restart(); + quickReplaySender.sendPacketsTill(fullReplaySender.currentTimeStamp()); + quickReplaySender.setAsyncMode(true); + } else { + quickReplaySender.setSyncModeAndWait(); + quickReplaySender.unregister(); + fullReplaySender.sendPacketsTill(0); + fullReplaySender.sendPacketsTill(quickReplaySender.currentTimeStamp()); + fullReplaySender.setAsyncMode(true); + } + } + + public boolean isQuickMode() { + return quickMode; + } + + public int getReplayDuration() { + return replayDuration; + } + /** * Return whether camera movement by user inputs and/or server packets should be suppressed. * @return {@code true} if these kinds of movement should be suppressed @@ -363,6 +398,12 @@ public class ReplayHandler { } public void doJump(int targetTime, boolean retainCameraPosition) { + if (getReplaySender() == quickReplaySender) { + quickReplaySender.sendPacketsTill(targetTime); + return; + } + FullReplaySender replaySender = fullReplaySender; + if (replaySender.isHurrying()) { return; // When hurrying, no Timeline jumping etc. is possible } diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 48d73d59..af18a138 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -142,6 +142,12 @@ public class ReplayModReplay { } }); + core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.quickmode", Keyboard.KEY_Q, () -> { + if (replayHandler != null) { + replayHandler.setQuickMode(!replayHandler.isQuickMode()); + } + }); + core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.rollclockwise", Keyboard.KEY_L, () -> { // Noop, actual handling logic in CameraEntity#update }); diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java old mode 100755 new mode 100644 index 481a61e2..8e2d63d4 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -1,740 +1,16 @@ package com.replaymod.replay; -import com.google.common.base.Preconditions; -import com.google.common.io.Files; -import com.replaymod.core.ReplayMod; -import com.replaymod.core.utils.Restrictions; -import com.replaymod.replay.camera.CameraEntity; -import com.replaymod.replaystudio.io.ReplayInputStream; -import com.replaymod.replaystudio.io.ReplayOutputStream; -import com.replaymod.replaystudio.replay.ReplayFile; -import com.replaymod.replaystudio.studio.ReplayStudio; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandler.Sharable; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; import net.minecraft.client.Minecraft; -import net.minecraft.client.entity.EntityOtherPlayerMP; -import net.minecraft.client.gui.GuiDownloadTerrain; -import net.minecraft.client.gui.GuiErrorScreen; -import net.minecraft.client.resources.I18n; -import net.minecraft.entity.Entity; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.network.*; -import net.minecraft.network.play.server.*; -import net.minecraft.world.EnumDifficulty; -import net.minecraft.world.World; -import net.minecraft.world.WorldType; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.IChunkProvider; -import net.minecraftforge.common.MinecraftForge; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -//#if MC>=11200 -import com.replaymod.core.utils.WrappedTimer; -//#endif -//#if MC>=11002 -import net.minecraft.world.GameType; -//#else -//$$ import net.minecraft.world.WorldSettings.GameType; -//#endif -//#if MC>=10904 -import net.minecraft.util.text.ITextComponent; -//#else -//$$ import net.minecraft.util.IChatComponent; -//#endif - -//#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//$$ import cpw.mods.fml.common.gameevent.TickEvent; -//$$ import org.apache.commons.io.Charsets; -//#endif - -import java.io.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static com.replaymod.core.versions.MCVer.*; - -/** - * Sends replay packets to netty channels. - * Even though {@link Sharable}, this should never be added to multiple pipes at once, it may however be re-added when - * the replay restart from the beginning. - */ -@Sharable -public class ReplaySender extends ChannelDuplexHandler { - /** - * These packets are ignored completely during replay. - */ - private static final List BAD_PACKETS = Arrays.asList( - //#if MC>=11200 - SPacketRecipeBook.class, - SPacketAdvancementInfo.class, - SPacketSelectAdvancementsTab.class, - //#endif - //#if MC>=10904 - // TODO Update possibly more? - SPacketUpdateHealth.class, - SPacketOpenWindow.class, - SPacketCloseWindow.class, - SPacketSetSlot.class, - SPacketWindowItems.class, - SPacketSignEditorOpen.class, - SPacketStatistics.class, - SPacketSetExperience.class, - SPacketCamera.class, - SPacketPlayerAbilities.class, - SPacketTitle.class - //#else - //#if MC>=10800 - //$$ S43PacketCamera.class, - //$$ S45PacketTitle.class, - //#endif - //$$ S06PacketUpdateHealth.class, - //$$ S2DPacketOpenWindow.class, - //$$ S2EPacketCloseWindow.class, - //$$ S2FPacketSetSlot.class, - //$$ S30PacketWindowItems.class, - //$$ S36PacketSignEditorOpen.class, - //$$ S37PacketStatistics.class, - //$$ S1FPacketSetExperience.class, - //$$ S39PacketPlayerAbilities.class - //#endif - ); - - private static int TP_DISTANCE_LIMIT = 128; - - /** - * The replay handler responsible for the current replay. - */ - private final ReplayHandler replayHandler; - - /** - * Whether to work in async mode. - * - * When in async mode, a separate thread send packets and waits according to their delays. - * This is default in normal playback mode. - * - * When in sync mode, no packets will be sent until {@link #sendPacketsTill(int)} is called. - * This is used during path playback and video rendering. - */ - protected boolean asyncMode; - - /** - * Timestamp of the last packet sent in milliseconds since the start. - */ - protected int lastTimeStamp; - - /** - * @see #currentTimeStamp() - */ - protected int currentTimeStamp; - - /** - * The replay file. - */ - protected ReplayFile replayFile; - - /** - * The channel handler context used to send packets to minecraft. - */ - protected ChannelHandlerContext ctx; - - /** - * The replay input stream from which new packets are read. - * When accessing this stream make sure to synchronize on {@code this} as it's used from multiple threads. - */ - protected ReplayInputStream replayIn; - - /** - * The next packet that should be sent. - * This is required as some actions such as jumping to a specified timestamp have to peek at the next packet. - */ - protected PacketData nextPacket; - - /** - * Whether we need to restart the current replay. E.g. when jumping backwards in time - */ - protected boolean startFromBeginning = true; - - /** - * Whether to terminate the replay. This only has an effect on the async mode and is {@code true} during sync mode. - */ - protected boolean terminate; - - /** - * The speed of the replay. 1 is normal, 2 is twice as fast, 0.5 is half speed and 0 is frozen - */ - protected double replaySpeed = 1f; - - /** - * Whether the world has been loaded and the dirt-screen should go away. - */ - protected boolean hasWorldLoaded; - - /** - * The minecraft instance. - */ - protected Minecraft mc = Minecraft.getMinecraft(); - - /** - * The total length of this replay in milliseconds. - */ - protected final int replayLength; - - /** - * Our actual entity id that the server gave to us. - */ - protected int actualID = -1; - - /** - * Whether to allow (process) the next player movement packet. - */ - protected boolean allowMovement; - - /** - * Directory to which resource packs are extracted. - */ - private final File tempResourcePackFolder = Files.createTempDir(); - - /** - * Create a new replay sender. - * @param file The replay file - * @param asyncMode {@code true} for async mode, {@code false} otherwise - * @see #asyncMode - */ - public ReplaySender(ReplayHandler replayHandler, ReplayFile file, boolean asyncMode) throws IOException { - this.replayHandler = replayHandler; - this.replayFile = file; - this.asyncMode = asyncMode; - this.replayLength = file.getMetaData().getDuration(); - - MinecraftForge.EVENT_BUS.register(this); - - if (asyncMode) { - new Thread(asyncSender, "replaymod-async-sender").start(); - } - } - - /** - * Set whether this replay sender operates in async mode. - * When in async mode, it will send packets timed from a separate thread. - * When not in async mode, it will send packets when {@link #sendPacketsTill(int)} is called. - * @param asyncMode {@code true} to enable async mode - */ - public void setAsyncMode(boolean asyncMode) { - if (this.asyncMode == asyncMode) return; - this.asyncMode = asyncMode; - if (asyncMode) { - this.terminate = false; - new Thread(asyncSender, "replaymod-async-sender").start(); - } else { - this.terminate = true; - } - } - - public boolean isAsyncMode() { - return asyncMode; - } - - /** - * Set whether this replay sender to operate in sync mode. - * When in sync mode, it will send packets when {@link #sendPacketsTill(int)} is called. - * This call will block until the async worker thread has stopped. - */ - public void setSyncModeAndWait() { - if (!this.asyncMode) return; - this.asyncMode = false; - this.terminate = true; - synchronized (this) { - // This will wait for the worker thread to leave the synchronized code part - } - } - - /** - * Return a fake {@link Minecraft#getSystemTime()} value that respects slowdown/speedup/pause and works in both, - * sync and async mode. - * Note: For sync mode this returns the last value passed to {@link #sendPacketsTill(int)}. - * @return The timestamp in milliseconds since the start of the replay - */ - public int currentTimeStamp() { - if (asyncMode) { - int timePassed = (int) (System.currentTimeMillis() - lastPacketSent); - return lastTimeStamp + (int) (timePassed * getReplaySpeed()); - } else { - return lastTimeStamp; - } - } - - /** - * Return the total length of the replay played. - * @return Total length in milliseconds - */ - public int replayLength() { - return replayLength; - } - - /** - * Terminate this replay sender. - */ - public void terminateReplay() { - terminate = true; - MinecraftForge.EVENT_BUS.unregister(this); - try { - channelInactive(ctx); - ctx.channel().pipeline().close(); - FileUtils.deleteDirectory(tempResourcePackFolder); - } catch(Exception e) { - e.printStackTrace(); - } - } - - @SubscribeEvent - public void onWorldTick(TickEvent.ClientTickEvent event) { - // Unfortunately the WorldTickEvent doesn't seem to be emitted on the CLIENT side - if (event.phase != TickEvent.Phase.START) return; - - // Spawning a player into an empty chunk (which we might do with the recording player) - // prevents it from being moved by teleport packets (it essentially gets stuck) because - // Entity#addedToChunk is not set and it is therefore not updated every tick. - // To counteract this, we need to manually update it's position if it hasn't been added - // to any chunk yet. - if (world(mc) != null) { - for (EntityPlayer playerEntity : playerEntities(world(mc))) { - if (!playerEntity.addedToChunk && playerEntity instanceof EntityOtherPlayerMP) { - playerEntity.onLivingUpdate(); - } - } - } - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) - throws Exception { - // When in async mode and the replay sender shut down, then don't send packets - if(terminate && asyncMode) { - return; - } - - // When a packet is sent directly, perform no filtering - if(msg instanceof Packet) { - super.channelRead(ctx, msg); - } - - if (msg instanceof byte[]) { - try { - Packet p = deserializePacket((byte[]) msg); - - if (p != null) { - p = processPacket(p); - if (p != null) { - 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 && world(mc) != null) { - //#if MC>=10904 - if (p instanceof SPacketSpawnPlayer - || p instanceof SPacketSpawnObject - || p instanceof SPacketSpawnMob - || p instanceof SPacketSpawnGlobalEntity - || p instanceof SPacketSpawnPainting - || p instanceof SPacketSpawnExperienceOrb - || p instanceof SPacketDestroyEntities) { - //#else - //$$ if (p instanceof S0CPacketSpawnPlayer - //$$ || p instanceof S0EPacketSpawnObject - //$$ || p instanceof S0FPacketSpawnMob - //$$ || p instanceof S2CPacketSpawnGlobalEntity - //$$ || p instanceof S10PacketSpawnPainting - //$$ || p instanceof S11PacketSpawnExperienceOrb - //$$ || p instanceof S13PacketDestroyEntities) { - //#endif - World world = world(mc); - for (int i = 0; i < world.loadedEntityList.size(); ++i) { - Entity entity = loadedEntityList(world).get(i); - if (entity.isDead) { - int chunkX = entity.chunkCoordX; - int chunkY = entity.chunkCoordZ; - - //#if MC>=10904 - if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) { - //#else - //$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) { - //#endif - world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity); - } - - world.loadedEntityList.remove(i--); - world.onEntityRemoved(entity); - } - - } - } - } - } - } catch (Exception e) { - // We'd rather not have a failure parsing one packet screw up the whole replay process - e.printStackTrace(); - } - } - - } - - private Packet deserializePacket(byte[] bytes) throws IOException, IllegalAccessException, InstantiationException { - ByteBuf bb = Unpooled.wrappedBuffer(bytes); - PacketBuffer pb = new PacketBuffer(bb); - - int i = readVarInt(pb); - - //#if MC>=10800 - Packet p = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, i); - //#else - //$$ Packet p = Packet.generatePacket(EnumConnectionState.PLAY.func_150755_b(), i); - //#endif - p.readPacketData(pb); - - return p; - } - - /** - * Process a packet and return the result. - * @param p The packet to process - * @return The processed packet or {@code null} if no packet shall be sent - */ - protected Packet processPacket(Packet p) throws Exception { - //#if MC>=10904 - if (p instanceof SPacketCustomPayload) { - SPacketCustomPayload packet = (SPacketCustomPayload) p; - //#else - //$$ if (p instanceof S3FPacketCustomPayload) { - //$$ S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; - //#endif - //#if MC>=10800 - String channelName = packet.getChannelName(); - //#else - //$$ String channelName = packet.func_149169_c(); - //#endif - if (Restrictions.PLUGIN_CHANNEL.equals(channelName)) { - final String unknown = replayHandler.getRestrictions().handle(packet); - if (unknown == null) { - return null; - } else { - // Failed to parse options, make sure that under no circumstances further packets are parsed - terminateReplay(); - // Then end replay and show error GUI - mc.addScheduledTask(new Runnable() { - @Override - public void run() { - try { - replayHandler.endReplay(); - } catch (IOException e) { - e.printStackTrace(); - } - mc.displayGuiScreen(new GuiErrorScreen( - I18n.format("replaymod.error.unknownrestriction1"), - I18n.format("replaymod.error.unknownrestriction2", unknown) - )); - } - }); - } - } - } - //#if MC>=10904 - if (p instanceof SPacketDisconnect) { - ITextComponent reason = ((SPacketDisconnect) p).getReason(); - //#else - //$$ if (p instanceof S40PacketDisconnect) { - //#if MC>=10809 - //$$ IChatComponent reason = ((S40PacketDisconnect) p).getReason(); - //#else - //$$ IChatComponent reason = ((S40PacketDisconnect) p).func_149165_c(); - //#endif - //#endif - if ("Please update to view this replay.".equals(reason.getUnformattedText())) { - // This version of the mod supports replay restrictions so we are allowed - // to remove this packet. - return null; - } - } - - if(BAD_PACKETS.contains(p.getClass())) return null; - - //#if MC>=10904 - if (p instanceof SPacketCustomPayload) { - SPacketCustomPayload packet = (SPacketCustomPayload) p; - //#else - //$$ if (p instanceof S3FPacketCustomPayload) { - //$$ S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; - //#endif - //#if MC>=10800 - String channelName = packet.getChannelName(); - //#else - //$$ String channelName = packet.func_149169_c(); - //#endif - if ("MC|BOpen".equals(channelName)) { - return null; - } - //#if MC>=10800 - } - - //#if MC>=10904 - if(p instanceof SPacketResourcePackSend) { - SPacketResourcePackSend packet = (SPacketResourcePackSend) p; - String url = packet.getURL(); - //#else - //$$ if(p instanceof S48PacketResourcePackSend) { - //$$ S48PacketResourcePackSend packet = (S48PacketResourcePackSend) p; - //#if MC>=10809 - //$$ String url = packet.getURL(); - //#else - //$$ String url = packet.func_179783_a(); - //#endif - //#endif - if (url.startsWith("replay://")) { - //#else - //$$ String url; - //$$ if ("MC|RPack".equals(channelName) && - //$$ (url = new String(packet.func_149168_d(), Charsets.UTF_8)).startsWith("replay://")) { - //#endif - int id = Integer.parseInt(url.substring("replay://".length())); - Map index = replayFile.getResourcePackIndex(); - if (index != null) { - String hash = index.get(id); - if (hash != null) { - File file = new File(tempResourcePackFolder, hash + ".zip"); - if (!file.exists()) { - IOUtils.copy(replayFile.getResourcePack(hash).get(), new FileOutputStream(file)); - } - setServerResourcePack(mc.getResourcePackRepository(), file); - } - } - return null; - } - } - - //#if MC>=10904 - if(p instanceof SPacketJoinGame) { - SPacketJoinGame packet = (SPacketJoinGame) p; - int entId = packet.getPlayerId(); - //#else - //$$ if(p instanceof S01PacketJoinGame) { - //$$ S01PacketJoinGame packet = (S01PacketJoinGame) p; - //#if MC>=10800 - //$$ int entId = packet.getEntityId(); - //#else - //$$ int entId = packet.func_149197_c(); - //#endif - //#endif - allowMovement = true; - actualID = entId; - entId = -1789435; // Camera entity id should be negative which is an invalid id and can't be used by servers - //#if MC>=10800 - int dimension = packet.getDimension(); - EnumDifficulty difficulty = packet.getDifficulty(); - int maxPlayers = packet.getMaxPlayers(); - WorldType worldType = packet.getWorldType(); - - //#if MC>=10904 - p = new SPacketJoinGame(entId, GameType.SPECTATOR, false, dimension, - difficulty, maxPlayers, worldType, false); - //#else - //$$ p = new S01PacketJoinGame(entId, GameType.SPECTATOR, false, dimension, - //$$ difficulty, maxPlayers, worldType, false); - //#endif - //#else - //$$ int dimension = packet.func_149194_f(); - //$$ EnumDifficulty difficulty = packet.func_149192_g(); - //$$ int maxPlayers = packet.func_149193_h(); - //$$ WorldType worldType = packet.func_149196_i(); - //$$ - //$$ p = new S01PacketJoinGame(entId, GameType.ADVENTURE, false, dimension, - //$$ difficulty, maxPlayers, worldType); - //#endif - } - - //#if MC>=10904 - if(p instanceof SPacketRespawn) { - SPacketRespawn respawn = (SPacketRespawn) p; - p = new SPacketRespawn(respawn.getDimensionID(), - respawn.getDifficulty(), respawn.getWorldType(), GameType.SPECTATOR); - //#else - //$$ if(p instanceof S07PacketRespawn) { - //$$ S07PacketRespawn respawn = (S07PacketRespawn) p; - //#if MC>=10809 - //$$ p = new S07PacketRespawn(respawn.getDimensionID(), - //$$ respawn.getDifficulty(), respawn.getWorldType(), GameType.SPECTATOR); - //#else - //$$ p = new S07PacketRespawn(respawn.func_149082_c(), - //$$ respawn.func_149081_d(), respawn.func_149080_f(), - //#if MC>=10800 - //$$ GameType.SPECTATOR); - //#else - //$$ GameType.ADVENTURE); - //#endif - //#endif - //#endif - - allowMovement = true; - } - - //#if MC>=10904 - if(p instanceof SPacketPlayerPosLook) { - final SPacketPlayerPosLook ppl = (SPacketPlayerPosLook) p; - //#else - //$$ if(p instanceof S08PacketPlayerPosLook) { - //$$ final S08PacketPlayerPosLook ppl = (S08PacketPlayerPosLook) p; - //#endif - if(!hasWorldLoaded) hasWorldLoaded = true; - - if (mc.currentScreen instanceof GuiDownloadTerrain) { - // Close the world loading screen manually in case we swallow the packet - mc.displayGuiScreen(null); - } - - if(replayHandler.shouldSuppressCameraMovements()) return null; - - CameraEntity cent = replayHandler.getCameraEntity(); - - //#if MC>=10800 - //#if MC>=10904 - for (SPacketPlayerPosLook.EnumFlags relative : ppl.getFlags()) { - if (relative == SPacketPlayerPosLook.EnumFlags.X - || relative == SPacketPlayerPosLook.EnumFlags.Y - || relative == SPacketPlayerPosLook.EnumFlags.Z) { - //#else - //$$ for (Object relative : ppl.func_179834_f()) { - //$$ if (relative == S08PacketPlayerPosLook.EnumFlags.X - //$$ || relative == S08PacketPlayerPosLook.EnumFlags.Y - //$$ || relative == S08PacketPlayerPosLook.EnumFlags.Z) { - //#endif - return null; // At least one of the coordinates is relative, so we don't care - } - } - //#endif - - if(cent != null) { - //#if MC>=10809 - if(!allowMovement && !((Math.abs(cent.posX - ppl.getX()) > TP_DISTANCE_LIMIT) || - (Math.abs(cent.posZ - ppl.getZ()) > TP_DISTANCE_LIMIT))) { - //#else - //$$ if(!allowMovement && !((Math.abs(cent.posX - ppl.func_148932_c()) > TP_DISTANCE_LIMIT) || - //$$ (Math.abs(cent.posZ - ppl.func_148933_e()) > TP_DISTANCE_LIMIT))) { - //#endif - return null; - } else { - allowMovement = false; - } - } - - new Runnable() { - @Override - @SuppressWarnings("unchecked") - public void run() { - if (world(mc) == null || !mc.isCallingFromMinecraftThread()) { - ReplayMod.instance.runLater(this); - return; - } - - CameraEntity cent = replayHandler.getCameraEntity(); - //#if MC>=10809 - cent.setCameraPosition(ppl.getX(), ppl.getY(), ppl.getZ()); - //#else - //$$ cent.setCameraPosition(ppl.func_148932_c(), ppl.func_148928_d(), ppl.func_148933_e()); - //#endif - } - }.run(); - } - - //#if MC>=10904 - if(p instanceof SPacketChangeGameState) { - SPacketChangeGameState pg = (SPacketChangeGameState)p; - int reason = pg.getGameState(); - //#else - //$$ if(p instanceof S2BPacketChangeGameState) { - //$$ S2BPacketChangeGameState pg = (S2BPacketChangeGameState)p; - //#if MC>=10809 - //$$ int reason = pg.getGameState(); - //#else - //$$ int reason = pg.func_149138_c(); - //#endif - //#endif - - // only allow the following packets: - // 1 - End raining - // 2 - Begin raining - // - // The following values are to control sky color (e.g. if thunderstorm) - // 7 - Fade value - // 8 - Fade time - if(!(reason == 1 || reason == 2 || reason == 7 || reason == 8)) { - return null; - } - } - - //#if MC>=10904 - if (p instanceof SPacketChat) { - //#else - //$$ if (p instanceof S02PacketChat) { - //#endif - if (!ReplayModReplay.instance.getCore().getSettingsRegistry().get(Setting.SHOW_CHAT)) { - return null; - } - } - - return asyncMode ? processPacketAsync(p) : processPacketSync(p); - } - - @Override - @SuppressWarnings("unchecked") - public void channelActive(ChannelHandlerContext ctx) throws Exception { - this.ctx = ctx; - //#if MC>=10904 - ctx.channel().attr(NetworkManager.PROTOCOL_ATTRIBUTE_KEY).set(EnumConnectionState.PLAY); - //#else - //#if MC>=10800 - //$$ ctx.attr(NetworkManager.attrKeyConnectionState).set(EnumConnectionState.PLAY); - //#endif - //#endif - super.channelActive(ctx); - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - // The embedded channel's event loop will consider every thread to be in it and as such provides no - // guarantees that only one thread is using the pipeline at any one time. - // For reading the replay sender (either sync or async) is the only thread ever writing. - // For writing it may very well happen that multiple threads want to use the pipline at the same time. - // It's unclear whether the EmbeddedChannel is supposed to be thread-safe (the behavior of the event loop - // does suggest that). However it seems like it either isn't (likely) or there is a race condition. - // See: https://www.replaymod.com/forum/thread/1752#post8045 (https://paste.replaymod.com/lotacatuwo) - // To work around this issue, we just outright drop all write/flush requests (they aren't needed anyway). - // This still leaves channel handlers upstream with the threading issue but they all seem to cope well with it. - promise.setSuccess(); - } - - @Override - public void flush(ChannelHandlerContext ctx) throws Exception { - // See write method above - } +public interface ReplaySender { + int currentTimeStamp(); /** * Whether the replay is currently paused. * @return {@code true} if it is paused, {@code false} otherwise */ - public boolean paused() { + public default boolean paused() { + Minecraft mc = Minecraft.getMinecraft(); //#if MC>=11200 return mc.timer.tickLength == Float.POSITIVE_INFINITY; //#else @@ -742,414 +18,13 @@ public class ReplaySender extends ChannelDuplexHandler { //#endif } - /** - * Returns the speed of the replay. 1 being normal speed, 0.5 half and 2 twice as fast. - * If 0 is returned, the replay is paused. - * @return speed multiplier - */ - public double getReplaySpeed() { - if(!paused()) return replaySpeed; - else return 0; - } + void setReplaySpeed(double factor); + double getReplaySpeed(); - /** - * Set the speed of the replay. 1 being normal speed, 0.5 half and 2 twice as fast. - * The speed may not be set to 0 nor to negative values. - * @param d Speed multiplier - */ - public void setReplaySpeed(final double d) { - if(d != 0) this.replaySpeed = d; - //#if MC>=11200 - mc.timer.tickLength = WrappedTimer.DEFAULT_MS_PER_TICK / (float) d; - //#else - //$$ mc.timer.timerSpeed = (float) d; - //#endif - } + boolean isAsyncMode(); + void setAsyncMode(boolean async); + void setSyncModeAndWait(); - ///////////////////////////////////////////////////////// - // Asynchronous packet processing // - ///////////////////////////////////////////////////////// - - /** - * The real time at which the last packet was sent in milliseconds. - */ - private long lastPacketSent; - - /** - * There is no waiting performed until a packet with at least this timestamp is reached (but not yet sent). - * If this is -1, then timing is normal. - */ - private long desiredTimeStamp = -1; - - /** - * Runnable which performs timed dispatching of packets from the input stream. - */ - private Runnable asyncSender = new Runnable() { - public void run() { - try { - while (ctx == null && !terminate) { - Thread.sleep(10); - } - REPLAY_LOOP: - while (!terminate) { - synchronized (ReplaySender.this) { - if (replayIn == null) { - replayIn = replayFile.getPacketData(); - } - // Packet loop - while (true) { - try { - // When playback is paused and the world has loaded (we don't want any dirt-screens) we sleep - while (paused() && hasWorldLoaded) { - // Unless we are going to terminate, restart or jump - if (terminate || startFromBeginning || desiredTimeStamp != -1) { - break; - } - Thread.sleep(10); - } - - if (terminate) { - break REPLAY_LOOP; - } - - if (startFromBeginning) { - // In case we need to restart from the beginning - // break out of the loop sending all packets which will - // cause the replay to be restarted by the outer loop - break; - } - - // Read the next packet if we don't already have one - if (nextPacket == null) { - nextPacket = new PacketData(replayIn); - } - - int nextTimeStamp = nextPacket.timestamp; - - // If we aren't jumping and the world has already been loaded (no dirt-screens) then wait - // the required amount to get proper packet timing - if (!isHurrying() && hasWorldLoaded) { - // How much time should have passed - int timeWait = (int) Math.round((nextTimeStamp - lastTimeStamp) / replaySpeed); - // How much time did pass - long timeDiff = System.currentTimeMillis() - lastPacketSent; - // How much time we need to wait to make up for the difference - long timeToSleep = Math.max(0, timeWait - timeDiff); - - Thread.sleep(timeToSleep); - lastPacketSent = System.currentTimeMillis(); - } - - // Process packet - channelRead(ctx, nextPacket.bytes); - nextPacket = null; - - lastTimeStamp = nextTimeStamp; - - // In case we finished jumping - // We need to check that we aren't planing to restart so we don't accidentally run this - // code before we actually restarted - if (isHurrying() && lastTimeStamp > desiredTimeStamp && !startFromBeginning) { - desiredTimeStamp = -1; - - replayHandler.moveCameraToTargetPosition(); - - // Pause after jumping - setReplaySpeed(0); - } - } catch (EOFException eof) { - // Reached end of file - // Pause the replay which will cause it to freeze before getting restarted - setReplaySpeed(0); - // Then wait until the user tells us to continue - while (paused() && hasWorldLoaded && desiredTimeStamp == -1 && !terminate) { - Thread.sleep(10); - } - break; - } catch (IOException e) { - e.printStackTrace(); - } - } - - // Restart the replay. - hasWorldLoaded = false; - lastTimeStamp = 0; - startFromBeginning = false; - nextPacket = null; - lastPacketSent = System.currentTimeMillis(); - replayHandler.restartedReplay(); - if (replayIn != null) { - replayIn.close(); - replayIn = null; - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - - /** - * Return whether this replay sender is currently rushing. When rushing, all packets are sent without waiting until - * a specified timestamp is passed. - * @return {@code true} if currently rushing, {@code false} otherwise - */ - public boolean isHurrying() { - return desiredTimeStamp != -1; - } - - /** - * Cancels the hurrying. - */ - public void stopHurrying() { - desiredTimeStamp = -1; - } - - /** - * Return the timestamp to which this replay sender is currently rushing. All packets with an lower or equal - * timestamp will be sent out without any sleeping. - * @return The timestamp in milliseconds since the start of the replay - */ - public long getDesiredTimestamp() { - return desiredTimeStamp; - } - - /** - * Jumps to the specified timestamp when in async mode by rushing all packets until one with a timestamp greater - * than the specified timestamp is found. - * If the timestamp has already passed, this causes the replay to restart and then rush all packets. - * @param millis Timestamp in milliseconds since the start of the replay - */ - public void jumpToTime(int millis) { - Preconditions.checkState(asyncMode, "Can only jump in async mode. Use sendPacketsTill(int) instead."); - if(millis < lastTimeStamp && !isHurrying()) { - startFromBeginning = true; - } - - desiredTimeStamp = millis; - } - - protected Packet processPacketAsync(Packet p) { - //If hurrying, ignore some packets, except for short durations - if(desiredTimeStamp - lastTimeStamp > 1000) { - //#if MC>=10904 - if(p instanceof SPacketParticles) return null; - - if(p instanceof SPacketSpawnObject) { - SPacketSpawnObject pso = (SPacketSpawnObject)p; - int type = pso.getType(); - //#else - //$$ if(p instanceof S2APacketParticles) return null; - //$$ - //$$ if(p instanceof S0EPacketSpawnObject) { - //$$ S0EPacketSpawnObject pso = (S0EPacketSpawnObject)p; - //#if MC>=10809 - //$$ int type = pso.getType(); - //#else - //$$ int type = pso.func_148993_l(); - //#endif - //#endif - if(type == 76) { // Firework rocket - return null; - } - } - } - return p; - } - - ///////////////////////////////////////////////////////// - // Synchronous packet processing // - ///////////////////////////////////////////////////////// - - /** - * 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. - * @param timestamp The timestamp in milliseconds since the beginning of this replay - */ - public void sendPacketsTill(int timestamp) { - Preconditions.checkState(!asyncMode, "This method cannot be used in async mode. Use jumpToTime(int) instead."); - try { - while (ctx == null && !terminate) { // Make sure channel is ready - Thread.sleep(10); - } - - synchronized (this) { - if (timestamp == lastTimeStamp) { // Do nothing if we're already there - return; - } - if (timestamp < lastTimeStamp) { // Restart the replay if we need to go backwards in time - hasWorldLoaded = false; - lastTimeStamp = 0; - if (replayIn != null) { - replayIn.close(); - replayIn = null; - } - startFromBeginning = false; - nextPacket = null; - replayHandler.restartedReplay(); - } - - if (replayIn == null) { - replayIn = replayFile.getPacketData(); - } - - while (true) { // Send packets - try { - PacketData pd; - if (nextPacket != null) { - // If there is still a packet left from before, use it first - pd = nextPacket; - nextPacket = null; - } else { - // Otherwise read one from the input stream - pd = new PacketData(replayIn); - } - - int nextTimeStamp = pd.timestamp; - if (nextTimeStamp > timestamp) { - // We are done sending all packets - nextPacket = pd; - break; - } - - // Process packet - channelRead(ctx, pd.bytes); - } catch (EOFException eof) { - // Shit! We hit the end before finishing our job! What shall we do now? - // well, let's just pretend we're done... - replayIn = null; - break; - } catch (IOException e) { - e.printStackTrace(); - } - } - - // This might be required if we change to async mode anytime soon - lastPacketSent = System.currentTimeMillis(); - lastTimeStamp = timestamp; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - protected Packet processPacketSync(Packet p) { - //#if MC>=10904 - if (p instanceof SPacketUnloadChunk) { - SPacketUnloadChunk packet = (SPacketUnloadChunk) p; - int x = packet.getX(); - int z = packet.getZ(); - //#else - //#if MC>=10809 - //$$ if (p instanceof S21PacketChunkData && ((S21PacketChunkData) p).getExtractedSize() == 0) { - //$$ S21PacketChunkData packet = (S21PacketChunkData) p; - //$$ int x = packet.getChunkX(); - //$$ int z = packet.getChunkZ(); - //#else - //$$ if (p instanceof S21PacketChunkData && ((S21PacketChunkData) p).func_149276_g() == 0) { - //$$ S21PacketChunkData packet = (S21PacketChunkData) p; - //$$ int x = packet.func_149273_e(); - //$$ int z = packet.func_149271_f(); - //#endif - //#endif - // If the chunk is getting unloaded, we will have to forcefully update the position of all entities - // within. Otherwise, if there wasn't a game tick recently, there may be entities that have moved - // out of the chunk by now but are still registered in it. If we do not update those, they will get - // unloaded even though they shouldn't. - // Note: This is only half of the truth. Entities may be removed by chunk-unloading, see else-case below. - // To make things worse, it seems like players were never supposed to be unloaded this way because - // they will remain glitched in the World#playerEntities list. - World world = world(mc); - IChunkProvider chunkProvider = world.getChunkProvider(); - // Get the chunk that will be unloaded - Chunk chunk = chunkProvider.provideChunk(x, z); - if (!chunk.isEmpty()) { - List entitiesInChunk = new ArrayList<>(); - // Gather all entities in that chunk - for (Collection entityList : getEntityLists(chunk)) { - entitiesInChunk.addAll(entityList); - } - for (Entity entity : entitiesInChunk) { - // Skip interpolation of position updates coming from server - // (See: newX in EntityLivingBase or otherPlayerMPX in EntityOtherPlayerMP) - // Needs to be called at least 4 times thanks to - // EntityOtherPlayerMP#otherPlayerMPPosRotationIncrements (max vanilla value is 3) - for (int i = 0; i < 4; i++) { - entity.onUpdate(); - } - - // Check whether the entity has left the chunk - int chunkX = floor(entity.posX / 16); - int chunkZ = floor(entity.posZ / 16); - if (entity.chunkCoordX != chunkX || entity.chunkCoordZ != chunkZ) { - // Entity has left the chunk - chunk.removeEntityAtIndex(entity, entity.chunkCoordY); - //#if MC>=10904 - Chunk newChunk = chunkProvider.getLoadedChunk(chunkX, chunkZ); - //#else - //$$ Chunk newChunk = chunkProvider.chunkExists(chunkX, chunkZ) - //$$ ? chunkProvider.provideChunk(chunkX, chunkZ) : null; - //#endif - if (newChunk != null) { - newChunk.addEntity(entity); - } else { - // Entity has left all loaded chunks - entity.addedToChunk = false; - } - } else { - // When entities remain in a chunk that's to be unloaded, they'll only be added to a unload - // queue and remain loaded as before until the next tick (which during jumping is way off). - // So, if they are re-spawned with the same entity id, MC actually cleans up the old entity and - // then adds the new one but leaves the unload queue as is. - // Finally, on the next tick the legitimate entity will be unloaded because it's part of the - // unload queue (entities .equals based purely on their id). However, the old entity object - // is used to determine the chunk the entity is removed from and in this case that'll allow the - // legitimate entity to remain registered in a loaded chunk, causing them to still be rendered. - // - // The usual removal-due-to-chunk-unload process will, without touching the entityList, call - // onEntityRemoved. In that method WorldClient checks to see whether the entity is still in the - // entityList (which it is) and then adds it to the entitySpawnQueue. - // As the final result the entity will remain loaded. - // To get the same result without ticking, we just remove the entity from the to-be-unloaded - // chunk but keep it loaded otherwise. They won't be rendered because they're not part of any - // chunk and will be removed properly if the server decides to re-spawn the entity. - chunk.removeEntityAtIndex(entity, entity.chunkCoordY); - entity.addedToChunk = false; - } - } - } - } - return p; // During synchronous playback everything is sent normally - } - - private static final class PacketData { - private static final ByteBuf byteBuf = Unpooled.buffer(); - private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); - private static final ReplayOutputStream encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut); - private final int timestamp; - private final byte[] bytes; - - public PacketData(ReplayInputStream in) throws IOException { - com.replaymod.replaystudio.PacketData data = in.readPacket(); - timestamp = (int) data.getTime(); - // We need to re-encode MCProtocolLib packets, so we can later decode them as NMS packets - // The main reason we aren't reading them as NMS packets is that we want ReplayStudio to be able - // to apply ViaVersion (and potentially other magic) to it. - synchronized (encoder) { - byteBuf.markReaderIndex(); // Mark the current reader and writer index (should be at start) - byteBuf.markWriterIndex(); - - encoder.write(data); // Re-encode packet, data will end up in byteBuf - encoder.flush(); - - byteBuf.skipBytes(8); // Skip packet length & timestamp - bytes = new byte[byteBuf.readableBytes()]; // Create bytes array of sufficient size - byteBuf.readBytes(bytes); // Read all data into bytes - - byteBuf.resetReaderIndex(); // Reset reader & writer index for next use - byteBuf.resetWriterIndex(); - } - } - } + void jumpToTime(int value); // async + void sendPacketsTill(int replayTime); // sync } diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java index d27379a0..6f968243 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java @@ -128,7 +128,7 @@ public class GuiReplayOverlay extends AbstractGuiOverlay { public void run(int time) { replayHandler.doJump(time, true); } - }).setLength(replayHandler.getReplaySender().replayLength()); + }).setLength(replayHandler.getReplayDuration()); } public double getSpeedSliderValue() { From ad9c82c9917cb870b29196861d3e650b6e58a5c1 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 20 Jul 2018 11:05:21 +0200 Subject: [PATCH 04/65] Fix compatibility with Optifine 1.12.2 E1 Optifine has changed some package names, this commit deals with that. --- .../compat/optifine/OptifineReflection.java | 8 +++++++- .../compat/shaders/ShaderReflection.java | 15 +++++++++------ .../compat/shaders/mixin/MixinShadersRender.java | 5 ++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/replaymod/compat/optifine/OptifineReflection.java b/src/main/java/com/replaymod/compat/optifine/OptifineReflection.java index 07aab724..b05205d2 100644 --- a/src/main/java/com/replaymod/compat/optifine/OptifineReflection.java +++ b/src/main/java/com/replaymod/compat/optifine/OptifineReflection.java @@ -27,7 +27,13 @@ public class OptifineReflection { public static void reloadLang() { try { - Class.forName("Lang").getDeclaredMethod("resourcesReloaded").invoke(null); + Class langClass; + try { + langClass = Class.forName("Lang"); // Pre Optifine 1.12.2 E1 + } catch (ClassNotFoundException ignore) { + langClass = Class.forName("net.optifine.Lang"); // Post Optifine 1.12.2 E1 + } + langClass.getDeclaredMethod("resourcesReloaded").invoke(null); } catch (ClassNotFoundException ignore) { // no optifine installed } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { diff --git a/src/main/java/com/replaymod/compat/shaders/ShaderReflection.java b/src/main/java/com/replaymod/compat/shaders/ShaderReflection.java index 387874fd..5daac450 100644 --- a/src/main/java/com/replaymod/compat/shaders/ShaderReflection.java +++ b/src/main/java/com/replaymod/compat/shaders/ShaderReflection.java @@ -25,16 +25,19 @@ public class ShaderReflection { static { try { - shaders_frameTimeCounter = Class.forName("shadersmod.client.Shaders") - .getDeclaredField("frameTimeCounter"); + Class shadersClass; + try { + shadersClass = Class.forName("shadersmod.client.Shaders"); // Pre Optifine 1.12.2 E1 + } catch (ClassNotFoundException ignore) { + shadersClass = Class.forName("net.optifine.shaders.Shaders"); // Post Optifine 1.12.2 E1 + } + shaders_frameTimeCounter = shadersClass.getDeclaredField("frameTimeCounter"); shaders_frameTimeCounter.setAccessible(true); - shaders_isShadowPass = Class.forName("shadersmod.client.Shaders") - .getDeclaredField("isShadowPass"); + shaders_isShadowPass = shadersClass.getDeclaredField("isShadowPass"); shaders_isShadowPass.setAccessible(true); - shaders_beginRender = Class.forName("shadersmod.client.Shaders") - .getDeclaredMethod("beginRender", Minecraft.class, float.class, long.class); + shaders_beginRender = shadersClass.getDeclaredMethod("beginRender", Minecraft.class, float.class, long.class); shaders_beginRender.setAccessible(true); renderGlobal_chunksToUpdateForced = Class.forName("net.minecraft.client.renderer.RenderGlobal") diff --git a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java index b81d176f..0ba5299f 100644 --- a/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java +++ b/src/main/java/com/replaymod/compat/shaders/mixin/MixinShadersRender.java @@ -9,7 +9,10 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Pseudo -@Mixin(targets = "shadersmod/client/ShadersRender", remap = false) +@Mixin(targets = { + "shadersmod/client/ShadersRender", // Pre Optifine 1.12.2 E1 + "net/optifine/shaders/ShadersRender" // Post Optifine 1.12.2 E1 +}, remap = false) public abstract class MixinShadersRender { @Inject(method = "renderHand0", at = @At("HEAD"), cancellable = true) From 6cb207fd47deaa049120d1b9efabc81904fa1213 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 27 Jul 2018 18:49:06 +0200 Subject: [PATCH 05/65] Also track weather and time during quick replay mode --- .../replaymod/replay/QuickReplaySender.java | 90 ++++++++++++++++--- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index fb299866..161b1794 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -22,7 +22,9 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.Serve import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMultiBlockChangePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerNotifyClientPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTimePacket; import com.google.common.base.Throwables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; @@ -101,6 +103,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private TreeMap> thingDespawnsT = new TreeMap<>(); private ListMultimap thingDespawns = Multimaps.newListMultimap(thingDespawnsT, ArrayList::new); private List activeThings = new LinkedList<>(); + private TreeMap> worldTimes = new TreeMap<>(); + private TreeMap> thunderStrengths = new TreeMap<>(); // For some reason, this isn't tied to Weather public QuickReplaySender(ReplayModReplay mod, ReplayFile replayFile) { this.mod = mod; @@ -246,10 +250,13 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe studio.setParsing(ServerBlockChangePacket.class, true); studio.setParsing(ServerMultiBlockChangePacket.class, true); studio.setParsing(ServerPlayerListEntryPacket.class, true); + studio.setParsing(ServerUpdateTimePacket.class, true); + studio.setParsing(ServerNotifyClientPacket.class, true); Map playerListEntries = new HashMap<>(); Map activeEntities = new HashMap<>(); Map activeChunks = new HashMap<>(); + Weather activeWeather = null; try (ReplayInputStream in = replayFile.getPacketData(studio)) { double duration = replayFile.getMetaData().getDuration(); @@ -337,6 +344,39 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } else if (packet instanceof ServerRespawnPacket) { // FIXME + } else if (packet instanceof ServerUpdateTimePacket) { + worldTimes.put(time, toMC(packet)); + } else if (packet instanceof ServerNotifyClientPacket) { + ServerNotifyClientPacket p = (ServerNotifyClientPacket) packet; + switch (p.getNotification()) { + case START_RAIN: + if (activeWeather != null) { + activeWeather.despawnPackets = Collections.singletonList(toMC(packet)); + activeWeather.despawnTime = time; + thingDespawns.put(time, activeWeather); + } + activeWeather = new Weather(toMC(packet)); + activeWeather.spawnTime = time; + thingSpawns.put(time, activeWeather); + break; + case STOP_RAIN: + if (activeWeather != null) { + activeWeather.despawnPackets = Collections.singletonList(toMC(packet)); + activeWeather.despawnTime = time; + thingDespawns.put(time, activeWeather); + activeWeather = null; + } + break; + case RAIN_STRENGTH: + if (activeWeather != null) { + activeWeather.rainStrengths.put(time, toMC(packet)); + } + break; + case THUNDER_STRENGTH: + thunderStrengths.put(time, toMC(packet)); + break; + default: break; + } } if (entityId != null) { Entity entity = activeEntities.get(entityId); @@ -374,6 +414,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } })); activeThings.forEach(thing -> thing.play(currentTimeStamp, replayTime, ctx::fireChannelRead)); + playMap(worldTimes, currentTimeStamp, replayTime, ctx::fireChannelRead); + playMap(thunderStrengths, currentTimeStamp, replayTime, ctx::fireChannelRead); } else { activeThings.removeIf(thing -> { if (thing.spawnTime > replayTime) { @@ -391,6 +433,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } })); activeThings.forEach(thing -> thing.rewind(currentTimeStamp, replayTime, ctx::fireChannelRead)); + rewindMap(worldTimes, currentTimeStamp, replayTime, ctx::fireChannelRead); + rewindMap(thunderStrengths, currentTimeStamp, replayTime, ctx::fireChannelRead); } currentTimeStamp = replayTime; }); @@ -432,6 +476,20 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe return (long)x << 32 | (long)z & 0xFFFFFFFFL; } + private static void playMap(NavigableMap updates, int currentTimeStamp, int replayTime, Consumer update) { + Map.Entry lastUpdate = updates.floorEntry(replayTime); + if (lastUpdate != null && lastUpdate.getKey() > currentTimeStamp) { + update.accept(lastUpdate.getValue()); + } + } + + private static void rewindMap(NavigableMap updates, int currentTimeStamp, int replayTime, Consumer update) { + Map.Entry lastUpdate = updates.floorEntry(replayTime); + if (lastUpdate != null && !lastUpdate.getKey().equals(updates.floorKey(currentTimeStamp))) { + update.accept(lastUpdate.getValue()); + } + } + private static abstract class TrackedThing { List> spawnPackets; List> despawnPackets; @@ -454,20 +512,14 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe @Override public void play(int currentTimeStamp, int replayTime, Consumer> send) { - Map.Entry lastUpdate = locations.floorEntry(replayTime); - if (lastUpdate != null && lastUpdate.getKey() > currentTimeStamp) { - Location l = lastUpdate.getValue(); - send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false))); - } + playMap(locations, currentTimeStamp, replayTime, l -> + send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false)))); } @Override public void rewind(int currentTimeStamp, int replayTime, Consumer> send) { - Map.Entry lastUpdate = locations.floorEntry(replayTime); - if (lastUpdate != null && !lastUpdate.getKey().equals(locations.floorKey(currentTimeStamp))) { - Location l = lastUpdate.getValue(); - send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false))); - } + rewindMap(locations, currentTimeStamp, replayTime, l -> + send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false)))); } } @@ -517,4 +569,22 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe this.to = to; } } + + private static class Weather extends TrackedThing { + private TreeMap> rainStrengths = new TreeMap<>(); + + private Weather(Packet spawnPacket) { + this.spawnPackets = Collections.singletonList(spawnPacket); + } + + @Override + public void play(int currentTimeStamp, int replayTime, Consumer> send) { + playMap(rainStrengths, currentTimeStamp, replayTime, send); + } + + @Override + public void rewind(int currentTimeStamp, int replayTime, Consumer> send) { + rewindMap(rainStrengths, currentTimeStamp, replayTime, send); + } + } } From 12605f51de9e9343c2739dd5c4a9a1e745babad2 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 27 Jul 2018 19:50:40 +0200 Subject: [PATCH 06/65] Add GUI popup for initializing quick mode and signaling failures --- .../replaymod/replay/QuickReplaySender.java | 8 ++- .../com/replaymod/replay/ReplayHandler.java | 64 ++++++++++++++++++- .../com/replaymod/replay/ReplayModReplay.java | 3 +- 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 161b1794..133beb94 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -124,6 +124,10 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe this.ctx = ctx; } + public ListenableFuture getInitializationPromise() { + return initPromise; + } + public ListenableFuture initialize(Consumer progress) { if (initPromise != null) { return initPromise; @@ -148,9 +152,9 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe return promise; } - public void ensureInitialized(Runnable body) { + private void ensureInitialized(Runnable body) { if (initPromise == null) { - // TODO progress popup + LOGGER.warn("QuickReplaySender used without prior initialization!", new Throwable()); initialize(progress -> {}); } Futures.addCallback(initPromise, new FutureCallback() { diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 33ba056b..81d70521 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -1,7 +1,11 @@ package com.replaymod.replay; import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.core.utils.Restrictions; +import com.replaymod.core.utils.Utils; import com.replaymod.core.utils.WrappedTimer; import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replay.camera.SpectatorCameraController; @@ -11,6 +15,9 @@ import com.replaymod.replay.gui.overlay.GuiReplayOverlay; import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.util.Location; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar; +import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.embedded.EmbeddedChannel; import net.minecraft.client.Minecraft; @@ -18,6 +25,7 @@ import net.minecraft.client.entity.EntityOtherPlayerMP; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.resources.I18n; +import net.minecraft.crash.CrashReport; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.network.NetworkManager; @@ -47,10 +55,13 @@ import static net.minecraft.client.renderer.GlStateManager.*; //$$ import java.net.SocketAddress; //$$ //$$ import static com.replaymod.core.versions.MCVer.GlStateManager.*; -//$$ import static com.replaymod.replay.ReplayModReplay.LOGGER; //#endif +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import static com.replaymod.core.versions.MCVer.*; +import static com.replaymod.replay.ReplayModReplay.LOGGER; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; @@ -262,6 +273,57 @@ public class ReplayHandler { return overlay; } + public void ensureQuickModeInitialized(Runnable andThen) { + ListenableFuture future = quickReplaySender.getInitializationPromise(); + if (future == null) { + InitializingQuickModePopup popup = new InitializingQuickModePopup(overlay); + future = quickReplaySender.initialize(progress -> popup.progressBar.setProgress(progress.floatValue())); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + popup.close(); + } + + @Override + public void onFailure(@Nonnull Throwable t) { + String message = "Failed to initialize quick mode. It will not be available."; + Utils.error(LOGGER, overlay, CrashReport.makeCrashReport(t, message), popup::close); + } + }); + } + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + andThen.run(); + } + + @Override + public void onFailure(@Nonnull Throwable t) { + // Exception already printed in callback added above + } + }); + } + + private class InitializingQuickModePopup extends AbstractGuiPopup { + private final GuiProgressBar progressBar = new GuiProgressBar(popup).setSize(300, 20) + .setI18nLabel("replaymod.gui.loadquickmode"); + + public InitializingQuickModePopup(GuiContainer container) { + super(container); + open(); + } + + @Override + public void close() { + super.close(); + } + + @Override + protected InitializingQuickModePopup getThis() { + return this; + } + } + public void setQuickMode(boolean quickMode) { if (quickMode == this.quickMode) return; if (quickMode && !fullReplaySender.isAsyncMode()) return; // Cannot activate quick mode when already in sync mode diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index af18a138..5ff5a373 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -144,7 +144,8 @@ public class ReplayModReplay { core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.quickmode", Keyboard.KEY_Q, () -> { if (replayHandler != null) { - replayHandler.setQuickMode(!replayHandler.isQuickMode()); + replayHandler.ensureQuickModeInitialized(() -> + replayHandler.setQuickMode(!replayHandler.isQuickMode())); } }); From 42a2ae35edb6fea84c04ce7ad587aff5728967fb Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 27 Jul 2018 21:26:18 +0200 Subject: [PATCH 07/65] fixup quick weather --- .../java/com/replaymod/replay/QuickReplaySender.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 133beb94..ec72a39f 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -10,6 +10,7 @@ import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.github.steveice10.mc.protocol.data.game.world.WorldType; import com.github.steveice10.mc.protocol.data.game.world.block.BlockChangeRecord; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.mc.protocol.data.game.world.notify.ClientNotification; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityDestroyPacket; @@ -355,17 +356,15 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe switch (p.getNotification()) { case START_RAIN: if (activeWeather != null) { - activeWeather.despawnPackets = Collections.singletonList(toMC(packet)); activeWeather.despawnTime = time; thingDespawns.put(time, activeWeather); } - activeWeather = new Weather(toMC(packet)); + activeWeather = new Weather(); activeWeather.spawnTime = time; thingSpawns.put(time, activeWeather); break; case STOP_RAIN: if (activeWeather != null) { - activeWeather.despawnPackets = Collections.singletonList(toMC(packet)); activeWeather.despawnTime = time; thingDespawns.put(time, activeWeather); activeWeather = null; @@ -577,8 +576,9 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private static class Weather extends TrackedThing { private TreeMap> rainStrengths = new TreeMap<>(); - private Weather(Packet spawnPacket) { - this.spawnPackets = Collections.singletonList(spawnPacket); + private Weather() { + this.spawnPackets = Collections.singletonList(toMC(new ServerNotifyClientPacket(ClientNotification.START_RAIN, null))); + this.despawnPackets = Collections.singletonList(toMC(new ServerNotifyClientPacket(ClientNotification.STOP_RAIN, null))); } @Override From ec5ffe3e02fafb52f0c782a4d335b9c3166e41b4 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 28 Jul 2018 12:59:58 +0200 Subject: [PATCH 08/65] Add caching to the quick replay sender --- .../replaymod/replay/QuickReplaySender.java | 371 ++++++++++++++++-- 1 file changed, 340 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index ec72a39f..9e32b5bc 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -26,6 +26,12 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMultiB import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerNotifyClientPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTimePacket; +import com.github.steveice10.mc.protocol.util.NetUtil; +import com.github.steveice10.packetlib.io.NetInput; +import com.github.steveice10.packetlib.io.NetOutput; +import com.github.steveice10.packetlib.io.stream.StreamNetInput; +import com.github.steveice10.packetlib.io.stream.StreamNetOutput; +import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; @@ -57,6 +63,8 @@ import net.minecraftforge.fml.common.gameevent.TickEvent; import javax.annotation.Nullable; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -66,6 +74,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NavigableMap; +import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; import java.util.function.Consumer; @@ -81,6 +90,9 @@ import static com.replaymod.replay.ReplayModReplay.LOGGER; */ @ChannelHandler.Sharable public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySender { + private static final String CACHE_ENTRY = "quickModeCache.bin"; + private static final int CACHE_VERSION = 0; + private final Minecraft mc = Minecraft.getMinecraft(); private final ReplayModReplay mod; @@ -99,11 +111,11 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private ListenableFuture initPromise; - private TreeMap> thingSpawnsT = new TreeMap<>(); - private ListMultimap thingSpawns = Multimaps.newListMultimap(thingSpawnsT, ArrayList::new); - private TreeMap> thingDespawnsT = new TreeMap<>(); - private ListMultimap thingDespawns = Multimaps.newListMultimap(thingDespawnsT, ArrayList::new); - private List activeThings = new LinkedList<>(); + private TreeMap> thingSpawnsT = new TreeMap<>(); + private ListMultimap thingSpawns = Multimaps.newListMultimap(thingSpawnsT, ArrayList::new); + private TreeMap> thingDespawnsT = new TreeMap<>(); + private ListMultimap thingDespawns = Multimaps.newListMultimap(thingDespawnsT, ArrayList::new); + private List activeThings = new LinkedList<>(); private TreeMap> worldTimes = new TreeMap<>(); private TreeMap> thunderStrengths = new TreeMap<>(); // For some reason, this isn't tied to Weather @@ -138,7 +150,11 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe new Thread(() -> { try { long start = System.currentTimeMillis(); - analyseReplay(progress); + if (!tryLoadFromCache(progress)) { + double progressSplit = 0.9; // 90% of progress time for analysing, 10% for loading + analyseReplay(replayFile, d -> progress.accept(d * progressSplit)); + tryLoadFromCache(d -> progress.accept(d * (1 - progressSplit) + progressSplit)); + } LOGGER.info("Initialized quick replay sender in " + (System.currentTimeMillis() - start) + "ms"); } catch (Throwable e) { LOGGER.error("Initializing quick replay sender:", e); @@ -242,7 +258,56 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe sendPacketsTill(currentTimeStamp + replayTimePassed); } - private void analyseReplay(Consumer progress) { + private boolean tryLoadFromCache(Consumer progress) throws IOException { + Optional cacheOpt = replayFile.get(CACHE_ENTRY); + if (!cacheOpt.isPresent()) return false; + boolean success = loadFromCache(cacheOpt.get(), progress); + if (!success) { + thingSpawnsT.clear(); + thingDespawnsT.clear(); + worldTimes.clear(); + thunderStrengths.clear(); + } + return success; + } + + private boolean loadFromCache(InputStream rawIn, Consumer progress) throws IOException { + long sysTimeStart = System.currentTimeMillis(); + NetInput in = new StreamNetInput(rawIn); + int version = in.readVarInt(); + if (version > CACHE_VERSION) return false; // More recent than this version, no chance we can read that + + int size = in.readVarInt(); + int spawnTime = 0; + for (int i = 0; i < size; i++) { + progress.accept((double) i / size); + spawnTime += in.readVarInt(); + for (int j = in.readVarInt(); j > 0; j--) { + int despawnTime = in.readVarInt(); + BakedTrackedThing trackedThing; + switch (in.readVarInt()) { + case 0: trackedThing = new BakedEntity(version, in); break; + case 1: trackedThing = new BakedChunk(version, in); break; + case 2: trackedThing = new BakedWeather(version, in); break; + default: return false; + } + trackedThing.spawnTime = spawnTime; + trackedThing.despawnTime = despawnTime; + thingSpawns.put(spawnTime, trackedThing); + thingDespawns.put(despawnTime, trackedThing); + } + } + + // These should go quick, they probably won't need to update progress + progress.accept(1.0); + readFromCache(version, in, worldTimes); + readFromCache(version, in, thunderStrengths); + + LOGGER.info("Loaded quick replay from cache in " + (System.currentTimeMillis() - sysTimeStart) + "ms"); + return true; + } + + private static void analyseReplay(ReplayFile replayFile, Consumer progress) throws IOException { ReplayStudio studio = new ReplayStudio(); PacketUtils.registerAllMovementRelated(studio); studio.setParsing(ServerSpawnMobPacket.class, true); @@ -258,29 +323,42 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe studio.setParsing(ServerUpdateTimePacket.class, true); studio.setParsing(ServerNotifyClientPacket.class, true); + TreeMap> thingSpawnsT = new TreeMap<>(); + ListMultimap thingSpawns = Multimaps.newListMultimap(thingSpawnsT, ArrayList::new); + TreeMap worldTimes = new TreeMap<>(); + TreeMap thunderStrengths = new TreeMap<>(); Map playerListEntries = new HashMap<>(); Map activeEntities = new HashMap<>(); Map activeChunks = new HashMap<>(); Weather activeWeather = null; + // 90% of estimated time are for analysis, 10% for writing cache + // This heuristic is based on simply running the function on some 3h replay. + // Numbers may be significantly different for smaller replays where GC doesn't kick in. But the progress bar + // will hopefully be barely visible in that case anyway. + // May need to be updated when performance of MCProtocolLib or this code is improved. + // Same for the split between analyseReplay and loadFromCache declared in #initialize(...) + final double progressSplit = 0.9; + + double sysTimeStart = System.currentTimeMillis(); + double duration; try (ReplayInputStream in = replayFile.getPacketData(studio)) { - double duration = replayFile.getMetaData().getDuration(); + duration = replayFile.getMetaData().getDuration(); PacketData packetData; while ((packetData = in.readPacket()) != null) { com.github.steveice10.packetlib.packet.Packet packet = packetData.getPacket(); int time = (int) packetData.getTime(); - progress.accept(time / duration); + progress.accept(time / duration * progressSplit); Integer entityId = PacketUtils.getEntityId(packet); if (packet instanceof ServerSpawnMobPacket || packet instanceof ServerSpawnObjectPacket || packet instanceof ServerSpawnPaintingPacket) { - Entity entity = new Entity(entityId, Collections.singletonList(toMC(packet))); + Entity entity = new Entity(entityId, Collections.singletonList(packet)); entity.spawnTime = time; thingSpawns.put(time, entity); Entity prev = activeEntities.put(entityId, entity); if (prev != null) { prev.despawnTime = time; - thingDespawns.put(time, prev); } } else if (packet instanceof ServerSpawnPlayerPacket) { ServerPlayerListEntryPacket listEntryPacket = new ServerPlayerListEntryPacket( @@ -289,20 +367,18 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe playerListEntries.get(((ServerSpawnPlayerPacket) packet).getUUID()) } ); - Entity entity = new Entity(entityId, Arrays.asList(toMC(listEntryPacket), toMC(packet))); + Entity entity = new Entity(entityId, Arrays.asList(listEntryPacket, packet)); entity.spawnTime = time; thingSpawns.put(time, entity); Entity prev = activeEntities.put(entityId, entity); if (prev != null) { prev.despawnTime = time; - thingDespawns.put(time, prev); } } else if (packet instanceof ServerEntityDestroyPacket) { for (int id : ((ServerEntityDestroyPacket) packet).getEntityIds()) { Entity entity = activeEntities.remove(id); if (entity != null) { entity.despawnTime = time; - thingDespawns.put(time, entity); } } } else if (packet instanceof ServerChunkDataPacket) { @@ -314,7 +390,6 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe if (prev != null) { prev.currentBlockState = null; // free memory because we no longer need it prev.despawnTime = time; - thingDespawns.put(time, prev); } } else if (packet instanceof ServerUnloadChunkPacket) { ServerUnloadChunkPacket p = (ServerUnloadChunkPacket) packet; @@ -322,7 +397,6 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe if (prev != null) { prev.currentBlockState = null; // free memory because we no longer need it prev.despawnTime = time; - thingDespawns.put(time, prev); } } else if (packet instanceof ServerBlockChangePacket || packet instanceof ServerMultiBlockChangePacket) { for (BlockChangeRecord record : @@ -350,14 +424,13 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } else if (packet instanceof ServerRespawnPacket) { // FIXME } else if (packet instanceof ServerUpdateTimePacket) { - worldTimes.put(time, toMC(packet)); + worldTimes.put(time, packet); } else if (packet instanceof ServerNotifyClientPacket) { ServerNotifyClientPacket p = (ServerNotifyClientPacket) packet; switch (p.getNotification()) { case START_RAIN: if (activeWeather != null) { activeWeather.despawnTime = time; - thingDespawns.put(time, activeWeather); } activeWeather = new Weather(); activeWeather.spawnTime = time; @@ -366,17 +439,16 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe case STOP_RAIN: if (activeWeather != null) { activeWeather.despawnTime = time; - thingDespawns.put(time, activeWeather); activeWeather = null; } break; case RAIN_STRENGTH: if (activeWeather != null) { - activeWeather.rainStrengths.put(time, toMC(packet)); + activeWeather.rainStrengths.put(time, packet); } break; case THUNDER_STRENGTH: - thunderStrengths.put(time, toMC(packet)); + thunderStrengths.put(time, packet); break; default: break; } @@ -392,9 +464,43 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } } - } catch (IOException e) { - e.printStackTrace(); } + LOGGER.info("Analysed replay in " + (System.currentTimeMillis() - sysTimeStart) + "ms"); + + sysTimeStart = System.currentTimeMillis(); + try (OutputStream cacheOut = replayFile.write(CACHE_ENTRY)) { + NetOutput out = new StreamNetOutput(cacheOut); + out.writeVarInt(CACHE_VERSION); + + out.writeVarInt(thingSpawnsT.size()); + int lastTime = 0; + for (Map.Entry> entry : thingSpawnsT.entrySet()) { + int time = entry.getKey(); + out.writeVarInt(time - lastTime); + lastTime = time; + + progress.accept(time / duration * (1 - progressSplit) + progressSplit); + + Collection trackedThings = entry.getValue(); + out.writeVarInt(trackedThings.size()); + for (TrackedThing trackedThing : trackedThings) { + out.writeVarInt(trackedThing.despawnTime); + if (trackedThing instanceof Entity) { + out.writeVarInt(0); + } else if (trackedThing instanceof Chunk) { + out.writeVarInt(1); + } else if (trackedThing instanceof Weather) { + out.writeVarInt(2); + } else { + throw new UnsupportedOperationException("Unknown type of tracked thing: " + trackedThing); + } + trackedThing.writeToCache(out); + } + } + writeToCache(out, worldTimes); + writeToCache(out, thunderStrengths); + } + LOGGER.info("Wrote quick replay to cache in " + (System.currentTimeMillis() - sysTimeStart) + "ms"); } @Override @@ -475,6 +581,86 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } + private static Packet readPacketFromCache(int version, NetInput in) throws IOException { + int readerIndex = byteBuf.readerIndex(); // Mark the current reader and writer index (should be at start) + int writerIndex = byteBuf.writerIndex(); + try { + packetBuf.writeBytes(in.readBytes(in.readVarInt())); + + int packetId = packetBuf.readVarInt(); + Packet mcPacket = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, packetId); + mcPacket.readPacketData(packetBuf); + return mcPacket; + } catch (Exception e) { + Throwables.throwIfInstanceOf(e, IOException.class); + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } finally { + byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use + byteBuf.writerIndex(writerIndex); + } + } + + private static List> readPacketsFromCache(int version, NetInput in) throws IOException { + int size = in.readVarInt(); + List> packets = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + packets.add(readPacketFromCache(version, in)); + } + return packets; + } + + private static void readFromCache(int version, NetInput in, SortedMap> packets) throws IOException { + int time = 0; + for (int i = in.readVarInt(); i > 0; i--) { + time += in.readVarInt(); + packets.put(time, readPacketFromCache(version, in)); + } + } + + private static void writeToCache(NetOutput out, com.github.steveice10.packetlib.packet.Packet packet) throws IOException { + int readerIndex = byteBuf.readerIndex(); // Mark the current reader and writer index (should be at start) + int writerIndex = byteBuf.writerIndex(); + try { + encoder.write(0, packet); // Re-encode packet, data will end up in byteBuf + encoder.flush(); + + byteBuf.skipBytes(8); // Skip packet length & timestamp + + int size = byteBuf.readableBytes(); + out.writeVarInt(size); + for (int i = 0; i < size; i++) { + out.writeByte(byteBuf.readByte()); + } + } catch (Exception e) { + Throwables.throwIfInstanceOf(e, IOException.class); + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } finally { + byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use + byteBuf.writerIndex(writerIndex); + } + } + + private static void writeToCache(NetOutput out, Collection packets) throws IOException { + out.writeVarInt(packets.size()); + for (com.github.steveice10.packetlib.packet.Packet packet : packets) { + writeToCache(out, packet); + } + } + + private static void writeToCache(NetOutput out, SortedMap packets) throws IOException { + out.writeVarInt(packets.size()); + int lastTime = 0; + for (Map.Entry entry : packets.entrySet()) { + int time = entry.getKey(); + out.writeVarInt(time - lastTime); + lastTime = time; + + writeToCache(out, entry.getValue()); + } + } + private static long coordToLong(int x, int z) { return (long)x << 32 | (long)z & 0xFFFFFFFFL; } @@ -494,11 +680,39 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } private static abstract class TrackedThing { + List spawnPackets; + List despawnPackets; + int spawnTime; + int despawnTime = Integer.MAX_VALUE; + + private TrackedThing(List spawnPackets, + List despawnPackets) { + this.spawnPackets = spawnPackets; + this.despawnPackets = despawnPackets; + } + + public void writeToCache(NetOutput out) throws IOException { + QuickReplaySender.writeToCache(out, spawnPackets); + QuickReplaySender.writeToCache(out, despawnPackets); + } + } + + // For quicker jumping we store MC packets. + // However, during replay analysis it's easier to use MCProtocolLib ones in part because MC packets have been + // renamed from 1.8 to 1.9 but also because we cannot easily serialize them for caching. + // Therefore during analysis we use TrackedThing which we then serialize (we'd have to do that anyway for caching) + // and afterwards unserialize in BakedTrackedThing as MC packets for replaying. + private static abstract class BakedTrackedThing { List> spawnPackets; List> despawnPackets; int spawnTime; int despawnTime = Integer.MAX_VALUE; + private BakedTrackedThing(int version, NetInput in) throws IOException { + spawnPackets = readPacketsFromCache(version, in); + despawnPackets = readPacketsFromCache(version, in); + } + public abstract void play(int currentTimeStamp, int replayTime, Consumer> send); public abstract void rewind(int currentTimeStamp, int replayTime, Consumer> send); } @@ -507,10 +721,45 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private int id; private NavigableMap locations = new TreeMap<>(); - private Entity(int entityId, List> spawnPackets) { + private Entity(int entityId, List spawnPackets) { + super(spawnPackets, Collections.singletonList(new ServerEntityDestroyPacket(entityId))); this.id = entityId; - this.spawnPackets = spawnPackets; - this.despawnPackets = Collections.singletonList(toMC(new ServerEntityDestroyPacket(entityId))); + } + + @Override + public void writeToCache(NetOutput out) throws IOException { + super.writeToCache(out); + + out.writeVarInt(id); + out.writeVarInt(locations.size()); + int lastTime = 0; + for (Map.Entry entry : locations.entrySet()) { + int time = entry.getKey(); + Location loc = entry.getValue(); + out.writeVarInt(time - lastTime); + lastTime = time; + out.writeDouble(loc.getX()); + out.writeDouble(loc.getY()); + out.writeDouble(loc.getZ()); + out.writeFloat(loc.getYaw()); + out.writeFloat(loc.getPitch()); + } + } + } + + private static class BakedEntity extends BakedTrackedThing { + private int id; + private NavigableMap locations = new TreeMap<>(); + + private BakedEntity(int version, NetInput in) throws IOException { + super(version, in); + + id = in.readVarInt(); + int time = 0; + for (int i = in.readVarInt(); i > 0; i--) { + time += in.readVarInt(); + locations.put(time, new Location(in.readDouble(), in.readDouble(), in.readDouble(), in.readFloat(), in.readFloat())); + } } @Override @@ -532,14 +781,57 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private BlockStorage[] currentBlockState = new BlockStorage[16]; private Chunk(Column column) { - this.spawnPackets = Collections.singletonList(toMC(new ServerChunkDataPacket(column))); - this.despawnPackets = Collections.singletonList(toMC(new ServerUnloadChunkPacket(column.getX(), column.getZ()))); + super(Collections.singletonList(new ServerChunkDataPacket(column)), + Collections.singletonList(new ServerUnloadChunkPacket(column.getX(), column.getZ()))); com.github.steveice10.mc.protocol.data.game.chunk.Chunk[] chunks = column.getChunks(); for (int i = 0; i < currentBlockState.length; i++) { currentBlockState[i] = chunks[i] == null ? new BlockStorage() : chunks[i].getBlocks(); } } + @Override + public void writeToCache(NetOutput out) throws IOException { + super.writeToCache(out); + + out.writeVarInt(blocksT.size()); + int lastTime = 0; + for (Map.Entry> entry : blocksT.entrySet()) { + int time = entry.getKey(); + out.writeVarInt(time - lastTime); + lastTime = time; + + Collection blockChanges = entry.getValue(); + out.writeVarInt(blockChanges.size()); + for (BlockChange blockChange : blockChanges) { + NetUtil.writePosition(out, blockChange.pos); + NetUtil.writeBlockState(out, blockChange.from); + NetUtil.writeBlockState(out, blockChange.to); + } + } + } + } + + private static class BakedChunk extends BakedTrackedThing { + private TreeMap> blocksT = new TreeMap<>(); + private ListMultimap blocks = Multimaps.newListMultimap(blocksT, LinkedList::new); // LinkedList to allow .descendingIterator + + private BakedChunk(int version, NetInput in) throws IOException { + super(version, in); + + int time = 0; + for (int i = in.readVarInt(); i > 0; i--) { + time += in.readVarInt(); + + for (int j = in.readVarInt(); j > 0; j--) { + blocks.put(time, new BlockChange( + NetUtil.readPosition(in), + NetUtil.readBlockState(in), + NetUtil.readBlockState(in) + )); + } + } + } + @Override public void play(int currentTimeStamp, int replayTime, Consumer> send) { blocksT.subMap(currentTimeStamp, false, replayTime, true).values() @@ -574,11 +866,28 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } private static class Weather extends TrackedThing { - private TreeMap> rainStrengths = new TreeMap<>(); + private TreeMap rainStrengths = new TreeMap<>(); private Weather() { - this.spawnPackets = Collections.singletonList(toMC(new ServerNotifyClientPacket(ClientNotification.START_RAIN, null))); - this.despawnPackets = Collections.singletonList(toMC(new ServerNotifyClientPacket(ClientNotification.STOP_RAIN, null))); + super(Collections.singletonList(new ServerNotifyClientPacket(ClientNotification.START_RAIN, null)), + Collections.singletonList(new ServerNotifyClientPacket(ClientNotification.STOP_RAIN, null))); + } + + @Override + public void writeToCache(NetOutput out) throws IOException { + super.writeToCache(out); + + QuickReplaySender.writeToCache(out, rainStrengths); + } + } + + private static class BakedWeather extends BakedTrackedThing { + private TreeMap> rainStrengths = new TreeMap<>(); + + private BakedWeather(int version, NetInput in) throws IOException { + super(version, in); + + readFromCache(version, in, rainStrengths); } @Override From 3db9d58f8f420d95d68b367796af060dda3ddedc Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 28 Jul 2018 13:07:57 +0200 Subject: [PATCH 09/65] Handle Respawn packet in QuickReplaySender --- .../java/com/replaymod/replay/QuickReplaySender.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 9e32b5bc..34acf4af 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -422,7 +422,14 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } } else if (packet instanceof ServerRespawnPacket) { - // FIXME + activeEntities.values().forEach(entity -> entity.despawnTime = time); + activeEntities.clear(); + activeChunks.values().forEach(chunk -> chunk.despawnTime = time); + activeChunks.clear(); + if (activeWeather != null) { + activeWeather.despawnTime = time; + } + activeWeather = null; } else if (packet instanceof ServerUpdateTimePacket) { worldTimes.put(time, packet); } else if (packet instanceof ServerNotifyClientPacket) { From 34343a809f5b83f6bff898781d704c757cbbd518 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 21 Aug 2018 11:24:19 +0200 Subject: [PATCH 10/65] Update ReplayStudio (fixes #131) --- gradle/reprod/checksums.txt | 36 ++++++++++++++++++------------------ versions/common.gradle | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/gradle/reprod/checksums.txt b/gradle/reprod/checksums.txt index 04ec2f70..20344785 100644 --- a/gradle/reprod/checksums.txt +++ b/gradle/reprod/checksums.txt @@ -30,24 +30,24 @@ cddfc1a07301f2fa07a5e1b6897e455d397d6d5c547d45082c267a1ee2c75f70 /com/github/Mod 7191124b8491d36b9fc1eb1a4e0f8ef710a4b98e27dde92854c24d45457fd9d0 /com/github/ModCoderPack/MCInjector/05aa80e/MCInjector-05aa80e.pom ecf57aab63509ec0ce3937c2215bc3a2e3cb6357cd11095978db18bd3251731a /com/github/ModCoderPack/MCInjector/7258466/MCInjector-7258466.jar dc7ed1f4f0ddd8f5ce3b449db944907a5eb2855dbbbc59b08dad1247ebafba33 /com/github/ModCoderPack/MCInjector/7258466/MCInjector-7258466.pom -e1a215146621ab100c5d88c6c65e7b76c91f2a06ab8714ac438738ddec5ea904 /com/github/ReplayMod/ReplayStudio/1.10.2/3680c2e/1.10.2-3680c2e.jar -da0b27f2719bf829f91eb4d6ea7d6e54c71b301a19578bafe6ec05e72ed04a6a /com/github/ReplayMod/ReplayStudio/1.10.2/3680c2e/1.10.2-3680c2e.pom -67e8b400d005235bd2825717864c4a9341567302877c857c3681adc74802b2ba /com/github/ReplayMod/ReplayStudio/1.11.2/3680c2e/1.11.2-3680c2e.jar -ff786e8da1d52bfdca8abe58bc83cf9200e027a85212da633e795811b5c8b1ee /com/github/ReplayMod/ReplayStudio/1.11.2/3680c2e/1.11.2-3680c2e.pom -68da074f5cccd590c9584efc885f6ea842a15f1ce968aad04cbac6af2c8ecf49 /com/github/ReplayMod/ReplayStudio/1.11/3680c2e/1.11-3680c2e.jar -79a6ed9587d6b23f5221b0fdff12f01de4859dc985f3cdbebf081edd4e478a54 /com/github/ReplayMod/ReplayStudio/1.11/3680c2e/1.11-3680c2e.pom -60549740b778011d5e4a8b786805c6de8c518ce8f627cf70369b2c71f0d399c4 /com/github/ReplayMod/ReplayStudio/1.12.1/3680c2e/1.12.1-3680c2e.jar -da248126087579d384175fa36eb654c8755027727aff6c285cd8e4e056ddf424 /com/github/ReplayMod/ReplayStudio/1.12.1/3680c2e/1.12.1-3680c2e.pom -30ecf796e3697cf7d86c21f245695e659b39f55f2aecf40864ec6ac2c0eeae87 /com/github/ReplayMod/ReplayStudio/1.12.2/3680c2e/1.12.2-3680c2e.jar -73c4c53e5f18cffd921db81019510c47afb306eb2aea86377f44fc91e8b45130 /com/github/ReplayMod/ReplayStudio/1.12.2/3680c2e/1.12.2-3680c2e.pom -2f0e27b40a0c3ac298573c6a2fad24829e968731676305b5f1c471a827ac7458 /com/github/ReplayMod/ReplayStudio/1.12/3680c2e/1.12-3680c2e.jar -282e2fa0ef80206ab592f90f8585d5ecf5adfb110dcd1cb5204971257e3fd44e /com/github/ReplayMod/ReplayStudio/1.12/3680c2e/1.12-3680c2e.pom -1aa22b15d5e89258fc58c2a310c915937cf93b42bf27b9fefc04d20b6b07b19c /com/github/ReplayMod/ReplayStudio/1.7.10/3680c2e/1.7.10-3680c2e.jar -fbd4bfe141d845248bb095d59f3aaf273bf20ad849cec89d21594754a69f3f53 /com/github/ReplayMod/ReplayStudio/1.7.10/3680c2e/1.7.10-3680c2e.pom -96b37e76cf8b4aac4f0419d84ba6427da156cd7c4f41f9b9380d149893dd50a7 /com/github/ReplayMod/ReplayStudio/1.8/3680c2e/1.8-3680c2e.jar -7a68b798f4029b98b391076992d39633b3060d06d43edc25422edbbed09914f5 /com/github/ReplayMod/ReplayStudio/1.8/3680c2e/1.8-3680c2e.pom -5e5bea81fd85631a5f80973fab581e991d793d543e2bbbdbf1bebc15b4a279cd /com/github/ReplayMod/ReplayStudio/1.9.4/3680c2e/1.9.4-3680c2e.jar -cb0a152f7c720de75325b2cb857879596edf8c4b0de6f5b39168a1be1aa6709f /com/github/ReplayMod/ReplayStudio/1.9.4/3680c2e/1.9.4-3680c2e.pom +72dd4d393e1103a8719629c2231b7fc4ac307f339c24d0da94c6cc1d050838ec /com/github/ReplayMod/ReplayStudio/1.10.2/2d8d41d/1.10.2-2d8d41d.jar +6849dab3156262b109d07bba0ac56ed996f6581b2f7f4065c88afe0a9d3a620c /com/github/ReplayMod/ReplayStudio/1.10.2/2d8d41d/1.10.2-2d8d41d.pom +868ce8eb3c2cf8628edb952828cbfec26fa9c443a33a6125a912c40548e0a835 /com/github/ReplayMod/ReplayStudio/1.11.2/2d8d41d/1.11.2-2d8d41d.jar +599546e6a69a60df616f657f3bda58d0d4e58103a17de351c65941e0b53bc658 /com/github/ReplayMod/ReplayStudio/1.11.2/2d8d41d/1.11.2-2d8d41d.pom +4ccffcdea734d26004364737b90cc394049a11f3ab0d47c66cdd2f0d5728e316 /com/github/ReplayMod/ReplayStudio/1.11/2d8d41d/1.11-2d8d41d.jar +e8e9bf0ee84eb6f89ed6ec28d5142fc0cdb68eea74a763a6f8ee5f4ee8af00d2 /com/github/ReplayMod/ReplayStudio/1.11/2d8d41d/1.11-2d8d41d.pom +57fef9ef4cbcf766f73122556d05aab6e80fecc3c9b12d0efdfb090500d422cf /com/github/ReplayMod/ReplayStudio/1.12.2/2d8d41d/1.12.2-2d8d41d.jar +55601b8a60329c2b5271dd8a44f7dd82bbf4288d698e840c111edeb9a0b52517 /com/github/ReplayMod/ReplayStudio/1.12.2/2d8d41d/1.12.2-2d8d41d.pom +f175ad66645b7fdf6e472ccac0e01893ea22cfb7812636013d0982b6280b4663 /com/github/ReplayMod/ReplayStudio/1.12.1/2d8d41d/1.12.1-2d8d41d.jar +d3ea142c6ef67c84d1b0f658a8a17612465fc2d89fb8c10fe15def51392a9fc2 /com/github/ReplayMod/ReplayStudio/1.12.1/2d8d41d/1.12.1-2d8d41d.pom +bcf66a214b72615ba0d60a7d4a8be0ae3480baa5a65aa37732616d0d7154fe68 /com/github/ReplayMod/ReplayStudio/1.12/2d8d41d/1.12-2d8d41d.jar +6681d0640cb569a2b2c8c082371e80f96e67b3ba292a4ba56cf188a71a6d915b /com/github/ReplayMod/ReplayStudio/1.12/2d8d41d/1.12-2d8d41d.pom +7f718341faa2b1203b078e8a416b74ae195dc98f98eebb0e08eb462cf98d6cd7 /com/github/ReplayMod/ReplayStudio/1.7.10/2d8d41d/1.7.10-2d8d41d.jar +d482d4a1d6b3b169822512ecb8a296ed43cbdba2f3ae85cc391c9e890526b7e7 /com/github/ReplayMod/ReplayStudio/1.7.10/2d8d41d/1.7.10-2d8d41d.pom +dc19cacb7b2e8dbce84200f78f0799ec8509343777b6d7511614e4b3a48e6f26 /com/github/ReplayMod/ReplayStudio/1.8/2d8d41d/1.8-2d8d41d.pom +6497cc087743beaaf397cda6d2ca76eb5afa37dae9889ea587ef8deae6483edd /com/github/ReplayMod/ReplayStudio/1.8/2d8d41d/1.8-2d8d41d.jar +9134cdee78eaac0c2c4a4728cef40fb8b9c824cbb587efdde5d768f23f295dfc /com/github/ReplayMod/ReplayStudio/1.9.4/2d8d41d/1.9.4-2d8d41d.jar +67faf1d44985182c4056ddbde300db813ca424b3f07818172eedcfc0f0a2c496 /com/github/ReplayMod/ReplayStudio/1.9.4/2d8d41d/1.9.4-2d8d41d.pom bfdcda4cbf23f28392384818a1417c2d6df687e15ce77530ef96381393102f8a /com/github/SpongePowered/MixinGradle/dcfaf61/MixinGradle-dcfaf61.jar 3552e0c04817d91d1b6671a051d0e1d952c8b6a8eec57b54e829de9fa3ddf002 /com/github/SpongePowered/MixinGradle/dcfaf61/MixinGradle-dcfaf61.pom c6ebd74cd6108b8c372c95b842725ee5bd6d3303ba26cb00f18bc92f80b47956 /com/github/tony19/named-regexp/0.2.3/named-regexp-0.2.3.jar diff --git a/versions/common.gradle b/versions/common.gradle index 3609449b..56f2e39b 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -131,7 +131,7 @@ dependencies { def studioVersion = "${(int)(mcVersion/10000)}.${(int)(mcVersion/100)%100}" + (mcVersion%100==0 ? '' : ".${mcVersion%100}") if (studioVersion == '1.8.9') studioVersion = '1.8' - shade("com.github.ReplayMod.ReplayStudio:$studioVersion:3680c2e"){ + shade("com.github.ReplayMod.ReplayStudio:$studioVersion:2d8d41d"){ exclude group: 'com.google.guava', module: 'guava' // provided by MC exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } From b6a7d3bcda3c5decb40a07766fadaac83ff7f0a4 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 22 Aug 2018 14:07:03 +0200 Subject: [PATCH 11/65] Disable maven artifact checksum verification because Mojang keeps breaking it (and so do some maven repos) and I can't be bothered to constantly fix it. --- gradle/reprod/checksums.empty.txt | 0 gradle/reprod/setup.sh | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 gradle/reprod/checksums.empty.txt diff --git a/gradle/reprod/checksums.empty.txt b/gradle/reprod/checksums.empty.txt new file mode 100644 index 00000000..e69de29b diff --git a/gradle/reprod/setup.sh b/gradle/reprod/setup.sh index 46d93973..00c0a8cf 100755 --- a/gradle/reprod/setup.sh +++ b/gradle/reprod/setup.sh @@ -34,7 +34,7 @@ popd java -Dproxywitness.httpUris=http://export.mcpbot.bspk.rs/versions.json \ -Dproxywitness.useCache=false \ -jar deps/proxy-witness/build/libs/proxy-witness.jar \ - "$PROXY_PORT" checksums.txt > proxy.log 2>&1 & + "$PROXY_PORT" checksums.empty.txt > proxy.log 2>&1 & proxy_pid=$! trap "kill $proxy_pid" EXIT From 73c52b5cf05c101645f554188391b1caf3cd18d0 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 22 Aug 2018 17:28:06 +0200 Subject: [PATCH 12/65] Backport quick mode to 1.9.4+ and disable below --- .../java/com/replaymod/core/utils/Utils.java | 13 +++++++ .../replaymod/replay/FullReplaySender.java | 2 +- .../replaymod/replay/QuickReplaySender.java | 33 +++++++++++----- .../com/replaymod/replay/ReplayHandler.java | 38 +++++++++++++++++++ src/main/resources/assets/replaymod/lang | 2 +- 5 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index d173d1c8..cef6e9a2 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -281,4 +281,17 @@ public class Utils { super.draw(renderer, size, renderInfo); } } + + public static void throwIfInstanceOf(Throwable t, Class cls) throws T { + if (cls.isInstance(t)) { + throw cls.cast(t); + } + } + public static void throwIfUnchecked(Throwable t) { + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else if (t instanceof Error) { + throw (Error) t; + } + } } diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index c91316fc..ec0a3693 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -496,7 +496,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#if MC>=10809 //$$ String url = packet.getURL(); //#else - //$$ String url = packet.func_179783_a(); + //$$ String url = packet.func_179783_a(); //#endif //#endif if (url.startsWith("replay://")) { diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 34acf4af..31b376cf 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -1,3 +1,4 @@ +//#if MC>=10904 package com.replaymod.replay; import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; @@ -32,14 +33,13 @@ import com.github.steveice10.packetlib.io.NetOutput; import com.github.steveice10.packetlib.io.stream.StreamNetInput; import com.github.steveice10.packetlib.io.stream.StreamNetOutput; import com.google.common.base.Optional; -import com.google.common.base.Throwables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; -import com.replaymod.core.utils.WrappedTimer; +import com.replaymod.core.utils.Utils; import com.replaymod.replaystudio.PacketData; import com.replaymod.replaystudio.io.ReplayInputStream; import com.replaymod.replaystudio.io.ReplayOutputStream; @@ -79,6 +79,10 @@ import java.util.TreeMap; import java.util.UUID; import java.util.function.Consumer; +//#if MC>=11200 +import com.replaymod.core.utils.WrappedTimer; +//#endif + import static com.replaymod.core.versions.MCVer.FML_BUS; import static com.replaymod.replay.ReplayModReplay.LOGGER; @@ -574,12 +578,17 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe byteBuf.skipBytes(8); // Skip packet length & timestamp - int packetId = packetBuf.readVarInt(); + int packetId = + //#if MC>=11102 + packetBuf.readVarInt(); + //#else + //$$ packetBuf.readVarIntFromBuffer(); + //#endif Packet mcPacket = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, packetId); mcPacket.readPacketData(packetBuf); return mcPacket; } catch (Exception e) { - Throwables.throwIfUnchecked(e); + Utils.throwIfUnchecked(e); throw new RuntimeException(e); } finally { byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use @@ -594,13 +603,18 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe try { packetBuf.writeBytes(in.readBytes(in.readVarInt())); - int packetId = packetBuf.readVarInt(); + int packetId = + //#if MC>=11102 + packetBuf.readVarInt(); + //#else + //$$ packetBuf.readVarIntFromBuffer(); + //#endif Packet mcPacket = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, packetId); mcPacket.readPacketData(packetBuf); return mcPacket; } catch (Exception e) { - Throwables.throwIfInstanceOf(e, IOException.class); - Throwables.throwIfUnchecked(e); + Utils.throwIfInstanceOf(e, IOException.class); + Utils.throwIfUnchecked(e); throw new RuntimeException(e); } finally { byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use @@ -640,8 +654,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe out.writeByte(byteBuf.readByte()); } } catch (Exception e) { - Throwables.throwIfInstanceOf(e, IOException.class); - Throwables.throwIfUnchecked(e); + Utils.throwIfInstanceOf(e, IOException.class); + Utils.throwIfUnchecked(e); throw new RuntimeException(e); } finally { byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use @@ -908,3 +922,4 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } } +//#endif diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 81d70521..45007209 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -34,6 +34,12 @@ import org.lwjgl.opengl.Display; import java.io.IOException; import java.util.*; +//#if MC<10904 +//$$ import de.johni0702.minecraft.gui.element.GuiLabel; +//$$ import de.johni0702.minecraft.gui.popup.GuiInfoPopup; +//$$ import de.johni0702.minecraft.gui.utils.Colors; +//#endif + //#if MC>=10800 import com.mojang.authlib.GameProfile; import net.minecraft.client.network.NetHandlerPlayClient; @@ -78,8 +84,12 @@ public class ReplayHandler { * Decodes and sends packets into channel. */ private final FullReplaySender fullReplaySender; + //#if MC>=10904 private final QuickReplaySender quickReplaySender; private boolean quickMode = false; + //#else + //$$ private static final String QUICK_MODE_MIN_MC = "1.9.4"; + //#endif /** * Currently active replay restrictions. @@ -117,7 +127,9 @@ public class ReplayHandler { markers = new ArrayList<>(replayFile.getMarkers().or(Collections.emptySet())); fullReplaySender = new FullReplaySender(this, replayFile, false); + //#if MC>=10904 quickReplaySender = new QuickReplaySender(ReplayModReplay.instance, replayFile); + //#endif setup(); @@ -149,7 +161,9 @@ public class ReplayHandler { FML_BUS.post(new ReplayCloseEvent.Pre(this)); fullReplaySender.terminateReplay(); + //#if MC>=10904 quickReplaySender.unregister(); + //#endif replayFile.save(); replayFile.close(); @@ -214,6 +228,9 @@ public class ReplayHandler { //$$ channel.attr(NetworkDispatcher.FML_DISPATCHER).set(networkDispatcher); //$$ //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); + //#if MC>=10904 + //$$ channel.pipeline().addFirst("ReplayModReplay_quickReplaySender", quickReplaySender); + //#endif //$$ channel.pipeline().addAfter("ReplayModReplay_replaySender", "fml:packet_handler", networkDispatcher); //$$ channel.pipeline().fireChannelActive(); //#endif @@ -266,13 +283,18 @@ public class ReplayHandler { } public ReplaySender getReplaySender() { + //#if MC>=10904 return quickMode ? quickReplaySender : fullReplaySender; + //#else + //$$ return fullReplaySender; + //#endif } public GuiReplayOverlay getOverlay() { return overlay; } + //#if MC>=10904 public void ensureQuickModeInitialized(Runnable andThen) { ListenableFuture future = quickReplaySender.getInitializationPromise(); if (future == null) { @@ -346,6 +368,20 @@ public class ReplayHandler { public boolean isQuickMode() { return quickMode; } + //#else + //$$ public void ensureQuickModeInitialized(@SuppressWarnings("unused") Runnable andThen) { + //$$ GuiInfoPopup.open(overlay, + //$$ new GuiLabel().setI18nText("replaymod.gui.noquickmode", QUICK_MODE_MIN_MC).setColor(Colors.BLACK)); + //$$ } + //$$ + //$$ public void setQuickMode(@SuppressWarnings("unused") boolean quickMode) { + //$$ throw new UnsupportedOperationException("Quick Mode not supported on this version."); + //$$ } + //$$ + //$$ public boolean isQuickMode() { + //$$ return false; + //$$ } + //#endif public int getReplayDuration() { return replayDuration; @@ -460,10 +496,12 @@ public class ReplayHandler { } public void doJump(int targetTime, boolean retainCameraPosition) { + //#if MC>=10904 if (getReplaySender() == quickReplaySender) { quickReplaySender.sendPacketsTill(targetTime); return; } + //#endif FullReplaySender replaySender = fullReplaySender; if (replaySender.isHurrying()) { diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 692a6765..16508289 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 692a6765560bd96f02c9364798395914470c2c9e +Subproject commit 165082892e4f323d0bc5ef6bc390325664372035 From 85a167a0d50aeaf05a56194355b0adb53e7b538e Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 25 Aug 2018 11:02:42 +0200 Subject: [PATCH 13/65] Fix camera position being able to become NaN with classic controls To reproduce, hold A for about a second, then release A and press D in the same frame. --- .../com/replaymod/replay/camera/ClassicCameraController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java b/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java index 621d1cbe..60d168c2 100644 --- a/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java +++ b/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java @@ -126,7 +126,7 @@ public class ClassicCameraController implements CameraController { lastCall = Sys.getTime(); - if(direction == null || motion < THRESHOLD) { + if(direction == null || direction.lengthSquared() == 0 || motion < THRESHOLD) { return; } From a9e32a53ea6b0b25f43c2efd0467c2c354c5f370 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 25 Aug 2018 13:47:08 +0200 Subject: [PATCH 14/65] Update jGui (fixes "Cannot convert pos unless rendered at least once.") --- jGui | 2 +- .../java/com/replaymod/render/gui/GuiVideoRenderer.java | 7 +++---- .../java/com/replaymod/render/rendering/VideoRenderer.java | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jGui b/jGui index 09713104..213ac136 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 097131041b4293245820b6f9b46271b5d5f4a830 +Subproject commit 213ac13627178f49ce9671b29b6a542cb0e4c922 diff --git a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java index 0f60f3b4..d8c00b4c 100644 --- a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java +++ b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java @@ -11,6 +11,7 @@ import de.johni0702.minecraft.gui.element.GuiButton; import de.johni0702.minecraft.gui.element.GuiCheckbox; import de.johni0702.minecraft.gui.element.GuiLabel; import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar; +import de.johni0702.minecraft.gui.function.Tickable; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import net.minecraft.client.renderer.texture.DynamicTexture; @@ -22,7 +23,7 @@ import org.lwjgl.util.ReadablePoint; import java.nio.ByteBuffer; -public class GuiVideoRenderer extends GuiScreen { +public class GuiVideoRenderer extends GuiScreen implements Tickable { private static final ResourceLocation NO_PREVIEW_TEXTURE = new ResourceLocation("replaymod", "logo.jpg"); private final VideoRenderer renderer; @@ -135,7 +136,7 @@ public class GuiVideoRenderer extends GuiScreen { private int currentIndex = 0; @Override - public void draw(GuiRenderer guiRenderer, ReadableDimension size, RenderInfo renderInfo) { + public void tick() { long current = System.currentTimeMillis(); //first, update the total render time (only if rendering is not paused and has already started) @@ -206,8 +207,6 @@ public class GuiVideoRenderer extends GuiScreen { int framesDone = renderer.getFramesDone(), framesTotal = renderer.getTotalFrames(); progressBar.setI18nLabel("replaymod.gui.rendering.progress", framesDone, framesTotal); progressBar.setProgress((float) framesDone / framesTotal); - - super.draw(guiRenderer, size, renderInfo); } private String secToString(int seconds) { diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 4ed6f223..4a890b7f 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -358,6 +358,7 @@ public class VideoRenderer implements RenderInfo { int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth; int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1; + gui.toMinecraft().updateScreen(); gui.toMinecraft().drawScreen(mouseX, mouseY, 0); guiFramebuffer.unbindFramebuffer(); From 20dfebbc2abdf2bf48a9eb7164914913116b8df7 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 25 Aug 2018 14:23:32 +0200 Subject: [PATCH 15/65] Release 2.1.4 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index ac2cdeba..7d2ed7c7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.3 +2.1.4 From a7ec6524b7b472298880dddb5b5e7e3e510b761c Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Thu, 6 Dec 2018 01:29:31 +0100 Subject: [PATCH 16/65] Add FOV slider for spherical render methods Before uploading to YouTube, convert all spherical non-mp4 files to mp4 to be able to inject metadata Properly re-throw errors during the rendering pipeline --- .../extras/youtube/YoutubeUploader.java | 109 +++++++++--------- .../com/replaymod/render/RenderSettings.java | 87 +++++++++++++- .../com/replaymod/render/VideoWriter.java | 60 +--------- .../replaymod/render/gui/GuiExportFailed.java | 4 +- .../render/gui/GuiRenderSettings.java | 104 +++++++++++++---- .../render/metadata/MetadataInjector.java | 59 ++++++++-- .../EquirectangularToRGBProcessor.java | 49 +++++--- .../render/processor/ODSToRGBProcessor.java | 8 +- .../replaymod/render/rendering/Pipelines.java | 18 ++- .../render/rendering/VideoRenderer.java | 16 ++- src/main/resources/assets/replaymod/lang | 2 +- 11 files changed, 340 insertions(+), 176 deletions(-) diff --git a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java index 79a53302..7abe58ce 100644 --- a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java +++ b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java @@ -21,6 +21,7 @@ import com.google.api.services.youtube.model.VideoSnippet; import com.google.api.services.youtube.model.VideoStatus; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.io.Files; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.replaymod.render.RenderSettings; @@ -41,8 +42,7 @@ import static com.replaymod.extras.ReplayModExtras.LOGGER; public class YoutubeUploader { private static final String CLIENT_ID = "743126594724-mfe7pj1k7e47uu5pk4503c8st9vj9ibu.apps.googleusercontent.com"; private static final String CLIENT_SECRET = "gMwcy3mRYCRamCIjJIYP7rqc"; - private static final String FFMPEG_ODS = - "-i %s -vf scale=iw:iw*9/16,setdar=16:9 -c:v libx264 -preset slow -crf 16 %s"; + private static final String FFMPEG_MP4 = "-i %s -c:v libx264 -preset slow -crf 16 %s"; private static final JsonFactory JSON_FACTORY = new GsonFactory(); private final NetHttpTransport httpTransport; private final DataStoreFactory dataStoreFactory; @@ -131,68 +131,73 @@ public class YoutubeUploader { } private File preUpload() throws InterruptedException, IOException { - if (settings.getRenderMethod() == RenderSettings.RenderMethod.ODS) { - File tmpFile = new File(videoFile.getParentFile(), System.currentTimeMillis() + ".mp4"); - tmpFile.deleteOnExit(); + File outputFile = videoFile; - String args = String.format(FFMPEG_ODS, videoFile.getName(), tmpFile.getName()); + if (settings.getRenderMethod().isSpherical()) { + // inject spherical metadata for YouTube unless already done - CommandLine commandLine = new CommandLine(settings.getExportCommand()); - commandLine.addArguments(args); - LOGGER.info("Re-encoding for ODS with {} {}", settings.getExportCommand(), args); - Process process = new ProcessBuilder(commandLine.toStrings()).directory(videoFile.getParentFile()).start(); + boolean isMp4 = Files.getFileExtension(outputFile.getName()).equalsIgnoreCase("mp4"); - final AtomicBoolean active = new AtomicBoolean(true); - final InputStream in = process.getErrorStream(); - new Thread(() -> { - try { - StringBuilder sb = new StringBuilder(); - while (active.get()) { - char c = (char) in.read(); - if (c == '\r') { - String str = sb.toString(); - LOGGER.debug("[FFmpeg] {}", str); - if (str.startsWith("frame=")) { - str = str.substring(6).trim(); - str = str.substring(0, str.indexOf(' ')); - double frame = Integer.parseInt(str); - progress = Suppliers.ofInstance(frame / videoFrames); + if (!isMp4) { + // convert non-mp4 videos into mp4 format to be able to inject metadata + outputFile = new File(outputFile.getParentFile(), System.currentTimeMillis() + ".mp4"); + outputFile.deleteOnExit(); + + String args = String.format(FFMPEG_MP4, videoFile.getName(), outputFile.getName()); + + CommandLine commandLine = new CommandLine(settings.getExportCommandOrDefault()); + commandLine.addArguments(args); + LOGGER.info("Re-encoding for metadata injection with {} {}", settings.getExportCommand(), args); + Process process = new ProcessBuilder(commandLine.toStrings()).directory(outputFile.getParentFile()).start(); + + final AtomicBoolean active = new AtomicBoolean(true); + final InputStream in = process.getErrorStream(); + new Thread(() -> { + try { + StringBuilder sb = new StringBuilder(); + while (active.get()) { + char c = (char) in.read(); + if (c == '\r') { + String str = sb.toString(); + LOGGER.debug("[FFmpeg] {}", str); + if (str.startsWith("frame=")) { + str = str.substring(6).trim(); + str = str.substring(0, str.indexOf(' ')); + double frame = Integer.parseInt(str); + progress = Suppliers.ofInstance(frame / videoFrames); + } + sb = new StringBuilder(); + } else { + sb.append(c); } - sb = new StringBuilder(); - } else { - sb.append(c); } + } catch (IOException e) { + e.printStackTrace(); } - } catch (IOException e) { - e.printStackTrace(); + }).start(); + + int result; + try { + result = process.waitFor(); + } catch (InterruptedException e) { + process.destroy(); + throw e; + } finally { + active.set(false); + } + if (result != 0) { + throw new IOException("FFmpeg returned: " + result); } - }).start(); - - int result; - try { - result = process.waitFor(); - } catch (InterruptedException e) { - process.destroy(); - throw e; - } finally { - active.set(false); - } - if (result != 0) { - throw new IOException("FFmpeg returned: " + result); } - MetadataInjector.injectODSMetadata(tmpFile); - - return tmpFile; - - } else if (settings.getRenderMethod() == RenderSettings.RenderMethod.EQUIRECTANGULAR) { - // if metadata hasn't been injected before, inject it before the upload - if (!settings.isInject360Metadata()) { - MetadataInjector.inject360Metadata(videoFile); + if (!settings.isInjectSphericalMetadata() || !isMp4) { + MetadataInjector.injectMetadata(settings.getRenderMethod(), outputFile, + settings.getTargetVideoWidth(), settings.getTargetVideoHeight(), + settings.getSphericalFovX(), settings.getSphericalFovY()); } } - return videoFile; + return outputFile; } private Video doUpload(YouTube youTube, File processedFile) throws IOException { diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index 37977dda..2d158800 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -3,10 +3,23 @@ package com.replaymod.render; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; +import net.minecraft.client.Minecraft; import net.minecraft.client.resources.I18n; +import net.minecraft.util.Util; import org.lwjgl.util.ReadableColor; import java.io.File; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Optional; + +import static com.replaymod.render.ReplayModRender.LOGGER; + +//#if MC>=10800 +import net.minecraftforge.fml.common.versioning.ComparableVersion; +//#else +//$$ import cpw.mods.fml.common.versioning.ComparableVersion; +//#endif @Data public class RenderSettings { @@ -21,6 +34,14 @@ public class RenderSettings { public String getDescription() { return I18n.format("replaymod.gui.rendersettings.renderer." + name().toLowerCase() + ".description"); } + + public boolean isSpherical() { + return this == EQUIRECTANGULAR || this == ODS; + } + + public boolean hasFixedAspectRatio() { + return this == EQUIRECTANGULAR || this == ODS || this == CUBIC; + } } public enum EncodingPreset { @@ -92,7 +113,9 @@ public class RenderSettings { private final boolean stabilizePitch; private final boolean stabilizeRoll; private final ReadableColor chromaKeyingColor; - private final boolean inject360Metadata; + private final int sphericalFovX; + private final int sphericalFovY; + private final boolean injectSphericalMetadata; private final AntiAliasing antiAliasing; private final String exportCommand; @@ -129,12 +152,66 @@ public class RenderSettings { } public String getVideoFilters() { - if (antiAliasing == AntiAliasing.NONE) { - return ""; - } else { + StringBuilder filters = new StringBuilder(); + + if (antiAliasing != AntiAliasing.NONE) { double factor = 1.0 / antiAliasing.getFactor(); - return String.format("-vf scale=iw*%1$s:ih*%1$s ", factor); + filters.append(String.format("-filter:v scale=iw*%1$s:ih*%1$s ", factor)); } + + return filters.toString(); + } + + public String getExportCommandOrDefault() { + return exportCommand.isEmpty() ? findFFmpeg() : exportCommand; + } + + private static String findFFmpeg() { + switch (Util.getOSType()) { + case WINDOWS: + // Allow windows users to unpack the ffmpeg archive into a sub-folder of their .minecraft folder + File inDotMinecraft = new File(Minecraft.getMinecraft().mcDataDir, "ffmpeg/bin/ffmpeg.exe"); + if (inDotMinecraft.exists()) { + LOGGER.debug("FFmpeg found in .minecraft/ffmpeg"); + return inDotMinecraft.getAbsolutePath(); + } + break; + case OSX: + // The PATH doesn't seem to be set as expected on OSX, therefore we check some common locations ourselves + for (String path : new String[]{"/usr/local/bin/ffmpeg", "/usr/bin/ffmpeg"}) { + File file = new File(path); + if (file.exists()) { + LOGGER.debug("Found FFmpeg at {}", path); + return path; + } else { + LOGGER.debug("FFmpeg not located at {}", path); + } + } + // Homebrew doesn't seem to reliably symlink its installed binaries either + File homebrewFolder = new File("/usr/local/Cellar/ffmpeg"); + String[] homebrewVersions = homebrewFolder.list(); + if (homebrewVersions != null) { + Optional latestOpt = Arrays.stream(homebrewVersions) + .map(ComparableVersion::new) // Convert file name to comparable version + .sorted(Comparator.reverseOrder()) // Sort for latest version + .map(ComparableVersion::toString) // Convert back to file name + .map(v -> new File(new File(homebrewFolder, v), "bin/ffmpeg")) // Convert to binary files + .filter(File::exists) // Filter invalid installations (missing executable) + .findFirst(); // Take first one + if (latestOpt.isPresent()) { + File latest = latestOpt.get(); + LOGGER.debug("Found {} versions of FFmpeg installed with homebrew, chose {}", + homebrewVersions.length, latest); + return latest.getAbsolutePath(); + } + } + break; + case LINUX: // Linux users are entrusted to have their PATH configured correctly (most package manager do this) + case SOLARIS: // Never heard of anyone running this mod on Solaris having any problems + case UNKNOWN: // Unknown OS, just try to use "ffmpeg" + } + LOGGER.debug("Using default FFmpeg executable"); + return "ffmpeg"; } } diff --git a/src/main/java/com/replaymod/render/VideoWriter.java b/src/main/java/com/replaymod/render/VideoWriter.java index d3502a3c..792814f4 100755 --- a/src/main/java/com/replaymod/render/VideoWriter.java +++ b/src/main/java/com/replaymod/render/VideoWriter.java @@ -8,7 +8,6 @@ import com.replaymod.render.utils.StreamPipe; import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; -import net.minecraft.util.Util; import org.apache.commons.exec.CommandLine; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -22,17 +21,8 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Optional; import java.util.concurrent.TimeUnit; -//#if MC>=10800 -import net.minecraftforge.fml.common.versioning.ComparableVersion; -//#else -//$$ import cpw.mods.fml.common.versioning.ComparableVersion; -//#endif - import static com.replaymod.render.ReplayModRender.LOGGER; import static org.apache.commons.lang3.Validate.isTrue; @@ -64,7 +54,7 @@ public class VideoWriter implements FrameConsumer { .replace("%BITRATE%", String.valueOf(settings.getBitRate())) .replace("%FILTERS%", settings.getVideoFilters()); - String executable = settings.getExportCommand().isEmpty() ? findFFmpeg() : settings.getExportCommand(); + String executable = settings.getExportCommandOrDefault(); LOGGER.info("Starting {} with args: {}", executable, commandArgs); String[] cmdline; try { @@ -86,54 +76,6 @@ public class VideoWriter implements FrameConsumer { channel = Channels.newChannel(outputStream); } - private String findFFmpeg() { - switch (Util.getOSType()) { - case WINDOWS: - // Allow windows users to unpack the ffmpeg archive into a sub-folder of their .minecraft folder - File inDotMinecraft = new File(Minecraft.getMinecraft().mcDataDir, "ffmpeg/bin/ffmpeg.exe"); - if (inDotMinecraft.exists()) { - LOGGER.debug("FFmpeg found in .minecraft/ffmpeg"); - return inDotMinecraft.getAbsolutePath(); - } - break; - case OSX: - // The PATH doesn't seem to be set as expected on OSX, therefore we check some common locations ourselves - for (String path : new String[]{"/usr/local/bin/ffmpeg", "/usr/bin/ffmpeg"}) { - File file = new File(path); - if (file.exists()) { - LOGGER.debug("Found FFmpeg at {}", path); - return path; - } else { - LOGGER.debug("FFmpeg not located at {}", path); - } - } - // Homebrew doesn't seem to reliably symlink its installed binaries either - File homebrewFolder = new File("/usr/local/Cellar/ffmpeg"); - String[] homebrewVersions = homebrewFolder.list(); - if (homebrewVersions != null) { - Optional latestOpt = Arrays.stream(homebrewVersions) - .map(ComparableVersion::new) // Convert file name to comparable version - .sorted(Comparator.reverseOrder()) // Sort for latest version - .map(ComparableVersion::toString) // Convert back to file name - .map(v -> new File(new File(homebrewFolder, v), "bin/ffmpeg")) // Convert to binary files - .filter(File::exists) // Filter invalid installations (missing executable) - .findFirst(); // Take first one - if (latestOpt.isPresent()) { - File latest = latestOpt.get(); - LOGGER.debug("Found {} versions of FFmpeg installed with homebrew, chose {}", - homebrewVersions.length, latest); - return latest.getAbsolutePath(); - } - } - break; - case LINUX: // Linux users are entrusted to have their PATH configured correctly (most package manager do this) - case SOLARIS: // Never heard of anyone running this mod on Solaris having any problems - case UNKNOWN: // Unknown OS, just try to use "ffmpeg" - } - LOGGER.debug("Using default FFmpeg executable"); - return "ffmpeg"; - } - @Override public void close() throws IOException { IOUtils.closeQuietly(outputStream); diff --git a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java index b86e7118..930851ca 100644 --- a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java +++ b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java @@ -93,7 +93,9 @@ public class GuiExportFailed extends GuiScreen { oldSettings.isStabilizePitch(), oldSettings.isStabilizeRoll(), oldSettings.getChromaKeyingColor(), - oldSettings.isInject360Metadata(), + oldSettings.getSphericalFovX(), + oldSettings.getSphericalFovY(), + oldSettings.isInjectSphericalMetadata(), oldSettings.getAntiAliasing(), oldSettings.getExportCommand(), oldSettings.getEncodingPreset().getValue(), diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index a07f801e..a0a5ffc6 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -1,5 +1,6 @@ package com.replaymod.render.gui; +import com.google.common.base.Preconditions; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.gson.Gson; @@ -149,6 +150,20 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { .setI18nLabel("replaymod.gui.rendersettings.chromakey"); public final GuiColorPicker chromaKeyingColor = new GuiColorPicker().setSize(30, 15); + public final int minSphericalFov = 120; + public final int maxSphericalFov = 360; + public final int sphericalFovSteps = 5; + public final GuiSlider sphericalFovSlider = new GuiSlider() + .onValueChanged(new Runnable() { + @Override + public void run() { + sphericalFovSlider.setText(I18n.format("replaymod.gui.rendersettings.sphericalFov") + + ": " + (minSphericalFov + sphericalFovSlider.getValue() * sphericalFovSteps) + "°"); + + updateInputs(); + } + }).setSize(200, 20).setSteps((maxSphericalFov - minSphericalFov) / sphericalFovSteps); + public final GuiCheckbox inject360Metadata = new GuiCheckbox() .setI18nLabel("replaymod.gui.rendersettings.360metadata"); @@ -161,8 +176,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { .addElements(new GridLayout.Data(0, 0.5), new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, chromaKeyingCheckbox, chromaKeyingColor, - inject360Metadata, - new GuiLabel(), // to show the anti-aliasing options in a new line + inject360Metadata, sphericalFovSlider, new GuiLabel().setI18nText("replaymod.gui.rendersettings.antialiasing"), antiAliasingDropdown)); public final GuiTextField exportCommand = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.command") @@ -289,6 +303,11 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { } protected void updateInputs() { + RenderSettings.RenderMethod renderMethod = renderMethodDropdown.getSelectedValue(); + + // Enable/Disable video with input + videoWidth.setEnabled(!renderMethod.hasFixedAspectRatio()); + // Validate video width and height String error = isResolutionValid(); if (error == null) { @@ -311,7 +330,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { } // Enable/Disable camera stabilization checkboxes - switch (renderMethodDropdown.getSelectedValue()) { + switch (renderMethod) { case CUBIC: case EQUIRECTANGULAR: case ODS: @@ -321,10 +340,12 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { stabilizePanel.forEach(IGuiCheckbox.class).setDisabled(); } + // Enable/Disable Spherical FOV slider + sphericalFovSlider.setEnabled(renderMethod.isSpherical()); + // Enable/Disable inject metadata checkbox if (encodingPresetDropdown.getSelectedValue().getFileExtension().equals("mp4") - && (renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.EQUIRECTANGULAR - || renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.ODS)) { + && renderMethod.isSpherical()) { inject360Metadata.setEnabled().setTooltip(null); } else { inject360Metadata.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED) @@ -335,33 +356,72 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { protected String isResolutionValid() { RenderSettings.EncodingPreset preset = encodingPresetDropdown.getSelectedValue(); RenderSettings.RenderMethod method = renderMethodDropdown.getSelectedValue(); - int videoWidth = this.videoWidth.getInteger(); + int videoHeight = this.videoHeight.getInteger(); + int videoWidth; + if (method.hasFixedAspectRatio()) { + // cubic rendering requires an aspect ratio of 4:3, + // therefore the height must be divisible by 3 + if (method == RenderSettings.RenderMethod.CUBIC && videoHeight % 3 != 0) { + return "replaymod.gui.rendersettings.customresolution.warning.cubic.height"; + } + + int sphericalFov = minSphericalFov + sphericalFovSlider.getValue() * sphericalFovSteps; + videoWidth = videoWidthForHeight(method, videoHeight, sphericalFov, sphericalFov); + + this.videoWidth.setValue(videoWidth); // TODO: isResolutionValid should not have side-effects + } else { + videoWidth = this.videoWidth.getInteger(); + } // Make sure the export arguments haven't been changed manually if (exportArguments.getText().equals(preset.getValue())) { // Yuv420 requires both dimensions to be even if (preset.isYuv420() && (videoWidth % 2 != 0 || videoHeight % 2 != 0)) { + + if (method == RenderSettings.RenderMethod.CUBIC) { + // cubic yuv rendering has the special case that the height must be + // divisible by both 3 and 2 - tell the user about it with a special message + return "replaymod.gui.rendersettings.customresolution.warning.yuv420.cubic"; + } + return "replaymod.gui.rendersettings.customresolution.warning.yuv420"; } } - if (method == RenderSettings.RenderMethod.CUBIC - && (videoWidth * 3 / 4 != videoHeight || videoWidth * 3 % 4 != 0)) { - return "replaymod.gui.rendersettings.customresolution.warning.cubic"; - } - if (method == RenderSettings.RenderMethod.EQUIRECTANGULAR - && (videoWidth / 2 != videoHeight || videoWidth % 2 != 0)) { - return "replaymod.gui.rendersettings.customresolution.warning.equirectangular"; - } - if (method == RenderSettings.RenderMethod.ODS - && videoWidth != videoHeight) { - return "replaymod.gui.rendersettings.customresolution.warning.ods"; - } return null; } + protected int videoWidthForHeight(RenderSettings.RenderMethod method, int height, + int sphericalFovX, int sphericalFovY) { + if (method.isSpherical()) { + if (sphericalFovY < 180) { + // calculate the non-cropped height of the video + height = Math.round(height * 180 / (float) sphericalFovY); + } + + int width = height * 2; + + if (sphericalFovX < 360) { + // crop the resulting width + width = Math.round(width * (float) sphericalFovX / 360); + } + + if (method == RenderSettings.RenderMethod.ODS) { + width = Math.round(width / 2f); + } + + return width; + + } else if (method == RenderSettings.RenderMethod.CUBIC) { + Preconditions.checkArgument(height % 3 == 0); + return height / 3 * 4; + } + + throw new IllegalArgumentException(); + } + public void load(RenderSettings settings) { renderMethodDropdown.setSelected(settings.getRenderMethod()); encodingPresetDropdown.setSelected(settings.getEncodingPreset()); @@ -397,7 +457,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { chromaKeyingCheckbox.setChecked(true); chromaKeyingColor.setColor(settings.getChromaKeyingColor()); } - inject360Metadata.setChecked(settings.isInject360Metadata()); + sphericalFovSlider.setValue((settings.getSphericalFovX() - minSphericalFov) / sphericalFovSteps); + inject360Metadata.setChecked(settings.isInjectSphericalMetadata()); antiAliasingDropdown.setSelected(settings.getAntiAliasing()); exportCommand.setText(settings.getExportCommand()); exportArguments.setText(settings.getExportArguments()); @@ -406,6 +467,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { } public RenderSettings save(boolean serialize) { + int sphericalFov = minSphericalFov + sphericalFovSlider.getValue() * sphericalFovSteps; + return new RenderSettings( renderMethodDropdown.getSelectedValue(), encodingPresetDropdown.getSelectedValue(), @@ -419,6 +482,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()), stabilizeRoll.isChecked() && (serialize || stabilizeRoll.isEnabled()), chromaKeyingCheckbox.isChecked() ? chromaKeyingColor.getColor() : null, + sphericalFov, sphericalFov, inject360Metadata.isChecked() && (serialize || inject360Metadata.isEnabled()), antiAliasingDropdown.getSelectedValue(), exportCommand.getText(), @@ -435,7 +499,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { private RenderSettings getDefaultRenderSettings() { return new RenderSettings(RenderSettings.RenderMethod.DEFAULT, RenderSettings.EncodingPreset.MP4_DEFAULT, 1920, 1080, 60, 10 << 20, null, - true, false, false, false, null, false, RenderSettings.AntiAliasing.NONE, "", RenderSettings.EncodingPreset.MP4_DEFAULT.getValue(), false); + true, false, false, false, null, 360, 180, false, RenderSettings.AntiAliasing.NONE, "", RenderSettings.EncodingPreset.MP4_DEFAULT.getValue(), false); } @Override diff --git a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java index 1b8d76ba..16d9017e 100644 --- a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java +++ b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java @@ -4,8 +4,10 @@ import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.*; import com.google.common.primitives.Bytes; import com.googlecode.mp4parser.BasicContainer; +import com.replaymod.render.RenderSettings; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.lwjgl.util.Dimension; import java.io.File; import java.io.FileOutputStream; @@ -30,12 +32,20 @@ public class MetadataInjector { "true " + ""+STITCHING_SOFTWARE+" " + "equirectangular "; + private static final String SPHERICAL_CROP_XML = + "%d " + + "%d " + + "%d " + + "%d " + + "%d " + + "%d "; private static final String STEREO_XML_CONTENTS = "top-bottom"; private static final String SPHERICAL_XML_FOOTER = ""; - private static final String XML_360_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS + SPHERICAL_XML_FOOTER; - private static final String XML_ODS_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS - + STEREO_XML_CONTENTS + SPHERICAL_XML_FOOTER; + private static final String XML_MONO_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS + + SPHERICAL_CROP_XML + SPHERICAL_XML_FOOTER; + private static final String XML_STEREO_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS + + SPHERICAL_CROP_XML + STEREO_XML_CONTENTS + SPHERICAL_XML_FOOTER; /** * These bytes are taken from the variable 'spherical_uuid_id' @@ -48,18 +58,43 @@ public class MetadataInjector { (byte)0x02, (byte)0x52, (byte)0x1f, (byte)0xdd }; - private static final byte[] BYTES_360_METADATA = Bytes.concat(UUID_BYTES, XML_360_METADATA.getBytes()); - private static final byte[] BYTES_ODS_METADATA = Bytes.concat(UUID_BYTES, XML_ODS_METADATA.getBytes()); + public static void injectMetadata(RenderSettings.RenderMethod renderMethod, File videoFile, + int videoWidth, int videoHeight, + int sphericalFovX, int sphericalFovY) { + String xmlString; + switch (renderMethod) { + case EQUIRECTANGULAR: + xmlString = XML_MONO_METADATA; + break; + case ODS: + xmlString = XML_STEREO_METADATA; + break; + default: + throw new IllegalArgumentException("Invalid render method"); + } - public static void inject360Metadata(File videoFile) { - writeMetadata(videoFile, BYTES_360_METADATA); + Dimension original = getOriginalDimensions(videoWidth, videoHeight, sphericalFovX, sphericalFovY); + writeMetadata(videoFile, String.format(xmlString, + original.getWidth(), original.getHeight(), videoWidth, videoHeight, + (original.getWidth() - videoWidth) / 2, (original.getHeight() - videoHeight) / 2)); } - public static void injectODSMetadata(File videoFile) { - writeMetadata(videoFile, BYTES_ODS_METADATA); + private static Dimension getOriginalDimensions(int videoWidth, int videoHeight, + int sphericalFovX, int sphericalFovY) { + if (sphericalFovX < 360) { + videoWidth = Math.round(videoWidth * 360 / (float) sphericalFovX); + } + + if (sphericalFovY < 180) { + videoHeight = Math.round(videoHeight * 180 / (float) sphericalFovY); + } + + return new Dimension(videoWidth, videoHeight); } - private static void writeMetadata(File videoFile, byte[] metadata) { + private static void writeMetadata(File videoFile, String metadata) { + byte[] bytes = Bytes.concat(UUID_BYTES, metadata.getBytes()); + File tempFile = null; FileOutputStream videoFileOutputStream = null; IsoFile tempIsoFile = null; @@ -80,7 +115,7 @@ public class MetadataInjector { //create a new UserBox, which actually contains the Metadata bytes UserBox metadataBox = new UserBox(new byte[0]); - metadataBox.setData(metadata); + metadataBox.setData(bytes); //add the Metadata UserBox to the Movie Track trackBox.addBox(metadataBox); @@ -97,7 +132,7 @@ public class MetadataInjector { videoFileOutputStream = new FileOutputStream(videoFile); tempIsoFile.getBox(videoFileOutputStream.getChannel()); } catch(Exception e) { - LOGGER.error("360 Degree Metadata couldn't be injected", e); + LOGGER.error("Spherical Metadata couldn't be injected", e); } finally { IOUtils.closeQuietly(tempIsoFile); IOUtils.closeQuietly(videoFileOutputStream); diff --git a/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java index 4d19bc11..b027b977 100644 --- a/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java @@ -3,6 +3,7 @@ package com.replaymod.render.processor; import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.utils.ByteBufferPool; +import lombok.Getter; import org.apache.commons.lang3.Validate; import org.lwjgl.util.Dimension; @@ -18,6 +19,7 @@ public class EquirectangularToRGBProcessor extends AbstractFrameProcessor= 1) { @@ -74,9 +97,9 @@ public class EquirectangularToRGBProcessor extends AbstractFrameProcessor { private final EquirectangularToRGBProcessor processor; - public ODSToRGBProcessor(int frameSize) { - processor = new EquirectangularToRGBProcessor(frameSize); + public ODSToRGBProcessor(int outputWidth, int outputHeight, int sphericalFovX) { + processor = new EquirectangularToRGBProcessor(outputWidth, outputHeight / 2, sphericalFovX); } @Override @@ -34,4 +34,8 @@ public class ODSToRGBProcessor extends AbstractFrameProcessor newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer consumer) { RenderSettings settings = renderInfo.getRenderSettings(); + + EquirectangularToRGBProcessor processor = new EquirectangularToRGBProcessor(settings.getVideoWidth(), + settings.getVideoHeight(), settings.getSphericalFovX()); + FrameCapturer capturer; if (PixelBufferObject.SUPPORTED) { - capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(settings, renderInfo), renderInfo, settings.getVideoWidth() / 4); + capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(settings, renderInfo), renderInfo, processor.getFrameSize()); } else { - capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(settings, renderInfo), renderInfo, settings.getVideoWidth() / 4); + capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(settings, renderInfo), renderInfo, processor.getFrameSize()); } - return new Pipeline<>(capturer, new EquirectangularToRGBProcessor(settings.getVideoWidth() / 4), consumer); + return new Pipeline<>(capturer, processor, consumer); } public static Pipeline newODSPipeline(RenderInfo renderInfo, FrameConsumer consumer) { RenderSettings settings = renderInfo.getRenderSettings(); + + ODSToRGBProcessor processor = new ODSToRGBProcessor(settings.getVideoWidth(), + settings.getVideoHeight(), settings.getSphericalFovX()); + FrameCapturer capturer = - new ODSFrameCapturer(new EntityRendererHandler(settings, renderInfo), renderInfo, settings.getVideoWidth() / 4); - return new Pipeline<>(capturer, new ODSToRGBProcessor(settings.getVideoWidth() / 4), consumer); + new ODSFrameCapturer(new EntityRendererHandler(settings, renderInfo), renderInfo, processor.getFrameSize()); + return new Pipeline<>(capturer, processor, consumer); } } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 4ed6f223..9e16a538 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -22,6 +22,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.shader.Framebuffer; +import net.minecraft.util.ReportedException; import net.minecraft.util.Timer; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; @@ -150,12 +151,15 @@ public class VideoRenderer implements RenderInfo { renderingPipeline.run(); - if (settings.isInject360Metadata()) { - if (settings.getRenderMethod() == RenderSettings.RenderMethod.ODS) { - MetadataInjector.injectODSMetadata(settings.getOutputFile()); - } else { - MetadataInjector.inject360Metadata(settings.getOutputFile()); - } + if (mc.hasCrashed) { + setFailure(mc.crashReporter.getCrashCause()); + throw new ReportedException(mc.crashReporter); + } + + if (settings.isInjectSphericalMetadata()) { + MetadataInjector.injectMetadata(settings.getRenderMethod(), settings.getOutputFile(), + settings.getTargetVideoWidth(), settings.getTargetVideoHeight(), + settings.getSphericalFovX(), settings.getSphericalFovY()); } finish(); diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 692a6765..70c4d164 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 692a6765560bd96f02c9364798395914470c2c9e +Subproject commit 70c4d16435aab0c8d8028b3096b0aa2510ea9f31 From 7c3fcd4e421813a026c3787e91db305dcfd3127d Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Thu, 6 Dec 2018 14:18:55 +0100 Subject: [PATCH 17/65] Adjustments for consistency --- .../extras/youtube/YoutubeUploader.java | 2 +- .../render/gui/GuiRenderSettings.java | 40 +++++++++---------- .../render/rendering/VideoRenderer.java | 1 - src/main/resources/assets/replaymod/lang | 2 +- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java index 7abe58ce..9c73a933 100644 --- a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java +++ b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java @@ -147,7 +147,7 @@ public class YoutubeUploader { CommandLine commandLine = new CommandLine(settings.getExportCommandOrDefault()); commandLine.addArguments(args); - LOGGER.info("Re-encoding for metadata injection with {} {}", settings.getExportCommand(), args); + LOGGER.info("Re-encoding for metadata injection with {} {}", commandLine.getExecutable(), args); Process process = new ProcessBuilder(commandLine.toStrings()).directory(outputFile.getParentFile()).start(); final AtomicBoolean active = new AtomicBoolean(true); diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index a0a5ffc6..27b112f2 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -150,22 +150,22 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { .setI18nLabel("replaymod.gui.rendersettings.chromakey"); public final GuiColorPicker chromaKeyingColor = new GuiColorPicker().setSize(30, 15); - public final int minSphericalFov = 120; - public final int maxSphericalFov = 360; - public final int sphericalFovSteps = 5; + public static final int MIN_SPHERICAL_FOV = 120; + public static final int MAX_SPHERICAL_FOV = 360; + public static final int SPHERICAL_FOV_STEP_SIZE = 5; public final GuiSlider sphericalFovSlider = new GuiSlider() .onValueChanged(new Runnable() { @Override public void run() { sphericalFovSlider.setText(I18n.format("replaymod.gui.rendersettings.sphericalFov") - + ": " + (minSphericalFov + sphericalFovSlider.getValue() * sphericalFovSteps) + "°"); + + ": " + (MIN_SPHERICAL_FOV + sphericalFovSlider.getValue() * SPHERICAL_FOV_STEP_SIZE) + "°"); updateInputs(); } - }).setSize(200, 20).setSteps((maxSphericalFov - minSphericalFov) / sphericalFovSteps); + }).setSize(200, 20).setSteps((MAX_SPHERICAL_FOV - MIN_SPHERICAL_FOV) / SPHERICAL_FOV_STEP_SIZE); - public final GuiCheckbox inject360Metadata = new GuiCheckbox() - .setI18nLabel("replaymod.gui.rendersettings.360metadata"); + public final GuiCheckbox injectSphericalMetadata = new GuiCheckbox() + .setI18nLabel("replaymod.gui.rendersettings.sphericalmetadata"); public final GuiDropdownMenu antiAliasingDropdown = new GuiDropdownMenu() .setSize(200, 20).setValues(RenderSettings.AntiAliasing.values()).setSelected(RenderSettings.AntiAliasing.NONE); @@ -176,7 +176,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { .addElements(new GridLayout.Data(0, 0.5), new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, chromaKeyingCheckbox, chromaKeyingColor, - inject360Metadata, sphericalFovSlider, + injectSphericalMetadata, sphericalFovSlider, new GuiLabel().setI18nText("replaymod.gui.rendersettings.antialiasing"), antiAliasingDropdown)); public final GuiTextField exportCommand = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.command") @@ -309,7 +309,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { videoWidth.setEnabled(!renderMethod.hasFixedAspectRatio()); // Validate video width and height - String error = isResolutionValid(); + String error = updateResolution(); if (error == null) { renderButton.setEnabled().setTooltip(null); videoWidth.setTextColor(Colors.WHITE); @@ -346,14 +346,14 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { // Enable/Disable inject metadata checkbox if (encodingPresetDropdown.getSelectedValue().getFileExtension().equals("mp4") && renderMethod.isSpherical()) { - inject360Metadata.setEnabled().setTooltip(null); + injectSphericalMetadata.setEnabled().setTooltip(null); } else { - inject360Metadata.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED) - .setI18nText("replaymod.gui.rendersettings.360metadata.error")); + injectSphericalMetadata.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED) + .setI18nText("replaymod.gui.rendersettings.sphericalmetadata.error")); } } - protected String isResolutionValid() { + protected String updateResolution() { RenderSettings.EncodingPreset preset = encodingPresetDropdown.getSelectedValue(); RenderSettings.RenderMethod method = renderMethodDropdown.getSelectedValue(); @@ -366,10 +366,10 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { return "replaymod.gui.rendersettings.customresolution.warning.cubic.height"; } - int sphericalFov = minSphericalFov + sphericalFovSlider.getValue() * sphericalFovSteps; + int sphericalFov = MIN_SPHERICAL_FOV + sphericalFovSlider.getValue() * SPHERICAL_FOV_STEP_SIZE; videoWidth = videoWidthForHeight(method, videoHeight, sphericalFov, sphericalFov); - this.videoWidth.setValue(videoWidth); // TODO: isResolutionValid should not have side-effects + this.videoWidth.setValue(videoWidth); } else { videoWidth = this.videoWidth.getInteger(); } @@ -457,8 +457,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { chromaKeyingCheckbox.setChecked(true); chromaKeyingColor.setColor(settings.getChromaKeyingColor()); } - sphericalFovSlider.setValue((settings.getSphericalFovX() - minSphericalFov) / sphericalFovSteps); - inject360Metadata.setChecked(settings.isInjectSphericalMetadata()); + sphericalFovSlider.setValue((settings.getSphericalFovX() - MIN_SPHERICAL_FOV) / SPHERICAL_FOV_STEP_SIZE); + injectSphericalMetadata.setChecked(settings.isInjectSphericalMetadata()); antiAliasingDropdown.setSelected(settings.getAntiAliasing()); exportCommand.setText(settings.getExportCommand()); exportArguments.setText(settings.getExportArguments()); @@ -467,7 +467,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { } public RenderSettings save(boolean serialize) { - int sphericalFov = minSphericalFov + sphericalFovSlider.getValue() * sphericalFovSteps; + int sphericalFov = MIN_SPHERICAL_FOV + sphericalFovSlider.getValue() * SPHERICAL_FOV_STEP_SIZE; return new RenderSettings( renderMethodDropdown.getSelectedValue(), @@ -482,8 +482,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()), stabilizeRoll.isChecked() && (serialize || stabilizeRoll.isEnabled()), chromaKeyingCheckbox.isChecked() ? chromaKeyingColor.getColor() : null, - sphericalFov, sphericalFov, - inject360Metadata.isChecked() && (serialize || inject360Metadata.isEnabled()), + sphericalFov, Math.min(180, sphericalFov), + injectSphericalMetadata.isChecked() && (serialize || injectSphericalMetadata.isEnabled()), antiAliasingDropdown.getSelectedValue(), exportCommand.getText(), exportArguments.getText(), diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 9e16a538..2c0b321e 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -152,7 +152,6 @@ public class VideoRenderer implements RenderInfo { renderingPipeline.run(); if (mc.hasCrashed) { - setFailure(mc.crashReporter.getCrashCause()); throw new ReportedException(mc.crashReporter); } diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 70c4d164..24611f70 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 70c4d16435aab0c8d8028b3096b0aa2510ea9f31 +Subproject commit 24611f70b401ba24ca5d4f22d085fa78cc4567c8 From 2f44d5f3809896034f5174ff26467cce0023f6a3 Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Thu, 6 Dec 2018 14:47:29 +0100 Subject: [PATCH 18/65] GuiRenderSettings: Fix bitrate label --- src/main/java/com/replaymod/render/gui/GuiRenderSettings.java | 2 +- src/main/resources/assets/replaymod/lang | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index a07f801e..38ff42ae 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -128,7 +128,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { new GuiLabel().setI18nText("replaymod.gui.rendersettings.renderer"), renderMethodDropdown, new GuiLabel().setI18nText("replaymod.gui.rendersettings.presets"), encodingPresetDropdown, new GuiLabel().setI18nText("replaymod.gui.rendersettings.customresolution"), videoResolutionPanel, - new GuiLabel().setI18nText("replaymod.gui.settings.bitrate"), new GuiPanel().addElements(null, + new GuiLabel().setI18nText("replaymod.gui.rendersettings.bitrate"), new GuiPanel().addElements(null, new GuiPanel().addElements(null, bitRateField, bitRateUnit).setLayout(new HorizontalLayout()), frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)), new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton) diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 692a6765..4bf10790 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 692a6765560bd96f02c9364798395914470c2c9e +Subproject commit 4bf107908d2a20442a587a87015ac3a2d2f08013 From d791eedae97149c76e88b34998515576db114487 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 17 Dec 2018 13:28:28 +0100 Subject: [PATCH 19/65] Replace dropdown replay selection with popup in ReplayEditor (fixes #132) --- jGui | 2 +- .../replaymod/editor/gui/GuiTrimPanel.java | 68 +++--- .../replay/gui/screen/GuiReplayViewer.java | 201 +++++++++++++----- 3 files changed, 188 insertions(+), 83 deletions(-) diff --git a/jGui b/jGui index 213ac136..43a58160 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 213ac13627178f49ce9671b29b6a542cb0e4c922 +Subproject commit 43a581607508c42d2ad7f67deaffb49cf620cfbe diff --git a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java index 3947214d..4b15de62 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java +++ b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java @@ -1,7 +1,10 @@ package com.replaymod.editor.gui; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.gson.JsonObject; import com.replaymod.core.utils.Utils; +import com.replaymod.replay.gui.screen.GuiReplayViewer; import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.filter.ChangeTimestampFilter; import com.replaymod.replaystudio.filter.RemoveFilter; @@ -12,6 +15,7 @@ import com.replaymod.replaystudio.replay.ZipReplayFile; import com.replaymod.replaystudio.stream.PacketStream; import com.replaymod.replaystudio.studio.ReplayStudio; import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.element.GuiButton; import de.johni0702.minecraft.gui.element.GuiLabel; import de.johni0702.minecraft.gui.element.GuiNumberField; import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu; @@ -20,13 +24,11 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; -import org.apache.commons.io.IOCase; -import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.util.Dimension; +import javax.annotation.Nullable; import java.io.File; -import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -50,9 +52,10 @@ public class GuiTrimPanel extends GuiPanel { private final GuiReplayEditor gui; - public final GuiDropdownMenu inputReplays = new GuiDropdownMenu(this) - .setMinSize(new Dimension(200, 20)).onSelection(i -> updateSelectedReplay()) - .setToString(f -> f == NO_REPLAY ? "" : Utils.fileNameToReplayName(f.getName())); + private final GuiButton inputReplayButton = new GuiButton(this) + .setMinSize(new Dimension(200, 20)) + .setMaxSize(new Dimension(350, 20)); + private File inputReplay = NO_REPLAY; public final GuiNumberField startHour = newGuiNumberField(); public final GuiNumberField startMin = newGuiNumberField(); @@ -104,24 +107,31 @@ public class GuiTrimPanel extends GuiPanel { endMarker.setToString(toString).setMinSize(new Dimension(100, 20)).onSelection(i -> onSelectedMarkerChanged(endMarker, endHour, endMin, endSec, endMilli)); - File[] files = null; - try { - File folder = gui.getMod().getReplayFolder(); - files = folder.listFiles((FileFilter) new SuffixFileFilter(".mcpr", IOCase.INSENSITIVE)); - } catch (IOException e) { - Utils.error(LOGGER, gui, CrashReport.makeCrashReport(e, "Listing available replays"), gui.backButton::onClick); - } - if (files == null) { - LOGGER.warn("Replay file list is null, has the replay folder been deleted?"); - files = new File[0]; - } else { - LOGGER.debug("Found {} replays in the replay folder", files.length); - } - if (files.length == 0) { - inputReplays.setValues(NO_REPLAY); - } else { - inputReplays.setValues(files); - } + inputReplayButton.onClick(() -> { + File folder; + try { + folder = gui.getMod().getReplayFolder(); + } catch (IOException e) { + Utils.error(LOGGER, gui, CrashReport.makeCrashReport(e, "Getting replay folder"), gui.backButton::onClick); + return; + } + GuiReplayViewer.GuiSelectReplayPopup popup = GuiReplayViewer.GuiSelectReplayPopup.openGui(gui, folder); + Futures.addCallback(popup.getFuture(), new FutureCallback() { + @Override + public void onSuccess(@Nullable File result) { + if (result != null) { + inputReplay = result; + updateSelectedReplay(); + updateReadyState(); + } + } + + @Override + public void onFailure(Throwable t) { + Utils.error(LOGGER, gui, CrashReport.makeCrashReport(t, "Selecting replay"), gui.backButton::onClick); + } + }); + }); updateSelectedReplay(); updateReadyState(); @@ -135,7 +145,7 @@ public class GuiTrimPanel extends GuiPanel { JsonObject config = new JsonObject(); config.addProperty("offset", -start); // Pass filters to save dialog - gui.save(inputReplays.getSelectedValue(), + gui.save(inputReplay, Pair.of(new PacketStream.FilterInfo(new SquashFilter(), -1, start), new JsonObject()), Pair.of(new PacketStream.FilterInfo(ctf, start, end), config), Pair.of(new PacketStream.FilterInfo(new RemoveFilter(), end, -1), new JsonObject()) @@ -161,7 +171,11 @@ public class GuiTrimPanel extends GuiPanel { } private void updateSelectedReplay() { - File file = inputReplays.getSelectedValue(); + File file = inputReplay; + + // Update input button label + inputReplayButton.setLabel(file == NO_REPLAY ? I18n.format("gui.none") : Utils.fileNameToReplayName(file.getName())); + // Load markers and meta data from replay file ReplayMetaData metaData; Set markers; @@ -196,6 +210,6 @@ public class GuiTrimPanel extends GuiPanel { } private void updateReadyState() { - gui.saveButton.setEnabled(inputReplays.getSelectedValue() != NO_REPLAY); + gui.saveButton.setEnabled(inputReplay != NO_REPLAY); } } diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index cd83f75a..388714ed 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -4,6 +4,7 @@ import com.google.common.base.Optional; import com.google.common.base.Supplier; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.SettableFuture; import com.mojang.realmsclient.gui.ChatFormatting; import com.replaymod.core.ReplayMod; import com.replaymod.core.SettingsRegistry; @@ -20,17 +21,21 @@ import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiScreen; import de.johni0702.minecraft.gui.element.*; -import de.johni0702.minecraft.gui.element.advanced.GuiResourceLoadingList; +import de.johni0702.minecraft.gui.element.advanced.AbstractGuiResourceLoadingList; import de.johni0702.minecraft.gui.function.Typeable; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; +import lombok.Getter; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.resources.I18n; +import net.minecraft.crash.CrashReport; +import net.minecraft.util.ReportedException; import net.minecraft.util.Util; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOCase; @@ -53,10 +58,10 @@ import java.util.Date; import static com.replaymod.replay.ReplayModReplay.LOGGER; -public class GuiReplayViewer extends GuiScreen implements Typeable { +public class GuiReplayViewer extends GuiScreen { private final ReplayModReplay mod; - public final GuiResourceLoadingList list = new GuiResourceLoadingList(this).onSelectionChanged(new Runnable() { + public final GuiReplayList list = new GuiReplayList(this).onSelectionChanged(new Runnable() { @Override public void run() { replayButtonPanel.forEach(IGuiButton.class).setEnabled(list.getSelected() != null); @@ -64,53 +69,13 @@ public class GuiReplayViewer extends GuiScreen implements Typeable { loadButton.setDisabled(); } } - }).onLoad(new Consumer>> () { - @Override - public void consume(Consumer> obj) { - try { - File folder = mod.getCore().getReplayFolder(); - for (final File file : folder.listFiles((FileFilter) new SuffixFileFilter(".mcpr", IOCase.INSENSITIVE))) { - if (Thread.interrupted()) break; - try (ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), file)) { - - Optional thumb = replayFile.getThumb(); - // Make sure that to int[] conversion doesn't have to occur in main thread - final BufferedImage theThumb; - if (thumb.isPresent()) { - BufferedImage buf = thumb.get(); - // This is the same way minecraft calls this method, we cache the result and hand - // minecraft a BufferedImage with way simpler logic using the precomputed values - final int[] theIntArray = buf.getRGB(0, 0, buf.getWidth(), buf.getHeight(), null, 0, buf.getWidth()); - theThumb = new BufferedImage(buf.getWidth(), buf.getHeight(), BufferedImage.TYPE_INT_ARGB) { - @Override - public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) { - System.arraycopy(theIntArray, 0, rgbArray, 0, theIntArray.length); - return null; // Minecraft doesn't use the return value - } - }; - } else { - theThumb = null; - } - final ReplayMetaData metaData = replayFile.getMetaData(); - - if (metaData != null) { - obj.consume(() -> new GuiReplayEntry(file, metaData, theThumb)); - } - } catch (Exception e) { - LOGGER.error("Could not load Replay File {}", file.getName(), e); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } }).onSelectionDoubleClicked(() -> { if (this.loadButton.isEnabled()) { this.loadButton.onClick(); // Disable load button to prevent the player from opening the replay twice at the same time this.loadButton.setDisabled(); } - }).setDrawShadow(true).setDrawSlider(true); + }); public final GuiButton loadButton = new GuiButton().onClick(new Runnable() { @Override @@ -264,6 +229,12 @@ public class GuiReplayViewer extends GuiScreen implements Typeable { public GuiReplayViewer(ReplayModReplay mod) { this.mod = mod; + try { + list.setFolder(mod.getCore().getReplayFolder()); + } catch (IOException e) { + throw new ReportedException(CrashReport.makeCrashReport(e, "Getting replay folder")); + } + setTitle(new GuiLabel().setI18nText("replaymod.gui.replayviewer")); setLayout(new CustomLayout() { @@ -277,20 +248,140 @@ public class GuiReplayViewer extends GuiScreen implements Typeable { }); } - private final GuiImage defaultThumbnail = new GuiImage().setTexture(Utils.DEFAULT_THUMBNAIL); + private static final GuiImage DEFAULT_THUMBNAIL = new GuiImage().setTexture(Utils.DEFAULT_THUMBNAIL); - @Override - public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) { - if (keyCode == Keyboard.KEY_F1) { - SettingsRegistry reg = ReplayMod.instance.getSettingsRegistry(); - reg.set(Setting.SHOW_SERVER_IPS, !reg.get(Setting.SHOW_SERVER_IPS)); - reg.save(); - list.load(); + public static class GuiSelectReplayPopup extends AbstractGuiPopup { + public static GuiSelectReplayPopup openGui(GuiContainer container, File folder) { + GuiSelectReplayPopup popup = new GuiSelectReplayPopup(container, folder); + popup.list.load(); + popup.open(); + return popup; + } + + @Getter + private final SettableFuture future = SettableFuture.create(); + + @Getter + private final GuiReplayList list = new GuiReplayList(popup); + + @Getter + private final GuiButton acceptButton = new GuiButton(popup).setI18nLabel("gui.done").setSize(50, 20).setDisabled(); + + @Getter + private final GuiButton cancelButton = new GuiButton(popup).setI18nLabel("gui.cancel").setSize(50, 20); + + + public GuiSelectReplayPopup(GuiContainer container, File folder) { + super(container); + + list.setFolder(folder); + + list.onSelectionChanged(() -> { + acceptButton.setEnabled(list.getSelected() != null); + }).onSelectionDoubleClicked(() -> { + close(); + future.set(list.getSelected().file); + }); + acceptButton.onClick(() -> { + future.set(list.getSelected().file); + close(); + }); + cancelButton.onClick(() -> { + future.set(null); + close(); + }); + + popup.setLayout(new CustomLayout() { + @Override + protected void layout(GuiPanel container, int width, int height) { + pos(cancelButton, width - width(cancelButton), height - height(cancelButton)); + pos(acceptButton, x(cancelButton) - 5 - width(acceptButton), y(cancelButton)); + pos(list, 0, 5); + size(list, width, height - height(cancelButton) - 10); + } + + @Override + public ReadableDimension calcMinSize(GuiContainer container) { + return new Dimension(330, 200); + } + }); + } + + @Override + protected GuiSelectReplayPopup getThis() { + return this; } - return false; } - public class GuiReplayEntry extends AbstractGuiContainer implements Comparable { + public static class GuiReplayList extends AbstractGuiResourceLoadingList implements Typeable { + private File folder = null; + + public GuiReplayList(GuiContainer container) { + super(container); + } + + { + onLoad((Consumer> results) -> { + File[] files = folder.listFiles((FileFilter) new SuffixFileFilter(".mcpr", IOCase.INSENSITIVE)); + if (files == null) { + LOGGER.warn("Failed to list files in {}", folder); + return; + } + for (final File file : files) { + if (Thread.interrupted()) break; + try (ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), file)) { + Optional thumb = replayFile.getThumb(); + // Make sure that to int[] conversion doesn't have to occur in main thread + final BufferedImage theThumb; + if (thumb.isPresent()) { + BufferedImage buf = thumb.get(); + // This is the same way minecraft calls this method, we cache the result and hand + // minecraft a BufferedImage with way simpler logic using the precomputed values + final int[] theIntArray = buf.getRGB(0, 0, buf.getWidth(), buf.getHeight(), null, 0, buf.getWidth()); + theThumb = new BufferedImage(buf.getWidth(), buf.getHeight(), BufferedImage.TYPE_INT_ARGB) { + @Override + public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) { + System.arraycopy(theIntArray, 0, rgbArray, 0, theIntArray.length); + return null; // Minecraft doesn't use the return value + } + }; + } else { + theThumb = null; + } + final ReplayMetaData metaData = replayFile.getMetaData(); + + if (metaData != null) { + results.consume(() -> new GuiReplayEntry(file, metaData, theThumb)); + } + } catch (Exception e) { + LOGGER.error("Could not load Replay File {}", file.getName(), e); + } + } + }).setDrawShadow(true).setDrawSlider(true); + } + + public void setFolder(File folder) { + this.folder = folder; + } + + @Override + public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) { + if (keyCode == Keyboard.KEY_F1) { + SettingsRegistry reg = ReplayMod.instance.getSettingsRegistry(); + reg.set(Setting.SHOW_SERVER_IPS, !reg.get(Setting.SHOW_SERVER_IPS)); + reg.save(); + load(); + } + return false; + } + + @Override + protected GuiReplayList getThis() { + return this; + } + } + + public static class GuiReplayEntry extends AbstractGuiContainer implements Comparable { public final File file; public final GuiLabel name = new GuiLabel(); public final GuiLabel server = new GuiLabel().setColor(Colors.LIGHT_GRAY); @@ -334,7 +425,7 @@ public class GuiReplayViewer extends GuiScreen implements Typeable { dateMillis = metaData.getDate(); date.setText(new SimpleDateFormat().format(new Date(dateMillis))); if (thumbImage == null) { - thumbnail = new GuiImage(defaultThumbnail).setSize(30 * 16 / 9, 30); + thumbnail = new GuiImage(DEFAULT_THUMBNAIL).setSize(30 * 16 / 9, 30); addElements(null, thumbnail); } else { thumbnail = new GuiImage(this).setTexture(thumbImage).setSize(30 * 16 / 9, 30); From c94f5d17f4f080a4576d1fc07b45fa5e1635ba37 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 17 Dec 2018 14:42:03 +0100 Subject: [PATCH 20/65] Fix packet data being trashed by Restore Replay GUI (fixes #129) --- src/main/java/com/replaymod/core/gui/RestoreReplayGui.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java index d922c1e4..62833344 100644 --- a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java +++ b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java @@ -43,6 +43,9 @@ public class RestoreReplayGui extends AbstractGuiScreen { yesButton.onClick(() -> { try { ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), null, file); + // Commit all not-yet-committed files into the main zip file. + // If we don't do this, then re-writing packet data below can actually overwrite uncommitted packet data! + replayFile.save(); ReplayMetaData metaData = replayFile.getMetaData(); if (metaData != null && metaData.getDuration() == 0) { // Try to restore replay duration From 3319d4263c0d4792d97f3d02f5e20a5c722283d9 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 17 Dec 2018 15:01:01 +0100 Subject: [PATCH 21/65] Ensure replay has ended when MP GUI is opened (fixes #134) --- .../com/replaymod/replay/handler/GuiHandler.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java index 4a98b72b..fd1116b4 100644 --- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java @@ -6,6 +6,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiIngameMenu; import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.GuiMultiplayer; import net.minecraft.client.resources.I18n; import net.minecraftforge.client.event.GuiScreenEvent; @@ -102,8 +103,8 @@ public class GuiHandler { } @SubscribeEvent - public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) { - if (!(getGui(event) instanceof GuiMainMenu)) { + public void ensureReplayStopped(GuiScreenEvent.InitGuiEvent event) { + if (!(getGui(event) instanceof GuiMainMenu || getGui(event) instanceof GuiMultiplayer)) { return; } @@ -120,7 +121,14 @@ public class GuiHandler { } } } + } + + @SubscribeEvent + public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) { + if (!(getGui(event) instanceof GuiMainMenu)) { + return; + } GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, getGui(event).width / 2 - 100, getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer")); button.width = button.width / 2 - 2; From c8ecd17fe9b8a0e558125b0c28ceed7a7dcde2b9 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 09:49:48 +0100 Subject: [PATCH 22/65] Re-write QuickReplaySender caching to be significantly more memory efficient --- .../replaymod/replay/QuickReplaySender.java | 464 +++++++++++------- 1 file changed, 293 insertions(+), 171 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 31b376cf..51a12bf0 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -32,6 +32,8 @@ import com.github.steveice10.packetlib.io.NetInput; import com.github.steveice10.packetlib.io.NetOutput; import com.github.steveice10.packetlib.io.stream.StreamNetInput; import com.github.steveice10.packetlib.io.stream.StreamNetOutput; +import com.github.steveice10.packetlib.tcp.io.ByteBufNetInput; +import com.github.steveice10.packetlib.tcp.io.ByteBufNetOutput; import com.google.common.base.Optional; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; @@ -53,6 +55,7 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; +import lombok.SneakyThrows; import net.minecraft.client.Minecraft; import net.minecraft.network.EnumConnectionState; import net.minecraft.network.EnumPacketDirection; @@ -95,6 +98,7 @@ import static com.replaymod.replay.ReplayModReplay.LOGGER; @ChannelHandler.Sharable public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySender { private static final String CACHE_ENTRY = "quickModeCache.bin"; + private static final String CACHE_INDEX_ENTRY = "quickModeCacheIndex.bin"; private static final int CACHE_VERSION = 0; private final Minecraft mc = Minecraft.getMinecraft(); @@ -115,6 +119,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private ListenableFuture initPromise; + private com.github.steveice10.netty.buffer.ByteBuf buf; + private NetInput bufInput; private TreeMap> thingSpawnsT = new TreeMap<>(); private ListMultimap thingSpawns = Multimaps.newListMultimap(thingSpawnsT, ArrayList::new); private TreeMap> thingDespawnsT = new TreeMap<>(); @@ -207,7 +213,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe public void setReplaySpeed(double factor) { if (factor != 0) { if (paused() && asyncMode) { - lastAsyncUpdateTime = System.currentTimeMillis(); // TODO test this + lastAsyncUpdateTime = System.currentTimeMillis(); } this.replaySpeed = factor; } @@ -263,49 +269,63 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } private boolean tryLoadFromCache(Consumer progress) throws IOException { - Optional cacheOpt = replayFile.get(CACHE_ENTRY); - if (!cacheOpt.isPresent()) return false; - boolean success = loadFromCache(cacheOpt.get(), progress); - if (!success) { - thingSpawnsT.clear(); - thingDespawnsT.clear(); - worldTimes.clear(); - thunderStrengths.clear(); - } - return success; - } + boolean success = false; - private boolean loadFromCache(InputStream rawIn, Consumer progress) throws IOException { - long sysTimeStart = System.currentTimeMillis(); - NetInput in = new StreamNetInput(rawIn); - int version = in.readVarInt(); - if (version > CACHE_VERSION) return false; // More recent than this version, no chance we can read that - - int size = in.readVarInt(); - int spawnTime = 0; - for (int i = 0; i < size; i++) { - progress.accept((double) i / size); - spawnTime += in.readVarInt(); - for (int j = in.readVarInt(); j > 0; j--) { - int despawnTime = in.readVarInt(); - BakedTrackedThing trackedThing; - switch (in.readVarInt()) { - case 0: trackedThing = new BakedEntity(version, in); break; - case 1: trackedThing = new BakedChunk(version, in); break; - case 2: trackedThing = new BakedWeather(version, in); break; - default: return false; - } - trackedThing.spawnTime = spawnTime; - trackedThing.despawnTime = despawnTime; - thingSpawns.put(spawnTime, trackedThing); - thingDespawns.put(despawnTime, trackedThing); + Optional cacheIndexOpt = replayFile.get(CACHE_INDEX_ENTRY); + if (!cacheIndexOpt.isPresent()) return false; + try (InputStream indexIn = cacheIndexOpt.get()) { + Optional cacheOpt = replayFile.get(CACHE_ENTRY); + if (!cacheOpt.isPresent()) return false; + try (InputStream cacheIn = cacheOpt.get()) { + success = loadFromCache(cacheIn, indexIn, progress); + } + } finally { + if (!success) { + buf = null; + bufInput = null; + thingSpawnsT.clear(); + thingDespawnsT.clear(); + worldTimes.clear(); + thunderStrengths.clear(); } } - // These should go quick, they probably won't need to update progress - progress.accept(1.0); - readFromCache(version, in, worldTimes); - readFromCache(version, in, thunderStrengths); + return success; + } + + private boolean loadFromCache(InputStream cacheIn, InputStream rawIndexIn, Consumer progress) throws IOException { + long sysTimeStart = System.currentTimeMillis(); + + NetInput in = new StreamNetInput(rawIndexIn); + if (in.readVarInt() != CACHE_VERSION) return false; // Incompatible cache version + if (new StreamNetInput(cacheIn).readVarInt() != CACHE_VERSION) return false; // Incompatible cache version + + things: while (true) { + BakedTrackedThing trackedThing; + switch (in.readVarInt()) { + case 0: break things; + case 1: trackedThing = new BakedEntity(in); break; + case 2: trackedThing = new BakedChunk(in); break; + case 3: trackedThing = new BakedWeather(in); break; + default: return false; + } + thingSpawns.put(trackedThing.spawnTime, trackedThing); + thingDespawns.put(trackedThing.despawnTime, trackedThing); + } + + readFromCache(in, worldTimes); + readFromCache(in, thunderStrengths); + int size = in.readVarInt(); + + buf = com.github.steveice10.netty.buffer.Unpooled.buffer(size); + int read = 0; + while (true) { + int len = buf.writeBytes(cacheIn, Math.min(size - read, 4096)); + if (len <= 0) break; + read += len; + progress.accept((double) read / size); + } + bufInput = new ByteBufNetInput(buf); LOGGER.info("Loaded quick replay from cache in " + (System.currentTimeMillis() - sysTimeStart) + "ms"); return true; @@ -327,8 +347,6 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe studio.setParsing(ServerUpdateTimePacket.class, true); studio.setParsing(ServerNotifyClientPacket.class, true); - TreeMap> thingSpawnsT = new TreeMap<>(); - ListMultimap thingSpawns = Multimaps.newListMultimap(thingSpawnsT, ArrayList::new); TreeMap worldTimes = new TreeMap<>(); TreeMap thunderStrengths = new TreeMap<>(); Map playerListEntries = new HashMap<>(); @@ -336,33 +354,33 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe Map activeChunks = new HashMap<>(); Weather activeWeather = null; - // 90% of estimated time are for analysis, 10% for writing cache - // This heuristic is based on simply running the function on some 3h replay. - // Numbers may be significantly different for smaller replays where GC doesn't kick in. But the progress bar - // will hopefully be barely visible in that case anyway. - // May need to be updated when performance of MCProtocolLib or this code is improved. - // Same for the split between analyseReplay and loadFromCache declared in #initialize(...) - final double progressSplit = 0.9; - double sysTimeStart = System.currentTimeMillis(); double duration; - try (ReplayInputStream in = replayFile.getPacketData(studio)) { + try (ReplayInputStream in = replayFile.getPacketData(studio); + OutputStream cacheOut = replayFile.write(CACHE_ENTRY); + OutputStream cacheIndexOut = replayFile.write(CACHE_INDEX_ENTRY)) { + NetOutput out = new StreamNetOutput(cacheOut); + out.writeVarInt(CACHE_VERSION); + NetOutput indexOut = new StreamNetOutput(cacheIndexOut); + indexOut.writeVarInt(CACHE_VERSION); + + int index = 0; + int time = 0; duration = replayFile.getMetaData().getDuration(); PacketData packetData; while ((packetData = in.readPacket()) != null) { com.github.steveice10.packetlib.packet.Packet packet = packetData.getPacket(); - int time = (int) packetData.getTime(); - progress.accept(time / duration * progressSplit); + time = (int) packetData.getTime(); + progress.accept(time / duration); Integer entityId = PacketUtils.getEntityId(packet); if (packet instanceof ServerSpawnMobPacket || packet instanceof ServerSpawnObjectPacket || packet instanceof ServerSpawnPaintingPacket) { Entity entity = new Entity(entityId, Collections.singletonList(packet)); entity.spawnTime = time; - thingSpawns.put(time, entity); Entity prev = activeEntities.put(entityId, entity); if (prev != null) { - prev.despawnTime = time; + index = prev.writeToCache(indexOut, out, time, index); } } else if (packet instanceof ServerSpawnPlayerPacket) { ServerPlayerListEntryPacket listEntryPacket = new ServerPlayerListEntryPacket( @@ -373,34 +391,30 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe ); Entity entity = new Entity(entityId, Arrays.asList(listEntryPacket, packet)); entity.spawnTime = time; - thingSpawns.put(time, entity); Entity prev = activeEntities.put(entityId, entity); if (prev != null) { - prev.despawnTime = time; + index = prev.writeToCache(indexOut, out, time, index); } } else if (packet instanceof ServerEntityDestroyPacket) { for (int id : ((ServerEntityDestroyPacket) packet).getEntityIds()) { Entity entity = activeEntities.remove(id); if (entity != null) { - entity.despawnTime = time; + index = entity.writeToCache(indexOut, out, time, index); } } } else if (packet instanceof ServerChunkDataPacket) { Column column = ((ServerChunkDataPacket) packet).getColumn(); Chunk chunk = new Chunk(column); chunk.spawnTime = time; - thingSpawns.put(time, chunk); Chunk prev = activeChunks.put(coordToLong(column.getX(), column.getZ()), chunk); if (prev != null) { - prev.currentBlockState = null; // free memory because we no longer need it - prev.despawnTime = time; + index = prev.writeToCache(indexOut, out, time, index); } } else if (packet instanceof ServerUnloadChunkPacket) { ServerUnloadChunkPacket p = (ServerUnloadChunkPacket) packet; Chunk prev = activeChunks.remove(coordToLong(p.getX(), p.getZ())); if (prev != null) { - prev.currentBlockState = null; // free memory because we no longer need it - prev.despawnTime = time; + index = prev.writeToCache(indexOut, out, time, index); } } else if (packet instanceof ServerBlockChangePacket || packet instanceof ServerMultiBlockChangePacket) { for (BlockChangeRecord record : @@ -426,12 +440,16 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } } else if (packet instanceof ServerRespawnPacket) { - activeEntities.values().forEach(entity -> entity.despawnTime = time); + for (Entity entity : activeEntities.values()) { + index = entity.writeToCache(indexOut, out, time, index); + } activeEntities.clear(); - activeChunks.values().forEach(chunk -> chunk.despawnTime = time); + for (Chunk chunk : activeChunks.values()) { + index = chunk.writeToCache(indexOut, out, time, index); + } activeChunks.clear(); if (activeWeather != null) { - activeWeather.despawnTime = time; + index = activeWeather.writeToCache(indexOut, out, time, index); } activeWeather = null; } else if (packet instanceof ServerUpdateTimePacket) { @@ -441,15 +459,14 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe switch (p.getNotification()) { case START_RAIN: if (activeWeather != null) { - activeWeather.despawnTime = time; + index = activeWeather.writeToCache(indexOut, out, time, index); } activeWeather = new Weather(); activeWeather.spawnTime = time; - thingSpawns.put(time, activeWeather); break; case STOP_RAIN: if (activeWeather != null) { - activeWeather.despawnTime = time; + index = activeWeather.writeToCache(indexOut, out, time, index); activeWeather = null; } break; @@ -475,43 +492,24 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } } + + for (Entity entity : activeEntities.values()) { + index = entity.writeToCache(indexOut, out, time, index); + } + for (Chunk chunk : activeChunks.values()) { + index = chunk.writeToCache(indexOut, out, time, index); + } + if (activeWeather != null) { + index = activeWeather.writeToCache(indexOut, out, time, index); + } + + indexOut.writeByte(0); + writeToCache(indexOut, worldTimes); + writeToCache(indexOut, thunderStrengths); + + indexOut.writeVarInt(index); } LOGGER.info("Analysed replay in " + (System.currentTimeMillis() - sysTimeStart) + "ms"); - - sysTimeStart = System.currentTimeMillis(); - try (OutputStream cacheOut = replayFile.write(CACHE_ENTRY)) { - NetOutput out = new StreamNetOutput(cacheOut); - out.writeVarInt(CACHE_VERSION); - - out.writeVarInt(thingSpawnsT.size()); - int lastTime = 0; - for (Map.Entry> entry : thingSpawnsT.entrySet()) { - int time = entry.getKey(); - out.writeVarInt(time - lastTime); - lastTime = time; - - progress.accept(time / duration * (1 - progressSplit) + progressSplit); - - Collection trackedThings = entry.getValue(); - out.writeVarInt(trackedThings.size()); - for (TrackedThing trackedThing : trackedThings) { - out.writeVarInt(trackedThing.despawnTime); - if (trackedThing instanceof Entity) { - out.writeVarInt(0); - } else if (trackedThing instanceof Chunk) { - out.writeVarInt(1); - } else if (trackedThing instanceof Weather) { - out.writeVarInt(2); - } else { - throw new UnsupportedOperationException("Unknown type of tracked thing: " + trackedThing); - } - trackedThing.writeToCache(out); - } - } - writeToCache(out, worldTimes); - writeToCache(out, thunderStrengths); - } - LOGGER.info("Wrote quick replay to cache in " + (System.currentTimeMillis() - sysTimeStart) + "ms"); } @Override @@ -519,8 +517,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe ensureInitialized(() -> { if (replayTime > currentTimeStamp) { activeThings.removeIf(thing -> { - if (thing.despawnTime < replayTime) { - thing.despawnPackets.forEach(ctx::fireChannelRead); + if (thing.despawnTime <= replayTime) { + thing.despawn(this, ctx::fireChannelRead); return true; } else { return false; @@ -529,7 +527,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe thingSpawnsT.subMap(currentTimeStamp, false, replayTime, true).values() .forEach(things -> things.forEach(thing -> { if (thing.despawnTime > replayTime) { - thing.spawnPackets.forEach(ctx::fireChannelRead); + thing.spawn(this, ctx::fireChannelRead); activeThings.add(thing); } })); @@ -539,7 +537,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } else { activeThings.removeIf(thing -> { if (thing.spawnTime > replayTime) { - thing.despawnPackets.forEach(ctx::fireChannelRead); + thing.despawn(this, ctx::fireChannelRead); return true; } else { return false; @@ -548,7 +546,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe thingDespawnsT.subMap(replayTime, false, currentTimeStamp, true).values() .forEach(things -> things.forEach(thing -> { if (thing.spawnTime <= replayTime) { - thing.spawnPackets.forEach(ctx::fireChannelRead); + thing.spawn(this, ctx::fireChannelRead); activeThings.add(thing); } })); @@ -560,6 +558,25 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe }); } + private static final com.github.steveice10.netty.buffer.ByteBuf mcplByteBuf = com.github.steveice10.netty.buffer.Unpooled.buffer(); + private static final ByteBufNetInput byteBufNetInput = new ByteBufNetInput(mcplByteBuf); + private static final ByteBufNetOutput byteBufNetOutput = new ByteBufNetOutput(mcplByteBuf); + + private static BlockStorage copy(BlockStorage of) { + int readerIndex = byteBuf.readerIndex(); // Mark the current reader and writer index (should be at start) + int writerIndex = byteBuf.writerIndex(); + try { + of.write(byteBufNetOutput); + return new BlockStorage(byteBufNetInput); + } catch (Exception e) { + Utils.throwIfUnchecked(e); + throw new RuntimeException(e); + } finally { + byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use + byteBuf.writerIndex(writerIndex); + } + } + private static final ByteBuf byteBuf = Unpooled.buffer(); private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); private static final PacketBuffer packetBuf = new PacketBuffer(byteBuf); @@ -597,7 +614,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } - private static Packet readPacketFromCache(int version, NetInput in) throws IOException { + private static Packet readPacketFromCache(NetInput in) throws IOException { int readerIndex = byteBuf.readerIndex(); // Mark the current reader and writer index (should be at start) int writerIndex = byteBuf.writerIndex(); try { @@ -622,24 +639,25 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } - private static List> readPacketsFromCache(int version, NetInput in) throws IOException { + @SneakyThrows(IOException.class) + private static List> readPacketsFromCache(NetInput in) { int size = in.readVarInt(); List> packets = new ArrayList<>(size); for (int i = 0; i < size; i++) { - packets.add(readPacketFromCache(version, in)); + packets.add(readPacketFromCache(in)); } return packets; } - private static void readFromCache(int version, NetInput in, SortedMap> packets) throws IOException { + private static void readFromCache(NetInput in, SortedMap> packets) throws IOException { int time = 0; for (int i = in.readVarInt(); i > 0; i--) { time += in.readVarInt(); - packets.put(time, readPacketFromCache(version, in)); + packets.put(time, readPacketFromCache(in)); } } - private static void writeToCache(NetOutput out, com.github.steveice10.packetlib.packet.Packet packet) throws IOException { + private static int writeToCache(NetOutput out, com.github.steveice10.packetlib.packet.Packet packet) throws IOException { int readerIndex = byteBuf.readerIndex(); // Mark the current reader and writer index (should be at start) int writerIndex = byteBuf.writerIndex(); try { @@ -649,10 +667,11 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe byteBuf.skipBytes(8); // Skip packet length & timestamp int size = byteBuf.readableBytes(); - out.writeVarInt(size); + int len = writeVarInt(out, size); for (int i = 0; i < size; i++) { out.writeByte(byteBuf.readByte()); } + return len + size; } catch (Exception e) { Utils.throwIfInstanceOf(e, IOException.class); Utils.throwIfUnchecked(e); @@ -663,23 +682,37 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } - private static void writeToCache(NetOutput out, Collection packets) throws IOException { - out.writeVarInt(packets.size()); + private static int writeToCache(NetOutput out, Collection packets) throws IOException { + int len = writeVarInt(out, packets.size()); for (com.github.steveice10.packetlib.packet.Packet packet : packets) { - writeToCache(out, packet); + len += writeToCache(out, packet); } + return len; } - private static void writeToCache(NetOutput out, SortedMap packets) throws IOException { - out.writeVarInt(packets.size()); + private static int writeToCache(NetOutput out, SortedMap packets) throws IOException { + int len = 0; + len += writeVarInt(out, packets.size()); int lastTime = 0; for (Map.Entry entry : packets.entrySet()) { int time = entry.getKey(); - out.writeVarInt(time - lastTime); + len += writeVarInt(out, time - lastTime); lastTime = time; - writeToCache(out, entry.getValue()); + len += writeToCache(out, entry.getValue()); } + return len; + } + + private static int writeVarInt(NetOutput out, int i) throws IOException { + int len = 1; + while ((i & -128) != 0) { + out.writeByte(i & 127 | 128); + i >>>= 7; + len++; + } + out.writeByte(i); + return len; } private static long coordToLong(int x, int z) { @@ -704,7 +737,6 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe List spawnPackets; List despawnPackets; int spawnTime; - int despawnTime = Integer.MAX_VALUE; private TrackedThing(List spawnPackets, List despawnPackets) { @@ -712,26 +744,41 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe this.despawnPackets = despawnPackets; } - public void writeToCache(NetOutput out) throws IOException { - QuickReplaySender.writeToCache(out, spawnPackets); - QuickReplaySender.writeToCache(out, despawnPackets); + public int writeToCache(NetOutput indexOut, NetOutput cacheOut, int despawnTime, int index) throws IOException { + indexOut.writeVarInt(spawnTime); + indexOut.writeVarInt(despawnTime); + indexOut.writeVarInt(index); + index += QuickReplaySender.writeToCache(cacheOut, spawnPackets); + indexOut.writeVarInt(index); + index += QuickReplaySender.writeToCache(cacheOut, despawnPackets); + return index; } } - // For quicker jumping we store MC packets. - // However, during replay analysis it's easier to use MCProtocolLib ones in part because MC packets have been - // renamed from 1.8 to 1.9 but also because we cannot easily serialize them for caching. - // Therefore during analysis we use TrackedThing which we then serialize (we'd have to do that anyway for caching) - // and afterwards unserialize in BakedTrackedThing as MC packets for replaying. + // For memory efficiency we store raw packets (we might even want to consider compression or memory mapped files). + // During analysis we use TrackedThing which we then serialize (we'd have to do that anyway for caching) + // and afterwards deserialize in BakedTrackedThing as MC packets on demand for replaying. private static abstract class BakedTrackedThing { - List> spawnPackets; - List> despawnPackets; + int indexSpawnPackets; + int indexDespawnPackets; int spawnTime; - int despawnTime = Integer.MAX_VALUE; + int despawnTime; - private BakedTrackedThing(int version, NetInput in) throws IOException { - spawnPackets = readPacketsFromCache(version, in); - despawnPackets = readPacketsFromCache(version, in); + private BakedTrackedThing(NetInput in) throws IOException { + spawnTime = in.readVarInt(); + despawnTime = in.readVarInt(); + indexSpawnPackets = in.readVarInt(); + indexDespawnPackets = in.readVarInt(); + } + + public void spawn(QuickReplaySender sender, Consumer> send) { + sender.buf.readerIndex(indexSpawnPackets); + readPacketsFromCache(sender.bufInput).forEach(send); + } + + public void despawn(QuickReplaySender sender, Consumer> send) { + sender.buf.readerIndex(indexDespawnPackets); + readPacketsFromCache(sender.bufInput).forEach(send); } public abstract void play(int currentTimeStamp, int replayTime, Consumer> send); @@ -748,34 +795,53 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } @Override - public void writeToCache(NetOutput out) throws IOException { - super.writeToCache(out); + public int writeToCache(NetOutput indexOut, NetOutput cacheOut, int despawnTime, int index) throws IOException { + indexOut.writeByte(1); + index = super.writeToCache(indexOut, cacheOut, despawnTime, index); - out.writeVarInt(id); - out.writeVarInt(locations.size()); + indexOut.writeVarInt(id); + indexOut.writeVarInt(index); + index += writeVarInt(cacheOut, locations.size()); int lastTime = 0; for (Map.Entry entry : locations.entrySet()) { int time = entry.getKey(); Location loc = entry.getValue(); - out.writeVarInt(time - lastTime); + index += writeVarInt(cacheOut, time - lastTime); lastTime = time; - out.writeDouble(loc.getX()); - out.writeDouble(loc.getY()); - out.writeDouble(loc.getZ()); - out.writeFloat(loc.getYaw()); - out.writeFloat(loc.getPitch()); + cacheOut.writeDouble(loc.getX()); + cacheOut.writeDouble(loc.getY()); + cacheOut.writeDouble(loc.getZ()); + cacheOut.writeFloat(loc.getYaw()); + cacheOut.writeFloat(loc.getPitch()); + index += 32; } + + return index; } } private static class BakedEntity extends BakedTrackedThing { private int id; - private NavigableMap locations = new TreeMap<>(); + private int index; + private NavigableMap locations; - private BakedEntity(int version, NetInput in) throws IOException { - super(version, in); + private BakedEntity(NetInput in) throws IOException { + super(in); id = in.readVarInt(); + index = in.readVarInt(); + } + + @Override + @SneakyThrows(IOException.class) + public void spawn(QuickReplaySender sender, Consumer> send) { + super.spawn(sender, send); + + sender.buf.readerIndex(index); + NetInput in = sender.bufInput; + + locations = new TreeMap<>(); + int time = 0; for (int i = in.readVarInt(); i > 0; i--) { time += in.readVarInt(); @@ -783,6 +849,12 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } + @Override + public void despawn(QuickReplaySender sender, Consumer> send) { + super.despawn(sender, send); + locations = null; + } + @Override public void play(int currentTimeStamp, int replayTime, Consumer> send) { playMap(locations, currentTimeStamp, replayTime, l -> @@ -806,38 +878,57 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe Collections.singletonList(new ServerUnloadChunkPacket(column.getX(), column.getZ()))); com.github.steveice10.mc.protocol.data.game.chunk.Chunk[] chunks = column.getChunks(); for (int i = 0; i < currentBlockState.length; i++) { - currentBlockState[i] = chunks[i] == null ? new BlockStorage() : chunks[i].getBlocks(); + currentBlockState[i] = chunks[i] == null ? new BlockStorage() : copy(chunks[i].getBlocks()); } } @Override - public void writeToCache(NetOutput out) throws IOException { - super.writeToCache(out); + public int writeToCache(NetOutput indexOut, NetOutput cacheOut, int despawnTime, int index) throws IOException { + indexOut.writeByte(2); + index = super.writeToCache(indexOut, cacheOut, despawnTime, index); - out.writeVarInt(blocksT.size()); + indexOut.writeVarInt(index); + index += writeVarInt(cacheOut, blocksT.size()); int lastTime = 0; for (Map.Entry> entry : blocksT.entrySet()) { int time = entry.getKey(); - out.writeVarInt(time - lastTime); + index += writeVarInt(cacheOut, time - lastTime); lastTime = time; Collection blockChanges = entry.getValue(); - out.writeVarInt(blockChanges.size()); + index += writeVarInt(cacheOut, blockChanges.size()); for (BlockChange blockChange : blockChanges) { - NetUtil.writePosition(out, blockChange.pos); - NetUtil.writeBlockState(out, blockChange.from); - NetUtil.writeBlockState(out, blockChange.to); + NetUtil.writePosition(cacheOut, blockChange.pos); + index += 8; + index += writeVarInt(cacheOut, blockChange.from.getId() << 4 | blockChange.from.getData() & 15); + index += writeVarInt(cacheOut, blockChange.to.getId() << 4 | blockChange.to.getData() & 15); } } + + return index; } } private static class BakedChunk extends BakedTrackedThing { - private TreeMap> blocksT = new TreeMap<>(); - private ListMultimap blocks = Multimaps.newListMultimap(blocksT, LinkedList::new); // LinkedList to allow .descendingIterator + private int index; + private TreeMap> blocksT; - private BakedChunk(int version, NetInput in) throws IOException { - super(version, in); + private BakedChunk(NetInput in) throws IOException { + super(in); + + index = in.readVarInt(); + } + + @Override + @SneakyThrows(IOException.class) + public void spawn(QuickReplaySender sender, Consumer> send) { + super.spawn(sender, send); + + sender.buf.readerIndex(index); + NetInput in = sender.bufInput; + + blocksT = new TreeMap<>(); + ListMultimap blocks = Multimaps.newListMultimap(blocksT, LinkedList::new); // LinkedList to allow .descendingIterator int time = 0; for (int i = in.readVarInt(); i > 0; i--) { @@ -853,6 +944,13 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } + @Override + public void despawn(QuickReplaySender sender, Consumer> send) { + super.despawn(sender, send); + + blocksT = null; + } + @Override public void play(int currentTimeStamp, int replayTime, Consumer> send) { blocksT.subMap(currentTimeStamp, false, replayTime, true).values() @@ -895,20 +993,44 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } @Override - public void writeToCache(NetOutput out) throws IOException { - super.writeToCache(out); + public int writeToCache(NetOutput indexOut, NetOutput cacheOut, int despawnTime, int index) throws IOException { + indexOut.writeByte(3); + index = super.writeToCache(indexOut, cacheOut, despawnTime, index); - QuickReplaySender.writeToCache(out, rainStrengths); + indexOut.writeVarInt(index); + index += QuickReplaySender.writeToCache(cacheOut, rainStrengths); + + return index; } } private static class BakedWeather extends BakedTrackedThing { - private TreeMap> rainStrengths = new TreeMap<>(); + private int index; + private TreeMap> rainStrengths; - private BakedWeather(int version, NetInput in) throws IOException { - super(version, in); + private BakedWeather(NetInput in) throws IOException { + super(in); - readFromCache(version, in, rainStrengths); + index = in.readVarInt(); + } + + @Override + @SneakyThrows(IOException.class) + public void spawn(QuickReplaySender sender, Consumer> send) { + super.spawn(sender, send); + + sender.buf.readerIndex(index); + + rainStrengths = new TreeMap<>(); + + readFromCache(sender.bufInput, rainStrengths); + } + + @Override + public void despawn(QuickReplaySender sender, Consumer> send) { + super.despawn(sender, send); + + rainStrengths = null; } @Override From e989b60ea861e379f5df403944dab6986c649af9 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 09:50:50 +0100 Subject: [PATCH 23/65] Properly handle non-full chunks in QuickReplaySender --- .../replaymod/replay/QuickReplaySender.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 51a12bf0..cc05c787 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -404,11 +404,40 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } } else if (packet instanceof ServerChunkDataPacket) { Column column = ((ServerChunkDataPacket) packet).getColumn(); - Chunk chunk = new Chunk(column); - chunk.spawnTime = time; - Chunk prev = activeChunks.put(coordToLong(column.getX(), column.getZ()), chunk); - if (prev != null) { - index = prev.writeToCache(indexOut, out, time, index); + if (column.hasBiomeData()) { + Chunk chunk = new Chunk(column); + chunk.spawnTime = time; + Chunk prev = activeChunks.put(coordToLong(column.getX(), column.getZ()), chunk); + if (prev != null) { + index = prev.writeToCache(indexOut, out, time, index); + } + } else { + Chunk chunk = activeChunks.get(coordToLong(column.getX(), column.getZ())); + if (chunk != null) { + int sectionY = 0; + for (com.github.steveice10.mc.protocol.data.game.chunk.Chunk section : column.getChunks()) { + if (section == null) { + sectionY++; + continue; + } + BlockStorage toBlocks = section.getBlocks(); + BlockStorage fromBlocks = chunk.currentBlockState[sectionY]; + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + BlockState fromState = fromBlocks.get(x, y, z); + BlockState toState = toBlocks.get(x, y, z); + if (!fromState.equals(toState)) { + Position pos = new Position(column.getX() << 4 | x, sectionY << 4 | y, column.getZ() << 4 | z); + chunk.blocks.put(time, new BlockChange(pos, fromState, toState)); + } + } + } + } + chunk.currentBlockState[sectionY] = toBlocks; + sectionY++; + } + } } } else if (packet instanceof ServerUnloadChunkPacket) { ServerUnloadChunkPacket p = (ServerUnloadChunkPacket) packet; From c30520ad3d85030e3b2587740ba6d24af6f6f15b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 09:59:45 +0100 Subject: [PATCH 24/65] Fix previous block state of BlockChange being loaded incorrectly --- src/main/java/com/replaymod/replay/QuickReplaySender.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index cc05c787..8aa50645 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -451,10 +451,10 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe ? new BlockChangeRecord[]{ ((ServerBlockChangePacket) packet).getRecord() } : ((ServerMultiBlockChangePacket) packet).getRecords()) { Position pos = record.getPosition(); - Chunk chunk = activeChunks.get(coordToLong(pos.getX() / 16, pos.getZ() / 16)); + Chunk chunk = activeChunks.get(coordToLong(pos.getX() >> 4, pos.getZ() >> 4)); if (chunk != null) { - BlockStorage blockStorage = chunk.currentBlockState[pos.getY() / 16]; - int x = Math.floorMod(pos.getX(), 16), y = Math.floorMod(pos.getY(), 16), z = Math.floorMod(pos.getZ(), 16); + BlockStorage blockStorage = chunk.currentBlockState[pos.getY() >> 4]; + int x = pos.getX() & 15, y = pos.getY() & 15, z = pos.getZ() & 15; BlockState prevState = blockStorage.get(x, y, z); BlockState newState = record.getBlock(); blockStorage.set(x, y, z, newState); From 1670ed4e8068dbc93d168a3b95289dc076a2ccfb Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 10:44:42 +0100 Subject: [PATCH 25/65] Fix dead lock when switching from async to quick mode --- .../java/com/replaymod/replay/ReplayHandler.java | 12 +++++++----- .../java/com/replaymod/replay/ReplayModReplay.java | 8 ++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 45007209..c54081a9 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -348,20 +348,22 @@ public class ReplayHandler { public void setQuickMode(boolean quickMode) { if (quickMode == this.quickMode) return; - if (quickMode && !fullReplaySender.isAsyncMode()) return; // Cannot activate quick mode when already in sync mode + if (quickMode && fullReplaySender.isAsyncMode()) { + // If this method is called via runLater, then it cannot switch to sync mode by itself as there might be + // some rogue packets in the task queue after it. Instead the caller must switch to sync mode first and + // use runLater until all packets have been processed (when using setAsyncModeAndWait, one runLater should + // be sufficient). + throw new IllegalStateException("Cannot switch to quick mode while in async mode."); + } this.quickMode = quickMode; if (quickMode) { - fullReplaySender.setSyncModeAndWait(); quickReplaySender.register(); quickReplaySender.restart(); quickReplaySender.sendPacketsTill(fullReplaySender.currentTimeStamp()); - quickReplaySender.setAsyncMode(true); } else { - quickReplaySender.setSyncModeAndWait(); quickReplaySender.unregister(); fullReplaySender.sendPacketsTill(0); fullReplaySender.sendPacketsTill(quickReplaySender.currentTimeStamp()); - fullReplaySender.setAsyncMode(true); } } diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 5ff5a373..0032acea 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -144,8 +144,12 @@ public class ReplayModReplay { core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.quickmode", Keyboard.KEY_Q, () -> { if (replayHandler != null) { - replayHandler.ensureQuickModeInitialized(() -> - replayHandler.setQuickMode(!replayHandler.isQuickMode())); + replayHandler.getReplaySender().setSyncModeAndWait(); + core.runLater(() -> + replayHandler.ensureQuickModeInitialized(() -> { + replayHandler.setQuickMode(!replayHandler.isQuickMode()); + replayHandler.getReplaySender().setAsyncMode(true); + })); } }); From 27370778286ad1595b5185503d1f7eebb96f95ba Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 10:44:57 +0100 Subject: [PATCH 26/65] Retain camera position when switching to and from quick mode --- src/main/java/com/replaymod/replay/ReplayHandler.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index c54081a9..59c06672 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -356,6 +356,14 @@ public class ReplayHandler { throw new IllegalStateException("Cannot switch to quick mode while in async mode."); } this.quickMode = quickMode; + + CameraEntity cam = getCameraEntity(); + if (cam != null) { + targetCameraPosition = new Location(cam.posX, cam.posY, cam.posZ, cam.rotationYaw, cam.rotationPitch); + } else { + targetCameraPosition = null; + } + if (quickMode) { quickReplaySender.register(); quickReplaySender.restart(); @@ -365,6 +373,8 @@ public class ReplayHandler { fullReplaySender.sendPacketsTill(0); fullReplaySender.sendPacketsTill(quickReplaySender.currentTimeStamp()); } + + moveCameraToTargetPosition(); } public boolean isQuickMode() { From 60ef8df5234a6b692ffdf4f4f8226ded410f57a1 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 10:48:31 +0100 Subject: [PATCH 27/65] Add Quick Mode keybinding translation key --- src/main/resources/assets/replaymod/lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 16508289..2dbb2d67 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 165082892e4f323d0bc5ef6bc390325664372035 +Subproject commit 2dbb2d6705b4f54bdfbed0de4a2647ceef14b651 From 5366758cf667cf6656dd113bc43450f43fef4fbb Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 12:43:39 +0100 Subject: [PATCH 28/65] Compress packets to reduce quick mode memory usage by ~80% --- .../replaymod/replay/QuickReplaySender.java | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 8aa50645..991d717a 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -81,6 +81,8 @@ import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; import java.util.function.Consumer; +import java.util.zip.Deflater; +import java.util.zip.Inflater; //#if MC>=11200 import com.replaymod.core.utils.WrappedTimer; @@ -317,6 +319,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe readFromCache(in, thunderStrengths); int size = in.readVarInt(); + LOGGER.info("Creating quick mode buffer of size: {}KB", size / 1024); buf = com.github.steveice10.netty.buffer.Unpooled.buffer(size); int read = 0; while (true) { @@ -610,6 +613,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); private static final PacketBuffer packetBuf = new PacketBuffer(byteBuf); private static final ReplayOutputStream encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut); + private static final Inflater inflater = new Inflater(); + private static final Deflater deflater = new Deflater(); private static Packet toMC(com.github.steveice10.packetlib.packet.Packet packet) { // We need to re-encode MCProtocolLib packets, so we can then decode them as NMS packets @@ -647,7 +652,21 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe int readerIndex = byteBuf.readerIndex(); // Mark the current reader and writer index (should be at start) int writerIndex = byteBuf.writerIndex(); try { - packetBuf.writeBytes(in.readBytes(in.readVarInt())); + int prefix = in.readVarInt(); + int len = prefix >> 1; + if ((prefix & 1) == 1) { + int fullLen = in.readVarInt(); + byteBuf.writeBytes(in.readBytes(len)); + byteBuf.capacity(byteBuf.writerIndex() + fullLen); + + inflater.setInput(byteBuf.array(), byteBuf.arrayOffset() + byteBuf.readerIndex(), len); + inflater.inflate(byteBuf.array(), byteBuf.arrayOffset() + byteBuf.writerIndex(), fullLen); + + byteBuf.readerIndex(byteBuf.readerIndex() + len); + byteBuf.writerIndex(byteBuf.writerIndex() + fullLen); + } else { + byteBuf.writeBytes(in.readBytes(len)); + } int packetId = //#if MC>=11102 @@ -665,6 +684,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } finally { byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use byteBuf.writerIndex(writerIndex); + inflater.reset(); } } @@ -695,12 +715,37 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe byteBuf.skipBytes(8); // Skip packet length & timestamp + int rawIndex = byteBuf.readerIndex(); int size = byteBuf.readableBytes(); - int len = writeVarInt(out, size); - for (int i = 0; i < size; i++) { - out.writeByte(byteBuf.readByte()); + + byteBuf.ensureWritable(size); + deflater.setInput(byteBuf.array(), byteBuf.arrayOffset() + byteBuf.readerIndex(), size); + deflater.finish(); + int compressedSize = 0; + while (!deflater.finished() && compressedSize < size) { + compressedSize += deflater.deflate( + byteBuf.array(), + byteBuf.arrayOffset() + byteBuf.writerIndex() + compressedSize, + size - compressedSize + ); } - return len + size; + + int len = 0; + if (compressedSize < size) { + byteBuf.readerIndex(rawIndex + size); + byteBuf.writerIndex(rawIndex + size + compressedSize); + len += writeVarInt(out, compressedSize << 1 | 1); + len += writeVarInt(out, size); + } else { + byteBuf.readerIndex(rawIndex); + byteBuf.writerIndex(rawIndex + size); + len += writeVarInt(out, size << 1); + } + while (byteBuf.isReadable()) { + out.writeByte(byteBuf.readByte()); + len += 1; + } + return len; } catch (Exception e) { Utils.throwIfInstanceOf(e, IOException.class); Utils.throwIfUnchecked(e); @@ -708,6 +753,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe } finally { byteBuf.readerIndex(readerIndex); // Reset reader & writer index for next use byteBuf.writerIndex(writerIndex); + deflater.reset(); } } From 1da7060bb2095060c2538bd4e8cd8741fb9f1543 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 12:55:51 +0100 Subject: [PATCH 29/65] Fix entity head rotation in quick mode --- .../com/replaymod/replay/QuickReplaySender.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 991d717a..8ec020a1 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -15,6 +15,7 @@ import com.github.steveice10.mc.protocol.data.game.world.notify.ClientNotificati import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityDestroyPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityHeadLookPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityTeleportPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnMobPacket; @@ -932,14 +933,18 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe @Override public void play(int currentTimeStamp, int replayTime, Consumer> send) { - playMap(locations, currentTimeStamp, replayTime, l -> - send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false)))); + playMap(locations, currentTimeStamp, replayTime, l -> { + send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false))); + send.accept(toMC(new ServerEntityHeadLookPacket(id, l.getYaw()))); + }); } @Override public void rewind(int currentTimeStamp, int replayTime, Consumer> send) { - rewindMap(locations, currentTimeStamp, replayTime, l -> - send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false)))); + rewindMap(locations, currentTimeStamp, replayTime, l -> { + send.accept(toMC(new ServerEntityTeleportPacket(id, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), false))); + send.accept(toMC(new ServerEntityHeadLookPacket(id, l.getYaw()))); + }); } } From c1b4964978818ff3f7147d3b161f8c4328b65b52 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 13:32:26 +0100 Subject: [PATCH 30/65] Handle entity position interpolation when jumping in quick mode --- .../com/replaymod/replay/ReplayHandler.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 59c06672..3317045b 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -510,7 +510,48 @@ public class ReplayHandler { public void doJump(int targetTime, boolean retainCameraPosition) { //#if MC>=10904 if (getReplaySender() == quickReplaySender) { + // Always round to full tick + targetTime = targetTime + targetTime % 50; + + if (targetTime >= 50) { + // Jump to time of previous tick first + quickReplaySender.sendPacketsTill(targetTime - 50); + } + + // Update all entity positions (especially prev/lastTick values) + for (Entity entity : loadedEntityList(world(mc))) { + if (entity instanceof EntityOtherPlayerMP) { + EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity; + e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); + e.rotationYaw = (float) e.otherPlayerMPYaw; + e.rotationPitch = (float) e.otherPlayerMPPitch; + } + entity.lastTickPosX = entity.prevPosX = entity.posX; + entity.lastTickPosY = entity.prevPosY = entity.posY; + entity.lastTickPosZ = entity.prevPosZ = entity.posZ; + entity.prevRotationYaw = entity.rotationYaw; + entity.prevRotationPitch = entity.rotationPitch; + } + + // Run previous tick + try { + mc.runTick(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Jump to target tick quickReplaySender.sendPacketsTill(targetTime); + + // Immediately apply player teleport interpolation + for (Entity entity : loadedEntityList(world(mc))) { + if (entity instanceof EntityOtherPlayerMP) { + EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity; + e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); + e.rotationYaw = (float) e.otherPlayerMPYaw; + e.rotationPitch = (float) e.otherPlayerMPPitch; + } + } return; } //#endif From 3f7818aeec14e5d892783b51e4db281de72440ed Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 18 Dec 2018 15:04:21 +0100 Subject: [PATCH 31/65] Auto-save the current timeline after changes and on exit This auto-saved timeline is loaded when the replay is opened the next time. --- .../pathing/gui/GuiKeyframeRepository.java | 1 + .../simplepathing/ReplayModSimplePathing.java | 100 +++++++++++++++++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java b/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java index 42a0032c..e794e272 100644 --- a/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java +++ b/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java @@ -229,6 +229,7 @@ public class GuiKeyframeRepository extends GuiScreen implements Closeable { timelines.putAll(replayFile.getTimelines(registry)); for (Map.Entry entry : timelines.entrySet()) { + if (entry.getKey().isEmpty()) continue; // don't show auto-save slot list.getListPanel().addElements(null, new Entry(entry.getKey())); } } diff --git a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java index f1cc4e0f..3a558ceb 100644 --- a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java +++ b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java @@ -6,11 +6,18 @@ import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.events.ReplayCloseEvent; import com.replaymod.replay.events.ReplayOpenEvent; import com.replaymod.replay.gui.overlay.GuiReplayOverlay; +import com.replaymod.replaystudio.pathing.PathingRegistry; +import com.replaymod.replaystudio.pathing.change.Change; import com.replaymod.replaystudio.pathing.path.Keyframe; +import com.replaymod.replaystudio.pathing.path.Timeline; +import com.replaymod.replaystudio.pathing.serialize.TimelineSerialization; +import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.simplepathing.SPTimeline.SPPath; import com.replaymod.simplepathing.gui.GuiPathing; import com.replaymod.simplepathing.preview.PathPreview; import lombok.Getter; +import net.minecraft.crash.CrashReport; +import net.minecraft.util.ReportedException; import org.apache.logging.log4j.Logger; import org.lwjgl.input.Keyboard; @@ -24,6 +31,14 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + import static com.replaymod.core.versions.MCVer.*; @Mod(modid = ReplayModSimplePathing.MOD_ID, @@ -74,8 +89,43 @@ public class ReplayModSimplePathing { @SubscribeEvent public void postReplayOpen(ReplayOpenEvent.Post event) { - clearCurrentTimeline(); + ReplayFile replayFile = event.getReplayHandler().getReplayFile(); + try { + synchronized (replayFile) { + Timeline timeline = replayFile.getTimelines(new SPTimeline()).get(""); + if (timeline != null) { + setCurrentTimeline(new SPTimeline(timeline)); + } else { + setCurrentTimeline(new SPTimeline()); + } + } + } catch (IOException e) { + throw new ReportedException(CrashReport.makeCrashReport(e, "Reading timeline")); + } + guiPathing = new GuiPathing(core, this, event.getReplayHandler()); + + saveService = Executors.newSingleThreadExecutor(); + new Runnable() { + @Override + public void run() { + maybeSaveTimeline(replayFile); + if (guiPathing != null) { + core.runLater(this); + } + } + }.run(); + } + + @SubscribeEvent + public void preReplayClose(ReplayCloseEvent.Pre event) { + saveService.shutdown(); + try { + saveService.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + saveService = null; } @SubscribeEvent @@ -151,4 +201,52 @@ public class ReplayModSimplePathing { public GuiPathing getGuiPathing() { return guiPathing; } + + private final AtomicInteger lastSaveId = new AtomicInteger(); + private ExecutorService saveService; + private Change lastChange; + private void maybeSaveTimeline(ReplayFile replayFile) { + SPTimeline spTimeline = currentTimeline; + if (spTimeline == null || saveService == null) { + lastChange = null; + return; + } + + Change latestChange = spTimeline.getTimeline().peekUndoStack(); + if (latestChange == null || latestChange == lastChange) { + return; + } + lastChange = latestChange; + + // Clone the timeline for async saving + Timeline timeline; + try { + TimelineSerialization serialization = new TimelineSerialization(spTimeline, null); + String serialized = serialization.serialize(Collections.singletonMap("", spTimeline.getTimeline())); + timeline = serialization.deserialize(serialized).get(""); + } catch (Throwable t) { + CrashReport report = CrashReport.makeCrashReport(t, "Cloning timeline"); + throw new ReportedException(report); + } + + int id = lastSaveId.incrementAndGet(); + saveService.submit(() -> { + if (lastSaveId.get() != id) { + return; // Another job has been scheduled, it will do the hard work. + } + try { + saveTimeline(replayFile, spTimeline, timeline); + } catch (IOException e) { + LOGGER.error("Auto-saving timeline:", e); + } + }); + } + + private void saveTimeline(ReplayFile replayFile, PathingRegistry pathingRegistry, Timeline timeline) throws IOException { + synchronized (replayFile) { + Map timelineMap = replayFile.getTimelines(pathingRegistry); + timelineMap.put("", timeline); + replayFile.writeTimelines(pathingRegistry, timelineMap); + } + } } From 9655677972f5d20d0517084dd9828652c0cad139 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 19 Dec 2018 09:44:21 +0100 Subject: [PATCH 32/65] Start update to 1.13 --- build.gradle | 3 + gradle.properties | 2 + gradle/wrapper/gradle-wrapper.properties | 4 +- jGui | 2 +- settings.gradle | 32 +-- .../replaymod/core/KeyBindingRegistry.java | 24 ++- .../com/replaymod/core/LoadingPlugin.java | 2 + .../java/com/replaymod/core/ReplayMod.java | 160 ++++++++++---- .../com/replaymod/core/SettingsRegistry.java | 2 +- .../core/events/SettingsChangedEvent.java | 6 +- .../core/handler/MainMenuHandler.java | 17 +- .../com/replaymod/core/utils/ModCompat.java | 30 ++- .../com/replaymod/core/utils/OpenGLUtils.java | 8 +- .../replaymod/core/utils/Restrictions.java | 1 + .../java/com/replaymod/core/utils/Utils.java | 25 ++- .../replaymod/core/utils/WrappedTimer.java | 20 +- .../com/replaymod/core/versions/MCVer.java | 202 ++++++++++++++++-- .../recording/ReplayModRecording.java | 62 +++--- .../recording/gui/GuiRecordingOverlay.java | 14 +- .../handler/ConnectionEventHandler.java | 28 ++- .../recording/handler/FMLHandshakeFilter.java | 54 ++--- .../recording/handler/GuiHandler.java | 6 +- .../handler/RecordingEventHandler.java | 46 +++- .../mixin/MixinNetHandlerLoginClient.java | 18 +- .../mixin/MixinNetHandlerPlayClient.java | 4 +- .../mixin/MixinNetworkDispatcher.java | 3 + .../mixin/MixinPlayerControllerMP.java | 2 + .../recording/mixin/MixinRenderGlobal.java | 2 + .../recording/mixin/MixinWorldClient.java | 15 +- .../recording/packet/PacketListener.java | 8 +- .../packet/ResourcePackRecorder.java | 24 ++- src/main/resources/META-INF/mods.toml | 28 +++ src/main/resources/META-INF/replaymod_at.cfg | 2 +- src/main/resources/pack.mcmeta | 6 + versions/1.13/.gitkeep | 0 versions/common.gradle | 165 +++++++++++--- versions/preprocessor.gradle | 11 + 37 files changed, 793 insertions(+), 245 deletions(-) create mode 100644 src/main/resources/META-INF/mods.toml create mode 100644 src/main/resources/pack.mcmeta create mode 100644 versions/1.13/.gitkeep diff --git a/build.gradle b/build.gradle index 4809b885..5b42ddd9 100755 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,6 @@ +apply plugin: 'eclipse' +apply plugin: 'idea' + import groovy.json.JsonOutput def latestVersion = file('version.txt').readLines().first() diff --git a/gradle.properties b/gradle.properties index e9b9fd5a..b209ec29 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,5 @@ # Sets default memory used for gradle commands. Can be overridden by user or command line properties. # This is required to provide enough memory for the Minecraft decompilation process. org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +org.gradle.parallel= diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca10d237..7d754af9 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-all.zip -distributionSha256Sum=69d99ade952de3727b65a88f3188ee72231ff4e28c2d00aacfc96a42c6d6f303 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionSha256Sum=39e2d5803bbd5eaf6c8efe07067b0e5a00235e8c71318642b2ed262920b27721 diff --git a/jGui b/jGui index 43a58160..48b3d759 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 43a581607508c42d2ad7f67deaffb49cf620cfbe +Subproject commit 48b3d75950b9610b214dd1f8f6f69c1ebe858320 diff --git a/settings.gradle b/settings.gradle index f67c539b..d73b297f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,11 +1,12 @@ include ':jGui' [ 'core', - '1.7.10', - '1.8', - '1.8.9', - '1.9.4', - '1.12' + //'1.7.10', FIXME + //'1.8', FIXME + //'1.8.9', FIXME + //'1.9.4', + //'1.12', + '1.13' ].each { version -> include ":jGui:$version" project(":jGui:$version").with { @@ -16,16 +17,17 @@ include ':jGui' def versions = [ 'core', - '1.7.10', - '1.8', - '1.8.9', - '1.9.4', - '1.10.2', - '1.11', - '1.11.2', - '1.12', - '1.12.1', - '1.12.2' + //'1.7.10', FIXME + //'1.8', FIXME + //'1.8.9', FIXME + //'1.9.4', + //'1.10.2', + //'1.11', + //'1.11.2', + //'1.12', + //'1.12.1', + //'1.12.2', + '1.13' ] versions.each { version -> include ":$version" diff --git a/src/main/java/com/replaymod/core/KeyBindingRegistry.java b/src/main/java/com/replaymod/core/KeyBindingRegistry.java index 1f02e0c3..9b2b87a6 100644 --- a/src/main/java/com/replaymod/core/KeyBindingRegistry.java +++ b/src/main/java/com/replaymod/core/KeyBindingRegistry.java @@ -6,12 +6,19 @@ import com.replaymod.core.versions.MCVer; import net.minecraft.client.settings.KeyBinding; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; -import net.minecraft.util.ReportedException; -import org.lwjgl.input.Keyboard; + +//#if MC>=11300 +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif import net.minecraftforge.fml.client.registry.ClientRegistry; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.InputEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; //#else @@ -95,24 +102,25 @@ public class KeyBindingRegistry { } catch (Throwable cause) { CrashReport crashReport = CrashReport.makeCrashReport(cause, "Handling Key Binding"); CrashReportCategory category = crashReport.makeCategory("Key Binding"); - category.addCrashSection("Key Binding", keyBinding); + MCVer.addDetail(category, "Key Binding", keyBinding::toString); MCVer.addDetail(category, "Handler", runnable::toString); - throw new ReportedException(crashReport); + throw newReportedException(crashReport); } } } public void handleRaw() { - int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); + //int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); + int keyCode = -1; // FIXME for (final Runnable runnable : rawHandlers.get(keyCode)) { try { runnable.run(); } catch (Throwable cause) { CrashReport crashReport = CrashReport.makeCrashReport(cause, "Handling Raw Key Binding"); CrashReportCategory category = crashReport.makeCategory("Key Binding"); - category.addCrashSection("Key Code", keyCode); + MCVer.addDetail(category, "Key Code", () -> "" + keyCode); MCVer.addDetail(category, "Handler", runnable::toString); - throw new ReportedException(crashReport); + throw newReportedException(crashReport); } } } diff --git a/src/main/java/com/replaymod/core/LoadingPlugin.java b/src/main/java/com/replaymod/core/LoadingPlugin.java index b2a9e0ac..d4709e37 100755 --- a/src/main/java/com/replaymod/core/LoadingPlugin.java +++ b/src/main/java/com/replaymod/core/LoadingPlugin.java @@ -1,3 +1,4 @@ +/* FIXME package com.replaymod.core; import org.apache.logging.log4j.LogManager; @@ -96,3 +97,4 @@ public class LoadingPlugin implements IFMLLoadingPlugin { } } +*/ diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 86c43a4b..9e55c3d8 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -6,17 +6,26 @@ import com.replaymod.core.gui.GuiReplaySettings; import com.replaymod.core.gui.RestoreReplayGui; import com.replaymod.core.handler.MainMenuHandler; import com.replaymod.core.utils.OpenGLUtils; +import com.replaymod.core.versions.MCVer; +import com.replaymod.recording.ReplayModRecording; import com.replaymod.replaystudio.util.I18n; import de.johni0702.minecraft.gui.container.GuiScreen; import lombok.Getter; import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.FolderResourcePack; -import net.minecraft.client.resources.IResourcePack; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.config.Configuration; import org.apache.commons.io.Charsets; import org.apache.commons.io.FileUtils; +//#if MC>=11300 +import net.minecraft.resources.FolderPack; +import net.minecraft.resources.IResourcePack; +import net.minecraftforge.fml.javafmlmod.FMLModLoadingContext; +//#else +//$$ import net.minecraft.client.resources.FolderResourcePack; +//$$ import net.minecraft.client.resources.IResourcePack; +//#endif + //#if MC>=10904 import net.minecraft.util.text.*; //#else @@ -28,18 +37,24 @@ import net.minecraft.util.text.*; //#endif //#if MC>=10800 -import net.minecraft.client.settings.GameSettings; -import net.minecraftforge.fml.client.FMLClientHandler; -import net.minecraftforge.fml.common.Loader; +//#if MC>=11300 +import net.minecraft.client.GameSettings; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.ModList; +//#else +//$$ import net.minecraft.client.settings.GameSettings; +//$$ import net.minecraftforge.fml.client.FMLClientHandler; +//$$ import net.minecraftforge.fml.common.Loader; +//$$ import net.minecraftforge.fml.common.Mod.EventHandler; +//$$ import net.minecraftforge.fml.common.Mod.Instance; +//$$ import net.minecraftforge.fml.common.ModContainer; +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.Mod.EventHandler; -import net.minecraftforge.fml.common.Mod.Instance; -import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.eventhandler.EventBus; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; //#else //$$ import cpw.mods.fml.common.Loader; @@ -50,7 +65,6 @@ import net.minecraftforge.fml.common.gameevent.TickEvent; //$$ import cpw.mods.fml.common.event.FMLInitializationEvent; //$$ import cpw.mods.fml.common.event.FMLPostInitializationEvent; //$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; -//$$ import cpw.mods.fml.common.eventhandler.EventBus; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //$$ import cpw.mods.fml.common.gameevent.TickEvent; //$$ import com.replaymod.replay.InputReplayTimer; @@ -62,37 +76,50 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.FutureTask; import static com.replaymod.core.versions.MCVer.*; -@Mod(modid = ReplayMod.MOD_ID, - useMetadata = true, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", +//#if MC>=11300 +@Mod(ReplayMod.MOD_ID) +//#else +//$$ @Mod(modid = ReplayMod.MOD_ID, +//$$ useMetadata = true, +//$$ version = "@MOD_VERSION@", +//$$ acceptedMinecraftVersions = "@MC_VERSION@", +//$$ acceptableRemoteVersions = "*", //#if MC>=10800 - clientSideOnly = true, - updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json", + //$$ clientSideOnly = true, + //$$ updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json", //#endif - guiFactory = "com.replaymod.core.gui.GuiFactory") +//$$ guiFactory = "com.replaymod.core.gui.GuiFactory") +//#endif public class ReplayMod { public static ModContainer getContainer() { - return Loader.instance().getIndexedModList().get(MOD_ID); + //#if MC>=11300 + return ModList.get().getModContainerById(MOD_ID).get(); + //#else + //$$ return Loader.instance().getIndexedModList().get(MOD_ID); + //#endif } @Getter(lazy = true) - private static final String minecraftVersion = Loader.MC_VERSION; + //#if MC>=11300 + private static final String minecraftVersion = "1.13"; // FIXME + //#else + //$$ private static final String minecraftVersion = Loader.MC_VERSION; + //#endif public static final String MOD_ID = "replaymod"; public static final ResourceLocation TEXTURE = new ResourceLocation("replaymod", "replay_gui.png"); public static final int TEXTURE_SIZE = 256; - private static final Minecraft mc = Minecraft.getMinecraft(); + private static final Minecraft mc = MCVer.getMinecraft(); @Deprecated public static Configuration config; @@ -101,9 +128,18 @@ public class ReplayMod { private final SettingsRegistry settingsRegistry = new SettingsRegistry(); // The instance of your mod that Forge uses. - @Instance(MOD_ID) + //#if MC>=11300 + { instance = this; } + //#else + //$$ @Instance(MOD_ID) + //#endif public static ReplayMod instance; + private final List modules = new ArrayList<>(); + { + modules.add(new ReplayModRecording(this)); + } + public KeyBindingRegistry getKeyBindingRegistry() { return keyBindingRegistry; } @@ -114,12 +150,16 @@ public class ReplayMod { public File getReplayFolder() throws IOException { String path = getSettingsRegistry().get(Setting.RECORDING_PATH); - File folder = new File(path.startsWith("./") ? getMinecraft().mcDataDir : null, path); + File folder = new File(path.startsWith("./") ? mcDataDir(getMinecraft()) : null, path); FileUtils.forceMkdir(folder); return folder; } - @EventHandler + //#if MC>=11300 + { FMLModLoadingContext.get().getModEventBus().addListener(this::preInit); } + //#else + //$$ @EventHandler + //#endif public void preInit(FMLPreInitializationEvent event) { // Initialize the static OpenGL info field from the minecraft main thread // Unfortunately lwjgl uses static methods so we have to make use of magic init calls as well @@ -127,20 +167,35 @@ public class ReplayMod { I18n.setI18n(net.minecraft.client.resources.I18n::format); - config = new Configuration(event.getSuggestedConfigurationFile()); - config.load(); + //#if MC>=11300 + config = new Configuration(new File(mcDataDir(mc), "configs/replaymod.cfg")); // FIXME where'd the suggestion go? + //#else + //$$ config = new Configuration(event.getSuggestedConfigurationFile()); + //#endif + // FIXME forge config api is currently broken config.load(); settingsRegistry.setConfiguration(config); + + modules.forEach(m -> m.preInit(event)); } //#ifdef DEV_ENV static { // Note: even preInit is too late and we'd have to issue another resource reload @SuppressWarnings("unchecked") - List defaultResourcePacks = mc.defaultResourcePacks; - FolderResourcePack jGuiResourcePack = new FolderResourcePack(new File("../jGui/src/main/resources")) { + //#if MC>=11300 + List defaultResourcePacks = new ArrayList<>(); // FIXME: probably replaced with DownloadingPackFinder + FolderPack jGuiResourcePack = new FolderPack(new File("../jGui/src/main/resources")) { @Override - protected InputStream getInputStreamByName(String resourceName) throws IOException { + protected InputStream getInputStream(String resourceName) throws IOException { try { - return super.getInputStreamByName(resourceName); + return super.getInputStream(resourceName); + //#else + //$$ List defaultResourcePacks = mc.defaultResourcePacks; + //$$ FolderResourcePack jGuiResourcePack = new FolderResourcePack(new File("../jGui/src/main/resources")) { + //$$ @Override + //$$ protected InputStream getInputStreamByName(String resourceName) throws IOException { + //$$ try { + //$$ return super.getInputStreamByName(resourceName); + //#endif } catch (IOException e) { if ("pack.mcmeta".equals(resourceName)) { return new ByteArrayInputStream(("{\"pack\": {\"description\": \"dummy pack for jGui resources in dev-env\", \"pack_format\": 1}}").getBytes(Charsets.UTF_8)); @@ -169,7 +224,11 @@ public class ReplayMod { } //#endif - @EventHandler + //#if MC>=11300 + { FMLModLoadingContext.get().getModEventBus().addListener(this::init); } + //#else + //$$ @EventHandler + //#endif public void init(FMLInitializationEvent event) { getSettingsRegistry().register(Setting.class); @@ -183,15 +242,21 @@ public class ReplayMod { getKeyBindingRegistry().registerKeyBinding("replaymod.input.settings", 0, () -> { new GuiReplaySettings(null, settingsRegistry).display(); }); + + modules.forEach(m -> m.init(event)); } - @EventHandler - public void postInit(FMLPostInitializationEvent event) throws IOException { + //#if MC>=11300 + { FMLModLoadingContext.get().getModEventBus().addListener(this::postInit); } + //#else + //$$ @EventHandler + //#endif + public void postInit(FMLPostInitializationEvent event) { settingsRegistry.save(); // Save default values to disk // 1.7.10 crashes when render distance > 16 //#if MC>=10800 - if(!FMLClientHandler.instance().hasOptifine()) + //if(!FMLClientHandler.instance().hasOptifine()) FIXME 1.13 update GameSettings.Options.RENDER_DISTANCE.setValueMax(64f); //#endif @@ -229,6 +294,8 @@ public class ReplayMod { e.printStackTrace(); } }); + + modules.forEach(m -> m.postInit(event)); } /** @@ -240,19 +307,18 @@ public class ReplayMod { public void runLater(Runnable runnable) { if (mc.isCallingFromMinecraftThread() && inRunLater) { - EventBus bus = FORGE_BUS; //#if MC>=10800 - bus.register(new Object() { + FORGE_BUS.register(new Object() { @SubscribeEvent public void onRenderTick(TickEvent.RenderTickEvent event) { if (event.phase == TickEvent.Phase.START) { runLater(runnable); - bus.unregister(this); + FORGE_BUS.unregister(this); } } }); //#else - //$$ bus.register(new RunLaterHelper(runnable)); + //$$ FORGE_BUS.register(new RunLaterHelper(runnable)); //#endif return; } @@ -309,7 +375,11 @@ public class ReplayMod { //#endif public String getVersion() { - return getContainer().getVersion(); + //#if MC>=11300 + return getContainer().getModInfo().getVersion().toString(); + //#else + //$$ return getContainer().getVersion(); + //#endif } private void testIfMoeshAndExitMinecraft() { @@ -332,6 +402,7 @@ public class ReplayMod { private void printToChat(boolean warning, String message, Object... args) { if (getSettingsRegistry().get(Setting.NOTIFICATIONS)) { + /* FIXME: needs RuntimeInvisibleParameterAnnotations workaround // Some nostalgia: "§8[§6Replay Mod§8]§r Your message goes here" //#if MC>=10904 Style coloredDarkGray = new Style().setColor(TextFormatting.DARK_GRAY); @@ -352,7 +423,14 @@ public class ReplayMod { //#endif // Send message to chat GUI // The ingame GUI is initialized at startup, therefore this is possible before the client is connected - mc.ingameGUI.getChatGUI().printChatMessage(text); + */ + mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentTranslation(message, args)); } } + + public static abstract class Module { + public void preInit(FMLPreInitializationEvent event) {} + public void init(FMLInitializationEvent event) {} + public void postInit(FMLPostInitializationEvent event) {} + } } diff --git a/src/main/java/com/replaymod/core/SettingsRegistry.java b/src/main/java/com/replaymod/core/SettingsRegistry.java index 55be8abc..e76ff630 100644 --- a/src/main/java/com/replaymod/core/SettingsRegistry.java +++ b/src/main/java/com/replaymod/core/SettingsRegistry.java @@ -88,7 +88,7 @@ public class SettingsRegistry { } public void save() { - configuration.save(); + // FIXME forge config api is currently broken configuration.save(); } public interface SettingKey { diff --git a/src/main/java/com/replaymod/core/events/SettingsChangedEvent.java b/src/main/java/com/replaymod/core/events/SettingsChangedEvent.java index 47105893..1c8f00c5 100644 --- a/src/main/java/com/replaymod/core/events/SettingsChangedEvent.java +++ b/src/main/java/com/replaymod/core/events/SettingsChangedEvent.java @@ -5,7 +5,11 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.Event; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.Event; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.Event; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.Event; //#endif diff --git a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java index 9a46cfc2..07ace9cb 100644 --- a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java +++ b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java @@ -9,7 +9,11 @@ import net.minecraftforge.common.MinecraftForge; import org.lwjgl.opengl.GL11; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -41,21 +45,25 @@ public class MainMenuHandler { // and then move back down by 10 to compensate for the space to the exit button that was already there int offset = -2 * 24 + 10; y(button, y(button) + offset); - //#if MC>=11202 + } + /* FIXME + //#if MC>=11300 if (button == gui.realmsButton) { realmsOffset = offset; } //#endif } - //#if MC>=11202 + //#if MC>=11300 if (realmsOffset != 0 && gui.realmsNotification instanceof GuiScreenRealmsProxy) { gui.realmsNotification = new RealmsNotificationProxy((GuiScreenRealmsProxy) gui.realmsNotification, realmsOffset); } //#endif + */ } } - //#if MC>=11202 + /* FIXME + //#if MC>=11300 private static class RealmsNotificationProxy extends GuiScreen { private final GuiScreenRealmsProxy proxy; private final int offset; @@ -98,4 +106,5 @@ public class MainMenuHandler { } } //#endif + */ } diff --git a/src/main/java/com/replaymod/core/utils/ModCompat.java b/src/main/java/com/replaymod/core/utils/ModCompat.java index 26dcba5b..8b867fdb 100644 --- a/src/main/java/com/replaymod/core/utils/ModCompat.java +++ b/src/main/java/com/replaymod/core/utils/ModCompat.java @@ -14,8 +14,12 @@ import net.minecraftforge.registries.RegistryManager; //#endif //#if MC>=10800 -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.ModContainer; +//#if MC>=11300 +// FIXME +//#else +//$$ import net.minecraftforge.fml.common.Loader; +//$$ import net.minecraftforge.fml.common.ModContainer; +//#endif //#else //$$ import cpw.mods.fml.common.Loader; //$$ import cpw.mods.fml.common.ModContainer; @@ -29,16 +33,19 @@ import java.util.stream.Stream; public class ModCompat { @SuppressWarnings("unchecked") public static Collection getInstalledNetworkMods() { - Map ignoreCaseMap = Loader.instance().getModList().stream() - .collect(Collectors.toMap(m -> m.getModId().toLowerCase(), Function.identity())); + //#if MC>=11300 + return Stream.empty().collect(Collectors.toList()); + //#else + //$$ Map ignoreCaseMap = Loader.instance().getModList().stream() + //$$ .collect(Collectors.toMap(m -> m.getModId().toLowerCase(), Function.identity())); //#if MC>=11200 - return RegistryManager.ACTIVE.takeSnapshot(false).keySet().stream() - .map(RegistryManager.ACTIVE::getRegistry) - .map(ForgeRegistry::getKeys).flatMap(Set::stream) - .map(ResourceLocation::getResourceDomain).filter(s -> !s.equals("minecraft")).distinct() - .map(String::toLowerCase).map(ignoreCaseMap::get).filter(mod -> mod != null) - .map(mod -> new ModInfo(mod.getModId(), mod.getName(), mod.getVersion())) - .collect(Collectors.toList()); + //$$ return RegistryManager.ACTIVE.takeSnapshot(false).keySet().stream() + //$$ .map(RegistryManager.ACTIVE::getRegistry) + //$$ .map(ForgeRegistry::getKeys).flatMap(Set::stream) + //$$ .map(ResourceLocation::getResourceDomain).filter(s -> !s.equals("minecraft")).distinct() + //$$ .map(String::toLowerCase).map(ignoreCaseMap::get).filter(mod -> mod != null) + //$$ .map(mod -> new ModInfo(mod.getModId(), mod.getName(), mod.getVersion())) + //$$ .collect(Collectors.toList()); //#else //#if MC>=10800 //$$ return Stream.concat( @@ -61,6 +68,7 @@ public class ModCompat { //$$ .collect(Collectors.toList()); //#endif //#endif + //#endif } public static final class ModInfoDifference { diff --git a/src/main/java/com/replaymod/core/utils/OpenGLUtils.java b/src/main/java/com/replaymod/core/utils/OpenGLUtils.java index 4e209865..2d790fa5 100644 --- a/src/main/java/com/replaymod/core/utils/OpenGLUtils.java +++ b/src/main/java/com/replaymod/core/utils/OpenGLUtils.java @@ -11,7 +11,13 @@ public class OpenGLUtils { static { IntBuffer buffer = BufferUtils.createIntBuffer(16); - GL11.glGetInteger(GL11.GL_MAX_VIEWPORT_DIMS, buffer); + //#if MC>=11300 + // FIXME GL11.glGetIntegerv(GL11.GL_MAX_VIEWPORT_DIMS, buffer); + buffer.put(0xffff); + buffer.put(0xffff); + //#else + //$$ GL11.glGetInteger(GL11.GL_MAX_VIEWPORT_DIMS, buffer); + //#endif VIEWPORT_MAX_WIDTH = buffer.get(); VIEWPORT_MAX_HEIGHT = buffer.get(); } diff --git a/src/main/java/com/replaymod/core/utils/Restrictions.java b/src/main/java/com/replaymod/core/utils/Restrictions.java index c198baf6..2a5f870f 100644 --- a/src/main/java/com/replaymod/core/utils/Restrictions.java +++ b/src/main/java/com/replaymod/core/utils/Restrictions.java @@ -18,6 +18,7 @@ import static com.replaymod.core.versions.MCVer.readString; * @see Replay Restrictions Gist */ public class Restrictions { + // FIXME these should be ResourceLocations now public static final String PLUGIN_CHANNEL = "Replay|Restrict"; private boolean noXray; private boolean noNoclip; diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index cef6e9a2..9fc63869 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -18,15 +18,19 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.utils.Colors; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.gui.GuiScreen; import net.minecraft.crash.CrashReport; import net.minecraft.util.ResourceLocation; import org.apache.commons.io.Charsets; import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.Logger; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; + +//#if MC>=11300 +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 import net.minecraft.client.network.NetworkPlayerInfo; @@ -64,7 +68,8 @@ import java.util.Date; import java.util.UUID; import java.util.function.Consumer; -import static net.minecraft.client.Minecraft.getMinecraft; +import static com.replaymod.core.versions.MCVer.Minecraft_mcDataDir; +import static com.replaymod.core.versions.MCVer.getMinecraft; public class Utils { @@ -189,7 +194,11 @@ public class Utils { } public static boolean isCtrlDown() { - return Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL); + //#if MC>=11300 + return GuiScreen.isCtrlKeyDown(); + //#else + //$$ return Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL); + //#endif } public static void addCallback(ListenableFuture future, Consumer onSuccess, Consumer onFailure) { @@ -216,7 +225,7 @@ public class Utils { // Try to save the crash report if (crashReport.getFile() == null) { try { - File folder = new File(getMinecraft().mcDataDir, "crash-reports"); + File folder = new File(Minecraft_mcDataDir(getMinecraft()), "crash-reports"); File file = new File(folder, "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-client.txt"); logger.debug("Saving crash report to file: {}", file); crashReport.saveToFile(file); @@ -262,8 +271,8 @@ public class Utils { l -> new GuiLabel().setText(l).setColor(Colors.BLACK)).toArray(GuiElement[]::new))); // Replace close button with panel containing close and copy buttons - GuiButton copyToClipboardButton = new GuiButton().setI18nLabel("chat.copy").onClick(() -> - GuiScreen.setClipboardString(crashReport)).setSize(150, 20); + GuiButton copyToClipboardButton = new GuiButton().setI18nLabel("chat.copy")/* FIXME .onClick(() -> + //GuiScreen.setClipboardString(crashReport))*/.setSize(150, 20); GuiButton closeButton = getCloseButton(); popup.removeElement(closeButton); popup.addElements(new VerticalLayout.Data(1), diff --git a/src/main/java/com/replaymod/core/utils/WrappedTimer.java b/src/main/java/com/replaymod/core/utils/WrappedTimer.java index 8063d24c..9d664035 100644 --- a/src/main/java/com/replaymod/core/utils/WrappedTimer.java +++ b/src/main/java/com/replaymod/core/utils/WrappedTimer.java @@ -8,15 +8,27 @@ public class WrappedTimer extends Timer { protected final Timer wrapped; public WrappedTimer(Timer wrapped) { - super(0); + //#if MC>=11300 + super(0, 0); + //#else + //$$ super(0); + //#endif this.wrapped = wrapped; copy(wrapped, this); } @Override - public void updateTimer() { + public void updateTimer( + //#if MC>=11300 + long sysClock + //#endif + ) { copy(this, wrapped); - wrapped.updateTimer(); + wrapped.updateTimer( + //#if MC>=11300 + sysClock + //#endif + ); copy(wrapped, this); } @@ -26,7 +38,7 @@ public class WrappedTimer extends Timer { to.lastSyncSysClock = from.lastSyncSysClock; to.elapsedPartialTicks = from.elapsedPartialTicks; //#if MC>=11200 - to.tickLength = from.tickLength; + // FIXME (should be good to go once AT are applied) to.tickLength = from.tickLength; //#else //$$ to.ticksPerSecond = from.ticksPerSecond; //$$ to.lastHRTime = from.lastHRTime; diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index ddd3a51d..fba3f8fd 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -6,21 +6,22 @@ import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.ServerList; import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.entity.RenderManager; -import net.minecraft.client.resources.ResourcePackRepository; import net.minecraft.client.settings.KeyBinding; +import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.network.Packet; import net.minecraft.network.PacketBuffer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.Util; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraftforge.client.event.GuiScreenEvent; @@ -28,6 +29,20 @@ import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderLivingEvent; import net.minecraftforge.common.MinecraftForge; +//#if MC>=11300 +import net.minecraft.client.MainWindow; +import net.minecraft.crash.ReportedException; +import org.lwjgl.glfw.GLFW; +//#else +//$$ import net.minecraft.client.gui.ScaledResolution; +//$$ import net.minecraft.client.resources.ResourcePackRepository; +//$$ import net.minecraft.util.ReportedException; +//$$ import org.apache.logging.log4j.LogManager; +//$$ import org.lwjgl.Sys; +//$$ import java.awt.Desktop; +//#endif + + //#if MC>=10904 //#if MC>=11200 import net.minecraft.client.renderer.BufferBuilder; @@ -53,9 +68,13 @@ import net.minecraft.client.renderer.vertex.DefaultVertexFormats; //#if MC>=10800 import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.GlStateManager.BooleanState; +// FIXME import net.minecraft.client.renderer.GlStateManager.BooleanState; import net.minecraft.world.WorldType; -import net.minecraftforge.fml.common.eventhandler.EventBus; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.IEventBus; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.EventBus; +//#endif //#else //$$ import com.google.common.util.concurrent.Futures; //$$ import com.replaymod.render.hooks.GLStateTracker; @@ -77,12 +96,17 @@ import java.util.concurrent.Callable; * Abstraction over things that have changed between different MC versions. */ public class MCVer { - public static EventBus FORGE_BUS = MinecraftForge.EVENT_BUS; + //#if MC>=11300 + public static IEventBus FORGE_BUS = MinecraftForge.EVENT_BUS; + public static IEventBus FML_BUS = FORGE_BUS; + //#else + //$$ public static EventBus FORGE_BUS = MinecraftForge.EVENT_BUS; //#if MC>=10809 - public static EventBus FML_BUS = FORGE_BUS; + //$$ public static EventBus FML_BUS = FORGE_BUS; //#else //$$ public static EventBus FML_BUS = FMLCommonHandler.instance().bus(); //#endif + //#endif public static void addDetail(CrashReportCategory category, String name, Callable callable) { //#if MC>=10904 @@ -218,6 +242,14 @@ public class MCVer { //#endif //#endif + public static File mcDataDir(Minecraft mc) { + //#if MC>=11300 + return mc.gameDir; + //#else + //$$ return mc.mcDataDir; + //#endif + } + //#if MC>=10800 public static Entity getRenderViewEntity(Minecraft mc) { return mc.getRenderViewEntity(); @@ -297,9 +329,13 @@ public class MCVer { return world.playerEntities; } + /* FIXME public static BooleanState fog() { + //#if MC>=11300 + return GlStateManager.FOG.fog; + //#else //#if MC>=10809 - return GlStateManager.fogState.fog; + //$$ return GlStateManager.fogState.fog; //#else //#if MC>=10800 //$$ return GlStateManager.fogState.field_179049_a; @@ -307,11 +343,15 @@ public class MCVer { //$$ return GLStateTracker.getInstance().fog; //#endif //#endif + //#endif } public static void fog(BooleanState fog) { + //#if MC>=11300 + GlStateManager.FOG.fog = fog; + //#else //#if MC>=10809 - GlStateManager.fogState.fog = fog; + //$$ GlStateManager.fogState.fog = fog; //#else //#if MC>=10800 //$$ GlStateManager.fogState.field_179049_a = fog; @@ -319,23 +359,33 @@ public class MCVer { //$$ GLStateTracker.getInstance().fog = fog; //#endif //#endif + //#endif } public static BooleanState texture2DState(int index) { + //#if MC>=11300 + return GlStateManager.TEXTURES[index].texture2DState; + //#else //#if MC>=10800 - return GlStateManager.textureState[index].texture2DState; + //$$ return GlStateManager.textureState[index].texture2DState; //#else //$$ return GLStateTracker.getInstance().texture[index]; //#endif + //#endif } public static void texture2DState(int index, BooleanState texture2DState) { + //#if MC>=11300 + GlStateManager.TEXTURES[index].texture2DState = texture2DState; + //#else //#if MC>=10800 - GlStateManager.textureState[index].texture2DState = texture2DState; + //$$ GlStateManager.textureState[index].texture2DState = texture2DState; //#else //$$ GLStateTracker.getInstance().texture[index] = texture2DState; //#endif + //#endif } + */ public static void ServerList_saveSingleServer(ServerData serverData) { //#if MC>=10904 @@ -353,14 +403,22 @@ public class MCVer { //#endif } - public static ScaledResolution newScaledResolution(Minecraft mc) { - //#if MC>=10809 - return new ScaledResolution(mc); - //#else - //$$ return new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); - //#endif - } + //#if MC>=11300 + public static MainWindow newScaledResolution(Minecraft mc) { + return mc.mainWindow; + } + //#else + //$$ public static ScaledResolution newScaledResolution(Minecraft mc) { + //#if MC>=10809 + //$$ return new ScaledResolution(mc); + //#else + //$$ return new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + //#endif + //$$ } + //#endif + + /* FIXME public static ListenableFuture setServerResourcePack(ResourcePackRepository repo, File file) { //#if MC>=10809 //#if MC>=11200 @@ -379,6 +437,7 @@ public class MCVer { //#endif //#endif } + */ public static boolean isKeyDown(KeyBinding keyBinding) { //#if MC>=10800 @@ -523,12 +582,28 @@ public class MCVer { public static RenderManager getRenderManager() { //#if MC>=10800 - return Minecraft.getMinecraft().getRenderManager(); + return getMinecraft().getRenderManager(); //#else //$$ return RenderManager.instance; //#endif } + public static Minecraft getMinecraft() { + //#if MC>=11300 + return Minecraft.getInstance(); + //#else + //$$ return Minecraft.getMinecraft(); + //#endif + } + + public static File Minecraft_mcDataDir(Minecraft mc) { + //#if MC>=11300 + return mc.gameDir; + //#else + //$$ return mc.mcDataDir; + //#endif + } + public static int floor(double val) { //#if MC>=11102 return MathHelper.floor(val); @@ -537,6 +612,14 @@ public class MCVer { //#endif } + public static void bindTexture(ResourceLocation texture) { + //#if MC>=11300 + getMinecraft().getTextureManager().bindTexture(texture); + //#else + //$$ getMinecraft().renderEngine.bindTexture(texture); + //#endif + } + public static float cos(float val) { return MathHelper.cos(val); } @@ -545,6 +628,40 @@ public class MCVer { return MathHelper.sin(val); } + public static ReportedException newReportedException(CrashReport crashReport) { + return new ReportedException(crashReport); + } + + public static void openFile(File file) { + //#if MC>=11300 + Util.getOSType().openFile(file); + //#else + //$$ String path = file.getAbsolutePath(); + //$$ + //$$ // First try OS specific methods + //$$ try { + //$$ switch (Util.getOSType()) { + //$$ case WINDOWS: + //$$ Runtime.getRuntime().exec(String.format("cmd.exe /C start \"Open file\" \"%s\"", path)); + //$$ return; + //$$ case OSX: + //$$ Runtime.getRuntime().exec(new String[]{"/usr/bin/open", path}); + //$$ return; + //$$ } + //$$ } catch (IOException e) { + //$$ LogManager.getLogger().error("Cannot open file", e); + //$$ } + //$$ + //$$ // Otherwise try to java way + //$$ try { + //$$ Desktop.getDesktop().browse(file.toURI()); + //$$ } catch (Throwable throwable) { + //$$ // And if all fails, lwjgl + //$$ Sys.openURL("file://" + path); + //$$ } + //#endif + } + //#if MC<=10710 //$$ public static class GlStateManager { //$$ public static void resetColor() { /* nop */ } @@ -561,4 +678,53 @@ public class MCVer { //$$ public static void rotate(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } //$$ } //#endif + + //#if MC>=11300 + public static void color(float r, float g, float b, float a) { GlStateManager.color4f(r, g, b, a); } + public static void enableAlpha() { GlStateManager.enableAlphaTest(); } + public static void disableAlpha() { GlStateManager.disableAlphaTest(); } + public static void tryBlendFuncSeparate(int l, int r, int vl, int vr) { GlStateManager.blendFuncSeparate(l, r, vl, vr); } + public static void colorLogicOp(int op) { GlStateManager.logicOp(op); } + + public static abstract class Keyboard { + public static final int KEY_ESCAPE = GLFW.GLFW_KEY_ESCAPE; + public static final int KEY_HOME = GLFW.GLFW_KEY_HOME; + public static final int KEY_END = GLFW.GLFW_KEY_END; + public static final int KEY_UP = GLFW.GLFW_KEY_UP; + public static final int KEY_DOWN = GLFW.GLFW_KEY_DOWN; + public static final int KEY_LEFT = GLFW.GLFW_KEY_LEFT; + public static final int KEY_RIGHT = GLFW.GLFW_KEY_RIGHT; + public static final int KEY_BACK = GLFW.GLFW_KEY_BACKSPACE; + public static final int KEY_DELETE = GLFW.GLFW_KEY_DELETE; + public static final int KEY_RETURN = GLFW.GLFW_KEY_ENTER; + public static final int KEY_TAB = GLFW.GLFW_KEY_TAB; + public static final int KEY_F1 = GLFW.GLFW_KEY_F1; + public static final int KEY_A = GLFW.GLFW_KEY_A; + public static final int KEY_B = GLFW.GLFW_KEY_B; + public static final int KEY_C = GLFW.GLFW_KEY_C; + public static final int KEY_D = GLFW.GLFW_KEY_D; + public static final int KEY_E = GLFW.GLFW_KEY_E; + public static final int KEY_F = GLFW.GLFW_KEY_F; + public static final int KEY_G = GLFW.GLFW_KEY_G; + public static final int KEY_H = GLFW.GLFW_KEY_H; + public static final int KEY_I = GLFW.GLFW_KEY_I; + public static final int KEY_J = GLFW.GLFW_KEY_J; + public static final int KEY_K = GLFW.GLFW_KEY_K; + public static final int KEY_L = GLFW.GLFW_KEY_L; + public static final int KEY_M = GLFW.GLFW_KEY_M; + public static final int KEY_N = GLFW.GLFW_KEY_N; + public static final int KEY_O = GLFW.GLFW_KEY_O; + public static final int KEY_P = GLFW.GLFW_KEY_P; + public static final int KEY_Q = GLFW.GLFW_KEY_Q; + public static final int KEY_R = GLFW.GLFW_KEY_R; + public static final int KEY_S = GLFW.GLFW_KEY_S; + public static final int KEY_T = GLFW.GLFW_KEY_T; + public static final int KEY_U = GLFW.GLFW_KEY_U; + public static final int KEY_V = GLFW.GLFW_KEY_V; + public static final int KEY_W = GLFW.GLFW_KEY_W; + public static final int KEY_X = GLFW.GLFW_KEY_X; + public static final int KEY_Y = GLFW.GLFW_KEY_Y; + public static final int KEY_Z = GLFW.GLFW_KEY_Z; + } + //#endif } diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index fef2207e..2035ec80 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -5,53 +5,54 @@ import com.replaymod.core.utils.Restrictions; import com.replaymod.recording.handler.ConnectionEventHandler; import com.replaymod.recording.handler.GuiHandler; import com.replaymod.recording.packet.PacketListener; +import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; +import io.netty.util.AttributeKey; import net.minecraft.network.NetworkManager; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.lwjgl.input.Keyboard; + +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 -import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.eventhandler.EventBus; -import net.minecraftforge.fml.common.network.NetworkRegistry; +//#if MC>=11300 +import net.minecraftforge.fml.network.NetworkRegistry; +//#else +//$$ import net.minecraftforge.fml.common.network.NetworkRegistry; +//#endif //#else -//$$ import cpw.mods.fml.common.Mod; //$$ import cpw.mods.fml.common.event.FMLInitializationEvent; //$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; -//$$ import cpw.mods.fml.common.eventhandler.EventBus; //$$ import cpw.mods.fml.common.network.NetworkRegistry; //#endif import static com.replaymod.core.versions.MCVer.*; -@Mod(modid = ReplayModRecording.MOD_ID, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", - //#if MC>=10800 - clientSideOnly = true, - //#endif - useMetadata = true) -public class ReplayModRecording { - public static final String MOD_ID = "replaymod-recording"; +public class ReplayModRecording extends ReplayMod.Module { - @Mod.Instance(MOD_ID) + private static final Logger LOGGER = LogManager.getLogger(); + private static final AttributeKey ATTR_CHECKED = AttributeKey.newInstance("ReplayModRecording_checked"); + + { instance = this; } public static ReplayModRecording instance; private ReplayMod core; - private Logger logger; - private ConnectionEventHandler connectionEventHandler; - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); - core = ReplayMod.instance; + public ReplayModRecording(ReplayMod mod) { + core = mod; + } + @Override + public void preInit(FMLPreInitializationEvent event) { core.getSettingsRegistry().register(Setting.class); core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.marker", Keyboard.KEY_M, new Runnable() { @@ -66,20 +67,27 @@ public class ReplayModRecording { }); } - @Mod.EventHandler + @Override public void init(FMLInitializationEvent event) { - EventBus bus = FML_BUS; - bus.register(connectionEventHandler = new ConnectionEventHandler(logger, core)); + FML_BUS.register(connectionEventHandler = new ConnectionEventHandler(LOGGER, core)); new GuiHandler(core).register(); - NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler()); + //#if MC>=11300 + // FIXME + //#else + //$$ NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler()); + //#endif } @ChannelHandler.Sharable private static class RestrictionsChannelHandler extends ChannelDuplexHandler {} public void initiateRecording(NetworkManager networkManager) { + Channel channel = networkManager.channel(); + if (channel.pipeline().get("ReplayModReplay_replaySender") != null) return; + if (channel.hasAttr(ATTR_CHECKED)) return; + channel.attr(ATTR_CHECKED).set(null); connectionEventHandler.onConnectedToServerEvent(networkManager); } } diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java index 467abfcc..ad21b033 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java @@ -10,8 +10,12 @@ import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.MinecraftForge; //#if MC>=10800 -import net.minecraft.client.renderer.GlStateManager; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif +import static net.minecraft.client.renderer.GlStateManager.*; //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -50,9 +54,9 @@ public class GuiRecordingOverlay { if (settingsRegistry.get(Setting.INDICATOR)) { FontRenderer fontRenderer = getFontRenderer(mc); fontRenderer.drawString(I18n.format("replaymod.gui.recording").toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff); - mc.renderEngine.bindTexture(TEXTURE); - GlStateManager.resetColor(); - GlStateManager.enableAlpha(); + bindTexture(TEXTURE); + resetColor(); + enableAlpha(); Gui.drawModalRectWithCustomSizedTexture(10, 10, 58, 20, 16, 16, TEXTURE_SIZE, TEXTURE_SIZE); } } diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index 1daef60b..c098ce8d 100755 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -15,8 +15,12 @@ import net.minecraft.network.NetworkManager; import org.apache.logging.log4j.Logger; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//$$ import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; +//#endif import static com.replaymod.core.versions.MCVer.WorldType_DEBUG_ALL_BLOCK_STATES; //#else @@ -28,6 +32,8 @@ import java.io.File; import java.text.SimpleDateFormat; import java.util.Calendar; +import static com.replaymod.core.versions.MCVer.getMinecraft; + /** * Handles connection events and initiates recording if enabled. */ @@ -36,7 +42,7 @@ public class ConnectionEventHandler { private static final String packetHandlerKey = "packet_handler"; private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss"; private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); - private static final Minecraft mc = Minecraft.getMinecraft(); + private static final Minecraft mc = getMinecraft(); private final Logger logger; private final ReplayMod core; @@ -55,7 +61,11 @@ public class ConnectionEventHandler { boolean local = networkManager.isLocalChannel(); if (local) { //#if MC>=10800 - if (mc.getIntegratedServer().getEntityWorld().getWorldType() == WorldType_DEBUG_ALL_BLOCK_STATES) { + //#if MC>=11300 + if (mc.getIntegratedServer().getWorld(0).getWorldType() == WorldType_DEBUG_ALL_BLOCK_STATES) { + //#else + //$$ if (mc.getIntegratedServer().getEntityWorld().getWorldType() == WorldType_DEBUG_ALL_BLOCK_STATES) { + //#endif logger.info("Debug World recording is not supported."); return; } @@ -74,10 +84,10 @@ public class ConnectionEventHandler { String worldName; if (local) { worldName = mc.getIntegratedServer().getWorldName(); - } else if (Minecraft.getMinecraft().getCurrentServerData() != null) { - worldName = Minecraft.getMinecraft().getCurrentServerData().serverIP; + } else if (mc.getCurrentServerData() != null) { + worldName = mc.getCurrentServerData().serverIP; //#if MC>=11100 - } else if (Minecraft.getMinecraft().isConnectedToRealms()) { + } else if (mc.isConnectedToRealms()) { // we can't access the server name without tapping too deep in the Realms Library worldName = "A Realms Server"; //#endif @@ -97,7 +107,7 @@ public class ConnectionEventHandler { ReplayMetaData metaData = new ReplayMetaData(); metaData.setSingleplayer(local); metaData.setServerName(worldName); - metaData.setGenerator("ReplayMod v" + ReplayMod.getContainer().getVersion()); + metaData.setGenerator("ReplayMod v" + ReplayMod.instance.getVersion()); metaData.setDate(System.currentTimeMillis()); metaData.setMcVersion(ReplayMod.getMinecraftVersion()); packetListener = new PacketListener(replayFile, metaData); @@ -116,6 +126,7 @@ public class ConnectionEventHandler { } } + /* FIXME event not (yet?) in 1.13 @SubscribeEvent public void onDisconnectedFromServerEvent(ClientDisconnectionFromServerEvent event) { if (packetListener != null) { @@ -126,6 +137,7 @@ public class ConnectionEventHandler { packetListener = null; } } + */ public PacketListener getPacketListener() { return packetListener; diff --git a/src/main/java/com/replaymod/recording/handler/FMLHandshakeFilter.java b/src/main/java/com/replaymod/recording/handler/FMLHandshakeFilter.java index 036be75f..d58262de 100644 --- a/src/main/java/com/replaymod/recording/handler/FMLHandshakeFilter.java +++ b/src/main/java/com/replaymod/recording/handler/FMLHandshakeFilter.java @@ -1,34 +1,36 @@ -package com.replaymod.recording.handler; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; - +//#if MC<11300 +//$$ package com.replaymod.recording.handler; +//$$ +//$$ import io.netty.channel.ChannelHandlerContext; +//$$ import io.netty.channel.SimpleChannelInboundHandler; +//$$ //#if MC>=10800 -import net.minecraftforge.fml.common.network.handshake.FMLHandshakeMessage; +//$$ import net.minecraftforge.fml.common.network.handshake.FMLHandshakeMessage; //#else //$$ import cpw.mods.fml.common.network.handshake.FMLHandshakeMessage; //#endif - -/** - * Filters out all handshake packets that were sent for recording but must - * not actually be handled. - * This handler is only present when connected to the integrated server as - * otherwise all packets must be handled. - * - * When in single player, the game state packets must never be handled - * otherwise wired bugs related to semi-singletons can occur. - * See https://bugs.replaymod.com/show_bug.cgi?id=44 - */ -public class FMLHandshakeFilter extends SimpleChannelInboundHandler { - @Override - protected void channelRead0(ChannelHandlerContext ctx, FMLHandshakeMessage msg) throws Exception { +//$$ +//$$ /** +//$$ * Filters out all handshake packets that were sent for recording but must +//$$ * not actually be handled. +//$$ * This handler is only present when connected to the integrated server as +//$$ * otherwise all packets must be handled. +//$$ * +//$$ * When in single player, the game state packets must never be handled +//$$ * otherwise wired bugs related to semi-singletons can occur. +//$$ * See https://bugs.replaymod.com/show_bug.cgi?id=44 +//$$ */ +//$$ public class FMLHandshakeFilter extends SimpleChannelInboundHandler { +//$$ @Override +//$$ protected void channelRead0(ChannelHandlerContext ctx, FMLHandshakeMessage msg) throws Exception { //#if MC>=10800 - if (!(msg instanceof FMLHandshakeMessage.RegistryData)) { + //$$ if (!(msg instanceof FMLHandshakeMessage.RegistryData)) { //#else //$$ if (!(msg instanceof FMLHandshakeMessage.ModIdData)) { //#endif - // Pass on everything but RegistryData messages - ctx.fireChannelRead(msg); - } - } -} +//$$ // Pass on everything but RegistryData messages +//$$ ctx.fireChannelRead(msg); +//$$ } +//$$ } +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/recording/handler/GuiHandler.java b/src/main/java/com/replaymod/recording/handler/GuiHandler.java index 8a1e7b94..6d76f468 100644 --- a/src/main/java/com/replaymod/recording/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/recording/handler/GuiHandler.java @@ -17,7 +17,11 @@ import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.common.MinecraftForge; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index 2a44af9e..c5c378ca 100755 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -7,11 +7,15 @@ import net.minecraft.item.ItemStack; import net.minecraft.network.Packet; import net.minecraft.network.play.server.*; import net.minecraft.server.integrated.IntegratedServer; -import net.minecraftforge.event.entity.minecart.MinecartInteractEvent; +// FIXME not (yet?) 1.13 import net.minecraftforge.event.entity.minecart.MinecartInteractEvent; import net.minecraftforge.event.entity.player.PlayerSleepInBedEvent; //#if MC>=10904 -import net.minecraft.entity.EntityLiving; +//#if MC>=11300 +import net.minecraft.entity.EntityLivingBase; +//#else +//$$ import net.minecraft.entity.EntityLiving; +//#endif import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.util.EnumHand; @@ -25,7 +29,11 @@ import net.minecraft.util.math.BlockPos; //#endif //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif import net.minecraftforge.fml.common.gameevent.PlayerEvent.ItemPickupEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent.PlayerTickEvent; @@ -42,7 +50,7 @@ import static com.replaymod.core.versions.MCVer.*; public class RecordingEventHandler { - private final Minecraft mc = Minecraft.getMinecraft(); + private final Minecraft mc = getMinecraft(); private final PacketListener packetListener; private Double lastX, lastY, lastZ; @@ -171,9 +179,15 @@ public class RecordingEventHandler { byte newPitch = (byte) ((int) (e.player.rotationPitch * 256.0F / 360.0F)); //#if MC>=10904 - packet = new SPacketEntity.S17PacketEntityLookMove(e.player.getEntityId(), + //#if MC>=11300 + packet = new SPacketEntity.Move( + //#else + //$$ packet = new SPacketEntity.S17PacketEntityLookMove( + //#endif + e.player.getEntityId(), (short) Math.round(dx * 4096), (short) Math.round(dy * 4096), (short) Math.round(dz * 4096), - newYaw, newPitch, e.player.onGround); + newYaw, newPitch, e.player.onGround + ); //#else //$$ packet = new S14PacketEntity.S17PacketEntityLookMove(e.player.getEntityId(), //$$ (byte) Math.round(dx * 32), (byte) Math.round(dy * 32), (byte) Math.round(dz * 32), @@ -300,9 +314,15 @@ public class RecordingEventHandler { //Leaving Ride - if((!player(mc).isRiding() && lastRiding != -1) || - (player(mc).isRiding() && lastRiding != getRidingEntity(player(mc)).getEntityId())) { - if(!player(mc).isRiding()) { + //#if MC>=11300 + if((!player(mc).isPassenger() && lastRiding != -1) || + (player(mc).isPassenger() && lastRiding != getRidingEntity(player(mc)).getEntityId())) { + if(!player(mc).isPassenger()) { + //#else + //$$ if((!player(mc).isRiding() && lastRiding != -1) || + //$$ (player(mc).isRiding() && lastRiding != getRidingEntity(player(mc)).getEntityId())) { + //$$ if(!player(mc).isRiding()) { + //#endif lastRiding = -1; } else { lastRiding = getRidingEntity(player(mc)).getEntityId(); @@ -331,7 +351,11 @@ public class RecordingEventHandler { lastActiveHand = player(mc).getActiveHand(); EntityDataManager dataManager = new EntityDataManager(null); int state = (wasHandActive ? 1 : 0) | (lastActiveHand == EnumHand.OFF_HAND ? 2 : 0); - dataManager.register(EntityLiving.HAND_STATES, (byte) state); + //#if MC>=11300 + dataManager.register(EntityLivingBase.LIVING_FLAGS, (byte) state); + //#else + //$$ dataManager.register(EntityLiving.HAND_STATES, (byte) state); + //#endif packetListener.save(new SPacketEntityMetadata(player(mc).getEntityId(), dataManager, true)); } //#endif @@ -394,6 +418,7 @@ public class RecordingEventHandler { } } + /* FIXME event not (yet?) on 1.13 @SubscribeEvent public void enterMinecart(MinecartInteractEvent event) { try { @@ -418,6 +443,7 @@ public class RecordingEventHandler { e.printStackTrace(); } } + */ //#if MC>=10800 public void onBlockBreakAnim(int breakerId, BlockPos pos, int progress) { diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java index 2431c3a0..a09681ef 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java @@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; //#if MC>=10800 -import net.minecraftforge.fml.common.network.FMLNetworkEvent; +// FIXME event not (yet?) in 1.13 import net.minecraftforge.fml.common.network.FMLNetworkEvent; //#else //$$ import cpw.mods.fml.common.network.FMLNetworkEvent; //#endif @@ -25,12 +25,16 @@ public abstract class MixinNetHandlerLoginClient { //$$ private NetworkManager field_147393_d; //#endif - /** - * Starts the recording right before switching into PLAY state. - * We cannot use the {@link FMLNetworkEvent.ClientConnectedToServerEvent} - * as it only fires after the forge handshake. - */ - @Inject(method = "handleLoginSuccess", at=@At("HEAD")) + //#if MC>=11300 + @Inject(method = "func_209521_a", at=@At("HEAD")) + //#else + //$$ /** + //$$ * Starts the recording right before switching into PLAY state. + //$$ * We cannot use the {@link FMLNetworkEvent.ClientConnectedToServerEvent} + //$$ * as it only fires after the forge handshake. + //$$ */ + //$$ @Inject(method = "handleLoginSuccess", at=@At("HEAD")) + //#endif public void replayModRecording_initiateRecording(CallbackInfo cb) { //#if MC>=10800 ReplayModRecording.instance.initiateRecording(networkManager); diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java index c7642946..a065f643 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java @@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; //#if MC>=10800 //#if MC>=10904 import net.minecraft.network.play.server.SPacketPlayerListItem.Action; -import net.minecraft.network.play.server.SPacketPlayerListItem.AddPlayerData; +// FIXME requires RuntimeInvisParam workaround import net.minecraft.network.play.server.SPacketPlayerListItem.AddPlayerData; //#else //$$ import net.minecraft.network.play.server.S38PacketPlayerListItem.Action; //$$ import net.minecraft.network.play.server.S38PacketPlayerListItem.AddPlayerData; @@ -61,6 +61,7 @@ public abstract class MixinNetHandlerPlayClient { RecordingEventHandler handler = getRecordingEventHandler(); if (handler != null && packet.getAction() == Action.ADD_PLAYER) { + /* FIXME see import for (AddPlayerData data : packet.getEntries()) { if (data.getProfile() == null || data.getProfile().getId() == null) continue; // Only add spawn packet for our own player and only if he isn't known yet @@ -69,6 +70,7 @@ public abstract class MixinNetHandlerPlayClient { handler.onPlayerJoin(); } } + */ } } //#else diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java b/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java index a0a30a11..1355c31c 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java @@ -1,3 +1,4 @@ +/* FIXME package com.replaymod.recording.mixin; import com.replaymod.recording.handler.FMLHandshakeFilter; @@ -42,6 +43,7 @@ public abstract class MixinNetworkDispatcher { * Injects a {@link FMLHandshakeFilter} on the client side to filter out * those extra, unexpected packets. */ +/* @Inject(method = "insertIntoChannel", at=@At("HEAD")) public void replayModRecording_setupForLocalRecording(CallbackInfo cb) { // If we're in multiplayer, everything is fine as is @@ -73,3 +75,4 @@ public abstract class MixinNetworkDispatcher { } //#endif } +*/ diff --git a/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java b/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java index 91b216ff..1b20b9fe 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java @@ -23,10 +23,12 @@ public abstract class MixinPlayerControllerMP implements RecordingEventHandler.R // The new method will then play it and (if applicable) record it. (See MixinWorldClient) // This is necessary for the block break event (particles and sound) to be recorded. Otherwise it looks like the // event was emitted because of a packet (player will be null) and not as it actually was (by the player). + /* FIXME test if this is still an issue (the call seems to be gone) @Redirect(method = "onPlayerDestroyBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;playEvent(ILnet/minecraft/util/math/BlockPos;I)V")) public void replayModRecording_playEvent_fixed(World world, int type, BlockPos pos, int data) { world.playEvent(player(mc), type, pos, data); } + */ } //#endif diff --git a/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java index ad65c8c7..b606ec8e 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java @@ -15,6 +15,7 @@ import net.minecraft.util.math.BlockPos; //#endif //#endif +/* FIXME runtimeinvisparameter @Mixin(RenderGlobal.class) public abstract class MixinRenderGlobal implements RecordingEventHandler.RecordingEventSender { @@ -53,3 +54,4 @@ public abstract class MixinRenderGlobal implements RecordingEventHandler.Recordi } } } +*/ diff --git a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java index 2cbce729..e885d5f9 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java @@ -10,7 +10,6 @@ import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -import net.minecraft.world.WorldProvider; import net.minecraft.world.storage.ISaveHandler; import net.minecraft.world.storage.WorldInfo; import org.spongepowered.asm.mixin.Mixin; @@ -19,6 +18,12 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11300 +import net.minecraft.world.dimension.Dimension; +//#else +//$$ import net.minecraft.world.WorldProvider; +//#endif + import static com.replaymod.core.versions.MCVer.*; @Mixin(WorldClient.class) @@ -26,7 +31,13 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa @Shadow private Minecraft mc; - protected MixinWorldClient(ISaveHandler saveHandlerIn, WorldInfo info, WorldProvider providerIn, Profiler profilerIn, boolean client) { + protected MixinWorldClient(ISaveHandler saveHandlerIn, WorldInfo info, + //#if MC>=11300 + Dimension providerIn, + //#else + //$$ WorldProvider providerIn, + //#endif + Profiler profilerIn, boolean client) { super(saveHandlerIn, info, providerIn, profilerIn, client); } diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index eddd68f1..e7e6be68 100755 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -29,7 +29,7 @@ import net.minecraft.util.text.TextComponentString; //#if MC>=10800 import net.minecraft.network.EnumPacketDirection; -import net.minecraftforge.fml.common.network.internal.FMLProxyPacket; +// FIXME import net.minecraftforge.fml.common.network.internal.FMLProxyPacket; //#else //$$ import cpw.mods.fml.common.network.internal.FMLProxyPacket; //#endif @@ -49,7 +49,7 @@ import static com.replaymod.core.versions.MCVer.*; public class PacketListener extends ChannelInboundHandlerAdapter { - private static final Minecraft mc = Minecraft.getMinecraft(); + private static final Minecraft mc = getMinecraft(); private static final Logger logger = LogManager.getLogger(); private final ReplayFile replayFile; @@ -229,6 +229,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter { //#endif //#endif + /* FIXME if (packet instanceof FMLProxyPacket) { // This packet requires special handling //#if MC>=10800 @@ -239,6 +240,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter { super.channelRead(ctx, msg); return; } + */ save(packet); @@ -358,7 +360,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter { } public void addMarker() { - Entity view = getRenderViewEntity(Minecraft.getMinecraft()); + Entity view = getRenderViewEntity(mc); int timestamp = (int) (System.currentTimeMillis() - startTime); Marker marker = new Marker(); diff --git a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java index c5818c9e..019d5539 100644 --- a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java +++ b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java @@ -9,7 +9,7 @@ import net.minecraft.client.gui.GuiYesNo; import net.minecraft.client.gui.GuiYesNoCallback; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.resources.I18n; -import net.minecraft.client.resources.ResourcePackRepository; +// FIXME import net.minecraft.client.resources.ResourcePackRepository; import net.minecraft.util.HttpUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -36,8 +36,6 @@ import net.minecraft.network.NetworkManager; import org.apache.commons.io.FileUtils; import javax.annotation.Nonnull; - -import static com.replaymod.core.versions.MCVer.*; //#else //$$ import net.minecraft.client.multiplayer.ServerData.ServerResourceMode; //$$ import net.minecraft.client.multiplayer.ServerList; @@ -52,12 +50,14 @@ import java.io.OutputStream; import java.util.HashMap; import java.util.Map; +import static com.replaymod.core.versions.MCVer.*; + /** * Records resource packs and handles incoming resource pack packets during recording. */ public class ResourcePackRecorder { private static final Logger logger = LogManager.getLogger(); - private static final Minecraft mc = Minecraft.getMinecraft(); + private static final Minecraft mc = getMinecraft(); private final ReplayFile replayFile; @@ -131,11 +131,12 @@ public class ResourcePackRecorder { if (url.startsWith("level://")) { String levelName = url.substring("level://".length()); - File savesDir = new File(mc.mcDataDir, "saves"); + File savesDir = new File(mcDataDir(mc), "saves"); final File levelDir = new File(savesDir, levelName); if (levelDir.isFile()) { netManager.sendPacket(makeStatusPacket(hash, Action.ACCEPTED)); + /* FIXME Futures.addCallback(setServerResourcePack(mc.getResourcePackRepository(), levelDir), new FutureCallback() { @Override public void onSuccess(Object result) { @@ -148,6 +149,7 @@ public class ResourcePackRecorder { netManager.sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); } }); + */ } else { netManager.sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); } @@ -155,7 +157,7 @@ public class ResourcePackRecorder { final ServerData serverData = mc.getCurrentServerData(); if (serverData != null && serverData.getResourceMode() == ServerData.ServerResourceMode.ENABLED) { netManager.sendPacket(makeStatusPacket(hash, Action.ACCEPTED)); - downloadResourcePackFuture(requestId, url, hash); + // FIXME downloadResourcePackFuture(requestId, url, hash); } else if (serverData != null && serverData.getResourceMode() != ServerData.ServerResourceMode.PROMPT) { netManager.sendPacket(makeStatusPacket(hash, Action.DECLINED)); } else { @@ -163,13 +165,17 @@ public class ResourcePackRecorder { //noinspection Convert2Lambda mc.addScheduledTask(() -> mc.displayGuiScreen(new GuiYesNo(new GuiYesNoCallback() { @Override - public void confirmClicked(boolean result, int id) { + //#if MC>=11300 + public void confirmResult(boolean result, int id) { + //#else + //$$ public void confirmClicked(boolean result, int id) { + //#endif if (serverData != null) { serverData.setResourceMode(result ? ServerData.ServerResourceMode.ENABLED : ServerData.ServerResourceMode.DISABLED); } if (result) { netManager.sendPacket(makeStatusPacket(hash, Action.ACCEPTED)); - downloadResourcePackFuture(requestId, url, hash); + // FIXME downloadResourcePackFuture(requestId, url, hash); } else { netManager.sendPacket(makeStatusPacket(hash, Action.DECLINED)); } @@ -188,6 +194,7 @@ public class ResourcePackRecorder { //#endif } + /* FIXME private void downloadResourcePackFuture(int requestId, String url, final String hash) { Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() { @Override @@ -370,4 +377,5 @@ public class ResourcePackRecorder { //$$ } //#endif +*/ } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 00000000..822397a1 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,28 @@ +modLoader="javafml" +loaderVersion="[13,)" +updateJSONURL="https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json" +issueTrackerURL="https://replaymod.com/forum" +displayURL="https://replaymod.com" +logoFile="replaymod_logo.png" +authors="CrushedPixel, johni0702" + +[[mods]] +modId="replaymod" +version="${version}" +displayName="Replay Mod" +description=''' +A Mod which allows you to record, replay and share your Minecraft experience. +''' + +[[dependencies.replaymod]] + modId="forge" + mandatory=true + versionRange="[14.23.2.0,)" + ordering="NONE" + side="BOTH" +[[dependencies.replaymod]] + modId="minecraft" + mandatory=true + versionRange="[ 1.13 ]" + ordering="NONE" + side="BOTH" \ No newline at end of file diff --git a/src/main/resources/META-INF/replaymod_at.cfg b/src/main/resources/META-INF/replaymod_at.cfg index 950bbdee..53e91c8c 100644 --- a/src/main/resources/META-INF/replaymod_at.cfg +++ b/src/main/resources/META-INF/replaymod_at.cfg @@ -143,7 +143,7 @@ public net.minecraft.crash.CrashReportCategory$Entry public net.minecraft.client.settings.KeyBinding field_151474_i # pressTime # Timer -public net.minecraft.util.Timer * +public-f net.minecraft.util.Timer * # GlStateManager public net.minecraft.client.renderer.GlStateManager * diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 00000000..6afaebbc --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "ReplayMod resources", + "pack_format": 4 + } +} diff --git a/versions/1.13/.gitkeep b/versions/1.13/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/versions/common.gradle b/versions/common.gradle index 56f2e39b..4c2aacf0 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -5,11 +5,12 @@ buildscript { mcVersion = "${major}${minor.padLeft(2, '0')}${(patch ?: '').padLeft(2, '0')}" as int } else { def f = file('mcVersion') - mcVersion = f.exists() ? f.readLines().first() as int : 11202 + mcVersion = f.exists() ? f.readLines().first() as int : 11300 } project.ext.mcVersion = mcVersion repositories { + mavenLocal() jcenter() mavenCentral() maven { @@ -30,6 +31,7 @@ buildscript { exclude group: 'org.ow2.asm', module: 'asm-debug-all' } classpath 'net.minecraftforge.gradle:ForgeGradle:' + ( + mcVersion >= 11300 ? '3.+' : mcVersion >= 11200 ? '2.3-SNAPSHOT' : mcVersion >= 10904 ? '2.2-SNAPSHOT' : mcVersion >= 10809 ? '2.1-SNAPSHOT' : @@ -40,9 +42,21 @@ buildscript { } } +def mcVersionStr = "${(int)(mcVersion/10000)}.${(int)(mcVersion/100)%100}" + (mcVersion%100==0 ? '' : ".${mcVersion%100}") + +def FG3 = mcVersion >= 11300 + if (mcVersion >= 10800) { - apply plugin: 'net.minecraftforge.gradle.forge' - apply plugin: 'org.spongepowered.mixin' + if (FG3) { + apply plugin: 'net.minecraftforge.gradle' + // FIXME use mixin plugin once updated + compileJava { + options.compilerArgs += ['-AreobfSrgFile=' + new File(project.buildDir, 'mcp-srg.srg').getCanonicalPath()] + } + } else { + apply plugin: 'net.minecraftforge.gradle.forge' + apply plugin: 'org.spongepowered.mixin' + } } else { apply plugin: 'forge' @@ -56,16 +70,27 @@ sourceCompatibility = 1.8 targetCompatibility = 1.8 afterEvaluate { - version = project.minecraft.version + '-' + rootProject.version + version = mcVersionStr + '-' + rootProject.version } group= "com.replaymod" archivesBaseName = "replaymod" minecraft { - if (mcVersion >= 10800) { - coreMod = 'com.replaymod.core.LoadingPlugin' + if (FG3) { + runConfig { + name = 'Minecraft Client' + main = 'net.minecraftforge.userdev.UserdevLauncher' + ideaModuleName = '${project.name}_main' + workingDirectory = project.file('eclipse').canonicalPath + environment 'target', 'fmldevclient' + environment 'assetDirectory', downloadAssets.output.absolutePath + } + } else { + if (mcVersion >= 10800) { + coreMod = 'com.replaymod.core.LoadingPlugin' + } + runDir = "../../eclipse" } - runDir = "../../eclipse" version = [ 11202: '1.12.2-14.23.0.2486', @@ -80,6 +105,7 @@ minecraft { 10710: '1.7.10-10.13.4.1558-1.7.10', ][mcVersion] mappings = [ + 11300: "snapshot_20180921-1.13", 11202: "snapshot_20170615", 11201: "snapshot_20170615", 11200: "snapshot_20170615", @@ -92,15 +118,20 @@ minecraft { 10710: "stable_12", ][mcVersion] } + afterEvaluate { - // Note cannot use minecraft.replace because that has already been forwarded to the task by FG by now - tasks.sourceMainJava.replace '@MOD_VERSION@', project.version - // Includes intentional whitespace to stop Forge from declaring the mod to be compatible with - // a newer srg-compatible MC version (that may be using a different protocol version) - tasks.sourceMainJava.replace '@MC_VERSION@', "[ ${project.minecraft.version} ]" + if (mcVersion >= 11300) { + } else { + // Note cannot use minecraft.replace because that has already been forwarded to the task by FG by now + tasks.sourceMainJava.replace '@MOD_VERSION@', project.version + // Includes intentional whitespace to stop Forge from declaring the mod to be compatible with + // a newer srg-compatible MC version (that may be using a different protocol version) + tasks.sourceMainJava.replace '@MC_VERSION@', "[ $mcVersionStr ]" + } } repositories { + mavenLocal() maven { name = "SpongePowered Repo" url = "http://repo.spongepowered.org/maven/" @@ -116,9 +147,20 @@ configurations { } dependencies { + if (FG3) { + minecraft 'net.minecraftforge.test:forge:' + [ + 11300: '1.13-24.0.33-1.13-pre', + ][mcVersion] + } + + annotationProcessor group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + // compile 'com.github.SpongePowered:Mixin:404f5da' // 0.7.5-SNAPSHOT // ^ mixin doesn't compile on jitpack, so we'll have to depend on the SNAPSHOT and build it manually for reprod - compile 'org.spongepowered:mixin:0.7.5-SNAPSHOT' + annotationProcessor 'org.spongepowered:mixin:0.7.8-SNAPSHOT' + compileOnly 'org.spongepowered:mixin:0.7.8-SNAPSHOT' + compile 'org.spongepowered:mixin:0.7.8-SNAPSHOT' shade 'com.googlecode.mp4parser:isoparser:1.1.7' shade 'org.apache.commons:commons-exec:1.3' def withoutGuava = { exclude group: 'com.google.guava', module: 'guava-jdk5' } @@ -129,9 +171,9 @@ dependencies { shade 'org.aspectj:aspectjrt:1.8.2' - def studioVersion = "${(int)(mcVersion/10000)}.${(int)(mcVersion/100)%100}" + (mcVersion%100==0 ? '' : ".${mcVersion%100}") + def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' - shade("com.github.ReplayMod.ReplayStudio:$studioVersion:2d8d41d"){ + shade("com.replaymod.replaystudio:$studioVersion:2d8d41d"){ // FIXME exclude group: 'com.google.guava', module: 'guava' // provided by MC exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } @@ -144,6 +186,7 @@ dependencies { exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } + annotationProcessor 'org.projectlombok:lombok:1.16.6' compile 'org.projectlombok:lombok:1.16.6' // runtime only for @SneakyThrows which isn't used testCompile 'junit:junit:4.11' @@ -173,8 +216,10 @@ if (project.name != 'core') { def preprocessedSrc = 'build/preprocessed/src' def preprocessedRes = 'build/preprocessed/res' + def preprocessedAT = 'build/preprocessed/at.cfg' def originalSrc = '../../src/main/java' def originalRes = '../../src/main/resources' + def originalAT = originalRes + '/META-INF/replaymod_at.cfg' def vars = [MC: mcVersion as int] sourceSets { @@ -200,19 +245,42 @@ if (project.name != 'core') { compileJava.dependsOn preprocessJava processResources.dependsOn preprocessResources - (mcVersion >= 10800 ? [deobfMcMCP, deobfMcSRG] : [deobfuscateJar, deobfBinJar]).each { task -> - task.dependsOn preprocessResources + if (FG3) { + // FG3 reads the AT file right after project evaluation, so we need to convert it right now + project.convertTree(vars, originalAT, preprocessedAT) + minecraft.accessTransformers << file(preprocessedAT) + } else { + (mcVersion >= 10800 ? [deobfMcMCP, deobfMcSRG] : [deobfuscateJar, deobfBinJar]).each { task -> + task.dependsOn preprocessResources + } + minecraft.accessTransformer preprocessedRes + '/META-INF/replaymod_at.cfg' } - - minecraft.accessTransformer preprocessedRes + '/META-INF/replaymod_at.cfg' } else { + if (FG3) { + minecraft.accessTransformers << file('../../src/main/resources/META-INF/replaymod_at.cfg') + } sourceSets { main.java.srcDirs = ['../../src/main/java'] main.resources.srcDirs = ['../../src/main/resources'] } } +sourceSets { + main { + java { + exclude 'com/replaymod/compat' + exclude 'com/replaymod/editor' + exclude 'com/replaymod/extras' + exclude 'com/replaymod/online' + exclude 'com/replaymod/pathing' + exclude 'com/replaymod/render' + exclude 'com/replaymod/replay' + exclude 'com/replaymod/restrictions' + exclude 'com/replaymod/simplepathing' + } + } +} - +if (!FG3) { // FIXME def reobfTask = mcVersion >= 10800 ? tasks.reobfJar : tasks.reobf task configureRelocation() { dependsOn tasks.jar @@ -237,6 +305,7 @@ task configureRelocation() { } } reobfTask.dependsOn configureRelocation +} if (mcVersion <= 10710) { // Dummy task so "./gradlew reobfJar" also builds 1.7.10 @@ -294,21 +363,23 @@ processResources { // this will ensure that this task is redone when the versions change. inputs.property 'version', { project.version } - inputs.property 'mcversion', { project.minecraft.version } + inputs.property 'mcversion', { mcVersionStr } - // replace stuff in mcmod.info, nothing else + // replace stuff in mcmod.info (pre 1.13) and mods.toml (1.13+), nothing else from(sourceSets.main.resources.srcDirs) { spec -> include 'mcmod.info' + include 'mods.toml' // replace version and mcversion afterEvaluate { - spec.expand 'version': project.version, 'mcversion': project.minecraft.version + spec.expand 'version': project.version, 'mcversion': mcVersionStr } } - // copy everything else, thats not the mcmod.info + // copy everything else, thats not the mcmod.info (or mods.toml) from(sourceSets.main.resources.srcDirs) { exclude 'mcmod.info' + exclude 'mods.toml' } } @@ -325,14 +396,42 @@ sourceSets { } } -task copySrg(type: Copy, dependsOn: 'genSrgs') { - from {project.tasks.genSrgs.mcpToSrg} - into 'build' +if (FG3) { + task copySrg(dependsOn: 'createMcpToSrg') { + doLast { + def tsrg = file(project.tasks.createMcpToSrg.output).readLines() + def srg = [] + def cls = '' + for (def line : tsrg) { + if (line[0] != '\t') { + srg.add('CL: ' + line) + cls = line.split(' ')[0] + } else { + def parts = line.substring(1).split(' ') + if (line.contains('(')) { + srg.add('MD: ' + cls + '/' + parts[0] + ' ' + parts[1] + ' ' + cls + '/' + parts[2] + ' ' + parts[1]) + } else { + srg.add('FD: ' + cls + '/' + parts[0] + ' ' + cls + '/' + parts[1]) + } + } + } + new File(project.buildDir, 'mcp-srg.srg').write(srg.join('\n')) + } + } +} else { + task copySrg(type: Copy, dependsOn: 'genSrgs') { + from {project.tasks.genSrgs.mcpToSrg} + into 'build' + } } -setupDecompWorkspace.dependsOn copySrg -setupDevWorkspace.dependsOn copySrg -project.tasks.idea.dependsOn copySrg +if (FG3) { + // FIXME project.tasks.idea.dependsOn copySrg + compileJava.dependsOn copySrg // FIXME can probably remove this one once mixingradle is updated +} else { + setupDecompWorkspace.dependsOn copySrg + setupDevWorkspace.dependsOn copySrg +} if (mcVersion <= 10710) { reobf.doFirst { @@ -351,6 +450,7 @@ if (mcVersion <= 10710) { } } +/* FIXME // Mixin uses multiple HashMaps to generate the refmap. // HashMaps are unordered collections and as such do not produce deterministic output. // To fix that, we simply sort the refmap json file. @@ -375,6 +475,7 @@ compileJava.doLast { refmapFile.withWriter { it.write json } } } +*/ import java.util.zip.ZipEntry import java.util.zip.ZipFile @@ -425,10 +526,11 @@ def annotationWorkaround = { jar.delete() tmp.renameTo(jar) } -if (mcVersion >= 10809) { +if (mcVersion >= 10809 && !FG3) { tasks.deobfMcMCP.doLast annotationWorkaround } +if (!FG3) { // FIXME task runIntegrationTest(type: JavaExec, dependsOn: ["makeStart", "jar"]) { main = 'GradleStart' standardOutput = System.out @@ -452,5 +554,6 @@ task runIntegrationTest(type: JavaExec, dependsOn: ["makeStart", "jar"]) { runIntegrationTest.classpath runClient.classpath + sourceSets.integrationTest.output } } +} defaultTasks 'build' diff --git a/versions/preprocessor.gradle b/versions/preprocessor.gradle index 5d47dee0..44e1e9f2 100644 --- a/versions/preprocessor.gradle +++ b/versions/preprocessor.gradle @@ -9,6 +9,17 @@ static def evalVar(vars, String var) { import java.util.regex.Pattern static def evalExpr(vars, String expr) { + expr = expr.trim() + + def parts = expr.split(/\|\|/) + if (parts.length > 1) { + return parts.any { evalExpr(vars, it) } + } + parts = expr.split(/&&/) + if (parts.length > 1) { + return !parts.any { !evalExpr(vars, it) } + } + def matcher = Pattern.compile(/(.+)(<=|>=|<|>)(.+)/).matcher(expr) if (matcher.matches()) { def lhs = evalVar(vars, matcher.group(1)) From 1470123ec9a70681db0896da5f11876a7b391d0a Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 19 Dec 2018 10:11:08 +0100 Subject: [PATCH 33/65] Get replay module compiling on 1.13 --- .../java/com/replaymod/core/ReplayMod.java | 2 + .../replaymod/replay/FullReplaySender.java | 59 ++++-- .../replaymod/replay/InputReplayTimer.java | 2 + .../com/replaymod/replay/NoGuiScreenshot.java | 10 +- .../replaymod/replay/QuickReplaySender.java | 19 +- .../com/replaymod/replay/ReplayHandler.java | 181 ++++++++++++------ .../com/replaymod/replay/ReplayModReplay.java | 45 ++--- .../com/replaymod/replay/ReplaySender.java | 4 +- .../replaymod/replay/camera/CameraEntity.java | 86 +++++++-- .../camera/ClassicCameraController.java | 20 +- .../camera/SpectatorCameraController.java | 15 +- .../camera/VanillaCameraController.java | 14 +- .../replay/events/ReplayChatMessageEvent.java | 9 +- .../replay/events/ReplayCloseEvent.java | 6 +- .../events/ReplayDispatchKeypressesEvent.java | 9 +- .../replay/events/ReplayOpenEvent.java | 6 +- .../gui/overlay/GuiEditMarkerPopup.java | 9 +- .../replay/gui/overlay/GuiMarkerTimeline.java | 6 +- .../replay/gui/overlay/GuiReplayOverlay.java | 23 ++- .../replay/gui/screen/GuiReplayViewer.java | 45 ++--- .../replaymod/replay/handler/GuiHandler.java | 11 +- .../replay/mixin/MixinGuiSpectator.java | 3 +- .../replay/mixin/MixinParticleManager.java | 12 +- .../replay/mixin/MixinPlayerControllerMP.java | 25 ++- .../replay/mixin/MixinRenderItem.java | 29 ++- .../replay/mixin/MixinRenderLivingBase.java | 3 +- .../replay/mixin/MixinRenderManager.java | 7 +- .../MixinTileEntityEndPortalRenderer.java | 21 +- versions/common.gradle | 1 - 29 files changed, 476 insertions(+), 206 deletions(-) diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 9e55c3d8..67cc6323 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -8,6 +8,7 @@ import com.replaymod.core.handler.MainMenuHandler; import com.replaymod.core.utils.OpenGLUtils; import com.replaymod.core.versions.MCVer; import com.replaymod.recording.ReplayModRecording; +import com.replaymod.replay.ReplayModReplay; import com.replaymod.replaystudio.util.I18n; import de.johni0702.minecraft.gui.container.GuiScreen; import lombok.Getter; @@ -138,6 +139,7 @@ public class ReplayMod { private final List modules = new ArrayList<>(); { modules.add(new ReplayModRecording(this)); + modules.add(new ReplayModReplay(this)); } public KeyBindingRegistry getKeyBindingRegistry() { diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index ec0a3693..c1b462e0 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -34,6 +34,10 @@ import net.minecraftforge.common.MinecraftForge; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +//#if MC>=11300 +import net.minecraft.util.ResourceLocation; +//#endif + //#if MC>=11200 import com.replaymod.core.utils.WrappedTimer; //#endif @@ -49,7 +53,11 @@ import net.minecraft.util.text.ITextComponent; //#endif //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif import net.minecraftforge.fml.common.gameevent.TickEvent; //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -185,7 +193,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend /** * The minecraft instance. */ - protected Minecraft mc = Minecraft.getMinecraft(); + protected Minecraft mc = getMinecraft(); /** * The total length of this replay in milliseconds. @@ -265,7 +273,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend } /** - * Return a fake {@link Minecraft#getSystemTime()} value that respects slowdown/speedup/pause and works in both, + * Return a fake system tile in milliseconds value that respects slowdown/speedup/pause and works in both, * sync and async mode. * Note: For sync mode this returns the last value passed to {@link #sendPacketsTill(int)}. * @return The timestamp in milliseconds since the start of the replay @@ -308,7 +316,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if (world(mc) != null) { for (EntityPlayer playerEntity : playerEntities(world(mc))) { if (!playerEntity.addedToChunk && playerEntity instanceof EntityOtherPlayerMP) { - playerEntity.onLivingUpdate(); + // FIXME playerEntity.onLivingUpdate(); } } } @@ -361,7 +369,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend World world = world(mc); for (int i = 0; i < world.loadedEntityList.size(); ++i) { Entity entity = loadedEntityList(world).get(i); - if (entity.isDead) { + //#if MC>=11300 + if (entity.removed) { + //#else + //$$ if (entity.isDead) { + //#endif int chunkX = entity.chunkCoordX; int chunkY = entity.chunkCoordZ; @@ -370,7 +382,12 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#else //$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) { //#endif - world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity); + //#if MC>=11300 + //#else + world.getChunk(chunkX, chunkY).removeEntity(entity); + //#else + //$$ world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity); + //#endif } world.loadedEntityList.remove(i--); @@ -418,11 +435,15 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //$$ if (p instanceof S3FPacketCustomPayload) { //$$ S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; //#endif + //#if MC>=11300 + ResourceLocation channelName = packet.getChannelName(); + //#else //#if MC>=10800 - String channelName = packet.getChannelName(); + //$$ String channelName = packet.getChannelName(); //#else //$$ String channelName = packet.func_149169_c(); //#endif + //#endif if (Restrictions.PLUGIN_CHANNEL.equals(channelName)) { final String unknown = replayHandler.getRestrictions().handle(packet); if (unknown == null) { @@ -459,7 +480,13 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //$$ IChatComponent reason = ((S40PacketDisconnect) p).func_149165_c(); //#endif //#endif - if ("Please update to view this replay.".equals(reason.getUnformattedText())) { + //#if MC>=11300 + // FIXME or is it getString? + String message = reason.getUnformattedComponentText(); + //#else + //$$ String message = reason.getUnformattedText(); + //#endif + if ("Please update to view this replay.".equals(message)) { // This version of the mod supports replay restrictions so we are allowed // to remove this packet. return null; @@ -475,6 +502,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //$$ if (p instanceof S3FPacketCustomPayload) { //$$ S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; //#endif + /* FIXME //#if MC>=10800 String channelName = packet.getChannelName(); //#else @@ -483,6 +511,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if ("MC|BOpen".equals(channelName)) { return null; } + */ //#if MC>=10800 } @@ -514,7 +543,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if (!file.exists()) { IOUtils.copy(replayFile.getResourcePack(hash).get(), new FileOutputStream(file)); } - setServerResourcePack(mc.getResourcePackRepository(), file); + // FIXME setServerResourcePack(mc.getResourcePackRepository(), file); } } return null; @@ -540,7 +569,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#if MC>=10800 int dimension = packet.getDimension(); EnumDifficulty difficulty = packet.getDifficulty(); - int maxPlayers = packet.getMaxPlayers(); + //#if MC>=11300 + int maxPlayers = 0;// FIXME needs AT packet.maxPlayers; + //#else + //$$ int maxPlayers = packet.getMaxPlayers(); + //#endif WorldType worldType = packet.getWorldType(); //#if MC>=10904 @@ -1064,7 +1097,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Needs to be called at least 4 times thanks to // EntityOtherPlayerMP#otherPlayerMPPosRotationIncrements (max vanilla value is 3) for (int i = 0; i < 4; i++) { - entity.onUpdate(); + //#if MC>=11300 + entity.tick(); + //#else + //$$ entity.onUpdate(); + //#endif } // Check whether the entity has left the chunk diff --git a/src/main/java/com/replaymod/replay/InputReplayTimer.java b/src/main/java/com/replaymod/replay/InputReplayTimer.java index 4baa0f1c..33c023a1 100644 --- a/src/main/java/com/replaymod/replay/InputReplayTimer.java +++ b/src/main/java/com/replaymod/replay/InputReplayTimer.java @@ -1,3 +1,4 @@ +/* FIXME package com.replaymod.replay; import com.replaymod.core.utils.WrappedTimer; @@ -264,3 +265,4 @@ public class InputReplayTimer extends WrappedTimer { //$$ public static class RunScheduledTasks extends Event {} //#endif } +*/ diff --git a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java index 88a8cc3a..5aec7e4e 100644 --- a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java +++ b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java @@ -38,6 +38,12 @@ public class NoGuiScreenshot { return; } + //#if MC>=11300 + int frameWidth = mc.mainWindow.getFramebufferWidth(), frameHeight = mc.mainWindow.getFramebufferHeight(); + //#else + //$$ int frameWidth = mc.displayWidth, frameHeight = mc.displayHeight; + //#endif + final boolean guiHidden = mc.gameSettings.hideGUI; try { mc.gameSettings.hideGUI = true; @@ -57,7 +63,7 @@ public class NoGuiScreenshot { mc.getFramebuffer().unbindFramebuffer(); GlStateManager.popMatrix(); GlStateManager.pushMatrix(); - mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); + mc.getFramebuffer().framebufferRender(frameWidth, frameHeight); GlStateManager.popMatrix(); } catch (Throwable t) { future.setException(t); @@ -73,7 +79,7 @@ public class NoGuiScreenshot { // disk for better maintainability File tmpFolder = Files.createTempDir(); try { - ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", mc.displayWidth, mc.displayHeight, mc.getFramebuffer()); + ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer()); File screenshotFile = new File(tmpFolder, "screenshots/tmp"); BufferedImage image = ImageIO.read(screenshotFile); int imageWidth = image.getWidth(); diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 8ec020a1..f5bf7543 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -62,7 +62,6 @@ import net.minecraft.network.EnumConnectionState; import net.minecraft.network.EnumPacketDirection; import net.minecraft.network.Packet; import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import javax.annotation.Nullable; @@ -85,11 +84,18 @@ import java.util.function.Consumer; import java.util.zip.Deflater; import java.util.zip.Inflater; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif + //#if MC>=11200 import com.replaymod.core.utils.WrappedTimer; //#endif import static com.replaymod.core.versions.MCVer.FML_BUS; +import static com.replaymod.core.versions.MCVer.getMinecraft; import static com.replaymod.replay.ReplayModReplay.LOGGER; /** @@ -104,7 +110,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private static final String CACHE_INDEX_ENTRY = "quickModeCacheIndex.bin"; private static final int CACHE_VERSION = 0; - private final Minecraft mc = Minecraft.getMinecraft(); + private final Minecraft mc = getMinecraft(); private final ReplayModReplay mod; private final ReplayFile replayFile; @@ -980,8 +986,13 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe for (BlockChange blockChange : blockChanges) { NetUtil.writePosition(cacheOut, blockChange.pos); index += 8; - index += writeVarInt(cacheOut, blockChange.from.getId() << 4 | blockChange.from.getData() & 15); - index += writeVarInt(cacheOut, blockChange.to.getId() << 4 | blockChange.to.getData() & 15); + //#if MC>=11300 + index += writeVarInt(cacheOut, blockChange.from.getId()); + index += writeVarInt(cacheOut, blockChange.to.getId()); + //#else + //$$ index += writeVarInt(cacheOut, blockChange.from.getId() << 4 | blockChange.from.getData() & 15); + //$$ index += writeVarInt(cacheOut, blockChange.to.getId() << 4 | blockChange.to.getData() & 15); + //#endif } } diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 3317045b..1ac42f97 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -21,19 +21,25 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.embedded.EmbeddedChannel; import net.minecraft.client.Minecraft; -import net.minecraft.client.entity.EntityOtherPlayerMP; import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.network.NetworkManager; -import org.lwjgl.opengl.Display; import java.io.IOException; import java.util.*; +//#if MC>=11300 +import net.minecraft.client.MainWindow; +import net.minecraft.entity.EntityLivingBase; +//#else +//$$ import net.minecraft.client.entity.EntityOtherPlayerMP; +//$$ import net.minecraft.client.gui.ScaledResolution; +//$$ import org.lwjgl.opengl.Display; +//#endif + //#if MC<10904 //$$ import de.johni0702.minecraft.gui.element.GuiLabel; //$$ import de.johni0702.minecraft.gui.popup.GuiInfoPopup; @@ -41,11 +47,16 @@ import java.util.*; //#endif //#if MC>=10800 -import com.mojang.authlib.GameProfile; -import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.network.EnumPacketDirection; -import net.minecraftforge.fml.client.FMLClientHandler; -import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher; +//#if MC>=11300 +import net.minecraft.client.network.NetHandlerLoginClient; +import net.minecraftforge.fml.network.NetworkHooks; +//#else +//$$ import com.mojang.authlib.GameProfile; +//$$ import net.minecraft.client.network.NetHandlerPlayClient; +//$$ import net.minecraftforge.fml.client.FMLClientHandler; +//$$ import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher; +//#endif import static net.minecraft.client.renderer.GlStateManager.*; //#else @@ -73,7 +84,7 @@ import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; public class ReplayHandler { - private static Minecraft mc = Minecraft.getMinecraft(); + private static Minecraft mc = getMinecraft(); /** * The file currently being played. @@ -146,7 +157,11 @@ public class ReplayHandler { // Force re-creation of camera entity by unloading the previous world mc.addScheduledTask(() -> { - mc.setIngameNotInFocus(); + //#if MC>=11300 + mc.mouseHelper.ungrabMouse(); + //#else + //$$ mc.setIngameNotInFocus(); + //#endif mc.loadWorld(null); }); @@ -171,7 +186,11 @@ public class ReplayHandler { channel.close().awaitUninterruptibly(); if (player(mc) instanceof CameraEntity) { - player(mc).setDead(); + //#if MC>=11300 + player(mc).remove(); + //#else + //$$ player(mc).setDead(); + //#endif } if (world(mc) != null) { @@ -207,21 +226,32 @@ public class ReplayHandler { t.printStackTrace(); } }; - NetHandlerPlayClient netHandlerPlayClient = - new NetHandlerPlayClient(mc, null, networkManager, new GameProfile(UUID.randomUUID(), "Player")); - networkManager.setNetHandler(netHandlerPlayClient); - FMLClientHandler.instance().setPlayClient(netHandlerPlayClient); - - //#if MC>=11200 + //#if MC>=11300 channel = new EmbeddedChannel(); - NetworkDispatcher networkDispatcher = new NetworkDispatcher(networkManager); - channel.attr(NetworkDispatcher.FML_DISPATCHER).set(networkDispatcher); channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); channel.pipeline().addFirst("ReplayModReplay_quickReplaySender", quickReplaySender); channel.pipeline().addLast("packet_handler", networkManager); channel.pipeline().fireChannelActive(); - networkDispatcher.clientToServerHandshake(); + + networkManager.setNetHandler(new NetHandlerLoginClient(networkManager, mc, null, it -> {})); + NetworkHooks.registerClientLoginChannel(networkManager); + //#else + //$$ NetHandlerPlayClient netHandlerPlayClient = + //$$ new NetHandlerPlayClient(mc, null, networkManager, new GameProfile(UUID.randomUUID(), "Player")); + //$$ networkManager.setNetHandler(netHandlerPlayClient); + //$$ FMLClientHandler.instance().setPlayClient(netHandlerPlayClient); + //$$ + //#if MC>=11200 + //$$ channel = new EmbeddedChannel(); + //$$ NetworkDispatcher networkDispatcher = new NetworkDispatcher(networkManager); + //$$ channel.attr(NetworkDispatcher.FML_DISPATCHER).set(networkDispatcher); + //$$ + //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); + //$$ channel.pipeline().addFirst("ReplayModReplay_quickReplaySender", quickReplaySender); + //$$ channel.pipeline().addLast("packet_handler", networkManager); + //$$ channel.pipeline().fireChannelActive(); + //$$ networkDispatcher.clientToServerHandshake(); //#else //$$ channel = new EmbeddedChannel(networkManager); //$$ NetworkDispatcher networkDispatcher = new NetworkDispatcher(networkManager); @@ -234,6 +264,7 @@ public class ReplayHandler { //$$ channel.pipeline().addAfter("ReplayModReplay_replaySender", "fml:packet_handler", networkDispatcher); //$$ channel.pipeline().fireChannelActive(); //#endif + //#endif //#else //$$ NetworkManager networkManager = new NetworkManager(true) { //$$ @Override @@ -520,12 +551,7 @@ public class ReplayHandler { // Update all entity positions (especially prev/lastTick values) for (Entity entity : loadedEntityList(world(mc))) { - if (entity instanceof EntityOtherPlayerMP) { - EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity; - e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); - e.rotationYaw = (float) e.otherPlayerMPYaw; - e.rotationPitch = (float) e.otherPlayerMPPitch; - } + skipTeleportInterpolation(entity); entity.lastTickPosX = entity.prevPosX = entity.posX; entity.lastTickPosY = entity.prevPosY = entity.posY; entity.lastTickPosZ = entity.prevPosZ = entity.posZ; @@ -534,23 +560,22 @@ public class ReplayHandler { } // Run previous tick - try { - mc.runTick(); - } catch (IOException e) { - throw new RuntimeException(e); - } + //#if MC>=11300 + mc.runTick(); + //#else + //$$ try { + //$$ mc.runTick(); + //$$ } catch (IOException e) { + //$$ throw new RuntimeException(e); + //$$ } + //#endif // Jump to target tick quickReplaySender.sendPacketsTill(targetTime); // Immediately apply player teleport interpolation for (Entity entity : loadedEntityList(world(mc))) { - if (entity instanceof EntityOtherPlayerMP) { - EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity; - e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); - e.rotationYaw = (float) e.otherPlayerMPYaw; - e.rotationPitch = (float) e.otherPlayerMPPitch; - } + skipTeleportInterpolation(entity); } return; } @@ -583,7 +608,11 @@ public class ReplayHandler { // Render our please-wait-screen GuiScreen guiScreen = new GuiScreen() { @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { + //#if MC>=11300 + public void render(int mouseX, int mouseY, float partialTicks) { + //#else + //$$ public void drawScreen(int mouseX, int mouseY, float partialTicks) { + //#endif drawBackground(0); drawCenteredString(getFontRenderer(mc), I18n.format("replaymod.gui.pleasewait"), width / 2, height / 2, 0xffffffff); @@ -598,47 +627,68 @@ public class ReplayHandler { clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); enableTexture2D(); mc.getFramebuffer().bindFramebuffer(true); - mc.entityRenderer.setupOverlayRendering(); + //#if MC>=11300 + mc.mainWindow.setupOverlayRendering(); + //#else + //$$ mc.entityRenderer.setupOverlayRendering(); + //#endif - ScaledResolution resolution = newScaledResolution(mc); + //#if MC>=11300 + MainWindow + //#else + //$$ ScaledResolution + //#endif + resolution = newScaledResolution(mc); guiScreen.setWorldAndResolution(mc, resolution.getScaledWidth(), resolution.getScaledHeight()); - guiScreen.drawScreen(0, 0, 0); + //#if MC>=11300 + guiScreen.render(0, 0, 0); + //#else + //$$ guiScreen.drawScreen(0, 0, 0); + //#endif mc.getFramebuffer().unbindFramebuffer(); popMatrix(); pushMatrix(); - mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); + //#if MC>=11300 + mc.getFramebuffer().framebufferRender(mc.mainWindow.getFramebufferWidth(), mc.mainWindow.getFramebufferHeight()); + //#else + //$$ mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); + //#endif popMatrix(); - Display.update(); + //#if MC>=11300 + mc.mainWindow.update(true); + //#else + //$$ Display.update(); + //#endif // Send the packets replaySender.sendPacketsTill(targetTime); replaySender.setAsyncMode(true); replaySender.setReplaySpeed(0); - getConnection(mc).getNetworkManager().processReceivedPackets(); + getConnection(mc).getNetworkManager() + //#if MC>=11300 + .tick(); + //#else + //$$ .processReceivedPackets(); + //#endif for (Entity entity : loadedEntityList(world(mc))) { - if (entity instanceof EntityOtherPlayerMP) { - EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity; - e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); - e.rotationYaw = (float) e.otherPlayerMPYaw; - e.rotationPitch = (float) e.otherPlayerMPPitch; - } + skipTeleportInterpolation(entity); entity.lastTickPosX = entity.prevPosX = entity.posX; entity.lastTickPosY = entity.prevPosY = entity.posY; entity.lastTickPosZ = entity.prevPosZ = entity.posZ; entity.prevRotationYaw = entity.rotationYaw; entity.prevRotationPitch = entity.rotationPitch; } - //#if MC>=10800 - try { - mc.runTick(); - } catch (IOException e) { - e.printStackTrace(); // This should never be thrown but whatever - } + //#if MC>=10800 && MC<11300 + //$$ try { + //$$ mc.runTick(); + //$$ } catch (IOException e) { + //$$ e.printStackTrace(); // This should never be thrown but whatever + //$$ } //#else - //$$ mc.runTick(); + mc.runTick(); //#endif //finally, updating the camera's position (which is not done by the sync jumping) @@ -649,4 +699,23 @@ public class ReplayHandler { } } } + + private void skipTeleportInterpolation(Entity entity) { + //#if MC>=11300 + if (entity instanceof EntityLivingBase) { + EntityLivingBase e = (EntityLivingBase) entity; + // FIXME + // e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); + // e.rotationYaw = (float) e.otherPlayerMPYaw; + // e.rotationPitch = (float) e.otherPlayerMPPitch; + } + //#else + //$$ if (entity instanceof EntityOtherPlayerMP) { + //$$ EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity; + //$$ e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); + //$$ e.rotationYaw = (float) e.otherPlayerMPYaw; + //$$ e.rotationPitch = (float) e.otherPlayerMPPitch; + //$$ } + //#endif + } } diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 0032acea..b1f087ba 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -6,6 +6,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.ModCompat; +import com.replaymod.core.versions.MCVer; import com.replaymod.replay.camera.CameraController; import com.replaymod.replay.camera.CameraControllerRegistry; import com.replaymod.replay.camera.CameraEntity; @@ -19,16 +20,20 @@ import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ZipReplayFile; import com.replaymod.replaystudio.studio.ReplayStudio; import net.minecraft.client.Minecraft; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.lwjgl.input.Keyboard; + +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 -import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; //#else -//$$ import cpw.mods.fml.common.Mod; //$$ import cpw.mods.fml.common.event.FMLInitializationEvent; //$$ import cpw.mods.fml.common.event.FMLPostInitializationEvent; //$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; @@ -40,25 +45,16 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Optional; -@Mod(modid = ReplayModReplay.MOD_ID, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", - //#if MC>=10800 - clientSideOnly = true, - //#endif - useMetadata = true) -public class ReplayModReplay { - public static final String MOD_ID = "replaymod-replay"; +public class ReplayModReplay extends ReplayMod.Module { - @Mod.Instance(MOD_ID) + { instance = this; } public static ReplayModReplay instance; private ReplayMod core; private final CameraControllerRegistry cameraControllerRegistry = new CameraControllerRegistry(); - public static Logger LOGGER; + public static Logger LOGGER = LogManager.getLogger(); protected ReplayHandler replayHandler; @@ -66,14 +62,15 @@ public class ReplayModReplay { return replayHandler; } - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - LOGGER = event.getModLog(); - core = ReplayMod.instance; + public ReplayModReplay(ReplayMod core) { + this.core = core; + } + @Override + public void preInit(FMLPreInitializationEvent event) { core.getSettingsRegistry().register(Setting.class); - core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.marker", Keyboard.KEY_M, new Runnable() { + core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.marker", MCVer.Keyboard.KEY_M, new Runnable() { @Override public void run() { if (replayHandler != null ) { @@ -109,7 +106,7 @@ public class ReplayModReplay { @Override public void run() { if (replayHandler != null) { - Minecraft mc = Minecraft.getMinecraft(); + Minecraft mc = MCVer.getMinecraft(); ListenableFuture future = NoGuiScreenshot.take(mc, 1280, 720); Futures.addCallback(future, new FutureCallback() { @Override @@ -181,15 +178,15 @@ public class ReplayModReplay { }); } - @Mod.EventHandler + @Override public void init(FMLInitializationEvent event) { Minecraft mc = core.getMinecraft(); - mc.timer = new InputReplayTimer(mc.timer, this); + // FIXME mc.timer = new InputReplayTimer(mc.timer, this); new GuiHandler(this).register(); } - @Mod.EventHandler + @Override public void postInit(FMLPostInitializationEvent event) { Setting.CAMERA.setChoices(new ArrayList<>(cameraControllerRegistry.getControllers())); } diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index 8e2d63d4..53a23c62 100644 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -2,6 +2,8 @@ package com.replaymod.replay; import net.minecraft.client.Minecraft; +import static com.replaymod.core.versions.MCVer.getMinecraft; + public interface ReplaySender { int currentTimeStamp(); @@ -10,7 +12,7 @@ public interface ReplaySender { * @return {@code true} if it is paused, {@code false} otherwise */ public default boolean paused() { - Minecraft mc = Minecraft.getMinecraft(); + Minecraft mc = getMinecraft(); //#if MC>=11200 return mc.timer.tickLength == Float.POSITIVE_INFINITY; //#else diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index fecd2ef0..cf769179 100755 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -3,6 +3,7 @@ package com.replaymod.replay.camera; import com.replaymod.core.ReplayMod; import com.replaymod.core.events.SettingsChangedEvent; import com.replaymod.core.utils.Utils; +import com.replaymod.core.versions.MCVer; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.Setting; import com.replaymod.replay.events.ReplayChatMessageEvent; @@ -26,10 +27,18 @@ import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderHandEvent; import net.minecraftforge.common.MinecraftForge; +//#if MC>=11300 +import net.minecraft.util.math.RayTraceFluidMode; +//#endif + //#if MC>=10904 import net.minecraft.inventory.EntityEquipmentSlot; //#if MC>=11200 -import net.minecraft.stats.RecipeBook; +//#if MC>=11300 +import net.minecraft.client.util.RecipeBookClient; +//#else +//$$ import net.minecraft.stats.RecipeBook; +//#endif //#endif import net.minecraft.stats.StatisticsManager; import net.minecraft.util.EnumHand; @@ -45,9 +54,14 @@ import net.minecraft.util.text.ITextComponent; //#if MC>=10800 import net.minecraft.client.entity.EntityPlayerSP; -import net.minecraftforge.fml.common.eventhandler.EventPriority; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.EventPriority; +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.EventPriority; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -97,9 +111,13 @@ public class CameraEntity */ private final EventHandler eventHandler = new EventHandler(); + //#if MC>=11300 + public CameraEntity(Minecraft mcIn, World worldIn, NetHandlerPlayClient netHandlerPlayClient, StatisticsManager statisticsManager, RecipeBookClient recipeBookClient) { + super(mcIn, worldIn, netHandlerPlayClient, statisticsManager, recipeBookClient); + //#else //#if MC>=11200 - public CameraEntity(Minecraft mcIn, World worldIn, NetHandlerPlayClient netHandlerPlayClient, StatisticsManager statisticsManager, RecipeBook recipeBook) { - super(mcIn, worldIn, netHandlerPlayClient, statisticsManager, recipeBook); + //$$ public CameraEntity(Minecraft mcIn, World worldIn, NetHandlerPlayClient netHandlerPlayClient, StatisticsManager statisticsManager, RecipeBook recipeBook) { + //$$ super(mcIn, worldIn, netHandlerPlayClient, statisticsManager, recipeBook); //#else //#if MC>=10904 //$$ public CameraEntity(Minecraft mcIn, World worldIn, NetHandlerPlayClient netHandlerPlayClient, StatisticsManager statisticsManager) { @@ -113,6 +131,7 @@ public class CameraEntity //$$ super(mcIn, worldIn, session, netHandlerPlayClient, statFileWriter); //#endif //#endif + //#endif //#endif FORGE_BUS.register(eventHandler); FML_BUS.register(eventHandler); @@ -196,7 +215,11 @@ public class CameraEntity private void updateBoundingBox() { //#if MC>=10800 - setEntityBoundingBox(new AxisAlignedBB( + //#if MC>=11300 + setBoundingBox(new AxisAlignedBB( + //#else + //$$ setEntityBoundingBox(new AxisAlignedBB( + //#endif //#else //$$ this.boundingBox.setBB(AxisAlignedBB.getBoundingBox( //#endif @@ -205,7 +228,11 @@ public class CameraEntity } @Override - public void onUpdate() { + //#if MC>=11300 + public void tick() { + //#else + //$$ public void onUpdate() { + //#endif //#if MC>=10800 Entity view = getRenderViewEntity(mc); //#else @@ -262,10 +289,12 @@ public class CameraEntity return falseUnlessSpectating(Entity::isEntityInsideOpaqueBlock); // Make sure no suffocation overlay is rendered } + /* FIXME @Override public boolean isInsideOfMaterial(Material materialIn) { return falseUnlessSpectating(e -> e.isInsideOfMaterial(materialIn)); // Make sure no overlays are rendered } + */ //#if MC>=10800 @Override @@ -405,19 +434,33 @@ public class CameraEntity return super.isHandActive(); } + //#if MC>=11300 @Override - public RayTraceResult rayTrace(double p_174822_1_, float p_174822_3_) { - RayTraceResult pos = super.rayTrace(p_174822_1_, 1f); + public RayTraceResult rayTrace(double blockReachDistance, float partialTicks, RayTraceFluidMode p_174822_4_) { + RayTraceResult pos = super.rayTrace(blockReachDistance, partialTicks, p_174822_4_); // Make sure we can never look at blocks (-> no outline) - if(pos != null && pos.typeOfHit == RayTraceResult.Type.BLOCK) { - pos.typeOfHit = RayTraceResult.Type.MISS; + if(pos != null && pos.type == RayTraceResult.Type.BLOCK) { + pos.type = RayTraceResult.Type.MISS; } return pos; } //#else //$$ @Override + //$$ public RayTraceResult rayTrace(double p_174822_1_, float p_174822_3_) { + //$$ RayTraceResult pos = super.rayTrace(p_174822_1_, 1f); + //$$ + //$$ // Make sure we can never look at blocks (-> no outline) + //$$ if(pos != null && pos.typeOfHit == RayTraceResult.Type.BLOCK) { + //$$ pos.typeOfHit = RayTraceResult.Type.MISS; + //$$ } + //$$ + //$$ return pos; + //$$ } + //#endif + //#else + //$$ @Override //$$ public MovingObjectPosition rayTrace(double p_174822_1_, float p_174822_3_) { //$$ MovingObjectPosition pos = super.rayTrace(p_174822_1_, 1f); //$$ @@ -430,15 +473,22 @@ public class CameraEntity //$$ } //#endif + /* FIXME @Override public void openGui(Object mod, int modGuiId, World world, int x, int y, int z) { // Do not open any block GUIs for the camera entities // Note: Vanilla GUIs are filtered out on a packet level, this only applies to mod GUIs } + */ @Override - public void setDead() { - super.setDead(); + //#if MC>=11300 + public void remove() { + super.remove(); + //#else + //$$ public void setDead() { + //$$ super.setDead(); + //#endif FORGE_BUS.unregister(eventHandler); FML_BUS.unregister(eventHandler); } @@ -530,11 +580,11 @@ public class CameraEntity @SubscribeEvent public void preCrosshairRender(RenderGameOverlayEvent.Pre event) { // The crosshair should only render if targeted entity can actually be spectated - if (getType(event) == RenderGameOverlayEvent.ElementType.CROSSHAIRS) { + if (MCVer.getType(event) == RenderGameOverlayEvent.ElementType.CROSSHAIRS) { event.setCanceled(!canSpectate(mc.pointedEntity)); } // Hotbar should never be rendered - if (getType(event) == RenderGameOverlayEvent.ElementType.HOTBAR) { + if (MCVer.getType(event) == RenderGameOverlayEvent.ElementType.HOTBAR) { event.setCanceled(true); } } @@ -568,12 +618,14 @@ public class CameraEntity lastHandRendered = player; //#if MC>=10904 + /* FIXME needs AT mc.entityRenderer.itemRenderer.prevEquippedProgressMainHand = 1; mc.entityRenderer.itemRenderer.prevEquippedProgressOffHand = 1; mc.entityRenderer.itemRenderer.equippedProgressMainHand = 1; mc.entityRenderer.itemRenderer.equippedProgressOffHand = 1; mc.entityRenderer.itemRenderer.itemStackMainHand = player.getItemStackFromSlot(EntityEquipmentSlot.MAINHAND); mc.entityRenderer.itemRenderer.itemStackOffHand = player.getItemStackFromSlot(EntityEquipmentSlot.OFFHAND); + */ //#else //$$ mc.entityRenderer.itemRenderer.prevEquippedProgress = 1; //$$ mc.entityRenderer.itemRenderer.equippedProgress = 1; @@ -605,7 +657,7 @@ public class CameraEntity @SubscribeEvent public void preRenderGameOverlay(RenderGameOverlayEvent.Pre event) { - switch (getType(event)) { + switch (MCVer.getType(event)) { case ALL: heldItemTooltipsWasTrue = mc.gameSettings.heldItemTooltips; mc.gameSettings.heldItemTooltips = false; @@ -641,7 +693,7 @@ public class CameraEntity @SubscribeEvent public void postRenderGameOverlay(RenderGameOverlayEvent.Post event) { - if (getType(event) != RenderGameOverlayEvent.ElementType.ALL) return; + if (MCVer.getType(event) != RenderGameOverlayEvent.ElementType.ALL) return; mc.gameSettings.heldItemTooltips = heldItemTooltipsWasTrue; } } diff --git a/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java b/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java index 60d168c2..e9b609cd 100644 --- a/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java +++ b/src/main/java/com/replaymod/replay/camera/ClassicCameraController.java @@ -1,9 +1,8 @@ package com.replaymod.replay.camera; -import net.minecraft.client.Minecraft; import net.minecraft.client.settings.KeyBinding; -import org.lwjgl.Sys; -import org.lwjgl.util.vector.Vector3f; + +import javax.vecmath.Vector3f; import static com.replaymod.core.versions.MCVer.*; @@ -22,7 +21,7 @@ public class ClassicCameraController implements CameraController { private Vector3f direction; private Vector3f dirBefore; private double motion; - private long lastCall = Sys.getTime(); + private long lastCall = System.currentTimeMillis(); private boolean speedup = false; @@ -34,7 +33,7 @@ public class ClassicCameraController implements CameraController { public void update(float partialTicksPassed) { boolean forward = false, backward = false, left = false, right = false, up = false, down = false; speedup = false; - for(KeyBinding kb : Minecraft.getMinecraft().gameSettings.keyBindings) { + for(KeyBinding kb : getMinecraft().gameSettings.keyBindings) { if(!isKeyDown(kb)) continue; if(kb.getKeyDescription().equals("key.forward")) { forward = true; @@ -109,7 +108,7 @@ public class ClassicCameraController implements CameraController { } private void updateMovement() { - long frac = Sys.getTime() - lastCall; + long frac = System.currentTimeMillis() - lastCall; if(frac == 0) return; @@ -124,13 +123,14 @@ public class ClassicCameraController implements CameraController { motion = Math.min(motion, MAX_SPEED); - lastCall = Sys.getTime(); + lastCall = System.currentTimeMillis(); if(direction == null || direction.lengthSquared() == 0 || motion < THRESHOLD) { return; } - Vector3f movement = direction.normalise(null); + Vector3f movement = new Vector3f(direction); + movement.normalize(); double factor = motion * (frac / 1000D); camera.moveCamera(movement.x * factor, movement.y * factor, movement.z * factor); @@ -162,7 +162,9 @@ public class ClassicCameraController implements CameraController { Vector3f dbf = direction; if(dirBefore != null) { - direction = Vector3f.add(direction, dirBefore.normalise(null), null); + dirBefore.normalize(); + dirBefore.add(direction); + direction = dirBefore; } dirBefore = dbf; diff --git a/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java b/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java index d66f954e..256440f7 100644 --- a/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java +++ b/src/main/java/com/replaymod/replay/camera/SpectatorCameraController.java @@ -6,7 +6,12 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.settings.KeyBinding; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; -import org.lwjgl.input.Mouse; + +//#if MC>=11300 +// FIXME +//#else +//$$ import org.lwjgl.input.Mouse; +//#endif import java.util.Arrays; @@ -18,7 +23,7 @@ public class SpectatorCameraController implements CameraController { @Override public void update(float partialTicksPassed) { - Minecraft mc = Minecraft.getMinecraft(); + Minecraft mc = getMinecraft(); if (mc.gameSettings.keyBindSneak.isPressed()) { ReplayModReplay.instance.getReplayHandler().spectateCamera(); } @@ -31,7 +36,11 @@ public class SpectatorCameraController implements CameraController { } // Prevent mouse movement - Mouse.updateCursor(); + //#if MC>=11300 + // FIXME + //#else + //$$ Mouse.updateCursor(); + //#endif // Always make sure the camera is in the exact same spot as the spectated entity // This is necessary as some rendering code for the hand doesn't respect the view entity diff --git a/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java b/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java index 7e1085e1..803322f8 100644 --- a/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java +++ b/src/main/java/com/replaymod/replay/camera/VanillaCameraController.java @@ -1,9 +1,15 @@ package com.replaymod.replay.camera; import net.minecraft.client.Minecraft; -import net.minecraft.client.settings.GameSettings; import net.minecraft.client.settings.KeyBinding; -import org.lwjgl.util.vector.Vector3f; + +//#if MC>=11300 +import net.minecraft.client.GameSettings; +//#else +//$$ import net.minecraft.client.settings.GameSettings; +//#endif + +import javax.vecmath.Vector3f; import static com.replaymod.core.versions.MCVer.*; @@ -42,11 +48,11 @@ public class VanillaCameraController implements CameraController { Vector3f direction = new Vector3f(0, 0, 0); for (int i = 0; i < 6; i++) { // First, get movement direction depending on keys pressed if (isKeyDown(bindings[i])) { - Vector3f.add(direction, DIRECTIONS[i], direction); + direction.add(DIRECTIONS[i]); } } if (direction.length() == 0) return; - direction.normalise(direction); // Normalize, so we don't move quicker if we hold down multiple keys + direction.normalize(); // Normalize, so we don't move quicker if we hold down multiple keys double yawRadians = Math.toRadians(camera.rotationYaw); float yawSin = (float) Math.sin(yawRadians), yawCos = (float) Math.cos(yawRadians); // Rotate by yaw diff --git a/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java b/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java index 9ad07adf..2c096a45 100644 --- a/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java +++ b/src/main/java/com/replaymod/replay/events/ReplayChatMessageEvent.java @@ -5,8 +5,13 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.Cancelable; -import net.minecraftforge.fml.common.eventhandler.Event; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.Cancelable; +//$$ import net.minecraftforge.fml.common.eventhandler.Event; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.Cancelable; //$$ import cpw.mods.fml.common.eventhandler.Event; diff --git a/src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java b/src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java index b228c675..23c0fc1d 100644 --- a/src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java +++ b/src/main/java/com/replaymod/replay/events/ReplayCloseEvent.java @@ -5,7 +5,11 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.Event; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.Event; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.Event; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.Event; //#endif diff --git a/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java b/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java index 5d972056..d769747d 100644 --- a/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java +++ b/src/main/java/com/replaymod/replay/events/ReplayDispatchKeypressesEvent.java @@ -1,8 +1,13 @@ package com.replaymod.replay.events; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.Cancelable; -import net.minecraftforge.fml.common.eventhandler.Event; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.Cancelable; +//$$ import net.minecraftforge.fml.common.eventhandler.Event; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.Cancelable; //$$ import cpw.mods.fml.common.eventhandler.Event; diff --git a/src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java b/src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java index 0e305532..6aef11ce 100644 --- a/src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java +++ b/src/main/java/com/replaymod/replay/events/ReplayOpenEvent.java @@ -5,7 +5,11 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.Event; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.Event; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.Event; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.Event; //#endif diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java index cc94f3cc..3d797cf6 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java @@ -12,8 +12,13 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.utils.Colors; import com.replaymod.replaystudio.data.Marker; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.ReadablePoint; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; + +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif public class GuiEditMarkerPopup extends AbstractGuiPopup implements Typeable { private static GuiNumberField newGuiNumberField() { diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java index 0a376fbf..d48343c0 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java @@ -8,10 +8,10 @@ import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; import de.johni0702.minecraft.gui.element.advanced.AbstractGuiTimeline; import de.johni0702.minecraft.gui.function.Draggable; +import de.johni0702.minecraft.gui.utils.lwjgl.Point; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import net.minecraft.client.resources.I18n; -import org.lwjgl.util.Point; -import org.lwjgl.util.ReadableDimension; -import org.lwjgl.util.ReadablePoint; import static de.johni0702.minecraft.gui.utils.Utils.clamp; diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java index 6f968243..ec9f5e1a 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiReplayOverlay.java @@ -14,15 +14,24 @@ import de.johni0702.minecraft.gui.element.GuiTooltip; import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; +import de.johni0702.minecraft.gui.utils.lwjgl.WritablePoint; import net.minecraft.client.resources.I18n; -import net.minecraft.client.settings.GameSettings; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.ReadableDimension; -import org.lwjgl.util.ReadablePoint; -import org.lwjgl.util.WritablePoint; + +//#if MC>=11300 +import net.minecraft.client.GameSettings; +//#else +//$$ import net.minecraft.client.settings.GameSettings; +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif import net.minecraftforge.fml.common.gameevent.InputEvent; //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -160,12 +169,14 @@ public class GuiReplayOverlay extends AbstractGuiOverlay { setMouseVisible(true); } } + /* FIXME if (Keyboard.getEventKeyState()) { // Handle the F1 key binding while the overlay is opened as a gui screen if (isMouseVisible() && Keyboard.getEventKey() == Keyboard.KEY_F1) { gameSettings.hideGUI = !gameSettings.hideGUI; } } + */ } @Override diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 388714ed..378750c3 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -10,6 +10,7 @@ import com.replaymod.core.ReplayMod; import com.replaymod.core.SettingsRegistry; import com.replaymod.core.gui.GuiReplaySettings; import com.replaymod.core.utils.Utils; +import com.replaymod.core.versions.MCVer; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.Setting; import com.replaymod.replaystudio.replay.ReplayFile; @@ -31,24 +32,24 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; +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 lombok.Getter; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; -import net.minecraft.util.Util; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.lwjgl.Sys; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; -import org.lwjgl.util.ReadablePoint; -import java.awt.*; +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif + import java.awt.image.BufferedImage; import java.io.File; import java.io.FileFilter; @@ -57,6 +58,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import static com.replaymod.replay.ReplayModReplay.LOGGER; +import static com.replaymod.core.versions.MCVer.*; public class GuiReplayViewer extends GuiScreen { private final ReplayModReplay mod; @@ -93,29 +95,8 @@ public class GuiReplayViewer extends GuiScreen { public void run() { try { File folder = mod.getCore().getReplayFolder(); - String path = folder.getAbsolutePath(); - // First try OS specific methods - try { - switch (Util.getOSType()) { - case WINDOWS: - Runtime.getRuntime().exec(String.format("cmd.exe /C start \"Open file\" \"%s\"", path)); - return; - case OSX: - Runtime.getRuntime().exec(new String[]{"/usr/bin/open", path}); - return; - } - } catch (IOException e) { - LogManager.getLogger().error("Cannot open file", e); - } - - // Otherwise try to java way - try { - Desktop.getDesktop().browse(folder.toURI()); - } catch (Throwable throwable) { - // And if all fails, lwjgl - Sys.openURL("file://" + path); - } + MCVer.openFile(folder); } catch (IOException e) { mod.getLogger().error("Cannot open file", e); } @@ -232,7 +213,7 @@ public class GuiReplayViewer extends GuiScreen { try { list.setFolder(mod.getCore().getReplayFolder()); } catch (IOException e) { - throw new ReportedException(CrashReport.makeCrashReport(e, "Getting replay folder")); + throw newReportedException(CrashReport.makeCrashReport(e, "Getting replay folder")); } setTitle(new GuiLabel().setI18nText("replaymod.gui.replayviewer")); diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java index fd1116b4..587d639d 100644 --- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java @@ -1,5 +1,6 @@ package com.replaymod.replay.handler; +import com.replaymod.core.versions.MCVer; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replay.gui.screen.GuiReplayViewer; import net.minecraft.client.Minecraft; @@ -11,7 +12,11 @@ import net.minecraft.client.resources.I18n; import net.minecraftforge.client.event.GuiScreenEvent; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -32,7 +37,7 @@ public class GuiHandler { private static final int BUTTON_REPLAY_VIEWER = 17890234; private static final int BUTTON_EXIT_REPLAY = 17890235; - private static final Minecraft mc = Minecraft.getMinecraft(); + private static final Minecraft mc = MCVer.getMinecraft(); private final ReplayModReplay mod; @@ -130,7 +135,7 @@ public class GuiHandler { return; } GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, getGui(event).width / 2 - 100, - getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer")); + getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer")){}; button.width = button.width / 2 - 2; getButtonList(event).add(button); } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java b/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java index e2ce7c2d..2a64aef8 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java @@ -2,7 +2,6 @@ package com.replaymod.replay.mixin; import com.replaymod.replay.camera.CameraEntity; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiSpectator; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -20,7 +19,7 @@ public abstract class MixinGuiSpectator { //#endif public void isInReplay(int i, CallbackInfo ci) { // Prevent spectator gui from opening while in a replay - if (player(Minecraft.getMinecraft()) instanceof CameraEntity) { + if (player(getMinecraft()) instanceof CameraEntity) { ci.cancel(); } } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java b/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java index dd3201a2..14210b94 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinParticleManager.java @@ -15,7 +15,11 @@ import java.util.Queue; @Mixin(ParticleManager.class) public abstract class MixinParticleManager { @Shadow - private Queue queueEntityFX; + //#if MC>=11300 + private Queue queue; + //#else + //$$ private Queue queueEntityFX; + //#endif /** * This method additionally clears the queue of particles to be added when the world is changed. @@ -27,7 +31,11 @@ public abstract class MixinParticleManager { */ @Inject(method = "clearEffects", at = @At("HEAD")) public void replayModReplay_clearParticleQueue(World world, CallbackInfo ci) { - queueEntityFX.clear(); + //#if MC>=11300 + queue.clear(); + //#else + //$$ queueEntityFX.clear(); + //#endif } } //#endif diff --git a/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java b/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java index ded49f19..d4a1cc14 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java @@ -13,7 +13,11 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; //#if MC>=11200 -import net.minecraft.stats.RecipeBook; +//#if MC>=11300 +import net.minecraft.client.util.RecipeBookClient; +//#else +//$$ import net.minecraft.stats.RecipeBook; +//#endif //#endif //#if MC>=10904 import net.minecraft.stats.StatisticsManager; @@ -44,10 +48,16 @@ public abstract class MixinPlayerControllerMP { //#endif //#if MC>=11200 - @Inject(method = "func_192830_a", at=@At("HEAD"), cancellable = true) - private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, RecipeBook recipeBook, CallbackInfoReturnable ci) { + @Inject(method = "createPlayer", at=@At("HEAD"), cancellable = true) + private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, RecipeBookClient recipeBookClient, CallbackInfoReturnable ci) { if (ReplayModReplay.instance.getReplayHandler() != null) { - ci.setReturnValue(new CameraEntity(mc, worldIn, connection, statisticsManager, recipeBook)); + ci.setReturnValue(new CameraEntity(mc, worldIn, connection, statisticsManager, recipeBookClient)); + //#else + //#if MC>=11200 + //$$ @Inject(method = "func_192830_a", at=@At("HEAD"), cancellable = true) + //$$ private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, RecipeBook recipeBook, CallbackInfoReturnable ci) { + //$$ if (ReplayModReplay.instance.getReplayHandler() != null) { + //$$ ci.setReturnValue(new CameraEntity(mc, worldIn, connection, statisticsManager, recipeBook)); //#else //#if MC>=10904 //$$ @Inject(method = "createClientPlayer", at=@At("HEAD"), cancellable = true) @@ -67,13 +77,18 @@ public abstract class MixinPlayerControllerMP { //$$ ci.setReturnValue(new CameraEntity(mc, worldIn, mc.getSession(), netClientHandler, statFileWriter)); //#endif //#endif + //#endif //#endif ci.cancel(); } } //#if MC>=10800 - @Inject(method = "isSpectator", at=@At("HEAD"), cancellable = true) + //#if MC>=11300 + @Inject(method = "isSpectatorMode", at=@At("HEAD"), cancellable = true) + //#else + //$$ @Inject(method = "isSpectator", at=@At("HEAD"), cancellable = true) + //#endif private void replayModReplay_isSpectator(CallbackInfoReturnable ci) { if (player(mc) instanceof CameraEntity) { // this check should in theory not be required ci.setReturnValue(player(mc).isSpectator()); diff --git a/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java index aaf11b77..bb9d3f06 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java @@ -2,25 +2,42 @@ package com.replaymod.replay.mixin; import com.replaymod.replay.ReplayHandler; import com.replaymod.replay.ReplayModReplay; -import net.minecraft.client.Minecraft; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; +//#if MC>=11300 +import net.minecraft.client.renderer.ItemRenderer; +import net.minecraft.util.Util; +//#else +//$$ import net.minecraft.client.Minecraft; //#if MC>=10904 -import net.minecraft.client.renderer.RenderItem; +//$$ import net.minecraft.client.renderer.RenderItem; //#else //$$ import net.minecraft.client.renderer.entity.RenderItem; //#endif +//#endif -@Mixin(RenderItem.class) +//#if MC>=11300 +@Mixin(ItemRenderer.class) +//#else +//$$ @Mixin(RenderItem.class) +//#endif public class MixinRenderItem { - @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) - private long getEnchantmentTime() { + //#if MC>=11300 + @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;milliTime()J")) + //#else + //$$ @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) + //#endif + private static long getEnchantmentTime() { ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); if (replayHandler != null) { return replayHandler.getReplaySender().currentTimeStamp(); } - return Minecraft.getSystemTime(); + //#if MC>=11300 + return Util.milliTime(); + //#else + //$$ return Minecraft.getSystemTime(); + //#endif } } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java index 75afcf23..24ca21b7 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderLivingBase.java @@ -1,7 +1,6 @@ package com.replaymod.replay.mixin; import com.replaymod.replay.camera.CameraEntity; -import net.minecraft.client.Minecraft; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import org.spongepowered.asm.mixin.Mixin; @@ -26,7 +25,7 @@ import static com.replaymod.core.versions.MCVer.*; public abstract class MixinRenderLivingBase { @Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true) private void replayModReplay_canRenderInvisibleName(EntityLivingBase entity, CallbackInfoReturnable ci) { - EntityPlayer thePlayer = player(Minecraft.getMinecraft()); + EntityPlayer thePlayer = player(getMinecraft()); if (thePlayer instanceof CameraEntity && entity.isInvisible()) { ci.setReturnValue(false); } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java index 71ab6cfb..7c6ef7bd 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderManager.java @@ -3,7 +3,6 @@ package com.replaymod.replay.mixin; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.entity.RenderManager; -import net.minecraft.client.settings.GameSettings; import net.minecraft.entity.Entity; import net.minecraft.entity.passive.EntityAnimal; import net.minecraft.world.World; @@ -13,6 +12,12 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11300 +import net.minecraft.client.GameSettings; +//#else +//$$ import net.minecraft.client.settings.GameSettings; +//#endif + @Mixin(RenderManager.class) public class MixinRenderManager { @Shadow diff --git a/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java index cd1d9493..0d07041c 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java @@ -2,11 +2,16 @@ package com.replaymod.replay.mixin; import com.replaymod.replay.ReplayHandler; import com.replaymod.replay.ReplayModReplay; -import net.minecraft.client.Minecraft; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; +//#if MC>=11300 +import net.minecraft.util.Util; +//#else +import net.minecraft.client.Minecraft; +//#endif + //#if MC>=10800 import net.minecraft.client.renderer.tileentity.TileEntityEndPortalRenderer; //#else @@ -19,8 +24,11 @@ import net.minecraft.client.renderer.tileentity.TileEntityEndPortalRenderer; //$$ @Mixin(RenderEndPortal.class) //#endif public class MixinTileEntityEndPortalRenderer { + //#if MC>=11300 + @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;milliTime()J")) + //#else //#if MC>=10809 - @Redirect(method = "renderTileEntityAt", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) + //$$ @Redirect(method = "renderTileEntityAt", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) //#else //#if MC>=10800 //$$ @Redirect(method = "func_180544_a", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) @@ -28,11 +36,16 @@ public class MixinTileEntityEndPortalRenderer { //$$ @Redirect(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntityEndPortal;DDDF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) //#endif //#endif - private long replayModReplay_getEnchantmentTime() { + //#endif + private static long replayModReplay_getEnchantmentTime() { ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); if (replayHandler != null) { return replayHandler.getReplaySender().currentTimeStamp(); } - return Minecraft.getSystemTime(); + //#if MC>=11300 + return Util.milliTime(); + //#else + //$$ return Minecraft.getSystemTime(); + //#endif } } diff --git a/versions/common.gradle b/versions/common.gradle index 4c2aacf0..a6d9c344 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -273,7 +273,6 @@ sourceSets { exclude 'com/replaymod/online' exclude 'com/replaymod/pathing' exclude 'com/replaymod/render' - exclude 'com/replaymod/replay' exclude 'com/replaymod/restrictions' exclude 'com/replaymod/simplepathing' } From 1e584769afcd6191fd4d16e1ee00f0fd5bd00eca Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 28 Feb 2019 17:12:25 +0100 Subject: [PATCH 34/65] Misc 1.13 progress --- .../java/com/replaymod/core/ReplayMod.java | 4 + .../core/versions/LangResourcePack.java | 146 ++++++++++++++++++ .../com/replaymod/core/versions/MCVer.java | 16 ++ .../replaymod/replay/FullReplaySender.java | 1 - .../replaymod/replay/handler/GuiHandler.java | 28 +++- .../MixinTileEntityEndPortalRenderer.java | 2 +- versions/common.gradle | 1 + 7 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/replaymod/core/versions/LangResourcePack.java diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 67cc6323..2a47ed8b 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -19,6 +19,7 @@ import org.apache.commons.io.Charsets; import org.apache.commons.io.FileUtils; //#if MC>=11300 +import com.replaymod.core.versions.LangResourcePack; import net.minecraft.resources.FolderPack; import net.minecraft.resources.IResourcePack; import net.minecraftforge.fml.javafmlmod.FMLModLoadingContext; @@ -168,6 +169,9 @@ public class ReplayMod { OpenGLUtils.init(); I18n.setI18n(net.minecraft.client.resources.I18n::format); + //#if MC>=11300 + MCVer.getMinecraft().resourcePackRepository.addPackFinder(new LangResourcePack.Finder()); + //#endif //#if MC>=11300 config = new Configuration(new File(mcDataDir(mc), "configs/replaymod.cfg")); // FIXME where'd the suggestion go? diff --git a/src/main/java/com/replaymod/core/versions/LangResourcePack.java b/src/main/java/com/replaymod/core/versions/LangResourcePack.java new file mode 100644 index 00000000..b29f9537 --- /dev/null +++ b/src/main/java/com/replaymod/core/versions/LangResourcePack.java @@ -0,0 +1,146 @@ +//#if MC>=11300 +package com.replaymod.core.versions; + +import com.google.gson.Gson; +import com.replaymod.core.ReplayMod; +import net.minecraft.resources.AbstractResourcePack; +import net.minecraft.resources.IPackFinder; +import net.minecraft.resources.IResourcePack; +import net.minecraft.resources.ResourcePackFileNotFoundException; +import net.minecraft.resources.ResourcePackInfo; +import net.minecraft.resources.ResourcePackType; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.packs.ModFileResourcePack; +import net.minecraftforge.fml.packs.ResourcePackLoader; +import org.apache.commons.io.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringBufferInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Resource pack which on-the-fly converts pre-1.13 language files into 1.13 json format. + */ +public class LangResourcePack extends AbstractResourcePack { + private static final Gson GSON = new Gson(); + private static final String NAME = "replaymod_lang"; + private static final Pattern JSON_FILE_PATTERN = Pattern.compile("^assets/" + ReplayMod.MOD_ID + "/lang/([a-z][a-z])_([a-z][a-z]).json$"); + private static final Pattern LANG_FILE_NAME_PATTERN = Pattern.compile("^([a-z][a-z])_([a-z][a-z]).lang$"); + + LangResourcePack() { + super(new File(NAME)); + } + + private ModFileResourcePack getParent() { + return (ModFileResourcePack) ResourcePackLoader.getResourcePackFor(ReplayMod.MOD_ID); + } + + private String langName(String path) { + Matcher matcher = JSON_FILE_PATTERN.matcher(path); + if (!matcher.matches()) return null; + return String.format("%s_%s.lang", matcher.group(1), matcher.group(2).toUpperCase()); + } + + private Path langPath(String path) { + ModFileResourcePack parent = getParent(); + if (parent == null) return null; + ModFile modFile = parent.getModFile(); + + String langName = langName(path); + if (langName == null) return null; + return modFile.getLocator().findPath(modFile, "assets", ReplayMod.MOD_ID, "lang", langName); + } + + private String convertValue(String value) { + return value; + } + + @Override + protected InputStream getInputStream(String path) throws IOException { + if ("pack.mcmeta".equals(path)) { + return new StringBufferInputStream("{\"pack\": {\"description\": \"ReplayMod language files\", \"pack_format\": 4}}"); + } + + Path langPath = langPath(path); + if (langPath == null) throw new ResourcePackFileNotFoundException(file, path); + + String langFile; + try (InputStream in = Files.newInputStream(langPath)) { + langFile = IOUtils.toString(in); + } + + Map properties = new HashMap<>(); + for (String line : langFile.split("\n")) { + if (line.trim().isEmpty() || line.trim().startsWith("#")) continue; + int i = line.indexOf('='); + String key = line.substring(0, i); + String value = line.substring(i + 1); + value = convertValue(value); + properties.put(key, value); + } + + return new ByteArrayInputStream(GSON.toJson(properties).getBytes(StandardCharsets.UTF_8)); + } + + @Override + protected boolean resourceExists(String path) { + Path langPath = langPath(path); + return langPath != null && Files.exists(langPath); + } + + @Override + public Collection getAllResourceLocations(ResourcePackType resourcePackType, String path, int maxDepth, Predicate filter) { + if (resourcePackType == ResourcePackType.CLIENT_RESOURCES && "lang".equals(path)) { + IResourcePack parent = getParent(); + if (parent == null) return Collections.emptyList(); + return parent.getAllResourceLocations(resourcePackType, path, maxDepth, name -> { + Matcher matcher = LANG_FILE_NAME_PATTERN.matcher(name); + if (matcher.matches()) { + return filter.test(String.format("%s_%s.json", matcher.group(1), matcher.group(1))); + } else { + return false; + } + }).stream().map(resourceLocation -> { + String p = resourceLocation.getPath().substring(0, "assets/lang/xx_XX".length()) + ".json"; + return new ResourceLocation(resourceLocation.getNamespace(), p); + }).collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + + @Override + public Set getResourceNamespaces(ResourcePackType resourcePackType) { + if (resourcePackType == ResourcePackType.CLIENT_RESOURCES) { + return Collections.singleton("replaymod"); + } else { + return Collections.emptySet(); + } + } + + @Override + public void close() {} + + public static class Finder implements IPackFinder { + @Override + public void addPackInfosToMap(Map packList, ResourcePackInfo.IFactory factory) { + packList.put(NAME, ResourcePackInfo.func_195793_a(NAME, true, LangResourcePack::new, factory, ResourcePackInfo.Priority.BOTTOM)); + } + } +} +//#endif diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index fba3f8fd..8d620079 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -120,6 +120,22 @@ public class MCVer { //#endif } + public static void addButton(GuiScreenEvent.InitGuiEvent event, GuiButton button) { + //#if MC>=11300 + event.addButton(button); + //#else + //$$ getButtonList(event).add(button); + //#endif + } + + public static void removeButton(GuiScreenEvent.InitGuiEvent event, GuiButton button) { + //#if MC>=11300 + event.removeButton(button); + //#else + //$$ getButtonList(event).remove(button); + //#endif + } + @SuppressWarnings("unchecked") public static List getButtonList(GuiScreenEvent.InitGuiEvent event) { //#if MC>=10904 diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index c1b462e0..d2ebf095 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -383,7 +383,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) { //#endif //#if MC>=11300 - //#else world.getChunk(chunkX, chunkY).removeEntity(entity); //#else //$$ world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity); diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java index 587d639d..62971eb9 100644 --- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java @@ -66,18 +66,25 @@ public class GuiHandler { switch (b.id) { // Replace "Exit Server" button with "Exit Replay" button case BUTTON_EXIT_SERVER: - b.displayString = I18n.format("replaymod.gui.exit"); - b.id = BUTTON_EXIT_REPLAY; + removeButton(event, b); + addButton(event, new GuiButton(BUTTON_EXIT_REPLAY, b.x, b.y, b.width, b.height, I18n.format("replaymod.gui.exit")) { + //#if MC>=11300 + @Override + public void onClick(double mouseX, double mouseY) { + onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>())); + } + //#endif + }); break; // Remove "Advancements", "Stats" and "Open to LAN" buttons case BUTTON_ADVANCEMENTS: - buttonList.remove(achievements = b); + removeButton(event, achievements = b); break; case BUTTON_STATS: - buttonList.remove(stats = b); + removeButton(event, stats = b); break; case BUTTON_OPEN_TO_LAN: - buttonList.remove(openToLan = b); + removeButton(event, openToLan = b); break; } } @@ -135,9 +142,16 @@ public class GuiHandler { return; } GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, getGui(event).width / 2 - 100, - getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer")){}; + getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer")) { + //#if MC>=11300 + @Override + public void onClick(double mouseX, double mouseY) { + onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>())); + } + //#endif + }; button.width = button.width / 2 - 2; - getButtonList(event).add(button); + addButton(event, button); } @SubscribeEvent diff --git a/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java index 0d07041c..92717657 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java @@ -9,7 +9,7 @@ import org.spongepowered.asm.mixin.injection.Redirect; //#if MC>=11300 import net.minecraft.util.Util; //#else -import net.minecraft.client.Minecraft; +//$$ import net.minecraft.client.Minecraft; //#endif //#if MC>=10800 diff --git a/versions/common.gradle b/versions/common.gradle index a6d9c344..7308c73f 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -121,6 +121,7 @@ minecraft { afterEvaluate { if (mcVersion >= 11300) { + // FIXME } else { // Note cannot use minecraft.replace because that has already been forwarded to the task by FG by now tasks.sourceMainJava.replace '@MOD_VERSION@', project.version From b6a4206816c94fd40f489f47427a812f423fdb42 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 28 Feb 2019 17:27:51 +0100 Subject: [PATCH 35/65] Update FG3 and Forge from git version to first official snapshots --- jGui | 2 +- settings.gradle | 4 +-- versions/{1.13 => 1.13.2}/.gitkeep | 0 versions/common.gradle | 54 +++++++++++++++++------------- 4 files changed, 33 insertions(+), 27 deletions(-) rename versions/{1.13 => 1.13.2}/.gitkeep (100%) diff --git a/jGui b/jGui index 48b3d759..73a6d9f7 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 48b3d75950b9610b214dd1f8f6f69c1ebe858320 +Subproject commit 73a6d9f774327ee753f350d8d772a61404f8bcaf diff --git a/settings.gradle b/settings.gradle index d73b297f..1b5630fa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,7 @@ include ':jGui' //'1.8.9', FIXME //'1.9.4', //'1.12', - '1.13' + '1.13.2' ].each { version -> include ":jGui:$version" project(":jGui:$version").with { @@ -27,7 +27,7 @@ def versions = [ //'1.12', //'1.12.1', //'1.12.2', - '1.13' + '1.13.2' ] versions.each { version -> include ":$version" diff --git a/versions/1.13/.gitkeep b/versions/1.13.2/.gitkeep similarity index 100% rename from versions/1.13/.gitkeep rename to versions/1.13.2/.gitkeep diff --git a/versions/common.gradle b/versions/common.gradle index 7308c73f..9761eb10 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -5,7 +5,7 @@ buildscript { mcVersion = "${major}${minor.padLeft(2, '0')}${(patch ?: '').padLeft(2, '0')}" as int } else { def f = file('mcVersion') - mcVersion = f.exists() ? f.readLines().first() as int : 11300 + mcVersion = f.exists() ? f.readLines().first() as int : 11302 } project.ext.mcVersion = mcVersion @@ -77,13 +77,17 @@ archivesBaseName = "replaymod" minecraft { if (FG3) { - runConfig { - name = 'Minecraft Client' - main = 'net.minecraftforge.userdev.UserdevLauncher' - ideaModuleName = '${project.name}_main' - workingDirectory = project.file('eclipse').canonicalPath - environment 'target', 'fmldevclient' - environment 'assetDirectory', downloadAssets.output.absolutePath + runs { + client { + workingDirectory project.file('eclipse') + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + property 'forge.logging.console.level', 'debug' + mods { + replaymod { + source sourceSets.main + } + } + } } } else { if (mcVersion >= 10800) { @@ -92,20 +96,22 @@ minecraft { runDir = "../../eclipse" } - version = [ - 11202: '1.12.2-14.23.0.2486', - 11201: '1.12.1-14.22.0.2444', - 11200: '1.12-14.21.1.2387', - 11102: '1.11.2-13.20.0.2216', - 11100: '1.11-13.19.1.2188', - 11002: '1.10.2-12.18.2.2099', - 10904: '1.9.4-12.17.0.1976', - 10809: '1.8.9-11.15.1.1722', - 10800: '1.8-11.14.4.1563', - 10710: '1.7.10-10.13.4.1558-1.7.10', - ][mcVersion] + if (!FG3) { + version = [ + 11202: '1.12.2-14.23.0.2486', + 11201: '1.12.1-14.22.0.2444', + 11200: '1.12-14.21.1.2387', + 11102: '1.11.2-13.20.0.2216', + 11100: '1.11-13.19.1.2188', + 11002: '1.10.2-12.18.2.2099', + 10904: '1.9.4-12.17.0.1976', + 10809: '1.8.9-11.15.1.1722', + 10800: '1.8-11.14.4.1563', + 10710: '1.7.10-10.13.4.1558-1.7.10', + ][mcVersion] + } mappings = [ - 11300: "snapshot_20180921-1.13", + 11302: "snapshot_20180921-1.13", 11202: "snapshot_20170615", 11201: "snapshot_20170615", 11200: "snapshot_20170615", @@ -149,8 +155,8 @@ configurations { dependencies { if (FG3) { - minecraft 'net.minecraftforge.test:forge:' + [ - 11300: '1.13-24.0.33-1.13-pre', + minecraft 'net.minecraftforge:forge:' + [ + 11302: '1.13.2-25.0.63', ][mcVersion] } @@ -174,7 +180,7 @@ dependencies { def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' - shade("com.replaymod.replaystudio:$studioVersion:2d8d41d"){ // FIXME + shade("com.replaymod.replaystudio:$studioVersion:98ab667"){ // FIXME exclude group: 'com.google.guava', module: 'guava' // provided by MC exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } From c1fefdeb3c60fff1f1e39219f4a51acc368d4fc4 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 1 Mar 2019 10:18:58 +0100 Subject: [PATCH 36/65] Get core, recording and replay modules compiling on 1.13.2 --- jGui | 2 +- src/main/java/com/replaymod/core/Module.java | 10 ++ .../java/com/replaymod/core/ReplayMod.java | 125 +++++++++-------- .../com/replaymod/core/SettingsRegistry.java | 130 +++++++++++++----- .../replaymod/core/gui/GuiReplaySettings.java | 4 +- .../java/com/replaymod/core/utils/Utils.java | 17 ++- .../core/versions/LangResourcePack.java | 2 +- .../recording/ReplayModRecording.java | 18 ++- .../handler/ConnectionEventHandler.java | 3 +- .../handler/RecordingEventHandler.java | 13 +- .../recording/mixin/MixinRenderGlobal.java | 6 +- .../recording/mixin/MixinWorldClient.java | 13 +- .../replaymod/replay/FullReplaySender.java | 32 ++++- .../com/replaymod/replay/NoGuiScreenshot.java | 13 +- .../com/replaymod/replay/ReplayModReplay.java | 42 ++---- .../camera/CameraControllerRegistry.java | 3 + src/main/resources/META-INF/mods.toml | 4 +- versions/common.gradle | 5 +- 18 files changed, 280 insertions(+), 162 deletions(-) create mode 100644 src/main/java/com/replaymod/core/Module.java diff --git a/jGui b/jGui index 73a6d9f7..e89934d6 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 73a6d9f774327ee753f350d8d772a61404f8bcaf +Subproject commit e89934d68671de490c4b7fc3245c6a7a74680968 diff --git a/src/main/java/com/replaymod/core/Module.java b/src/main/java/com/replaymod/core/Module.java new file mode 100644 index 00000000..321b1613 --- /dev/null +++ b/src/main/java/com/replaymod/core/Module.java @@ -0,0 +1,10 @@ +package com.replaymod.core; + +public interface Module { + // FMLCommonSetupEvent for 1.13+, FMLInitializationEvent below + default void initCommon() {} + // FMLClientSetupEvent for 1.13+, FMLInitializationEvent (if client) below + default void initClient() {} + // FMLClientSetupEvent for 1.13+, FMLInitializationEvent below + default void registerKeyBindings(KeyBindingRegistry registry) {} +} diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 2a47ed8b..ed840ffa 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -14,7 +14,6 @@ import de.johni0702.minecraft.gui.container.GuiScreen; import lombok.Getter; import net.minecraft.client.Minecraft; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.common.config.Configuration; import org.apache.commons.io.Charsets; import org.apache.commons.io.FileUtils; @@ -22,10 +21,15 @@ import org.apache.commons.io.FileUtils; import com.replaymod.core.versions.LangResourcePack; import net.minecraft.resources.FolderPack; import net.minecraft.resources.IResourcePack; -import net.minecraftforge.fml.javafmlmod.FMLModLoadingContext; +import net.minecraftforge.fml.DeferredWorkQueue; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.versions.mcp.MCPVersion; //#else //$$ import net.minecraft.client.resources.FolderResourcePack; //$$ import net.minecraft.client.resources.IResourcePack; +//$$ import net.minecraftforge.common.config.Configuration; //#endif //#if MC>=10904 @@ -51,12 +55,12 @@ import net.minecraftforge.fml.ModList; //$$ import net.minecraftforge.fml.common.Mod.EventHandler; //$$ import net.minecraftforge.fml.common.Mod.Instance; //$$ import net.minecraftforge.fml.common.ModContainer; +//$$ import net.minecraftforge.fml.common.event.FMLInitializationEvent; +//$$ import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +//$$ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; //$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; //#endif import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; //#else //$$ import cpw.mods.fml.common.Loader; @@ -99,7 +103,7 @@ import static com.replaymod.core.versions.MCVer.*; //#endif //$$ guiFactory = "com.replaymod.core.gui.GuiFactory") //#endif -public class ReplayMod { +public class ReplayMod implements Module { public static ModContainer getContainer() { //#if MC>=11300 @@ -111,7 +115,7 @@ public class ReplayMod { @Getter(lazy = true) //#if MC>=11300 - private static final String minecraftVersion = "1.13"; // FIXME + private static final String minecraftVersion = MCPVersion.getMCVersion(); //#else //$$ private static final String minecraftVersion = Loader.MC_VERSION; //#endif @@ -123,11 +127,16 @@ public class ReplayMod { private static final Minecraft mc = MCVer.getMinecraft(); - @Deprecated - public static Configuration config; + //#if MC<11300 + //$$ @Deprecated + //$$ public static Configuration config; + //#endif private final KeyBindingRegistry keyBindingRegistry = new KeyBindingRegistry(); private final SettingsRegistry settingsRegistry = new SettingsRegistry(); + { + settingsRegistry.register(Setting.class); + } // The instance of your mod that Forge uses. //#if MC>=11300 @@ -138,9 +147,31 @@ public class ReplayMod { public static ReplayMod instance; private final List modules = new ArrayList<>(); - { + + public ReplayMod() { + I18n.setI18n(net.minecraft.client.resources.I18n::format); + + // Initialize the static OpenGL info field from the minecraft main thread + // Unfortunately lwjgl uses static methods so we have to make use of magic init calls as well + DeferredWorkQueue.runLater(OpenGLUtils::init); + + //#if MC>=11300 + DeferredWorkQueue.runLater(() -> MCVer.getMinecraft().resourcePackRepository.addPackFinder(new LangResourcePack.Finder())); + //#endif + + // Register all RM modules + modules.add(this); modules.add(new ReplayModRecording(this)); modules.add(new ReplayModReplay(this)); + + //#if MC>=11300 + settingsRegistry.register(); + //#else + //$$ config = new Configuration(event.getSuggestedConfigurationFile()); + //$$ config.load(); + //$$ settingsRegistry.setConfiguration(config); + //$$ settingsRegistry.save(); // Save default values to disk + //#endif } public KeyBindingRegistry getKeyBindingRegistry() { @@ -158,32 +189,6 @@ public class ReplayMod { return folder; } - //#if MC>=11300 - { FMLModLoadingContext.get().getModEventBus().addListener(this::preInit); } - //#else - //$$ @EventHandler - //#endif - public void preInit(FMLPreInitializationEvent event) { - // Initialize the static OpenGL info field from the minecraft main thread - // Unfortunately lwjgl uses static methods so we have to make use of magic init calls as well - OpenGLUtils.init(); - - I18n.setI18n(net.minecraft.client.resources.I18n::format); - //#if MC>=11300 - MCVer.getMinecraft().resourcePackRepository.addPackFinder(new LangResourcePack.Finder()); - //#endif - - //#if MC>=11300 - config = new Configuration(new File(mcDataDir(mc), "configs/replaymod.cfg")); // FIXME where'd the suggestion go? - //#else - //$$ config = new Configuration(event.getSuggestedConfigurationFile()); - //#endif - // FIXME forge config api is currently broken config.load(); - settingsRegistry.setConfiguration(config); - - modules.forEach(m -> m.preInit(event)); - } - //#ifdef DEV_ENV static { // Note: even preInit is too late and we'd have to issue another resource reload @SuppressWarnings("unchecked") @@ -231,39 +236,41 @@ public class ReplayMod { //#endif //#if MC>=11300 - { FMLModLoadingContext.get().getModEventBus().addListener(this::init); } + { + FMLJavaModLoadingContext.get().getModEventBus().addListener((FMLCommonSetupEvent event) -> modules.forEach(Module::initCommon)); + FMLJavaModLoadingContext.get().getModEventBus().addListener((FMLClientSetupEvent event) -> modules.forEach(Module::initClient)); + FMLJavaModLoadingContext.get().getModEventBus().addListener((FMLClientSetupEvent event) -> modules.forEach(m -> m.registerKeyBindings(keyBindingRegistry))); + } //#else //$$ @EventHandler + //$$ public void init(FMLInitializationEvent event) { + //$$ modules.forEach(Module::initCommon); + //$$ modules.forEach(Module::initClient); + //$$ modules.forEach(m -> m.registerKeyBindings(keyBindingRegistry)); + //$$ } //#endif - public void init(FMLInitializationEvent event) { - getSettingsRegistry().register(Setting.class); + @Override + public void registerKeyBindings(KeyBindingRegistry registry) { + registry.registerKeyBinding("replaymod.input.settings", 0, () -> { + new GuiReplaySettings(null, settingsRegistry).display(); + }); + } + + @Override + public void initClient() { new MainMenuHandler().register(); //#if MC<=10710 //$$ FML_BUS.register(this); // For runLater(Runnable) //#endif + FML_BUS.register(keyBindingRegistry); - getKeyBindingRegistry().registerKeyBinding("replaymod.input.settings", 0, () -> { - new GuiReplaySettings(null, settingsRegistry).display(); - }); - - modules.forEach(m -> m.init(event)); - } - - //#if MC>=11300 - { FMLModLoadingContext.get().getModEventBus().addListener(this::postInit); } - //#else - //$$ @EventHandler - //#endif - public void postInit(FMLPostInitializationEvent event) { - settingsRegistry.save(); // Save default values to disk - // 1.7.10 crashes when render distance > 16 //#if MC>=10800 //if(!FMLClientHandler.instance().hasOptifine()) FIXME 1.13 update - GameSettings.Options.RENDER_DISTANCE.setValueMax(64f); + GameSettings.Options.RENDER_DISTANCE.setValueMax(64f); //#endif testIfMoeshAndExitMinecraft(); @@ -300,8 +307,6 @@ public class ReplayMod { e.printStackTrace(); } }); - - modules.forEach(m -> m.postInit(event)); } /** @@ -433,10 +438,4 @@ public class ReplayMod { mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentTranslation(message, args)); } } - - public static abstract class Module { - public void preInit(FMLPreInitializationEvent event) {} - public void init(FMLInitializationEvent event) {} - public void postInit(FMLPostInitializationEvent event) {} - } } diff --git a/src/main/java/com/replaymod/core/SettingsRegistry.java b/src/main/java/com/replaymod/core/SettingsRegistry.java index e76ff630..99885bf7 100644 --- a/src/main/java/com/replaymod/core/SettingsRegistry.java +++ b/src/main/java/com/replaymod/core/SettingsRegistry.java @@ -1,30 +1,67 @@ package com.replaymod.core; import com.replaymod.core.events.SettingsChangedEvent; -import net.minecraft.client.resources.I18n; -import net.minecraftforge.common.config.Configuration; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +//#if MC>=11300 +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +//#else +//$$ import net.minecraftforge.common.config.Configuration; +//#endif + import static com.replaymod.core.versions.MCVer.*; public class SettingsRegistry { private static final Object NULL_OBJECT = new Object(); private Map, Object> settings = new ConcurrentHashMap<>(); - private Configuration configuration; + //#if MC>=11300 + private ForgeConfigSpec spec; + private ModConfig config; + //#else + //$$ private Configuration configuration; + //#endif - public void setConfiguration(Configuration configuration) { - this.configuration = configuration; + //#if MC>=11300 + public void register() { + if (spec == null) { + ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); + for (SettingKey key : settings.keySet()) { + builder + .translation(key.getDisplayString()) + .define(key.getCategory() + "." + key.getKey(), key.getDefault()); + } + spec = builder.build(); + } + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::load); + ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, spec); + } - List> keys = new ArrayList<>(settings.keySet()); - settings.clear(); - for (SettingKey key : keys) { - register(key); + private void load(ModConfig.Loading event) { + config = event.getConfig(); + for (Map.Entry, Object> entry : settings.entrySet()) { + SettingKey key = entry.getKey(); + Object value = config.getConfigData().get(key.getCategory() + "." + key.getKey()); + entry.setValue(value == null ? key.getDefault() : value); } } + //#else + //$$ public void setConfiguration(Configuration configuration) { + //$$ this.configuration = configuration; + //$$ + //$$ List> keys = new ArrayList<>(settings.keySet()); + //$$ settings.clear(); + //$$ for (SettingKey key : keys) { + //$$ register(key); + //$$ } + //$$ } + //#endif public void register(Class settingsClass) { for (Field field : settingsClass.getDeclaredFields()) { @@ -40,23 +77,30 @@ public class SettingsRegistry { } public void register(SettingKey key) { - Object value; - if (configuration != null) { - if (key.getDefault() instanceof Boolean) { - value = configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).getBoolean(); - } else if (key.getDefault() instanceof Integer) { - value = configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).getInt(); - } else if (key.getDefault() instanceof Double) { - value = configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).getDouble(); - } else if (key.getDefault() instanceof String) { - value = configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).getString(); - } else { - throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported."); - } - } else { - value = NULL_OBJECT; + //#if MC>=11300 + if (spec != null) { + throw new IllegalStateException("Cannot register more settings are spec has been built."); } - settings.put(key, value); + settings.put(key, NULL_OBJECT); + //#else + //$$ Object value; + //$$ if (configuration != null) { + //$$ if (key.getDefault() instanceof Boolean) { + //$$ value = configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).getBoolean(); + //$$ } else if (key.getDefault() instanceof Integer) { + //$$ value = configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).getInt(); + //$$ } else if (key.getDefault() instanceof Double) { + //$$ value = configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).getDouble(); + //$$ } else if (key.getDefault() instanceof String) { + //$$ value = configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).getString(); + //$$ } else { + //$$ throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported."); + //$$ } + //$$ } else { + //$$ value = NULL_OBJECT; + //$$ } + //$$ settings.put(key, value); + //#endif } public Set> getSettings() { @@ -72,23 +116,35 @@ public class SettingsRegistry { } public void set(SettingKey key, T value) { - if (key.getDefault() instanceof Boolean) { - configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).set((Boolean) value); - } else if (key.getDefault() instanceof Integer) { - configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).set((Integer) value); - } else if (key.getDefault() instanceof Double) { - configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).set((Double) value); - } else if (key.getDefault() instanceof String) { - configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).set((String) value); - } else { - throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported."); + //#if MC>=11300 + if (config != null) { + config.getConfigData().set(key.getCategory() + "." + key.getKey(), value); } + //#else + //$$ if (key.getDefault() instanceof Boolean) { + //$$ configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).set((Boolean) value); + //$$ } else if (key.getDefault() instanceof Integer) { + //$$ configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).set((Integer) value); + //$$ } else if (key.getDefault() instanceof Double) { + //$$ configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).set((Double) value); + //$$ } else if (key.getDefault() instanceof String) { + //$$ configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).set((String) value); + //$$ } else { + //$$ throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported."); + //$$ } + //#endif settings.put(key, value); FML_BUS.post(new SettingsChangedEvent(this, key)); } public void save() { - // FIXME forge config api is currently broken configuration.save(); + //#if MC>=11300 + if (config != null) { + config.save(); + } + //#else + //$$ configuration.save(); + //#endif } public interface SettingKey { @@ -127,7 +183,7 @@ public class SettingsRegistry { @Override public String getDisplayString() { - return displayString == null ? null : I18n.format(displayString); + return displayString; } @Override diff --git a/src/main/java/com/replaymod/core/gui/GuiReplaySettings.java b/src/main/java/com/replaymod/core/gui/GuiReplaySettings.java index a4621a7c..921ae1f4 100644 --- a/src/main/java/com/replaymod/core/gui/GuiReplaySettings.java +++ b/src/main/java/com/replaymod/core/gui/GuiReplaySettings.java @@ -41,7 +41,7 @@ public class GuiReplaySettings extends AbstractGuiScreen { @SuppressWarnings("unchecked") final SettingsRegistry.SettingKey booleanKey = (SettingsRegistry.SettingKey) key; final GuiToggleButton button = new GuiToggleButton<>().setSize(150, 20) - .setLabel(key.getDisplayString()).setSelected(settingsRegistry.get(booleanKey) ? 0 : 1) + .setI18nLabel(key.getDisplayString()).setSelected(settingsRegistry.get(booleanKey) ? 0 : 1) .setValues(I18n.format("options.on"), I18n.format("options.off")); element = button.onClick(new Runnable() { @Override @@ -60,7 +60,7 @@ public class GuiReplaySettings extends AbstractGuiScreen { for (int j = 0; j < entries.length; j++) { Object value = values.get(j); entries[j] = new MultipleChoiceDropdownEntry(value, - multipleChoiceKey.getDisplayString() + ": " + I18n.format(value.toString())); + I18n.format(multipleChoiceKey.getDisplayString()) + ": " + I18n.format(value.toString())); if (currentValue.equals(value)) { selected = j; } diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 9fc63869..81592bc0 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -73,12 +73,25 @@ import static com.replaymod.core.versions.MCVer.getMinecraft; public class Utils { + private static InputStream getResourceAsStream(String path) { + // FIXME this seems broken in 1.13, hence the workaround. probably want to open an issue with modlauncher (or forge?) + //#ifdef DEV_ENV + try { + return new java.io.FileInputStream(new File("../src/main/resources" + path)); + } catch (java.io.FileNotFoundException e) { + throw new RuntimeException(e); + } + //#else + //$$ return Utils.class.getResourceAsStream(path); + //#endif + } + public static final BufferedImage DEFAULT_THUMBNAIL; static { BufferedImage thumbnail; try { - thumbnail = ImageIO.read(Utils.class.getClassLoader().getResourceAsStream("default_thumb.jpg")); + thumbnail = ImageIO.read(getResourceAsStream("/default_thumb.jpg")); } catch (Exception e) { thumbnail = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR); e.printStackTrace(); @@ -103,7 +116,7 @@ public class Utils { static { // Largely from https://community.letsencrypt.org/t/134/37 - try (InputStream in = Utils.class.getResourceAsStream("/dst_root_ca_x3.pem")){ + try (InputStream in = getResourceAsStream("/dst_root_ca_x3.pem")){ Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(in); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); diff --git a/src/main/java/com/replaymod/core/versions/LangResourcePack.java b/src/main/java/com/replaymod/core/versions/LangResourcePack.java index b29f9537..61038bb5 100644 --- a/src/main/java/com/replaymod/core/versions/LangResourcePack.java +++ b/src/main/java/com/replaymod/core/versions/LangResourcePack.java @@ -47,7 +47,7 @@ public class LangResourcePack extends AbstractResourcePack { } private ModFileResourcePack getParent() { - return (ModFileResourcePack) ResourcePackLoader.getResourcePackFor(ReplayMod.MOD_ID); + return ResourcePackLoader.getResourcePackFor(ReplayMod.MOD_ID).orElseThrow(() -> new RuntimeException("Failed to get ReplayMod resource pack!")); } private String langName(String path) { diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index 2035ec80..415883fc 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -1,5 +1,7 @@ package com.replaymod.recording; +import com.replaymod.core.KeyBindingRegistry; +import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Restrictions; import com.replaymod.recording.handler.ConnectionEventHandler; @@ -20,22 +22,18 @@ import com.replaymod.core.versions.MCVer.Keyboard; //#endif //#if MC>=10800 -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; //#if MC>=11300 import net.minecraftforge.fml.network.NetworkRegistry; //#else //$$ import net.minecraftforge.fml.common.network.NetworkRegistry; //#endif //#else -//$$ import cpw.mods.fml.common.event.FMLInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; //$$ import cpw.mods.fml.common.network.NetworkRegistry; //#endif import static com.replaymod.core.versions.MCVer.*; -public class ReplayModRecording extends ReplayMod.Module { +public class ReplayModRecording implements Module { private static final Logger LOGGER = LogManager.getLogger(); private static final AttributeKey ATTR_CHECKED = AttributeKey.newInstance("ReplayModRecording_checked"); @@ -49,13 +47,13 @@ public class ReplayModRecording extends ReplayMod.Module { public ReplayModRecording(ReplayMod mod) { core = mod; + + core.getSettingsRegistry().register(Setting.class); } @Override - public void preInit(FMLPreInitializationEvent event) { - core.getSettingsRegistry().register(Setting.class); - - core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.marker", Keyboard.KEY_M, new Runnable() { + public void registerKeyBindings(KeyBindingRegistry registry) { + registry.registerKeyBinding("replaymod.input.marker", Keyboard.KEY_M, new Runnable() { @Override public void run() { PacketListener packetListener = connectionEventHandler.getPacketListener(); @@ -68,7 +66,7 @@ public class ReplayModRecording extends ReplayMod.Module { } @Override - public void init(FMLInitializationEvent event) { + public void initClient() { FML_BUS.register(connectionEventHandler = new ConnectionEventHandler(LOGGER, core)); new GuiHandler(core).register(); diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index c098ce8d..f93f7572 100755 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -16,6 +16,7 @@ import org.apache.logging.log4j.Logger; //#if MC>=10800 //#if MC>=11300 +import net.minecraft.world.dimension.DimensionType; import net.minecraftforge.eventbus.api.SubscribeEvent; //#else //$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -62,7 +63,7 @@ public class ConnectionEventHandler { if (local) { //#if MC>=10800 //#if MC>=11300 - if (mc.getIntegratedServer().getWorld(0).getWorldType() == WorldType_DEBUG_ALL_BLOCK_STATES) { + if (mc.getIntegratedServer().getWorld(DimensionType.OVERWORLD).getWorldType() == WorldType_DEBUG_ALL_BLOCK_STATES) { //#else //$$ if (mc.getIntegratedServer().getEntityWorld().getWorldType() == WorldType_DEBUG_ALL_BLOCK_STATES) { //#endif diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index c5c378ca..cfa35138 100755 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -370,8 +370,17 @@ public class RecordingEventHandler { try { //#if MC>=11100 //#if MC>=11200 - packetListener.save(new SPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId(), - event.pickedUp.getItem().getMaxStackSize())); + //#if MC>=11300 + ItemStack stack = event.getStack(); + packetListener.save(new SPacketCollectItem( + event.getOriginalEntity().getEntityId(), + event.getPlayer().getEntityId(), + event.getStack().getCount() + )); + //#else + //$$ packetListener.save(new SPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId(), + //$$ event.pickedUp.getItem().getMaxStackSize())); + //#endif //#else //$$ packetListener.save(new SPacketCollectItem(event.pickedUp.getEntityId(), event.player.getEntityId(), //$$ event.pickedUp.getEntityItem().getMaxStackSize())); diff --git a/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java index b606ec8e..0c11e4ed 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java @@ -1,12 +1,16 @@ package com.replaymod.recording.mixin; import com.replaymod.recording.handler.RecordingEventHandler; -import net.minecraft.client.renderer.RenderGlobal; 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; +//#if MC>=11300 +//#else +//$$ import net.minecraft.client.renderer.RenderGlobal; +//#endif + //#if MC>=10904 import net.minecraft.util.math.BlockPos; //#else diff --git a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java index e885d5f9..39137b2d 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java @@ -20,6 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; //#if MC>=11300 import net.minecraft.world.dimension.Dimension; +import net.minecraft.world.storage.WorldSavedDataStorage; //#else //$$ import net.minecraft.world.WorldProvider; //#endif @@ -31,14 +32,22 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa @Shadow private Minecraft mc; - protected MixinWorldClient(ISaveHandler saveHandlerIn, WorldInfo info, + protected MixinWorldClient(ISaveHandler saveHandlerIn, + //#if MC>=11300 + WorldSavedDataStorage mapStorage, + //#endif + WorldInfo info, //#if MC>=11300 Dimension providerIn, //#else //$$ WorldProvider providerIn, //#endif Profiler profilerIn, boolean client) { - super(saveHandlerIn, info, providerIn, profilerIn, client); + super(saveHandlerIn, + //#if MC>=11300 + mapStorage, + //#endif + info, providerIn, profilerIn, client); } private RecordingEventHandler replayModRecording_getRecordingEventHandler() { diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index d2ebf095..4dffa766 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -36,6 +36,7 @@ import org.apache.commons.io.IOUtils; //#if MC>=11300 import net.minecraft.util.ResourceLocation; +import net.minecraft.world.dimension.DimensionType; //#endif //#if MC>=11200 @@ -377,10 +378,14 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend int chunkX = entity.chunkCoordX; int chunkY = entity.chunkCoordZ; + //#if MC>=11300 + if (entity.addedToChunk && world.getChunkProvider().provideChunk(chunkX, chunkY, false, false) != null) { + //#else //#if MC>=10904 - if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) { + //$$ if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) { //#else //$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) { + //#endif //#endif //#if MC>=11300 world.getChunk(chunkX, chunkY).removeEntity(entity); @@ -566,7 +571,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend actualID = entId; entId = -1789435; // Camera entity id should be negative which is an invalid id and can't be used by servers //#if MC>=10800 - int dimension = packet.getDimension(); + //#if MC>=11300 + DimensionType dimension = packet.func_212642_e(); + //#else + //$$ int dimension = packet.getDimension(); + //#endif EnumDifficulty difficulty = packet.getDifficulty(); //#if MC>=11300 int maxPlayers = 0;// FIXME needs AT packet.maxPlayers; @@ -596,7 +605,12 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#if MC>=10904 if(p instanceof SPacketRespawn) { SPacketRespawn respawn = (SPacketRespawn) p; - p = new SPacketRespawn(respawn.getDimensionID(), + p = new SPacketRespawn( + //#if MC>=11300 + respawn.func_212643_b(), + //#else + //$$ respawn.getDimensionID(), + //#endif respawn.getDifficulty(), respawn.getWorldType(), GameType.SPECTATOR); //#else //$$ if(p instanceof S07PacketRespawn) { @@ -1083,7 +1097,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend World world = world(mc); IChunkProvider chunkProvider = world.getChunkProvider(); // Get the chunk that will be unloaded - Chunk chunk = chunkProvider.provideChunk(x, z); + //#if MC>=11300 + Chunk chunk = chunkProvider.provideChunk(x, z, false, false); + //#else + //$$ Chunk chunk = chunkProvider.provideChunk(x, z); + //#endif if (!chunk.isEmpty()) { List entitiesInChunk = new ArrayList<>(); // Gather all entities in that chunk @@ -1109,12 +1127,16 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if (entity.chunkCoordX != chunkX || entity.chunkCoordZ != chunkZ) { // Entity has left the chunk chunk.removeEntityAtIndex(entity, entity.chunkCoordY); + //#if MC>=11300 + Chunk newChunk = chunkProvider.provideChunk(chunkX, chunkZ, false, false); + //#else //#if MC>=10904 - Chunk newChunk = chunkProvider.getLoadedChunk(chunkX, chunkZ); + //$$ Chunk newChunk = chunkProvider.getLoadedChunk(chunkX, chunkZ); //#else //$$ Chunk newChunk = chunkProvider.chunkExists(chunkX, chunkZ) //$$ ? chunkProvider.provideChunk(chunkX, chunkZ) : null; //#endif + //#endif if (newChunk != null) { newChunk.addEntity(entity); } else { diff --git a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java index 5aec7e4e..d2c38ec3 100644 --- a/src/main/java/com/replaymod/replay/NoGuiScreenshot.java +++ b/src/main/java/com/replaymod/replay/NoGuiScreenshot.java @@ -54,11 +54,15 @@ public class NoGuiScreenshot { mc.getFramebuffer().bindFramebuffer(true); GlStateManager.enableTexture2D(); + //#if MC>=11300 + mc.entityRenderer.renderWorld(mc.timer.renderPartialTicks, System.nanoTime()); + //#else //#if MC>=10809 - mc.entityRenderer.updateCameraAndRender(mc.timer.renderPartialTicks, System.nanoTime()); + //$$ mc.entityRenderer.updateCameraAndRender(mc.timer.renderPartialTicks, System.nanoTime()); //#else //$$ mc.entityRenderer.updateCameraAndRender(mc.timer.renderPartialTicks); //#endif + //#endif mc.getFramebuffer().unbindFramebuffer(); GlStateManager.popMatrix(); @@ -79,7 +83,12 @@ public class NoGuiScreenshot { // disk for better maintainability File tmpFolder = Files.createTempDir(); try { - ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer()); + //#if MC>=11300 + ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer(), (msg) -> + mc.addScheduledTask(() -> mc.ingameGUI.getChatGUI().printChatMessage(msg))); + //#else + //$$ ScreenShotHelper.saveScreenshot(tmpFolder, "tmp", frameWidth, frameHeight, mc.getFramebuffer()); + //#endif File screenshotFile = new File(tmpFolder, "screenshots/tmp"); BufferedImage image = ImageIO.read(screenshotFile); int imageWidth = image.getWidth(); diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index b1f087ba..0fb53214 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -4,6 +4,8 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.replaymod.core.KeyBindingRegistry; +import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.ModCompat; import com.replaymod.core.versions.MCVer; @@ -29,23 +31,12 @@ import com.replaymod.core.versions.MCVer.Keyboard; //$$ import org.lwjgl.input.Keyboard; //#endif -//#if MC>=10800 -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -//#else -//$$ import cpw.mods.fml.common.event.FMLInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPostInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; -//#endif - import javax.annotation.Nullable; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.Optional; -public class ReplayModReplay extends ReplayMod.Module { +public class ReplayModReplay implements Module { { instance = this; } public static ReplayModReplay instance; @@ -64,13 +55,13 @@ public class ReplayModReplay extends ReplayMod.Module { public ReplayModReplay(ReplayMod core) { this.core = core; + + core.getSettingsRegistry().register(Setting.class); } @Override - public void preInit(FMLPreInitializationEvent event) { - core.getSettingsRegistry().register(Setting.class); - - core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.marker", MCVer.Keyboard.KEY_M, new Runnable() { + public void registerKeyBindings(KeyBindingRegistry registry) { + registry.registerKeyBinding("replaymod.input.marker", MCVer.Keyboard.KEY_M, new Runnable() { @Override public void run() { if (replayHandler != null ) { @@ -91,7 +82,7 @@ public class ReplayModReplay extends ReplayMod.Module { } }); - core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> { + registry.registerRaw(Keyboard.KEY_DELETE, () -> { if (replayHandler != null) { GuiMarkerTimeline timeline = replayHandler.getOverlay().timeline; if (timeline.getSelectedMarker() != null) { @@ -102,7 +93,7 @@ public class ReplayModReplay extends ReplayMod.Module { } }); - core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.thumbnail", Keyboard.KEY_N, new Runnable() { + registry.registerKeyBinding("replaymod.input.thumbnail", Keyboard.KEY_N, new Runnable() { @Override public void run() { if (replayHandler != null) { @@ -130,7 +121,7 @@ public class ReplayModReplay extends ReplayMod.Module { } }); - core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.playpause", Keyboard.KEY_P, new Runnable() { + registry.registerKeyBinding("replaymod.input.playpause", Keyboard.KEY_P, new Runnable() { @Override public void run() { if (replayHandler != null) { @@ -139,7 +130,7 @@ public class ReplayModReplay extends ReplayMod.Module { } }); - core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.quickmode", Keyboard.KEY_Q, () -> { + registry.registerKeyBinding("replaymod.input.quickmode", Keyboard.KEY_Q, () -> { if (replayHandler != null) { replayHandler.getReplaySender().setSyncModeAndWait(); core.runLater(() -> @@ -161,7 +152,10 @@ public class ReplayModReplay extends ReplayMod.Module { core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.resettilt", Keyboard.KEY_K, () -> { Optional.ofNullable(replayHandler).map(ReplayHandler::getCameraEntity).ifPresent(c -> c.roll = 0); }); + } + @Override + public void initClient() { cameraControllerRegistry.register("replaymod.camera.classic", new Function() { @Nullable @Override @@ -176,21 +170,13 @@ public class ReplayModReplay extends ReplayMod.Module { return new VanillaCameraController(core.getMinecraft(), cameraEntity); } }); - } - @Override - public void init(FMLInitializationEvent event) { Minecraft mc = core.getMinecraft(); // FIXME mc.timer = new InputReplayTimer(mc.timer, this); new GuiHandler(this).register(); } - @Override - public void postInit(FMLPostInitializationEvent event) { - Setting.CAMERA.setChoices(new ArrayList<>(cameraControllerRegistry.getControllers())); - } - public void startReplay(File file) throws IOException { startReplay(new ZipReplayFile(new ReplayStudio(), file)); } diff --git a/src/main/java/com/replaymod/replay/camera/CameraControllerRegistry.java b/src/main/java/com/replaymod/replay/camera/CameraControllerRegistry.java index 1a3a31df..fcb937f3 100644 --- a/src/main/java/com/replaymod/replay/camera/CameraControllerRegistry.java +++ b/src/main/java/com/replaymod/replay/camera/CameraControllerRegistry.java @@ -2,7 +2,9 @@ package com.replaymod.replay.camera; import com.google.common.base.Function; import com.google.common.base.Preconditions; +import com.replaymod.replay.Setting; +import java.util.ArrayList; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -14,6 +16,7 @@ public class CameraControllerRegistry { public void register(String name, Function constructor) { Preconditions.checkState(!constructors.containsKey(name), "Controller " + name + " is already registered."); constructors.put(name, constructor); + Setting.CAMERA.setChoices(new ArrayList<>(getControllers())); } public Set getControllers() { diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 822397a1..d439e932 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -17,12 +17,12 @@ A Mod which allows you to record, replay and share your Minecraft experience. [[dependencies.replaymod]] modId="forge" mandatory=true - versionRange="[14.23.2.0,)" + versionRange="[15.0,)" ordering="NONE" side="BOTH" [[dependencies.replaymod]] modId="minecraft" mandatory=true - versionRange="[ 1.13 ]" + versionRange="[ 1.13.2 ]" ordering="NONE" side="BOTH" \ No newline at end of file diff --git a/versions/common.gradle b/versions/common.gradle index 9761eb10..65dd4254 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -79,9 +79,8 @@ minecraft { if (FG3) { runs { client { - workingDirectory project.file('eclipse') - property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' - property 'forge.logging.console.level', 'debug' + workingDirectory rootProject.file('eclipse') + property 'forge.logging.console.level', 'info' mods { replaymod { source sourceSets.main From 641014b0d1a76d499c6daabd720f84fcc94baca0 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 1 Mar 2019 11:55:00 +0100 Subject: [PATCH 37/65] Get pathing module compiling --- .../replaymod/core/utils/WrappedTimer.java | 2 +- .../pathing/gui/GuiKeyframeRepository.java | 4 ++-- .../player/AbstractTimelinePlayer.java | 8 +++++-- .../replaymod/pathing/player/ReplayTimer.java | 24 +++++++++++++++---- versions/common.gradle | 1 - 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/replaymod/core/utils/WrappedTimer.java b/src/main/java/com/replaymod/core/utils/WrappedTimer.java index 9d664035..929f0097 100644 --- a/src/main/java/com/replaymod/core/utils/WrappedTimer.java +++ b/src/main/java/com/replaymod/core/utils/WrappedTimer.java @@ -38,7 +38,7 @@ public class WrappedTimer extends Timer { to.lastSyncSysClock = from.lastSyncSysClock; to.elapsedPartialTicks = from.elapsedPartialTicks; //#if MC>=11200 - // FIXME (should be good to go once AT are applied) to.tickLength = from.tickLength; + to.tickLength = from.tickLength; //#else //$$ to.ticksPerSecond = from.ticksPerSecond; //$$ to.lastHRTime = from.lastHRTime; diff --git a/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java b/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java index e794e272..c6c761a4 100644 --- a/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java +++ b/src/main/java/com/replaymod/pathing/gui/GuiKeyframeRepository.java @@ -28,8 +28,8 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import java.io.IOException; import java.util.LinkedHashMap; diff --git a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java index 4d586132..98a8a92f 100644 --- a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java +++ b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java @@ -14,7 +14,11 @@ import com.replaymod.replaystudio.pathing.path.Timeline; import net.minecraft.client.Minecraft; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -28,7 +32,7 @@ import static com.replaymod.core.versions.MCVer.*; * Plays a timeline. */ public abstract class AbstractTimelinePlayer { - private final Minecraft mc = Minecraft.getMinecraft(); + private final Minecraft mc = getMinecraft(); private final ReplayHandler replayHandler; private Timeline timeline; protected long startOffset; diff --git a/src/main/java/com/replaymod/pathing/player/ReplayTimer.java b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java index a2bdc331..597fe805 100644 --- a/src/main/java/com/replaymod/pathing/player/ReplayTimer.java +++ b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java @@ -4,7 +4,11 @@ import com.replaymod.core.utils.WrappedTimer; import net.minecraft.util.Timer; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.Event; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.Event; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.Event; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.Event; //#endif @@ -15,16 +19,28 @@ import static com.replaymod.core.versions.MCVer.*; * Wrapper around the current timer that prevents the timer from advancing by itself. */ public class ReplayTimer extends WrappedTimer { - private final Timer state = new Timer(0); + //#if MC>=11300 + private final Timer state = new Timer(0, 0); + //#else + //$$ private final Timer state = new Timer(0); + //#endif public ReplayTimer(Timer wrapped) { super(wrapped); } @Override - public void updateTimer() { + public void updateTimer( + //#if MC>=11300 + long sysClock + //#endif + ) { copy(this, state); // Save our current state - super.updateTimer(); // Update current state + wrapped.updateTimer( + //#if MC>=11300 + sysClock + //#endif + ); // Update current state copy(state, this); // Restore our old state FML_BUS.post(new UpdatedEvent()); } diff --git a/versions/common.gradle b/versions/common.gradle index 65dd4254..f5756395 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -277,7 +277,6 @@ sourceSets { exclude 'com/replaymod/editor' exclude 'com/replaymod/extras' exclude 'com/replaymod/online' - exclude 'com/replaymod/pathing' exclude 'com/replaymod/render' exclude 'com/replaymod/restrictions' exclude 'com/replaymod/simplepathing' From 344a550dc13873c26c928951b940f077436a265f Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 1 Mar 2019 15:16:59 +0100 Subject: [PATCH 38/65] Get render module compiling on 1.13 --- .../java/com/replaymod/core/ReplayMod.java | 2 + .../com/replaymod/core/versions/MCVer.java | 18 +- .../com/replaymod/render/RenderSettings.java | 12 +- .../com/replaymod/render/ReplayModRender.java | 54 ++--- .../com/replaymod/render/VideoWriter.java | 12 +- .../render/capturer/ODSFrameCapturer.java | 8 +- .../render/capturer/OpenGlFrameCapturer.java | 27 ++- .../replaymod/render/capturer/RenderInfo.java | 2 +- .../SimplePboOpenGlFrameCapturer.java | 2 +- .../render/events/ReplayRenderEvent.java | 6 +- .../replaymod/render/frame/OpenGlFrame.java | 2 +- .../com/replaymod/render/frame/RGBFrame.java | 2 +- .../replaymod/render/gui/GuiExportFailed.java | 9 +- .../replaymod/render/gui/GuiRenderQueue.java | 4 +- .../render/gui/GuiRenderSettings.java | 38 +-- .../render/gui/GuiRenderingDone.java | 31 +-- .../render/gui/GuiVideoRenderer.java | 36 ++- .../hooks/ChunkLoadingRenderGlobal.java | 62 ++++- .../render/hooks/EntityRendererHandler.java | 41 +++- .../render/metadata/MetadataInjector.java | 2 +- .../render/mixin/MixinEntityRenderer.java | 147 ++++++++---- .../replaymod/render/mixin/MixinFrustum.java | 4 +- .../render/mixin/MixinParticleManager.java | 4 +- .../replaymod/render/mixin/MixinRender.java | 4 +- .../render/mixin/MixinRenderGlobal.java | 14 +- .../render/mixin/MixinRenderManager.java | 4 +- .../render/processor/CubicToRGBProcessor.java | 2 +- .../EquirectangularToRGBProcessor.java | 2 +- .../render/processor/ODSToRGBProcessor.java | 4 +- .../processor/OpenGlToRGBProcessor.java | 2 +- .../processor/StereoscopicToRGBProcessor.java | 4 +- .../replaymod/render/rendering/Pipeline.java | 23 +- .../render/rendering/VideoRenderer.java | 220 +++++++++++++----- .../com/replaymod/render/shader/Program.java | 11 +- .../render/utils/PixelBufferObject.java | 112 ++++++--- .../replaymod/render/utils/SoundHandler.java | 4 +- src/main/resources/META-INF/replaymod_at.cfg | 27 ++- versions/common.gradle | 1 - 38 files changed, 632 insertions(+), 327 deletions(-) diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index ed840ffa..fbe56f50 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -8,6 +8,7 @@ import com.replaymod.core.handler.MainMenuHandler; import com.replaymod.core.utils.OpenGLUtils; import com.replaymod.core.versions.MCVer; import com.replaymod.recording.ReplayModRecording; +import com.replaymod.render.ReplayModRender; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replaystudio.util.I18n; import de.johni0702.minecraft.gui.container.GuiScreen; @@ -163,6 +164,7 @@ public class ReplayMod implements Module { modules.add(this); modules.add(new ReplayModRecording(this)); modules.add(new ReplayModReplay(this)); + modules.add(new ReplayModRender(this)); //#if MC>=11300 settingsRegistry.register(); diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index 8d620079..6213cac0 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -345,8 +345,7 @@ public class MCVer { return world.playerEntities; } - /* FIXME - public static BooleanState fog() { + public static GlStateManager.BooleanState fog() { //#if MC>=11300 return GlStateManager.FOG.fog; //#else @@ -362,7 +361,7 @@ public class MCVer { //#endif } - public static void fog(BooleanState fog) { + public static void fog(GlStateManager.BooleanState fog) { //#if MC>=11300 GlStateManager.FOG.fog = fog; //#else @@ -378,7 +377,7 @@ public class MCVer { //#endif } - public static BooleanState texture2DState(int index) { + public static GlStateManager.BooleanState texture2DState(int index) { //#if MC>=11300 return GlStateManager.TEXTURES[index].texture2DState; //#else @@ -390,7 +389,7 @@ public class MCVer { //#endif } - public static void texture2DState(int index, BooleanState texture2DState) { + public static void texture2DState(int index, GlStateManager.BooleanState texture2DState) { //#if MC>=11300 GlStateManager.TEXTURES[index].texture2DState = texture2DState; //#else @@ -401,7 +400,6 @@ public class MCVer { //#endif //#endif } - */ public static void ServerList_saveSingleServer(ServerData serverData) { //#if MC>=10904 @@ -612,6 +610,14 @@ public class MCVer { //#endif } + public static long milliTime() { + //#if MC>=11300 + return Util.milliTime(); + //#else + //$$ return Minecraft.getSystemTime(); + //#endif + } + public static File Minecraft_mcDataDir(Minecraft mc) { //#if MC>=11300 return mc.gameDir; diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index 2d158800..917fc9bb 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -1,12 +1,12 @@ package com.replaymod.render; +import com.replaymod.core.versions.MCVer; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; -import net.minecraft.client.Minecraft; import net.minecraft.client.resources.I18n; import net.minecraft.util.Util; -import org.lwjgl.util.ReadableColor; import java.io.File; import java.util.Arrays; @@ -15,11 +15,15 @@ import java.util.Optional; import static com.replaymod.render.ReplayModRender.LOGGER; +//#if MC>=11300 +import org.apache.maven.artifact.versioning.ComparableVersion; +//#else //#if MC>=10800 -import net.minecraftforge.fml.common.versioning.ComparableVersion; +//$$ import net.minecraftforge.fml.common.versioning.ComparableVersion; //#else //$$ import cpw.mods.fml.common.versioning.ComparableVersion; //#endif +//#endif @Data public class RenderSettings { @@ -170,7 +174,7 @@ public class RenderSettings { switch (Util.getOSType()) { case WINDOWS: // Allow windows users to unpack the ffmpeg archive into a sub-folder of their .minecraft folder - File inDotMinecraft = new File(Minecraft.getMinecraft().mcDataDir, "ffmpeg/bin/ffmpeg.exe"); + File inDotMinecraft = new File(MCVer.mcDataDir(MCVer.getMinecraft()), "ffmpeg/bin/ffmpeg.exe"); if (inDotMinecraft.exists()) { LOGGER.debug("FFmpeg found in .minecraft/ffmpeg"); return inDotMinecraft.getAbsolutePath(); diff --git a/src/main/java/com/replaymod/render/ReplayModRender.java b/src/main/java/com/replaymod/render/ReplayModRender.java index 1f9cfcda..15cf3414 100644 --- a/src/main/java/com/replaymod/render/ReplayModRender.java +++ b/src/main/java/com/replaymod/render/ReplayModRender.java @@ -1,65 +1,57 @@ package com.replaymod.render; +import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.render.utils.RenderJob; import com.replaymod.replay.events.ReplayCloseEvent; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; -import net.minecraftforge.common.config.Configuration; import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; //#if MC>=10800 import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.Mod; -//$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import static com.replaymod.core.versions.MCVer.*; -@Mod(modid = ReplayModRender.MOD_ID, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", - //#if MC>=10800 - clientSideOnly = true, - //#endif - useMetadata = true) -public class ReplayModRender { - public static final String MOD_ID = "replaymod-render"; - - @Mod.Instance(MOD_ID) +public class ReplayModRender implements Module { + { instance = this; } public static ReplayModRender instance; private ReplayMod core; - public static Logger LOGGER; + public static Logger LOGGER = LogManager.getLogger(); - private Configuration configuration; private final List renderQueue = new ArrayList<>(); + public ReplayModRender(ReplayMod core) { + this.core = core; + + core.getSettingsRegistry().register(Setting.class); + } + public ReplayMod getCore() { return core; } - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - LOGGER = event.getModLog(); - core = ReplayMod.instance; - configuration = new Configuration(event.getSuggestedConfigurationFile()); - + @Override + public void initClient() { FML_BUS.register(this); - - core.getSettingsRegistry().register(Setting.class); } @SubscribeEvent @@ -69,17 +61,17 @@ public class ReplayModRender { public File getVideoFolder() { String path = core.getSettingsRegistry().get(Setting.RENDER_PATH); - File folder = new File(path.startsWith("./") ? core.getMinecraft().mcDataDir : null, path); + File folder = new File(path.startsWith("./") ? mcDataDir(core.getMinecraft()) : null, path); try { FileUtils.forceMkdir(folder); } catch (IOException e) { - throw new ReportedException(CrashReport.makeCrashReport(e, "Cannot create video folder.")); + throw newReportedException(CrashReport.makeCrashReport(e, "Cannot create video folder.")); } return folder; } - public Configuration getConfiguration() { - return configuration; + public Path getRenderSettingsPath() { + return mcDataDir(core.getMinecraft()).toPath().resolve("config/replaymod-rendersettings.json"); } public List getRenderQueue() { diff --git a/src/main/java/com/replaymod/render/VideoWriter.java b/src/main/java/com/replaymod/render/VideoWriter.java index 792814f4..e90cedcb 100755 --- a/src/main/java/com/replaymod/render/VideoWriter.java +++ b/src/main/java/com/replaymod/render/VideoWriter.java @@ -1,18 +1,18 @@ package com.replaymod.render; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.rendering.FrameConsumer; import com.replaymod.render.rendering.VideoRenderer; import com.replaymod.render.utils.ByteBufferPool; import com.replaymod.render.utils.StreamPipe; -import net.minecraft.client.Minecraft; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import org.apache.commons.exec.CommandLine; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.TeeOutputStream; -import org.lwjgl.util.ReadableDimension; import java.io.ByteArrayOutputStream; import java.io.File; @@ -68,7 +68,7 @@ public class VideoWriter implements FrameConsumer { } catch (IOException e) { throw new NoFFmpegException(e); } - File exportLogFile = new File(Minecraft.getMinecraft().mcDataDir, "export.log"); + File exportLogFile = new File(MCVer.mcDataDir(MCVer.getMinecraft()), "export.log"); OutputStream exportLogOut = new TeeOutputStream(new FileOutputStream(exportLogFile), ffmpegLog); new StreamPipe(process.getInputStream(), exportLogOut).start(); new StreamPipe(process.getErrorStream(), exportLogOut).start(); @@ -124,9 +124,9 @@ public class VideoWriter implements FrameConsumer { } CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame"); CrashReportCategory exportDetails = report.makeCategory("Export details"); - exportDetails.addCrashSection("Export command", settings.getExportCommand()); - exportDetails.addCrashSection("Export args", commandArgs); - Minecraft.getMinecraft().crashed(report); + MCVer.addDetail(exportDetails, "Export command", settings::getExportCommand); + MCVer.addDetail(exportDetails, "Export args", commandArgs::toString); + MCVer.getMinecraft().crashed(report); } } diff --git a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java index 465575e8..e3c6d2bf 100644 --- a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java @@ -1,16 +1,16 @@ package com.replaymod.render.capturer; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.ODSOpenGlFrame; import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.rendering.FrameCapturer; import com.replaymod.render.shader.Program; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; import net.minecraft.util.ResourceLocation; -import org.lwjgl.util.ReadableDimension; //#if MC>=10800 import net.minecraft.client.renderer.GlStateManager; @@ -38,7 +38,7 @@ public class ODSFrameCapturer implements FrameCapturer { private final BooleanState[] previousStates = new BooleanState[3]; private final BooleanState previousFogState; - private final Minecraft mc = Minecraft.getMinecraft(); + private final Minecraft mc = MCVer.getMinecraft(); public ODSFrameCapturer(WorldRenderer worldRenderer, final RenderInfo renderInfo, int frameSize) { RenderInfo fakeInfo = new RenderInfo() { @@ -98,7 +98,7 @@ public class ODSFrameCapturer implements FrameCapturer { }); shaderProgram.stopUsing(); } catch (Exception e) { - throw new ReportedException(CrashReport.makeCrashReport(e, "Creating ODS shaders")); + throw newReportedException(CrashReport.makeCrashReport(e, "Creating ODS shaders")); } } diff --git a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java index 6654b43f..0f7ab0de 100644 --- a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java @@ -1,16 +1,17 @@ package com.replaymod.render.capturer; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.rendering.Frame; import com.replaymod.render.rendering.FrameCapturer; import com.replaymod.render.utils.ByteBufferPool; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.WritableDimension; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.shader.Framebuffer; import org.lwjgl.opengl.GL11; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; -import org.lwjgl.util.WritableDimension; import java.io.IOException; import java.nio.ByteBuffer; @@ -30,7 +31,7 @@ public abstract class OpenGlFrameCapturer=11300 + Framebuffer fb = mc.getFramebuffer(); + if (fb.framebufferWidth != width || fb.framebufferHeight != height) { + fb.createFramebuffer(width, height); } - } - - private void setWindowSize(int width, int height) { - mc.resize(width, height); + //#else + //$$ if (width != mc.displayWidth || height != mc.displayHeight) { + //$$ setWindowSize(width, height); + //$$ mc.resize(width, height); + //$$ } + //#endif } @Override diff --git a/src/main/java/com/replaymod/render/capturer/RenderInfo.java b/src/main/java/com/replaymod/render/capturer/RenderInfo.java index 72f7596e..826214c3 100644 --- a/src/main/java/com/replaymod/render/capturer/RenderInfo.java +++ b/src/main/java/com/replaymod/render/capturer/RenderInfo.java @@ -1,7 +1,7 @@ package com.replaymod.render.capturer; import com.replaymod.render.RenderSettings; -import org.lwjgl.util.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; public interface RenderInfo { ReadableDimension getFrameSize(); diff --git a/src/main/java/com/replaymod/render/capturer/SimplePboOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/SimplePboOpenGlFrameCapturer.java index 13492562..4914d5a2 100644 --- a/src/main/java/com/replaymod/render/capturer/SimplePboOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/SimplePboOpenGlFrameCapturer.java @@ -3,9 +3,9 @@ package com.replaymod.render.capturer; import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.utils.ByteBufferPool; import com.replaymod.render.utils.PixelBufferObject; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.renderer.OpenGlHelper; import org.lwjgl.opengl.GL11; -import org.lwjgl.util.ReadableDimension; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/events/ReplayRenderEvent.java b/src/main/java/com/replaymod/render/events/ReplayRenderEvent.java index a6ff19b9..4bb94b80 100644 --- a/src/main/java/com/replaymod/render/events/ReplayRenderEvent.java +++ b/src/main/java/com/replaymod/render/events/ReplayRenderEvent.java @@ -5,7 +5,11 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.Event; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.Event; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.Event; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.Event; //#endif diff --git a/src/main/java/com/replaymod/render/frame/OpenGlFrame.java b/src/main/java/com/replaymod/render/frame/OpenGlFrame.java index 1d595aab..82d40770 100644 --- a/src/main/java/com/replaymod/render/frame/OpenGlFrame.java +++ b/src/main/java/com/replaymod/render/frame/OpenGlFrame.java @@ -1,9 +1,9 @@ package com.replaymod.render.frame; import com.replaymod.render.rendering.Frame; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.lwjgl.util.ReadableDimension; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/frame/RGBFrame.java b/src/main/java/com/replaymod/render/frame/RGBFrame.java index 8a1f5a8c..57a9904e 100644 --- a/src/main/java/com/replaymod/render/frame/RGBFrame.java +++ b/src/main/java/com/replaymod/render/frame/RGBFrame.java @@ -1,9 +1,9 @@ package com.replaymod.render.frame; import com.replaymod.render.rendering.Frame; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import lombok.Getter; import org.apache.commons.lang3.Validate; -import org.lwjgl.util.ReadableDimension; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java index 930851ca..d9eb4e1c 100644 --- a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java +++ b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java @@ -13,11 +13,12 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; -import net.minecraft.util.ReportedException; import java.util.Arrays; import java.util.function.Consumer; +import static com.replaymod.core.versions.MCVer.addDetail; +import static com.replaymod.core.versions.MCVer.newReportedException; import static com.replaymod.render.ReplayModRender.LOGGER; public class GuiExportFailed extends GuiScreen { @@ -31,9 +32,9 @@ public class GuiExportFailed extends GuiScreen { // If they haven't, then this is probably a faulty ffmpeg installation and there's nothing we can do CrashReport crashReport = CrashReport.makeCrashReport(e, "Exporting video"); CrashReportCategory details = crashReport.makeCategory("Export details"); - details.addCrashSection("Settings", settings); - details.addCrashSection("FFmpeg log", e.getLog()); - throw new ReportedException(crashReport); + addDetail(details, "Settings", settings::toString); + addDetail(details, "FFmpeg log", e::getLog); + throw newReportedException(crashReport); } else { // If they have, ask them whether it was intentional GuiExportFailed gui = new GuiExportFailed(e, doRestart); diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java index 7801479f..ef3cf4e3 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java @@ -27,10 +27,10 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.crash.CrashReport; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; import java.util.List; diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 76593189..792b4262 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -28,18 +28,21 @@ import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; 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.ReadableColor; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.common.config.Property; -import org.lwjgl.util.Color; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableColor; -import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; @@ -294,7 +297,14 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { this.replayHandler = replayHandler; this.timeline = timeline; - String json = getConfigProperty(ReplayModRender.instance.getConfiguration()).getString(); + Path path = ReplayModRender.instance.getRenderSettingsPath(); + String json = "{}"; + try { + json = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + } catch (FileNotFoundException ignored) { + } catch (IOException e) { + LOGGER.error("Reading render settings:", e); + } RenderSettings settings = new GsonBuilder() .registerTypeAdapter(RenderSettings.class, (InstanceCreator) type -> getDefaultRenderSettings()) .registerTypeAdapter(ReadableColor.class, new Gson().getAdapter(Color.class)) @@ -506,14 +516,12 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { public void close() { RenderSettings settings = save(true); String json = new Gson().toJson(settings); - Configuration config = ReplayModRender.instance.getConfiguration(); - getConfigProperty(config).set(json); - config.save(); - } - - protected Property getConfigProperty(Configuration configuration) { - return configuration.get("rendersettings", "settings", "{}", - "Last state of the render settings GUI. Internal use only."); + Path path = ReplayModRender.instance.getRenderSettingsPath(); + try { + Files.write(path, json.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + LOGGER.error("Saving render settings:", e); + } } public ReplayHandler getReplayHandler() { diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderingDone.java b/src/main/java/com/replaymod/render/gui/GuiRenderingDone.java index 659f145e..6e534d4d 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderingDone.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderingDone.java @@ -1,6 +1,7 @@ package com.replaymod.render.gui; import com.replaymod.core.SettingsRegistry; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.ReplayModRender; import com.replaymod.render.Setting; @@ -13,13 +14,8 @@ import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import lombok.RequiredArgsConstructor; -import net.minecraft.util.Util; -import org.apache.logging.log4j.LogManager; -import org.lwjgl.Sys; -import java.awt.*; import java.io.File; -import java.io.IOException; @RequiredArgsConstructor public class GuiRenderingDone extends GuiScreen { @@ -34,30 +30,7 @@ public class GuiRenderingDone extends GuiScreen { public final GuiButton openFolder = new GuiButton().onClick(new Runnable() { @Override public void run() { - File folder = videoFile.getParentFile(); - String path = folder.getAbsolutePath(); - - // First try OS specific methods - try { - switch (Util.getOSType()) { - case WINDOWS: - Runtime.getRuntime().exec(String.format("cmd.exe /C start \"Open file\" \"%s\"", path)); - return; - case OSX: - Runtime.getRuntime().exec(new String[]{"/usr/bin/open", path}); - return; - } - } catch (IOException e) { - LogManager.getLogger().error("Cannot open file", e); - } - - // Otherwise try the java way - try { - Desktop.getDesktop().browse(folder.toURI()); - } catch (Throwable throwable) { - // And if all fails, lwjgl - Sys.openURL("file://" + path); - } + MCVer.openFile(videoFile.getParentFile()); } }).setSize(200, 20).setI18nLabel("replaymod.gui.openfolder"); diff --git a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java index d8c00b4c..c9aa63ce 100644 --- a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java +++ b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java @@ -14,12 +14,13 @@ import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar; import de.johni0702.minecraft.gui.function.Tickable; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; +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.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.texture.NativeImage; import net.minecraft.client.resources.I18n; import net.minecraft.util.ResourceLocation; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; -import org.lwjgl.util.ReadablePoint; import java.nio.ByteBuffer; @@ -228,7 +229,11 @@ public class GuiVideoRenderer extends GuiScreen implements Tickable { final int videoHeight = videoSize.getHeight(); if (previewTexture == null) { - previewTexture = new DynamicTexture(videoWidth, videoHeight); + //#if MC>=11300 + previewTexture = new DynamicTexture(videoWidth, videoHeight, true); + //#else + //$$ previewTexture = new DynamicTexture(videoWidth, videoHeight); + //#endif } if (previewTextureDirty) { @@ -262,13 +267,24 @@ public class GuiVideoRenderer extends GuiScreen implements Tickable { ByteBuffer buffer = frame.getByteBuffer(); buffer.mark(); synchronized (this) { - int[] data = previewTexture.getTextureData(); - // Optifine changes the texture data array to be three times as long (for use by shaders), - // we only want to initialize the first third which is why we use the length of the buffer instead - // of the length of the data array - for (int i = 0; buffer.remaining() > 0; i++) { - data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); + //#if MC>=11300 + NativeImage data = previewTexture.getTextureData(); + assert data != null; + for (int x = 0; x < data.getWidth(); x++) { + for (int y = 0; y < data.getHeight(); y++) { + int value = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); + data.setPixelRGBA(x, y, value); + } } + //#else + //$$ int[] data = previewTexture.getTextureData(); + //$$ // Optifine changes the texture data array to be three times as long (for use by shaders), + //$$ // we only want to initialize the first third which is why we use the length of the buffer instead + //$$ // of the length of the data array + //$$ for (int i = 0; buffer.remaining() > 0; i++) { + //$$ data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); + //$$ } + //#endif previewTextureDirty = true; } buffer.reset(); diff --git a/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java b/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java index 8fe6c2b9..91996a9b 100644 --- a/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java +++ b/src/main/java/com/replaymod/render/hooks/ChunkLoadingRenderGlobal.java @@ -3,8 +3,6 @@ package com.replaymod.render.hooks; import com.replaymod.render.utils.JailingQueue; import net.minecraft.client.renderer.RegionRenderCacheBuilder; -import net.minecraft.client.renderer.RenderGlobal; -import net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator; import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher; import net.minecraft.client.renderer.chunk.ChunkRenderWorker; import net.minecraft.client.renderer.chunk.RenderChunk; @@ -12,6 +10,14 @@ import net.minecraft.client.renderer.chunk.RenderChunk; import java.lang.reflect.Field; import java.util.Iterator; +//#if MC>=11300 +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.chunk.ChunkRenderTask; +//#else +//$$ import net.minecraft.client.renderer.RenderGlobal; +//$$ import net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator; +//#endif + //#if MC>=10904 import java.util.concurrent.PriorityBlockingQueue; //#else @@ -22,14 +28,28 @@ import static com.replaymod.core.versions.MCVer.*; public class ChunkLoadingRenderGlobal { - private final RenderGlobal hooked; + //#if MC>=11300 + private final WorldRenderer hooked; + //#else + //$$ private final RenderGlobal hooked; + //#endif private ChunkRenderDispatcher renderDispatcher; - private JailingQueue workerJailingQueue; + //#if MC>=11300 + private JailingQueue workerJailingQueue; + //#else + //$$ private JailingQueue workerJailingQueue; + //#endif private CustomChunkRenderWorker renderWorker; private int frame; @SuppressWarnings("unchecked") - public ChunkLoadingRenderGlobal(RenderGlobal renderGlobal) { + public ChunkLoadingRenderGlobal( + //#if MC>=11300 + WorldRenderer renderGlobal + //#else + //$$ RenderGlobal renderGlobal + //#endif + ) { this.hooked = renderGlobal; setup(renderGlobal.renderDispatcher); @@ -51,14 +71,22 @@ public class ChunkLoadingRenderGlobal { int workerThreads = renderDispatcher.listThreadedWorkers.size(); //#if MC>=10904 - PriorityBlockingQueue queueChunkUpdates = renderDispatcher.queueChunkUpdates; + //#if MC>=11300 + PriorityBlockingQueue queueChunkUpdates = renderDispatcher.queueChunkUpdates; + //#else + //$$ PriorityBlockingQueue queueChunkUpdates = renderDispatcher.queueChunkUpdates; + //#endif //#else //$$ BlockingQueue queueChunkUpdates = renderDispatcher.queueChunkUpdates; //#endif workerJailingQueue = new JailingQueue<>(queueChunkUpdates); renderDispatcher.queueChunkUpdates = workerJailingQueue; //#if MC>=10904 - ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null, 0); + //#if MC>=11300 + ChunkRenderTask element = new ChunkRenderTask(null, null, 0); + //#else + //$$ ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null, 0); + //#endif //#else //$$ ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null); //#endif @@ -75,7 +103,11 @@ public class ChunkLoadingRenderGlobal { renderDispatcher.queueChunkUpdates = queueChunkUpdates; try { - Field hookField = RenderGlobal.class.getField("replayModRender_hook"); + //#if MC>=11300 + Field hookField = WorldRenderer.class.getField("replayModRender_hook"); + //#else + //$$ Field hookField = RenderGlobal.class.getField("replayModRender_hook"); + //#endif hookField.set(hooked, this); } catch (NoSuchFieldException | IllegalAccessException e) { throw new Error(e); @@ -119,7 +151,11 @@ public class ChunkLoadingRenderGlobal { workerJailingQueue.freeAll(); try { - Field hookField = RenderGlobal.class.getField("replayModRender_hook"); + //#if MC>=11300 + Field hookField = WorldRenderer.class.getField("replayModRender_hook"); + //#else + //$$ Field hookField = RenderGlobal.class.getField("replayModRender_hook"); + //#endif hookField.set(hooked, null); } catch (NoSuchFieldException | IllegalAccessException e) { throw new Error(e); @@ -139,7 +175,13 @@ public class ChunkLoadingRenderGlobal { } @Override - protected void processTask(ChunkCompileTaskGenerator p_178474_1_) throws InterruptedException { + protected void processTask( + //#if MC>=11300 + ChunkRenderTask p_178474_1_ + //#else + //$$ ChunkCompileTaskGenerator p_178474_1_ + //#endif + ) throws InterruptedException { super.processTask(p_178474_1_); } } diff --git a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java index fa6d2ef3..9566662f 100644 --- a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java +++ b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java @@ -1,5 +1,6 @@ package com.replaymod.render.hooks; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.capturer.CaptureData; import com.replaymod.render.capturer.RenderInfo; @@ -8,8 +9,12 @@ import lombok.Getter; import net.minecraft.client.Minecraft; //#if MC>=10800 -import net.minecraft.client.renderer.GlStateManager; -import net.minecraftforge.fml.common.FMLCommonHandler; +//#if MC>=11300 +import net.minecraftforge.fml.hooks.BasicEventHooks; +//#else +//$$ import net.minecraft.client.renderer.GlStateManager; +//$$ import net.minecraftforge.fml.common.FMLCommonHandler; +//#endif //#else //$$ import com.replaymod.core.versions.MCVer.GlStateManager; //$$ import cpw.mods.fml.common.FMLCommonHandler; @@ -18,7 +23,7 @@ import net.minecraftforge.fml.common.FMLCommonHandler; import java.io.IOException; public class EntityRendererHandler implements WorldRenderer { - public final Minecraft mc = Minecraft.getMinecraft(); + public final Minecraft mc = MCVer.getMinecraft(); @Getter protected final RenderSettings settings; @@ -44,21 +49,33 @@ public class EntityRendererHandler implements WorldRenderer { } public void renderWorld(float partialTicks, long finishTimeNano) { - FMLCommonHandler.instance().onRenderTickStart(partialTicks); - - mc.entityRenderer.updateLightmap(partialTicks); - - GlStateManager.enableDepth(); - GlStateManager.enableAlpha(); - GlStateManager.alphaFunc(516, 0.5F); + //#if MC>=11300 + BasicEventHooks.onRenderTickStart(partialTicks); + //#else + //$$ FMLCommonHandler.instance().onRenderTickStart(partialTicks); + //#endif + //#if MC>=11300 + mc.entityRenderer.renderWorld(partialTicks, finishTimeNano); + //#else + //$$ mc.entityRenderer.updateLightmap(partialTicks); + //$$ + //$$ GlStateManager.enableDepth(); + //$$ GlStateManager.enableAlpha(); + //$$ GlStateManager.alphaFunc(516, 0.5F); + //$$ //#if MC>=10800 - mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano); + //$$ mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano); //#else //$$ mc.entityRenderer.renderWorld(partialTicks, finishTimeNano); //#endif + //#endif - FMLCommonHandler.instance().onRenderTickEnd(partialTicks); + //#if MC>=11300 + BasicEventHooks.onRenderTickEnd(partialTicks); + //#else + //$$ FMLCommonHandler.instance().onRenderTickEnd(partialTicks); + //#endif } @Override diff --git a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java index 16d9017e..9042eeb0 100644 --- a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java +++ b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java @@ -5,9 +5,9 @@ import com.coremedia.iso.boxes.*; import com.google.common.primitives.Bytes; import com.googlecode.mp4parser.BasicContainer; import com.replaymod.render.RenderSettings; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.lwjgl.util.Dimension; import java.io.File; import java.io.FileOutputStream; diff --git a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java index 919a88bc..6bbbca70 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java +++ b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java @@ -1,20 +1,17 @@ package com.replaymod.render.mixin; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.capturer.CubicOpenGlFrameCapturer; import com.replaymod.render.capturer.StereoscopicOpenGlFrameCapturer; import com.replaymod.render.hooks.EntityRendererHandler; import com.replaymod.replay.camera.CameraEntity; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.EntityRenderer; -import net.minecraft.client.renderer.RenderGlobal; -import net.minecraft.client.settings.GameSettings; +import net.minecraft.client.renderer.Matrix4f; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import org.lwjgl.opengl.GL11; -import org.lwjgl.util.ReadableColor; -import org.lwjgl.util.glu.Project; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -22,6 +19,16 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11300 +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.GameSettings; +//#else +//$$ import net.minecraft.client.renderer.EntityRenderer; +//$$ import net.minecraft.client.renderer.RenderGlobal; +//$$ import net.minecraft.client.settings.GameSettings; +//#endif + //#if MC>=10904 import net.minecraft.util.math.RayTraceResult; //#else @@ -31,12 +38,17 @@ import net.minecraft.util.math.RayTraceResult; //#if MC>=10800 import net.minecraft.client.renderer.GlStateManager; //#else +//$$ import net.minecraft.entity.EntityLivingBase; //$$ import com.replaymod.core.versions.MCVer.GlStateManager; //#endif import static com.replaymod.core.versions.MCVer.*; -@Mixin(value = EntityRenderer.class) +//#if MC>=11300 +@Mixin(value = GameRenderer.class) +//#else +//$$ @Mixin(value = EntityRenderer.class) +//#endif public abstract class MixinEntityRenderer implements EntityRendererHandler.IEntityRenderer, EntityRendererHandler.GluPerspective { @Shadow public Minecraft mc; @@ -75,7 +87,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti @Inject(method = "orientCamera", at = @At("HEAD")) private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) { if (replayModRender_handler != null) { - Entity entity = getRenderViewEntity(Minecraft.getMinecraft()); + Entity entity = getRenderViewEntity(MCVer.getMinecraft()); RenderSettings settings = replayModRender_handler.getSettings(); if (settings.isStabilizeYaw()) { entity.prevRotationYaw = entity.rotationYaw = 0; @@ -98,7 +110,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti @Inject(method = "setupCameraTransform", at = @At("HEAD")) private void replayModRender_beforeSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { if (replayModRender_handler != null) { - Entity entity = getRenderViewEntity(Minecraft.getMinecraft()); + Entity entity = getRenderViewEntity(MCVer.getMinecraft()); orgYaw = entity.rotationYaw; orgPitch = entity.rotationPitch; orgPrevYaw = entity.prevRotationYaw; @@ -110,7 +122,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti @Inject(method = "setupCameraTransform", at = @At("RETURN")) private void replayModRender_afterSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { if (replayModRender_handler != null) { - Entity entity = getRenderViewEntity(Minecraft.getMinecraft()); + Entity entity = getRenderViewEntity(MCVer.getMinecraft()); entity.rotationYaw = orgYaw; entity.rotationPitch = orgPitch; entity.prevRotationYaw = orgPrevYaw; @@ -128,7 +140,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti ci.cancel(); return; // No spectator hands during 360° view, we wouldn't even know where to put it } - Entity currentEntity = getRenderViewEntity(Minecraft.getMinecraft()); + Entity currentEntity = getRenderViewEntity(MCVer.getMinecraft()); if (currentEntity instanceof EntityPlayer && !(currentEntity instanceof CameraEntity)) { if (renderPass == 2) { // Need to update render pass renderPass = replayModRender_handler.data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE ? 1 : 0; @@ -142,9 +154,13 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti @Shadow public abstract void renderHand(float partialTicks, int renderPass); + //#if MC>=11300 + @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/math/RayTraceResult;IF)V")) + private void replayModRender_drawSelectionBox(WorldRenderer instance, EntityPlayer player, RayTraceResult rtr, int alwaysZero, float partialTicks) { + //#else //#if MC>=10904 - @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/math/RayTraceResult;IF)V")) - private void replayModRender_drawSelectionBox(RenderGlobal instance, EntityPlayer player, RayTraceResult rtr, int alwaysZero, float partialTicks) { + //$$ @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/math/RayTraceResult;IF)V")) + //$$ private void replayModRender_drawSelectionBox(RenderGlobal instance, EntityPlayer player, RayTraceResult rtr, int alwaysZero, float partialTicks) { //#else //#if MC>=10800 //$$ @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/MovingObjectPosition;IF)V")) @@ -152,6 +168,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti //$$ @Redirect(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/MovingObjectPosition;IF)V")) //#endif //$$ private void replayModRender_drawSelectionBox(RenderGlobal instance, EntityPlayer player, MovingObjectPosition rtr, int alwaysZero, float partialTicks) { + //#endif //#endif if (replayModRender_handler == null) { instance.drawSelectionBox(player, rtr, alwaysZero, partialTicks); @@ -160,38 +177,51 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti private int orgRenderDistanceChunks; + //#if MC>=11300 + @Inject(method = "updateCameraAndRender", at = @At(value = "JUMP", ordinal = 0)) + //#else //#if MC>=10800 - @Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0)) + //$$ @Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0)) //#else //$$ @Inject(method = "renderWorld", at = @At(value = "INVOKE", ordinal = 0, shift = At.Shift.AFTER, //$$ target = "Lnet/minecraft/client/renderer/culling/ClippingHelperImpl;getInstance()Lnet/minecraft/client/renderer/culling/ClippingHelper;")) //#endif + //#endif private void replayModRender_beforeRenderSky(CallbackInfo ci) { if (replayModRender_handler != null && replayModRender_handler.getSettings().getChromaKeyingColor() != null) { - GameSettings settings = Minecraft.getMinecraft().gameSettings; + GameSettings settings = MCVer.getMinecraft().gameSettings; orgRenderDistanceChunks = settings.renderDistanceChunks; settings.renderDistanceChunks = 5; // Set render distance to 5 so we're always rendering sky when chroma keying } } + //#if MC>=11300 + @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;renderSky(F)V")) + private void replayModRender_renderSky(WorldRenderer instance, float partialTicks) { + //#else //#if MC>=10800 - @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(FI)V")) - private void replayModRender_renderSky(RenderGlobal instance, float partialTicks, int renderPass) { + //$$ @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(FI)V")) + //$$ private void replayModRender_renderSky(RenderGlobal instance, float partialTicks, int renderPass) { //#else //$$ @Redirect(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(F)V")) //$$ private void replayModRender_renderSky(RenderGlobal instance, float partialTicks) { + //#endif //#endif if (replayModRender_handler != null && replayModRender_handler.getSettings().getChromaKeyingColor() != null) { - Minecraft.getMinecraft().gameSettings.renderDistanceChunks = orgRenderDistanceChunks; + MCVer.getMinecraft().gameSettings.renderDistanceChunks = orgRenderDistanceChunks; ReadableColor color = replayModRender_handler.getSettings().getChromaKeyingColor(); GlStateManager.clearColor(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, 1); GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT); } else { + //#if MC>=11300 + instance.renderSky(partialTicks); + //#else //#if MC>=10800 - instance.renderSky(partialTicks, renderPass); + //$$ instance.renderSky(partialTicks, renderPass); //#else //$$ instance.renderSky(partialTicks); //#endif + //#endif } } @@ -207,9 +237,9 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti private void replayModRender_setupStereoscopicProjection(float partialTicks, int renderPass, CallbackInfo ci) { if (replayModRender_getHandler() != null) { if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { - GlStateManager.translate(0.07, 0, 0); + GL11.glTranslatef(0.07f, 0, 0); } else if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) { - GlStateManager.translate(-0.07, 0, 0); + GL11.glTranslatef(-0.07f, 0, 0); } } } @@ -222,9 +252,9 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti private void replayModRender_setupStereoscopicModelView(float partialTicks, int renderPass, CallbackInfo ci) { if (replayModRender_getHandler() != null) { if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { - GlStateManager.translate(0.1, 0, 0); + GL11.glTranslatef(0.1f, 0, 0); } else if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) { - GlStateManager.translate(-0.1, 0, 0); + GL11.glTranslatef(-0.1f, 0, 0); } } } @@ -233,63 +263,84 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti * Cubic Renderer */ - @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) - private void replayModRender_gluPerspective$0(float fovY, float aspect, float zNear, float zFar) { - replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + //#if MC>=11300 + @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)V")) + private void replayModRender_gluPerspective$0(double fovY, float aspect, float zNear, float zFar) { + replayModRender_gluPerspective((float) fovY, aspect, zNear, zFar); } - //#if MC>=10800 - @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) - //#else - //$$ @Redirect(method = "renderHand", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) - //#endif - private void replayModRender_gluPerspective$1(float fovY, float aspect, float zNear, float zFar) { - replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)V")) + private void replayModRender_gluPerspective$1(double fovY, float aspect, float zNear, float zFar) { + replayModRender_gluPerspective((float) fovY, aspect, zNear, zFar); } - //#if MC>=10800 - @Redirect(method = "renderCloudsCheck", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) - private void replayModRender_gluPerspective$2(float fovY, float aspect, float zNear, float zFar) { - replayModRender_gluPerspective(fovY, aspect, zNear, zFar); - } - //#endif - @Override public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) { if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { fovY = 90; aspect = 1; } - Project.gluPerspective(fovY, aspect, zNear, zFar); + Matrix4f.perspective(fovY, aspect, zNear, zFar); } + //#else + //$$ @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) + //$$ private void replayModRender_gluPerspective$0(float fovY, float aspect, float zNear, float zFar) { + //$$ replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + //$$ } + //$$ + //#if MC>=10800 + //$$ @Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) + //#else + //$$ @Redirect(method = "renderHand", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) + //#endif + //$$ private void replayModRender_gluPerspective$1(float fovY, float aspect, float zNear, float zFar) { + //$$ replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + //$$ } + //$$ + //#if MC>=10800 + //$$ @Redirect(method = "renderCloudsCheck", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) + //$$ private void replayModRender_gluPerspective$2(float fovY, float aspect, float zNear, float zFar) { + //$$ replayModRender_gluPerspective(fovY, aspect, zNear, zFar); + //$$ } + //#endif + //$$ + //$$ @Override + //$$ public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) { + //$$ if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { + //$$ fovY = 90; + //$$ aspect = 1; + //$$ } + //$$ Project.gluPerspective(fovY, aspect, zNear, zFar); + //$$ } + //#endif @Inject(method = "orientCamera", at = @At("HEAD")) private void replayModRender_setupCubicFrameRotation(float partialTicks, CallbackInfo ci) { if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) { switch ((CubicOpenGlFrameCapturer.Data) replayModRender_getHandler().data) { case FRONT: - GlStateManager.rotate(0, 0.0F, 1.0F, 0.0F); + GL11.glRotatef(0, 0.0F, 1.0F, 0.0F); break; case RIGHT: - GlStateManager.rotate(90, 0.0F, 1.0F, 0.0F); + GL11.glRotatef(90, 0.0F, 1.0F, 0.0F); break; case BACK: - GlStateManager.rotate(180, 0.0F, 1.0F, 0.0F); + GL11.glRotatef(180, 0.0F, 1.0F, 0.0F); break; case LEFT: - GlStateManager.rotate(-90, 0.0F, 1.0F, 0.0F); + GL11.glRotatef(-90, 0.0F, 1.0F, 0.0F); break; case BOTTOM: - GlStateManager.rotate(90, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(90, 1.0F, 0.0F, 0.0F); break; case TOP: - GlStateManager.rotate(-90, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(-90, 1.0F, 0.0F, 0.0F); break; } } if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { // Minecraft goes back a little so we have to revert that - GlStateManager.translate(0.0F, 0.0F, 0.1F); + GL11.glTranslatef(0.0F, 0.0F, 0.1F); } } } diff --git a/src/main/java/com/replaymod/render/mixin/MixinFrustum.java b/src/main/java/com/replaymod/render/mixin/MixinFrustum.java index 4a6facbe..aad8f545 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinFrustum.java +++ b/src/main/java/com/replaymod/render/mixin/MixinFrustum.java @@ -1,7 +1,7 @@ package com.replaymod.render.mixin; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.hooks.EntityRendererHandler; -import net.minecraft.client.Minecraft; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -21,7 +21,7 @@ import net.minecraft.client.renderer.culling.Frustum; public abstract class MixinFrustum { @Inject(method = "isBoxInFrustum", at = @At("HEAD"), cancellable = true) public void isBoxInFrustum(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, CallbackInfoReturnable ci) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler(); if (handler != null && handler.omnidirectional && handler.data == null) { // 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 diff --git a/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java b/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java index 5cf6df2c..87fed192 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java +++ b/src/main/java/com/replaymod/render/mixin/MixinParticleManager.java @@ -1,8 +1,8 @@ package com.replaymod.render.mixin; //#if MC>=10904 +import com.replaymod.core.versions.MCVer; import com.replaymod.render.hooks.EntityRendererHandler; -import net.minecraft.client.Minecraft; import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.ParticleManager; import net.minecraft.entity.Entity; @@ -46,7 +46,7 @@ public abstract class MixinParticleManager { //$$ private void renderParticle(Particle particle, VertexBuffer vertexBuffer, Entity view, float partialTicks, //#endif float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler(); if (handler != null && handler.omnidirectional) { // Align all particles towards the camera double dx = particle.prevPosX + (particle.posX - particle.prevPosX) * partialTicks - view.posX; diff --git a/src/main/java/com/replaymod/render/mixin/MixinRender.java b/src/main/java/com/replaymod/render/mixin/MixinRender.java index 898f7140..5f1bfd71 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRender.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRender.java @@ -1,7 +1,7 @@ package com.replaymod.render.mixin; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.hooks.EntityRendererHandler; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.Render; import net.minecraft.entity.Entity; import org.spongepowered.asm.mixin.Mixin; @@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class MixinRender { @Inject(method = "renderLivingLabel", at = @At("HEAD"), cancellable = true) private void replayModRender_areAllNamesHidden(Entity entityIn, String str, double x, double y, double z, int maxDistance, CallbackInfo ci) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler(); if (handler != null && !handler.getSettings().isRenderNameTags()) { ci.cancel(); } diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java index c81175b0..4636d729 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java @@ -2,7 +2,6 @@ package com.replaymod.render.mixin; import com.replaymod.render.hooks.ChunkLoadingRenderGlobal; -import net.minecraft.client.renderer.RenderGlobal; import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher; import net.minecraft.client.renderer.culling.ICamera; import net.minecraft.entity.Entity; @@ -10,16 +9,25 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11300 +import net.minecraft.client.renderer.WorldRenderer; +//#else +//$$ import net.minecraft.client.renderer.RenderGlobal; +//#endif + //#if MC<10904 //$$ import net.minecraft.client.renderer.chunk.RenderChunk; //$$ import net.minecraft.util.BlockPos; //$$ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; //#endif -@Mixin(RenderGlobal.class) +//#if MC>=11300 +@Mixin(WorldRenderer.class) +//#else +//$$ @Mixin(RenderGlobal.class) +//#endif public abstract class MixinRenderGlobal { public ChunkLoadingRenderGlobal replayModRender_hook; private boolean replayModRender_passThroughSetupTerrain; diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java index 7f9f1562..cc67a9d6 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java @@ -1,7 +1,7 @@ package com.replaymod.render.mixin; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.hooks.EntityRendererHandler; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; import org.spongepowered.asm.mixin.Mixin; @@ -29,7 +29,7 @@ public abstract class MixinRenderManager { //#else //$$ private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) { //#endif - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); + EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler(); if (handler != null && handler.omnidirectional) { double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)); double yaw = -Math.atan2(dx, dz); diff --git a/src/main/java/com/replaymod/render/processor/CubicToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/CubicToRGBProcessor.java index 223ea375..9d5c8dec 100644 --- a/src/main/java/com/replaymod/render/processor/CubicToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/CubicToRGBProcessor.java @@ -3,7 +3,7 @@ package com.replaymod.render.processor; import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.utils.ByteBufferPool; -import org.lwjgl.util.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java index b027b977..b8954917 100644 --- a/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/EquirectangularToRGBProcessor.java @@ -3,9 +3,9 @@ package com.replaymod.render.processor; import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.utils.ByteBufferPool; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import lombok.Getter; import org.apache.commons.lang3.Validate; -import org.lwjgl.util.Dimension; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/processor/ODSToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/ODSToRGBProcessor.java index 2db32280..6e626eef 100644 --- a/src/main/java/com/replaymod/render/processor/ODSToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/ODSToRGBProcessor.java @@ -3,8 +3,8 @@ package com.replaymod.render.processor; import com.replaymod.render.frame.ODSOpenGlFrame; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.utils.ByteBufferPool; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/processor/OpenGlToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/OpenGlToRGBProcessor.java index 0cd54e5f..b6e6a6a3 100644 --- a/src/main/java/com/replaymod/render/processor/OpenGlToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/OpenGlToRGBProcessor.java @@ -2,7 +2,7 @@ package com.replaymod.render.processor; import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.frame.RGBFrame; -import org.lwjgl.util.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/processor/StereoscopicToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/StereoscopicToRGBProcessor.java index 02ed1de3..38f244b9 100644 --- a/src/main/java/com/replaymod/render/processor/StereoscopicToRGBProcessor.java +++ b/src/main/java/com/replaymod/render/processor/StereoscopicToRGBProcessor.java @@ -3,8 +3,8 @@ package com.replaymod.render.processor; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.frame.StereoscopicOpenGlFrame; import com.replaymod.render.utils.ByteBufferPool; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import java.nio.ByteBuffer; diff --git a/src/main/java/com/replaymod/render/rendering/Pipeline.java b/src/main/java/com/replaymod/render/rendering/Pipeline.java index 5b771aac..cff1d7fa 100644 --- a/src/main/java/com/replaymod/render/rendering/Pipeline.java +++ b/src/main/java/com/replaymod/render/rendering/Pipeline.java @@ -1,16 +1,23 @@ package com.replaymod.render.rendering; +import com.replaymod.core.versions.MCVer; import lombok.RequiredArgsConstructor; import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; -import org.lwjgl.opengl.Display; + +//#if MC>=11300 +import org.lwjgl.glfw.GLFW; +//#else +//$$ import org.lwjgl.opengl.Display; +//#endif import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import static com.replaymod.core.versions.MCVer.newReportedException; + public class Pipeline implements Runnable { private final FrameCapturer capturer; @@ -48,9 +55,13 @@ public class Pipeline implements Runnable { } }, new ThreadPoolExecutor.DiscardPolicy()); - Minecraft mc = Minecraft.getMinecraft(); + Minecraft mc = MCVer.getMinecraft(); while (!capturer.isDone() && !Thread.currentThread().isInterrupted()) { - if (Display.isCloseRequested() || mc.hasCrashed) { + //#if MC>=11300 + if (GLFW.glfwWindowShouldClose(mc.mainWindow.getHandle()) || mc.hasCrashed) { + //#else + //$$ if (Display.isCloseRequested() || mc.hasCrashed) { + //#endif Thread.currentThread().interrupt(); return; } @@ -73,7 +84,7 @@ public class Pipeline implements Runnable { consumer.close(); } catch (Throwable t) { CrashReport crashReport = CrashReport.makeCrashReport(t, "Cleaning up rendering pipeline"); - throw new ReportedException(crashReport); + throw newReportedException(crashReport); } } @@ -105,7 +116,7 @@ public class Pipeline implements Runnable { } } catch (Throwable t) { CrashReport crashReport = CrashReport.makeCrashReport(t, "Processing frame"); - Minecraft.getMinecraft().crashed(crashReport); + MCVer.getMinecraft().crashed(crashReport); } } } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 87367523..c329139d 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -1,6 +1,7 @@ package com.replaymod.render.rendering; import com.replaymod.core.utils.WrappedTimer; +import com.replaymod.core.versions.MCVer; import com.replaymod.pathing.player.AbstractTimelinePlayer; import com.replaymod.pathing.player.ReplayTimer; import com.replaymod.pathing.properties.TimestampProperty; @@ -18,16 +19,19 @@ import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Keyframe; import com.replaymod.replaystudio.pathing.path.Path; import com.replaymod.replaystudio.pathing.path.Timeline; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.shader.Framebuffer; -import net.minecraft.util.ReportedException; import net.minecraft.util.Timer; -import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.Display; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; + +//#if MC>=11300 +import org.lwjgl.glfw.GLFW; +//#else +//$$ import net.minecraft.client.gui.ScaledResolution; +//$$ import org.lwjgl.opengl.Display; +//#endif //#if MC>=10904 import net.minecraft.util.SoundCategory; @@ -57,7 +61,7 @@ import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; public class VideoRenderer implements RenderInfo { - private final Minecraft mc = Minecraft.getMinecraft(); + private final Minecraft mc = MCVer.getMinecraft(); private final RenderSettings settings; private final ReplayHandler replayHandler; private final Timeline timeline; @@ -152,7 +156,7 @@ public class VideoRenderer implements RenderInfo { renderingPipeline.run(); if (mc.hasCrashed) { - throw new ReportedException(mc.crashReporter); + throw newReportedException(mc.crashReporter); } if (settings.isInjectSphericalMetadata()) { @@ -175,18 +179,25 @@ public class VideoRenderer implements RenderInfo { @Override public float updateForNextFrame() { // because the jGui lib uses Minecraft's displayWidth and displayHeight values, update these temporarily - int displayWidthBefore = mc.displayWidth; - int displayHeightBefore = mc.displayHeight; - - mc.displayWidth = displayWidth; - mc.displayHeight = displayHeight; + //#if MC>=11300 + // FIXME is this even necessary? + //#else + //$$ int displayWidthBefore = mc.displayWidth; + //$$ int displayHeightBefore = mc.displayHeight; + //$$ mc.displayWidth = displayWidth; + //$$ mc.displayHeight = displayHeight; + //#endif if (!settings.isHighPerformance() || framesDone % fps == 0) { drawGui(); } // Updating the timer will cause the timeline player to update the game state - mc.timer.updateTimer(); + mc.timer.updateTimer( + //#if MC>=11300 + MCVer.milliTime() + //#endif + ); int elapsedTicks = mc.timer.elapsedTicks; while (elapsedTicks-- > 0) { @@ -194,8 +205,12 @@ public class VideoRenderer implements RenderInfo { } // change Minecraft's display size back - mc.displayWidth = displayWidthBefore; - mc.displayHeight = displayHeightBefore; + //#if MC>=11300 + // FIXME + //#else + //$$ mc.displayWidth = displayWidthBefore; + //$$ mc.displayHeight = displayHeightBefore; + //#endif framesDone++; return mc.timer.renderPartialTicks; @@ -211,31 +226,52 @@ public class VideoRenderer implements RenderInfo { timelinePlayerFuture = timelinePlayer.start(timeline); if (!OpenGlHelper.isFramebufferEnabled()) { - Display.setResizable(false); + //#if MC>=11300 + GLFW.glfwSetWindowAttrib(mc.mainWindow.getHandle(), GLFW.GLFW_RESIZABLE, 1); + //#else + //$$ Display.setResizable(false); + //#endif } if (mc.gameSettings.showDebugInfo) { debugInfoWasShown = true; mc.gameSettings.showDebugInfo = false; } - if (Mouse.isGrabbed()) { + //#if MC>=11300 + if (mc.mouseHelper.isMouseGrabbed()) { mouseWasGrabbed = true; } - Mouse.setGrabbed(false); + mc.mouseHelper.ungrabMouse(); + //#else + //$$ if (Mouse.isGrabbed()) { + //$$ mouseWasGrabbed = true; + //$$ } + //$$ Mouse.setGrabbed(false); + //#endif // Mute all sounds except GUI sounds (buttons, etc.) - Map mutedSounds = new EnumMap<>(SoundCategory.class); + //#if MC>=11300 + originalSoundLevels = new EnumMap<>(SoundCategory.class); for (SoundCategory category : SoundCategory.values()) { - mutedSounds.put(category, 0f); + if (category != SoundCategory.MASTER) { + originalSoundLevels.put(category, mc.gameSettings.getSoundLevel(category)); + mc.gameSettings.setSoundLevel(category, 0); + } } + //#else + //$$ Map mutedSounds = new EnumMap<>(SoundCategory.class); + //$$ for (SoundCategory category : SoundCategory.values()) { + //$$ mutedSounds.put(category, 0f); + //$$ } //#if MC>=10904 - originalSoundLevels = mc.gameSettings.soundLevels; - mutedSounds.put(SoundCategory.MASTER, originalSoundLevels.get(SoundCategory.MASTER)); - mc.gameSettings.soundLevels = mutedSounds; + //$$ originalSoundLevels = mc.gameSettings.soundLevels; + //$$ mutedSounds.put(SoundCategory.MASTER, originalSoundLevels.get(SoundCategory.MASTER)); + //$$ mc.gameSettings.soundLevels = mutedSounds; //#else //$$ originalSoundLevels = mc.gameSettings.mapSoundLevels; //$$ mutedSounds.put(SoundCategory.MASTER, (Float) originalSoundLevels.get(SoundCategory.MASTER)); //$$ mc.gameSettings.mapSoundLevels = mutedSounds; //#endif + //#endif fps = settings.getFramesPerSecond(); @@ -277,14 +313,28 @@ public class VideoRenderer implements RenderInfo { timelinePlayer.onTick(new ReplayTimer.UpdatedEvent()); if (!OpenGlHelper.isFramebufferEnabled()) { - Display.setResizable(true); + //#if MC>=11300 + GLFW.glfwSetWindowAttrib(mc.mainWindow.getHandle(), GLFW.GLFW_RESIZABLE, 0); + //#else + //$$ Display.setResizable(true); + //#endif } mc.gameSettings.showDebugInfo = debugInfoWasShown; if (mouseWasGrabbed) { - mc.mouseHelper.grabMouseCursor(); + //#if MC>=11300 + mc.mouseHelper.grabMouse(); + //#else + //$$ mc.mouseHelper.grabMouseCursor(); + //#endif } //#if MC>=10904 - mc.gameSettings.soundLevels = originalSoundLevels; + //#if MC>=11300 + for (Map.Entry entry : originalSoundLevels.entrySet()) { + mc.gameSettings.setSoundLevel(entry.getKey(), entry.getValue()); + } + //#else + //$$ mc.gameSettings.soundLevels = originalSoundLevels; + //#endif //#else //$$ mc.gameSettings.mapSoundLevels = originalSoundLevels; //#endif @@ -306,7 +356,11 @@ public class VideoRenderer implements RenderInfo { } // Finally, resize the Minecraft framebuffer to the actual width/height of the window - mc.resize(displayWidth, displayHeight); + //#if MC>=11300 + mc.getFramebuffer().createFramebuffer(displayWidth, displayHeight); + //#else + //$$ mc.resize(displayWidth, displayHeight); + //#endif } private void tick() { @@ -317,14 +371,14 @@ public class VideoRenderer implements RenderInfo { } mc.currentScreen = gui.toMinecraft(); - //#if MC>=10800 - try { - mc.runTick(); - } catch (IOException e) { - throw new RuntimeException(e); - } + //#if MC>=10800 && MC<11300 + //$$ try { + //$$ mc.runTick(); + //$$ } catch (IOException e) { + //$$ throw new RuntimeException(e); + //$$ } //#else - //$$ mc.runTick(); + mc.runTick(); //#endif } @@ -333,7 +387,11 @@ public class VideoRenderer implements RenderInfo { // Resize the GUI framebuffer if the display size changed if (!settings.isHighPerformance() && displaySizeChanged()) { updateDisplaySize(); - guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight); + //#if MC>=11300 + guiFramebuffer.createBindFramebuffer(mc.mainWindow.getFramebufferWidth(), mc.mainWindow.getFramebufferHeight()); + //#else + //$$ guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight); + //#endif } pushMatrix(); @@ -341,28 +399,47 @@ public class VideoRenderer implements RenderInfo { enableTexture2D(); guiFramebuffer.bindFramebuffer(true); - mc.entityRenderer.setupOverlayRendering(); + //#if MC>=11300 + mc.mainWindow.setupOverlayRendering(); + //#else + //$$ mc.entityRenderer.setupOverlayRendering(); + //#endif - ScaledResolution scaled = newScaledResolution(mc); - gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight()); + //#if MC>=11300 + gui.toMinecraft().setWorldAndResolution(mc, mc.mainWindow.getScaledWidth(), mc.mainWindow.getScaledHeight()); + //#else + //$$ ScaledResolution scaled = newScaledResolution(mc); + //$$ gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight()); + //#endif + // Events are polled on 1.13+ in mainWindow.update which is called later + //#if MC<11300 //#if MC>=10800 - try { - gui.toMinecraft().handleInput(); - } catch (IOException e) { - // That's a strange exception from this kind of method O_o - // It isn't actually thrown here, so we'll deal with it the easy way - throw new RuntimeException(e); - } + //$$ try { + //$$ gui.toMinecraft().handleInput(); + //$$ } catch (IOException e) { + //$$ // That's a strange exception from this kind of method O_o + //$$ // It isn't actually thrown here, so we'll deal with it the easy way + //$$ throw new RuntimeException(e); + //$$ } //#else //$$ gui.toMinecraft().handleInput(); //#endif + //#endif - int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth; - int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1; + //#if MC>=11300 + int mouseX = (int) mc.mouseHelper.getMouseX(); + int mouseY = (int) mc.mouseHelper.getMouseY(); - gui.toMinecraft().updateScreen(); - gui.toMinecraft().drawScreen(mouseX, mouseY, 0); + gui.toMinecraft().tick(); + gui.toMinecraft().render(mouseX, mouseY, 0); + //#else + //$$ int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth; + //$$ int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1; + //$$ + //$$ gui.toMinecraft().updateScreen(); + //$$ gui.toMinecraft().drawScreen(mouseX, mouseY, 0); + //#endif guiFramebuffer.unbindFramebuffer(); popMatrix(); @@ -370,20 +447,30 @@ public class VideoRenderer implements RenderInfo { guiFramebuffer.framebufferRender(displayWidth, displayHeight); popMatrix(); - // if not in high performance mode, update the gui size if screen size changed - // otherwise just swap the progress gui to screen - if (settings.isHighPerformance()) { - Display.update(); - } else { + //#if MC>=11300 + mc.mainWindow.update(false); + //#else + //$$ // if not in high performance mode, update the gui size if screen size changed + //$$ // otherwise just swap the progress gui to screen + //$$ if (settings.isHighPerformance()) { + //$$ Display.update(); + //$$ } else { //#if MC>=10800 - mc.updateDisplay(); + //$$ mc.updateDisplay(); //#else //$$ mc.resetSize(); //#endif + //$$ } + //#endif + //#if MC>=11300 + if (mc.mouseHelper.isMouseGrabbed()) { + mc.mouseHelper.ungrabMouse(); } - if (Mouse.isGrabbed()) { - Mouse.setGrabbed(false); - } + //#else + //$$ if (Mouse.isGrabbed()) { + //$$ Mouse.setGrabbed(false); + //$$ } + //#endif if (paused) { try { Thread.sleep(50); @@ -396,12 +483,21 @@ public class VideoRenderer implements RenderInfo { } private boolean displaySizeChanged() { - return displayWidth != Display.getWidth() || displayHeight != Display.getHeight(); + //#if MC>=11300 + return displayWidth != mc.mainWindow.getWidth() || displayHeight != mc.mainWindow.getHeight(); + //#else + //$$ return displayWidth != Display.getWidth() || displayHeight != Display.getHeight(); + //#endif } private void updateDisplaySize() { - displayWidth = Display.getWidth(); - displayHeight = Display.getHeight(); + //#if MC>=11300 + displayWidth = mc.mainWindow.getWidth(); + displayHeight = mc.mainWindow.getHeight(); + //#else + //$$ displayWidth = Display.getWidth(); + //$$ displayHeight = Display.getHeight(); + //#endif } public int getFramesDone() { diff --git a/src/main/java/com/replaymod/render/shader/Program.java b/src/main/java/com/replaymod/render/shader/Program.java index b4d2c8e8..3481c5f9 100644 --- a/src/main/java/com/replaymod/render/shader/Program.java +++ b/src/main/java/com/replaymod/render/shader/Program.java @@ -1,7 +1,6 @@ package com.replaymod.render.shader; -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.IResource; +import com.replaymod.core.versions.MCVer; import net.minecraft.util.ResourceLocation; import org.apache.commons.io.IOUtils; import org.lwjgl.opengl.ARBFragmentShader; @@ -9,6 +8,12 @@ import org.lwjgl.opengl.ARBShaderObjects; import org.lwjgl.opengl.ARBVertexShader; import org.lwjgl.opengl.GL11; +//#if MC>=11300 +import net.minecraft.resources.IResource; +//#else +//$$ import net.minecraft.client.resources.IResource; +//#endif + import java.io.InputStream; import static org.lwjgl.opengl.ARBShaderObjects.*; @@ -47,7 +52,7 @@ public class Program { if(shader == 0) throw new Exception("glCreateShaderObjectARB failed"); - IResource resource = Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation); + IResource resource = MCVer.getMinecraft().getResourceManager().getResource(resourceLocation); try (InputStream is = resource.getInputStream()) { glShaderSourceARB(shader, IOUtils.toString(is)); } diff --git a/src/main/java/com/replaymod/render/utils/PixelBufferObject.java b/src/main/java/com/replaymod/render/utils/PixelBufferObject.java index 87794da9..7d400846 100644 --- a/src/main/java/com/replaymod/render/utils/PixelBufferObject.java +++ b/src/main/java/com/replaymod/render/utils/PixelBufferObject.java @@ -1,12 +1,18 @@ package com.replaymod.render.utils; import com.google.common.base.Objects; +import com.replaymod.core.versions.MCVer; import lombok.RequiredArgsConstructor; -import net.minecraft.client.Minecraft; import org.apache.logging.log4j.LogManager; -import org.lwjgl.opengl.ARBBufferObject; -import org.lwjgl.opengl.GLContext; -import org.lwjgl.opengl.Util; + +//#if MC>=11300 +import org.lwjgl.opengl.ARBVertexBufferObject; +import org.lwjgl.opengl.GL; +//#else +//$$ import org.lwjgl.opengl.ARBBufferObject; +//$$ import org.lwjgl.opengl.GLContext; +//$$ import org.lwjgl.opengl.Util; +//#endif import java.nio.ByteBuffer; @@ -14,6 +20,10 @@ import static org.lwjgl.opengl.ARBPixelBufferObject.*; import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL21.GL_PIXEL_PACK_BUFFER; +//#if MC>=11300 +import static org.lwjgl.opengl.ARBVertexBufferObject.*; +//#endif + public class PixelBufferObject { @RequiredArgsConstructor public enum Usage { @@ -24,8 +34,13 @@ public class PixelBufferObject { private final int arb, gl15; } - public static final boolean SUPPORTED = GLContext.getCapabilities().GL_ARB_pixel_buffer_object || GLContext.getCapabilities().OpenGL15; - private static final boolean arb = !GLContext.getCapabilities().OpenGL15; + //#if MC>=11300 + public static final boolean SUPPORTED = GL.getCapabilities().GL_ARB_pixel_buffer_object || GL.getCapabilities().OpenGL15; + private static final boolean arb = !GL.getCapabilities().OpenGL15; + //#else + //$$ public static final boolean SUPPORTED = GLContext.getCapabilities().GL_ARB_pixel_buffer_object || GLContext.getCapabilities().OpenGL15; + //$$ private static final boolean arb = !GLContext.getCapabilities().OpenGL15; + //#endif private static ThreadLocal bound = new ThreadLocal<>(); private static ThreadLocal mapped = new ThreadLocal<>(); @@ -39,12 +54,20 @@ public class PixelBufferObject { } this.size = size; - this.handle = arb ? ARBBufferObject.glGenBuffersARB() : glGenBuffers(); + //#if MC>=11300 + this.handle = arb ? ARBVertexBufferObject.glGenBuffersARB() : glGenBuffers(); + //#else + //$$ this.handle = arb ? ARBBufferObject.glGenBuffersARB() : glGenBuffers(); + //#endif bind(); if (arb) { - ARBBufferObject.glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size, usage.arb); + //#if MC>=11300 + ARBVertexBufferObject.glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size, usage.arb); + //#else + //$$ ARBBufferObject.glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size, usage.arb); + //#endif } else { glBufferData(GL_PIXEL_PACK_BUFFER, size, usage.gl15); } @@ -61,7 +84,11 @@ public class PixelBufferObject { public void bind() { if (arb) { - ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, getHandle()); + //#if MC>=11300 + ARBVertexBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, getHandle()); + //#else + //$$ ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, getHandle()); + //#endif } else { glBindBuffer(GL_PIXEL_PACK_BUFFER, getHandle()); } @@ -71,7 +98,11 @@ public class PixelBufferObject { public void unbind() { checkBound(); if (arb) { - ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); + //#if MC>=11300 + ARBVertexBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); + //#else + //$$ ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); + //#endif } else { glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); } @@ -95,13 +126,19 @@ public class PixelBufferObject { checkNotMapped(); ByteBuffer buffer; if (arb) { - buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB, size, null); + //#if MC>=11300 + buffer = ARBVertexBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB, size, null); + //#else + //$$ buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB, size, null); + //#endif } else { buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY, size, null); } - if (buffer == null) { - Util.checkGLError(); - } + //#if MC<11300 + //$$ if (buffer == null) { + //$$ Util.checkGLError(); + //$$ } + //#endif mapped.set(getHandle()); return buffer; } @@ -111,13 +148,19 @@ public class PixelBufferObject { checkNotMapped(); ByteBuffer buffer; if (arb) { - buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_WRITE_ONLY_ARB, size, null); + //#if MC>=11300 + buffer = ARBVertexBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_WRITE_ONLY_ARB, size, null); + //#else + //$$ buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_WRITE_ONLY_ARB, size, null); + //#endif } else { buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_WRITE_ONLY, size, null); } - if (buffer == null) { - Util.checkGLError(); - } + //#if MC<11300 + //$$ if (buffer == null) { + //$$ Util.checkGLError(); + //$$ } + //#endif mapped.set(getHandle()); return buffer; } @@ -127,13 +170,19 @@ public class PixelBufferObject { checkNotMapped(); ByteBuffer buffer; if (arb) { - buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_WRITE_ARB, size, null); + //#if MC>=11300 + buffer = ARBVertexBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_WRITE_ARB, size, null); + //#else + //$$ buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_WRITE_ARB, size, null); + //#endif } else { buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_WRITE, size, null); } - if (buffer == null) { - Util.checkGLError(); - } + //#if MC<11300 + //$$ if (buffer == null) { + //$$ Util.checkGLError(); + //$$ } + //#endif mapped.set(getHandle()); return buffer; } @@ -144,7 +193,11 @@ public class PixelBufferObject { throw new IllegalStateException("Buffer not mapped."); } if (arb) { - ARBBufferObject.glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); + //#if MC>=11300 + ARBVertexBufferObject.glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); + //#else + //$$ ARBBufferObject.glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); + //#endif } else { glUnmapBuffer(GL_PIXEL_PACK_BUFFER); } @@ -154,7 +207,11 @@ public class PixelBufferObject { public void delete() { if (handle != -1) { if (arb) { - ARBBufferObject.glDeleteBuffersARB(getHandle()); + //#if MC>=11300 + ARBVertexBufferObject.glDeleteBuffersARB(getHandle()); + //#else + //$$ ARBBufferObject.glDeleteBuffersARB(getHandle()); + //#endif } else { glDeleteBuffers(getHandle()); } @@ -167,12 +224,7 @@ public class PixelBufferObject { super.finalize(); if (handle != -1) { LogManager.getLogger().warn("PBO garbage collected before deleted!"); - Minecraft.getMinecraft().addScheduledTask(new Runnable() { - @Override - public void run() { - delete(); - } - }); + MCVer.getMinecraft().addScheduledTask(this::delete); } } } diff --git a/src/main/java/com/replaymod/render/utils/SoundHandler.java b/src/main/java/com/replaymod/render/utils/SoundHandler.java index c0ea3f1d..8959689d 100644 --- a/src/main/java/com/replaymod/render/utils/SoundHandler.java +++ b/src/main/java/com/replaymod/render/utils/SoundHandler.java @@ -1,6 +1,6 @@ package com.replaymod.render.utils; -import net.minecraft.client.Minecraft; +import com.replaymod.core.versions.MCVer; import net.minecraft.util.ResourceLocation; import org.apache.commons.io.IOUtils; @@ -24,7 +24,7 @@ public class SoundHandler { */ public void playSound(ResourceLocation loc) { try { - InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(loc).getInputStream(); + InputStream is = MCVer.getMinecraft().getResourceManager().getResource(loc).getInputStream(); byte[] bytes = IOUtils.toByteArray(is); is.close(); AudioInputStream ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(bytes)); diff --git a/src/main/resources/META-INF/replaymod_at.cfg b/src/main/resources/META-INF/replaymod_at.cfg index 53e91c8c..b1d4b8ff 100644 --- a/src/main/resources/META-INF/replaymod_at.cfg +++ b/src/main/resources/META-INF/replaymod_at.cfg @@ -74,15 +74,24 @@ public net.minecraft.client.renderer.EntityRenderer * public net.minecraft.client.renderer.EntityRenderer *() # RenderGlobal -public net.minecraft.client.renderer.RenderGlobal field_174995_M # renderDispatcher -public net.minecraft.client.renderer.RenderGlobal field_147595_R # displayListEntitiesDirty -public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdate -public net.minecraft.client.renderer.RenderGlobal field_72755_R # renderInfos -public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdate -public net.minecraft.client.renderer.RenderGlobal field_72740_G # renderEntitiesStartupCounter +##if MC>=11300 +public net.minecraft.client.renderer.WorldRenderer field_174995_M # renderDispatcher +public net.minecraft.client.renderer.WorldRenderer field_147595_R # displayListEntitiesDirty +public net.minecraft.client.renderer.WorldRenderer field_175009_l # chunksToUpdate +public net.minecraft.client.renderer.WorldRenderer field_72755_R # renderInfos +public net.minecraft.client.renderer.WorldRenderer field_175009_l # chunksToUpdate +public net.minecraft.client.renderer.WorldRenderer field_72740_G # renderEntitiesStartupCounter +##else +#$$ public net.minecraft.client.renderer.RenderGlobal field_174995_M # renderDispatcher +#$$ public net.minecraft.client.renderer.RenderGlobal field_147595_R # displayListEntitiesDirty +#$$ public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdate +#$$ public net.minecraft.client.renderer.RenderGlobal field_72755_R # renderInfos +#$$ public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdate +#$$ public net.minecraft.client.renderer.RenderGlobal field_72740_G # renderEntitiesStartupCounter ##if MC<10904 #$$ public net.minecraft.client.renderer.RenderGlobal func_174983_a(Lnet.minecraft.util.BlockPos;Lnet.minecraft.client.renderer.chunk.RenderChunk;)Z # isPositionInRenderChunk ##endif +##endif public net.minecraft.client.renderer.RenderGlobal$ContainerLocalRenderInformation #public net.minecraft.client.renderer.RenderGlobal$ContainerLocalRenderInformation field_178553_a # renderChunk @@ -120,7 +129,11 @@ public net.minecraft.server.integrated.IntegratedServer field_71348_o # isGamePa # GameSettings ##if MC>=10904 ##if MC>=11002 -public-f net.minecraft.client.settings.GameSettings field_186714_aM # soundLevels +##if MC>=11300 +public-f net.minecraft.client.GameSettings field_186714_aM # soundLevels +##else +#$$ public-f net.minecraft.client.settings.GameSettings field_186714_aM # soundLevels +##endif ##else #$$ public net.minecraft.client.settings.GameSettings field_186714_aM # soundLevels ##endif diff --git a/versions/common.gradle b/versions/common.gradle index f5756395..ad76535e 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -277,7 +277,6 @@ sourceSets { exclude 'com/replaymod/editor' exclude 'com/replaymod/extras' exclude 'com/replaymod/online' - exclude 'com/replaymod/render' exclude 'com/replaymod/restrictions' exclude 'com/replaymod/simplepathing' } From 1727702de4f32b1c19a7df4dfc2885ca4be2cee3 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 1 Mar 2019 15:20:59 +0100 Subject: [PATCH 39/65] Get simplepathing module compiling on 1.13 --- .../java/com/replaymod/core/ReplayMod.java | 2 + .../com/replaymod/core/versions/MCVer.java | 7 +++ .../simplepathing/ReplayModSimplePathing.java | 54 +++++++++---------- .../replaymod/simplepathing/SPTimeline.java | 7 ++- .../simplepathing/gui/GuiEditKeyframe.java | 9 +++- .../gui/GuiKeyframeTimeline.java | 12 ++--- .../simplepathing/gui/GuiPathing.java | 27 +++++----- .../simplepathing/preview/PathPreview.java | 21 +++++--- .../preview/PathPreviewRenderer.java | 14 +++-- versions/common.gradle | 1 - 10 files changed, 84 insertions(+), 70 deletions(-) diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index fbe56f50..fb14912a 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -11,6 +11,7 @@ import com.replaymod.recording.ReplayModRecording; import com.replaymod.render.ReplayModRender; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replaystudio.util.I18n; +import com.replaymod.simplepathing.ReplayModSimplePathing; import de.johni0702.minecraft.gui.container.GuiScreen; import lombok.Getter; import net.minecraft.client.Minecraft; @@ -165,6 +166,7 @@ public class ReplayMod implements Module { modules.add(new ReplayModRecording(this)); modules.add(new ReplayModReplay(this)); modules.add(new ReplayModRender(this)); + modules.add(new ReplayModSimplePathing(this)); //#if MC>=11300 settingsRegistry.register(); diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index 6213cac0..a73a7fd1 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -31,6 +31,7 @@ import net.minecraftforge.common.MinecraftForge; //#if MC>=11300 import net.minecraft.client.MainWindow; +import net.minecraft.client.util.InputMappings; import net.minecraft.crash.ReportedException; import org.lwjgl.glfw.GLFW; //#else @@ -709,6 +710,8 @@ public class MCVer { public static void colorLogicOp(int op) { GlStateManager.logicOp(op); } public static abstract class Keyboard { + public static final int KEY_LCONTROL = GLFW.GLFW_KEY_LEFT_CONTROL; + public static final int KEY_LSHIFT = GLFW.GLFW_KEY_LEFT_SHIFT; public static final int KEY_ESCAPE = GLFW.GLFW_KEY_ESCAPE; public static final int KEY_HOME = GLFW.GLFW_KEY_HOME; public static final int KEY_END = GLFW.GLFW_KEY_END; @@ -747,6 +750,10 @@ public class MCVer { public static final int KEY_X = GLFW.GLFW_KEY_X; public static final int KEY_Y = GLFW.GLFW_KEY_Y; public static final int KEY_Z = GLFW.GLFW_KEY_Z; + + public static boolean isKeyDown(int keyCode) { + return InputMappings.isKeyDown(keyCode); + } } //#endif } diff --git a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java index 3a558ceb..16ff409c 100644 --- a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java +++ b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java @@ -1,5 +1,7 @@ package com.replaymod.simplepathing; +import com.replaymod.core.KeyBindingRegistry; +import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.core.events.SettingsChangedEvent; import com.replaymod.replay.ReplayModReplay; @@ -17,19 +19,19 @@ import com.replaymod.simplepathing.gui.GuiPathing; import com.replaymod.simplepathing.preview.PathPreview; import lombok.Getter; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.lwjgl.input.Keyboard; -//#if MC>=10800 -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#if MC>=10800 +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; //#else -//$$ import cpw.mods.fml.common.Mod; -//$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif +//#endif import java.io.IOException; import java.util.Collections; @@ -41,38 +43,32 @@ import java.util.concurrent.atomic.AtomicInteger; import static com.replaymod.core.versions.MCVer.*; -@Mod(modid = ReplayModSimplePathing.MOD_ID, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", - //#if MC>=10800 - clientSideOnly = true, - //#endif - useMetadata = true) -public class ReplayModSimplePathing { - public static final String MOD_ID = "replaymod-simplepathing"; - - @Mod.Instance(MOD_ID) +public class ReplayModSimplePathing implements Module { + { instance = this; } public static ReplayModSimplePathing instance; private ReplayMod core; - public static Logger LOGGER; + public static Logger LOGGER = LogManager.getLogger(); private GuiPathing guiPathing; + private PathPreview pathPreview = new PathPreview(this); - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - LOGGER = event.getModLog(); - core = ReplayMod.instance; + public ReplayModSimplePathing(ReplayMod core) { + this.core = core; core.getSettingsRegistry().register(Setting.class); + } + @Override + public void initClient() { FML_BUS.register(this); - - PathPreview pathPreview = new PathPreview(this); pathPreview.register(); + } + @Override + public void registerKeyBindings(KeyBindingRegistry registry) { + pathPreview.registerKeyBindings(registry); core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.keyframerepository", Keyboard.KEY_X, () -> { if (guiPathing != null) guiPathing.keyframeRepoButtonPressed(); }); @@ -100,7 +96,7 @@ public class ReplayModSimplePathing { } } } catch (IOException e) { - throw new ReportedException(CrashReport.makeCrashReport(e, "Reading timeline")); + throw newReportedException(CrashReport.makeCrashReport(e, "Reading timeline")); } guiPathing = new GuiPathing(core, this, event.getReplayHandler()); @@ -226,7 +222,7 @@ public class ReplayModSimplePathing { timeline = serialization.deserialize(serialized).get(""); } catch (Throwable t) { CrashReport report = CrashReport.makeCrashReport(t, "Cloning timeline"); - throw new ReportedException(report); + throw newReportedException(report); } int id = lastSaveId.incrementAndGet(); diff --git a/src/main/java/com/replaymod/simplepathing/SPTimeline.java b/src/main/java/com/replaymod/simplepathing/SPTimeline.java index cb47cf4a..e0761d86 100644 --- a/src/main/java/com/replaymod/simplepathing/SPTimeline.java +++ b/src/main/java/com/replaymod/simplepathing/SPTimeline.java @@ -31,7 +31,6 @@ import com.replaymod.simplepathing.properties.ExplicitInterpolationProperty; import lombok.Getter; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; -import net.minecraft.util.ReportedException; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Triple; @@ -660,7 +659,7 @@ public class SPTimeline implements PathingRegistry { CrashReport crash = CrashReport.makeCrashReport(e, "Serializing interpolator"); CrashReportCategory category = crash.makeCategory("Serializing interpolator"); addDetail(category, "Interpolator", interpolator::toString); - throw new ReportedException(crash); + throw newReportedException(crash); } return baos.toString(); @@ -674,8 +673,8 @@ public class SPTimeline implements PathingRegistry { } catch (IOException e) { CrashReport crash = CrashReport.makeCrashReport(e, "De-serializing interpolator"); CrashReportCategory category = crash.makeCategory("De-serializing interpolator"); - category.addCrashSection("Interpolator", json); - throw new ReportedException(crash); + addDetail(category, "Interpolator", json::toString); + throw newReportedException(crash); } } } diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java b/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java index 1b9e8bc0..9bdd2e4c 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java @@ -33,16 +33,21 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import lombok.Getter; import net.minecraft.client.resources.I18n; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.ReadablePoint; import java.util.Map; import java.util.Optional; +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif + import static de.johni0702.minecraft.gui.utils.Utils.link; public abstract class GuiEditKeyframe> extends AbstractGuiPopup implements Typeable { diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java b/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java index d0285924..413cb501 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiKeyframeTimeline.java @@ -1,6 +1,7 @@ package com.replaymod.simplepathing.gui; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer; import com.replaymod.pathing.properties.CameraProperties; import com.replaymod.pathing.properties.SpectatorProperty; import com.replaymod.pathing.properties.TimestampProperty; @@ -16,11 +17,10 @@ import com.replaymod.simplepathing.SPTimeline.SPPath; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.element.advanced.AbstractGuiTimeline; import de.johni0702.minecraft.gui.function.Draggable; -import net.minecraft.client.Minecraft; import org.apache.commons.lang3.tuple.Pair; -import org.lwjgl.util.Point; -import org.lwjgl.util.ReadableDimension; -import org.lwjgl.util.ReadablePoint; +import de.johni0702.minecraft.gui.utils.lwjgl.Point; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import java.util.Comparator; import java.util.Optional; @@ -46,7 +46,7 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline=11300 +//#else +//$$ import org.lwjgl.input.Keyboard; //#if MC>=10800 -import net.minecraftforge.fml.common.Loader; +//$$ import net.minecraftforge.fml.common.Loader; //#else //$$ import cpw.mods.fml.common.Loader; //#endif +//#endif import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -211,15 +214,9 @@ public class GuiPathing { }).addElements(null, timelineTime, timeline, scrollbar, zoomButtonPanel); public final GuiPanel panel = new GuiPanel() - .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)); - - { - panel.addElements(new HorizontalLayout.Data(0.5), playPauseButton); - if (Loader.isModLoaded("replaymod-render")) { - panel.addElements(new HorizontalLayout.Data(0.5), renderButton); - } - panel.addElements(new HorizontalLayout.Data(0.5), positionKeyframeButton, timeKeyframeButton, timelinePanel); - } + .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)) + .addElements(new HorizontalLayout.Data(0.5), + playPauseButton, renderButton, positionKeyframeButton, timeKeyframeButton, timelinePanel); /** * IGuiClickable dummy component that is inserted at a high level. diff --git a/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java b/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java index 9608c34b..578c5f9c 100644 --- a/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java +++ b/src/main/java/com/replaymod/simplepathing/preview/PathPreview.java @@ -1,6 +1,6 @@ package com.replaymod.simplepathing.preview; -import com.replaymod.core.ReplayMod; +import com.replaymod.core.KeyBindingRegistry; import com.replaymod.core.SettingsRegistry; import com.replaymod.core.events.SettingsChangedEvent; import com.replaymod.replay.ReplayHandler; @@ -8,13 +8,17 @@ import com.replaymod.replay.events.ReplayCloseEvent; import com.replaymod.replay.events.ReplayOpenEvent; import com.replaymod.simplepathing.ReplayModSimplePathing; import com.replaymod.simplepathing.Setting; -import org.lwjgl.input.Keyboard; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import org.lwjgl.input.Keyboard; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif +//#endif import static com.replaymod.core.versions.MCVer.*; @@ -30,12 +34,13 @@ public class PathPreview { public void register() { FML_BUS.register(this); + } - ReplayMod core = mod.getCore(); - mod.getCore().getKeyBindingRegistry().registerKeyBinding("replaymod.input.pathpreview", Keyboard.KEY_H, () -> { - SettingsRegistry registry = core.getSettingsRegistry(); - registry.set(Setting.PATH_PREVIEW, !registry.get(Setting.PATH_PREVIEW)); - registry.save(); + public void registerKeyBindings(KeyBindingRegistry registry) { + registry.registerKeyBinding("replaymod.input.pathpreview", Keyboard.KEY_H, () -> { + SettingsRegistry settings = mod.getCore().getSettingsRegistry(); + settings.set(Setting.PATH_PREVIEW, !settings.get(Setting.PATH_PREVIEW)); + settings.save(); }); } diff --git a/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java b/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java index 062d46d6..619f01a5 100644 --- a/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java +++ b/src/main/java/com/replaymod/simplepathing/preview/PathPreviewRenderer.java @@ -1,6 +1,7 @@ package com.replaymod.simplepathing.preview; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer; import com.replaymod.pathing.properties.CameraProperties; import com.replaymod.pathing.properties.SpectatorProperty; import com.replaymod.pathing.properties.TimestampProperty; @@ -15,7 +16,6 @@ import com.replaymod.simplepathing.ReplayModSimplePathing; import com.replaymod.simplepathing.SPTimeline; import com.replaymod.simplepathing.gui.GuiPathing; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.event.RenderWorldLastEvent; @@ -26,7 +26,11 @@ import org.lwjgl.opengl.GL11; //#if MC>=10800 import net.minecraft.client.renderer.GlStateManager; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import com.replaymod.core.versions.MCVer.GlStateManager; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -40,7 +44,7 @@ import static com.replaymod.core.versions.MCVer.*; public class PathPreviewRenderer { private static final ResourceLocation CAMERA_HEAD = new ResourceLocation("replaymod", "camera_head.png"); - private static final Minecraft mc = Minecraft.getMinecraft(); + private static final Minecraft mc = MCVer.getMinecraft(); private static final int SLOW_PATH_COLOR = 0xffcccc; private static final int FAST_PATH_COLOR = 0x660000; @@ -239,7 +243,7 @@ public class PathPreviewRenderer { Keyframe keyframe) { BufferBuilder_setTranslation(0, 0, 0); - mc.renderEngine.bindTexture(TEXTURE); + MCVer.bindTexture(TEXTURE); float posX = 80f / ReplayMod.TEXTURE_SIZE; float posY = 0f; @@ -286,7 +290,7 @@ public class PathPreviewRenderer { Triple rot) { BufferBuilder_setTranslation(0, 0, 0); - mc.renderEngine.bindTexture(CAMERA_HEAD); + MCVer.bindTexture(CAMERA_HEAD); GL11.glPushMatrix(); diff --git a/versions/common.gradle b/versions/common.gradle index ad76535e..9c2120e1 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -278,7 +278,6 @@ sourceSets { exclude 'com/replaymod/extras' exclude 'com/replaymod/online' exclude 'com/replaymod/restrictions' - exclude 'com/replaymod/simplepathing' } } } From cae970ec6bd320c0e514e2cedd1bd56d7c039780 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 1 Mar 2019 15:52:06 +0100 Subject: [PATCH 40/65] Get online module compiling on 1.13 --- .../java/com/replaymod/core/ReplayMod.java | 5 +- .../replaymod/online/AuthenticationHash.java | 4 +- .../online/ConfigurationAuthData.java | 128 +++++++++--------- .../replaymod/online/PlainFileAuthData.java | 68 ++++++++++ .../com/replaymod/online/ReplayModOnline.java | 94 +++++++------ .../com/replaymod/online/api/ApiClient.java | 3 +- .../api/replay/holders/MinecraftVersion.java | 3 +- .../com/replaymod/online/gui/GuiRegister.java | 5 +- .../replaymod/online/gui/GuiReplayCenter.java | 6 +- .../online/gui/GuiReplayCenterSearch.java | 9 +- .../replaymod/online/gui/GuiUploadReplay.java | 16 ++- .../replaymod/online/handler/GuiHandler.java | 18 ++- versions/common.gradle | 1 - 13 files changed, 236 insertions(+), 124 deletions(-) create mode 100644 src/main/java/com/replaymod/online/PlainFileAuthData.java diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index fb14912a..8075204e 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -7,6 +7,7 @@ import com.replaymod.core.gui.RestoreReplayGui; import com.replaymod.core.handler.MainMenuHandler; import com.replaymod.core.utils.OpenGLUtils; import com.replaymod.core.versions.MCVer; +import com.replaymod.online.ReplayModOnline; import com.replaymod.recording.ReplayModRecording; import com.replaymod.render.ReplayModRender; import com.replaymod.replay.ReplayModReplay; @@ -164,7 +165,9 @@ public class ReplayMod implements Module { // Register all RM modules modules.add(this); modules.add(new ReplayModRecording(this)); - modules.add(new ReplayModReplay(this)); + ReplayModReplay replayModule = new ReplayModReplay(this); + modules.add(replayModule); + modules.add(new ReplayModOnline(this, replayModule)); modules.add(new ReplayModRender(this)); modules.add(new ReplayModSimplePathing(this)); diff --git a/src/main/java/com/replaymod/online/AuthenticationHash.java b/src/main/java/com/replaymod/online/AuthenticationHash.java index 0b212ef7..1aac201f 100644 --- a/src/main/java/com/replaymod/online/AuthenticationHash.java +++ b/src/main/java/com/replaymod/online/AuthenticationHash.java @@ -1,6 +1,6 @@ package com.replaymod.online; -import net.minecraft.client.Minecraft; +import com.replaymod.core.versions.MCVer; import java.util.Random; @@ -9,7 +9,7 @@ public class AuthenticationHash { private static final Random random = new Random(); public AuthenticationHash() { - username = Minecraft.getMinecraft().getSession().getUsername(); + username = MCVer.getMinecraft().getSession().getUsername(); currentTime = System.currentTimeMillis(); randomLong = random.nextLong(); hash = getAuthenticationHash(); diff --git a/src/main/java/com/replaymod/online/ConfigurationAuthData.java b/src/main/java/com/replaymod/online/ConfigurationAuthData.java index d13d6cab..d866b972 100644 --- a/src/main/java/com/replaymod/online/ConfigurationAuthData.java +++ b/src/main/java/com/replaymod/online/ConfigurationAuthData.java @@ -1,63 +1,65 @@ -package com.replaymod.online; - -import com.replaymod.online.api.ApiClient; -import com.replaymod.online.api.AuthData; -import com.replaymod.online.api.replay.holders.AuthConfirmation; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.common.config.Property; - -/** - * Auth data stored in a {@link Configuration}. - */ -public class ConfigurationAuthData implements AuthData { - - private final Configuration config; - private String userName; - private String authKey; - - public ConfigurationAuthData(Configuration config) { - this.config = config; - } - - /** - * Loads the data from the configuration and checks for its validity. - * If the data is invalid, it is removed from the config. - * @param apiClient Api client used for validating the auth data - */ - public void load(ApiClient apiClient) { - Property property = config.get("authkey", "authkey", (String) null); - if (property != null) { - String authKey = property.getString(); - AuthConfirmation result = apiClient.checkAuthkey(authKey); - if (result != null) { - this.authKey = authKey; - this.userName = result.getUsername(); - } else { - setData(null, null); - } - } - } - - @Override - public String getUserName() { - return userName; - } - - @Override - public String getAuthKey() { - return authKey; - } - - @Override - public void setData(String userName, String authKey) { - this.userName = userName; - this.authKey = authKey; - - config.removeCategory(config.getCategory("authkey")); - if (authKey != null) { - // Note: .get() actually creates the entry with the default value if it doesn't exist - config.get("authkey", "authkey", authKey); - } - config.save(); - } -} +//#if MC<11300 +//$$ package com.replaymod.online; +//$$ +//$$ import com.replaymod.online.api.ApiClient; +//$$ import com.replaymod.online.api.AuthData; +//$$ import com.replaymod.online.api.replay.holders.AuthConfirmation; +//$$ import net.minecraftforge.common.config.Configuration; +//$$ import net.minecraftforge.common.config.Property; +//$$ +//$$ /** +//$$ * Auth data stored in a {@link Configuration}. +//$$ */ +//$$ public class ConfigurationAuthData implements AuthData { +//$$ +//$$ private final Configuration config; +//$$ private String userName; +//$$ private String authKey; +//$$ +//$$ public ConfigurationAuthData(Configuration config) { +//$$ this.config = config; +//$$ } +//$$ +//$$ /** +//$$ * Loads the data from the configuration and checks for its validity. +//$$ * If the data is invalid, it is removed from the config. +//$$ * @param apiClient Api client used for validating the auth data +//$$ */ +//$$ public void load(ApiClient apiClient) { +//$$ Property property = config.get("authkey", "authkey", (String) null); +//$$ if (property != null) { +//$$ String authKey = property.getString(); +//$$ AuthConfirmation result = apiClient.checkAuthkey(authKey); +//$$ if (result != null) { +//$$ this.authKey = authKey; +//$$ this.userName = result.getUsername(); +//$$ } else { +//$$ setData(null, null); +//$$ } +//$$ } +//$$ } +//$$ +//$$ @Override +//$$ public String getUserName() { +//$$ return userName; +//$$ } +//$$ +//$$ @Override +//$$ public String getAuthKey() { +//$$ return authKey; +//$$ } +//$$ +//$$ @Override +//$$ public void setData(String userName, String authKey) { +//$$ this.userName = userName; +//$$ this.authKey = authKey; +//$$ +//$$ config.removeCategory(config.getCategory("authkey")); +//$$ if (authKey != null) { +//$$ // Note: .get() actually creates the entry with the default value if it doesn't exist +//$$ config.get("authkey", "authkey", authKey); +//$$ } +//$$ config.save(); +//$$ } +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/online/PlainFileAuthData.java b/src/main/java/com/replaymod/online/PlainFileAuthData.java new file mode 100644 index 00000000..b237d2a4 --- /dev/null +++ b/src/main/java/com/replaymod/online/PlainFileAuthData.java @@ -0,0 +1,68 @@ +package com.replaymod.online; + +import com.replaymod.online.api.ApiClient; +import com.replaymod.online.api.AuthData; +import com.replaymod.online.api.replay.holders.AuthConfirmation; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * Auth data stored in a plain text file. + */ +public class PlainFileAuthData implements AuthData { + private final Path path; + private String userName; + private String authKey; + + public PlainFileAuthData(Path path) { + this.path = path; + } + + /** + * Loads the data from the JSON file and checks for its validity. + * If the data is invalid, the JSON file is removed. + * @param apiClient Api client used for validating the auth data + */ + public void load(ApiClient apiClient) throws IOException { + String authKey; + try { + authKey = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + } catch (FileNotFoundException ignored) { + return; + } + AuthConfirmation result = apiClient.checkAuthkey(authKey); + if (result != null) { + this.authKey = authKey; + this.userName = result.getUsername(); + } else { + Files.deleteIfExists(path); + } + } + + @Override + public String getUserName() { + return userName; + } + + @Override + public String getAuthKey() { + return authKey; + } + + @Override + public void setData(String userName, String authKey) { + this.userName = userName; + this.authKey = authKey; + + try { + Files.write(path, authKey.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE_NEW); + } catch (IOException e) { + ReplayModOnline.LOGGER.error("Saving auth data:", e); + } + } +} diff --git a/src/main/java/com/replaymod/online/ReplayModOnline.java b/src/main/java/com/replaymod/online/ReplayModOnline.java index badc3a46..059a3a35 100644 --- a/src/main/java/com/replaymod/online/ReplayModOnline.java +++ b/src/main/java/com/replaymod/online/ReplayModOnline.java @@ -1,7 +1,10 @@ package com.replaymod.online; +import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer; import com.replaymod.online.api.ApiClient; +import com.replaymod.online.api.AuthData; import com.replaymod.online.gui.GuiLoginPrompt; import com.replaymod.online.gui.GuiReplayDownloading; import com.replaymod.online.gui.GuiSaveModifiedReplay; @@ -12,48 +15,36 @@ import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ZipReplayFile; import com.replaymod.replaystudio.studio.ReplayStudio; import de.johni0702.minecraft.gui.container.GuiScreen; -import net.minecraftforge.common.config.Configuration; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; //#if MC>=10800 -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.common.config.Configuration; +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else -//$$ import cpw.mods.fml.common.Mod; -//$$ import cpw.mods.fml.common.event.FMLInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPostInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import static com.replaymod.core.versions.MCVer.*; -import static net.minecraft.client.Minecraft.getMinecraft; -@Mod(modid = ReplayModOnline.MOD_ID, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", - //#if MC>=10800 - clientSideOnly = true, - //#endif - useMetadata = true) -public class ReplayModOnline { - public static final String MOD_ID = "replaymod-online"; - - @Mod.Instance(MOD_ID) +public class ReplayModOnline implements Module { + { instance = this; } public static ReplayModOnline instance; private ReplayMod core; private ReplayModReplay replayModule; - public static Logger LOGGER; + public static Logger LOGGER = LogManager.getLogger(); private ApiClient apiClient; @@ -64,22 +55,31 @@ public class ReplayModOnline { */ private File currentReplayOutputFile; - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - LOGGER = event.getModLog(); - core = ReplayMod.instance; - replayModule = ReplayModReplay.instance; + public ReplayModOnline(ReplayMod core, ReplayModReplay replayModule) { + this.core = core; + this.replayModule = replayModule; core.getSettingsRegistry().register(Setting.class); - - Configuration config = new Configuration(event.getSuggestedConfigurationFile()); - ConfigurationAuthData authData = new ConfigurationAuthData(config); - apiClient = new ApiClient(authData); - authData.load(apiClient); } - @Mod.EventHandler - public void init(FMLInitializationEvent event) { + @Override + public void initClient() { + Path path = MCVer.mcDataDir(MCVer.getMinecraft()).toPath().resolve("config/replaymod-online.token"); + PlainFileAuthData authData = new PlainFileAuthData(path); + apiClient = new ApiClient(authData); + if (Files.notExists(path)) { + AuthData oldAuthData = loadOldAuthData(); + if (oldAuthData != null) { + authData.setData(oldAuthData.getUserName(), oldAuthData.getAuthKey()); + } + } else { + try { + authData.load(apiClient); + } catch (IOException e) { + ReplayModOnline.LOGGER.error("Loading auth data:", e); + } + } + if (!getDownloadsFolder().exists()){ if (!getDownloadsFolder().mkdirs()) { LOGGER.warn("Failed to create downloads folder: " + getDownloadsFolder()); @@ -88,10 +88,7 @@ public class ReplayModOnline { new GuiHandler(this).register(); FML_BUS.register(this); - } - @Mod.EventHandler - public void postInit(FMLPostInitializationEvent event) { // Initial login prompt if (!core.getSettingsRegistry().get(Setting.SKIP_LOGIN_PROMPT)) { if (!isLoggedIn()) { @@ -103,6 +100,23 @@ public class ReplayModOnline { } } + /** + * Loads old auth data from the legacy Configuration system and stores it in the new json file. + * Always returns null on 1.13+ where the Configuration system has been removed. + */ + private AuthData loadOldAuthData() { + //#if MC<11300 + //$$ Path path = MCVer.mcDataDir(MCVer.getMinecraft()).toPath().resolve("config/replaymod-online.cfg"); + //$$ Configuration config = new Configuration(path.toFile()); + //$$ ConfigurationAuthData authData = new ConfigurationAuthData(config); + //$$ authData.load(new ApiClient(authData)); + //$$ if (authData.getAuthKey() != null) { + //$$ return authData; + //$$ } + //#endif + return null; + } + public ReplayMod getCore() { return core; } @@ -125,7 +139,7 @@ public class ReplayModOnline { public File getDownloadsFolder() { String path = core.getSettingsRegistry().get(Setting.DOWNLOAD_PATH); - return new File(path.startsWith("./") ? getMinecraft().mcDataDir : null, path); + return new File(path.startsWith("./") ? mcDataDir(getMinecraft()) : null, path); } public File getDownloadedFile(int id) { diff --git a/src/main/java/com/replaymod/online/api/ApiClient.java b/src/main/java/com/replaymod/online/api/ApiClient.java index a12cd822..0a351c37 100755 --- a/src/main/java/com/replaymod/online/api/ApiClient.java +++ b/src/main/java/com/replaymod/online/api/ApiClient.java @@ -6,6 +6,7 @@ import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.mojang.authlib.exceptions.AuthenticationException; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer; import com.replaymod.online.AuthenticationHash; import com.replaymod.online.api.replay.ReplayModApiMethods; import com.replaymod.online.api.replay.SearchQuery; @@ -30,7 +31,7 @@ import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; public class ApiClient { - private static final Minecraft mc = Minecraft.getMinecraft(); + private static final Minecraft mc = MCVer.getMinecraft(); private static final Gson gson = new Gson(); private static final JsonParser jsonParser = new JsonParser(); diff --git a/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java b/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java index 7320efb2..2a3216eb 100644 --- a/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java +++ b/src/main/java/com/replaymod/online/api/replay/holders/MinecraftVersion.java @@ -9,7 +9,8 @@ public enum MinecraftVersion { MC_1_11_2("Minecraft 1.11.2", "1.11.2"), MC_1_12("Minecraft 1.12", "1.12"), MC_1_12_1("Minecraft 1.12.1", "1.12.1"), - MC_1_12_2("Minecraft 1.12.2", "1.12.2"); + MC_1_12_2("Minecraft 1.12.2", "1.12.2"), + MC_1_13_2("Minecraft 1.13.2", "1.13.2"); private String niceName, apiName; diff --git a/src/main/java/com/replaymod/online/gui/GuiRegister.java b/src/main/java/com/replaymod/online/gui/GuiRegister.java index 81e4bcc4..610f6f8d 100644 --- a/src/main/java/com/replaymod/online/gui/GuiRegister.java +++ b/src/main/java/com/replaymod/online/gui/GuiRegister.java @@ -1,7 +1,6 @@ package com.replaymod.online.gui; import com.mojang.authlib.exceptions.AuthenticationException; -import com.replaymod.core.versions.MCVer; import com.replaymod.online.api.ApiClient; import com.replaymod.online.api.ApiException; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; @@ -13,9 +12,9 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.utils.Consumers; import de.johni0702.minecraft.gui.utils.Utils; import com.replaymod.core.utils.Patterns; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; import net.minecraft.client.gui.FontRenderer; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableColor; import static com.replaymod.core.versions.MCVer.*; diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java index fe26f083..29999215 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java @@ -37,9 +37,9 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import org.apache.commons.lang3.StringUtils; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; import java.awt.image.BufferedImage; import java.io.IOException; @@ -462,7 +462,7 @@ public class GuiReplayCenter extends GuiScreen { @Override public ReadableDimension calcMinSize(GuiContainer container) { - return new org.lwjgl.util.Dimension(300, thumbnail.getMinSize().getHeight()); + return new Dimension(300, thumbnail.getMinSize().getHeight()); } }); } diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayCenterSearch.java b/src/main/java/com/replaymod/online/gui/GuiReplayCenterSearch.java index 9a39e54d..00f6b017 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayCenterSearch.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayCenterSearch.java @@ -15,9 +15,14 @@ import de.johni0702.minecraft.gui.function.Typeable; import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.utils.Colors; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import net.minecraft.client.resources.I18n; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.ReadablePoint; + +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java index 3ceb46d6..d95b2d30 100644 --- a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java +++ b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java @@ -33,13 +33,16 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import net.minecraft.client.resources.I18n; -import net.minecraft.client.settings.GameSettings; import net.minecraft.client.settings.KeyBinding; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +//#if MC>=11300 +//#else +//$$ import net.minecraft.client.settings.GameSettings; +//#endif + import javax.annotation.Nullable; import java.awt.image.BufferedImage; import java.io.File; @@ -47,6 +50,7 @@ import java.io.IOException; import java.util.Set; import java.util.stream.Collectors; +import static com.replaymod.core.versions.MCVer.newReportedException; import static java.util.Arrays.stream; public class GuiUploadReplay extends GuiScreen { @@ -130,7 +134,7 @@ public class GuiUploadReplay extends GuiScreen { metaData = replayFile.getMetaData(); optThumbnail = replayFile.getThumb(); } catch (IOException e) { - throw new ReportedException(CrashReport.makeCrashReport(e, "Read replay file " + file.getName())); + throw newReportedException(CrashReport.makeCrashReport(e, "Read replay file " + file.getName())); } // Apply to gui @@ -147,7 +151,11 @@ public class GuiUploadReplay extends GuiScreen { // Get name of key used for thumbnail creation KeyBindingRegistry registry = mod.getCore().getKeyBindingRegistry(); KeyBinding keyBinding = registry.getKeyBindings().get("replaymod.input.thumbnail"); - String keyName = keyBinding == null ? "???" : GameSettings.getKeyDisplayString(keyBinding.getKeyCode()); + //#if MC>=11300 + String keyName = keyBinding == null ? "???" : keyBinding.func_197978_k(); + //#else + //$$ String keyName = keyBinding == null ? "???" : GameSettings.getKeyDisplayString(keyBinding.getKeyCode()); + //#endif // No thumbnail, show default thumbnail and hint on how to create one thumbnail.setTexture(Utils.DEFAULT_THUMBNAIL); diff --git a/src/main/java/com/replaymod/online/handler/GuiHandler.java b/src/main/java/com/replaymod/online/handler/GuiHandler.java index 34363760..7ac54ca5 100644 --- a/src/main/java/com/replaymod/online/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/online/handler/GuiHandler.java @@ -15,7 +15,12 @@ import net.minecraft.client.resources.I18n; import net.minecraftforge.client.event.GuiScreenEvent; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +import java.util.ArrayList; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -45,8 +50,15 @@ public class GuiHandler { } GuiButton button = new GuiButton(BUTTON_REPLAY_CENTER, getGui(event).width / 2 - 100, - getGui(event).height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replaycenter")); - getButtonList(event).add(button); + getGui(event).height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replaycenter")) { + //#if MC>=11300 + @Override + public void onClick(double mouseX, double mouseY) { + onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>())); + } + //#endif + }; + addButton(event, button); } @SubscribeEvent diff --git a/versions/common.gradle b/versions/common.gradle index 9c2120e1..0b6c691a 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -276,7 +276,6 @@ sourceSets { exclude 'com/replaymod/compat' exclude 'com/replaymod/editor' exclude 'com/replaymod/extras' - exclude 'com/replaymod/online' exclude 'com/replaymod/restrictions' } } From c8cf4d66c599732fdae463691606b6ef7fd6ec82 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 1 Mar 2019 16:07:34 +0100 Subject: [PATCH 41/65] Get editor module compiling on 1.13 --- .../java/com/replaymod/core/ReplayMod.java | 2 + .../com/replaymod/editor/ReplayModEditor.java | 38 +++++-------------- .../replaymod/editor/gui/GuiReplayEditor.java | 2 +- .../replaymod/editor/gui/GuiTrimPanel.java | 2 +- .../replaymod/editor/handler/GuiHandler.java | 18 +++++++-- versions/common.gradle | 1 - 6 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 8075204e..6d6bc794 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -7,6 +7,7 @@ import com.replaymod.core.gui.RestoreReplayGui; import com.replaymod.core.handler.MainMenuHandler; import com.replaymod.core.utils.OpenGLUtils; import com.replaymod.core.versions.MCVer; +import com.replaymod.editor.ReplayModEditor; import com.replaymod.online.ReplayModOnline; import com.replaymod.recording.ReplayModRecording; import com.replaymod.render.ReplayModRender; @@ -170,6 +171,7 @@ public class ReplayMod implements Module { modules.add(new ReplayModOnline(this, replayModule)); modules.add(new ReplayModRender(this)); modules.add(new ReplayModSimplePathing(this)); + modules.add(new ReplayModEditor(this)); //#if MC>=11300 settingsRegistry.register(); diff --git a/src/main/java/com/replaymod/editor/ReplayModEditor.java b/src/main/java/com/replaymod/editor/ReplayModEditor.java index 7756dbd6..1b4dd02b 100644 --- a/src/main/java/com/replaymod/editor/ReplayModEditor.java +++ b/src/main/java/com/replaymod/editor/ReplayModEditor.java @@ -1,48 +1,28 @@ package com.replaymod.editor; +import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.editor.handler.GuiHandler; import com.replaymod.online.Setting; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -//#if MC>=10800 -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -//#else -//$$ import cpw.mods.fml.common.Mod; -//$$ import cpw.mods.fml.common.event.FMLInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; -//#endif - -@Mod(modid = ReplayModEditor.MOD_ID, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", - //#if MC>=10800 - clientSideOnly = true, - //#endif - useMetadata = true) -public class ReplayModEditor { - public static final String MOD_ID = "replaymod-editor"; - - @Mod.Instance(MOD_ID) +public class ReplayModEditor implements Module { + { instance = this; } public static ReplayModEditor instance; private ReplayMod core; - public static Logger LOGGER; + public static Logger LOGGER = LogManager.getLogger(); - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - ReplayModEditor.LOGGER = event.getModLog(); - core = ReplayMod.instance; + public ReplayModEditor(ReplayMod core) { + this.core = core; core.getSettingsRegistry().register(Setting.class); } - @Mod.EventHandler - public void init(FMLInitializationEvent event) { + @Override + public void initClient() { new GuiHandler(this).register(); } diff --git a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java index 359c3164..d9d5c7ae 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java +++ b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java @@ -34,10 +34,10 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.crash.CrashReport; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; -import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; import java.io.File; diff --git a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java index 4b15de62..975124ab 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java +++ b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java @@ -22,10 +22,10 @@ import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu; import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; import org.apache.commons.lang3.tuple.Pair; -import org.lwjgl.util.Dimension; import javax.annotation.Nullable; import java.io.File; diff --git a/src/main/java/com/replaymod/editor/handler/GuiHandler.java b/src/main/java/com/replaymod/editor/handler/GuiHandler.java index f0fab8f1..0a673790 100644 --- a/src/main/java/com/replaymod/editor/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/editor/handler/GuiHandler.java @@ -9,7 +9,12 @@ import net.minecraft.client.resources.I18n; import net.minecraftforge.client.event.GuiScreenEvent; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +import java.util.ArrayList; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -37,9 +42,16 @@ public class GuiHandler { } GuiButton button = new GuiButton(BUTTON_REPLAY_EDITOR, getGui(event).width / 2 + 2, - getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayeditor")); + getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayeditor")) { + //#if MC>=11300 + @Override + public void onClick(double mouseX, double mouseY) { + onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>())); + } + //#endif + }; button.width = button.width / 2 - 2; - getButtonList(event).add(button); + addButton(event, button); } @SubscribeEvent diff --git a/versions/common.gradle b/versions/common.gradle index 0b6c691a..94924e65 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -274,7 +274,6 @@ sourceSets { main { java { exclude 'com/replaymod/compat' - exclude 'com/replaymod/editor' exclude 'com/replaymod/extras' exclude 'com/replaymod/restrictions' } From f13f2dda1c931c48c5969580dfe12d1e2f58bd3a Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 1 Mar 2019 17:18:22 +0100 Subject: [PATCH 42/65] Get extras module compiling on 1.13 Remove LocalizationExtra (too much update work for too little gain) --- .../java/com/replaymod/core/ReplayMod.java | 2 + .../com/replaymod/extras/FullBrightness.java | 28 +++- .../com/replaymod/extras/HotkeyButtons.java | 22 ++- .../replaymod/extras/LocalizationExtra.java | 134 ------------------ .../com/replaymod/extras/OpenEyeExtra.java | 19 ++- .../com/replaymod/extras/ReplayModExtras.java | 39 ++--- .../AdvancedScreenshots.java | 42 ++++-- .../GuiCreateScreenshot.java | 11 +- .../GuiUploadScreenshot.java | 2 +- .../ScreenshotRenderer.java | 23 ++- .../advancedscreenshots/ScreenshotWriter.java | 6 +- .../extras/playeroverview/PlayerOverview.java | 12 +- .../playeroverview/PlayerOverviewGui.java | 36 ++--- .../extras/urischeme/LinuxUriScheme.java | 3 +- .../replaymod/extras/urischeme/UriScheme.java | 18 +++ .../extras/urischeme/WindowsUriScheme.java | 3 +- .../extras/youtube/GuiYoutubeUpload.java | 15 +- .../extras/youtube/YoutubeUpload.java | 6 +- .../extras/youtube/YoutubeUploader.java | 3 +- .../render/gui/GuiRenderSettings.java | 10 +- versions/common.gradle | 1 - 21 files changed, 184 insertions(+), 251 deletions(-) delete mode 100644 src/main/java/com/replaymod/extras/LocalizationExtra.java diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 6d6bc794..b00570f7 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -8,6 +8,7 @@ import com.replaymod.core.handler.MainMenuHandler; import com.replaymod.core.utils.OpenGLUtils; import com.replaymod.core.versions.MCVer; import com.replaymod.editor.ReplayModEditor; +import com.replaymod.extras.ReplayModExtras; import com.replaymod.online.ReplayModOnline; import com.replaymod.recording.ReplayModRecording; import com.replaymod.render.ReplayModRender; @@ -172,6 +173,7 @@ public class ReplayMod implements Module { modules.add(new ReplayModRender(this)); modules.add(new ReplayModSimplePathing(this)); modules.add(new ReplayModEditor(this)); + modules.add(new ReplayModExtras(this)); //#if MC>=11300 settingsRegistry.register(); diff --git a/src/main/java/com/replaymod/extras/FullBrightness.java b/src/main/java/com/replaymod/extras/FullBrightness.java index a1610370..d129e492 100644 --- a/src/main/java/com/replaymod/extras/FullBrightness.java +++ b/src/main/java/com/replaymod/extras/FullBrightness.java @@ -8,11 +8,21 @@ import com.replaymod.replay.gui.overlay.GuiReplayOverlay; import de.johni0702.minecraft.gui.element.GuiImage; import de.johni0702.minecraft.gui.element.IGuiImage; import de.johni0702.minecraft.gui.layout.HorizontalLayout; -import net.minecraft.client.settings.GameSettings; -import org.lwjgl.input.Keyboard; + +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +import net.minecraft.client.GameSettings; +//#else +//$$ import net.minecraft.client.settings.GameSettings; +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif import net.minecraftforge.fml.common.gameevent.TickEvent; //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; @@ -28,7 +38,11 @@ public class FullBrightness implements Extra { private GameSettings gameSettings; private boolean active; - private float originalGamma; + //#if MC>=11300 + private double originalGamma; + //#else + //$$ private float originalGamma; + //#endif @Override public void register(final ReplayMod mod) throws Exception { @@ -39,7 +53,11 @@ public class FullBrightness implements Extra { @Override public void run() { active = !active; - mod.getMinecraft().entityRenderer.lightmapUpdateNeeded = true; + //#if MC>=11300 + // FIXME check if this is no longer required (especially when replay is paused) + //#else + //$$ mod.getMinecraft().entityRenderer.lightmapUpdateNeeded = true; + //#endif ReplayHandler replayHandler = module.getReplayHandler(); if (replayHandler != null) { updateIndicator(replayHandler.getOverlay()); diff --git a/src/main/java/com/replaymod/extras/HotkeyButtons.java b/src/main/java/com/replaymod/extras/HotkeyButtons.java index 4f24c558..2275854e 100644 --- a/src/main/java/com/replaymod/extras/HotkeyButtons.java +++ b/src/main/java/com/replaymod/extras/HotkeyButtons.java @@ -16,13 +16,21 @@ import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.LayoutData; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.client.resources.I18n; -import org.lwjgl.input.Keyboard; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; + +//#if MC>=11300 +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -89,7 +97,11 @@ public class HotkeyButtons implements Extra { // There doesn't seem to be an KeyBindingUpdate event, so we'll just update it every time String keyName = "???"; try { - keyName = Keyboard.getKeyName(keyBinding.getKeyCode()); + //#if MC>=11300 + keyName = keyBinding.func_197978_k(); + //#else + //$$ keyName = Keyboard.getKeyName(keyBinding.getKeyCode()); + //#endif } catch (ArrayIndexOutOfBoundsException e) { // Apparently windows likes to press strange keys, see https://www.replaymod.com/forum/thread/55 } diff --git a/src/main/java/com/replaymod/extras/LocalizationExtra.java b/src/main/java/com/replaymod/extras/LocalizationExtra.java deleted file mode 100644 index 13f4d1d2..00000000 --- a/src/main/java/com/replaymod/extras/LocalizationExtra.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.replaymod.extras; - -import com.google.common.collect.ImmutableSet; -import com.replaymod.compat.optifine.OptifineReflection; -import com.replaymod.core.ReplayMod; -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.IResourcePack; -import net.minecraft.client.resources.data.IMetadataSection; -import net.minecraft.util.ResourceLocation; -import org.apache.commons.io.IOUtils; - -//#if MC>=10904 -import net.minecraft.client.resources.data.MetadataSerializer; -//#else -//$$ import net.minecraft.client.resources.data.IMetadataSerializer; -//#endif - -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import static com.replaymod.extras.ReplayModExtras.LOGGER; - -public class LocalizationExtra implements Extra { - private static final String ZIP_FILE_URL = "https://github.com/ReplayMod/Translations/archive/master.zip"; - private static final String LANG_PREFIX = "Translations-master/"; - - @Override - public void register(ReplayMod mod) throws Exception { - final Minecraft mc = mod.getMinecraft(); - if (Boolean.parseBoolean(System.getProperty("replaymod.offline", "false"))) { - return; - } - Thread localizedResourcePackLoader = new Thread(() -> { - try { - // Download zip of lang files - LOGGER.debug("Downloading languages from {}", ZIP_FILE_URL); - Map languages = new HashMap<>(); - try (InputStream urlIn = new URL(ZIP_FILE_URL).openStream(); - ZipInputStream in = new ZipInputStream(urlIn)) { - ZipEntry entry; - while ((entry = in.getNextEntry()) != null) { - String name = entry.getName(); - if (!name.startsWith(LANG_PREFIX) || !name.endsWith(".lang")) { - continue; - } - name = name.substring(LANG_PREFIX.length()); - languages.put(name, IOUtils.toByteArray(in)); - LOGGER.debug("Added language file {}", name); - } - } - LOGGER.debug("Downloaded {} languages", languages.size()); - - // Add lang files as resource pack - mc.addScheduledTask(() -> { - @SuppressWarnings("unchecked") - List defaultResourcePacks = mc.defaultResourcePacks; - defaultResourcePacks.add(new LocalizedResourcePack(languages)); - mc.getLanguageManager().onResourceManagerReload(mc.getResourceManager()); - // Optifine does its own, additional language data loading - OptifineReflection.reloadLang(); - LOGGER.debug("Added language files to resource packs and reloaded LanguageManager"); - }); - } catch (Throwable t) { - LOGGER.error("Loading localized resource pack:", t); - } - }, "localizedResourcePackLoader"); - localizedResourcePackLoader.setDaemon(true); - localizedResourcePackLoader.start(); - } - - public static class LocalizedResourcePack implements IResourcePack { - private final Pattern LANG_PATTERN = Pattern.compile("^lang/([.+].lang)$"); - private final Map languages; - - public LocalizedResourcePack(Map languages) { - this.languages = languages; - } - - @Override - public InputStream getInputStream(ResourceLocation loc) { - if (!"replaymod".equals(loc.getResourceDomain())) return null; - Matcher matcher = LANG_PATTERN.matcher(loc.getResourcePath()); - if (matcher.matches()) { - byte[] bytes = languages.get(matcher.group()); - if (bytes != null) { - return new ByteArrayInputStream(bytes); - } - } - return null; - } - - @Override - public boolean resourceExists(ResourceLocation loc) { - // Assumes that getInputStream returns a ByteArrayInputStream that doesn't need to be closed - return getInputStream(loc) != null; - } - - @Override - public Set getResourceDomains() { - return ImmutableSet.of("replaymod"); - } - - //#if MC>=10904 - @Override - public T getPackMetadata(MetadataSerializer metadataSerializer, String metadataSectionName) throws IOException { - //#else - //$$ @Override - //$$ public IMetadataSection getPackMetadata(IMetadataSerializer metadataSerializer, String metadataSectionName) throws IOException { - //#endif - return null; - } - - @Override - public BufferedImage getPackImage() throws IOException { - return null; - } - - @Override - public String getPackName() { - return "ReplayModLocalizationResourcePack"; - } - } -} diff --git a/src/main/java/com/replaymod/extras/OpenEyeExtra.java b/src/main/java/com/replaymod/extras/OpenEyeExtra.java index 55776d80..99580a16 100644 --- a/src/main/java/com/replaymod/extras/OpenEyeExtra.java +++ b/src/main/java/com/replaymod/extras/OpenEyeExtra.java @@ -2,6 +2,7 @@ package com.replaymod.extras; import com.replaymod.core.ReplayMod; import com.replaymod.core.Setting; +import com.replaymod.core.versions.MCVer; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiPanel; @@ -16,11 +17,15 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.utils.Colors; import org.apache.commons.io.FileUtils; +//#if MC>=11300 +import net.minecraftforge.fml.ModList; +//#else //#if MC>=10800 -import net.minecraftforge.fml.common.Loader; +//$$ import net.minecraftforge.fml.common.Loader; //#else //$$ import cpw.mods.fml.common.Loader; //#endif +//#endif import javax.net.ssl.HttpsURLConnection; import java.io.File; @@ -34,7 +39,7 @@ import static com.replaymod.core.utils.Utils.SSL_SOCKET_FACTORY; import static com.replaymod.extras.ReplayModExtras.LOGGER; public class OpenEyeExtra implements Extra { - private static final String DOWNLOAD_URL = "https://www.replaymod.com/dl/openeye/" + Loader.MC_VERSION; + private static final String DOWNLOAD_URL = "https://www.replaymod.com/dl/openeye/" + ReplayMod.getMinecraftVersion(); private static final Setting ASK_FOR_OPEN_EYE = new Setting<>("advanced", "askForOpenEye", null, true); private ReplayMod mod; @@ -42,9 +47,13 @@ public class OpenEyeExtra implements Extra { @Override public void register(ReplayMod mod) throws Exception { this.mod = mod; - mod.getSettingsRegistry().register(ASK_FOR_OPEN_EYE); - if (!Loader.isModLoaded("OpenEye") && mod.getSettingsRegistry().get(ASK_FOR_OPEN_EYE)) { + //#if MC>=11300 + boolean isOpenEyeLoaded = ModList.get().isLoaded("openeye"); + //#else + //$$ boolean isOpenEyeLoaded = Loader.isModLoaded("OpenEye"); + //#endif + if (!isOpenEyeLoaded && mod.getSettingsRegistry().get(ASK_FOR_OPEN_EYE)) { new Thread(() -> { try { LOGGER.trace("Checking for OpenEye availability"); @@ -93,7 +102,7 @@ public class OpenEyeExtra implements Extra { GuiPopup popup = new GuiPopup(OfferGui.this); new Thread(() -> { try { - File targetFile = new File(mod.getMinecraft().mcDataDir, "mods/" + Loader.MC_VERSION + "/OpenEye.jar"); + File targetFile = new File(MCVer.mcDataDir(mod.getMinecraft()), "mods/" + ReplayMod.getMinecraftVersion() + "/OpenEye.jar"); FileUtils.forceMkdir(targetFile.getParentFile()); HttpsURLConnection connection = (HttpsURLConnection) new URL(DOWNLOAD_URL).openConnection(); diff --git a/src/main/java/com/replaymod/extras/ReplayModExtras.java b/src/main/java/com/replaymod/extras/ReplayModExtras.java index 768afb44..4c6f5666 100644 --- a/src/main/java/com/replaymod/extras/ReplayModExtras.java +++ b/src/main/java/com/replaymod/extras/ReplayModExtras.java @@ -1,40 +1,22 @@ package com.replaymod.extras; +import com.replaymod.core.Module; import com.replaymod.core.ReplayMod; import com.replaymod.extras.advancedscreenshots.AdvancedScreenshots; import com.replaymod.extras.playeroverview.PlayerOverview; import com.replaymod.extras.urischeme.UriSchemeExtra; import com.replaymod.extras.youtube.YoutubeUpload; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -//#if MC>=10800 -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -//#else -//$$ import cpw.mods.fml.common.Mod; -//$$ import cpw.mods.fml.common.event.FMLInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; -//#endif - import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -@Mod(modid = ReplayModExtras.MOD_ID, - version = "@MOD_VERSION@", - acceptedMinecraftVersions = "@MC_VERSION@", - acceptableRemoteVersions = "*", - //#if MC>=10800 - clientSideOnly = true, - //#endif - useMetadata = true) -public class ReplayModExtras { - public static final String MOD_ID = "replaymod-extras"; - - @Mod.Instance(MOD_ID) +public class ReplayModExtras implements Module { + { instance = this; } public static ReplayModExtras instance; private static final List> builtin = Arrays.asList( @@ -44,22 +26,19 @@ public class ReplayModExtras { YoutubeUpload.class, FullBrightness.class, HotkeyButtons.class, - LocalizationExtra.class, OpenEyeExtra.class ); private final Map, Extra> instances = new HashMap<>(); - public static Logger LOGGER; + public static Logger LOGGER = LogManager.getLogger(); - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - LOGGER = event.getModLog(); - ReplayMod.instance.getSettingsRegistry().register(Setting.class); + public ReplayModExtras(ReplayMod core) { + core.getSettingsRegistry().register(Setting.class); } - @Mod.EventHandler - public void init(FMLInitializationEvent event) { + @Override + public void initClient() { for (Class cls : builtin) { try { Extra extra = cls.newInstance(); diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java index b7e4d18a..8cefee8c 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java @@ -1,15 +1,26 @@ package com.replaymod.extras.advancedscreenshots; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer; import com.replaymod.extras.Extra; import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiControls; import net.minecraftforge.common.MinecraftForge; -import org.lwjgl.input.Keyboard; + +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +import net.minecraftforge.client.event.GuiScreenEvent; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif @@ -18,7 +29,7 @@ public class AdvancedScreenshots implements Extra { private ReplayMod mod; - private final Minecraft mc = Minecraft.getMinecraft(); + private final Minecraft mc = MCVer.getMinecraft(); @Override public void register(ReplayMod mod) throws Exception { @@ -28,19 +39,20 @@ public class AdvancedScreenshots implements Extra { @SubscribeEvent public void onDispatchKeypresses(ReplayDispatchKeypressesEvent.Pre event) { - int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() : Keyboard.getEventKey(); + if (mc.currentScreen instanceof GuiControls) return; + //#if MC>=11300 + // FIXME + //#else + //$$ if (!Keyboard.getEventKeyState()) return; + //$$ if (Keyboard.isRepeatEvent()) return; + //$$ int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() : Keyboard.getEventKey(); + //$$ if (keyCode == 0 || keyCode != mc.gameSettings.keyBindScreenshot.getKeyCode()) return; + //#endif - // all the conditions required to trigger a screenshot condensed in a single if statement - if (keyCode != 0 && !Keyboard.isRepeatEvent() - && (!(mc.currentScreen instanceof GuiControls) || ((GuiControls) mc.currentScreen).time <= mc.getSystemTime() - 20L) - && Keyboard.getEventKeyState() - && keyCode == mc.gameSettings.keyBindScreenshot.getKeyCode()) { + ReplayMod.instance.runLater(() -> { + new GuiCreateScreenshot(mod).display(); + }); - ReplayMod.instance.runLater(() -> { - new GuiCreateScreenshot(mod).display(); - }); - - event.setCanceled(true); - } + event.setCanceled(true); } } diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java index 3ef09234..a5422804 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java @@ -1,6 +1,7 @@ package com.replaymod.extras.advancedscreenshots; import com.replaymod.core.ReplayMod; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.gui.GuiRenderSettings; import com.replaymod.replay.ReplayModReplay; @@ -12,10 +13,9 @@ import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import net.minecraft.crash.CrashReport; import net.minecraft.util.ScreenShotHelper; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.common.config.Property; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import static com.replaymod.core.utils.Utils.error; @@ -82,13 +82,12 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable { @Override protected File generateOutputFile(RenderSettings.EncodingPreset encodingPreset) { - File screenshotFolder = new File(getMinecraft().mcDataDir, "screenshots"); + File screenshotFolder = new File(MCVer.mcDataDir(getMinecraft()), "screenshots"); return ScreenShotHelper.getTimestampedPNGFileForDirectory(screenshotFolder); } @Override - protected Property getConfigProperty(Configuration configuration) { - return configuration.get("screenshotsettings", "settings", "{}", - "Last state of the screenshot settings GUI. Internal use only."); + protected Path getSettingsPath() { + return MCVer.mcDataDir(getMinecraft()).toPath().resolve("config/replaymod-screenshotsettings.json"); } } \ No newline at end of file diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java index 4c2cf396..593f20b9 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiUploadScreenshot.java @@ -12,7 +12,7 @@ import de.johni0702.minecraft.gui.element.GuiLabel; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; -import org.lwjgl.util.ReadableColor; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; import java.awt.*; import java.io.IOException; diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java index 56f47ac9..59c959de 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java @@ -1,13 +1,14 @@ package com.replaymod.extras.advancedscreenshots; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.capturer.RenderInfo; import com.replaymod.render.rendering.Pipelines; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import lombok.RequiredArgsConstructor; import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; //#if MC>=10800 import com.replaymod.render.hooks.ChunkLoadingRenderGlobal; @@ -16,7 +17,7 @@ import com.replaymod.render.hooks.ChunkLoadingRenderGlobal; @RequiredArgsConstructor public class ScreenshotRenderer implements RenderInfo { - private final Minecraft mc = Minecraft.getMinecraft(); + private final Minecraft mc = MCVer.getMinecraft(); private final RenderSettings settings; @@ -24,8 +25,12 @@ public class ScreenshotRenderer implements RenderInfo { public boolean renderScreenshot() throws Throwable { try { - int displayWidthBefore = mc.displayWidth; - int displayHeightBefore = mc.displayHeight; + //#if MC>=11300 + // FIXME + //#else + //$$ int displayWidthBefore = mc.displayWidth; + //$$ int displayHeightBefore = mc.displayHeight; + //#endif boolean hideGUIBefore = mc.gameSettings.hideGUI; mc.gameSettings.hideGUI = true; @@ -42,12 +47,16 @@ public class ScreenshotRenderer implements RenderInfo { //#endif mc.gameSettings.hideGUI = hideGUIBefore; - mc.resize(displayWidthBefore, displayHeightBefore); + //#if MC>=11300 + // FIXME + //#else + //$$ mc.resize(displayWidthBefore, displayHeightBefore); + //#endif return true; } catch (OutOfMemoryError e) { e.printStackTrace(); CrashReport report = CrashReport.makeCrashReport(e, "Creating Equirectangular Screenshot"); - Minecraft.getMinecraft().crashed(report); + MCVer.getMinecraft().crashed(report); } return false; } diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java index fcabe95c..dc9c1236 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotWriter.java @@ -2,13 +2,13 @@ package com.replaymod.extras.advancedscreenshots; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Utils; +import com.replaymod.core.versions.MCVer; import com.replaymod.extras.ReplayModExtras; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.rendering.FrameConsumer; import com.replaymod.replay.ReplayModReplay; -import net.minecraft.client.Minecraft; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.crash.CrashReport; -import org.lwjgl.util.ReadableDimension; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -48,7 +48,7 @@ public class ScreenshotWriter implements FrameConsumer { } catch (OutOfMemoryError e) { e.printStackTrace(); CrashReport report = CrashReport.makeCrashReport(e, "Exporting frame"); - Minecraft.getMinecraft().crashed(report); + MCVer.getMinecraft().crashed(report); } catch (Throwable t) { CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame"); diff --git a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java index 29e64631..1e16ecc9 100644 --- a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java +++ b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverview.java @@ -11,11 +11,19 @@ import com.replaymod.replay.events.ReplayOpenEvent; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraftforge.client.event.RenderHandEvent; -import org.lwjgl.input.Keyboard; + +//#if MC>=11300 +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif //#if MC>=10800 import com.google.common.base.Predicate; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.EventPriority; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; diff --git a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverviewGui.java b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverviewGui.java index 0659410c..af40d4d2 100644 --- a/src/main/java/com/replaymod/extras/playeroverview/PlayerOverviewGui.java +++ b/src/main/java/com/replaymod/extras/playeroverview/PlayerOverviewGui.java @@ -18,15 +18,13 @@ import de.johni0702.minecraft.gui.function.Closeable; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.utils.Colors; -import net.minecraft.client.audio.PositionedSoundRecord; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ResourceLocation; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableDimension; //#if MC>=10904 import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; //#else //$$ import net.minecraft.potion.Potion; //#endif @@ -55,30 +53,12 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable { public final GuiCheckbox checkAll = new GuiCheckbox(contentPanel){ @Override public void onClick() { - //#if MC>=10904 - getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F)); - //#else - //#if MC>=10800 - //$$ getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.create(BUTTON_SOUND, 1.0F)); - //#else - //$$ getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.createPositionedSoundRecord(BUTTON_SOUND, 1.0F)); - //#endif - //#endif playersScrollable.forEach(IGuiCheckbox.class).setChecked(true); } }.setLabel("").setChecked(true).setTooltip(new GuiTooltip().setI18nText("replaymod.gui.playeroverview.showall")); public final GuiCheckbox uncheckAll = new GuiCheckbox(contentPanel){ @Override public void onClick() { - //#if MC>=10904 - getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F)); - //#else - //#if MC>=10800 - //$$ getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.create(BUTTON_SOUND, 1.0F)); - //#else - //$$ getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.createPositionedSoundRecord(BUTTON_SOUND, 1.0F)); - //#endif - //#endif playersScrollable.forEach(IGuiCheckbox.class).setChecked(false); } }.setLabel("").setChecked(false).setTooltip(new GuiTooltip().setI18nText("replaymod.gui.playeroverview.hideall")); @@ -135,11 +115,15 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable { } }.setSize(16, 16), new GuiLabel().setText( + //#if MC>=11300 + p.getName().getFormattedText() + //#else //#if MC>=10800 - p.getName() + //$$ p.getName() //#else //$$ p.getDisplayName() //#endif + //#endif ).setColor(isSpectator(p) ? Colors.DKGREY : Colors.WHITE) ).onClick(new Runnable() { @Override @@ -196,11 +180,15 @@ public class PlayerOverviewGui extends GuiScreen implements Closeable { public int compare(EntityPlayer o1, EntityPlayer o2) { if (isSpectator(o1) && !isSpectator(o2)) return 1; if (isSpectator(o2) && !isSpectator(o1)) return -1; + //#if MC>=11300 + return o1.getName().getFormattedText().compareToIgnoreCase(o2.getName().getFormattedText()); + //#else //#if MC>=10800 - return o1.getName().compareToIgnoreCase(o2.getName()); + //$$ return o1.getName().compareToIgnoreCase(o2.getName()); //#else //$$ return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName()); //#endif + //#endif } } } diff --git a/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java b/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java index 2a4db226..9b19158a 100644 --- a/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java +++ b/src/main/java/com/replaymod/extras/urischeme/LinuxUriScheme.java @@ -1,6 +1,5 @@ package com.replaymod.extras.urischeme; -import com.replaymod.core.ReplayMod; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.StringBuilderWriter; @@ -17,7 +16,7 @@ public class LinuxUriScheme extends UriScheme { public void install() throws URISyntaxException, IOException { File file = new File("replaymod.desktop"); File iconFile = new File("replaymod-icon.jpg"); - String path = ReplayMod.getContainer().getSource().getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\""); + String path = findJarFile().getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\""); String content = "[Desktop Entry]\n" + "Name=ReplayMod\n" + diff --git a/src/main/java/com/replaymod/extras/urischeme/UriScheme.java b/src/main/java/com/replaymod/extras/urischeme/UriScheme.java index 2af92bcc..17b873a0 100644 --- a/src/main/java/com/replaymod/extras/urischeme/UriScheme.java +++ b/src/main/java/com/replaymod/extras/urischeme/UriScheme.java @@ -6,6 +6,9 @@ import javax.swing.*; import java.io.*; import java.net.InetAddress; import java.net.Socket; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.CodeSource; import java.util.Arrays; public abstract class UriScheme { @@ -82,4 +85,19 @@ public abstract class UriScheme { } public abstract void install() throws Exception; + + File findJarFile() throws IOException { + CodeSource codeSource = UriScheme.class.getProtectionDomain().getCodeSource(); + if (codeSource != null) { + URL location = codeSource.getLocation(); + if (location != null) { + try { + return new File(location.toURI()); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + } + throw new IOException("No jar file found. Is this a development environment?"); + } } diff --git a/src/main/java/com/replaymod/extras/urischeme/WindowsUriScheme.java b/src/main/java/com/replaymod/extras/urischeme/WindowsUriScheme.java index 748db63a..64bde10d 100644 --- a/src/main/java/com/replaymod/extras/urischeme/WindowsUriScheme.java +++ b/src/main/java/com/replaymod/extras/urischeme/WindowsUriScheme.java @@ -1,6 +1,5 @@ package com.replaymod.extras.urischeme; -import com.replaymod.core.ReplayMod; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.StringBuilderWriter; @@ -9,7 +8,7 @@ import java.io.IOException; public class WindowsUriScheme extends UriScheme { @Override public void install() throws IOException, InterruptedException { - String path = ReplayMod.getContainer().getSource().getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\""); + String path = findJarFile().getAbsolutePath().replace("\\", "\\\\").replace("\"", "\\\""); regAdd("\\replaymod /f /ve /d \"URL:replaymod Protocol\""); regAdd("\\replaymod /f /v \"URL Protocol\" /d \"\""); regAdd("\\replaymod\\shell\\open\\command /f /ve /d \"java -cp \\\"" + path + "\\\" " + UriScheme.class.getName() + " \\\"%1\\\"\""); diff --git a/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java index d3af732e..3122ab43 100644 --- a/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java +++ b/src/main/java/com/replaymod/extras/youtube/GuiYoutubeUpload.java @@ -18,12 +18,17 @@ import de.johni0702.minecraft.gui.element.advanced.GuiTextArea; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import joptsimple.internal.Strings; import lombok.RequiredArgsConstructor; import net.minecraft.client.resources.I18n; import org.apache.commons.io.IOUtils; -import org.lwjgl.Sys; -import org.lwjgl.util.ReadableDimension; + +//#if MC>=11300 +import net.minecraft.util.Util; +//#else +//$$ import org.lwjgl.Sys; +//#endif import javax.annotation.Nullable; import javax.imageio.ImageIO; @@ -213,7 +218,11 @@ public class GuiYoutubeUpload extends GuiScreen { try { Desktop.getDesktop().browse(new URL(url).toURI()); } catch(Throwable throwable) { - Sys.openURL(url); + //#if MC>=11300 + Util.getOSType().openURI(url); + //#else + //$$ Sys.openURL(url); + //#endif } upload = null; progressBar.setLabel(I18n.format("replaymod.gui.ytuploadprogress.done", url)); diff --git a/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java b/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java index 9527665b..d171cd87 100644 --- a/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java +++ b/src/main/java/com/replaymod/extras/youtube/YoutubeUpload.java @@ -9,7 +9,11 @@ import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.common.MinecraftForge; //#if MC>=10800 -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif diff --git a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java index 9c73a933..e2f37a32 100644 --- a/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java +++ b/src/main/java/com/replaymod/extras/youtube/YoutubeUploader.java @@ -24,6 +24,7 @@ import com.google.common.base.Suppliers; import com.google.common.io.Files; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.metadata.MetadataInjector; import lombok.Getter; @@ -78,7 +79,7 @@ public class YoutubeUploader { this.videoVisibility = videoVisibility; this.videoSnippet = videoSnippet; this.httpTransport = GoogleNetHttpTransport.newTrustedTransport(); - this.dataStoreFactory = new FileDataStoreFactory(minecraft.mcDataDir); + this.dataStoreFactory = new FileDataStoreFactory(MCVer.mcDataDir(minecraft)); } diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 792b4262..000ca02a 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -297,10 +297,9 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { this.replayHandler = replayHandler; this.timeline = timeline; - Path path = ReplayModRender.instance.getRenderSettingsPath(); String json = "{}"; try { - json = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + json = new String(Files.readAllBytes(getSettingsPath()), StandardCharsets.UTF_8); } catch (FileNotFoundException ignored) { } catch (IOException e) { LOGGER.error("Reading render settings:", e); @@ -507,6 +506,10 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { return new File(folder, fileName + "." + encodingPreset.getFileExtension()); } + protected Path getSettingsPath() { + return ReplayModRender.instance.getRenderSettingsPath(); + } + private RenderSettings getDefaultRenderSettings() { return new RenderSettings(RenderSettings.RenderMethod.DEFAULT, RenderSettings.EncodingPreset.MP4_DEFAULT, 1920, 1080, 60, 10 << 20, null, true, false, false, false, null, 360, 180, false, RenderSettings.AntiAliasing.NONE, "", RenderSettings.EncodingPreset.MP4_DEFAULT.getValue(), false); @@ -516,9 +519,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { public void close() { RenderSettings settings = save(true); String json = new Gson().toJson(settings); - Path path = ReplayModRender.instance.getRenderSettingsPath(); try { - Files.write(path, json.getBytes(StandardCharsets.UTF_8)); + Files.write(getSettingsPath(), json.getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { LOGGER.error("Saving render settings:", e); } diff --git a/versions/common.gradle b/versions/common.gradle index 94924e65..5afbf933 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -274,7 +274,6 @@ sourceSets { main { java { exclude 'com/replaymod/compat' - exclude 'com/replaymod/extras' exclude 'com/replaymod/restrictions' } } From 528791c91fe6de2017112a469466bfb702581da7 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 2 Mar 2019 15:10:15 +0100 Subject: [PATCH 43/65] fixup snapshot --- versions/common.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/versions/common.gradle b/versions/common.gradle index 5afbf933..1034a599 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -10,7 +10,6 @@ buildscript { project.ext.mcVersion = mcVersion repositories { - mavenLocal() jcenter() mavenCentral() maven { From 8705bbad9aecdb1564e13ebc527098aa9f17b8ab Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 2 Mar 2019 18:11:01 +0100 Subject: [PATCH 44/65] Re-enable and fix building for older MC versions --- .gitignore | Bin 2558 -> 2582 bytes README.md | 4 ++ build.gradle | 12 ---- gradle/wrapper/gradle-wrapper-old.jar | Bin 0 -> 53638 bytes gradle/wrapper/gradle-wrapper-old.properties | 6 ++ gradlew | 7 +- jGui | 2 +- settings.gradle | 68 +++++++++++------- .../java/com/replaymod/core/ReplayMod.java | 25 ++++--- .../com/replaymod/core/versions/MCVer.java | 10 +-- .../com/replaymod/online/ReplayModOnline.java | 4 +- .../recording/ReplayModRecording.java | 4 ++ .../recording/gui/GuiRecordingOverlay.java | 1 + .../packet/ResourcePackRecorder.java | 9 ++- .../render/capturer/OpenGlFrameCapturer.java | 1 - .../render/gui/GuiVideoRenderer.java | 5 +- .../render/mixin/MixinEntityRenderer.java | 7 +- .../render/rendering/VideoRenderer.java | 1 + .../replaymod/replay/FullReplaySender.java | 14 ++-- .../replaymod/replay/InputReplayTimer.java | 20 +++--- .../com/replaymod/replay/ReplayModReplay.java | 2 +- .../replaymod/replay/handler/GuiHandler.java | 2 +- .../replay/mixin/MixinPlayerControllerMP.java | 2 +- versions/common.gradle | 20 ++++-- 24 files changed, 142 insertions(+), 84 deletions(-) create mode 100755 gradle/wrapper/gradle-wrapper-old.jar create mode 100755 gradle/wrapper/gradle-wrapper-old.properties diff --git a/.gitignore b/.gitignore index b56d0cb77bb5f4a0fd006b9f50fcb8a275d0d71f..cf5548565aece1f7b720e4211a634881c38b0d43 100644 GIT binary patch delta 32 ncmew-JWXUn6_c=HdQoCZPO5HsQDQ+sYLRY!PRiy|rcQPM$SDio delta 12 TcmbOx@=tg}71QQPOfBpHB1Htc diff --git a/README.md b/README.md index cc6f2ed1..a3e842e0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ After the initial clone, running `./gradlew setupDecompWorkspace` is necessary. This will also be necessary if the `replaymod_at.cfg` file has been changed (getting errors about code that is trying to access private fields is a good indication that this has happened). +**Note**: ForgeGradle 1.2 (used for 1.7.10) is not compatible with Gradle 4. +As such, you need to use an older version (Gradle 3.x) to build the ReplayMod for 1.7.10. +An easy way to do that is to pass `--old-gradle` as the first argument to the Gradle wrapper shell script: `./gradlew --old-gradle build` + ### No IDE You can build the mod by running `./gradlew build`. You can then find the final jar files in `versions/$MCVERSION/build/libs/`. You can also build single versions by running `./gradlew :1.8:build` (builds the MC 1.8 version). diff --git a/build.gradle b/build.gradle index 5b42ddd9..de31bda2 100755 --- a/build.gradle +++ b/build.gradle @@ -26,18 +26,6 @@ subprojects { repositories { maven { url 'https://jitpack.io' } } - configurations.classpath.resolutionStrategy.eachDependency { dep -> - if (System.getenv('REPRODUCIBLE_BUILD') == '1') return - def req = dep.target - // Substitute FG 2.0 with custom fork that works with Gradle 3 - if (req.group == 'net.minecraftforge.gradle' && req.name == 'ForgeGradle' && req.version == '2.0-SNAPSHOT') { - dep.useTarget 'com.github.ReplayMod:ForgeGradle:dd521d0' - } - // Substitute FG 2.1 with custom fork that works with Gradle 3 - if (req.group == 'net.minecraftforge.gradle' && req.name == 'ForgeGradle' && req.version == '2.1-SNAPSHOT') { - dep.useTarget 'com.github.ReplayMod:ForgeGradle:9c2f79c' - } - } } // Thanks for wasting my time gradle... diff --git a/gradle/wrapper/gradle-wrapper-old.jar b/gradle/wrapper/gradle-wrapper-old.jar new file mode 100755 index 0000000000000000000000000000000000000000..5ccda13e9cb94678ba179b32452cf3d60dc36353 GIT binary patch literal 53638 zcmafaW0a=B^559DjdyI@wr$%scWm3Xy<^+Pj_sKpY&N+!|K#4>Bz;ajPk*RBjZ;RV75EK*;j-;d{(BB5~-#>pF^k0$_Qx&35mhPeng zP5V`%P1S)(UiPcRczm!G=Nud7!TH`9_!bdReTmO0lO(Zfn zfMqP~+s2VTE#?sl=$9e(CuBOAH2n}=c4idsipAKKQIO=pSsMiScd0TvKs^d1xMCym`IZxg&Xd3ii(^3$M#K)myV2qM z{o5&z?1rtP{gnX6zegV2 z$03xe*g2pGA^BqfBE}XDD-CN?H&?w?kE0wG3~``ie?T+IRmsT;*cpLZ)OnaXBtFzX zMcc97L%tQqGz+E@0i)gy&0g_7PV@3~zaE{g-2zQ|T9d>EL&JiD+E8t?H^#-Hv8!mV z-r%F^bl8v}j19B}`M^zJ>hD+Tg?Fg%no2GYmKkbR`2=|`@o`~1&VbKUJ@ZSojd|ihP|{9&8F<6 zcAvtwl6yo{Js7UQ{l~MjA7Rz8(sYEcRXX(n*(Mp75OBEijo(25zwC)pc=#%f_xV93 z`&d+DI*TAtd3KBd(^964h=X;uks5gf3MU}cR#X_wnk6_crcAVLsTV2SzsGM$h~aq~ z6oX|v?jSq%txd-SHwHy`7x4*jJqH^;0*1`Sztp*aYi4tRMBD|Ryyl%F{IC{(=Y{Y5 zhSQPvqZN~4uvCi*h``E|llqjfc4rnRQPs3@(MnB9jto`dtz!CB-ojaReyN}7BwMp! zU@jEkH2hS%NUB|wE0d;=hS4^M^dTz`r=^`7LNsQkU26nlt4o?Ki13cwDXkQH+)w#uNVQo2o@pEJOAZV3Uf z8WWqpN|lDuGdkokHkKLwmo@qCdV6}M=~DGq+P3}@$$yqQssE{3|BxxM*q?tD3oiW6 z^!W)Iau1CDv+;dTH4Lbb;*)+mGrKg;g)4tHB;h~=3QsCF)I|E{`=jp;ArQuy&zUzA zlz$NoIhz7h@;Sw+#%u~;!w56XV3JkGLOHaVlvs1eSSck_-2#zs%EynXvEnsUsO3{@ z=2B!(Gdra;oKm@A@~#LeoDFC2&V->;dgCP}x`Qm{yZA&ULeNnWvNIGzcgjx2?Rx#m z_I4lu^j~)hR_VQ?`&Yk|{^}Rqf8MFY|1el;E@sY>4t8d;4h}YMj{n$ntcs2Tju6_n zc%t6wvvLifwar=wOlL#;T5V}~s_KU-6cMz7X&7`JeYdHW?WaaBnYH!e82^(58{d#J z&3H)nMCXi0pUcVg^sRt^KZxdFRj|_ZglEw{Ri0EN6_laAxbE8zB=H8KgU;Xtpk5?z zC2?g-xj`9d8MtJf-!H#~s0}tJ>Ksa+7KP;J(%hHwUBewO);ZZ&ry8oYXI%5+YQgNQeyS*ViKL>Yy2`MsK? zB7Y$Zk@YAy#-Kwyo5KSK$lcvER(OV>qrW1VXPo7Ih%dEJZ<|5sEmeC)do0(dJ;7Fc z#v{T#df-92-StcUzRO7OjZ?g-Ik?9eGEDWsUL(f2jUmS9_ajH?wV&{0Aj)-0IP36} z4!4}CW2D{v(ZmPjB$#&;fps(Hvph<>^IORq|0^=eDhYiA%W5!rM_K_y(bsu@*)m3P++I=?)h!HA@uUc{zxJ0ibvxU%Ke8OQ+KDRndS#XDA4T zto-I$zC-%q0v8ZL`!Z;MMK0`Irsn?gZwiTbsJAr&4g~c3FEn8J&tfX(X=3ZOyEhpng#DDSOc6XLr%uGtB2|0=_Au$%heH3&*ID*ZPhs8iJyw{$c z)`ySqPVndS_Rnv2f$xtMcp${1WTLjhW)S3(;l*PK4#cXz09;@vNj*6?$Q>%5jIboV2fgAyb?c(W#K{@rj#6OKF&J#QQ9 zboB4HeJ?hXrHp)H9rx@Rta#*PdvkHJ<<2Asc#ClKA;st5qadT0NZHEA111(&qsaLb zkeTH_h(yr92XkyqqslQgTo(|RN$hhQ*IL7<12W?$+q6R2jtnWadKrIyeA>bj=;9mM zaPK&0{1#djnc2d@4fR{7K872i*IvH0mK#eqf4=iu8F5=2P#fG-GBZ|`J2MyJ(^^*5 z$tQaAS;Y(2k!j10=adaH9^!>^k+iBMVWD9#+F=&Q(yZ5NVJ>A}t>1R@32TZ0JTMas z%42sos08y0NMkb$BsDnQo8nVhd#ksaq8UyBjAO1FHRfW*u`ojc^y3)=(f&PTM`k@F zMoZFS>HCeNX1c@G{<=x`IQr>{11kPK#7AZWy_q&GQYwk*t|uTE9H*TVi|@g8P95wBlwf};`RANPqd z@rf3B=~Q8%Wgl5i2t$W?Ns1WgZ1t0sFVJF73Rc!d@X---3W@e+Dbvpj;l~8r`F9Sc zKd8G>dR2>61(|Bw&XdTlG}}fnu~6{2xsz6Efmc;nRDupK!KI-q=^*h{`b$W=VCBWe|mhK3YN$PO28ZaH@2V;Nbgpwl8Fig6xxkWN7UhWjM%G z<^O`4VX45jgsIIO-R$7F$`uCa6O>(WOZ>i>Gor3X^yySAwSB;0*X`pWy<(jya4!HO zYDvrso1n6V3G<>TnrSv{+unA;cSAWuH!9k`z#^j306@cy?0{jP-p4NUCSRP<_wNHG6^axCR zMECIg`Vz^ja7F}a@eRp)X%2>D5;2HR8kL?^&npLgqU9<#pY)7;V<(`jjbZL5j>8!3 zLF`9aB2GDCO#Q|6P6_x!My0QO@k9M-2f2-|AF33CZ))eL<;OOi;76DiE|3}S)dWs znAG6VC4EKe9MedTp6d0%J409Iu^T12e)n(N?^s^DlM+cUS5h4SEqq4NjK+%c6KPr%EUeiN1v_&WkfIr9Do+Q{2=Ap?{FS|6D&*Au8=PW}~Rt~+3EJpNK(;7R!k z%&9kpQ;0c6#VF?+W0D~mUp&bc96HK5g~~9ch$%)z5Z@Wq`!V zLK|>+`YZuZ?H_46y1zmqL~@TLC0lsiaSZzy-7!W4m2VsY*S@u zW}zeWzr7N1oL)qzKa8J;I?@RxaLmPEowX2BlSD2hnYW}WyvQ$FK%;PTPl4|ftNkVB-&5MvFWP;Yx2`zBS8o_7QfB#MLS!;fRlOU$t z%iCYD|0>%`e)v|$e?d9+U&O=spApZ$`@51x_J1P_|AafS>X$C4YG{A#vQ3BFr-~J& zrNf{=tbRuZqT9ky&r6pgk}1*#cgZ;@Dve26=%&Eu887bdn12Lfj~ET zV5IjzrKV8JQNK?MSC0X660d`nnw55z4~eTJnzix7U0S>nhTZ)+f}uOuozz3gz(ZMj zYs1J$Dy2L`8>*UTBLriGUKTY410qsp)+K??D$!Mr_7=C*Ec`Q^X$b{2>?+1_7Ka7f zd2{rtBr%g2PbF_kHb0yF5ypGWmJ(ftYt&YJ_ynIc9aWyTF~dXbrN!iNKZ_(_u>s(I z2*X|;szamJz{2dl(P2I;%foJLF51)WO50&h!EXP{5|gzy^b(4+nn<;j$NCt?UP_cL zEU1-XN`f*1Gl}Fht4&Pn5xo)Ma$kZyBt2qju1yuSTysFNG^pi%vvU=hqPhQ-Cpl`! zSt+cpY2TVYkvTflcqwieu*7MHVIVSQSivl850|@O!h+yWxUM;@nYhP*y8k$MMX>^Vzs= zqdctvR$e(}|H4~b=2=Dszgy^xqjuY6alG6sJ&J~#*>K6$Dl|Pf&Y~sh$@07*amh*T z_wIHl$BbJK)7B2V0P=;_iRjD@X9TGO4h+mK7laU=qy zmy|9(xNb7=49$9mLHL1jK#9O+t+CJ;3IDtkK4I0H_{C zMZMuxRK)|E4155y$!RGIcAVHwpQK;2>ssTa8^Y0_8A*sk}a3gZD5Il8pD}4GR1;_@6*%Y^za=h*V zu5|)zJI%1zR^G!TWc5ioA9xPwtfcqbul(N(r%J#%9D&&zkuV4xj0VXfymNc> zXa2;S1FBKAgNu0n(1&iyT^XFajIG9aRR$`eVEejDBr(KR%NpK5M4^XoP2r^{5!QII z=({hFfVnU7w+W~i2BF-t(|u6~1LVuM`kfH|v*hfA+X=o<%CrInhQid%%Pn@a#zcB{ z^o7+|r0o0_JFn1}AG;)N{NfS8LI&fnSX*e@KcPEe6OV6U-=oiHjXw;D&;Ui32rr=^ zeI$9fsv_3NJU2zXlqE0HwYjRkF^(*=dmpb#bAGI-iGF7mAYYyfIwG8Z;|+==LK(t5 z=LlsTsJKX@6c-VZ+w{_}$Z-T9i*wIN-d5;hIXRENbV;h#&hIG%+U%XScSeP_M`St_ zW*|AQg18~yjh5x7_;)cRFt3fbcf={_ULTh(DfkEq%mVu$EcpgdN}OOAmZBAc2`mzY-_S)k2M=`*UssiSqyy?xVKo=MbGuFS6XRVx2Djkn-AEcIgSQvVAF zfc`p)g#X!O$~sv5mqQqq{Nb>uh&I-rK1N;7H0mguftm{=rV;MIL=kQZjZ6q_PVrHj zl3gcbRfrb*Cn_KeXJnZ06ZEq<=ClnSMYA~}FVE$cEB}!?;QRYG{63OGvgE!wNV+3U z&{hS7QV6Z6UL=q3cB&(hP~yB{WPCY<2lhJj&?d_e^Y$rRxGZ6c1-iXZdJo;c_d!-U7axq4RNZah&+JQde7@OZf;An_X_9 z;&omnq3Vb#o!)t8oY6U5q9V@2gy>2y91VhMexMd;_^7e(hpL;mFYj^Ms7`BGG-h5q zK%Q)s!X(^S6HkOCW#c?l`3P0RZTN{hKdkGb8X4YR z`Q>!^OJIGZmB9N^htzi$VRW^@G$NKIzjI7BcO^B;MhbLW^3e|uMwSeIP|Zs8x`x5{ zeL>F`oNu5Vl>~_AJi)C>nNM&Fhx!XDe?wEG`x8B+)*W-m%b^U0@g03H+h=q%dPTeRj5TVw;2U@yvqaijNc-yOdE0sdE!oOQm8LWcwb zvPS>{qWI4usc7VCDdgf}W@r3gcXz7#y9ura^7ra0x>qu1l*@W+h%sd>?FNRF3P1|= zZbh`V{x`M!W`~UpsRQ+GS9kSrxHBr?)ej6Lo3uG_H zt<~-{2g_b|`=9T*FAm_G(f&ius6m395oJ6G`(dhHA`zwVV}R^Nn`tA;NVLNfW^Hs9AWg~k&`b-e$0W2lP^ww;)sP<7yihp z>9;T5*j*ExLF8eyk}p*_!`MPt{qUWd(sve|0cQ3d(s?$MugrLsb=F0U=Z>3QNKb~xQiuR|wEjrfU z+ zcuJ(5)AJajIhR#qf<4<5IRe+i9ySGsxMdU+)OPV`G3%LV?Y&rh6sS?3B$pjiq)Tp> z7}ce0mnh`&H|iOQ1Gb0uRJX^%qxxmUZ=$BivYy9aXQU8){*Y>2BZ9l-I9bd_Cb^=f%{ zmiWjT=K4anJYD<1{Uv&xtE!H~-PThqR_6Xk6JGHrBqqGQKH?tN;Y{bu{vvRtU8UsW z(llN(rJar!7lEQU6Og+kl{x(`fYfSOcD-A(iW^ymja51?n_e*XdzPU6$KMxkZcJK` zJda&tRCKm42Sb&*e*F*DU{{L^KV#|D}q2fz@SkOgh!`P8>llzU)uQmjI>w+ojmYH8DUYso9od6~Q z;4`O#*N}<@^x4|bmeg+8V z#FzePXp9j-$0LxzgYFM1c8Sfb>{Ps6=4+{2OSxf{aUT^)eNk{Itv=A6UH-NWAk zbZSac^MG}0zqXgc*4EY)bsLAv>)zhim))%o>em2scaCgRq(g@OFY^hHi1@3ZH?0RyOJwn?nXpt|EK zC7C$;BzYJGJRO=@=P;0~`pP4Xo506(sb4GFpOQ5bebMSZAyb9jZC0r^8@4#6@ zN1(csX24ZP&hIq|Wy)DqPP#K}FbsE(!eN*bF(bfPb223XmHFutE}fA+X)(F@1;%yH zk1$?!wEHfeYJqddk074aDxPkEX(5=S7o}DSWlDe=WSG+rui7V8D&L^N{0Eoj{52^F znZVhj=S5qnyRaUmgH@o~<9q*IvbmW>Rd7#O2emhnur1TYHlW(mYhdexX!Kp=0`-Hf z!SuIx<@XNREb6dlpjM~u!9pOrQ+I)gFWa|Hkzd!e`}FK$)l*+RDtXAr6jhG2g7^(& zS#O*-Cp0BefE>7adHa;nq#sma9#q??1@@V^r}|p+lu`QF`_*sO?>*YP{B-=C+6Oin z!SJy*7wjAkT`h%`ZcC$OE7=@uwGN%GB0FWRvSBmnm)%cUkp!1DR;?)JH7?*wZ@)ch ztfdr-ApH==lQ4l~Yl$%t!PHH1zsCEliydl^cX*{BK4Bwl&; zJx=3~4w4j}SylJRk^&{HD^znI={^oh5?NHd2d1H_jh;qml_=-(7Web$GL)idHHN(A!9a;z33kdFv;y;z&UxF)%AeR4wcX*@Dd`TbAx)+%j<6 z7<;WW)i;(q6bO+@sG3~TKii83s*~Hn6H8`K%#A+`Q1P~mlINmHgw45@lEne3jJ17P zZh;hz8;0&UQZnl9P!Lf~yjjLORIsk_d{Ii_YQEQ%aR@^GyDuwHeJ}ihLhY_)AdGHg zhFSd5RVy#l^BEgfnr@AE7^Ft-Psf|qCcFrebXbLZme1nEm7HrU@Z!uVjNSN%-b3af z7qZUkhP=v4wwvDaxCFp{JDGra@o7D-oGogt?x{pR{FrAwaZ3wj-{uzAmKMqt2&XDh ze1?H=?{VpKPF0hrr{{u7|~@}3h?pBn#AQcs3}tn(ld_+ zqfibU?{%p}DH=QlEiIzWnzdn6rJbfL=v^pfoHN&UQod6xHxUy033s>27|xW2!iAIt zRMTOs@W ztWJX=uuAhKLfXd7Xin6FH>PB>VwRXl4F(L&>kRYIq}#{TDkrvZbm;tKBkE*r5OWJg ze$J|ti%j?`N}(DXhW1uC21DuyX5QdoV|XVRKtk$BO(Z z!r{tOk9B6*;^R}Wbbq0T8n^5h^;eF6;UW=V@uJ%kc4}Rrjhc5Tf!euvA3@BRs^etQ zTvbc9z2dp|@0!7GwZ0$+)pv^B;=9vdN9L&x2Rdrsyn9jC@59nFpfyrwenQ6#59tMB zbxBZXtF3No6)I@oO_M(F>|pLUp*fD=$lgK+TWWnF{Y4KN@A9dy_j=>>H)bJ&9@U?0 z-(8chcmAoj#}1M%`IE2FD2NrGL|<^YKj#l)D>KZ*V{He`yRLk7DCyeO=TFkfPHhf4 z5|@pgWJH9l&&_3*O#Cv^2K;f;@ziVC$~X*WqHORbe`PI1`*AOp(@oD_R*Ppc4F~vr z9G|JbXZs4my(CQT9e&C3$OA;PJWyy4wV#ru3Lb_&?NV=uGtP(tB#tEv(Qle)=vYk4 z_2_mBJ16D~hlCE6WQ@rh*$0;0&BE>^ZBhShUv^)=m zh*`f{oNA2|<*ho;N|huZ(U*r;ql>cY94QNLs#2$sN*iX*R7#Fg49~f#84)>us9SYM z8@F_Gk7c(OJeB~Djj!~LB-D6=PR&OfBRuiHS{2D`rca#zAoHu@6%XY^snOZ`Pe{_!OQ!llj z(vX$_-^MCRyV?lYDAQT~lA!9eCC;-*J^9Q>{{U0rLXqySl({*)}PenaIaXeRr|kqvv>R|%fO}InxZ~VOfnRIr--(j~C;1hh zD*m7*eDdTK57}_}{UZVr*caO06;H%}gb&))zDo;^G(P1=7mFuZQv=`$A05DWSR}Yj zz8ZepdD~0R;-d)k(Aaw^lnN|J!2D4^-n)OM?w|7@Y0vZ25q!@+PfU=QX}7$BJdUxj zdp&-GJ8nv;ER~ac$O(wj00ZDTP2$N1C|hle^8}xhQXJzom#1|R)U+mBjk=y|-iY!q z#0nokHC_*isP`5cvj%!?Ooi>_cRXO*x2mYm_L4oGpC8HQUTzGFv%d@~!e9%SuPlpj z{+Ksf!<#*U0k`m-)`OUxR@_@EbDXB26M2=)J>8l$jiL)8FEthy`J}zQnE^>+4bdYbk){#$Ae;MY{p}>NLh`~ z%kMR=#_oMtE+mw(R^lc%8j^q`_5VQoF zdsg1*Je;fRn1)0{M!eq2XZ@bD?`m0tNP`8$^5dk~b~$&7lVu};YMm&UAe?26^F0+) zQ#M%s*_)O?gbLFsB}@In8QSnvrhwl_hj}eQ#Dg$d!fkT2Bpahm|HITp`fg&o5lq3; zIhE8y*U&7&SRM}j)U^6QBW7u5?zLzorLgcxF7fF%IbQblWkITm@JWRX`9%5#5AFm3 z?qVtRQf9en5;}Gc7cg0+4t*tiyZN8fM3#oZS>Ty_0y23t91&jU(~bv4MIo%-OCEV7 zdAXNo%|)mFOFE?n)-|vp2eT+$NF-%FZ)ZJTBUbSk05b~^3%&W1_W4{Qt~t9odd5QM z`Gc4TcYEVcVve}3n#zgRpAFF!s0U_MxOA0)q)trM{)o)oLggxismMzF;OEj^Q5kik z8U%h>Fvp&;ufkk5TM9Wz*OYP7p7KApanC7QeD2)v0iO3c8vKIb$02BzG$mvFyE6cnmvjQ_D+w=-K2%xW?Oqod6 zB7Pl(^a*8csuMhcKhn@tK~z-am+?uA#K$Hy!*QpDIFp;!7GbNxPhhS)*SU3=9qWo2 zlrRVt`EqCX$@&P93(B0audju)9=r1FK+l*9&iT1wn$MJGx^nFLKC#uj7$2`SLY%5e z5ZVhH0FiQ_$qNYobPI9S))@hsvAm~!l+P2Ld$W0r?(zDtGFM~l&o`%k#PGW1`c9uT z>??a$mVcqFF?ro?X7rTop_TB;- z-W57af`0;N3i0G!+UI{3p4KevSam`(@cxay(;!sWX!1I>p<3VQ>!lK81nJ%b+ zaC{l?Bb+#xHQjN8jlax_iYvts#}cHKzm8Gms}{A*qiP!gaY6SXmN-qK^hnk2{Y7k zpFB~8I{Ao7f!d|!hCoNV8t=r5DH_O%vQ-`O3Ij(>ItU^Rdpg~c^&*XyHm`-oj#j$< zs@56Irzq;y_xL50pG(TO$lB~^%`SH3y71v^^PHT<)Fe!in!K<@`4L#QKo1UWCIb=t zsSrf|{N{PF6+M&c%}mwiq0W$O579{QuZikvDg70zm`~43QGO-r5Pk{^lf0W@cj{ei)J8*ALTs_Cpj#}WC zi~dg`S)L3Gmwe@?Qcu2X?ANb%K6(0%i&Xuf^x$WIkRrJ)2)z*Dm7R3N4yu5;J@DJ% z7QO8HEGp|}R620#Xlf#k_WQ$EfvsI1ws3xNbliM)pTUuFnkB3bYRZLSJ}s3GFL*ww zxCqqp2--kYmf9t(woiP*gSs97*`>%XM1f~pqciQDv}yl|R>f0=Y3Ec#{H^n0b*cUf zT?z`b6|cYwVav`F|FlPc8)QB8n0b#WsS^%EKR8`vz8C8L6UjM}H(wCJX}}#Cy?6)l zat}6oCqnMj-)GcV)Q|4apKY~;bkGOsbd9*rK}hyiVwg7);0g8OkJUmj^nu#&Wb_FS z5;q1k>;crXcO>Jx1@HJmgwwCUFnFk!9`{KFcT?=lYbe}3PuIMZW7h5sWj$Zr^BlSB z%4zYVvbVE{aarM@2NJO7Gw;HP>m9}(Upz3!lm*cu2Wc%DVucXK>X1q?uj8(HJQ3!V zH5Zbg+|b!G7#Cs98{?Lz)=b?9ouh9*RPDS7)fF4rXSx;gGFo9;pdPe&uUvoVIw}%D(mbE03&j@<0#ahS#Y}6C zzg;$sg8j!*_paDJnNWjA0=H@ZrE9_p`T0i5lkMTLb2za(w`2TOvGGEQGJny0X}+=G zIc^9V2J;y%k<5M`x8M=}etGa@Bj}g|W?6U1l0!fE*BF9O zF}^F8q0pPw3rnD`Cu$zYPicTy1ByF1g1U6(!G#XLV)}q zAn5=>`!7;UzfkT=LZNd~RiuDfibAh->%HQ=!8V?TnQw$@@{!w_O(zDwgk8>;;Hm+8 zl#60Sp%qZH%iosK?uTL>B>OK|n=X$T9`1z&gVyfS11B_L)WxTSP-; z**~VIOErBGO|wcRK?Z##|U}R@;6~V7UR}1!CHv0ERDFhD zS$yjwLc7+Zqang$sdZ(wtE9>m%h$g6=5H?YkXxGFY|&C@Bekzio2iik-la8wIYYD? zd07T!tEv{~#%ZUUkyPkHnTeUT;?8!xt5DA(X<25(AnGPQoq;pz{QFozFr->fl}l7KL=wwU0c$$0u6|Tn_0ut2k%t6$hP)9U8FA)$YI>KADk7I00%L1I zYcZ~iyUCV8VSB-!$*VTizC66F^K@f~Y^$AZX_?R=SDnRP}HCj$}I|n5d4*6S5u5RX2efSh`aD zC&D6%JG$k1@||c5Ek6vkOqA2j_uxgC>~=dUOb!t z)k|iO1Ez1pL-pzrsG4hNmHDd15X9VKIwubXrwo6&@`@dHvzzuz84AM>0`s;8f~^=J zZ-p?3-Hb z(WDr+^=6inA%7u{Z#8R-&)(D^n&^RbVqjksyK+cLGE`Td?i{WWKzBukjHI{d0YW$L zyP0A~m8zHpd!;NwkYLNY^aUAtn~9FOib#_*o((EbtnS?Eao=%w;f3qjtYqG!g$ZI3`1M80Xw1433 zYDuT%h~5S7xgT!7BmQdwUvjmbZ0T*t0K%+9$P9QPGmyEzXEd?WB=Pso;#S*e+%jtxnvqYOP;|Zfp=h!2kf%MR+7= zqGGk}Lgx^XfkhZ2xVInt_k*Agcds+D?9E$t@BvrZuf4Cmw{L!9J|Dka5Cvcidr=;a z==`^l2XsOJKXd)J(M7QlAPV>GwK~V*+rb^{2^|m*@jWe&&^s6+rSYDQ^n_H848ghf z=!SeuImw26j-NhEJ^LGci2@NPUqzZ8j57Vm05goac}pbcX)}pAEqx_0{oc6 z8=P1J$q!$?Kn76z(ZIe`fees$sX%?yQws0*twdd+*Ow1p;cyCShuLpwlJ9MVd(cIg zd+6kQu!kRSK^ZSYME;?KkoTyctbzGd6?==g_}DksIQZxIc{ObIb|s5xX-)2y6ESs$W_f{ivzGA^!rv!~r{nB%Cs92! zXKk}PxU;-A98m+b+z6F5D3dnrRIuE_ACKCfjd-^`g|YBr=)6-s`VJpTJ3e+`EkKEw^^iEn=}HPap7lN6VM~4Fn$X zutC%X2g+Hcm1c95+mf}-GN=fN`3h;OEzTc`$%qs~2*Wfado zPzd<{tWY>h!$b`Jnc73EI=}cSth`#km4MnUlg+@_MUIxdOhZSKN!zt%hJ|sxRQADk zFUbNBn_vbW%%oMMYqxL88&xk7gJTD!|rdWQ_?n zcG*h4WcftfU7B*^ zyY;4$6>n_*L)?0fo}9@|C@S7{4d^Xh>|vOp5wG(Zj!RjtDxCHSve@dHdvl9c7BjTt zIC$wdXJZa(ys>*f2KSn+F}3)p-H*(uM;l~DN0mvWTB$P3 z9XjJ@R28*M3U$A7Ej1-T2Hh0_%c#3^9?yo^x6^+>DLZe~#MnLu0&OP8>q;|((mR>R zJ5cfFwZus71_JM?CeQoIW7#+QSWIP;)}&MK$|`$1RpSAHhAU^P*K_W9S6MzDu-kVT zwS%Q3g^RFVH76n$0&tc#C0D&$Z+A zV)?!i&p;#EX1K%24(pd_RiUjSdzbcw@h>av$Iixg@X}!=hrvzbITP@5uTpmfYx5M$ zw!ElwFZ1IsSS{^@hZh-XZWfVM-KSj3Q?*2R5HuMal(y6{efA5~ z1T&LnfJ0_jz-Suospc$;Ad73` zU*~dB@=2(oS@R*|EEY{oBTPB27Hv@+i<{SaOLxumq#~@R6R&mBJr8g+X={q1hNdUC zK#4cHD0yiVNLDS5+pk?E@ zs=i@E$z?;yV`F6OE{##0JED?1BA)FO+uXLQWz)2lg;;1*JQq%SIIuwCxcp(h@UZ^n zY65>V$ftP<()hOq-99k>a)lGWl`}+10G0B9wk{_GYL>hexp!TZlYMB|s{{B{MVzu! zH_*q4xaq77m^Wm8JNmc-E?tE{NEk(hJ*AnwB8o2=j=IqT#DVJ67uz|dV$@6i1qWKw zhEU1Y2jtoPgf}pYcC&NwC*0&=OJ-V_VYscGPURO#MT;vN{XJOALTgdXJ6|k~Wti0D zm_ycwy8NrV4?NPkNa3@VEoX443umrYw?Br(^x-tNwwt;U0=&|(d)ZlJt+JbF2X${0 zGnd#L^TzQnc*+lx=$3y}1#1QCo}v;{Hbov+$L zIy3nf)T*-#h8F%Tu{n9=)>ZZ12n>H{ z(3K&i=jM(hT8Ctl@Z;WeeO)<+kv*udON*kb5&nl;GdIaQ@E2TNIO#j zqsR@_F$d5C4A3F=bZla?E@R9+-RvJahY`JR_;wm_TQV@7nv_1ub8HLY{L)Q zX?Jd&ASuon4BbfjVo&Hk?tG-m#&h~1YY(;w=6jNgzFa_L|BbP?ii^9;wzUba!QCOa zyF+ky*Wm8%5TtN-cXxMpcc*Z73l<0;Z+Gu=y5GIe_uChBR~IE~t^b%~jrq(!ZybBv zwQF=k-wQqN&xDpW!dg-ze)0}Ndum9c=kfnMg6BR7Wx#%)c>UctYj zI2usyXd~!5>?j;+R ztcEQ})Fp88$?c(Ka#E~?%U+;S$hmo5Pn~x0yqLMUa0BUtesm*FU3PlBcX?bR$n)Rd z6o4#8L{T^I(xcH;(AOy3NTEZZ==iHsaqf>)yt_wjcBu`W+qV!tJ>Pml*eNKI z*NxwZdAQevbL>vRZsq6Cf9X*j`r0Xb=iQ;RMV#VR-immwbYlx~eY@`q<5?4a=$;es zRG|@!SgHYNfF)2+ByHOaL8N|;B}6PQ9STt`WFqb%KfH_8A$T+zkBKS0*+*{kQ|aSE zCmb@OCKwq}@y5$hxRvD1+fjz)uQFuGR=LIYUXGcOOrQCE3jy&XG3Q5oi2T?9gS6PE zSVkW^sqpod?OQ3La~nIv_1&cR>p2~1QSrvzR=m*_=%xtkso8^i&eQQN+#7ig3(wgz zgY~V>N9;i=UH8as>Z;hc_p=-MInd$R&hz!@;{5z#jRxt2yEtcdfQgSE<*Er~?s*jB zXFNMgcH=`UTkePw;5%hZXIDW@Q$s}o-#|&f=-T%7+FZ&{`V5FEQtC~dDDf&AP`L9m zC~$9^BgUd67t#IUt;JPj(zz1CFl;4Bmi!UO&x_(K-P{?RC~}Bp*R-&>8wnx8Xzbkt z7$5IVi6FQ24MDS&D)D{RagO01KgnD|?qr*b&cl%JDsDJ26m+LMeg%Cm3q)oc;K^qS zEGgQDNeEF}l#mMbk7tyv;FH?yci@X-eXiryo{^x%v1J(biDGOz?kqhSM%{&MD zndH(?z~n^%R>5tE7o~oXyK+427*`?N9C_Ey%POchfv>+=+y4&5?Ik}#_pTylu=i{v zqy3U4iH)h+K9A0#|1F+f?65I1v%2HdqAsWG-Q+euLw=j1&}Gw<{nbQ#C)@@tgk>w_iX~Pb z9ggj=EKc+;xmp37?l**~E?XzxBBq@n zX%vahMtu8cBAEVr71W(-rG;b!E_RNdbW0Sv5UqZ>NeR@H3uT7cc)oFm0y4iWTBwjL zWn}4%GMi%1hK5+RK`Sa7=+N3XA@remIit2z`6zXfFmfr;G?H<)Itg$z#k?@7It^rL zRzeg1}G&58g5hLuSGb5qO?ncqDnuu71PVYObpw4o7wpYII3oA1>GW< zkcHw>vt^n91MdgI}HISUKD#lo8X7i21k*=5)OK@xmFYr4YvPm1^qRo|t zx#WkfD8|}opr-7wi&myQ?PGB7b`FUs*~MNGW3b3Sj@X6X<2=Z4M5VXCzOZfyEi64b z&fMyM#o$|TagMHE+-GJWSW2i0i(5=ZZlN#K0@P%}M2m?l!RS@8d6;l)W3Ao6UX&Si zFS|!WXd1iD8PVV{RbEtU@B!=Ypdf~_jKHhY#S2FF`o~*~_{|tXN)wl;EA(>dF;j-C zyKg83O*}B6oOn8vt6aV0keW}c24fH>JQg}K4&`YZE~idCU+QiML@^J)QU&R9P#D#y zF#*5w%A2YBYWo|ms?8FIbk+|m2Q5VL&UCg1(;e^KckoO=AP~?xbzIYvE698mMW396 zlLx$bn3y$Y@7Opq=TSDK&@I+Jo;eyXq71vsq!dVzj0H})+`|^EFskziD5d{k2eV1I z9IT65*0e^dgu5)hPuJx0fl7$fC9%xA2*QeXOK)D{_9F{9SqA4o`w9F|sZ!aA8%&;S;<%gqRHjwi^5O`VaVmhQ${Rk#EYrMQY_K^ z$?dT~5p2_3S8Al!U=54VTFhN%3^oz&%cr6}#?fF3OG1T~HCR%Z75KS?+UgF=cF;r~ z)x?r~E#`lY!|h3DQ&lu@rWg!KD^5y3#C;*e^0;85Zp7EsfDD;eQVY{!$YLuFrx#Gg z8Ax-BrDby+#X~@{rO4wQbH~Lc6@#%$Eb>Aod`Co>T%aMr^|TK;7}`WQ!UW*#^5yHY zjh80o(vda?g*9n%pu`W(nPeLv7l+_JiN|kNl*UtQjg#vA!)=ONG>%zMbHmKh5I$Bt z_u|G*(Zk(vSF!pCH@bHOAEAa>Gp@!N(Rp~L+1SKCEX#u>g}(q`i_+9 z1kda0UH~h5v4aIpN>QD~j1T2$djC=0FpQ2vG_w|{_WY_RVmTS3ebF_tHLlj`EKk@a zs{1mRdfGCKj>~egs@%5Dm1TVZv%g+HB=n^)*=rhipiF*+haaM8r}|hdF#I^SJ5%yr zhd0K)$D6LBoqq}mgP18t6NRR@G2?O z^5vnT`obE9M@V}>x0BaJLs#bs-|?sqS@Ue){Fu!nLv7aB_S7}-vBqcy@)h-sI{%1_ z_KT`aVUo-cPO%fI4{mWvjs9W_qm3Jj!4soqQ4~Vrtc0{($z{D%(;kF7@h%OF4|0MA zQbQ_%dmVnkw=4V0Ewd8(_f;fHDWh#*sdIc%#=8YxEPsy3wah`*MI$9~aFNVM8oY+V z58N?ee-{1I6#dgY)kB;G44F)>07s1NZ^+iBk==7Yi35$FJzf7V zIMhF*=l?}r%Tl*;!(BoLuHa~f^Gf1<`JxyC&PYVFDg_VoO-=}v$6t_%SZ;4vR(p7! zD263lQJj<_4>7m1pL7bH>~;r@K{pX^^}#0*UZVccJSZb%W1~@CEky_9hCkeNP29gY8&>G4bZb>-K0PgFCJ8gq8z{Hv{Z% z&;l5@_GmGouX9+w0V9U)I(w0OVU*dCdsL^{NVkoXe&k|}Gh#P7;1s0pdU-zMwLCT) zR;vYSyK)ok=xWB~kyNBMZ5G&1RE=S>)6}nE$tCuhHV2wUWZRBeVA*^eV@&T4KYao- z8>J@etM7A@7(4*0RL&+k3^~(YV$bu9)@#Rx?= ze3jW(AYMZ^vk}*kf`#Mc!y~SdQi!|QxZ~^%Ew*}6dZp4A*w@^B#sC|%2y(WS&wldh zD4iBDGg=j|`ii=4Cck<{^>+oi@J&M(Y8?VB=HITKvm9-(E2#I913ZSFKVKY2Se=Hx zJe3-Ch{*{?p`=}7FdrpaBru39>m8F11i)gUN8&E9PYLndHdk*H#r-vY296s+4#_nZCzlQW4W3f}v`#qflyLh3FkP(s)bMd@6~${pwe2gSWmhZr**B{T z5En}1-PoKH;=5FCS$31_wiaX0pv?A7=y5rlOxGN88=X>E^ zQwDj#0*=q0i%R{1f3Rp(89KMi@xGG>xVdzOD06R(Kq0@Wja=V&;S=s5a{mckK=v-& zmw$EwCp=F@7BIOuHel3WFJ_RFz$IV&6}ID?EB&ThJKvZ1~x6MmPge5 zt(SYdXt(59X5{!;Xk>fv(4V&5h>9RVHi}I}qrn&%#?f~i&jPKw+a51q%CmSk1MDEX zKe-r6bb+qh+%7o2ekA2s)~|-R7IaU9lYp6gN^E_a&~$HO*Y`}0or?{lth{q)u=E;| zI`}+cO0H^nzhsK~>!OtU&k2N0OLVYInlPthjkzZ2?zWYiku)sQ47LbfGme|WDV;l* zb>mOd&2kDRZSsW(mrUaRVkLmJONrT*|Hs&Hz=dkdo6?%J&MdaFQHRVK`qY_&J%MA* z_bu1cSSTyp`fE@g{Xd~mM@Q{e2?*+Q0g zVIH8a>a3_Qfq!l3jX8{A+0iQ!cZm2NOV?bksI0G3Rp0dotW5Q7al~fl7a-JGHl9n% z;oE>%4dION#<$t49%%DllWnO8T5z?RpbziccFCUVExL|4hwyOGgT(ixrM1zBV+8!d z^X4{oDIEy<8`?G=1FBkjj!DT+Iz@+~qsPvnA3m$YYa44D5f~z?Td7(CU`g#BkaT5N z*g;A#HjrA>$k~FNN#(p?3L%cbt2&LNuOsx5&k0 zi`vIXe2K4EbSAVsA**>T=5S)3*c1CSl;av6fEaX3VUx=A;q+f7gC%w8uw>&q111fv zZ_bP?e{e|Fsm${(@fT7x=0DJ#{)DtoXz-Enn>m;)G5VqlC>JHg<*H(Yt{#e#jGs(-y04=5aS>Vo$1;BY%!$&ez*ZtGli<{b0rA5}r9)af6 z>H94&tGZUGoI89eu}dOeSbH=dzRfAM3u-hI)p$zs?nqeek+n_bh1t+8m z)Hb4(LrOb-pe5*2>HcVG!fJtDU#w>qFKY3{eoZdw1yHgW!0E(yWylc8FqYQA1TEXD zbf~B#yG!7!%30~Ut7m9o(CKG@2tdj|R&9;>!RGlD3(LDB$M;FGK#^s$JHht}O6NdN z5j`8AY_jR?2}2T60dlmdKv23$I4Du{j`3BsJ8Ex32ISc(jt3{Hr{-@x%a%l+Y?3b^ zAZlPBAe{fRv-2N4%im<|j0~(rEIuP47KSeW7qFA0q%Dp6Ne7%Z1ui*PI5ah{Yd3m? zTAi=&X-f;DfD<Q_M{%F8}X;?4MUJ}8nu+pvwssN^tipM>X-sA+cApuG5Qmea`I+!?Wz zULdjKq+Jc-)2)X^`dQ3fOXK??zua!=6M0RESPpv-s+8Zw1a+M~+)B=kS>MO*EY~-G zqh(d07C`WlE$e}SMClh>Mj6YS9Y5I5w~9o;u&J4TY_k07lvEV7LnK>NO%_n(VeD4k zG){-&tGycc@kilbQR7!qRoa)@uMNncI^CHCej*vD~KpBG?G4FFP`b=h3c<5 zL3cO5n2TX8Ns;w10gjIPBve?{hVeUA_R9O5-CF_W@8-PrpMl)(WyZAYU+qv3Uowv` zc<@@i8N{_o6&`Tq9z27D9!$ZXBG12K(1xTbPA-}(BGM8IhS8Xq8qs3B1V%+B8<7^+ zHWC<^RHEO@de4X)Q)d1leX>^6?%(m$myvMyrdaKQ;co-G*a_oPU!!}z9L&OeLum*7 zPmeF&ND3Dl8U#cd5d?(ze_jdyNx=+O5A#Da!}c|8=wRW2=fXhIpo}d>u)ZRJjc;81 z7VEmf+D8kctKd-2vqlS?v^Fz^QL*VN|7F`mpjxPht{s!yY)v`4r>erMRi&o=hWUoM zc$@*eKvtV%c!_@cy|wR^bL@3Ik>r=-2hr>LrfgyqTnolEx5{gqRP6DN}YZqiH^9hZ1}POOQ87awyCk^DBpT` z3l3r4lFjkr7P(P=KPd$5rY|V&t1lX0jgKn$b(q*dDD$8`!MP{PJ zE5l~1+~7lOtKRTIY^&dZLCB9i=Y=mk$3>Qy@RIJ2pb!a0q!k;)n7WKG;lbeFqDEWM zUbIwY8gyU}?4CS_?RN6mrP`90btKc}IeaW0;4A;$yCp4flat@16AYKs;ImANuE@Ch zgjeDskVwH)h!u|vKg!~VjeNZ*;rLlKir#Nb$sHWzp0HE zKRf85#yP2R6x6LR0UblQ6mtru36wz`)a5ZMZllv_vKg+$cXf8K8lG(R`W7itv%jz9 zv?A4R`t4b~3Ju5+WhP@p(!*%A3iYs7i5Z);*uc`Bg?t}Bdp&KIIwJcE@45+Nsj^<}B6FhfzPXOMZwm$|zrP`#no437g$~jT=c@NXr4r3@7zb;@7=b}XQ@+T0An$s3 zy-I}GgwuAIcFneODJl1gC!5vLa4zl^6=rI})~MKOwK&^^*Mp~{|Avzd_dePHu_OxR zWb)10*H+H|oQJnRcJi%1k+|P(d`(SDgv0VMSW)SSUaRS@I;h!^H^GA=#VyTK_(!gb zfYYG+%8EPLgbXQFz~R>)fb%Mo_f*X)0&>nVZ#B25(?d$K$|%>Rf;cJv@dw2r0QP_k zOmz*loRlwq%RV0tfGOWV zh1Ja{Gc3yusN*N6APmm1UxMQcFeAHY`&woD=fh3C)mv^D8)0T20-M2-Ga>*$mbt(f z1%u6C!5IU0aO@~2&ig$|g^hi%FkNU#y1!3!u9J2j9UkFJap6$er-4nKm)0;T+aZzzSI<)c~I+d3DgJ zBgQ(WOG%eh-479Awz40?Ic#qbcB9V~pVpl$g5StF&L3iwtG-RYO7M2I^rT*EYB@}5 zlr_g!Sw!cg0-U<5zV5mO>=Wnpf~@J-rwvuM1B40%d&fu0Q`&tx1iO7|^cpZmgR%yk z3R=5%0-k1ulIeTxr1o<2aCk%Rd4Trb91M(hfeW=!m(`+T%a$6+4Xmp63wOl+%y~JpiZ=VoB60)9h8{km2x5%$kR_#)es^mlcUlVs=*0cHly>6$+gT)P zh0gTZSy#^7GE9M#%m)`!ff}C}tp13BHR<3_=&N zhGv3Ic(b|b)`N`6#z8|8f5>iIN_kVdk@>3U3O0t2@!QapkmnpkH*md(+p zeb;W_Ru~AoJ_jTh!CuE~h&ESGm1Q@S!L83OCoMmdp1|3!FIQ_3KGq|gPHihQzCKSV zg;&`PH!e@vqQY+5n$J|QnYWd|K3A{+YYvAe)1X?2DDx2HFN^FgdwCser(jP74#!{E&2X00Yr`cdQbzgR(vimiMd zNA_!FBf*@-xW%lsWt^?AddX)l4RS4u`z}JI?_+2-`t*%_hFGZ=Kr#Eu%MP%k)L($G zmJ4d3&3jkMSjX|y*e+@=vvSxMI=NLgzw|}!zLAC4^qxh<=~(Ozou9>_&b@!|%YIaA z!KkG}X@w_4p(I*&!v^|5IA;yZ25P;#XjY0hN}IVag2>$kgZ$Bbcr_#;B*Go^li_D( z@pm&ocQqt+jYGXAm+EL^9-TvKv&W6w9thfs2KUHIKz)is_}cbNwDt_G{6cBKDQU_p zYRfCknnMP{ps>$tQlsP=T)#r;6N26xZw zKx7F=It;ntA6NaNhJ93Fl3J6j|F}!XFB~2F72h3((74h!`%-q2WUMX!@V^_rJG`Nz?Eck!gZGL3RRbAlc8PT{kyZ{bF%57iM2edLIrX5&C?+2PYI%I zXt99XAiTJJm?DmB^RJI}#5Q}!H_^xAW!E=&)3di-GJ>FVyjz|GE^vr#Nh25g#3JFv z;XjkU;_aN_Dfh|qxh!lASsQ>JbwyBB+W|-gsv>tNKR_GVL`ua{xA-x#D^v6cF*-SA zgy^IqVrH*F($Qbn1$&o1{-!ImiF^qZKJ|p$=j$(U{XdcTbZ-A-HPZcqRB!)@#i6(V zKP-3U=_z@tDM?icN!f|X3EHvAnSG_lsTpN^X;~%4S_Q`1MrOuZW_AU8R|G{yW=7V1 zYRd5$DOq}|W?2R)nK8*F+3B$ZYU(MPu}RscL(qSt34nd_zT?-~nPxse2;Qgc{k+cv zF?6x8Hl}klF*32RcmB6e!51TFH$Vg*eEpVG2E70o50kTr=lIgy6AqZLFN$?e<0)rt9fNOYGuj}+R!Ob5B z>a#gOe&>t)>d!@gDVMA8>96f(EjrrupF#rtS)(#1^q9F`VLKt~oQ}h(Ntw*GrQ*F% zNmvfD^=MC8BKh2eqbIo0+itJM@4)_GFL%kHP=t&qL%JK4{a!*JA}6P=Na=+yHxCHF zt^HMZU`>NPwoAy1&-40ky8o{q;q!a{pIHl^g6|gNK(z{uL-Kc)}-_4e4&hh*MQ z+LrPsZHwu@zg^JquZXyjv#_0w%_o;o#=_R*6T<2GN#_5ruy~S+?k9#HjSmbif~Gmt zrfPc@@vy*ogFj730(#C|s2q8IaKr?A#YR5`Ubw)oe>1R7IIWHU2UEG)%1~beL^mhq*kFr#ttMPg&%1fPIIwM)17ddY(dM{ zm|$7$c*Jfo;UWY`M3CXl*Efd-R7=iV(J%4Qjz&GvG3^8xSn2gn_H9Ekp>>@kCSg}n z1)p>MY^Jm@X4Lbk^Z3!;%`sayVqbB}olO=JP<^3q!Ja=3Qe-KJ z<-$Q;E<*?*5BcqYTTs~5X=TR$b>6UVlCe{Vv+>0vo~VIESk&zgxe7I2)8d1b<}1y& z??NZr$i&i9d2Es$IGSSYYONr@1?AA(%%h<6acCOJN4{u?hF<`sCDLgNrmGaQ^M8w} zk#@Ym#l>KTW}``wng=U61g&mC-(D}k(ijVRz*H{yl9sAq9m`Qu*=23j_IAk22jwff z`-db}Ovk}+vQr#mjR>R<3lu+799RYM4mEuD1Z3Bx45{h2{5xgqucgTR!z8Boa|xb& zzWx@{{cGj^+Y(eTaQx)h|CcWt94YwMGz`%X^Cm4g*TeQuE8!UhG;q;A)Irx$Itx(A zU-tC8{vjyJI_>uS_X32bTj%fYprc43(83}?C)T#jJ2q9RwWG2dQo2`GQxq2X8!n{R z@t^gm5VrdHSub!Yk=Qh-s4l6rk`_HeXld`*BMJF?sGC;9RFZQvKJjUskfd(de$j3| zY2k;2JDsw?cv(JVNXOn*=F_LuU7xd9aK4#e4tImyciMp9F6T66vLjC>80~na( zSo`z?)DbJw*nrCO7~2e;x5K#aM0yhWq__QuJ0Z;3C3H@>(!;e}M3E zgV52L4pavfLlaoZmMW-GQj^U@sis7jaEI6+ht^#Xq(zuU7#~&>a_l$eE)h~XxC0-* zLj(0#+V)Sr4(P1aR}7U;(G=@#GDYpBt5!HDQsD|cy^}|OPo^3VCx(B*7!YbE{BD~- zTyLsMnImY?+O58^d|BeXQJAn>-!w2KYm2Ld_?7euE^r>-!mplJ zT%MXtb>FVVW!`$lPh7G7Fy#%MAzeo$0=HV$cA||Az}Qm3+(XKM9iMD2XzlXd+5d6n zJ&0_X&H;;hj(K}YuFVZ`0f)#Wj69(uU#Q8@Oq5)or#TL_8c_TdK4ZYQo3WW5gdX+fWum-4aLQo-t7dCMT!enzWjL%H7HNP zV3~Z}`VNqJa%GeuFyizrFakKW3YT--=qjfenAY<#M4mMn))d2~5Cf;MEnfYHh}A7L z8h zh^<&eLd+I{lpnykh;*~L!yYL4S?^IILpVG5|M0g3-6@kH%*Em-Y)v#}nB>gs0ggx-FUGem-(sOAidI2#zY zh&d;jIr0lCjRE)_rdq$~Jj;U!_2a)q^8ecHot3i8e^H+UKOa1N|C=4~&!w;YX;0aj zeeMr?gHH_ff6ao)QknUrK%@OJZ8tHn$-67iZwk=LQ!u6?`iz(qhQZV!qr!)66<-#^ zYr2kIB|q2nj%7f3-GYXZ4>$NF-cGqz30YZkI&Wn>b$eWI*lj(G%rI<&pxsXPA{`-M zS!^k>IyfreOR5+j~VywzG;xQb0w z%vugs{rpJTD!(a9AwuTOB(Af-=UpDbhvy*Y&(y(tt2UUPr6OTg*NX)iA>l947mYoQfXk^i!S#*U@1J@ z2QxQ8j~Mw)cm)o6Wb1vaQpc)Mov+?fu#6`k=k{qr-AjZkiMVJ53n}^h_sdXxd(h?w z2sYr+WB^%EIjsH{L;2MA^&>*EmiFM)Vx{5kjR(tRxH4Nd;F7H*z7)|SOkwfFzHwu zjyQb=&j52A)7Pv%WBN0lYwa?HW( zarYIRMDIjG1c=2P^h@kN8wb9$KO$>cd);0G+mrbMnewtor?uCd4zr1?o4SR(Cg_nF zkUVpjEWC662=|IJO^(DK?x!B2j^0b6y`ZUikbi4jw#7kK+WJ;36wlKtOhDmSF}g!P zH$f8Obzo73Pm&!K+EXYKY3-PIga1nMPS3b{_VSCxM!!Teo>DGkDn2nojggH_aaXpU zH$LH4G7AvW8Fsi#Kl+Y|_v>v#Dx3|0kueJzq0pCt13sdNMIxa77x~y1i2v)r{k7Il zND=PDKSinZ=X(DiMApBZ_Ma!ai_Pbtqt7uICjU6<|9QkG#Z5_pF`)&^zp@lGHEY?> zob_KdszR+K1%w7Lw$>K?cE%}=OA}#cIkRe`ZoT>9P01uFjZPP!xp|Pi`5TCO_viK% zatD421$LF$U_%rr8raKq98kg+@S5i*PgsVji0t{U;(+WL0{{<}<}w9W&4F{x7$Pl( zbVjo%I-2ko6E1HZJ2oWFx(V7DOrd@d5*` z$^w|?A0wYs&yY_5+5hSUlD7Sv!}ZT&=${JY-yXt14J&uFLv-IB;{;7|FhDTKPqmWt zSYs!|FA=ki#QBAQ@3=CNjq3((GLRfB8)<9c7ei9omq5K)b~Ud$=ylJZQYb>5i+Z*B zzn1gmKOQ|^d3|2DbE^2vOkEo?fVE))zmGHSv#vM0oqkVW4Si($efiF_l~f1eR-H>m zT!3nXB7;q=5h;0h=b>ki`3J>AN^bpM0t?`p#cS6RwMP{$3 zf5YpWx9YH`H(#WeiS>XF^CvIoZmh-lGlgi-e+{_h8{Yi!^Lihu^G%^Y=J}bEP=IJ3 ziy-mF2-~H${~5-Q!o!0yGUx8e06S*KOy>7J>>KEl^fOt7hf06Vr(Ld4S!ktk$I!o38En7`E1Gm*Q%|uEZ}mZi#3G(m+FYjfWOvb#E__Z- z@(F2jsA4hk!V}px!d;J|`8r=x_bYB$J1ll8Ve2jmxjaLGP=xs4h~P@22`UkOuxu$Wok0kqIKZY z7v{k&6b-io3)$3e)lbt}Z_~fO@<4lyx#RLPgJs(e+HrP2uvELL0%L{k5$EjyTCw_+ ze))JVuA@XZtJY(UUm@n~7IQSIt*5nZU*U$mCqwT1D3z8{4@xE$)w^EVdEw%s?L;?S z2|Izmbm=3AO)bgLcxt0pe<;d7iUbn__kCyk1MyTdvmGeC8&7J}*?2$SH-VwY?XfoQ zqYq<9sSl<$9(~Gzm3ef2XRgI*4*Qs^-iOc{mq!7#_VR<*pu?P9q(kH6HYv$S-z zs5+&xfW0mB?!dqQgwI_$GFrdABguYE1tGtW{WA+#TpmLPEWl=W5pr5Aw28uX;H%y; zShKW7UZO*hqu~tPrf!AcpmE0#hMhK)=uhlz&ZpnD;{0lol*8qGnCqv4KG-m|@U3b4 zl?Xtc+Q$Xc06T3Y*XQIe)_)JH*Bb%rJJNTuo*~r=xnwEUULbeU+->~Au=|AaCwC|2 zB2ZvgDEj*SPVt7h<9)_mv`@5?y4II?s5&t&^ql?IDp)~#`3~D#aLNhVyyKm%5s< zL&hyL{c)Pza*a=VISCsC*4=Tp=uQtG2X4&bwB(@~G@9X0<6#s;1YfU8ceDfM>1HP@ zBy+SOJ62?4+$-QM&((;$^prmdeX|xh66wMGkkKp(BhH)FWT^D$W>Xk4W6R9ON7z@% zVRY~IWw$uox{cpl(D*&pG(%7Kh}q0AA?!&l`stAjp6n;qfF)3#d6PKfdL*zVS>}wq z$k(Z_&pyty-RR4EK022?p=*_MgQjvXnxjs<8w|nxC^pBs8a0=({ZRGv?T7#LmUdbo z5r#Hl!!S!-O;@J)d>IFHl5f20y!@7DrF^ z_z?7%h9EOc?w4A3|Xj1uxkVSJ`K>#xa_TW471++r*d$j(!vspAXkqQ`^ zoZD|(BIil5=7YRnGxHTWM533mjo-NK;i4vPo| zn?ra*s~AYnANQ59amix31-PTaLuxI)j@uiXoOo2?&_`tlv)X@EpUk}*8n&pEBNV@j zfuXm{97;!lkNX6+v9zp?5+KP7qR1!wDyh@V6iji)=-R7v3v}szoJ4rNwSbSc0fn-6 z(%(nYl;3B!l$3VIQB5c{^=Z(cU<^xg(h!)O2HJD0|Dx{h)-S(T7;?(y9>PQ`{RE6a zN^$cR<(fZBWlxS$nRU8TMF?c>_K?{Wy8Giv%3+vXj7lz=Xr6dgwdby=-Tc90f7Rmn z^RwmZ}Z^0gA} zpDV7;h)ustN7vN)e6%NhRNEoJ_;&(`T^2pSMT}J*hGEJ`LU99Th$e4vERN_Cl7g#1fFt#NO)B_sw+ciHwEO-DTLU;d;C;dye z;aOqSO?_&%jn68b<9}bH{JlC!Qnis^P(bswU71tl#@~x8%r(>Y)Y<4)?fVX?&>J1e zdSv`?r;hj;u^UCIqcnnP3%jix85v47(NBMO6yqQ~NFgO$8%!~D(h#Rsy^ znD7kV2Yq1PEh7pH_)>|VT8Le`$~LvRf){u#Ty)~YrURJVsxwRhFE305zP=MK(trR9WN;qn5XYoiV7$sVI!-sf{?Fc5ip&cf@ zGudnOY3pS~zMc(WnAgRHe8MTkv!F<~v{cir@{BfEiuf5tq};~7zttC~cORaG;|u%A z8FeY29j1eTQTh7pT&GEc=CMS64QhN+jpHJHpUzv5^nu@Wie3C#1CM*Fx&F-9i<)v5-{o*hh{<{gKx zd=UIIR`8%zBdo!+hVyj&mt4`*Z~b^{sH4B1EheAOX$fYz1wCYxsp=EgFdIn{NTQ?! z>J?uF%QS{ge*9-;b6~hcvHQ70Y(I6h!2f24sM=cn|ETCL)d{7~I-ZYj?3)4ecW9*B z;84mjBN46j zdtzrh_wdA5IPhahW^WKPnw#v7R@qkTkoZGzAsT?)f=Pb)y67CDIEV%IE>TE6pUgTg zL9ZdRW$gY5xw!Ci462Hx`Qj`7ql{#Fy#ut?rfFT}i$MKVFUxodF!_t)=KFcy*2A>u z_moV!M0=Ff>}`{iEgwBS$0#Yu7Ct48thL-)BjwNg@UxAr_*gIIEQ=QTa3jL`EsI>+ zjYuZFeNxBqt$JrBLk_Ion==8{S`XJPyZS~xDjB_0DuZHdF|98P6U&+5+OyYb`R`>$ zT&R0d)Shz`lbzwV-&7um%7g?{!VTcX>T>tUF$!9T7@uYq`WTncHb52^h>~r@jbC@! zF~bd5ftDhUhb}_yZU8OJ2*Xn2&1ia$Rn;Sm7sb%p)Pb+^?Qaz*iRYMB z{HOnwh5+*Sxe|YKy#HBs{>$%G({@@=L;915HBl#u0zbG*K;&xLx*mU+% zHqg$khwqNi@8cEMKjqd2kB5=Wh&!5H#zmiJ>!cUnH29&|eUi)`>>g%wknYx9dUyk7 z6F_YwD8?T>eO49_%SzluTA1%nJDfXWFHX4~)rVA0=CF*0C@m&A?1f@nad48XnLNwp zE8bOcL>+B=P|RwkAvBVx$EsRlXod2)+=E|ci?Bp{RE($15ivmFA*6U|Xd=mybqYmB zu#QxV!?x&{MxKG=LW=FctT;RFq3zZuAprm?YtBkFC#wgKRa#~&N`;=AcGrK;T9x2{ zZa3FXnyoEKCn}04|aPM{QB&kb#R?2`EaZAdyVs z4NDNhxCE(jNZ3uV>6F;=aw^FftX^83sJ&I4mXoE;^pDxhmL3*Or|-UBbQG$UN9wWJ z_Gr&8`m-^ODr@p$q=n>t?3;VRW@%$E@F%O+)E0^L7Bk0GL2V=ugD4wo6jw;B4HfBf z9$QHbDDg?Ad$rCSd7b5Atz^Z2FIX`vgL0-hQrrvRe!D7O@;)!aZEA8H(l^x>vYSzH zF35#}be?z1YA86ZtzpZPL0PsKsJ()yYc7G}B+!0WSBI8hz}D*h*3E68CD?Du(Q-3L zwfLiSYE_|iz$(4oxcw(>fV}v^$3TkoAXSCoanpT!u9y7iS?o>u4w3bIBgSsNmYy*h z1W%o`nC8#GF4)IVaWQjh*sHzl$ltI8`Z9gLpT7&a8Oa-am+p{yPkvq2i8(iE=2^<$ zfPFOR36Q@j3jh;(h0lq&#C%}-VI{P!Psq@u4Lgsezk6)uhVSyjP^C}(S*wqO#C1D@ zS~bMmaW^{IO-E_6a?kb0qz1PaVOn;FXEYz|4+s>)b~lTLRcW%R!an^43kyD5MPZhRfWFGU)S%1LxsUp2 zE;Us$5)4>8;;&AzRRzfgNC~-xa1+bPl^r;)_xBxv+pm=I0VetdtyTryvPe_gPxm>Q z_J?3wcRi!xZ1W~0RUF-rRWG7hZKtOXPpeMOs9HYA}f$?TK=>Aff z@~*63NyAagJAJClb{y!OE`fH6#4%WiTMHeu%x(UjE6ke>r+;+=D#;049D6mD``YoG z&czj|I5U=`*T2rGnx~>8-_;gEt>-kGvt!5mgZR9IqqNN_j4}M1cGl@?_vG#0PW6A?WBxxZ#Uv%&zmm6n4Vn$Eva*fodv1|bVpO(hKfaNee@6>|jpKc0 zmjqmnnyQ&nkbM;N6v_wlKZCs%guAhSL58z%C790YIL@BnbNlo5{tmx`^uyb@_HUue}m7G$vd zI5w&QbjX4RGH=e-Y&Izgagbzx3!ogC(g+bO^w;IE3jJuu$#Z`eX#9;(yw!S zpqe!4ErF|esQfMRrgi`X06a6k9B2myu~agt{0Whb(z^ZbHdNFoP1U`Rw+iEw`Z*Lh zc-Q<@l@U!(xBZegoF3uD1t$e-JVD%mQkJBR%(I{VA|=?!j6|KU9U{n>$@I~$??v)w z;98=w&&N+hv|};w=V$2FMt0i5)=+sWdIPSZ{3%RYClgv0faav`D_>A_k9tv--8&ZBr9J#qbF7|M*D%$3JfTZ-)QB{C)K` zKU{V6KVloB^CESsBhf%|)dkLE6E48FS@*JteR5g64Jrpodo5U0D-!uwv&6a4Fl{O6 zyxHKQB8c>4AC`!pg{&~-0?8cDx6`0ER% z(2ZLil$_5P#|*_SXgAVG1ao)lP8|c;Y;=mTH`h;FEC+AV&L7eH=(oPA=AGnnzHDJN zyY(8rx**=d!{-j2ao-WU(*`r#rBA%2dAsQd?8cs>gDy?imSqbZjXjo|oeM^@$|BC< zxiy-8&F?g75yZ_hS70J6RcoaOB}DxY2bxH-g$L62jwV{5Lq#NOQCbAvllj~@ER~xF z!#Fu8vcW31=Vdw4Mn@uZsWvx;o337|70o?Ynkpam4QGJ_evK`nOPAK_gOajPx0c3l z311T3b1G>1Y9>_!ebR;7%-$Crbq27Fkvy4Prpl~klji#|FZMbu5h{}kA?rSKV_F_@ z%ytfQD=XwVo~bTvrXr1LW!Ertm`NTalpqU3>{Ao;$bF4x)NFB^q2&7P43-oNqy|O~ zJWOA#xsyxq7&4qBfdI+9&&Exo9v_JS-<(9XP-HJzQOdHAaG)7)hIn4(~N< z(mX#}|4=|llvZ6>9**iSEQ8ArOdxb-R=bTgQ7?tQs&H+yls>WKwl6GCflZgFs#K3Y zRSVXd=Zag)y#b|hcAjYe`KOt-G)ar^a+KZbVJ>4CVyU8S!L}rhhya%(*@~j0-UWik zAX#d?bD)K@tIHUpq>-%Z;K@w9wmwK_Cqx#M5V{!~@3jp<mvau-CC&*inT)s~J(4bQwef5NKwgT8Q!5-TWq+{Q14#0wUy=9Y&Ypv{g%UCpcdp)X5E#mp<_EW} z=2<&j9^Fa z3EXedVfxY5Pe70wdS^%p+8aT|)B!sa1#u?2OPp=CV&*`*TT`71PI~dxxHQMLqlV+_ zWUMw+Ruw)|rszJSTi7>|fUNVG>}E*etC>g}1Y86wHL@tqh2Qqg*qt5}vw^D+3tB2+ zOlY&s^1h6tuT%NS6X}W?gx*ns*7yHkXhnJ+UWZ{Ae>r|Qbk_)fB06SPL%>VMWpwm+x zP?NliTb!q@+a~(@Q&ktpE>{)L@>Q;Wgg8=R663fb>J2Q!78L#C@8y9LY2?-4vk z_VD8&5wvW2OdCi?KW4s%6jHk~mEnZx$`Hrv=UxVFN3|Te!P+28e*l_yVLT?&m0tzF zmVCLhb12&m4(bWEq&P>sQiougHCV+$&>Ci9;0S1h?h-E;Zi(Cvg;uYtg2|5aE7~sA zlE;v1(-5Hja}8O%R<7?(FE7NU;?vf~)EeP)K62hF4l^F`dlP53jXB(ANaPMf(LA zfmVcBTft7P)(yoHv{zG1JXx74PpyZbHDuCh3Mb>T=t*v!oOq@r?DC-d_s}%(rFs^^ zZ%d6I;6Giz^0sukzltv_A#7%@4AKf&qE=|ox(x0C`?lw{Z^gvhO?*eU;u_V^d6E9W zaC>^PN#!|DT>$3Zp&}#PJ}eydoid0XvIwPB3pE)2kBgEmre!NXABiU*HxMV7`Uz$yH)SRse@1O zoS+s^kzqW=Bmk$B17sAN(<_%zh&u=%w9Bp3=QzeRq`5~r^rb{D=12B3v5=xPUc1&8 zCLh@^od>R{eDUR3l*po`zs;uh_X)JBHCGHG)wgq$QMZp10IY zY2=XbVB_DLnONUkW5OQ*=0V-wY%*MU)o$!<{o_(bY2-BxVcMdMSYM5)5F15&N}G z|I&m%7V5&f_J#mGGD9nSQwLKg!{7TN=Bk*{ZvfixElr(VcAT1vR{dY%M0Tljq9WV}=Z$6IiTT;#oFAJFftdiMJ#wEMeD@ z51w^Pmnw7ll|jVNT-J<1oIp)}pY^abX z$(&%+DJ@cldZ?NZnM|!;*O=buWpVUW))kWmtz4NgS%^jN&Ywi!&&4)DLbp!JEbFytdRjmC-honQklfu%p{-bM^z#od$M*bQzSw5j zX3^Lp$w2swMHoF}`Z9WIzG8}u;B1R!=LYOf1}#jITe~~nQ;Uw&c+fa3Lgwn6)nstW z)1$tR#Q}yfd>bWF+Ob&g+BT(j=L_^e7|fj#j6cl=k0M`f(ftD-n>(#4Nl@}rN~tYS`Skee3!%|*_nJ(j6D=FB{b~2vk%t2&mxW2z+zjLWk+q27P_>9h-);iNanhX7PBDbcujL#~5 z&?(YirpKTZD!9x6l*G3Iph2-*)mo%M7O15Y?K^}Ss$rFWCN3IIg9>mn9T@G zzu)<~9Bo<%^_osbyA{t=qjdLDcdWLqyImc2r20f(BX$ETg5YLvVam~48XGJG7W5fN zeWs9lWApcxW-#tWmXVqY?yLp20j9M$T>My(y|^v)LKO%s%ODWLrLz*#LpXDdEQUUd z+D6kuG2t%RG2}8P=+solX{al2{qh6tDmV_>7 zZ>CY~Nt`JNL&QdxG8121=3=w47?gNkfrjE=GvVnB4cFHzeYMiDr zF5_|pv(dl>#!gc}Y4?VQ?1O6TUQNUqR0*hUlY5Q4E~dlc>lW+{v_{@P5fZYFa1c#X z=!TAp#_q{|jfLfaxnemk*gTcZFm4I4BIwKCaX7oX z6_wEK72gdI5+u7z4z_teh3zKKJ{7F9Y3tIuRr_Ri@Bvf7W=@p9;Bjmp&Ktwe1go@g z{G?eWDaFAruknsF)IB5n9^l#&E2dmJK4$G4!9U`y0i_Q|(feC(gNFEr!T9rk;$TvS zqh@V8#8Bth!us%G-sNs{bO&Os3lgsVUB}zcmS08vt@n65^4Xp|reP<{!%*rXNk~aK)LSlNoJNI(_qdG2Jodh6AlOv+{ z;JW)ApZ^)A@XJnTtgl4;2A{h<2G$v-cp><^qRl*d)+?YIEFckv*!>B&DkwlnP&>%I z+A}uR>lB9l{8NnJYkY7UV&6}?r1g&u1J2qohUe>(H(k~rmv5R|-%=k|EDZVRiDG+tP<5~o z(OJW^xW0D_;s0>w3YAjcdIyc7-{DI_UM2>*Sf4_S)}oEi zS*jSwX~y7w)XP%PZ-fS^-)jT~H9o1m>_H)Z8@SZ+!=i9q?d)pND0RQcBS}y7^0)A!t86RLC=*8Zt!3gCklci zCWm;JEelpqZpMib)>0@$T6o&hMwL;38r(T`wNj0t*bXH`>4`_=8;)Ce1p}eMf_!&& zM;&EckhL~k#kMf|v|%ZDuXyBE#h?gsErc=rIFo@re{kVy&K2n{o6VKG3}4gh!VYnG z$T0Z}F(zjh8}9uHtKO4jLFt|bESh#3O@;GbEg3M*LBwNyi14t_fM>W zCHt(icYV<*C*4#5LG4lqo|u{WdFTkUAXiF^wuX?kMivYwG;cUv&>EMm`fy5DY_GEY znMK9$!aj|hES+Z<83-j~skhCI;Pu%H%+>^Bz%{6~ z>}YpJ(w89-R@khjmJ>;l(@rr{&Ut7oxQ4ROI#n&FwiPsAf@tHpW)*0}`|%SE1dzds@`VuDdk_DPCuFR*g))jsMvKP(mSkc zWo>iNM5cWmg-V{(>-AG8A$LSX1>5fmn2oSeZn%(_a0~7T8&|LOPYoH6-pY3oV-UAG z>$w!2>A-)ojaB{TUre9i&ZqFm*+jc}1Bec>7Hc z)^d17RH6xWWS;#2-z8jH`e`9#2wDjwYAJiX_=FtYQNC8?RGJ(08CKIcTk6nC8|x<4 zInb7n1cgS>8AxTxduWGD6vjTII3%LBmVM%od2z(4Bqn3?jQ1bxhKHi31_*Gjl)}nX z4;pA9m94^*U+@Y=>peo zNo?*$Q9)7N$K6y$*p)D{cc%Zu`DTK%K-lB8n^%GL_$`in;M$b1F~KI~*UTFnhF5T3{g*2a#kgwo$-jYFM0vcd$qn9xB+JyI$&7jpv=+%4#Xh0b<3g`3 zhdF_vA*~t{>b+!2>!`@0Elcl!sxDEbBBGAVD}8c_0)Eg^8|ej6d_yHd_!=@JmoYjU zDmq^-Q-Y^4Iaf={Hd9=CS%Sxv^kma=dNjj<{4BrZ7U*O{+?0VoYOUXdkY9f8yred3 ztpN8Hv^myWp5ub>K;@BkrH^pxge&e5<$+hC(#oJ)^}zggRWkm-)(xahJjQ~#K#-+7 zGw=m_285~N{?R4b0iN&}e;|6(xLHZ+m7tYXuBueU)KvCVwtw4faK<;_SvQmp^E)m# z2wuc@7d(|cOp;f4&di7t?qK&Dvz;BHR`}wHayxGn2zepXZYY_=6nL{@%h-F`d>*0Q zn0&eInMklJ-Zc>ySO7s;tT*FI;YoqMpa<82%uwHk)3aqzTi)x`BaHh-o(A5M(-c%C z>xbJ`o88`;vnf)yqQ#QGGlmJtz6c!pA6!rAUmxa%Af&ZuA6pl#o} zi;9zZ1L7ofenGj%Fj-a-nA*sqtn6mXc1h9sIIkyAemj!SNmgZ#+E!dS6+GY#zxqki zb%?-K82eoIzTOgfj^;CH>KDy#wY5d9Nn_w1vmh!-Y{1}0ZU?PFVQB5w6L!hDS!2Nm zFFx3+QkhQ!b)#H!Qnv*Nwz8+e29Cm1DoBJxbWja$>oKZ_j?7xTXnI@*OLCl)7N~te z^!mihr{LC84r@yM>a9}%1)_{R)=SSrg^r5K4bCz@v=5aU^X*cuVK=&yFmrD^Pd;58 zc{+qj?;Dd4ZD*S#@P<3-KttrOkR6cMPKCYnlb`7~wXB2OAeMfC-YoxO7+49pwP7q54W_vapya&dxE)%o2JOs4eiSpYf}8%s{8$4xxWR-`$?m$BIN3E zQoj|Q14<@0uqS?Ca)WJUljz837q%vSxmIWj)I!Phz!PwNGaWWFenr)_BF$zXoB&x3 z5AKB{|Ju|RZhRi1@rkc3yozo8Lm%0PcvIh1fczcQot9UMmk7TY%I%%{Jv6J$ghe~# z;;NX{@iF*)kraz$Dwl~RuXp0)#U7Bd<}3rF(S-|X?-p-f{tJfT(EGh4lN?CxX?fMo z6r(nG$vg9Hdaau3(&+jEObS+Bh2&`$3W9=Vps}yJSHN7y8hLvl)nB19>mWMwUr(xB z`cjbCljUKIR{#?{A!NG}3S4ASfj_~%Te3Fc^2~0n0$T)oW%Z5r8}9Y4I6v?`CyjTc zW1ibtcRdK^@IHShDdckCr;LN_ihXbY*`Rwwplb1h2Iv^eU_*mB%Aq4r%EZRv717gB z#EEi&Sa+vLapDq6h49;lqPn6=`dFaMzOCt1X+b5q=+_c-Qxn1)-O#BL z?4w>BKo5Oh8p~w)#te?LkZ8AgTQ_-ON;EC+bWR0OfLCGR@EB%TLDu*uOzv5Tz6qzw zd;41Q!&&Wk=*46P=F>+7yRpw?PlVqlbeRx^nUAjur^`NQj3Pzy`#W8}LQ^~C z6SA6PQBuL;kvli0x~wfGh6oEV+a7p%`B+Nk8B&T7fhVfnSP`m+nY$9nqsg%d@Wc~# zM_=SMx$)~a9@i=R+US4X?quG8RXz`Q(BSkHT zzZL%YCBF43|FhH0ZxHz=-vbP*)PRj}mVaTYA`>(M#1r_#4Z{8pyH(k{|M*PzKQ-VU z?L**Fg?xhb6M#*~$qivr@PHZ=L3IdiWpXyhpDAZ^7Ck4u)%G9`mC6kz>_jwt*--Mn zI@XU#*6kD5&GH)8-m3jlR!93cpjUli5a?4l)yW5xlB!H4#F4J>6c|bg%5==zhh8;< zyd|41?AFey>+YZ$aGVh4n4mH6TB)adN zYK+1QTX*9O8#m{gG5r@PhWvu{NdH>wcM?lXya3nbw$IihoXQR|ZpYSOOQuu&(b}=T@mEqbXCPJXqLOIyr3b!WAfOaIeN;` z-7LGUIB@FC#~qNa$tcN zX5}GhmZSvke@P$`rZPL;e>k2T2(loybV{`eh9reDyw4RH3)L+0{_^!}LY`8%ZSv(X zrUV3G;^Cqgrl1<+E#$K7eJT&}`=%UkF)5?@n**RqY!GS7IfP0i;CzQDvcT<&My#AO zF*fr!GY?U*wL&wyAZP=1+Qi~nVslG71q?od^V8Vqh^&c7-pUnF?Di1DTMGeFr%jIJnq3Q=CtEJBZ_*;5gE(xykw7*}hyR9nX9@#V3r8;{bg= z9k7}3uloES-TjX}gdbiYiYId4y`QHL>l5m+*$u)umJtKqa7I!B zrxZZFnHOBz$eMEvzmLF>{uWRmRIz35G5{1bhdJrWmsOl9dOYRFs%(mZ4TJfS3GUoY z8%!{#xmMt_#(CV~1B|S-+k=;OYwgZ>zdOx{bV!sg7PlQ%>~3rn9h{y{k%j&?l)QCzmpt zHxxOZhMRAamdjbDet~6M;}H_T<)05-)m@PTUhZTgox1US@22rBt{ z-J$qMz3nMW5lg-gSZYiiq_NTjy6dn7UP4QJVp2i=3C6W}#NW{4pV))OEIfENSc%N5 zbbE!*rXyV#@6d8HG0N;1ObGGvQsuc!K@Wsp*` zj5Feo3Uo)L`d(V)_%z0LSuP*wifuNhm>MU`mP|5@&}jDLHOFDCobqak;&7M-#@@sJ zeFwsYVpHiYKvWb576^#v*J1t7STX-*6BG~_&>2S=?GXV^hB;-|FNckyOcD+q1iq}^ zl%7>;YWnSDtZ(>_2##64LRp>! z*9*pH*4_{4p0`^SZ@eLG!1*OOgO^AW$vzk|~XXb*oA8s&FrVSWL}fi3lc5@!SyzO+s%&`?Oe4 zkR;*QmG35AhhA2SfnDHMst&Gbu8v2CbziPhoC3y$#s^&wx>%s65fF~LQNgVrv@mxR zUq4}YMRP*IJc$7-3xj3VGR{d6j^BbJ%WPJ!5>-JxBeLC-&aBEt1x+U_>6S}iMZJ^S z{JJ8w*pMp*q;3Av#JW3}di-p3a0qT$C*YE=iO|fgLuudeK7z#s& zNv`(!Jnv6-HodjGbW|B!i*4|(Ys5zBJ~*>wcA0o%dipNK0kmzf#N-#>oxdnB9C@qJ zqmO+ij2xS!AfV7k_eP~hiZHVz>MJGKryBS=Jt9|x%z6QXsaUE)u?4@0oV~6XtKPH!#1vpK* z)jy-Us#vP8CUT(F$k5$gTT-(9BIt=Sr2MEW=D5G!{LNB#?;~qb<#@@alHw*Y(rGCW z_UNXnRikDXV|HCJ)w{NGc>Ec*^~BeMOa69Pq~D7xb5A}Z)SD|_#A(FgMFKXqvAD$jkUTW(k@wWxE*Am-Wev1Tj7 zx-)GB`2%V-{OznB{Oz0`D>^RL{NtQC5J+7-m_u0m9x_QZ-baTi4OCwin2G4vRVd|b z#uKAfml%wjM@rWl#jJ3<4tO|rB7EHt-EWh~20>YsLjEjp3>!_lsi{&A`x9vIP&8bv0rcuU>SbE>yjv-bF98a?n5Z z<&ufd_I~;=Do5YX@x^DfqPGI(W+K4cO!w=+^@~&HUr$>^Dr){Gv`Bpe{Rv6F7MdUx zW)A&O7-s9M5-=uW)1EvCe`{eL^`P&QGCi_4%qNQW+w4%O7!gmvmj0*X7x`$C9w`}I z4;g13Yg{ZHPaaReZ;@%K4PL!ckRkRHLcC|p%H0VHOOvF_i}A_qqo<>=)$}_1&`8^H zvt6CH*&YT7gW^77VXG=wP(6H|y!s`eNHySyo_4s*1 z=f!O8)JUBi;6FWAAFRSw+n8{(KDOC1urUIu`~PH6oZe|(s}4- zFv%F83l3UnrivGQB0Y$6A3I}%UjsuSS5lrv!I7+fmE1qKI@|b3G{lEhwV^4k#)6@t z``RT+{ekO=nQlNxk%qv`1uX6*PM4%>`2;iX1goo$1GGk$e)G!}Iktpzk#0q5lk2BG z*C0cE_oTTG!AWCLL8l?KtKe;#h3|rB#j94i?up(xh?~CE_J?^IY%l53HfL?~l$lH$ z9QV)l9egqA>?4N^MgwjDis2eFsWsyx@54jl)hHan_28D?XK6RS)qj$rJ24+Lc?&)l__&M~~Eks$7NHRVjM8kB= zM2*Q*(ao%}v5^$}0_WY+$FEo4Abng*DPwxz;km27m4?S>tJ|l^o=!_A>N7_qX-!bEf>kUTDZId`-pC}2>~VC`+6y6aEnRr{w%^6Ww_2d$q?v1ErT)c*nRj2k zD4R5}HmXK#oGFBb+e%-lH#dr$u0(C3DvT{C@YPPFaXWQA-NxOH3Y!EA+zqUB9S+RLG+TB1#P43ER_cn=mVZa!pWn!m&=jF`ZEUFd8K z)@|48>!9(1S2XvPNpz4&_1*w2GKmzsrOcT>_AxDqm>1!MydeFD#4mJ+k)uStModCg za62D@jzzP-k+G5VxI(`NzJR$yL1!uG5V#xP2p23@t_r7|-3Z-aE`OD|LDL74$qQGj zAiBkpE5as_=qY@R&kXs>IiHec+Vv~%Ye

i1O>I_eGKr^&y;^hm-V94&lC5U~@}s zTa5yXP^(8uV=BE6Dt+3? zLaE_7J031uGhDE7|b&w1(^tMyMTy4Ezl&wqN)pF3smtPe#|`O&uE&FOFx;Lv>2r%dWXcsINN|d3 zzSwY&aG}}M#$$|eJ8DI()+D|nV{jSt3X)A(IZ~?lt!A^rpo2z1YmxIU1%lU1G1LL~ z*NFxe{FkBI2+QE9yNI%iZ4g#m1ZMFxYZ10u>`lpV;=aIeh-*?bt~GKz2fOafq^Y$g zM%cM+$NDj_M%^@usUdiHr=2peP3Y;}A%428!?wZ1t|ZvKyDc#F=ivQGFk$n$Auf_1 z)(P76l5Zj$=3GpYqa$Aox}1I>2ghWjQ1cu{;^*$HRyLhx?bh*pJ)4^KC^Ti5mx3`! zvq54tCQ8wFggOQH(f{j65`l?f+7=HujTDJq%0}^CEPFKPxXlgtM$%#TJB|{_HY7WM zd$9`r31iNC3?q53j|V!gg-2DPTYgyV?leMeIyCC^Np)_#+#JG5N3RiYT!$iODzD5A z6YnL^J_$q67@&_DHO|XTjV7qNFUdl}?SlKC>tG$*#ANykA3n~@zH4~pwrK89kqYs~ zSO`7+bxlDW-G#QxDvwT)N*#jp0`2Cv3kbvct-1AYh_@X*vgtjvB$wGRQ#=vi{<*DF z1Bh84t}k!gqK!CAPYZQId%Xi0FyxOvKCz5?x@0X*=|yS_6KV?iX!k=J;1;lT=_hz} zV=L5=<93TT&R^bhK#YsG)gu;R$yf4TTK0PRo?+jke){Nn3knK58FEQES$;Py4-j^G z(PxSif~@1(9Q{#mzxI-64DH)s7Yo=8Z4%`WBkwkcvPME0UF!K%y(a+o`}UBc@#`*3by8Z}nAp3?M6pH77=*ri%#`G z1QtkLqKP)tWpQF`2<_A6X&3Kj6}edvVlZF^2D?8dS3ObqWNwC zOIenicvYA65wda#o|w6m)lF2wmrdDsn*hSj7*D6e69S?j~z zk(0vUKo34jm&;RR0=U1()qpAHGe6dN0{eTD(xaRBK?0hy1z;><{QXNjG8ek|8)3)_iqnw}LZMK$v> zcP>Dw%~pt!=i@kesT1!3flkCj`90M=pS#q`TcWS1IWH~c(~TT<0|S<;Ugeay#fHqM zJQ3xWaJNGVCn0LC2t=mGq6n`!LbUHW_?@c_v%j(>0%{`@K^ll2$dZF9Bt}qbpR27XI#7 z;U+c(3^sbw9~!92lm}5O2ccjQ zWP+fnk`Y);H#FHdqQ1i4ozrAYV;T`diRyj;tH8UHfkT-Ix!6wNNo0_jkx3`w-eb56PVT}m! z1Lohml~_I04KHA|F$Dcv`{S?O>Ob2`|7Edp^m^(Q(xaRvR35Mtcn^=BVK#j&IC7;6 zwepMCRBECVZ*qEYsG#E^&e1)9)tfu>s&RL^@i2C- zY2oU4^msrZ3KB&g4Pl)`L7(sok*F4A2(ppxtn^vZVxbZ0O!nac=@WIal@ADc03m>q zV4O~~o)#h+8qb)F)FLw;C~+l^8!d^=Yp)_UFQHae#4vcDjb&R+pvpGW+Rr$1Wbs%L zNUlR)6%L3w?Gkri+%?_>exZp=8+NXwGZ^2KHCbclCCRST&H%%^tQ}O}W3I?Vq5S@> zQN2o74BTWy0ks%2111cNdL-vP7AHdR=A{uLeYB*kD;b<@b0U*rw~TdaaXGXEbM0cQ zCpxukm)k;`M*P}PY-{Nb3HH>hI;~c%$-QG&Z1%hL#7I1OEo=f$u9a%aLMb6>Ov9P> zHcM2@&oRJoN5)?^GP=XndpF!Lccdd2{Na2x#+X8|J%iMsr zBE*WMg3U}K_*z&RWY`E(;yLc7IZ3h+#;7TT>kAZAX&28F;W7`=bw*n`uO3woyC?-kF~99-?(=qf@bQg@*mw8t;f@}$ zFUn4>$|}JG@pYra1v+m$O1NV#np*C1*N1RlgXts+LJo3Eh+lI?hx_zS7?5Chqc@L= zee;2E38C=ITE7R8zok9Yy7j%kd(8TLCa8uGTR=SEG|^ZCX`8Zi^05UECSKxjO_luw zuW@tF=a@@l0k*cr5Nw`yI3wG0!j}hHOPr(WV-s#q6V*NyNF#JZ^!rN;Z5c(AuX%5K zM-cSO_ktIMpc`#~USvnFC-;Jvec-|rnjbRj2xRhs6``a89lhK}}SIMA??Kul-qE0}hn{(s5eKb02g3JKp&&xim%gn-Wv834dh_x}$G z07k`MB>;B*7Wf|+1Pv4*Tn+^Vgafef0eJr}>OEj|_;Dctod|yaDl4iaNG~ZT1~{ny z_hiPcx7GrHe^3T|&;Xx5uRP!HZ;StzOjb}%QcP4)>4mJ=Ul9kQyS`Xl-G zTdglXVEy$yuRoao1o%@H>d#2)&vVqf8aRsvnCEQ(CI8<1e_pk|z<|I06X5ez9bkRu z;wtFqU;>DJYpVYkP=lZx|*2EW;l!na}aSiriMT z0C`maq^AEHo-V*v{2M%RJ6!_{LuEktc?oMvLm6vpb4Q!sL*s2~FZBXI?g72@`-bqJ zSFJCg&;AY?5cAi<(H?NFOw826P*7jr(B58F*Gkv;Po;(Dc^`q-5&IrN`wn2w`+MFA z0Gg)1$-C+w${YYwhmonVBOrV>Ae8*?^RD2DZ@L4h5^O-;8UM^U;9l!{kMvvU=j|y0 z5R0(*ZyC>bWo%XfWE==kJKsl)Kd)L}dW_$q%UB!#*IF2}I|@Sw_?cJ%%KZIU)Sp+a zZzn(l@aFu)k;e_Z9J6 za`#W@THgeKhRkm&;zzaqx8dix#Nkgo2f%>-8@#_Y{eKfYd|n&R<@0{Bc&YxD<)25K z=c+kB;b!0b-{kpY0RHU+`q%t@-`skxdh-*|`0an!z@pBQGKad+*WUQO z-kwWv{6s}D{1>SIAjt7N|Ieikelnq%{0pYPItl?wXY*I#gXdJwBhCM$0y6(6s{ayq z{yF^fkm*0+6D|G;{zpsvQvmhnRL^6t{-kPj_!m@vRNDWluIJ$^f08UZ{|l0T4rckh zM9-uB{UpP3|0lA)?nXbyd>(7+C+4ZoKVkl8kUyaR`0*sP<1z(dhrp`TODO zxlip+%+ERh8}m0Fw$C&D+=1~Y!FJxiAo%Mj_4^V$cOUyn&|mlq!Jo6lzcKtiFUfOv zkDo+7rN0pU(p~@QaeKan{u4Q<;uqv!JJTP~zu!=QUWn&gmp`dotN)4Wf6S%NckF(` zF4X)J?2iHCANKE_7vQ;F|0ffE<1b9l-yi-`cmJb6&uz^=X+AgoLh~2c9|ij3_77v< zbED%=eDT(Qf&br4kk9MuxgF&v(F(vY@W-1uU_tnMtI8{BaKKyOcXb&iAS6KOxwoC) GfBiq/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +if [ "$1" == "--old-gradle" ]; then + shift + CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper-old.jar +else + CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +fi # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then diff --git a/jGui b/jGui index e89934d6..fe5d344b 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit e89934d68671de490c4b7fc3245c6a7a74680968 +Subproject commit fe5d344be41f1c084a301a954e9fc278110c402f diff --git a/settings.gradle b/settings.gradle index 1b5630fa..1fb85e6d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,13 +1,47 @@ +def f = file('versions/core/mcVersion') +def coreMcVersion = f.exists() ? f.readLines().first() as int : 11302 + +def jGuiVersions = [] +def replayModVersions = [] +if (gradle.gradleVersion.startsWith('3.')) { + if (coreMcVersion <= 10710) { + jGuiVersions += ['core'] + replayModVersions += ['core'] + } + jGuiVersions += [ + '1.7.10', + ] + replayModVersions += [ + '1.7.10', + ] +} else { + if (coreMcVersion > 10710) { + jGuiVersions += ['core'] + replayModVersions += ['core'] + } + jGuiVersions += [ + '1.8', + '1.8.9', + '1.9.4', + '1.12', + '1.13.2' + ] + replayModVersions += [ + '1.8', + '1.8.9', + '1.9.4', + '1.10.2', + '1.11', + '1.11.2', + '1.12', + '1.12.1', + '1.12.2', + '1.13.2' + ] +} + include ':jGui' -[ - 'core', - //'1.7.10', FIXME - //'1.8', FIXME - //'1.8.9', FIXME - //'1.9.4', - //'1.12', - '1.13.2' -].each { version -> +jGuiVersions.each { version -> include ":jGui:$version" project(":jGui:$version").with { projectDir = file("jGui/versions/$version") @@ -15,21 +49,7 @@ include ':jGui' } } -def versions = [ - 'core', - //'1.7.10', FIXME - //'1.8', FIXME - //'1.8.9', FIXME - //'1.9.4', - //'1.10.2', - //'1.11', - //'1.11.2', - //'1.12', - //'1.12.1', - //'1.12.2', - '1.13.2' -] -versions.each { version -> +replayModVersions.each { version -> include ":$version" project(":$version").with { projectDir = file("versions/$version") diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index b00570f7..5ee952f2 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -61,7 +61,6 @@ import net.minecraftforge.fml.ModList; //$$ import net.minecraftforge.fml.common.Mod.Instance; //$$ import net.minecraftforge.fml.common.ModContainer; //$$ import net.minecraftforge.fml.common.event.FMLInitializationEvent; -//$$ import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; //$$ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; //$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; //#endif @@ -74,7 +73,6 @@ import net.minecraftforge.fml.common.gameevent.TickEvent; //$$ import cpw.mods.fml.common.Mod.Instance; //$$ import cpw.mods.fml.common.ModContainer; //$$ import cpw.mods.fml.common.event.FMLInitializationEvent; -//$$ import cpw.mods.fml.common.event.FMLPostInitializationEvent; //$$ import cpw.mods.fml.common.event.FMLPreInitializationEvent; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //$$ import cpw.mods.fml.common.gameevent.TickEvent; @@ -158,7 +156,11 @@ public class ReplayMod implements Module { // Initialize the static OpenGL info field from the minecraft main thread // Unfortunately lwjgl uses static methods so we have to make use of magic init calls as well + //#if MC>=11300 DeferredWorkQueue.runLater(OpenGLUtils::init); + //#else + //$$ OpenGLUtils.init(); + //#endif //#if MC>=11300 DeferredWorkQueue.runLater(() -> MCVer.getMinecraft().resourcePackRepository.addPackFinder(new LangResourcePack.Finder())); @@ -177,14 +179,19 @@ public class ReplayMod implements Module { //#if MC>=11300 settingsRegistry.register(); - //#else - //$$ config = new Configuration(event.getSuggestedConfigurationFile()); - //$$ config.load(); - //$$ settingsRegistry.setConfiguration(config); - //$$ settingsRegistry.save(); // Save default values to disk //#endif } + //#if MC<=11300 + //$$ @EventHandler + //$$ public void init(FMLPreInitializationEvent event) { + //$$ config = new Configuration(event.getSuggestedConfigurationFile()); + //$$ config.load(); + //$$ settingsRegistry.setConfiguration(config); + //$$ settingsRegistry.save(); // Save default values to disk + //$$ } + //#endif + public KeyBindingRegistry getKeyBindingRegistry() { return keyBindingRegistry; } @@ -424,7 +431,6 @@ public class ReplayMod implements Module { private void printToChat(boolean warning, String message, Object... args) { if (getSettingsRegistry().get(Setting.NOTIFICATIONS)) { - /* FIXME: needs RuntimeInvisibleParameterAnnotations workaround // Some nostalgia: "§8[§6Replay Mod§8]§r Your message goes here" //#if MC>=10904 Style coloredDarkGray = new Style().setColor(TextFormatting.DARK_GRAY); @@ -445,8 +451,7 @@ public class ReplayMod implements Module { //#endif // Send message to chat GUI // The ingame GUI is initialized at startup, therefore this is possible before the client is connected - */ - mc.ingameGUI.getChatGUI().printChatMessage(new TextComponentTranslation(message, args)); + mc.ingameGUI.getChatGUI().printChatMessage(text); } } } diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index a73a7fd1..43352ae4 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -69,7 +69,7 @@ import net.minecraft.client.renderer.vertex.DefaultVertexFormats; //#if MC>=10800 import net.minecraft.client.renderer.GlStateManager; -// FIXME import net.minecraft.client.renderer.GlStateManager.BooleanState; +import net.minecraft.client.renderer.GlStateManager.BooleanState; import net.minecraft.world.WorldType; //#if MC>=11300 import net.minecraftforge.eventbus.api.IEventBus; @@ -346,7 +346,7 @@ public class MCVer { return world.playerEntities; } - public static GlStateManager.BooleanState fog() { + public static BooleanState fog() { //#if MC>=11300 return GlStateManager.FOG.fog; //#else @@ -362,7 +362,7 @@ public class MCVer { //#endif } - public static void fog(GlStateManager.BooleanState fog) { + public static void fog(BooleanState fog) { //#if MC>=11300 GlStateManager.FOG.fog = fog; //#else @@ -378,7 +378,7 @@ public class MCVer { //#endif } - public static GlStateManager.BooleanState texture2DState(int index) { + public static BooleanState texture2DState(int index) { //#if MC>=11300 return GlStateManager.TEXTURES[index].texture2DState; //#else @@ -390,7 +390,7 @@ public class MCVer { //#endif } - public static void texture2DState(int index, GlStateManager.BooleanState texture2DState) { + public static void texture2DState(int index, BooleanState texture2DState) { //#if MC>=11300 GlStateManager.TEXTURES[index].texture2DState = texture2DState; //#else diff --git a/src/main/java/com/replaymod/online/ReplayModOnline.java b/src/main/java/com/replaymod/online/ReplayModOnline.java index 059a3a35..9a87a68a 100644 --- a/src/main/java/com/replaymod/online/ReplayModOnline.java +++ b/src/main/java/com/replaymod/online/ReplayModOnline.java @@ -18,16 +18,16 @@ import de.johni0702.minecraft.gui.container.GuiScreen; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -//#if MC>=10800 //#if MC>=11300 import net.minecraftforge.eventbus.api.SubscribeEvent; //#else //$$ import net.minecraftforge.common.config.Configuration; +//#if MC>=10800 //$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//#endif //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif +//#endif import java.io.File; import java.io.IOException; diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index 415883fc..19937f80 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -36,7 +36,9 @@ import static com.replaymod.core.versions.MCVer.*; public class ReplayModRecording implements Module { private static final Logger LOGGER = LogManager.getLogger(); + //#if MC>=11300 private static final AttributeKey ATTR_CHECKED = AttributeKey.newInstance("ReplayModRecording_checked"); + //#endif { instance = this; } public static ReplayModRecording instance; @@ -84,8 +86,10 @@ public class ReplayModRecording implements Module { public void initiateRecording(NetworkManager networkManager) { Channel channel = networkManager.channel(); if (channel.pipeline().get("ReplayModReplay_replaySender") != null) return; + //#if MC>=11300 if (channel.hasAttr(ATTR_CHECKED)) return; channel.attr(ATTR_CHECKED).set(null); + //#endif connectionEventHandler.onConnectedToServerEvent(networkManager); } } diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java index ad21b033..50e0f033 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java @@ -18,6 +18,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import static net.minecraft.client.renderer.GlStateManager.*; //#else //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; +//$$ import static com.replaymod.core.versions.MCVer.GlStateManager.*; //#endif import static com.replaymod.core.ReplayMod.TEXTURE; diff --git a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java index 019d5539..61292713 100644 --- a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java +++ b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java @@ -9,11 +9,16 @@ import net.minecraft.client.gui.GuiYesNo; import net.minecraft.client.gui.GuiYesNoCallback; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.resources.I18n; -// FIXME import net.minecraft.client.resources.ResourcePackRepository; import net.minecraft.util.HttpUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +//#if MC>=11300 +// FIXME +//#else +//$$ import net.minecraft.client.resources.ResourcePackRepository; +//#endif + //#if MC>=10904 import net.minecraft.network.play.client.CPacketResourcePackStatus; import net.minecraft.network.play.client.CPacketResourcePackStatus.Action; @@ -303,6 +308,7 @@ public class ResourcePackRecorder { //#endif } } + */ //#else //$$ public synchronized S3FPacketCustomPayload handleResourcePack(S3FPacketCustomPayload packet) { //$$ final int requestId = nextRequestId++; @@ -377,5 +383,4 @@ public class ResourcePackRecorder { //$$ } //#endif -*/ } diff --git a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java index 0f7ab0de..2f65b566 100644 --- a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java @@ -121,7 +121,6 @@ public abstract class OpenGlFrameCapturer=11300 +import net.minecraft.client.renderer.texture.NativeImage; +//#endif + import java.nio.ByteBuffer; public class GuiVideoRenderer extends GuiScreen implements Tickable { diff --git a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java index 6bbbca70..a32a2dcf 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java +++ b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java @@ -8,7 +8,6 @@ import com.replaymod.render.hooks.EntityRendererHandler; import com.replaymod.replay.camera.CameraEntity; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableColor; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.Matrix4f; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import org.lwjgl.opengl.GL11; @@ -20,13 +19,15 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; //#if MC>=11300 -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.client.GameSettings; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.Matrix4f; +import net.minecraft.client.renderer.WorldRenderer; //#else //$$ import net.minecraft.client.renderer.EntityRenderer; //$$ import net.minecraft.client.renderer.RenderGlobal; //$$ import net.minecraft.client.settings.GameSettings; +//$$ import org.lwjgl.util.glu.Project; //#endif //#if MC>=10904 diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index c329139d..84d9c57a 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -30,6 +30,7 @@ import net.minecraft.util.Timer; import org.lwjgl.glfw.GLFW; //#else //$$ import net.minecraft.client.gui.ScaledResolution; +//$$ import org.lwjgl.input.Mouse; //$$ import org.lwjgl.opengl.Display; //#endif diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index 4dffa766..cab8b6f3 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -506,16 +506,22 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //$$ if (p instanceof S3FPacketCustomPayload) { //$$ S3FPacketCustomPayload packet = (S3FPacketCustomPayload) p; //#endif - /* FIXME //#if MC>=10800 - String channelName = packet.getChannelName(); + //#if MC>=11300 + ResourceLocation channelName = packet.getChannelName(); + //#else + //$$ String channelName = packet.getChannelName(); + //#endif //#else //$$ String channelName = packet.func_149169_c(); //#endif - if ("MC|BOpen".equals(channelName)) { + //#if MC>=11300 + if (SPacketCustomPayload.BOOK_OPEN.equals(channelName)) { + //#else + //$$ if ("MC|BOpen".equals(channelName)) { + //#endif return null; } - */ //#if MC>=10800 } diff --git a/src/main/java/com/replaymod/replay/InputReplayTimer.java b/src/main/java/com/replaymod/replay/InputReplayTimer.java index 33c023a1..0a9e1efb 100644 --- a/src/main/java/com/replaymod/replay/InputReplayTimer.java +++ b/src/main/java/com/replaymod/replay/InputReplayTimer.java @@ -1,4 +1,3 @@ -/* FIXME package com.replaymod.replay; import com.replaymod.core.utils.WrappedTimer; @@ -7,20 +6,21 @@ import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.settings.GameSettings; import net.minecraft.client.settings.KeyBinding; import net.minecraft.crash.CrashReport; -import net.minecraft.util.ReportedException; import net.minecraft.util.Timer; import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.common.MinecraftForge; -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; +//#if MC>=11300 +//#else +//$$ import net.minecraft.client.settings.GameSettings; +//$$ import org.lwjgl.input.Keyboard; +//$$ import org.lwjgl.input.Mouse; //#if MC>=10800 -import net.minecraftforge.fml.common.FMLCommonHandler; - -import java.io.IOException; +//$$ import net.minecraftforge.fml.common.FMLCommonHandler; +//$$ +//$$ import java.io.IOException; //#else //$$ import cpw.mods.fml.common.FMLCommonHandler; //$$ import cpw.mods.fml.common.eventhandler.Event; @@ -28,6 +28,7 @@ import java.io.IOException; //$$ //$$ import static com.replaymod.core.versions.MCVer.FML_BUS; //#endif +//#endif public class InputReplayTimer extends WrappedTimer { private final ReplayModReplay mod; @@ -39,6 +40,7 @@ public class InputReplayTimer extends WrappedTimer { this.mc = mod.getCore().getMinecraft(); } + /* FIXME @Override public void updateTimer() { super.updateTimer(); @@ -260,9 +262,9 @@ public class InputReplayTimer extends WrappedTimer { FMLCommonHandler.instance().fireKeyInput(); } + */ //#if MC<=10710 //$$ public static class RunScheduledTasks extends Event {} //#endif } -*/ diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 0fb53214..07e57031 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -61,7 +61,7 @@ public class ReplayModReplay implements Module { @Override public void registerKeyBindings(KeyBindingRegistry registry) { - registry.registerKeyBinding("replaymod.input.marker", MCVer.Keyboard.KEY_M, new Runnable() { + registry.registerKeyBinding("replaymod.input.marker", Keyboard.KEY_M, new Runnable() { @Override public void run() { if (replayHandler != null ) { diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java index 62971eb9..b8875240 100644 --- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java @@ -67,7 +67,7 @@ public class GuiHandler { // Replace "Exit Server" button with "Exit Replay" button case BUTTON_EXIT_SERVER: removeButton(event, b); - addButton(event, new GuiButton(BUTTON_EXIT_REPLAY, b.x, b.y, b.width, b.height, I18n.format("replaymod.gui.exit")) { + addButton(event, new GuiButton(BUTTON_EXIT_REPLAY, x(b), y(b), b.width, b.height, I18n.format("replaymod.gui.exit")) { //#if MC>=11300 @Override public void onClick(double mouseX, double mouseY) { diff --git a/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java b/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java index d4a1cc14..fce97331 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinPlayerControllerMP.java @@ -47,7 +47,7 @@ public abstract class MixinPlayerControllerMP { //$$ private NetHandlerPlayClient netClientHandler; //#endif - //#if MC>=11200 + //#if MC>=11300 @Inject(method = "createPlayer", at=@At("HEAD"), cancellable = true) private void replayModReplay_createReplayCamera(World worldIn, StatisticsManager statisticsManager, RecipeBookClient recipeBookClient, CallbackInfoReturnable ci) { if (ReplayModReplay.instance.getReplayHandler() != null) { diff --git a/versions/common.gradle b/versions/common.gradle index 1034a599..29feb7b8 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -148,7 +148,7 @@ configurations { shade compile.extendsFrom shade - shade.exclude group: 'com.google.code.gson', module: 'gson' // provided by MC + // FIXME seems to be inherited now? shade.exclude group: 'com.google.code.gson', module: 'gson' // provided by MC } dependencies { @@ -158,13 +158,19 @@ dependencies { ][mcVersion] } - annotationProcessor group: 'com.google.code.gson', name: 'gson', version: '2.8.5' - compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + if (FG3) { + // FIXME gson should be bundled with MC, not sure why forge doesn't add it (probably related to above shade.exclude) + annotationProcessor group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + } // compile 'com.github.SpongePowered:Mixin:404f5da' // 0.7.5-SNAPSHOT // ^ mixin doesn't compile on jitpack, so we'll have to depend on the SNAPSHOT and build it manually for reprod - annotationProcessor 'org.spongepowered:mixin:0.7.8-SNAPSHOT' - compileOnly 'org.spongepowered:mixin:0.7.8-SNAPSHOT' + if (!gradle.gradleVersion.startsWith('3.')) { + annotationProcessor 'org.spongepowered:mixin:0.7.8-SNAPSHOT' + compileOnly 'org.spongepowered:mixin:0.7.8-SNAPSHOT' + } compile 'org.spongepowered:mixin:0.7.8-SNAPSHOT' shade 'com.googlecode.mp4parser:isoparser:1.1.7' shade 'org.apache.commons:commons-exec:1.3' @@ -191,7 +197,9 @@ dependencies { exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } - annotationProcessor 'org.projectlombok:lombok:1.16.6' + if (!gradle.gradleVersion.startsWith('3.')) { + annotationProcessor 'org.projectlombok:lombok:1.16.6' + } compile 'org.projectlombok:lombok:1.16.6' // runtime only for @SneakyThrows which isn't used testCompile 'junit:junit:4.11' From f8fc3462e860f2f959c769655f0f1497d49e3388 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 10 Mar 2019 12:53:03 +0100 Subject: [PATCH 45/65] Get recording and replay working --- .../java/com/replaymod/core/ReplayMod.java | 16 +- .../replaymod/core/utils/Restrictions.java | 8 +- .../AdvancedScreenshots.java | 59 +-- .../handler/RecordingEventHandler.java | 6 + .../mixin/MixinKeyboardListener.java | 45 +++ .../recording/mixin/MixinMouseHelper.java | 41 ++ .../mixin/MixinNetHandlerLoginClient.java | 26 +- .../mixin/MixinNetHandlerPlayClient.java | 8 +- .../mixin/MixinNetworkDispatcher.java | 135 ++++--- .../recording/mixin/MixinRenderGlobal.java | 9 +- .../recording/packet/PacketListener.java | 19 +- .../replaymod/replay/FullReplaySender.java | 56 ++- .../replaymod/replay/InputReplayTimer.java | 365 +++++++++--------- .../replaymod/replay/QuickReplaySender.java | 9 +- .../com/replaymod/replay/ReplayHandler.java | 26 +- .../com/replaymod/replay/ReplayModReplay.java | 2 +- .../replay/gui/screen/GuiReplayViewer.java | 2 +- .../replaymod/replay/handler/GuiHandler.java | 19 +- .../MixinTileEntityEndPortalRenderer.java | 2 +- src/main/resources/META-INF/replaymod_at.cfg | 6 + .../resources/mixins.recording.replaymod.json | 8 +- versions/common.gradle | 2 +- 22 files changed, 538 insertions(+), 331 deletions(-) create mode 100644 src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java create mode 100644 src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 5ee952f2..b57c5260 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -25,7 +25,8 @@ import org.apache.commons.io.FileUtils; //#if MC>=11300 import com.replaymod.core.versions.LangResourcePack; import net.minecraft.resources.FolderPack; -import net.minecraft.resources.IResourcePack; +import net.minecraft.resources.IPackFinder; +import net.minecraft.resources.ResourcePackInfo; import net.minecraftforge.fml.DeferredWorkQueue; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -87,6 +88,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Queue; import java.util.concurrent.FutureTask; @@ -211,7 +213,6 @@ public class ReplayMod implements Module { static { // Note: even preInit is too late and we'd have to issue another resource reload @SuppressWarnings("unchecked") //#if MC>=11300 - List defaultResourcePacks = new ArrayList<>(); // FIXME: probably replaced with DownloadingPackFinder FolderPack jGuiResourcePack = new FolderPack(new File("../jGui/src/main/resources")) { @Override protected InputStream getInputStream(String resourceName) throws IOException { @@ -233,7 +234,16 @@ public class ReplayMod implements Module { } } }; - defaultResourcePacks.add(jGuiResourcePack); + //#if MC>=11300 + mc.resourcePackRepository.addPackFinder(new IPackFinder() { + @Override + public void addPackInfosToMap(Map map, ResourcePackInfo.IFactory factory) { + map.put("jgui", ResourcePackInfo.func_195793_a("jgui", true, () -> jGuiResourcePack, factory, ResourcePackInfo.Priority.BOTTOM)); + } + }); + //#else + //$$ defaultResourcePacks.add(jGuiResourcePack); + //#endif //#if MC<=10710 //$$ FolderResourcePack mainResourcePack = new FolderResourcePack(new File("../src/main/resources")) { //$$ @Override diff --git a/src/main/java/com/replaymod/core/utils/Restrictions.java b/src/main/java/com/replaymod/core/utils/Restrictions.java index 2a5f870f..1deccf7f 100644 --- a/src/main/java/com/replaymod/core/utils/Restrictions.java +++ b/src/main/java/com/replaymod/core/utils/Restrictions.java @@ -3,6 +3,7 @@ package com.replaymod.core.utils; import net.minecraft.network.PacketBuffer; //#if MC>=10904 import net.minecraft.network.play.server.SPacketCustomPayload; +import net.minecraft.util.ResourceLocation; //#else //$$ import net.minecraft.network.play.server.S3FPacketCustomPayload; //#endif @@ -18,8 +19,11 @@ import static com.replaymod.core.versions.MCVer.readString; * @see Replay Restrictions Gist */ public class Restrictions { - // FIXME these should be ResourceLocations now - public static final String PLUGIN_CHANNEL = "Replay|Restrict"; + //#if MC>=11300 + public static final ResourceLocation PLUGIN_CHANNEL = new ResourceLocation("replaymod", "restrict"); + //#else + //$$ public static final String PLUGIN_CHANNEL = "Replay|Restrict"; + //#endif private boolean noXray; private boolean noNoclip; private boolean onlyFirstPerson; diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java index 8cefee8c..49d7e2c8 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/AdvancedScreenshots.java @@ -1,23 +1,20 @@ package com.replaymod.extras.advancedscreenshots; import com.replaymod.core.ReplayMod; -import com.replaymod.core.versions.MCVer; import com.replaymod.extras.Extra; -import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiControls; import net.minecraftforge.common.MinecraftForge; -//#if MC>=11300 -import com.replaymod.core.versions.MCVer.Keyboard; -import net.minecraftforge.client.event.GuiScreenEvent; -//#else +//#if MC<11300 +//$$ import com.replaymod.core.versions.MCVer; +//$$ import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; +//$$ import net.minecraft.client.Minecraft; +//$$ import net.minecraft.client.gui.GuiControls; +//$$ import net.minecraftforge.client.event.GuiScreenEvent; //$$ import org.lwjgl.input.Keyboard; //#endif //#if MC>=10800 //#if MC>=11300 -import net.minecraftforge.eventbus.api.SubscribeEvent; //#else //$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; //#endif @@ -29,30 +26,36 @@ public class AdvancedScreenshots implements Extra { private ReplayMod mod; - private final Minecraft mc = MCVer.getMinecraft(); - @Override - public void register(ReplayMod mod) throws Exception { + public void register(ReplayMod mod) { this.mod = mod; MinecraftForge.EVENT_BUS.register(this); } - @SubscribeEvent - public void onDispatchKeypresses(ReplayDispatchKeypressesEvent.Pre event) { - if (mc.currentScreen instanceof GuiControls) return; - //#if MC>=11300 - // FIXME - //#else - //$$ if (!Keyboard.getEventKeyState()) return; - //$$ if (Keyboard.isRepeatEvent()) return; - //$$ int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() : Keyboard.getEventKey(); - //$$ if (keyCode == 0 || keyCode != mc.gameSettings.keyBindScreenshot.getKeyCode()) return; - //#endif + //#if MC>=11300 + private static AdvancedScreenshots instance; { instance = this; } + public static void take() { + if (instance != null) { + instance.takeScreenshot(); + } + } + //#else + //$$ @SubscribeEvent + //$$ public void onDispatchKeypresses(ReplayDispatchKeypressesEvent.Pre event) { + //$$ Minecraft mc = MCVer.getMinecraft(); + //$$ if (mc.currentScreen instanceof GuiControls) return; + //$$ if (!Keyboard.getEventKeyState()) return; + //$$ if (Keyboard.isRepeatEvent()) return; + //$$ int keyCode = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() : Keyboard.getEventKey(); + //$$ if (keyCode == 0 || keyCode != mc.gameSettings.keyBindScreenshot.getKeyCode()) return; + //$$ + //$$ takeScreenshot(); + //$$ + //$$ event.setCanceled(true); + //$$ } + //#endif - ReplayMod.instance.runLater(() -> { - new GuiCreateScreenshot(mod).display(); - }); - - event.setCanceled(true); + private void takeScreenshot() { + ReplayMod.instance.runLater(() -> new GuiCreateScreenshot(mod).display()); } } diff --git a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java index cfa35138..a3313842 100755 --- a/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/RecordingEventHandler.java @@ -83,6 +83,12 @@ public class RecordingEventHandler { } } + //#if MC>=11300 + public void onPacket(Packet packet) { + packetListener.save(packet); + } + //#endif + public void onPlayerJoin() { try { //#if MC>=10904 diff --git a/src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java b/src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java new file mode 100644 index 00000000..c52c209d --- /dev/null +++ b/src/main/java/com/replaymod/recording/mixin/MixinKeyboardListener.java @@ -0,0 +1,45 @@ +//#if MC>=11300 +package com.replaymod.recording.mixin; + +import com.replaymod.core.versions.MCVer; +import com.replaymod.extras.advancedscreenshots.AdvancedScreenshots; +import com.replaymod.replay.ReplayModReplay; +import net.minecraft.client.KeyboardListener; +import net.minecraft.client.settings.KeyBinding; +import net.minecraft.client.shader.Framebuffer; +import net.minecraft.util.ScreenShotHelper; +import net.minecraft.util.text.ITextComponent; +import net.minecraftforge.fml.hooks.BasicEventHooks; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.io.File; +import java.util.function.Consumer; + +@Mixin(KeyboardListener.class) +public abstract class MixinKeyboardListener { + @Inject(method = "onKeyEvent", at = @At("HEAD"), cancellable = true) + private void dispatchKeyEvent(long windowPointer, int key, int scanCode, int action, int modifiers, CallbackInfo ci) { + // FIXME this is a hack and will hopefully be done properly by forge before its release (MinecraftForge#5481) + // (and therefore before RM's release), this code will have to be removed then and handling code updated + KeyBinding keyBindChat = MCVer.getMinecraft().gameSettings.keyBindChat; + if (action == 1 && keyBindChat.matchesKey(key, scanCode)) { + keyBindChat.pressTime++; + BasicEventHooks.fireKeyInput(); + ci.cancel(); + } + } + + @Redirect(method = "onKeyEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/ScreenShotHelper;saveScreenshot(Ljava/io/File;IILnet/minecraft/client/shader/Framebuffer;Ljava/util/function/Consumer;)V")) + private void takeScreenshot(File p_148260_0_, int p_148260_1_, int p_148260_2_, Framebuffer p_148260_3_, Consumer p_148260_4_) { + if (ReplayModReplay.instance.getReplayHandler() != null) { + AdvancedScreenshots.take(); + } else { + ScreenShotHelper.saveScreenshot(p_148260_0_, p_148260_1_, p_148260_2_, p_148260_3_, p_148260_4_); + } + } +} +//#endif diff --git a/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java new file mode 100644 index 00000000..4dba3a3f --- /dev/null +++ b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java @@ -0,0 +1,41 @@ +//#if MC>=11300 +package com.replaymod.recording.mixin; + +import com.replaymod.replay.InputReplayTimer; +import com.replaymod.replay.ReplayModReplay; +import net.minecraft.client.MouseHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(MouseHelper.class) +public abstract class MixinMouseHelper { + //#ifdef DEV_ENV + @Shadow + private boolean mouseGrabbed; + + @Inject(method = "grabMouse", at = @At("HEAD"), cancellable = true) + private void noGrab(CallbackInfo ci) { + // Used to be provided by Forge for 1.12.2 and below + if (Boolean.valueOf(System.getProperty("fml.noGrab", "false"))) { + mouseGrabbed = true; + ci.cancel(); + } + } + //#endif + + @Inject(method = "scrollCallback", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/EntityPlayerSP;isSpectator()Z"), + locals = LocalCapture.CAPTURE_FAILHARD, + cancellable = true) + private void handleReplayModScroll(long _p0, double _p1, double _p2, CallbackInfo ci, double _l1, double yOffsetAccumulated) { + if (ReplayModReplay.instance.getReplayHandler() != null) { + InputReplayTimer.handleScroll((int) yOffsetAccumulated); + ci.cancel(); + } + } +} +//#endif diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java index a09681ef..e3df345e 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerLoginClient.java @@ -9,11 +9,17 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11300 +import com.replaymod.core.versions.MCVer; +import com.replaymod.recording.handler.RecordingEventHandler.RecordingEventSender; +import net.minecraft.network.login.server.SPacketCustomPayloadLogin; +//#else //#if MC>=10800 -// FIXME event not (yet?) in 1.13 import net.minecraftforge.fml.common.network.FMLNetworkEvent; +//$$ import net.minecraftforge.fml.common.network.FMLNetworkEvent; //#else //$$ import cpw.mods.fml.common.network.FMLNetworkEvent; //#endif +//#endif @Mixin(NetHandlerLoginClient.class) public abstract class MixinNetHandlerLoginClient { @@ -27,6 +33,16 @@ public abstract class MixinNetHandlerLoginClient { //#if MC>=11300 @Inject(method = "func_209521_a", at=@At("HEAD")) + private void replayModRecording_initiateRecording(SPacketCustomPayloadLogin packet, CallbackInfo ci) { + RecordingEventSender eventSender = (RecordingEventSender) MCVer.getMinecraft().renderGlobal; + if (eventSender.getRecordingEventHandler() != null) { + return; // already recording + } + ReplayModRecording.instance.initiateRecording(networkManager); + if (eventSender.getRecordingEventHandler() != null) { + eventSender.getRecordingEventHandler().onPacket(packet); + } + } //#else //$$ /** //$$ * Starts the recording right before switching into PLAY state. @@ -34,14 +50,14 @@ public abstract class MixinNetHandlerLoginClient { //$$ * as it only fires after the forge handshake. //$$ */ //$$ @Inject(method = "handleLoginSuccess", at=@At("HEAD")) - //#endif - public void replayModRecording_initiateRecording(CallbackInfo cb) { + //$$ public void replayModRecording_initiateRecording(CallbackInfo cb) { //#if MC>=10800 - ReplayModRecording.instance.initiateRecording(networkManager); + //$$ ReplayModRecording.instance.initiateRecording(networkManager); //#else //$$ ReplayModRecording.instance.initiateRecording(field_147393_d); //#endif - } + //$$ } + //#endif //#if MC>=11200 @Inject(method = "handleLoginSuccess", at=@At("RETURN")) diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java index a065f643..6af64ea5 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetHandlerPlayClient.java @@ -1,5 +1,6 @@ package com.replaymod.recording.mixin; +import com.replaymod.core.versions.MCVer; import com.replaymod.recording.handler.RecordingEventHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.network.NetHandlerPlayClient; @@ -13,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; //#if MC>=10800 //#if MC>=10904 import net.minecraft.network.play.server.SPacketPlayerListItem.Action; -// FIXME requires RuntimeInvisParam workaround import net.minecraft.network.play.server.SPacketPlayerListItem.AddPlayerData; +import net.minecraft.network.play.server.SPacketPlayerListItem.AddPlayerData; //#else //$$ import net.minecraft.network.play.server.S38PacketPlayerListItem.Action; //$$ import net.minecraft.network.play.server.S38PacketPlayerListItem.AddPlayerData; @@ -30,8 +31,7 @@ import static com.replaymod.core.versions.MCVer.*; @Mixin(NetHandlerPlayClient.class) public abstract class MixinNetHandlerPlayClient { - @Shadow - private Minecraft gameController; + private static Minecraft gameController = MCVer.getMinecraft(); //#if MC>=10800 @Shadow @@ -61,7 +61,6 @@ public abstract class MixinNetHandlerPlayClient { RecordingEventHandler handler = getRecordingEventHandler(); if (handler != null && packet.getAction() == Action.ADD_PLAYER) { - /* FIXME see import for (AddPlayerData data : packet.getEntries()) { if (data.getProfile() == null || data.getProfile().getId() == null) continue; // Only add spawn packet for our own player and only if he isn't known yet @@ -70,7 +69,6 @@ public abstract class MixinNetHandlerPlayClient { handler.onPlayerJoin(); } } - */ } } //#else diff --git a/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java b/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java index 1355c31c..7f91df30 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinNetworkDispatcher.java @@ -1,78 +1,77 @@ -/* FIXME -package com.replaymod.recording.mixin; - -import com.replaymod.recording.handler.FMLHandshakeFilter; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.embedded.EmbeddedChannel; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - +//#if MC<11300 +//$$ package com.replaymod.recording.mixin; +//$$ +//$$ import com.replaymod.recording.handler.FMLHandshakeFilter; +//$$ import io.netty.channel.ChannelPipeline; +//$$ import io.netty.channel.embedded.EmbeddedChannel; +//$$ import org.spongepowered.asm.mixin.Mixin; +//$$ import org.spongepowered.asm.mixin.Shadow; +//$$ import org.spongepowered.asm.mixin.injection.At; +//$$ import org.spongepowered.asm.mixin.injection.Inject; +//$$ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//$$ //#if MC>=11200 -import io.netty.channel.ChannelConfig; -import net.minecraft.network.EnumConnectionState; -import net.minecraft.network.NetworkManager; -import org.spongepowered.asm.mixin.injection.Redirect; +//$$ import io.netty.channel.ChannelConfig; +//$$ import net.minecraft.network.EnumConnectionState; +//$$ import net.minecraft.network.NetworkManager; +//$$ import org.spongepowered.asm.mixin.injection.Redirect; //#endif - +//$$ //#if MC>=10800 -import net.minecraftforge.fml.common.network.handshake.FMLHandshakeCodec; -import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher; -import net.minecraftforge.fml.relauncher.Side; +//$$ import net.minecraftforge.fml.common.network.handshake.FMLHandshakeCodec; +//$$ import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher; +//$$ import net.minecraftforge.fml.relauncher.Side; //#else //$$ import cpw.mods.fml.common.network.handshake.FMLHandshakeCodec; //$$ import cpw.mods.fml.common.network.handshake.NetworkDispatcher; //$$ import cpw.mods.fml.relauncher.Side; //#endif - -@Mixin(value = NetworkDispatcher.class, remap = false) -public abstract class MixinNetworkDispatcher { - - @Shadow - private Side side; - - @Shadow - private EmbeddedChannel handshakeChannel; - - /** - * Always sets fml:isLocal to false on the server side. - * This effectively removes the difference in the FML handshake between SP and MP - * and forces the block/item ids, etc. to always be send. - * Injects a {@link FMLHandshakeFilter} on the client side to filter out - * those extra, unexpected packets. - */ -/* - @Inject(method = "insertIntoChannel", at=@At("HEAD")) - public void replayModRecording_setupForLocalRecording(CallbackInfo cb) { - // If we're in multiplayer, everything is fine as is - if (!handshakeChannel.attr(NetworkDispatcher.IS_LOCAL).get()) return; - - if (side == Side.SERVER) { - // On the server side, force all packets to be sent - handshakeChannel.attr(NetworkDispatcher.IS_LOCAL).set(false); - } else { - // On the client side, discard additional packets - ChannelPipeline pipeline = handshakeChannel.pipeline(); - pipeline.addAfter(pipeline.context(FMLHandshakeCodec.class).name(), - "replaymod_filter", new FMLHandshakeFilter()); - } - } - +//$$ +//$$ @Mixin(value = NetworkDispatcher.class, remap = false) +//$$ public abstract class MixinNetworkDispatcher { +//$$ +//$$ @Shadow +//$$ private Side side; +//$$ +//$$ @Shadow +//$$ private EmbeddedChannel handshakeChannel; +//$$ +//$$ /** +//$$ * Always sets fml:isLocal to false on the server side. +//$$ * This effectively removes the difference in the FML handshake between SP and MP +//$$ * and forces the block/item ids, etc. to always be send. +//$$ * Injects a {@link FMLHandshakeFilter} on the client side to filter out +//$$ * those extra, unexpected packets. +//$$ */ +//$$ @Inject(method = "insertIntoChannel", at=@At("HEAD")) +//$$ public void replayModRecording_setupForLocalRecording(CallbackInfo cb) { +//$$ // If we're in multiplayer, everything is fine as is +//$$ if (!handshakeChannel.attr(NetworkDispatcher.IS_LOCAL).get()) return; +//$$ +//$$ if (side == Side.SERVER) { +//$$ // On the server side, force all packets to be sent +//$$ handshakeChannel.attr(NetworkDispatcher.IS_LOCAL).set(false); +//$$ } else { +//$$ // On the client side, discard additional packets +//$$ ChannelPipeline pipeline = handshakeChannel.pipeline(); +//$$ pipeline.addAfter(pipeline.context(FMLHandshakeCodec.class).name(), +//$$ "replaymod_filter", new FMLHandshakeFilter()); +//$$ } +//$$ } +//$$ //#if MC>=11200 - @Redirect(method = "clientListenForServerHandshake", at = @At(value = "INVOKE", remap = true, target = - "Lnet/minecraft/network/NetworkManager;setConnectionState(Lnet/minecraft/network/EnumConnectionState;)V")) - public void replayModRecording_raceConditionWorkAround1(NetworkManager self, EnumConnectionState ignored) { } - - @Redirect(method = "insertIntoChannel", at = @At(value = "INVOKE", target = - "Lio/netty/channel/ChannelConfig;setAutoRead(Z)Lio/netty/channel/ChannelConfig;")) - public ChannelConfig replayModRecording_raceConditionWorkAround2(ChannelConfig self, boolean autoRead) { - if (side == Side.CLIENT) { - autoRead = false; - } - return self.setAutoRead(autoRead); - } + //$$ @Redirect(method = "clientListenForServerHandshake", at = @At(value = "INVOKE", remap = true, target = + //$$ "Lnet/minecraft/network/NetworkManager;setConnectionState(Lnet/minecraft/network/EnumConnectionState;)V")) + //$$ public void replayModRecording_raceConditionWorkAround1(NetworkManager self, EnumConnectionState ignored) { } + //$$ + //$$ @Redirect(method = "insertIntoChannel", at = @At(value = "INVOKE", target = + //$$ "Lio/netty/channel/ChannelConfig;setAutoRead(Z)Lio/netty/channel/ChannelConfig;")) + //$$ public ChannelConfig replayModRecording_raceConditionWorkAround2(ChannelConfig self, boolean autoRead) { + //$$ if (side == Side.CLIENT) { + //$$ autoRead = false; + //$$ } + //$$ return self.setAutoRead(autoRead); + //$$ } //#endif -} -*/ +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java index 0c11e4ed..637ba04a 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinRenderGlobal.java @@ -7,6 +7,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; //#if MC>=11300 +import net.minecraft.client.renderer.WorldRenderer; //#else //$$ import net.minecraft.client.renderer.RenderGlobal; //#endif @@ -19,8 +20,11 @@ import net.minecraft.util.math.BlockPos; //#endif //#endif -/* FIXME runtimeinvisparameter -@Mixin(RenderGlobal.class) +//#if MC>=11300 +@Mixin(WorldRenderer.class) +//#else +//$$ @Mixin(RenderGlobal.class) +//#endif public abstract class MixinRenderGlobal implements RecordingEventHandler.RecordingEventSender { private RecordingEventHandler recordingEventHandler; @@ -58,4 +62,3 @@ public abstract class MixinRenderGlobal implements RecordingEventHandler.Recordi } } } -*/ diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index e7e6be68..c33e5ff3 100755 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -17,6 +17,10 @@ import net.minecraft.network.play.server.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +//#if MC>=11300 +import net.minecraft.network.login.server.SPacketLoginSuccess; +//#endif + //#if MC>=10904 import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.util.text.TextComponentString; @@ -67,6 +71,11 @@ public class PacketListener extends ChannelInboundHandlerAdapter { private long lastSentPacket; private long timePassedWhilePaused; private volatile boolean serverWasPaused; + //#if MC>=11300 + private EnumConnectionState connectionState = EnumConnectionState.LOGIN; + //#else + //$$ private EnumConnectionState connectionState = EnumConnectionState.PLAY; + //#endif /** * Used to keep track of the last metadata save job submitted to the save service and @@ -147,6 +156,12 @@ public class PacketListener extends ChannelInboundHandlerAdapter { throw new RuntimeException(e); } }); + + //#if MC>=11300 + if (packet instanceof SPacketLoginSuccess) { + connectionState = EnumConnectionState.PLAY; + } + //#endif } catch(Exception e) { logger.error("Writing packet:", e); } @@ -339,9 +354,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter { //#endif //#if MC>=10800 - Integer packetId = EnumConnectionState.PLAY.getPacketId(EnumPacketDirection.CLIENTBOUND, packet); + Integer packetId = connectionState.getPacketId(EnumPacketDirection.CLIENTBOUND, packet); //#else - //$$ Integer packetId = (Integer) EnumConnectionState.PLAY.func_150755_b().inverse().get(packet.getClass()); + //$$ Integer packetId = (Integer) connectionState.func_150755_b().inverse().get(packet.getClass()); //#endif if (packetId == null) { throw new IOException("Unknown packet type:" + packet.getClass()); diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index cab8b6f3..b56d2f5a 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -48,8 +48,10 @@ import net.minecraft.world.GameType; //$$ import net.minecraft.world.WorldSettings.GameType; //#endif //#if MC>=10904 +import net.minecraft.network.login.server.SPacketLoginSuccess; import net.minecraft.util.text.ITextComponent; //#else +//$$ import net.minecraft.network.login.server.S02PacketLoginSuccess; //$$ import net.minecraft.util.IChatComponent; //#endif @@ -123,6 +125,9 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend private static int TP_DISTANCE_LIMIT = 128; + private static final ByteBuf byteBuf = Unpooled.buffer(); + private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); + /** * The replay handler responsible for the current replay. */ @@ -165,12 +170,22 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend */ protected ReplayInputStream replayIn; + /** + * @see PacketData#PacketData(ReplayInputStream, ReplayOutputStream) + */ + private ReplayOutputStream encoder; + /** * The next packet that should be sent. * This is required as some actions such as jumping to a specified timestamp have to peek at the next packet. */ protected PacketData nextPacket; + /** + * Whether we're currently reading packets from the login phase. + */ + private boolean loginPhase; + /** * Whether we need to restart the current replay. E.g. when jumping backwards in time */ @@ -317,7 +332,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if (world(mc) != null) { for (EntityPlayer playerEntity : playerEntities(world(mc))) { if (!playerEntity.addedToChunk && playerEntity instanceof EntityOtherPlayerMP) { - // FIXME playerEntity.onLivingUpdate(); + //#if MC>=11300 + playerEntity.livingTick(); + //#else + //$$ playerEntity.onLivingUpdate(); + //#endif } } } @@ -416,10 +435,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend int i = readVarInt(pb); + EnumConnectionState state = loginPhase ? EnumConnectionState.LOGIN : EnumConnectionState.PLAY; //#if MC>=10800 - Packet p = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, i); + Packet p = state.getPacket(EnumPacketDirection.CLIENTBOUND, i); //#else - //$$ Packet p = Packet.generatePacket(EnumConnectionState.PLAY.func_150755_b(), i); + //$$ Packet p = Packet.generatePacket(state.func_150755_b(), i); //#endif p.readPacketData(pb); @@ -432,6 +452,15 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend * @return The processed packet or {@code null} if no packet shall be sent */ protected Packet processPacket(Packet p) throws Exception { + //#if MC>=10904 + if (p instanceof SPacketLoginSuccess) { + //#else + //$$ if (p instanceof S02PacketLoginSuccess) { + //#endif + loginPhase = false; + return p; + } + //#if MC>=10904 if (p instanceof SPacketCustomPayload) { SPacketCustomPayload packet = (SPacketCustomPayload) p; @@ -584,7 +613,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#endif EnumDifficulty difficulty = packet.getDifficulty(); //#if MC>=11300 - int maxPlayers = 0;// FIXME needs AT packet.maxPlayers; + int maxPlayers = packet.maxPlayers; //#else //$$ int maxPlayers = packet.getMaxPlayers(); //#endif @@ -832,7 +861,8 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend while (!terminate) { synchronized (FullReplaySender.this) { if (replayIn == null) { - replayIn = replayFile.getPacketData(); + replayIn = replayFile.getPacketData(new ReplayStudio(), true); + encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut, true); } // Packet loop while (true) { @@ -859,7 +889,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Read the next packet if we don't already have one if (nextPacket == null) { - nextPacket = new PacketData(replayIn); + nextPacket = new PacketData(replayIn, encoder); } int nextTimeStamp = nextPacket.timestamp; @@ -912,6 +942,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Restart the replay. hasWorldLoaded = false; lastTimeStamp = 0; + loginPhase = true; startFromBeginning = false; nextPacket = null; lastPacketSent = System.currentTimeMillis(); @@ -1025,13 +1056,15 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend replayIn.close(); replayIn = null; } + loginPhase = true; startFromBeginning = false; nextPacket = null; replayHandler.restartedReplay(); } if (replayIn == null) { - replayIn = replayFile.getPacketData(); + replayIn = replayFile.getPacketData(new ReplayStudio(), true); + encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut, true); } while (true) { // Send packets @@ -1043,7 +1076,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend nextPacket = null; } else { // Otherwise read one from the input stream - pd = new PacketData(replayIn); + pd = new PacketData(replayIn, encoder); } int nextTimeStamp = pd.timestamp; @@ -1176,19 +1209,16 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend } private static final class PacketData { - private static final ByteBuf byteBuf = Unpooled.buffer(); - private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); - private static final ReplayOutputStream encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut); private final int timestamp; private final byte[] bytes; - public PacketData(ReplayInputStream in) throws IOException { + PacketData(ReplayInputStream in, ReplayOutputStream encoder) throws IOException { com.replaymod.replaystudio.PacketData data = in.readPacket(); timestamp = (int) data.getTime(); // We need to re-encode MCProtocolLib packets, so we can later decode them as NMS packets // The main reason we aren't reading them as NMS packets is that we want ReplayStudio to be able // to apply ViaVersion (and potentially other magic) to it. - synchronized (encoder) { + synchronized (byteBuf) { byteBuf.markReaderIndex(); // Mark the current reader and writer index (should be at start) byteBuf.markWriterIndex(); diff --git a/src/main/java/com/replaymod/replay/InputReplayTimer.java b/src/main/java/com/replaymod/replay/InputReplayTimer.java index 0a9e1efb..4359da74 100644 --- a/src/main/java/com/replaymod/replay/InputReplayTimer.java +++ b/src/main/java/com/replaymod/replay/InputReplayTimer.java @@ -3,18 +3,19 @@ package com.replaymod.replay; import com.replaymod.core.utils.WrappedTimer; import com.replaymod.replay.camera.CameraController; import com.replaymod.replay.camera.CameraEntity; -import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.settings.KeyBinding; -import net.minecraft.crash.CrashReport; import net.minecraft.util.Timer; -import net.minecraftforge.client.ForgeHooksClient; -import net.minecraftforge.common.MinecraftForge; //#if MC>=11300 +import org.lwjgl.glfw.GLFW; //#else +//$$ import com.replaymod.replay.events.ReplayDispatchKeypressesEvent; +//$$ import net.minecraft.client.gui.GuiScreen; //$$ import net.minecraft.client.settings.GameSettings; +//$$ import net.minecraft.client.settings.KeyBinding; +//$$ import net.minecraft.crash.CrashReport; +//$$ import net.minecraftforge.client.ForgeHooksClient; +//$$ import net.minecraftforge.common.MinecraftForge; //$$ import org.lwjgl.input.Keyboard; //$$ import org.lwjgl.input.Mouse; //#if MC>=10800 @@ -28,6 +29,7 @@ import net.minecraftforge.common.MinecraftForge; //$$ //$$ import static com.replaymod.core.versions.MCVer.FML_BUS; //#endif +//$$ import static com.replaymod.core.versions.MCVer.newReportedException; //#endif public class InputReplayTimer extends WrappedTimer { @@ -40,10 +42,14 @@ public class InputReplayTimer extends WrappedTimer { this.mc = mod.getCore().getMinecraft(); } - /* FIXME @Override - public void updateTimer() { - super.updateTimer(); + //#if MC>=11300 + public void updateTimer(long sysClock) { + super.updateTimer(sysClock); + //#else + //$$ public void updateTimer() { + //$$ super.updateTimer(); + //#endif // 1.7.10: We have to run the scheduled executables (ours only) because MC would only run them every tick //#if MC<=10710 @@ -53,43 +59,41 @@ public class InputReplayTimer extends WrappedTimer { // If we are in a replay, we have to manually process key and mouse events as the // tick speed may vary or there may not be any ticks at all (when the replay is paused) if (mod.getReplayHandler() != null) { + //#if MC>=11300 if (mc.currentScreen == null || mc.currentScreen.allowUserInput) { - while (Mouse.next()) { - handleMouseEvent(); - } - - while (Keyboard.next()) { - handleKeyEvent(); - } - } else { + GLFW.glfwPollEvents(); + mc.processKeyBinds(); + } + mc.keyboardListener.tick(); + //#else + //$$ if (mc.currentScreen == null || mc.currentScreen.allowUserInput) { + //$$ while (Mouse.next()) { + //$$ handleMouseEvent(); + //$$ } + //$$ + //$$ while (Keyboard.next()) { + //$$ handleKeyEvent(); + //$$ } + //$$ } else { + //#if MC<11300 //#if MC>=10800 - try { - mc.currentScreen.handleInput(); - } catch (IOException e) { // *SIGH* - e.printStackTrace(); - } + //$$ try { + //$$ mc.currentScreen.handleInput(); + //$$ } catch (IOException e) { // *SIGH* + //$$ e.printStackTrace(); + //$$ } //#else //$$ mc.currentScreen.handleInput(); //#endif - } + //#endif + //$$ } + //#endif } } - protected void handleMouseEvent() { - if (ForgeHooksClient.postMouseEvent()) return; - - int button = Mouse.getEventButton() - 100; - boolean pressed = Mouse.getEventButtonState(); - - // Update key binding states - KeyBinding.setKeyBindState(button, pressed); - if (pressed) { - KeyBinding.onTick(button); - } - - int wheel = Mouse.getEventDWheel(); + public static void handleScroll(int wheel) { if (wheel != 0) { - ReplayHandler replayHandler = mod.getReplayHandler(); + ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); if (replayHandler != null) { CameraEntity cameraEntity = replayHandler.getCameraEntity(); if (cameraEntity != null) { @@ -105,164 +109,181 @@ public class InputReplayTimer extends WrappedTimer { } } } + } - if (mc.currentScreen == null) { - if (!mc.inGameHasFocus && Mouse.getEventButtonState()) { - // Regrab mouse if the user clicks into the window - mc.setIngameFocus(); - } - } else { + //#if MC<11300 + //$$ protected void handleMouseEvent() { + //$$ if (ForgeHooksClient.postMouseEvent()) return; + //$$ + //$$ int button = Mouse.getEventButton() - 100; + //$$ boolean pressed = Mouse.getEventButtonState(); + //$$ + //$$ // Update key binding states + //$$ KeyBinding.setKeyBindState(button, pressed); + //$$ if (pressed) { + //$$ KeyBinding.onTick(button); + //$$ } + //$$ + //$$ int wheel = Mouse.getEventDWheel(); + //$$ handleScroll(wheel); + //$$ + //$$ if (mc.currentScreen == null) { + //$$ if (!mc.inGameHasFocus && Mouse.getEventButtonState()) { + //$$ // Regrab mouse if the user clicks into the window + //$$ mc.setIngameFocus(); + //$$ } + //$$ } else { //#if MC>=10800 - try { - mc.currentScreen.handleMouseInput(); - } catch (IOException e) { // WHO IS RESPONSIBLE FOR THIS MESS?!? - e.printStackTrace(); - } + //$$ try { + //$$ mc.currentScreen.handleMouseInput(); + //$$ } catch (IOException e) { // WHO IS RESPONSIBLE FOR THIS MESS?!? + //$$ e.printStackTrace(); + //$$ } //#else //$$ mc.currentScreen.handleMouseInput(); //#endif - } - - FMLCommonHandler.instance().fireMouseInput(); - } - - protected void handleKeyEvent() { - // TODO 1.7.10: This might be missing some 1.7.10-only key bindings or implement some of them incorrectly - int key = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); - boolean pressed = Keyboard.getEventKeyState(); - - KeyBinding.setKeyBindState(key, pressed); - if (pressed) { - KeyBinding.onTick(key); - } - - // Still want to be able to create debug crashes ]:D - if (mc.debugCrashKeyPressTime > 0) { - if (Minecraft.getSystemTime() - mc.debugCrashKeyPressTime >= 6000L) { - throw new ReportedException(new CrashReport("Manually triggered debug crash", new Throwable())); - } - - if (!Keyboard.isKeyDown(Keyboard.KEY_F3) || !Keyboard.isKeyDown(Keyboard.KEY_C)) { - mc.debugCrashKeyPressTime = -1; - } - } else if (Keyboard.isKeyDown(Keyboard.KEY_F3) && Keyboard.isKeyDown(Keyboard.KEY_C)) { - mc.debugCrashKeyPressTime = Minecraft.getSystemTime(); - } - - // Twitch, screenshot, fullscreen, etc. (stuff that works everywhere) - if (!MinecraftForge.EVENT_BUS.post(new ReplayDispatchKeypressesEvent.Pre())) { - mc.dispatchKeypresses(); - } - - if (pressed) { - // This might be subject to change as vanilla shaders are still kinda unused in 1.8 - if (key == Keyboard.KEY_F4 && mc.entityRenderer != null) { + //$$ } + //$$ + //$$ FMLCommonHandler.instance().fireMouseInput(); + //$$ } + //$$ + //$$ protected void handleKeyEvent() { + //$$ // TODO 1.7.10: This might be missing some 1.7.10-only key bindings or implement some of them incorrectly + //$$ int key = Keyboard.getEventKey() == 0 ? Keyboard.getEventCharacter() + 256 : Keyboard.getEventKey(); + //$$ boolean pressed = Keyboard.getEventKeyState(); + //$$ + //$$ KeyBinding.setKeyBindState(key, pressed); + //$$ if (pressed) { + //$$ KeyBinding.onTick(key); + //$$ } + //$$ + //$$ // Still want to be able to create debug crashes ]:D + //$$ if (mc.debugCrashKeyPressTime > 0) { + //$$ if (Minecraft.getSystemTime() - mc.debugCrashKeyPressTime >= 6000L) { + //$$ throw newReportedException(new CrashReport("Manually triggered debug crash", new Throwable())); + //$$ } + //$$ + //$$ if (!Keyboard.isKeyDown(Keyboard.KEY_F3) || !Keyboard.isKeyDown(Keyboard.KEY_C)) { + //$$ mc.debugCrashKeyPressTime = -1; + //$$ } + //$$ } else if (Keyboard.isKeyDown(Keyboard.KEY_F3) && Keyboard.isKeyDown(Keyboard.KEY_C)) { + //$$ mc.debugCrashKeyPressTime = Minecraft.getSystemTime(); + //$$ } + //$$ + //$$ // Twitch, screenshot, fullscreen, etc. (stuff that works everywhere) + //$$ if (!MinecraftForge.EVENT_BUS.post(new ReplayDispatchKeypressesEvent.Pre())) { + //$$ mc.dispatchKeypresses(); + //$$ } + //$$ + //$$ if (pressed) { + //$$ // This might be subject to change as vanilla shaders are still kinda unused in 1.8 + //$$ if (key == Keyboard.KEY_F4 && mc.entityRenderer != null) { //#if MC>=10800 - mc.entityRenderer.switchUseShader(); + //$$ mc.entityRenderer.switchUseShader(); //#else //$$ mc.entityRenderer.activateNextShader(); //#endif - } - - if (mc.currentScreen != null) { + //$$ } + //$$ + //$$ if (mc.currentScreen != null) { //#if MC>=10800 - try { - mc.currentScreen.handleKeyboardInput(); - } catch (IOException e) { // AND WHO THOUGHT THIS WAS A GREAT IDEA? - e.printStackTrace(); - } + //$$ try { + //$$ mc.currentScreen.handleKeyboardInput(); + //$$ } catch (IOException e) { // AND WHO THOUGHT THIS WAS A GREAT IDEA? + //$$ e.printStackTrace(); + //$$ } //#else //$$ mc.currentScreen.handleKeyboardInput(); //#endif - } else { - if (key == Keyboard.KEY_ESCAPE) { - mc.displayInGameMenu(); - } - - // Following are a ton of vanilla keyboard shortcuts, some are removed as they're useless in the - // replay viewer as of now - // TODO Update maybe add new key bindings - // TODO: Translate magic values to Keyboard.KEY_ constants - - if (key == 32 && Keyboard.isKeyDown(61) && mc.ingameGUI != null) { + //$$ } else { + //$$ if (key == Keyboard.KEY_ESCAPE) { + //$$ mc.displayInGameMenu(); + //$$ } + //$$ + //$$ // Following are a ton of vanilla keyboard shortcuts, some are removed as they're useless in the + //$$ // replay viewer as of now + //$$ // TODO Update maybe add new key bindings + //$$ // TODO: Translate magic values to Keyboard.KEY_ constants + //$$ + //$$ if (key == 32 && Keyboard.isKeyDown(61) && mc.ingameGUI != null) { //#if MC>=11100 - mc.ingameGUI.getChatGUI().clearChatMessages(false); + //$$ mc.ingameGUI.getChatGUI().clearChatMessages(false); //#else //$$ mc.ingameGUI.getChatGUI().clearChatMessages(); //#endif - } - - if (key == 31 && Keyboard.isKeyDown(61)) { - mc.refreshResources(); - } - - if (key == 20 && Keyboard.isKeyDown(61)) { - mc.refreshResources(); - } - - if (key == 33 && Keyboard.isKeyDown(61)) { - boolean flag1 = Keyboard.isKeyDown(42) | Keyboard.isKeyDown(54); - mc.gameSettings.setOptionValue(GameSettings.Options.RENDER_DISTANCE, flag1 ? -1 : 1); - } - - if (key == 30 && Keyboard.isKeyDown(61)) { - mc.renderGlobal.loadRenderers(); - } - - if (key == 48 && Keyboard.isKeyDown(61)) { + //$$ } + //$$ + //$$ if (key == 31 && Keyboard.isKeyDown(61)) { + //$$ mc.refreshResources(); + //$$ } + //$$ + //$$ if (key == 20 && Keyboard.isKeyDown(61)) { + //$$ mc.refreshResources(); + //$$ } + //$$ + //$$ if (key == 33 && Keyboard.isKeyDown(61)) { + //$$ boolean flag1 = Keyboard.isKeyDown(42) | Keyboard.isKeyDown(54); + //$$ mc.gameSettings.setOptionValue(GameSettings.Options.RENDER_DISTANCE, flag1 ? -1 : 1); + //$$ } + //$$ + //$$ if (key == 30 && Keyboard.isKeyDown(61)) { + //$$ mc.renderGlobal.loadRenderers(); + //$$ } + //$$ + //$$ if (key == 48 && Keyboard.isKeyDown(61)) { //#if MC>=10800 - mc.getRenderManager().setDebugBoundingBox(!mc.getRenderManager().isDebugBoundingBox()); + //$$ mc.getRenderManager().setDebugBoundingBox(!mc.getRenderManager().isDebugBoundingBox()); //#else //$$ RenderManager.debugBoundingBox = !RenderManager.debugBoundingBox; //#endif - } - - if (key == 25 && Keyboard.isKeyDown(61)) { - mc.gameSettings.pauseOnLostFocus = !mc.gameSettings.pauseOnLostFocus; - mc.gameSettings.saveOptions(); - } - - if (key == 59) { - mc.gameSettings.hideGUI = !mc.gameSettings.hideGUI; - } - - if (key == 61) { - mc.gameSettings.showDebugInfo = !mc.gameSettings.showDebugInfo; - mc.gameSettings.showDebugProfilerChart = GuiScreen.isShiftKeyDown(); - } - - if (mc.gameSettings.keyBindTogglePerspective.isPressed()) { - mc.gameSettings.thirdPersonView = (mc.gameSettings.thirdPersonView + 1) % 3; - + //$$ } + //$$ + //$$ if (key == 25 && Keyboard.isKeyDown(61)) { + //$$ mc.gameSettings.pauseOnLostFocus = !mc.gameSettings.pauseOnLostFocus; + //$$ mc.gameSettings.saveOptions(); + //$$ } + //$$ + //$$ if (key == 59) { + //$$ mc.gameSettings.hideGUI = !mc.gameSettings.hideGUI; + //$$ } + //$$ + //$$ if (key == 61) { + //$$ mc.gameSettings.showDebugInfo = !mc.gameSettings.showDebugInfo; + //$$ mc.gameSettings.showDebugProfilerChart = GuiScreen.isShiftKeyDown(); + //$$ } + //$$ + //$$ if (mc.gameSettings.keyBindTogglePerspective.isPressed()) { + //$$ mc.gameSettings.thirdPersonView = (mc.gameSettings.thirdPersonView + 1) % 3; + //$$ //#if MC>=10800 - if (mc.entityRenderer != null) { // Extra check, not in vanilla code - if (mc.gameSettings.thirdPersonView == 0) { - mc.entityRenderer.loadEntityShader(mc.getRenderViewEntity()); - } else if (mc.gameSettings.thirdPersonView == 1) { - mc.entityRenderer.loadEntityShader(null); - } - } + //$$ if (mc.entityRenderer != null) { // Extra check, not in vanilla code + //$$ if (mc.gameSettings.thirdPersonView == 0) { + //$$ mc.entityRenderer.loadEntityShader(mc.getRenderViewEntity()); + //$$ } else if (mc.gameSettings.thirdPersonView == 1) { + //$$ mc.entityRenderer.loadEntityShader(null); + //$$ } + //$$ } //#endif - } - } - - // Navigation in the debug chart - if (mc.gameSettings.showDebugInfo && mc.gameSettings.showDebugProfilerChart) { - if (key == Keyboard.KEY_0) { - mc.updateDebugProfilerName(0); - } - - for (int i = 0; i < 9; ++i) { - if (key == 2 + i) { - mc.updateDebugProfilerName(i + 1); - } - } - } - } - - FMLCommonHandler.instance().fireKeyInput(); - } - */ + //$$ } + //$$ } + //$$ + //$$ // Navigation in the debug chart + //$$ if (mc.gameSettings.showDebugInfo && mc.gameSettings.showDebugProfilerChart) { + //$$ if (key == Keyboard.KEY_0) { + //$$ mc.updateDebugProfilerName(0); + //$$ } + //$$ + //$$ for (int i = 0; i < 9; ++i) { + //$$ if (key == 2 + i) { + //$$ mc.updateDebugProfilerName(i + 1); + //$$ } + //$$ } + //$$ } + //$$ } + //$$ + //$$ FMLCommonHandler.instance().fireKeyInput(); + //$$ } + //#endif //#if MC<=10710 //$$ public static class RunScheduledTasks extends Event {} diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index f5bf7543..7f2087d4 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -1,6 +1,7 @@ //#if MC>=10904 package com.replaymod.replay; +import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; import com.github.steveice10.mc.protocol.data.game.chunk.BlockStorage; @@ -28,6 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMultiB import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerNotifyClientPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTimePacket; +import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; import com.github.steveice10.mc.protocol.util.NetUtil; import com.github.steveice10.packetlib.io.NetInput; import com.github.steveice10.packetlib.io.NetOutput; @@ -209,6 +211,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe public void restart() { activeThings.clear(); currentTimeStamp = 0; + ctx.fireChannelRead(toMC(new LoginSuccessPacket(new GameProfile(UUID.nameUUIDFromBytes(new byte[0]), "Player")), EnumConnectionState.LOGIN)); ctx.fireChannelRead(toMC(new ServerRespawnPacket(0, Difficulty.NORMAL, GameMode.SPECTATOR, WorldType.DEFAULT))); ctx.fireChannelRead(toMC(new ServerPlayerPositionRotationPacket(0, 0, 0, 0, 0, 0))); } @@ -624,6 +627,10 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private static final Deflater deflater = new Deflater(); private static Packet toMC(com.github.steveice10.packetlib.packet.Packet packet) { + return toMC(packet, EnumConnectionState.PLAY); + } + + private static Packet toMC(com.github.steveice10.packetlib.packet.Packet packet, EnumConnectionState state) { // We need to re-encode MCProtocolLib packets, so we can then decode them as NMS packets // The main reason we aren't reading them as NMS packets is that we want ReplayStudio to be able // to apply ViaVersion (and potentially other magic) to it. @@ -642,7 +649,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe //#else //$$ packetBuf.readVarIntFromBuffer(); //#endif - Packet mcPacket = EnumConnectionState.PLAY.getPacket(EnumPacketDirection.CLIENTBOUND, packetId); + Packet mcPacket = state.getPacket(EnumPacketDirection.CLIENTBOUND, packetId); mcPacket.readPacketData(packetBuf); return mcPacket; } catch (Exception e) { diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 1ac42f97..5abef2d5 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -49,7 +49,6 @@ import net.minecraft.entity.EntityLivingBase; //#if MC>=10800 import net.minecraft.network.EnumPacketDirection; //#if MC>=11300 -import net.minecraft.client.network.NetHandlerLoginClient; import net.minecraftforge.fml.network.NetworkHooks; //#else //$$ import com.mojang.authlib.GameProfile; @@ -57,6 +56,7 @@ import net.minecraftforge.fml.network.NetworkHooks; //$$ import net.minecraftforge.fml.client.FMLClientHandler; //$$ import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher; //#endif +import net.minecraft.client.network.NetHandlerLoginClient; import static net.minecraft.client.renderer.GlStateManager.*; //#else @@ -236,32 +236,25 @@ public class ReplayHandler { networkManager.setNetHandler(new NetHandlerLoginClient(networkManager, mc, null, it -> {})); NetworkHooks.registerClientLoginChannel(networkManager); + // FIXME make this work (with vanilla and mods) on all other versions again, now that login phase is included + // probably have to change some of the forge handshake calls //#else - //$$ NetHandlerPlayClient netHandlerPlayClient = - //$$ new NetHandlerPlayClient(mc, null, networkManager, new GameProfile(UUID.randomUUID(), "Player")); - //$$ networkManager.setNetHandler(netHandlerPlayClient); - //$$ FMLClientHandler.instance().setPlayClient(netHandlerPlayClient); + //$$ NetHandlerLoginClient netHandlerLoginClient = + //$$ new NetHandlerLoginClient(networkManager, mc, null); + //$$ networkManager.setNetHandler(netHandlerLoginClient); //$$ //#if MC>=11200 //$$ channel = new EmbeddedChannel(); - //$$ NetworkDispatcher networkDispatcher = new NetworkDispatcher(networkManager); - //$$ channel.attr(NetworkDispatcher.FML_DISPATCHER).set(networkDispatcher); - //$$ //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); //$$ channel.pipeline().addFirst("ReplayModReplay_quickReplaySender", quickReplaySender); //$$ channel.pipeline().addLast("packet_handler", networkManager); //$$ channel.pipeline().fireChannelActive(); - //$$ networkDispatcher.clientToServerHandshake(); //#else //$$ channel = new EmbeddedChannel(networkManager); - //$$ NetworkDispatcher networkDispatcher = new NetworkDispatcher(networkManager); - //$$ channel.attr(NetworkDispatcher.FML_DISPATCHER).set(networkDispatcher); - //$$ //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); //#if MC>=10904 //$$ channel.pipeline().addFirst("ReplayModReplay_quickReplaySender", quickReplaySender); //#endif - //$$ channel.pipeline().addAfter("ReplayModReplay_replaySender", "fml:packet_handler", networkDispatcher); //$$ channel.pipeline().fireChannelActive(); //#endif //#endif @@ -295,13 +288,6 @@ public class ReplayHandler { //$$ channel.pipeline().addFirst("ReplayModReplay_replaySender", fullReplaySender); //$$ channel.pipeline().addAfter("ReplayModReplay_replaySender", "packet_handler", networkManager); //$$ channel.pipeline().fireChannelActive(); - //$$ - //$$ // Call twice to force-overwrite the NetworkManager's internal state - //$$ networkManager.setConnectionState(EnumConnectionState.PLAY); - //$$ networkManager.getNetHandler().onConnectionStateTransition(EnumConnectionState.LOGIN, EnumConnectionState.PLAY); - //$$ networkManager.setConnectionState(EnumConnectionState.PLAY); - //$$ - //$$ FMLNetworkHandler.fmlClientHandshake(networkManager); //#endif } diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 07e57031..077b8f20 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -172,7 +172,7 @@ public class ReplayModReplay implements Module { }); Minecraft mc = core.getMinecraft(); - // FIXME mc.timer = new InputReplayTimer(mc.timer, this); + mc.timer = new InputReplayTimer(mc.timer, this); new GuiHandler(this).register(); } diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 378750c3..c24b4607 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -399,7 +399,7 @@ public class GuiReplayViewer extends GuiScreen { } else { server.setText(metaData.getServerName()); } - incompatible = !new ReplayStudio().isCompatible(metaData.getFileFormatVersion()); + incompatible = !new ReplayStudio().isCompatible(metaData.getFileFormatVersion(), metaData.getProtocolVersion()); if (incompatible) { version.setText("Minecraft " + metaData.getMcVersion()); } diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java index b8875240..4dbbb260 100644 --- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java @@ -29,6 +29,9 @@ import static com.replaymod.core.versions.MCVer.*; import static com.replaymod.replay.ReplayModReplay.LOGGER; public class GuiHandler { + //#if MC>=11300 + private static final int BUTTON_OPTIONS = 0; + //#endif private static final int BUTTON_EXIT_SERVER = 1; private static final int BUTTON_ADVANCEMENTS = 5; private static final int BUTTON_STATS = 6; @@ -86,16 +89,24 @@ public class GuiHandler { case BUTTON_OPEN_TO_LAN: removeButton(event, openToLan = b); break; + //#if MC>=11300 + case BUTTON_OPTIONS: + b.width = 200; + break; + //#endif } } if (achievements != null && stats != null) { moveAllButtonsDirectlyBelowUpwards(buttonList, y(achievements), x(achievements), x(stats) + stats.width); } - if (openToLan != null) { - moveAllButtonsDirectlyBelowUpwards(buttonList, y(openToLan), - x(openToLan), x(openToLan) + openToLan.width); - } + // In 1.13+ Forge, the Options button shares one row with the Open to LAN button + //#if MC<11300 + //$$ if (openToLan != null) { + //$$ moveAllButtonsDirectlyBelowUpwards(buttonList, y(openToLan), + //$$ x(openToLan), x(openToLan) + openToLan.width); + //$$ } + //#endif } } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java index 92717657..8ff5e3ad 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java @@ -37,7 +37,7 @@ public class MixinTileEntityEndPortalRenderer { //#endif //#endif //#endif - private static long replayModReplay_getEnchantmentTime() { + private long replayModReplay_getEnchantmentTime() { ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); if (replayHandler != null) { return replayHandler.getReplaySender().currentTimeStamp(); diff --git a/src/main/resources/META-INF/replaymod_at.cfg b/src/main/resources/META-INF/replaymod_at.cfg index b1d4b8ff..48677d95 100644 --- a/src/main/resources/META-INF/replaymod_at.cfg +++ b/src/main/resources/META-INF/replaymod_at.cfg @@ -1,6 +1,9 @@ # To apply changes in this file run: ./gradlew clean setupDecompWorkspace idea # Minecraft +##if MC>=11300 +public net.minecraft.client.Minecraft func_184117_aA()V # processKeyBinds +##endif ##if MC>=11002 public-f net.minecraft.client.Minecraft field_71428_T # timer ##else @@ -25,6 +28,9 @@ public net.minecraft.client.Minecraft func_147112_ai()V # middleClickMouse public net.minecraft.client.Minecraft func_147115_a(Z)V # sendClickBlockToController # Packets +##if MC>=11300 +public net.minecraft.network.play.server.SPacketJoinGame field_149200_f # maxPlayers +##endif ##if MC>=10904 public net.minecraft.network.play.server.SPacketSpawnMob field_149043_l # dataManager public net.minecraft.network.play.server.SPacketSpawnPlayer field_148960_i # watcher diff --git a/src/main/resources/mixins.recording.replaymod.json b/src/main/resources/mixins.recording.replaymod.json index a7429f91..d6278d85 100644 --- a/src/main/resources/mixins.recording.replaymod.json +++ b/src/main/resources/mixins.recording.replaymod.json @@ -4,6 +4,10 @@ "mixins": [], "server": [], "client": [ + //#if MC>=11300 + "MixinKeyboardListener", + "MixinMouseHelper", + //#endif //#if MC>=10904 "MixinPlayerControllerMP", "MixinWorldClient", @@ -14,7 +18,9 @@ //#endif "MixinNetHandlerLoginClient", "MixinNetHandlerPlayClient", - "MixinNetworkDispatcher", + //#if MC<11300 + //$$ "MixinNetworkDispatcher", + //#endif "MixinRenderGlobal" ], "compatibilityLevel": "JAVA_8", diff --git a/versions/common.gradle b/versions/common.gradle index 29feb7b8..bf8f86c7 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -184,7 +184,7 @@ dependencies { def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' - shade("com.replaymod.replaystudio:$studioVersion:98ab667"){ // FIXME + shade("com.replaymod.replaystudio:$studioVersion:9e362ef"){ // FIXME exclude group: 'com.google.guava', module: 'guava' // provided by MC exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } From cdcd6f689320704b3af8ab072db1933b6e1a2b97 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 10 Mar 2019 15:56:48 +0100 Subject: [PATCH 46/65] Get render module mostly working --- .../replaymod/online/PlainFileAuthData.java | 3 +- .../render/capturer/OpenGlFrameCapturer.java | 2 + .../render/gui/GuiRenderSettings.java | 3 +- .../render/gui/GuiVideoRenderer.java | 11 +- .../render/hooks/EntityRendererHandler.java | 4 - .../render/mixin/MixinEntityRenderer.java | 114 ++++++++++++------ .../render/mixin/MixinFogRenderer.java | 24 ++++ .../render/mixin/MixinRenderGlobal.java | 27 ++++- .../render/mixin/MixinRenderManager.java | 6 +- .../render/rendering/VideoRenderer.java | 10 +- src/main/resources/META-INF/replaymod_at.cfg | 6 + .../resources/mixins.render.replaymod.json | 3 + 12 files changed, 158 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java diff --git a/src/main/java/com/replaymod/online/PlainFileAuthData.java b/src/main/java/com/replaymod/online/PlainFileAuthData.java index b237d2a4..4ff020ae 100644 --- a/src/main/java/com/replaymod/online/PlainFileAuthData.java +++ b/src/main/java/com/replaymod/online/PlainFileAuthData.java @@ -8,6 +8,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -32,7 +33,7 @@ public class PlainFileAuthData implements AuthData { String authKey; try { authKey = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); - } catch (FileNotFoundException ignored) { + } catch (NoSuchFileException | FileNotFoundException ignored) { return; } AuthConfirmation result = apiClient.checkAuthkey(authKey); diff --git a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java index 2f65b566..3af26f93 100644 --- a/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/OpenGlFrameCapturer.java @@ -119,6 +119,8 @@ public abstract class OpenGlFrameCapturer=11300 NativeImage data = previewTexture.getTextureData(); assert data != null; - for (int x = 0; x < data.getWidth(); x++) { - for (int y = 0; y < data.getHeight(); y++) { - int value = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff); - data.setPixelRGBA(x, y, value); + for (int y = 0; y < data.getHeight(); y++) { + for (int x = 0; x < data.getWidth(); x++) { + int r = buffer.get() & 0xff; + int g = buffer.get() & 0xff; + int b = buffer.get() & 0xff; + int value = 0xff << 24 | b << 16 | g << 8 | r; + data.setPixelRGBA(x, y, value); // actually takes ABGR, not RGBA } } //#else diff --git a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java index 9566662f..fe6ef9fa 100644 --- a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java +++ b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java @@ -88,10 +88,6 @@ public class EntityRendererHandler implements WorldRenderer { this.omnidirectional = omnidirectional; } - public interface GluPerspective { - void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar); - } - public interface IEntityRenderer { void replayModRender_setHandler(EntityRendererHandler handler); EntityRendererHandler replayModRender_getHandler(); diff --git a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java index a32a2dcf..aca1cea1 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java +++ b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java @@ -50,7 +50,7 @@ import static com.replaymod.core.versions.MCVer.*; //#else //$$ @Mixin(value = EntityRenderer.class) //#endif -public abstract class MixinEntityRenderer implements EntityRendererHandler.IEntityRenderer, EntityRendererHandler.GluPerspective { +public abstract class MixinEntityRenderer implements EntityRendererHandler.IEntityRenderer { @Shadow public Minecraft mc; @@ -77,13 +77,16 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti //$$ } //#endif - @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true) - private void replayModRender_onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) { - if (replayModRender_handler == null) return; - if (replayModRender_handler.getSettings().getChromaKeyingColor() != null) { - ci.cancel(); - } - } + // Moved to MixinFogRenderer in 1.13 + //#if MC<11300 + //$$ @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true) + //$$ private void replayModRender_onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) { + //$$ if (replayModRender_handler == null) return; + //$$ if (replayModRender_handler.getSettings().getChromaKeyingColor() != null) { + //$$ ci.cancel(); + //$$ } + //$$ } + //#endif @Inject(method = "orientCamera", at = @At("HEAD")) private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) { @@ -109,7 +112,13 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti private float orgRoll; @Inject(method = "setupCameraTransform", at = @At("HEAD")) - private void replayModRender_beforeSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { + private void replayModRender_beforeSetupCameraTransform( + float partialTicks, + //#if MC<11300 + //$$ int renderPass, + //#endif + CallbackInfo ci + ) { if (replayModRender_handler != null) { Entity entity = getRenderViewEntity(MCVer.getMinecraft()); orgYaw = entity.rotationYaw; @@ -121,7 +130,13 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti } @Inject(method = "setupCameraTransform", at = @At("RETURN")) - private void replayModRender_afterSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) { + private void replayModRender_afterSetupCameraTransform( + float partialTicks, + //#if MC<11300 + //$$ int renderPass, + //#endif + CallbackInfo ci + ) { if (replayModRender_handler != null) { Entity entity = getRenderViewEntity(MCVer.getMinecraft()); entity.rotationYaw = orgYaw; @@ -135,28 +150,41 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti } @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true) - private void replayModRender_renderSpectatorHand(float partialTicks, int renderPass, CallbackInfo ci) { + private void replayModRender_renderSpectatorHand( + float partialTicks, + //#if MC<11300 + //$$ int renderPass, + //#endif + CallbackInfo ci + ) { if (replayModRender_handler != null) { if (replayModRender_handler.omnidirectional) { + // No spectator hands during 360° view, we wouldn't even know where to put it ci.cancel(); - return; // No spectator hands during 360° view, we wouldn't even know where to put it - } - Entity currentEntity = getRenderViewEntity(MCVer.getMinecraft()); - if (currentEntity instanceof EntityPlayer && !(currentEntity instanceof CameraEntity)) { - if (renderPass == 2) { // Need to update render pass - renderPass = replayModRender_handler.data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE ? 1 : 0; - renderHand(partialTicks, renderPass); - ci.cancel(); - } // else, render normally + //#if MC>=11300 } + //#else + //$$ } else { + //$$ Entity currentEntity = getRenderViewEntity(MCVer.getMinecraft()); + //$$ if (currentEntity instanceof EntityPlayer && !(currentEntity instanceof CameraEntity)) { + //$$ if (renderPass == 2) { // Need to update render pass + //$$ renderPass = replayModRender_handler.data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE ? 1 : 0; + //$$ renderHand(partialTicks, renderPass); + //$$ ci.cancel(); + //$$ } // else, render normally + //$$ } + //$$ } + //#endif } } - @Shadow - public abstract void renderHand(float partialTicks, int renderPass); + //#if MC<11300 + //$$ @Shadow + //$$ public abstract void renderHand(float partialTicks, int renderPass); + //#endif //#if MC>=11300 - @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/math/RayTraceResult;IF)V")) + @Redirect(method = "updateCameraAndRender(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/math/RayTraceResult;IF)V")) private void replayModRender_drawSelectionBox(WorldRenderer instance, EntityPlayer player, RayTraceResult rtr, int alwaysZero, float partialTicks) { //#else //#if MC>=10904 @@ -179,7 +207,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti private int orgRenderDistanceChunks; //#if MC>=11300 - @Inject(method = "updateCameraAndRender", at = @At(value = "JUMP", ordinal = 0)) + @Inject(method = "updateCameraAndRender(FJ)V", at = @At(value = "JUMP", ordinal = 0)) //#else //#if MC>=10800 //$$ @Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0)) @@ -197,7 +225,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti } //#if MC>=11300 - @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;renderSky(F)V")) + @Redirect(method = "updateCameraAndRender(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/WorldRenderer;renderSky(F)V")) private void replayModRender_renderSky(WorldRenderer instance, float partialTicks) { //#else //#if MC>=10800 @@ -235,7 +263,13 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti //#else //$$ @Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glLoadIdentity()V", shift = At.Shift.AFTER, ordinal = 0, remap = false)) //#endif - private void replayModRender_setupStereoscopicProjection(float partialTicks, int renderPass, CallbackInfo ci) { + private void replayModRender_setupStereoscopicProjection( + float partialTicks, + //#if MC<11300 + //$$ int renderPass, + //#endif + CallbackInfo ci + ) { if (replayModRender_getHandler() != null) { if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { GL11.glTranslatef(0.07f, 0, 0); @@ -250,7 +284,13 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti //#else //$$ @Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glLoadIdentity()V", shift = At.Shift.AFTER, ordinal = 1, remap = false)) //#endif - private void replayModRender_setupStereoscopicModelView(float partialTicks, int renderPass, CallbackInfo ci) { + private void replayModRender_setupStereoscopicModelView( + float partialTicks, + //#if MC<11300 + //$$ int renderPass, + //#endif + CallbackInfo ci + ) { if (replayModRender_getHandler() != null) { if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) { GL11.glTranslatef(0.1f, 0, 0); @@ -265,23 +305,22 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti */ //#if MC>=11300 - @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)V")) - private void replayModRender_gluPerspective$0(double fovY, float aspect, float zNear, float zFar) { - replayModRender_gluPerspective((float) fovY, aspect, zNear, zFar); + @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)Lnet/minecraft/client/renderer/Matrix4f;")) + private Matrix4f replayModRender_perspective$0(double fovY, float aspect, float zNear, float zFar) { + return replayModRender_perspective((float) fovY, aspect, zNear, zFar); } - @Redirect(method = "updateCameraAndRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)V")) - private void replayModRender_gluPerspective$1(double fovY, float aspect, float zNear, float zFar) { - replayModRender_gluPerspective((float) fovY, aspect, zNear, zFar); + @Redirect(method = "updateCameraAndRender(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)Lnet/minecraft/client/renderer/Matrix4f;")) + private Matrix4f replayModRender_perspective$1(double fovY, float aspect, float zNear, float zFar) { + return replayModRender_perspective((float) fovY, aspect, zNear, zFar); } - @Override - public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) { + private Matrix4f replayModRender_perspective(float fovY, float aspect, float zNear, float zFar) { if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { fovY = 90; aspect = 1; } - Matrix4f.perspective(fovY, aspect, zNear, zFar); + return Matrix4f.perspective(fovY, aspect, zNear, zFar); } //#else //$$ @Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false)) @@ -305,8 +344,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti //$$ } //#endif //$$ - //$$ @Override - //$$ public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) { + //$$ private void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) { //$$ if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { //$$ fovY = 90; //$$ aspect = 1; diff --git a/src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java new file mode 100644 index 00000000..5f51e5a5 --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/MixinFogRenderer.java @@ -0,0 +1,24 @@ +//#if MC>=11300 +package com.replaymod.render.mixin; + +import com.replaymod.core.versions.MCVer; +import com.replaymod.render.hooks.EntityRendererHandler; +import net.minecraft.client.renderer.FogRenderer; +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(FogRenderer.class) +public abstract class MixinFogRenderer { + @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true) + private void replayModRender_onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) { + EntityRendererHandler handler = + ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().entityRenderer).replayModRender_getHandler(); + if (handler == null) return; + if (handler.getSettings().getChromaKeyingColor() != null) { + ci.cancel(); + } + } +} +//#endif diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java index 4636d729..86374b7e 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderGlobal.java @@ -39,12 +39,31 @@ public abstract class MixinRenderGlobal { public ChunkRenderDispatcher renderDispatcher; @Shadow - public abstract void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, - int frameCount, boolean playerSpectator); + public abstract void setupTerrain( + Entity viewEntity, + //#if MC>=11300 + float partialTicks, + //#else + //$$ double partialTicks, + //#endif + ICamera camera, + int frameCount, + boolean playerSpectator + ); @Inject(method = "setupTerrain", at = @At("HEAD"), cancellable = true) - public void replayModRender_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, - int frameCount, boolean playerSpectator, CallbackInfo ci) { + private void replayModRender_setupTerrain( + Entity viewEntity, + //#if MC>=11300 + float partialTicks, + //#else + //$$ double partialTicks, + //#endif + ICamera camera, + int frameCount, + boolean playerSpectator, + CallbackInfo ci + ) { if (replayModRender_hook != null && !replayModRender_passThroughSetupTerrain) { replayModRender_passThroughSetupTerrain = true; diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java index cc67a9d6..389c7d88 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java @@ -23,7 +23,11 @@ public abstract class MixinRenderManager { @Shadow private float playerViewY; - @Inject(method = "doRenderEntity", at = @At("HEAD")) + //#if MC>=11300 + @Inject(method = "renderEntity", at = @At("HEAD")) + //#else + //$$ @Inject(method = "doRenderEntity", at = @At("HEAD")) + //#endif //#if MC>=10904 private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfo ci) { //#else diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 84d9c57a..f886624e 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -181,7 +181,10 @@ public class VideoRenderer implements RenderInfo { public float updateForNextFrame() { // because the jGui lib uses Minecraft's displayWidth and displayHeight values, update these temporarily //#if MC>=11300 - // FIXME is this even necessary? + int displayWidthBefore = mc.mainWindow.framebufferWidth; + int displayHeightBefore = mc.mainWindow.framebufferHeight; + mc.mainWindow.framebufferWidth = displayWidth; + mc.mainWindow.framebufferHeight = displayHeight; //#else //$$ int displayWidthBefore = mc.displayWidth; //$$ int displayHeightBefore = mc.displayHeight; @@ -207,7 +210,8 @@ public class VideoRenderer implements RenderInfo { // change Minecraft's display size back //#if MC>=11300 - // FIXME + mc.mainWindow.framebufferWidth = displayWidthBefore; + mc.mainWindow.framebufferHeight = displayHeightBefore; //#else //$$ mc.displayWidth = displayWidthBefore; //$$ mc.displayHeight = displayHeightBefore; @@ -359,6 +363,8 @@ public class VideoRenderer implements RenderInfo { // Finally, resize the Minecraft framebuffer to the actual width/height of the window //#if MC>=11300 mc.getFramebuffer().createFramebuffer(displayWidth, displayHeight); + mc.mainWindow.framebufferWidth = displayWidth; + mc.mainWindow.framebufferHeight = displayHeight; //#else //$$ mc.resize(displayWidth, displayHeight); //#endif diff --git a/src/main/resources/META-INF/replaymod_at.cfg b/src/main/resources/META-INF/replaymod_at.cfg index 48677d95..2c3a2c35 100644 --- a/src/main/resources/META-INF/replaymod_at.cfg +++ b/src/main/resources/META-INF/replaymod_at.cfg @@ -27,6 +27,12 @@ public net.minecraft.client.Minecraft func_147121_ag()V # rightClickMouse public net.minecraft.client.Minecraft func_147112_ai()V # middleClickMouse public net.minecraft.client.Minecraft func_147115_a(Z)V # sendClickBlockToController +##if MC>=11300 +# MainWindow +public net.minecraft.client.MainWindow field_198131_r # framebufferWidth +public net.minecraft.client.MainWindow field_198132_s # framebufferHeight +##endif + # Packets ##if MC>=11300 public net.minecraft.network.play.server.SPacketJoinGame field_149200_f # maxPlayers diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index 305fa27c..ab421e58 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -11,6 +11,9 @@ //$$ "MixinRendererLivingEntity", //#endif "MixinEntityRenderer", + //#if MC>=11300 + "MixinFogRenderer", + //#endif "MixinFrustum", "MixinRender", //#if MC>=10800 From 5f0bd1cf5d88b76649a734d3d6e707b2b3fa404c Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 10 Mar 2019 16:07:41 +0100 Subject: [PATCH 47/65] Only update uniforms when ODS shader is bound (fixes GL errors) --- settings.gradle | 2 + .../render/capturer/ODSFrameCapturer.java | 67 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/settings.gradle b/settings.gradle index 1fb85e6d..59ab2954 100644 --- a/settings.gradle +++ b/settings.gradle @@ -56,3 +56,5 @@ replayModVersions.each { version -> buildFileName = '../common.gradle' } } + +include ':mixin-hack' \ No newline at end of file diff --git a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java index e3c6d2bf..2acbbe39 100644 --- a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java @@ -1,6 +1,5 @@ package com.replaymod.render.capturer; -import com.replaymod.core.versions.MCVer; import com.replaymod.render.RenderSettings; import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.ODSOpenGlFrame; @@ -8,12 +7,10 @@ import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.rendering.FrameCapturer; import com.replaymod.render.shader.Program; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; -import net.minecraft.client.Minecraft; import net.minecraft.crash.CrashReport; import net.minecraft.util.ResourceLocation; //#if MC>=10800 -import net.minecraft.client.renderer.GlStateManager; import static net.minecraft.client.renderer.GlStateManager.*; //#else //$$ import com.replaymod.render.hooks.GLStateTracker.BooleanState; @@ -36,9 +33,7 @@ public class ODSFrameCapturer implements FrameCapturer { private final Program.Uniform leftEyeVariable; private final BooleanState[] previousStates = new BooleanState[3]; - private final BooleanState previousFogState; - - private final Minecraft mc = MCVer.getMinecraft(); + private BooleanState previousFogState; public ODSFrameCapturer(WorldRenderer worldRenderer, final RenderInfo renderInfo, int frameSize) { RenderInfo fakeInfo = new RenderInfo() { @@ -62,9 +57,9 @@ public class ODSFrameCapturer implements FrameCapturer { @Override public float updateForNextFrame() { if (call++ % 2 == 0) { - shaderProgram.stopUsing(); + unbindProgram(); partialTicks = renderInfo.updateForNextFrame(); - shaderProgram.use(); + bindProgram(); } return partialTicks; } @@ -78,30 +73,43 @@ public class ODSFrameCapturer implements FrameCapturer { right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize); try { shaderProgram = new Program(vertexResource, fragmentResource); - shaderProgram.use(); leftEyeVariable = shaderProgram.getUniformVariable("leftEye"); directionVariable = shaderProgram.getUniformVariable("direction"); - setTexture("texture", 0); - setTexture("lightMap", 1); - linkState(0, "textureEnabled"); - linkState(1, "lightMapEnabled"); - linkState(2, "hurtTextureEnabled"); - final Program.Uniform uniform = shaderProgram.getUniformVariable("fogEnabled"); - previousFogState = fog(); - uniform.set(previousFogState.currentState); - fog(new BooleanState(previousFogState.capability) { - @Override - public void setState(boolean state) { - super.setState(state); - uniform.set(state); - } - }); - shaderProgram.stopUsing(); } catch (Exception e) { throw newReportedException(CrashReport.makeCrashReport(e, "Creating ODS shaders")); } } + private void bindProgram() { + shaderProgram.use(); + setTexture("texture", 0); + setTexture("lightMap", 1); + linkState(0, "textureEnabled"); + linkState(1, "lightMapEnabled"); + linkState(2, "hurtTextureEnabled"); + final Program.Uniform uniform = shaderProgram.getUniformVariable("fogEnabled"); + previousFogState = fog(); + uniform.set(previousFogState.currentState); + fog(new BooleanState(previousFogState.capability) { + { currentState = previousFogState.currentState; } + @Override + public void setState(boolean state) { + super.setState(state); + uniform.set(state); + } + }); + } + + private void unbindProgram() { + for (int i = 0; i < previousStates.length; i++) { + previousStates[i].currentState = texture2DState(i).currentState; + texture2DState(i, previousStates[i]); + } + previousFogState.currentState = fog().currentState; + fog(previousFogState); + shaderProgram.stopUsing(); + } + private void setTexture(String texture, int i) { shaderProgram.getUniformVariable(texture).set(i); } @@ -111,6 +119,7 @@ public class ODSFrameCapturer implements FrameCapturer { previousStates[id] = texture2DState(id); uniform.set(previousStates[id].currentState); texture2DState(id, new BooleanState(previousStates[id].capability) { + { currentState = previousStates[id].currentState; } @Override public void setState(boolean state) { super.setState(state); @@ -126,12 +135,12 @@ public class ODSFrameCapturer implements FrameCapturer { @Override public ODSOpenGlFrame process() { - shaderProgram.use(); + bindProgram(); leftEyeVariable.set(true); CubicOpenGlFrame leftFrame = left.process(); leftEyeVariable.set(false); CubicOpenGlFrame rightFrame = right.process(); - shaderProgram.stopUsing(); + unbindProgram(); if (leftFrame != null && rightFrame != null) { return new ODSOpenGlFrame(leftFrame, rightFrame); @@ -144,10 +153,6 @@ public class ODSFrameCapturer implements FrameCapturer { left.close(); right.close(); shaderProgram.delete(); - for (int i = 0; i < previousStates.length; i++) { - texture2DState(i, previousStates[i]); - } - fog(previousFogState); } private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer { From e9ca1bb47e026aaba71fc179ffa8a94abb7bb5c8 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 10 Mar 2019 16:41:16 +0100 Subject: [PATCH 48/65] Fix FOV during 360 rendering --- .../replaymod/render/mixin/MixinEntityRenderer.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java index aca1cea1..65c04f07 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java +++ b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java @@ -315,6 +315,11 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti return replayModRender_perspective((float) fovY, aspect, zNear, zFar); } + @Redirect(method = "renderCloudsCheck", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Matrix4f;perspective(DFFF)Lnet/minecraft/client/renderer/Matrix4f;")) + private Matrix4f replayModRender_perspective$2(double fovY, float aspect, float zNear, float zFar) { + return replayModRender_perspective((float) fovY, aspect, zNear, zFar); + } + private Matrix4f replayModRender_perspective(float fovY, float aspect, float zNear, float zFar) { if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { fovY = 90; @@ -379,7 +384,11 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti } if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { // Minecraft goes back a little so we have to revert that - GL11.glTranslatef(0.0F, 0.0F, 0.1F); + //#if MC>=11300 + GL11.glTranslatef(0.0F, 0.0F, -0.05F); + //#else + //$$ GL11.glTranslatef(0.0F, 0.0F, 0.1F); + //#endif } } } From 0f3a6253e0df4f83910cdf8ee4a53136ec4ae1e4 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 10 Mar 2019 22:41:14 +0100 Subject: [PATCH 49/65] Complete all currently actionable 1.13.2 FIXMEs --- jGui | 2 +- .../com/replaymod/core/LoadingPlugin.java | 144 ++++++++-------- .../java/com/replaymod/core/ReplayMod.java | 9 - .../core/handler/MainMenuHandler.java | 32 ++-- .../com/replaymod/core/utils/ModCompat.java | 14 +- .../com/replaymod/core/utils/OpenGLUtils.java | 32 ---- .../java/com/replaymod/core/utils/Utils.java | 5 +- .../com/replaymod/core/versions/MCVer.java | 11 +- .../com/replaymod/extras/FullBrightness.java | 2 +- .../com/replaymod/extras/OpenEyeExtra.java | 6 +- .../java/com/replaymod/extras/Setting.java | 2 + .../ScreenshotRenderer.java | 7 +- .../recording/ReplayModRecording.java | 2 +- .../mixin/MixinDownloadingPackFinder.java | 36 ++++ .../mixin/MixinPlayerControllerMP.java | 60 ++++--- .../recording/packet/PacketListener.java | 20 ++- .../packet/ResourcePackRecorder.java | 157 ++++++++++-------- .../replaymod/replay/FullReplaySender.java | 2 +- .../com/replaymod/replay/ReplayHandler.java | 7 +- .../replaymod/replay/camera/CameraEntity.java | 33 ++-- src/main/resources/META-INF/replaymod_at.cfg | 12 ++ .../resources/mixins.recording.replaymod.json | 5 +- versions/common.gradle | 2 +- 23 files changed, 313 insertions(+), 289 deletions(-) delete mode 100644 src/main/java/com/replaymod/core/utils/OpenGLUtils.java create mode 100644 src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java diff --git a/jGui b/jGui index fe5d344b..31fecfa0 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit fe5d344be41f1c084a301a954e9fc278110c402f +Subproject commit 31fecfa0d62ab5c689d301a24a5584c3537f8f16 diff --git a/src/main/java/com/replaymod/core/LoadingPlugin.java b/src/main/java/com/replaymod/core/LoadingPlugin.java index d4709e37..a1c78347 100755 --- a/src/main/java/com/replaymod/core/LoadingPlugin.java +++ b/src/main/java/com/replaymod/core/LoadingPlugin.java @@ -1,13 +1,13 @@ -/* FIXME package com.replaymod.core; -import org.apache.logging.log4j.LogManager; -import org.spongepowered.asm.launch.MixinBootstrap; -import org.spongepowered.asm.mixin.Mixins; - +//#if MC<11300 +//$$ import org.apache.logging.log4j.LogManager; +//$$ import org.spongepowered.asm.launch.MixinBootstrap; +//$$ import org.spongepowered.asm.mixin.Mixins; +//$$ //#if MC>=10800 -import net.minecraftforge.fml.relauncher.CoreModManager; -import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +//$$ import net.minecraftforge.fml.relauncher.CoreModManager; +//$$ import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; //#else //$$ import com.replaymod.core.asm.GLErrorTransformer; //$$ import com.replaymod.core.asm.GLStateTrackerTransformer; @@ -17,56 +17,56 @@ import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; //$$ import java.util.ArrayList; //$$ import java.util.List; //#endif - -import java.io.File; -import java.net.URISyntaxException; -import java.net.URL; -import java.security.CodeSource; -import java.util.Map; - -@IFMLLoadingPlugin.TransformerExclusions("com.replaymod.core.asm.") -public class LoadingPlugin implements IFMLLoadingPlugin { - - public LoadingPlugin() { - MixinBootstrap.init(); - Mixins.addConfiguration("mixins.recording.replaymod.json"); - Mixins.addConfiguration("mixins.render.replaymod.json"); - Mixins.addConfiguration("mixins.replay.replaymod.json"); - Mixins.addConfiguration("mixins.compat.mapwriter.replaymod.json"); - Mixins.addConfiguration("mixins.compat.shaders.replaymod.json"); - Mixins.addConfiguration("mixins.extras.playeroverview.replaymod.json"); - - CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); - if (codeSource != null) { - URL location = codeSource.getLocation(); - try { - File file = new File(location.toURI()); - if (file.isFile()) { - // This forces forge to reexamine the jar file for FML mods - // Should eventually be handled by Mixin itself, maybe? +//$$ +//$$ import java.io.File; +//$$ import java.net.URISyntaxException; +//$$ import java.net.URL; +//$$ import java.security.CodeSource; +//$$ import java.util.Map; +//$$ +//$$ @IFMLLoadingPlugin.TransformerExclusions("com.replaymod.core.asm.") +//$$ public class LoadingPlugin implements IFMLLoadingPlugin { +//$$ +//$$ public LoadingPlugin() { +//$$ MixinBootstrap.init(); +//$$ Mixins.addConfiguration("mixins.recording.replaymod.json"); +//$$ Mixins.addConfiguration("mixins.render.replaymod.json"); +//$$ Mixins.addConfiguration("mixins.replay.replaymod.json"); +//$$ Mixins.addConfiguration("mixins.compat.mapwriter.replaymod.json"); +//$$ Mixins.addConfiguration("mixins.compat.shaders.replaymod.json"); +//$$ Mixins.addConfiguration("mixins.extras.playeroverview.replaymod.json"); +//$$ +//$$ CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); +//$$ if (codeSource != null) { +//$$ URL location = codeSource.getLocation(); +//$$ try { +//$$ File file = new File(location.toURI()); +//$$ if (file.isFile()) { +//$$ // This forces forge to reexamine the jar file for FML mods +//$$ // Should eventually be handled by Mixin itself, maybe? //#if MC>=10809 - CoreModManager.getIgnoredMods().remove(file.getName()); + //$$ CoreModManager.getIgnoredMods().remove(file.getName()); //#else //$$ CoreModManager.getLoadedCoremods().remove(file.getName()); //#if MC<=10710 //$$ CoreModManager.getReparseableCoremods().add(file.getName()); //#endif //#endif - } - } catch (URISyntaxException e) { - e.printStackTrace(); - } - } else { - LogManager.getLogger().warn("No CodeSource, if this is not a development environment we might run into problems!"); - LogManager.getLogger().warn(getClass().getProtectionDomain()); - } - } - - @Override - public String[] getASMTransformerClass() { +//$$ } +//$$ } catch (URISyntaxException e) { +//$$ e.printStackTrace(); +//$$ } +//$$ } else { +//$$ LogManager.getLogger().warn("No CodeSource, if this is not a development environment we might run into problems!"); +//$$ LogManager.getLogger().warn(getClass().getProtectionDomain()); +//$$ } +//$$ } +//$$ +//$$ @Override +//$$ public String[] getASMTransformerClass() { //#if MC>=10800 - return new String[]{ - }; + //$$ return new String[]{ + //$$ }; //#else //$$ List transformers = new ArrayList<>(); //$$ if ("true".equals(System.getProperty("replaymod.glerrors", "false"))) { @@ -75,26 +75,26 @@ public class LoadingPlugin implements IFMLLoadingPlugin { //$$ transformers.add(GLStateTrackerTransformer.class.getName()); //$$ return transformers.stream().toArray(String[]::new); //#endif - } - - @Override - public String getModContainerClass() { - return null; - } - - @Override - public String getSetupClass() { - return null; - } - - @Override - public void injectData(Map data) { - } - - @Override - public String getAccessTransformerClass() { - return null; - } - -} -*/ +//$$ } +//$$ +//$$ @Override +//$$ public String getModContainerClass() { +//$$ return null; +//$$ } +//$$ +//$$ @Override +//$$ public String getSetupClass() { +//$$ return null; +//$$ } +//$$ +//$$ @Override +//$$ public void injectData(Map data) { +//$$ } +//$$ +//$$ @Override +//$$ public String getAccessTransformerClass() { +//$$ return null; +//$$ } +//$$ +//$$ } +//#endif diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index b57c5260..ed41c6aa 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -5,7 +5,6 @@ import com.google.common.util.concurrent.ListenableFutureTask; import com.replaymod.core.gui.GuiReplaySettings; import com.replaymod.core.gui.RestoreReplayGui; import com.replaymod.core.handler.MainMenuHandler; -import com.replaymod.core.utils.OpenGLUtils; import com.replaymod.core.versions.MCVer; import com.replaymod.editor.ReplayModEditor; import com.replaymod.extras.ReplayModExtras; @@ -156,14 +155,6 @@ public class ReplayMod implements Module { public ReplayMod() { I18n.setI18n(net.minecraft.client.resources.I18n::format); - // Initialize the static OpenGL info field from the minecraft main thread - // Unfortunately lwjgl uses static methods so we have to make use of magic init calls as well - //#if MC>=11300 - DeferredWorkQueue.runLater(OpenGLUtils::init); - //#else - //$$ OpenGLUtils.init(); - //#endif - //#if MC>=11300 DeferredWorkQueue.runLater(() -> MCVer.getMinecraft().resourcePackRepository.addPackFinder(new LangResourcePack.Finder())); //#endif diff --git a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java index 07ace9cb..20b66dbd 100644 --- a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java +++ b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java @@ -1,5 +1,6 @@ package com.replaymod.core.handler; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiMainMenu; import net.minecraft.client.gui.GuiScreen; @@ -18,8 +19,6 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif -import java.io.IOException; - import static com.replaymod.core.versions.MCVer.*; /** @@ -45,10 +44,9 @@ public class MainMenuHandler { // and then move back down by 10 to compensate for the space to the exit button that was already there int offset = -2 * 24 + 10; y(button, y(button) + offset); - } - /* FIXME + //#if MC>=11300 - if (button == gui.realmsButton) { + if (button.id == 14) { realmsOffset = offset; } //#endif @@ -58,11 +56,9 @@ public class MainMenuHandler { gui.realmsNotification = new RealmsNotificationProxy((GuiScreenRealmsProxy) gui.realmsNotification, realmsOffset); } //#endif - */ } } - /* FIXME //#if MC>=11300 private static class RealmsNotificationProxy extends GuiScreen { private final GuiScreenRealmsProxy proxy; @@ -74,30 +70,25 @@ public class MainMenuHandler { } @Override - public void setGuiSize(int w, int h) { - proxy.setGuiSize(w, h); + public void setWorldAndResolution(Minecraft mc, int width, int height) { + proxy.setWorldAndResolution(mc, width, height); } @Override - public void initGui() { - proxy.initGui(); + public void tick() { + proxy.tick(); } @Override - public void updateScreen() { - proxy.updateScreen(); - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { + public void render(int mouseX, int mouseY, float partialTicks) { GL11.glTranslated(0, offset, 0); - proxy.drawScreen(mouseX, mouseY - offset, partialTicks); + proxy.render(mouseX, mouseY - offset, partialTicks); GL11.glTranslated(0, -offset, 0); } @Override - public void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { - proxy.mouseClicked(mouseX, mouseY - offset, mouseButton); + public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) { + return proxy.mouseClicked(mouseX, mouseY - offset, mouseButton); } @Override @@ -106,5 +97,4 @@ public class MainMenuHandler { } } //#endif - */ } diff --git a/src/main/java/com/replaymod/core/utils/ModCompat.java b/src/main/java/com/replaymod/core/utils/ModCompat.java index 8b867fdb..41813c1c 100644 --- a/src/main/java/com/replaymod/core/utils/ModCompat.java +++ b/src/main/java/com/replaymod/core/utils/ModCompat.java @@ -11,11 +11,12 @@ import net.minecraftforge.registries.RegistryManager; //#else //$$ import cpw.mods.fml.common.registry.GameData; //#endif +//$$ import java.util.stream.Stream; //#endif //#if MC>=10800 //#if MC>=11300 -// FIXME +import net.minecraftforge.fml.ModList; //#else //$$ import net.minecraftforge.fml.common.Loader; //$$ import net.minecraftforge.fml.common.ModContainer; @@ -28,13 +29,20 @@ import net.minecraftforge.registries.RegistryManager; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; public class ModCompat { @SuppressWarnings("unchecked") public static Collection getInstalledNetworkMods() { //#if MC>=11300 - return Stream.empty().collect(Collectors.toList()); + Map modInfoMap = ModList.get().getMods().stream() + .map(m -> new ModInfo(m.getModId(), m.getDisplayName(), m.getVersion().toString())) + .collect(Collectors.toMap(ModInfo::getId, Function.identity())); + return RegistryManager.ACTIVE.takeSnapshot(false).keySet().stream() + .map(RegistryManager.ACTIVE::getRegistry) + .map(ForgeRegistry::getKeys).flatMap(Set::stream) + .map(ResourceLocation::getNamespace).filter(s -> !s.equals("minecraft")).distinct() + .map(modInfoMap::get).filter(Objects::nonNull) + .collect(Collectors.toList()); //#else //$$ Map ignoreCaseMap = Loader.instance().getModList().stream() //$$ .collect(Collectors.toMap(m -> m.getModId().toLowerCase(), Function.identity())); diff --git a/src/main/java/com/replaymod/core/utils/OpenGLUtils.java b/src/main/java/com/replaymod/core/utils/OpenGLUtils.java deleted file mode 100644 index 2d790fa5..00000000 --- a/src/main/java/com/replaymod/core/utils/OpenGLUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.replaymod.core.utils; - -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; - -import java.nio.IntBuffer; - -public class OpenGLUtils { - public static final int VIEWPORT_MAX_WIDTH; - public static final int VIEWPORT_MAX_HEIGHT; - - static { - IntBuffer buffer = BufferUtils.createIntBuffer(16); - //#if MC>=11300 - // FIXME GL11.glGetIntegerv(GL11.GL_MAX_VIEWPORT_DIMS, buffer); - buffer.put(0xffff); - buffer.put(0xffff); - //#else - //$$ GL11.glGetInteger(GL11.GL_MAX_VIEWPORT_DIMS, buffer); - //#endif - VIEWPORT_MAX_WIDTH = buffer.get(); - VIEWPORT_MAX_HEIGHT = buffer.get(); - } - - /** - * Magic init method which has to be called from the OpenGL thread so the variables in this class - * can be initialized successfully. - * Does not perform any work on its own. - */ - public static void init() { - } -} diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 81592bc0..94a22dd7 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -20,6 +20,7 @@ import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import de.johni0702.minecraft.gui.versions.MCVer; import net.minecraft.client.gui.GuiScreen; import net.minecraft.crash.CrashReport; import net.minecraft.util.ResourceLocation; @@ -284,8 +285,8 @@ public class Utils { l -> new GuiLabel().setText(l).setColor(Colors.BLACK)).toArray(GuiElement[]::new))); // Replace close button with panel containing close and copy buttons - GuiButton copyToClipboardButton = new GuiButton().setI18nLabel("chat.copy")/* FIXME .onClick(() -> - //GuiScreen.setClipboardString(crashReport))*/.setSize(150, 20); + GuiButton copyToClipboardButton = new GuiButton().setI18nLabel("chat.copy").onClick(() -> + MCVer.setClipboardString(crashReport)).setSize(150, 20); GuiButton closeButton = getCloseButton(); popup.removeElement(closeButton); popup.addElements(new VerticalLayout.Data(1), diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index 43352ae4..0a199fa7 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -433,11 +433,14 @@ public class MCVer { //$$ } //#endif - /* FIXME - public static ListenableFuture setServerResourcePack(ResourcePackRepository repo, File file) { + public static ListenableFuture setServerResourcePack(File file) { + //#if MC>=11300 + return getMinecraft().getPackFinder().func_195741_a(file); + //#else + //$$ ResourcePackRepository repo = getMinecraft().getResourcePackRepository(); //#if MC>=10809 //#if MC>=11200 - return repo.setServerResourcePack(file); + //$$ return repo.setServerResourcePack(file); //#else //$$ return repo.setResourcePackInstance(file); //#endif @@ -451,8 +454,8 @@ public class MCVer { //$$ return Futures.immediateFuture(null); //#endif //#endif + //#endif } - */ public static boolean isKeyDown(KeyBinding keyBinding) { //#if MC>=10800 diff --git a/src/main/java/com/replaymod/extras/FullBrightness.java b/src/main/java/com/replaymod/extras/FullBrightness.java index d129e492..b06a8aa7 100644 --- a/src/main/java/com/replaymod/extras/FullBrightness.java +++ b/src/main/java/com/replaymod/extras/FullBrightness.java @@ -54,7 +54,7 @@ public class FullBrightness implements Extra { public void run() { active = !active; //#if MC>=11300 - // FIXME check if this is no longer required (especially when replay is paused) + mod.getMinecraft().entityRenderer.tick(); // need to tick once to update lightmap when replay is paused //#else //$$ mod.getMinecraft().entityRenderer.lightmapUpdateNeeded = true; //#endif diff --git a/src/main/java/com/replaymod/extras/OpenEyeExtra.java b/src/main/java/com/replaymod/extras/OpenEyeExtra.java index 99580a16..4d88d726 100644 --- a/src/main/java/com/replaymod/extras/OpenEyeExtra.java +++ b/src/main/java/com/replaymod/extras/OpenEyeExtra.java @@ -1,7 +1,6 @@ package com.replaymod.extras; import com.replaymod.core.ReplayMod; -import com.replaymod.core.Setting; import com.replaymod.core.versions.MCVer; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; import de.johni0702.minecraft.gui.container.GuiContainer; @@ -40,7 +39,6 @@ import static com.replaymod.extras.ReplayModExtras.LOGGER; public class OpenEyeExtra implements Extra { private static final String DOWNLOAD_URL = "https://www.replaymod.com/dl/openeye/" + ReplayMod.getMinecraftVersion(); - private static final Setting ASK_FOR_OPEN_EYE = new Setting<>("advanced", "askForOpenEye", null, true); private ReplayMod mod; @@ -53,7 +51,7 @@ public class OpenEyeExtra implements Extra { //#else //$$ boolean isOpenEyeLoaded = Loader.isModLoaded("OpenEye"); //#endif - if (!isOpenEyeLoaded && mod.getSettingsRegistry().get(ASK_FOR_OPEN_EYE)) { + if (!isOpenEyeLoaded && mod.getSettingsRegistry().get(Setting.ASK_FOR_OPEN_EYE)) { new Thread(() -> { try { LOGGER.trace("Checking for OpenEye availability"); @@ -121,7 +119,7 @@ public class OpenEyeExtra implements Extra { }).start(); }); noButton.onClick(() -> { - mod.getSettingsRegistry().set(ASK_FOR_OPEN_EYE, false); + mod.getSettingsRegistry().set(Setting.ASK_FOR_OPEN_EYE, false); mod.getSettingsRegistry().save(); parent.display(); }); diff --git a/src/main/java/com/replaymod/extras/Setting.java b/src/main/java/com/replaymod/extras/Setting.java index 2f69e4c1..ae6a359c 100644 --- a/src/main/java/com/replaymod/extras/Setting.java +++ b/src/main/java/com/replaymod/extras/Setting.java @@ -3,6 +3,8 @@ package com.replaymod.extras; import com.replaymod.core.SettingsRegistry; public final class Setting { + public static final SettingsRegistry.SettingKey ASK_FOR_OPEN_EYE = + new SettingsRegistry.SettingKeys<>("advanced", "askForOpenEye", null, true); public static final SettingsRegistry.SettingKey SKIP_POST_SCREENSHOT_GUI = new SettingsRegistry.SettingKeys<>("advanced", "skipPostScreenshotGui", null, false); } diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java index 59c959de..54f1dada 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/ScreenshotRenderer.java @@ -26,7 +26,8 @@ public class ScreenshotRenderer implements RenderInfo { public boolean renderScreenshot() throws Throwable { try { //#if MC>=11300 - // FIXME + int displayWidthBefore = mc.mainWindow.framebufferWidth; + int displayHeightBefore = mc.mainWindow.framebufferHeight; //#else //$$ int displayWidthBefore = mc.displayWidth; //$$ int displayHeightBefore = mc.displayHeight; @@ -48,7 +49,9 @@ public class ScreenshotRenderer implements RenderInfo { mc.gameSettings.hideGUI = hideGUIBefore; //#if MC>=11300 - // FIXME + mc.mainWindow.framebufferWidth = displayWidthBefore; + mc.mainWindow.framebufferHeight = displayHeightBefore; + mc.getFramebuffer().createBindFramebuffer(displayWidthBefore, displayHeightBefore); //#else //$$ mc.resize(displayWidthBefore, displayHeightBefore); //#endif diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index 19937f80..d949a66c 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -74,7 +74,7 @@ public class ReplayModRecording implements Module { new GuiHandler(core).register(); //#if MC>=11300 - // FIXME + NetworkRegistry.newEventChannel(Restrictions.PLUGIN_CHANNEL, () -> "0", any -> true, any -> true); //#else //$$ NetworkRegistry.INSTANCE.newChannel(Restrictions.PLUGIN_CHANNEL, new RestrictionsChannelHandler()); //#endif diff --git a/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java b/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java new file mode 100644 index 00000000..226f21ce --- /dev/null +++ b/src/main/java/com/replaymod/recording/mixin/MixinDownloadingPackFinder.java @@ -0,0 +1,36 @@ +//#if MC>=11300 +package com.replaymod.recording.mixin; + +import com.google.common.util.concurrent.ListenableFuture; +import com.replaymod.recording.packet.ResourcePackRecorder; +import de.johni0702.minecraft.gui.utils.Consumer; +import net.minecraft.client.resources.DownloadingPackFinder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.io.File; + +@Mixin(DownloadingPackFinder.class) +public abstract class MixinDownloadingPackFinder implements ResourcePackRecorder.IDownloadingPackFinder { + private Consumer requestCallback; + + @Override + public void setRequestCallback(Consumer callback) { + requestCallback = callback; + } + + @Shadow + public abstract ListenableFuture func_195741_a(File file); + + @Redirect(method = "downloadResourcePack", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/DownloadingPackFinder;func_195741_a(Ljava/io/File;)Lcom/google/common/util/concurrent/ListenableFuture;")) + private ListenableFuture recordDownloadedPack(DownloadingPackFinder downloadingPackFinder, File file) { + if (requestCallback != null) { + requestCallback.consume(file); + requestCallback = null; + } + return func_195741_a(file); + } +} +//#endif diff --git a/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java b/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java index 1b20b9fe..37bf6451 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinPlayerControllerMP.java @@ -1,34 +1,32 @@ package com.replaymod.recording.mixin; -//#if MC>=10904 -import com.replaymod.recording.handler.RecordingEventHandler; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.PlayerControllerMP; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -import static com.replaymod.core.versions.MCVer.*; - -@Mixin(PlayerControllerMP.class) -public abstract class MixinPlayerControllerMP implements RecordingEventHandler.RecordingEventSender { - - @Shadow - private Minecraft mc; - - // Redirects the call to playEvent without the initial player argument to the method with that argument - // The new method will then play it and (if applicable) record it. (See MixinWorldClient) - // This is necessary for the block break event (particles and sound) to be recorded. Otherwise it looks like the - // event was emitted because of a packet (player will be null) and not as it actually was (by the player). - /* FIXME test if this is still an issue (the call seems to be gone) - @Redirect(method = "onPlayerDestroyBlock", at = @At(value = "INVOKE", - target = "Lnet/minecraft/world/World;playEvent(ILnet/minecraft/util/math/BlockPos;I)V")) - public void replayModRecording_playEvent_fixed(World world, int type, BlockPos pos, int data) { - world.playEvent(player(mc), type, pos, data); - } - */ -} +//#if MC>=10904 && MC<11300 +//$$ import com.replaymod.recording.handler.RecordingEventHandler; +//$$ import net.minecraft.client.Minecraft; +//$$ import net.minecraft.client.multiplayer.PlayerControllerMP; +//$$ import net.minecraft.util.math.BlockPos; +//$$ import net.minecraft.world.World; +//$$ import org.spongepowered.asm.mixin.Mixin; +//$$ import org.spongepowered.asm.mixin.Shadow; +//$$ import org.spongepowered.asm.mixin.injection.At; +//$$ import org.spongepowered.asm.mixin.injection.Redirect; +//$$ +//$$ import static com.replaymod.core.versions.MCVer.*; +//$$ +//$$ @Mixin(PlayerControllerMP.class) +//$$ public abstract class MixinPlayerControllerMP implements RecordingEventHandler.RecordingEventSender { +//$$ +//$$ @Shadow +//$$ private Minecraft mc; +//$$ +//$$ // Redirects the call to playEvent without the initial player argument to the method with that argument +//$$ // The new method will then play it and (if applicable) record it. (See MixinWorldClient) +//$$ // This is necessary for the block break event (particles and sound) to be recorded. Otherwise it looks like the +//$$ // event was emitted because of a packet (player will be null) and not as it actually was (by the player). +//$$ @Redirect(method = "onPlayerDestroyBlock", at = @At(value = "INVOKE", +//$$ target = "Lnet/minecraft/world/World;playEvent(ILnet/minecraft/util/math/BlockPos;I)V")) +//$$ public void replayModRecording_playEvent_fixed(World world, int type, BlockPos pos, int data) { +//$$ world.playEvent(player(mc), type, pos, data); +//$$ } +//$$ } //#endif diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index c33e5ff3..2ead09fb 100755 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -33,7 +33,9 @@ import net.minecraft.util.text.TextComponentString; //#if MC>=10800 import net.minecraft.network.EnumPacketDirection; -// FIXME import net.minecraftforge.fml.common.network.internal.FMLProxyPacket; +//#if MC<11300 +//$$ import net.minecraftforge.fml.common.network.internal.FMLProxyPacket; +//#endif //#else //$$ import cpw.mods.fml.common.network.internal.FMLProxyPacket; //#endif @@ -244,18 +246,18 @@ public class PacketListener extends ChannelInboundHandlerAdapter { //#endif //#endif - /* FIXME - if (packet instanceof FMLProxyPacket) { - // This packet requires special handling + //#if MC<11300 + //$$ if (packet instanceof FMLProxyPacket) { + //$$ // This packet requires special handling //#if MC>=10800 - ((FMLProxyPacket) packet).toS3FPackets().forEach(this::save); + //$$ ((FMLProxyPacket) packet).toS3FPackets().forEach(this::save); //#else //$$ save(((FMLProxyPacket) packet).toS3FPacket()); //#endif - super.channelRead(ctx, msg); - return; - } - */ + //$$ super.channelRead(ctx, msg); + //$$ return; + //$$ } + //#endif save(packet); diff --git a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java index 61292713..82de6586 100644 --- a/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java +++ b/src/main/java/com/replaymod/recording/packet/ResourcePackRecorder.java @@ -4,19 +4,20 @@ import com.google.common.hash.Hashing; import com.google.common.io.Files; import com.replaymod.replaystudio.replay.ReplayFile; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreenWorking; import net.minecraft.client.gui.GuiYesNo; import net.minecraft.client.gui.GuiYesNoCallback; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.resources.I18n; -import net.minecraft.util.HttpUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; //#if MC>=11300 -// FIXME +import de.johni0702.minecraft.gui.utils.Consumer; +import net.minecraft.client.resources.DownloadingPackFinder; //#else +//$$ import net.minecraft.client.gui.GuiScreenWorking; //$$ import net.minecraft.client.resources.ResourcePackRepository; +//$$ import net.minecraft.util.HttpUtil; //#endif //#if MC>=10904 @@ -38,7 +39,9 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.network.NetworkManager; -import org.apache.commons.io.FileUtils; +//#if MC<11300 +//$$ import org.apache.commons.io.FileUtils; +//#endif import javax.annotation.Nonnull; //#else @@ -141,8 +144,7 @@ public class ResourcePackRecorder { if (levelDir.isFile()) { netManager.sendPacket(makeStatusPacket(hash, Action.ACCEPTED)); - /* FIXME - Futures.addCallback(setServerResourcePack(mc.getResourcePackRepository(), levelDir), new FutureCallback() { + Futures.addCallback(setServerResourcePack(levelDir), new FutureCallback() { @Override public void onSuccess(Object result) { recordResourcePack(levelDir, requestId); @@ -154,7 +156,6 @@ public class ResourcePackRecorder { netManager.sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); } }); - */ } else { netManager.sendPacket(makeStatusPacket(hash, Action.FAILED_DOWNLOAD)); } @@ -162,7 +163,7 @@ public class ResourcePackRecorder { final ServerData serverData = mc.getCurrentServerData(); if (serverData != null && serverData.getResourceMode() == ServerData.ServerResourceMode.ENABLED) { netManager.sendPacket(makeStatusPacket(hash, Action.ACCEPTED)); - // FIXME downloadResourcePackFuture(requestId, url, hash); + downloadResourcePackFuture(requestId, url, hash); } else if (serverData != null && serverData.getResourceMode() != ServerData.ServerResourceMode.PROMPT) { netManager.sendPacket(makeStatusPacket(hash, Action.DECLINED)); } else { @@ -180,7 +181,7 @@ public class ResourcePackRecorder { } if (result) { netManager.sendPacket(makeStatusPacket(hash, Action.ACCEPTED)); - // FIXME downloadResourcePackFuture(requestId, url, hash); + downloadResourcePackFuture(requestId, url, hash); } else { netManager.sendPacket(makeStatusPacket(hash, Action.DECLINED)); } @@ -199,7 +200,6 @@ public class ResourcePackRecorder { //#endif } - /* FIXME private void downloadResourcePackFuture(int requestId, String url, final String hash) { Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() { @Override @@ -214,101 +214,112 @@ public class ResourcePackRecorder { }); } + //#if MC>=11300 private ListenableFuture downloadResourcePack(final int requestId, String url, String hash) { - final ResourcePackRepository repo = mc.mcResourcePackRepository; - String fileName; - if (hash.matches("^[a-f0-9]{40}$")) { - fileName = hash; - } else { - fileName = url.substring(url.lastIndexOf("/") + 1); + DownloadingPackFinder packFinder = mc.getPackFinder(); + ((IDownloadingPackFinder) packFinder).setRequestCallback(file -> recordResourcePack(file, requestId)); + return packFinder.downloadResourcePack(url, hash); + } - if (fileName.contains("?")) { - fileName = fileName.substring(0, fileName.indexOf("?")); - } - - if (!fileName.endsWith(".zip")) { - return Futures.immediateFailedFuture(new IllegalArgumentException("Invalid filename; must end in .zip")); - } - - fileName = "legacy_" + fileName.replaceAll("\\W", ""); - } - - final File file = new File(repo.dirServerResourcepacks, fileName); + public interface IDownloadingPackFinder { + void setRequestCallback(Consumer callback); + } + //#else + //$$ private ListenableFuture downloadResourcePack(final int requestId, String url, String hash) { + //$$ final ResourcePackRepository repo = mc.mcResourcePackRepository; + //$$ String fileName; + //$$ if (hash.matches("^[a-f0-9]{40}$")) { + //$$ fileName = hash; + //$$ } else { + //$$ fileName = url.substring(url.lastIndexOf("/") + 1); + //$$ + //$$ if (fileName.contains("?")) { + //$$ fileName = fileName.substring(0, fileName.indexOf("?")); + //$$ } + //$$ + //$$ if (!fileName.endsWith(".zip")) { + //$$ return Futures.immediateFailedFuture(new IllegalArgumentException("Invalid filename; must end in .zip")); + //$$ } + //$$ + //$$ fileName = "legacy_" + fileName.replaceAll("\\W", ""); + //$$ } + //$$ + //$$ final File file = new File(repo.dirServerResourcepacks, fileName); //#if MC>=10809 - repo.lock.lock(); + //$$ repo.lock.lock(); //#else //$$ repo.field_177321_h.lock(); //#endif - try { + //$$ try { //#if MC>=10809 - repo.clearResourcePack(); + //$$ repo.clearResourcePack(); //#else //$$ repo.func_148529_f(); //#endif - - if (file.exists() && hash.length() == 40) { - try { - String fileHash = Hashing.sha1().hashBytes(Files.toByteArray(file)).toString(); - if (fileHash.equals(hash)) { - recordResourcePack(file, requestId); - return setServerResourcePack(repo, file); - } - - logger.warn("File " + file + " had wrong hash (expected " + hash + ", found " + fileHash + "). Deleting it."); - FileUtils.deleteQuietly(file); - } catch (IOException ioexception) { - logger.warn("File " + file + " couldn\'t be hashed. Deleting it.", ioexception); - FileUtils.deleteQuietly(file); - } - } - - final GuiScreenWorking guiScreen = new GuiScreenWorking(); - final Minecraft mc = Minecraft.getMinecraft(); - - Futures.getUnchecked(mc.addScheduledTask(() -> mc.displayGuiScreen(guiScreen))); - + //$$ + //$$ if (file.exists() && hash.length() == 40) { + //$$ try { + //$$ String fileHash = Hashing.sha1().hashBytes(Files.toByteArray(file)).toString(); + //$$ if (fileHash.equals(hash)) { + //$$ recordResourcePack(file, requestId); + //$$ return setServerResourcePack(file); + //$$ } + //$$ + //$$ logger.warn("File " + file + " had wrong hash (expected " + hash + ", found " + fileHash + "). Deleting it."); + //$$ FileUtils.deleteQuietly(file); + //$$ } catch (IOException ioexception) { + //$$ logger.warn("File " + file + " couldn\'t be hashed. Deleting it.", ioexception); + //$$ FileUtils.deleteQuietly(file); + //$$ } + //$$ } + //$$ + //$$ final GuiScreenWorking guiScreen = new GuiScreenWorking(); + //$$ final Minecraft mc = Minecraft.getMinecraft(); + //$$ + //$$ Futures.getUnchecked(mc.addScheduledTask(() -> mc.displayGuiScreen(guiScreen))); + //$$ //#if MC>=10809 //#if MC>=11002 //#if MC>=11100 - Map sessionInfo = ResourcePackRepository.getDownloadHeaders(); + //$$ Map sessionInfo = ResourcePackRepository.getDownloadHeaders(); //#else //$$ Map sessionInfo = ResourcePackRepository.func_190115_a(); //#endif //#else //$$ Map sessionInfo = Minecraft.getSessionInfo(); //#endif - repo.downloadingPacks = HttpUtil.downloadResourcePack(file, url, sessionInfo, 50 * 1024 * 1024, guiScreen, mc.getProxy()); - Futures.addCallback(repo.downloadingPacks, new FutureCallback() { + //$$ repo.downloadingPacks = HttpUtil.downloadResourcePack(file, url, sessionInfo, 50 * 1024 * 1024, guiScreen, mc.getProxy()); + //$$ Futures.addCallback(repo.downloadingPacks, new FutureCallback() { //#else //$$ Map sessionInfo = Minecraft.getSessionInfo(); //$$ repo.field_177322_i = HttpUtil.func_180192_a(file, url, sessionInfo, 50 * 1024 * 1024, guiScreen, mc.getProxy()); //$$ Futures.addCallback(repo.field_177322_i, new FutureCallback() { //#endif - @Override - public void onSuccess(Object value) { - recordResourcePack(file, requestId); - setServerResourcePack(repo, file); - } - - @Override - public void onFailure(@Nonnull Throwable throwable) { - throwable.printStackTrace(); - } - }); + //$$ @Override + //$$ public void onSuccess(Object value) { + //$$ recordResourcePack(file, requestId); + //$$ setServerResourcePack(file); + //$$ } + //$$ + //$$ @Override + //$$ public void onFailure(@Nonnull Throwable throwable) { + //$$ throwable.printStackTrace(); + //$$ } + //$$ }); //#if MC>=10809 - return repo.downloadingPacks; + //$$ return repo.downloadingPacks; //#else //$$ return repo.field_177322_i; //#endif - } finally { + //$$ } finally { //#if MC>=10809 - repo.lock.unlock(); + //$$ repo.lock.unlock(); //#else //$$ repo.field_177321_h.unlock(); //#endif - } - } - */ + //$$ } + //$$ } + //#endif //#else //$$ public synchronized S3FPacketCustomPayload handleResourcePack(S3FPacketCustomPayload packet) { //$$ final int requestId = nextRequestId++; diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index b56d2f5a..033bff73 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -582,7 +582,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if (!file.exists()) { IOUtils.copy(replayFile.getResourcePack(hash).get(), new FileOutputStream(file)); } - // FIXME setServerResourcePack(mc.getResourcePackRepository(), file); + setServerResourcePack(file); } } return null; diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 5abef2d5..e240b52d 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -690,10 +690,9 @@ public class ReplayHandler { //#if MC>=11300 if (entity instanceof EntityLivingBase) { EntityLivingBase e = (EntityLivingBase) entity; - // FIXME - // e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ); - // e.rotationYaw = (float) e.otherPlayerMPYaw; - // e.rotationPitch = (float) e.otherPlayerMPPitch; + e.setPosition(e.interpTargetX, e.interpTargetY, e.interpTargetZ); + e.rotationYaw = (float) e.interpTargetYaw; + e.rotationPitch = (float) e.interpTargetPitch; } //#else //$$ if (entity instanceof EntityOtherPlayerMP) { diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index cf769179..9fd861e4 100755 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -10,14 +10,12 @@ import com.replaymod.replay.events.ReplayChatMessageEvent; import com.replaymod.replaystudio.util.Location; import lombok.Getter; import lombok.Setter; -import net.minecraft.block.material.Material; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.AbstractClientPlayer; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.client.settings.KeyBinding; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItemFrame; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ResourceLocation; @@ -29,6 +27,9 @@ import net.minecraftforge.common.MinecraftForge; //#if MC>=11300 import net.minecraft.util.math.RayTraceFluidMode; +//#else +//$$ import net.minecraft.block.material.Material; +//$$ import net.minecraft.entity.EntityLivingBase; //#endif //#if MC>=10904 @@ -289,12 +290,12 @@ public class CameraEntity return falseUnlessSpectating(Entity::isEntityInsideOpaqueBlock); // Make sure no suffocation overlay is rendered } - /* FIXME - @Override - public boolean isInsideOfMaterial(Material materialIn) { - return falseUnlessSpectating(e -> e.isInsideOfMaterial(materialIn)); // Make sure no overlays are rendered - } - */ + //#if MC<11300 + //$$ @Override + //$$ public boolean isInsideOfMaterial(Material materialIn) { + //$$ return falseUnlessSpectating(e -> e.isInsideOfMaterial(materialIn)); // Make sure no overlays are rendered + //$$ } + //#endif //#if MC>=10800 @Override @@ -473,13 +474,13 @@ public class CameraEntity //$$ } //#endif - /* FIXME - @Override - public void openGui(Object mod, int modGuiId, World world, int x, int y, int z) { - // Do not open any block GUIs for the camera entities - // Note: Vanilla GUIs are filtered out on a packet level, this only applies to mod GUIs - } - */ + //#if MC<11300 + //$$ @Override + //$$ public void openGui(Object mod, int modGuiId, World world, int x, int y, int z) { + //$$ // Do not open any block GUIs for the camera entities + //$$ // Note: Vanilla GUIs are filtered out on a packet level, this only applies to mod GUIs + //$$ } + //#endif @Override //#if MC>=11300 @@ -618,14 +619,12 @@ public class CameraEntity lastHandRendered = player; //#if MC>=10904 - /* FIXME needs AT mc.entityRenderer.itemRenderer.prevEquippedProgressMainHand = 1; mc.entityRenderer.itemRenderer.prevEquippedProgressOffHand = 1; mc.entityRenderer.itemRenderer.equippedProgressMainHand = 1; mc.entityRenderer.itemRenderer.equippedProgressOffHand = 1; mc.entityRenderer.itemRenderer.itemStackMainHand = player.getItemStackFromSlot(EntityEquipmentSlot.MAINHAND); mc.entityRenderer.itemRenderer.itemStackOffHand = player.getItemStackFromSlot(EntityEquipmentSlot.OFFHAND); - */ //#else //$$ mc.entityRenderer.itemRenderer.prevEquippedProgress = 1; //$$ mc.entityRenderer.itemRenderer.equippedProgress = 1; diff --git a/src/main/resources/META-INF/replaymod_at.cfg b/src/main/resources/META-INF/replaymod_at.cfg index 2c3a2c35..ce8621b7 100644 --- a/src/main/resources/META-INF/replaymod_at.cfg +++ b/src/main/resources/META-INF/replaymod_at.cfg @@ -33,6 +33,11 @@ public net.minecraft.client.MainWindow field_198131_r # framebufferWidth public net.minecraft.client.MainWindow field_198132_s # framebufferHeight ##endif +##if MC>=11300 +# FirstPersonRenderer +public net.minecraft.client.renderer.FirstPersonRenderer * +##endif + # Packets ##if MC>=11300 public net.minecraft.network.play.server.SPacketJoinGame field_149200_f # maxPlayers @@ -181,6 +186,13 @@ public net.minecraft.client.renderer.GlStateManager$BooleanState * # EntityLivingBase public net.minecraft.entity.EntityLivingBase field_184621_as # HAND_STATES public net.minecraft.entity.EntityLivingBase field_184628_bn # activeItemStackUseCount +##if MC>=11300 +public net.minecraft.entity.EntityLivingBase field_184623_bh # interpTargetX +public net.minecraft.entity.EntityLivingBase field_184624_bi # interpTargetY +public net.minecraft.entity.EntityLivingBase field_184625_bj # interpTargetZ +public net.minecraft.entity.EntityLivingBase field_184626_bk # interpTargetYaw +public net.minecraft.entity.EntityLivingBase field_70709_bj # interpTargetPitch +##endif ##endif # Particles diff --git a/src/main/resources/mixins.recording.replaymod.json b/src/main/resources/mixins.recording.replaymod.json index d6278d85..b1300588 100644 --- a/src/main/resources/mixins.recording.replaymod.json +++ b/src/main/resources/mixins.recording.replaymod.json @@ -5,11 +5,14 @@ "server": [], "client": [ //#if MC>=11300 + "MixinDownloadingPackFinder", "MixinKeyboardListener", "MixinMouseHelper", //#endif //#if MC>=10904 - "MixinPlayerControllerMP", + //#if MC<11300 + //$$ "MixinPlayerControllerMP", + //#endif "MixinWorldClient", //#endif //#if MC<=10710 diff --git a/versions/common.gradle b/versions/common.gradle index bf8f86c7..3f49329a 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -154,7 +154,7 @@ configurations { dependencies { if (FG3) { minecraft 'net.minecraftforge:forge:' + [ - 11302: '1.13.2-25.0.63', + 11302: '1.13.2-25.0.76', ][mcVersion] } From 4da57dceb08b73b55e80dc5f66f2ef61fd08642b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 11 Mar 2019 15:08:52 +0100 Subject: [PATCH 50/65] Fix GSON dependency configuration for FG3 --- versions/common.gradle | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/versions/common.gradle b/versions/common.gradle index 3f49329a..18b88719 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -125,7 +125,7 @@ minecraft { afterEvaluate { if (mcVersion >= 11300) { - // FIXME + // No longer required in 1.13+ because all version info is in the toml file } else { // Note cannot use minecraft.replace because that has already been forwarded to the task by FG by now tasks.sourceMainJava.replace '@MOD_VERSION@', project.version @@ -147,8 +147,14 @@ repositories { configurations { shade compile.extendsFrom shade +} - // FIXME seems to be inherited now? shade.exclude group: 'com.google.code.gson', module: 'gson' // provided by MC +def shadeExclusions = { + // Cannot just add these to the shade configuration because they'd be inherited by the compile configuration then + exclude group: 'com.google.guava', module: 'guava-jdk5' + exclude group: 'com.google.guava', module: 'guava' // provided by MC + exclude group: 'com.google.code.gson', module: 'gson' // provided by MC + exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used } dependencies { @@ -158,13 +164,6 @@ dependencies { ][mcVersion] } - if (FG3) { - // FIXME gson should be bundled with MC, not sure why forge doesn't add it (probably related to above shade.exclude) - annotationProcessor group: 'com.google.code.gson', name: 'gson', version: '2.8.5' - compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.8.5' - compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' - } - // compile 'com.github.SpongePowered:Mixin:404f5da' // 0.7.5-SNAPSHOT // ^ mixin doesn't compile on jitpack, so we'll have to depend on the SNAPSHOT and build it manually for reprod if (!gradle.gradleVersion.startsWith('3.')) { @@ -174,20 +173,16 @@ dependencies { compile 'org.spongepowered:mixin:0.7.8-SNAPSHOT' shade 'com.googlecode.mp4parser:isoparser:1.1.7' shade 'org.apache.commons:commons-exec:1.3' - def withoutGuava = { exclude group: 'com.google.guava', module: 'guava-jdk5' } - shade 'com.google.apis:google-api-services-youtube:v3-rev178-1.22.0', withoutGuava - shade 'com.google.api-client:google-api-client-gson:1.20.0', withoutGuava - shade 'com.google.api-client:google-api-client-java6:1.20.0', withoutGuava + shade 'com.google.apis:google-api-services-youtube:v3-rev178-1.22.0', shadeExclusions + shade 'com.google.api-client:google-api-client-gson:1.20.0', shadeExclusions + shade 'com.google.api-client:google-api-client-java6:1.20.0', shadeExclusions shade 'com.google.oauth-client:google-oauth-client-jetty:1.20.0' shade 'org.aspectj:aspectjrt:1.8.2' def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' - shade("com.replaymod.replaystudio:$studioVersion:9e362ef"){ // FIXME - exclude group: 'com.google.guava', module: 'guava' // provided by MC - exclude group: 'org.projectlombok', module: 'lombok' // runtime only for @SneakyThrows which isn't used - } + shade "com.replaymod.replaystudio:$studioVersion:9e362ef", shadeExclusions // FIXME def jGuiVersion = project.name if (['1.10.2', '1.11', '1.11.2'].contains(jGuiVersion)) jGuiVersion = '1.9.4' From 8526264daba3cc243db84c0f21e4dfabefc75a8c Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 11 Mar 2019 15:24:55 +0100 Subject: [PATCH 51/65] Disable reproducible builds for now since people keep breaking it Need to re-enable it before 1.13 release since it's required for sigs. --- .drone.yml | 5 +++-- .drone.yml.sig | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5b371337..ecea2c97 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,8 +8,9 @@ pipeline: commands: - curl --fail --create-dirs -o $HOME/.gradle/init.d/mirror.gradle https://maven.johni0702.de/config/gradle - git config --global user.email "replaymod@drone" && git config --global user.name "Drone" - - REPRODUCIBLE_BUILD=1 ./gradlew :1.7.10:setupCIWorkspace :jGui:1.7.10:setupCIWorkspace - - SIGNED_JAR=$(! git describe --exact 2>&1 > /dev/null; echo $?) REPRODUCIBLE_BUILD=1 ./gradlew $(bash -c "echo :{1.7.10,1.8,1.8.9,1.9.4,1.10.2,1.11,1.11.2,1.12,1.12.1,1.12.2}:reobfJar") --parallel + - ./gradlew --old-gradle :1.7.10:setupCIWorkspace :jGui:1.7.10:setupCIWorkspace + - ./gradlew --old-gradle :1.7.10:reobfJar + - ./gradlew $(bash -c "echo :{1.8,1.8.9,1.9.4,1.10.2,1.11,1.11.2,1.12,1.12.1,1.12.2,1.13.2}:reobfJar") --parallel - mkdir -p build/libs && mv versions/*/build/libs/* build/libs/ archive: image: plugins/s3 diff --git a/.drone.yml.sig b/.drone.yml.sig index 3bd52b21..fff644e1 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgY2xvbmU6CiAgICBpbWFnZTogcGx1Z2lucy9naXQKICAgIHRhZ3M6IHRydWUKICAgIHJlY3Vyc2l2ZTogdHJ1ZQogIGJ1aWxkOgogICAgaW1hZ2U6IG1hdmVuOjMuMy45LWpkay04CiAgICBjb21tYW5kczoKICAgICAgLSBjdXJsIC0tZmFpbCAtLWNyZWF0ZS1kaXJzIC1vICRIT01FLy5ncmFkbGUvaW5pdC5kL21pcnJvci5ncmFkbGUgaHR0cHM6Ly9tYXZlbi5qb2huaTA3MDIuZGUvY29uZmlnL2dyYWRsZQogICAgICAtIGdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5lbWFpbCAicmVwbGF5bW9kQGRyb25lIiAmJiBnaXQgY29uZmlnIC0tZ2xvYmFsIHVzZXIubmFtZSAiRHJvbmUiCiAgICAgIC0gUkVQUk9EVUNJQkxFX0JVSUxEPTEgLi9ncmFkbGV3IDoxLjcuMTA6c2V0dXBDSVdvcmtzcGFjZSA6akd1aToxLjcuMTA6c2V0dXBDSVdvcmtzcGFjZQogICAgICAtIFNJR05FRF9KQVI9JCghIGdpdCBkZXNjcmliZSAtLWV4YWN0IDI-JjEgPiAvZGV2L251bGw7IGVjaG8gJD8pIFJFUFJPRFVDSUJMRV9CVUlMRD0xIC4vZ3JhZGxldyAkKGJhc2ggLWMgImVjaG8gOnsxLjcuMTAsMS44LDEuOC45LDEuOS40LDEuMTAuMiwxLjExLDEuMTEuMiwxLjEyLDEuMTIuMSwxLjEyLjJ9OnJlb2JmSmFyIikgLS1wYXJhbGxlbAogICAgICAtIG1rZGlyIC1wIGJ1aWxkL2xpYnMgJiYgbXYgdmVyc2lvbnMvKi9idWlsZC9saWJzLyogYnVpbGQvbGlicy8KICBhcmNoaXZlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIGFjbDogcHVibGljLXJlYWQKICAgIHJlZ2lvbjogInVzLWVhc3QtMSIKICAgIGJ1Y2tldDogInJlcGxheW1vZCIKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHNvdXJjZTogYnVpbGQvbGlicy8qCiAgICBzdHJpcF9wcmVmaXg6IGJ1aWxkL2xpYnMvCiAgICB0YXJnZXQ6IC8K.2bNe9Pd4FUTIP9VTrFR4YuHJkgCmjIbfBCUu-Wrvap8 \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgY2xvbmU6CiAgICBpbWFnZTogcGx1Z2lucy9naXQKICAgIHRhZ3M6IHRydWUKICAgIHJlY3Vyc2l2ZTogdHJ1ZQogIGJ1aWxkOgogICAgaW1hZ2U6IG1hdmVuOjMuMy45LWpkay04CiAgICBjb21tYW5kczoKICAgICAgLSBjdXJsIC0tZmFpbCAtLWNyZWF0ZS1kaXJzIC1vICRIT01FLy5ncmFkbGUvaW5pdC5kL21pcnJvci5ncmFkbGUgaHR0cHM6Ly9tYXZlbi5qb2huaTA3MDIuZGUvY29uZmlnL2dyYWRsZQogICAgICAtIGdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5lbWFpbCAicmVwbGF5bW9kQGRyb25lIiAmJiBnaXQgY29uZmlnIC0tZ2xvYmFsIHVzZXIubmFtZSAiRHJvbmUiCiAgICAgIC0gLi9ncmFkbGV3IC0tb2xkLWdyYWRsZSA6MS43LjEwOnNldHVwQ0lXb3Jrc3BhY2UgOmpHdWk6MS43LjEwOnNldHVwQ0lXb3Jrc3BhY2UKICAgICAgLSAuL2dyYWRsZXcgLS1vbGQtZ3JhZGxlIDoxLjcuMTA6cmVvYmZKYXIKICAgICAgLSAuL2dyYWRsZXcgJChiYXNoIC1jICJlY2hvIDp7MS44LDEuOC45LDEuOS40LDEuMTAuMiwxLjExLDEuMTEuMiwxLjEyLDEuMTIuMSwxLjEyLjIsMS4xMy4yfTpyZW9iZkphciIpIC0tcGFyYWxsZWwKICAgICAgLSBta2RpciAtcCBidWlsZC9saWJzICYmIG12IHZlcnNpb25zLyovYnVpbGQvbGlicy8qIGJ1aWxkL2xpYnMvCiAgYXJjaGl2ZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBhY2w6IHB1YmxpYy1yZWFkCiAgICByZWdpb246ICJ1cy1lYXN0LTEiCiAgICBidWNrZXQ6ICJyZXBsYXltb2QiCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzb3VyY2U6IGJ1aWxkL2xpYnMvKgogICAgc3RyaXBfcHJlZml4OiBidWlsZC9saWJzLwogICAgdGFyZ2V0OiAvCg.xvkvCrkXlWrSXghw1bLwsewIfd0vJZyz-Wk15airj_s \ No newline at end of file From 79f02b5dfb8ad7b4ccdc249933f5bd4b46e154f2 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 11 Mar 2019 15:48:11 +0100 Subject: [PATCH 52/65] Fix regression in MixinRenderItem redirect method signature --- src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java index bb9d3f06..c1e6fecc 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java @@ -26,10 +26,11 @@ import net.minecraft.util.Util; public class MixinRenderItem { //#if MC>=11300 @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;milliTime()J")) + private static long getEnchantmentTime() { //#else //$$ @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) + //$$ private long getEnchantmentTime() { //#endif - private static long getEnchantmentTime() { ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); if (replayHandler != null) { return replayHandler.getReplaySender().currentTimeStamp(); From fde807f8ff8fc92cf391a9251db88aedbb605bf3 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 11 Mar 2019 17:29:22 +0100 Subject: [PATCH 53/65] Fix pack.mcmeta for pre-1.13 versions --- src/main/resources/pack.mcmeta | 8 ++++++++ versions/preprocessor.gradle | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 6afaebbc..700d98b0 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,6 +1,14 @@ { "pack": { "description": "ReplayMod resources", + //#if MC>=11300 "pack_format": 4 + //#else + //#if MC>=11002 + //$$ "pack_format": 2 + //#else + //$$ "pack_format": 1 + //#endif + //#endif } } diff --git a/versions/preprocessor.gradle b/versions/preprocessor.gradle index 44e1e9f2..b316eb59 100644 --- a/versions/preprocessor.gradle +++ b/versions/preprocessor.gradle @@ -146,7 +146,13 @@ static def convertFile(Map kws, Map vars, File project.ext.convertTree = { Map vars, String inName, String outName=inName -> def defaultKws = [if: '//#if ', ifdef: '//#ifdef ', else: '//#else', endif: '//#endif', eval: '//$$'] def cfgKws = [if: '##if ', ifdef: '##ifdef ', else: '##else', endif: '##endif', eval: '#$$'] - def extensions = ['.java': defaultKws, '.gradle': defaultKws, '.json': defaultKws, '.cfg': cfgKws] + def extensions = [ + '.java': defaultKws, + '.gradle': defaultKws, + '.json': defaultKws, + '.mcmeta': defaultKws, + '.cfg': cfgKws + ] def inPath = project.file(inName).toPath() def outPath = project.file(outName).toPath() def inPlace = inPath.toAbsolutePath().equals(outPath.toAbsolutePath()) From 32de147ae2385e719a427525353db96d6c4bdc6c Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 11 Mar 2019 17:41:29 +0100 Subject: [PATCH 54/65] Update ReplayStudio for dedicated replay cache Also change artifact back from localMaven to jitpack --- src/main/java/com/replaymod/replay/QuickReplaySender.java | 8 ++++---- versions/common.gradle | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 7f2087d4..2e4f62b2 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -283,10 +283,10 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe private boolean tryLoadFromCache(Consumer progress) throws IOException { boolean success = false; - Optional cacheIndexOpt = replayFile.get(CACHE_INDEX_ENTRY); + Optional cacheIndexOpt = replayFile.getCache(CACHE_INDEX_ENTRY); if (!cacheIndexOpt.isPresent()) return false; try (InputStream indexIn = cacheIndexOpt.get()) { - Optional cacheOpt = replayFile.get(CACHE_ENTRY); + Optional cacheOpt = replayFile.getCache(CACHE_ENTRY); if (!cacheOpt.isPresent()) return false; try (InputStream cacheIn = cacheOpt.get()) { success = loadFromCache(cacheIn, indexIn, progress); @@ -370,8 +370,8 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe double sysTimeStart = System.currentTimeMillis(); double duration; try (ReplayInputStream in = replayFile.getPacketData(studio); - OutputStream cacheOut = replayFile.write(CACHE_ENTRY); - OutputStream cacheIndexOut = replayFile.write(CACHE_INDEX_ENTRY)) { + OutputStream cacheOut = replayFile.writeCache(CACHE_ENTRY); + OutputStream cacheIndexOut = replayFile.writeCache(CACHE_INDEX_ENTRY)) { NetOutput out = new StreamNetOutput(cacheOut); out.writeVarInt(CACHE_VERSION); NetOutput indexOut = new StreamNetOutput(cacheIndexOut); diff --git a/versions/common.gradle b/versions/common.gradle index 18b88719..343feb07 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -136,7 +136,6 @@ afterEvaluate { } repositories { - mavenLocal() maven { name = "SpongePowered Repo" url = "http://repo.spongepowered.org/maven/" @@ -182,7 +181,7 @@ dependencies { def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' - shade "com.replaymod.replaystudio:$studioVersion:9e362ef", shadeExclusions // FIXME + shade "com.github.ReplayMod.ReplayStudio:$studioVersion:e0562a30", shadeExclusions def jGuiVersion = project.name if (['1.10.2', '1.11', '1.11.2'].contains(jGuiVersion)) jGuiVersion = '1.9.4' From 9d315134fc6a67c78e856fcc95824c7e3bc1ba3f Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 11 Mar 2019 18:16:23 +0100 Subject: [PATCH 55/65] Fix packet re-encoding in pre-1.13 versions --- .../replaymod/replay/FullReplaySender.java | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index 033bff73..cf7d4e04 100755 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -6,11 +6,11 @@ import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Restrictions; import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replaystudio.io.ReplayInputStream; -import com.replaymod.replaystudio.io.ReplayOutputStream; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.studio.ReplayStudio; +import com.replaymod.replaystudio.studio.protocol.StudioCodec; +import com.replaymod.replaystudio.studio.protocol.StudioSession; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler.Sharable; @@ -125,9 +125,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend private static int TP_DISTANCE_LIMIT = 128; - private static final ByteBuf byteBuf = Unpooled.buffer(); - private static final ByteBufOutputStream byteBufOut = new ByteBufOutputStream(byteBuf); - /** * The replay handler responsible for the current replay. */ @@ -170,11 +167,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend */ protected ReplayInputStream replayIn; - /** - * @see PacketData#PacketData(ReplayInputStream, ReplayOutputStream) - */ - private ReplayOutputStream encoder; - /** * The next packet that should be sent. * This is required as some actions such as jumping to a specified timestamp have to peek at the next packet. @@ -862,7 +854,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend synchronized (FullReplaySender.this) { if (replayIn == null) { replayIn = replayFile.getPacketData(new ReplayStudio(), true); - encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut, true); } // Packet loop while (true) { @@ -889,7 +880,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Read the next packet if we don't already have one if (nextPacket == null) { - nextPacket = new PacketData(replayIn, encoder); + nextPacket = new PacketData(replayIn, loginPhase); } int nextTimeStamp = nextPacket.timestamp; @@ -1064,7 +1055,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend if (replayIn == null) { replayIn = replayFile.getPacketData(new ReplayStudio(), true); - encoder = new ReplayOutputStream(new ReplayStudio(), byteBufOut, true); } while (true) { // Send packets @@ -1076,7 +1066,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend nextPacket = null; } else { // Otherwise read one from the input stream - pd = new PacketData(replayIn, encoder); + pd = new PacketData(replayIn, loginPhase); } int nextTimeStamp = pd.timestamp; @@ -1209,10 +1199,14 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend } private static final class PacketData { + private static final com.github.steveice10.netty.buffer.ByteBuf byteBuf = com.github.steveice10.netty.buffer.Unpooled.buffer(); + private static final StudioCodec codecLoginPhase = new StudioCodec(new StudioSession(new ReplayStudio(), false, true)); + private static final StudioCodec codecPlayPhase = new StudioCodec(new StudioSession(new ReplayStudio(), false, false)); + private final int timestamp; private final byte[] bytes; - PacketData(ReplayInputStream in, ReplayOutputStream encoder) throws IOException { + PacketData(ReplayInputStream in, boolean loginPhase) throws IOException { com.replaymod.replaystudio.PacketData data = in.readPacket(); timestamp = (int) data.getTime(); // We need to re-encode MCProtocolLib packets, so we can later decode them as NMS packets @@ -1222,10 +1216,12 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend byteBuf.markReaderIndex(); // Mark the current reader and writer index (should be at start) byteBuf.markWriterIndex(); - encoder.write(data); // Re-encode packet, data will end up in byteBuf - encoder.flush(); + try { + (loginPhase ? codecLoginPhase : codecPlayPhase).encode(null, data.getPacket(), byteBuf); + } catch (Exception e) { + throw new IOException(e); + } - byteBuf.skipBytes(8); // Skip packet length & timestamp bytes = new byte[byteBuf.readableBytes()]; // Create bytes array of sufficient size byteBuf.readBytes(bytes); // Read all data into bytes From fe68f76bd120149ae6fa0418181757b8b0534cd8 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 11 Mar 2019 18:52:00 +0100 Subject: [PATCH 56/65] Remove accidentally added mixin-hack subproject inclusion --- settings.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index 59ab2954..1fb85e6d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -56,5 +56,3 @@ replayModVersions.each { version -> buildFileName = '../common.gradle' } } - -include ':mixin-hack' \ No newline at end of file From eb6e7b79d06431c8e490692144bb51422c526178 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 12 Mar 2019 13:25:05 +0100 Subject: [PATCH 57/65] Add start/stop/pause/resume recording functionality Using automatically applied, ReplayStudio-based post processing. --- jGui | 2 +- .../java/com/replaymod/core/ReplayMod.java | 8 + .../core/gui/GuiBackgroundProcesses.java | 135 ++++++++++++++ .../replaymod/editor/gui/MarkerProcessor.java | 173 ++++++++++++++++++ .../recording/ReplayModRecording.java | 6 +- .../java/com/replaymod/recording/Setting.java | 4 +- .../recording/gui/GuiRecordingControls.java | 110 +++++++++++ .../recording/gui/GuiRecordingOverlay.java | 8 +- .../handler/ConnectionEventHandler.java | 30 +-- .../recording/packet/PacketListener.java | 72 ++++++-- src/main/resources/assets/replaymod/lang | 2 +- 11 files changed, 513 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java create mode 100644 src/main/java/com/replaymod/editor/gui/MarkerProcessor.java create mode 100644 src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java diff --git a/jGui b/jGui index 31fecfa0..63aec9fd 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 31fecfa0d62ab5c689d301a24a5584c3537f8f16 +Subproject commit 63aec9fd9a7acf4dad8b128ee86e70ea05fa4f5c diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index ed41c6aa..5efd7fea 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -2,6 +2,7 @@ package com.replaymod.core; import com.google.common.io.Files; import com.google.common.util.concurrent.ListenableFutureTask; +import com.replaymod.core.gui.GuiBackgroundProcesses; import com.replaymod.core.gui.GuiReplaySettings; import com.replaymod.core.gui.RestoreReplayGui; import com.replaymod.core.handler.MainMenuHandler; @@ -152,6 +153,8 @@ public class ReplayMod implements Module { private final List modules = new ArrayList<>(); + private final GuiBackgroundProcesses backgroundProcesses = new GuiBackgroundProcesses(); + public ReplayMod() { I18n.setI18n(net.minecraft.client.resources.I18n::format); @@ -285,6 +288,7 @@ public class ReplayMod implements Module { //#endif FML_BUS.register(keyBindingRegistry); + FORGE_BUS.register(backgroundProcesses); // 1.7.10 crashes when render distance > 16 //#if MC>=10800 @@ -455,4 +459,8 @@ public class ReplayMod implements Module { mc.ingameGUI.getChatGUI().printChatMessage(text); } } + + public GuiBackgroundProcesses getBackgroundProcesses() { + return backgroundProcesses; + } } diff --git a/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java new file mode 100644 index 00000000..d0754278 --- /dev/null +++ b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java @@ -0,0 +1,135 @@ +package com.replaymod.core.gui; + +import de.johni0702.minecraft.gui.GuiRenderer; +import de.johni0702.minecraft.gui.RenderInfo; +import de.johni0702.minecraft.gui.container.AbstractGuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.container.VanillaGuiScreen; +import de.johni0702.minecraft.gui.element.GuiElement; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; + +//#if MC>=10800 +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif +//#else +//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; +//#endif + +import static com.replaymod.core.versions.MCVer.getGui; +import static com.replaymod.core.versions.MCVer.getMinecraft; + +public class GuiBackgroundProcesses { + private GuiPanel panel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10)); + + public void register() { + MinecraftForge.EVENT_BUS.register(this); + } + + public void unregister() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { + if (getGui(event) != getMinecraft().currentScreen) return; // people tend to construct GuiScreens without opening them + + VanillaGuiScreen.setup(getGui(event)).setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + pos(panel, width - 5 - width(panel), 5); + } + }).addElements(null, panel); + } + + public void addProcess(GuiElement element) { + panel.addElements(new VerticalLayout.Data(1.0), + new Element(element)); + } + + public void removeProcess(GuiElement element) { + for (GuiElement child : panel.getChildren()) { + if (((Element) child).inner == element) { + panel.removeElement(child); + return; + } + } + } + + private static class Element extends AbstractGuiContainer { + private GuiElement inner; + + Element(GuiElement inner) { + this.inner = inner; + addElements(null, inner); + + setLayout(new CustomLayout() { + @Override + protected void layout(Element container, int width, int height) { + pos(inner, 10, 10); + size(inner, width - 20, height - 20); + } + }); + } + + @Override + public ReadableDimension getMinSize() { + ReadableDimension minSize = inner.getMinSize(); + return new Dimension(minSize.getWidth() + 20, minSize.getHeight() + 20); + } + + @Override + public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { + final int u0 = 0; + final int v0 = 39; + renderer.bindTexture(TEXTURE); + + int w = size.getWidth(); + int h = size.getHeight(); + + // Corners + renderer.drawTexturedRect(0, 0, u0, v0, 5, 5); // Top left + renderer.drawTexturedRect(w - 5, 0, u0 + 12, v0, 5, 5); // Top right + renderer.drawTexturedRect(0, h - 5, u0, v0 + 12, 5, 5); // Bottom left + renderer.drawTexturedRect(w - 5, h - 5, u0 + 12, v0 + 12, 5, 5); // Bottom right + + // Top and bottom edge + for (int x = 5; x < w - 5; x += 5) { + int rx = Math.min(5, w - 5 - x); + renderer.drawTexturedRect(x, 0, u0 + 6, v0, rx, 5); // Top + renderer.drawTexturedRect(x, h - 5, u0 + 6, v0 + 12, rx, 5); // Bottom + } + + // Left and right edge + for (int y = 5; y < h - 5; y += 5) { + int ry = Math.min(5, h - 5 - y); + renderer.drawTexturedRect(0, y, u0, v0 + 6, 5, ry); // Left + renderer.drawTexturedRect(w - 5, y, u0 + 12, v0 + 6, 5, ry); // Right + } + + // Center + for (int x = 5; x < w - 5; x += 5) { + for (int y = 5; y < h - 5; y += 5) { + int rx = Math.min(5, w - 5 - x); + int ry = Math.min(5, h - 5 - y); + renderer.drawTexturedRect(x, y, u0 + 6, v0 + 6, rx, ry); + } + } + + super.draw(renderer, size, renderInfo); + } + + @Override + protected Element getThis() { + return this; + } + } +} diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java new file mode 100644 index 00000000..a71da532 --- /dev/null +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -0,0 +1,173 @@ +package com.replaymod.editor.gui; + +import com.replaymod.replaystudio.PacketData; +import com.replaymod.replaystudio.Studio; +import com.replaymod.replaystudio.data.Marker; +import com.replaymod.replaystudio.filter.SquashFilter; +import com.replaymod.replaystudio.filter.StreamFilter; +import com.replaymod.replaystudio.io.ReplayInputStream; +import com.replaymod.replaystudio.io.ReplayOutputStream; +import com.replaymod.replaystudio.replay.ReplayMetaData; +import com.replaymod.replaystudio.replay.ZipReplayFile; +import com.replaymod.replaystudio.stream.IteratorStream; +import com.replaymod.replaystudio.studio.ReplayStudio; +import com.replaymod.replaystudio.util.Utils; +import org.apache.commons.io.FilenameUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class MarkerProcessor { + /** + * Signals the start of a section which is to be cut from the replay. + */ + public static final String MARKER_NAME_START_CUT = "_RM_START_CUT"; + /** + * Signals the end of a section which is to be cut from the replay. + */ + public static final String MARKER_NAME_END_CUT = "_RM_END_CUT"; + /** + * Signals the point at which a replay is to be split into multiple replays. + * If it is found in a section which is to be cut, that section will be cut from both replays. + */ + public static final String MARKER_NAME_SPLIT = "_RM_SPLIT"; + + private static boolean hasWork(Path path) throws IOException { + try (ZipReplayFile inputReplayFile = new ZipReplayFile(new ReplayStudio(), path.toFile())) { + return inputReplayFile.getMarkers().or(HashSet::new).stream().anyMatch(m -> m.getName().startsWith("_RM_")); + } + } + + public static void apply(Path path) throws IOException { + if (!hasWork(path)) { + return; + } + + String replayName = FilenameUtils.getBaseName(path.getFileName().toString()); + int splitCounter = 0; + + Studio studio = new ReplayStudio(); + SquashFilter squashFilter = new SquashFilter(); + squashFilter.init(studio, null); + + Path inputPath = path.resolveSibling("raw").resolve(path.getFileName()); + Files.createDirectories(inputPath.getParent()); + Files.move(path, inputPath); + + try (ZipReplayFile inputReplayFile = new ZipReplayFile(studio, inputPath.toFile())) { + List markers = inputReplayFile.getMarkers().or(HashSet::new) + .stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList()); + Iterator markerIterator = markers.iterator(); + boolean anySplit = markers.stream().anyMatch(m -> m.getName().equals(MARKER_NAME_SPLIT)); + + ReplayInputStream replayInputStream = inputReplayFile.getPacketData(studio, true); + int timeOffset = 0; + SquashFilter cutFilter = null; + int startCutOffset = 0; + PacketData nextPacket = replayInputStream.readPacket(); + Marker nextMarker = markerIterator.next(); + + while (nextPacket != null) { + try (ZipReplayFile outputReplayFile = new ZipReplayFile(studio, null, + (anySplit ? path.resolveSibling(replayName + "_" + splitCounter + ".mcpr") : path).toFile())) { + long duration = 0; + Set outputMarkers = new HashSet<>(); + ReplayMetaData metaData = inputReplayFile.getMetaData(); + metaData.setDate(metaData.getDate() + timeOffset); + + try (ReplayOutputStream replayOutputStream = outputReplayFile.writePacketData(true)) { + if (cutFilter != null) { + cutFilter = squashFilter; + } else if (splitCounter > 0) { + List packets = new ArrayList<>(); + squashFilter.onEnd( + new IteratorStream(packets.listIterator(), (StreamFilter) null), + timeOffset + ); + for (PacketData packet : packets) { + replayOutputStream.write(packet.getTime() - timeOffset, packet.getPacket()); + } + } + + while (nextPacket != null) { + if (nextMarker != null && nextPacket.getTime() > nextMarker.getTime()) { + if (MARKER_NAME_START_CUT.equals(nextMarker.getName())) { + startCutOffset = nextMarker.getTime(); + cutFilter = new SquashFilter(); + } else if (MARKER_NAME_END_CUT.equals(nextMarker.getName())) { + timeOffset += nextMarker.getTime() - startCutOffset; + if (cutFilter != null) { + List packets = new ArrayList<>(); + cutFilter.onEnd( + new IteratorStream(packets.listIterator(), (StreamFilter) null), + nextMarker.getTime() + ); + for (PacketData packet : packets) { + replayOutputStream.write(packet.getTime() - timeOffset, packet.getPacket()); + } + cutFilter = null; + } + } else if (MARKER_NAME_SPLIT.equals(nextMarker.getName())) { + splitCounter++; + timeOffset = nextMarker.getTime(); + startCutOffset = timeOffset; + nextMarker = markerIterator.hasNext() ? markerIterator.next() : null; + break; + } else { + nextMarker.setTime(nextMarker.getTime() - timeOffset); + outputMarkers.add(nextMarker); + } + nextMarker = markerIterator.hasNext() ? markerIterator.next() : null; + continue; + } + + squashFilter.onPacket(null, nextPacket); + if (cutFilter != null) { + if (cutFilter != squashFilter) { + cutFilter.onPacket(null, nextPacket); + } + } else { + replayOutputStream.write(nextPacket.getTime() - timeOffset, nextPacket.getPacket()); + duration = nextPacket.getTime() - timeOffset; + } + nextPacket = replayInputStream.readPacket(); + } + } + + metaData.setDuration((int) duration); + outputReplayFile.writeMetaData(metaData); + + outputReplayFile.writeMarkers(outputMarkers); + + outputReplayFile.writeModInfo(inputReplayFile.getModInfo()); + + // We could filter these but ReplayStudio doesn't yet provide a nice method for determining + // which ones are used. + Map resourcePackIndex = inputReplayFile.getResourcePackIndex(); + if (resourcePackIndex != null) { + outputReplayFile.writeResourcePackIndex(resourcePackIndex); + for (String hash : resourcePackIndex.values()) { + try (InputStream in = inputReplayFile.getResourcePack(hash).get(); + OutputStream out = outputReplayFile.writeResourcePack(hash)) { + Utils.copy(in, out); + } + } + } + + outputReplayFile.save(); + } + } + } + } +} diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index d949a66c..f8137d65 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -60,7 +60,7 @@ public class ReplayModRecording implements Module { public void run() { PacketListener packetListener = connectionEventHandler.getPacketListener(); if (packetListener != null) { - packetListener.addMarker(); + packetListener.addMarker(null); core.printInfoToChat("replaymod.chat.addedmarker"); } } @@ -92,4 +92,8 @@ public class ReplayModRecording implements Module { //#endif connectionEventHandler.onConnectedToServerEvent(networkManager); } + + public ConnectionEventHandler getConnectionEventHandler() { + return connectionEventHandler; + } } diff --git a/src/main/java/com/replaymod/recording/Setting.java b/src/main/java/com/replaymod/recording/Setting.java index e3742d8b..27d2d69c 100644 --- a/src/main/java/com/replaymod/recording/Setting.java +++ b/src/main/java/com/replaymod/recording/Setting.java @@ -6,12 +6,14 @@ public final class Setting extends SettingsRegistry.SettingKeys { public static final Setting RECORD_SINGLEPLAYER = make("recordSingleplayer", "recordsingleplayer", true); public static final Setting RECORD_SERVER = make("recordServer", "recordserver", true); public static final Setting INDICATOR = make("indicator", "indicator", true); + public static final Setting AUTO_START_RECORDING = make("autoStartRecording", "autostartrecording", true); + public static final Setting AUTO_POST_PROCESS = make("autoPostProcess", null, true); private static Setting make(String key, String displayName, T defaultValue) { return new Setting<>(key, displayName, defaultValue); } public Setting(String key, String displayString, T defaultValue) { - super("recording", key, "replaymod.gui.settings." + displayString, defaultValue); + super("recording", key, displayString == null ? null : "replaymod.gui.settings." + displayString, defaultValue); } } diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java new file mode 100644 index 00000000..8eb81b62 --- /dev/null +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java @@ -0,0 +1,110 @@ +package com.replaymod.recording.gui; + +import com.replaymod.core.ReplayMod; +import com.replaymod.editor.gui.MarkerProcessor; +import com.replaymod.recording.Setting; +import com.replaymod.recording.packet.PacketListener; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.container.VanillaGuiScreen; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import net.minecraft.client.gui.GuiIngameMenu; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; + +//#if MC>=10800 +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif +//#else +//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; +//#endif + +import static com.replaymod.core.versions.MCVer.getGui; + +public class GuiRecordingControls { + private ReplayMod core; + private PacketListener packetListener; + private boolean paused; + private boolean stopped; + + private GuiPanel panel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4)); + + private GuiButton buttonPauseResume = new GuiButton(panel).onClick(() -> { + if (paused) { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_END_CUT); + } else { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT); + } + paused = !paused; + updateState(); + }).setSize(98, 20); + + private GuiButton buttonStartStop = new GuiButton(panel).onClick(() -> { + if (stopped) { + paused = false; + packetListener.addMarker(MarkerProcessor.MARKER_NAME_END_CUT); + core.printInfoToChat("replaymod.chat.recordingstarted"); + } else { + int timestamp = (int) packetListener.getCurrentDuration(); + if (!paused) { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT, timestamp); + } + packetListener.addMarker(MarkerProcessor.MARKER_NAME_SPLIT, timestamp + 1); + } + stopped = !stopped; + updateState(); + }).setSize(98, 20); + + public GuiRecordingControls(ReplayMod core, PacketListener packetListener) { + this.core = core; + this.packetListener = packetListener; + + paused = stopped = !core.getSettingsRegistry().get(Setting.AUTO_START_RECORDING); + + updateState(); + } + + public void register() { + MinecraftForge.EVENT_BUS.register(this); + } + + public void unregister() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { + if (getGui(event) instanceof GuiIngameMenu) { + show((GuiIngameMenu) getGui(event)); + } + } + + private void updateState() { + buttonPauseResume.setI18nLabel("replaymod.gui.recording." + (paused ? "resume" : "pause")); + buttonStartStop.setI18nLabel("replaymod.gui.recording." + (stopped ? "start" : "stop")); + + buttonPauseResume.setEnabled(!stopped); + } + + public void show(GuiIngameMenu gui) { + VanillaGuiScreen.setup(gui).setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + pos(panel, width / 2 - 100, height / 4 + 128); + } + }).addElements(null, panel); + } + + public boolean isPaused() { + return paused; + } + + public boolean isStopped() { + return stopped; + } +} diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java index 50e0f033..a0237d75 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java @@ -31,10 +31,12 @@ import static com.replaymod.core.versions.MCVer.*; public class GuiRecordingOverlay { private final Minecraft mc; private final SettingsRegistry settingsRegistry; + private final GuiRecordingControls guiControls; - public GuiRecordingOverlay(Minecraft mc, SettingsRegistry settingsRegistry) { + public GuiRecordingOverlay(Minecraft mc, SettingsRegistry settingsRegistry, GuiRecordingControls guiControls) { this.mc = mc; this.settingsRegistry = settingsRegistry; + this.guiControls = guiControls; } public void register() { @@ -52,9 +54,11 @@ public class GuiRecordingOverlay { @SubscribeEvent public void renderRecordingIndicator(RenderGameOverlayEvent.Post event) { if (getType(event) != RenderGameOverlayEvent.ElementType.ALL) return; + if (guiControls.isStopped()) return; if (settingsRegistry.get(Setting.INDICATOR)) { FontRenderer fontRenderer = getFontRenderer(mc); - fontRenderer.drawString(I18n.format("replaymod.gui.recording").toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff); + String text = guiControls.isPaused() ? I18n.format("replaymod.gui.paused") : I18n.format("replaymod.gui.recording"); + fontRenderer.drawString(text.toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff); bindTexture(TEXTURE); resetColor(); enableAlpha(); diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index f93f7572..cd1cd4e9 100755 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -3,7 +3,9 @@ package com.replaymod.recording.handler; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.ModCompat; import com.replaymod.core.utils.Utils; +import com.replaymod.editor.gui.MarkerProcessor; import com.replaymod.recording.Setting; +import com.replaymod.recording.gui.GuiRecordingControls; import com.replaymod.recording.gui.GuiRecordingOverlay; import com.replaymod.recording.packet.PacketListener; import com.replaymod.replaystudio.replay.ReplayFile; @@ -17,16 +19,9 @@ import org.apache.logging.log4j.Logger; //#if MC>=10800 //#if MC>=11300 import net.minecraft.world.dimension.DimensionType; -import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//$$ import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; //#endif import static com.replaymod.core.versions.MCVer.WorldType_DEBUG_ALL_BLOCK_STATES; -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//$$ import cpw.mods.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; //#endif import java.io.File; @@ -51,6 +46,7 @@ public class ConnectionEventHandler { private RecordingEventHandler recordingEventHandler; private PacketListener packetListener; private GuiRecordingOverlay guiOverlay; + private GuiRecordingControls guiControls; public ConnectionEventHandler(Logger logger, ReplayMod core) { this.logger = logger; @@ -111,26 +107,33 @@ public class ConnectionEventHandler { metaData.setGenerator("ReplayMod v" + ReplayMod.instance.getVersion()); metaData.setDate(System.currentTimeMillis()); metaData.setMcVersion(ReplayMod.getMinecraftVersion()); - packetListener = new PacketListener(replayFile, metaData); + packetListener = new PacketListener(core, currentFile.toPath(), replayFile, metaData); networkManager.channel().pipeline().addBefore(packetHandlerKey, "replay_recorder", packetListener); recordingEventHandler = new RecordingEventHandler(packetListener); recordingEventHandler.register(); - guiOverlay = new GuiRecordingOverlay(mc, core.getSettingsRegistry()); + guiControls = new GuiRecordingControls(core, packetListener); + guiControls.register(); + + guiOverlay = new GuiRecordingOverlay(mc, core.getSettingsRegistry(), guiControls); guiOverlay.register(); - core.printInfoToChat("replaymod.chat.recordingstarted"); + if (core.getSettingsRegistry().get(Setting.AUTO_START_RECORDING)) { + core.printInfoToChat("replaymod.chat.recordingstarted"); + } else { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT, 0); + } } catch (Throwable e) { e.printStackTrace(); core.printWarningToChat("replaymod.chat.recordingfailed"); } } - /* FIXME event not (yet?) in 1.13 - @SubscribeEvent - public void onDisconnectedFromServerEvent(ClientDisconnectionFromServerEvent event) { + public void reset() { if (packetListener != null) { + guiControls.unregister(); + guiControls = null; guiOverlay.unregister(); guiOverlay = null; recordingEventHandler.unregister(); @@ -138,7 +141,6 @@ public class ConnectionEventHandler { packetListener = null; } } - */ public PacketListener getPacketListener() { return packetListener; diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index 2ead09fb..44e9851f 100755 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -1,9 +1,16 @@ package com.replaymod.recording.packet; +import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Restrictions; +import com.replaymod.editor.gui.MarkerProcessor; +import com.replaymod.recording.ReplayModRecording; +import com.replaymod.recording.Setting; +import com.replaymod.recording.handler.ConnectionEventHandler; import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.utils.Colors; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; @@ -42,6 +49,7 @@ import net.minecraft.network.EnumPacketDirection; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.file.Path; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -58,6 +66,8 @@ public class PacketListener extends ChannelInboundHandlerAdapter { private static final Minecraft mc = getMinecraft(); private static final Logger logger = LogManager.getLogger(); + private final ReplayMod core; + private final Path outputPath; private final ReplayFile replayFile; private final ResourcePackRecorder resourcePackRecorder; @@ -85,7 +95,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter { */ private final AtomicInteger lastSaveMetaDataId = new AtomicInteger(); - public PacketListener(ReplayFile replayFile, ReplayMetaData metaData) throws IOException { + public PacketListener(ReplayMod core, Path outputPath, ReplayFile replayFile, ReplayMetaData metaData) throws IOException { + this.core = core; + this.outputPath = outputPath; this.replayFile = replayFile; this.metaData = metaData; this.resourcePackRecorder = new ResourcePackRecorder(replayFile); @@ -174,21 +186,39 @@ public class PacketListener extends ChannelInboundHandlerAdapter { metaData.setDuration((int) lastSentPacket); saveMetaData(); - saveService.shutdown(); - try { - saveService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - logger.error("Waiting for save service termination:", e); - } - - synchronized (replayFile) { - try { - replayFile.save(); - replayFile.close(); - } catch (IOException e) { - logger.error("Saving replay file:", e); + core.runLater(() -> { + ConnectionEventHandler connectionEventHandler = ReplayModRecording.instance.getConnectionEventHandler(); + if (connectionEventHandler.getPacketListener() == this) { + connectionEventHandler.reset(); } - } + }); + + GuiLabel savingLabel = new GuiLabel().setI18nText("replaymod.gui.replaysaving.title").setColor(Colors.BLACK); + new Thread(() -> { + core.runLater(() -> core.getBackgroundProcesses().addProcess(savingLabel)); + + saveService.shutdown(); + try { + saveService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.error("Waiting for save service termination:", e); + } + + synchronized (replayFile) { + try { + replayFile.save(); + replayFile.close(); + + if (core.getSettingsRegistry().get(Setting.AUTO_POST_PROCESS)) { + MarkerProcessor.apply(outputPath); + } + } catch (IOException e) { + logger.error("Saving replay file:", e); + } + } + + core.runLater(() -> core.getBackgroundProcesses().removeProcess(savingLabel)); + }).start(); } @Override @@ -376,11 +406,15 @@ public class PacketListener extends ChannelInboundHandlerAdapter { return array; } - public void addMarker() { + public void addMarker(String name) { + addMarker(name, (int) getCurrentDuration()); + } + + public void addMarker(String name, int timestamp) { Entity view = getRenderViewEntity(mc); - int timestamp = (int) (System.currentTimeMillis() - startTime); Marker marker = new Marker(); + marker.setName(name); marker.setTime(timestamp); marker.setX(view.posX); marker.setY(view.posY); @@ -401,6 +435,10 @@ public class PacketListener extends ChannelInboundHandlerAdapter { }); } + public long getCurrentDuration() { + return lastSentPacket; + } + public void setServerWasPaused() { this.serverWasPaused = true; } diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 552d02cd..967405aa 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 552d02cdc7e67ddfaeecb2bd60b5a5d1632e5130 +Subproject commit 967405aafad5e7613c3eb9760d8eab03ad4e1567 From 51c073012e6bb8d032cf44bf58ebdb3ef8459638 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 12 Mar 2019 19:44:09 +0100 Subject: [PATCH 58/65] Re-work Replay Editor UI --- .../core/handler/MainMenuHandler.java | 4 +- .../com/replaymod/editor/ReplayModEditor.java | 2 +- .../replaymod/editor/gui/GuiEditReplay.java | 262 ++++++++++++++++++ .../replaymod/editor/gui/MarkerProcessor.java | 5 +- .../replaymod/editor/handler/GuiHandler.java | 68 +++-- .../replaymod/online/handler/GuiHandler.java | 9 +- .../recording/packet/PacketListener.java | 2 +- .../com/replaymod/replay/ReplayHandler.java | 24 +- .../com/replaymod/replay/ReplayModReplay.java | 15 +- .../gui/overlay/GuiEditMarkerPopup.java | 13 +- .../replay/gui/overlay/GuiMarkerTimeline.java | 86 +++++- .../replaymod/replay/handler/GuiHandler.java | 2 +- src/main/resources/assets/replaymod/lang | 2 +- 13 files changed, 396 insertions(+), 98 deletions(-) create mode 100644 src/main/java/com/replaymod/editor/gui/GuiEditReplay.java diff --git a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java index 20b66dbd..42ec9dbb 100644 --- a/src/main/java/com/replaymod/core/handler/MainMenuHandler.java +++ b/src/main/java/com/replaymod/core/handler/MainMenuHandler.java @@ -40,9 +40,9 @@ public class MainMenuHandler { if (x(button) + button.width < gui.width / 2 - 100 || x(button) > gui.width / 2 + 100 || y(button) > gui.height / 4 + 10 + 4 * 24) continue; - // Move button up to make space for two rows of buttons + // Move button up to make space for one rows of buttons // and then move back down by 10 to compensate for the space to the exit button that was already there - int offset = -2 * 24 + 10; + int offset = -1 * 24 + 10; y(button, y(button) + offset); //#if MC>=11300 diff --git a/src/main/java/com/replaymod/editor/ReplayModEditor.java b/src/main/java/com/replaymod/editor/ReplayModEditor.java index 1b4dd02b..6e5b0304 100644 --- a/src/main/java/com/replaymod/editor/ReplayModEditor.java +++ b/src/main/java/com/replaymod/editor/ReplayModEditor.java @@ -23,7 +23,7 @@ public class ReplayModEditor implements Module { @Override public void initClient() { - new GuiHandler(this).register(); + new GuiHandler().register(); } public ReplayMod getCore() { diff --git a/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java b/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java new file mode 100644 index 00000000..9045f32c --- /dev/null +++ b/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java @@ -0,0 +1,262 @@ +package com.replaymod.editor.gui; + +import com.replaymod.core.ReplayMod; +import com.replaymod.core.utils.Utils; +import com.replaymod.editor.ReplayModEditor; +import com.replaymod.replay.gui.overlay.GuiMarkerTimeline; +import com.replaymod.replaystudio.data.Marker; +import com.replaymod.replaystudio.replay.ZipReplayFile; +import com.replaymod.replaystudio.studio.ReplayStudio; +import de.johni0702.minecraft.gui.GuiRenderer; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.element.GuiHorizontalScrollbar; +import de.johni0702.minecraft.gui.element.GuiTexturedButton; +import de.johni0702.minecraft.gui.element.GuiTooltip; +import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar; +import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; +import de.johni0702.minecraft.gui.utils.lwjgl.Color; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import net.minecraft.crash.CrashReport; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class GuiEditReplay extends AbstractGuiPopup { + private final Path inputPath; + + private final EditTimeline timeline; + + private final GuiTimelineTime timelineTime = new GuiTimelineTime<>(); + + private final GuiHorizontalScrollbar scrollbar = new GuiHorizontalScrollbar().setSize(300, 9); + + + private final GuiTexturedButton zoomInButton = new GuiTexturedButton().setSize(9, 9) + .onClick(() -> zoomTimeline(2d / 3d)) + .setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20) + .setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomin")); + + private final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9) + .onClick(() -> zoomTimeline(3d / 2d)) + .setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30) + .setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomout")); + + private final GuiPanel zoomButtonPanel = new GuiPanel() + .setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(2)) + .addElements(null, zoomInButton, zoomOutButton); + + private Set markers; + + protected GuiEditReplay(GuiContainer container, Path inputPath) throws IOException { + super(container); + this.inputPath = inputPath; + + try (ZipReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), inputPath.toFile())) { + markers = replayFile.getMarkers().or(HashSet::new); + timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers); + timeline.setSize(300, 20) + .setMarkers() + .setLength(replayFile.getMetaData().getDuration()) + .onClick(timeline::setCursorPosition); + } + + timelineTime.setTimeline(timeline); + + scrollbar.onValueChanged(() -> { + timeline.setOffset((int) (scrollbar.getPosition() * timeline.getLength())); + timeline.setZoom(scrollbar.getZoom()); + }).setZoom(1); + + GuiPanel timelinePanel = new GuiPanel() + .setSize(300, 40) + .setLayout(new CustomLayout() { + @Override + protected void layout(GuiPanel container, int width, int height) { + pos(zoomButtonPanel, width - width(zoomButtonPanel), 10); + pos(timelineTime, 0, 2); + size(timelineTime, x(zoomButtonPanel), 8); + pos(timeline, 0, y(timelineTime) + height(timelineTime)); + size(timeline, x(zoomButtonPanel) - 2, 20); + pos(scrollbar, 0, y(timeline) + height(timeline) + 1); + size(scrollbar, x(zoomButtonPanel) - 2, 9); + } + }) + .addElements(null, timelineTime, timeline, scrollbar, zoomButtonPanel); + + GuiButton buttonAddSplit = new GuiButton() + .setSize(100, 20) + .setI18nLabel("replaymod.gui.edit.split") + .setTooltip(new GuiTooltip().setI18nText("replaymod.gui.edit.split.tooltip")) + .onClick(() -> { + Marker marker = new Marker(); + marker.setTime(timeline.getCursorPosition()); + marker.setName(MarkerProcessor.MARKER_NAME_SPLIT); + timeline.addMarker(marker); + }); + + GuiButton buttonInsertCut = new GuiButton() + .setSize(100, 20) + .setI18nLabel("replaymod.gui.edit.cut.start") + .setTooltip(new GuiTooltip().setI18nText("replaymod.gui.edit.cut.tooltip")) + .onClick(() -> { + Marker marker = new Marker(); + marker.setTime(timeline.getCursorPosition()); + marker.setName(MarkerProcessor.MARKER_NAME_START_CUT); + timeline.addMarker(marker); + }); + + GuiButton buttonEndCut = new GuiButton() + .setSize(100, 20) + .setI18nLabel("replaymod.gui.edit.cut.end") + .setTooltip(new GuiTooltip().setI18nText("replaymod.gui.edit.cut.tooltip")) + .onClick(() -> { + Marker marker = new Marker(); + marker.setTime(timeline.getCursorPosition()); + marker.setName(MarkerProcessor.MARKER_NAME_END_CUT); + timeline.addMarker(marker); + }); + + GuiPanel controlPanel = new GuiPanel() + .setLayout(new HorizontalLayout().setSpacing(4)) + .addElements(null, buttonAddSplit, buttonInsertCut, buttonEndCut); + + GuiButton applyButton = new GuiButton().setI18nLabel("replaymod.gui.edit.apply").setSize(150, 20).onClick(this::apply); + GuiButton closeButton = new GuiButton().setI18nLabel("replaymod.gui.close").setSize(150, 20).onClick(this::close); + GuiPanel buttonPanel = new GuiPanel() + .setLayout(new HorizontalLayout().setSpacing(8)) + .addElements(null, applyButton, closeButton); + + popup.setLayout(new VerticalLayout(VerticalLayout.Alignment.TOP).setSpacing(10)); + popup.addElements(new VerticalLayout.Data(0.5), timelinePanel, controlPanel, buttonPanel); + } + + private void zoomTimeline(double factor) { + scrollbar.setZoom(scrollbar.getZoom() * factor); + } + + private void apply() { + ProgressPopup progressPopup = new ProgressPopup(this); + + new Thread(() -> { + try (ZipReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), inputPath.toFile())) { + replayFile.writeMarkers(markers); + replayFile.save(); + } catch (IOException e) { + Utils.error(ReplayModEditor.LOGGER, this, CrashReport.makeCrashReport(e, "Writing markers"), this::close); + } + + try { + MarkerProcessor.apply(inputPath, progressPopup.progressBar::setProgress); + } catch (IOException e) { + Utils.error(ReplayModEditor.LOGGER, this, CrashReport.makeCrashReport(e, "Running marker processor"), this::close); + } + + ReplayMod.instance.runLater(() -> { + progressPopup.close(); + close(); + }); + }).start(); + } + + @Override + public void open() { + super.open(); + } + + @Override + protected GuiEditReplay getThis() { + return this; + } + + private class ProgressPopup extends AbstractGuiPopup { + private final GuiProgressBar progressBar = new GuiProgressBar(popup).setSize(300, 20); + + ProgressPopup(GuiContainer container) { + super(container); + open(); + } + + @Override + public void close() { + super.close(); + } + + @Override + protected ProgressPopup getThis() { + return this; + } + } + + private class EditTimeline extends GuiMarkerTimeline { + EditTimeline(Set markers, Consumer> saveMarkers) { + super(markers, saveMarkers); + } + + @Override + protected void drawMarkers(GuiRenderer renderer, ReadableDimension size) { + drawCutQuads(renderer, size); + super.drawMarkers(renderer, size); + } + + @Override + protected void drawMarker(GuiRenderer renderer, ReadableDimension size, Marker marker, int markerX) { + if (MarkerProcessor.MARKER_NAME_SPLIT.equals(marker.getName())) { + int height = size.getHeight() - BORDER_BOTTOM - BORDER_TOP - MARKER_SIZE + 1; + for (int y = 0; y < height; y += 3) { + renderer.drawRect(markerX, BORDER_TOP + y, 1, 2, Color.WHITE); + } + } + + super.drawMarker(renderer, size, marker, markerX); + } + + private void drawCutQuads(GuiRenderer renderer, ReadableDimension size) { + boolean inCut = false; + int startTime = 0; + for (Marker marker : markers.stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList())) { + if (MarkerProcessor.MARKER_NAME_START_CUT.equals(marker.getName()) && !inCut) { + inCut = true; + startTime = marker.getTime(); + } else if (MarkerProcessor.MARKER_NAME_END_CUT.equals(marker.getName()) && inCut) { + drawCutQuad(renderer, size, startTime, marker.getTime()); + inCut = false; + } + } + + if (inCut) { + drawCutQuad(renderer, size, startTime, getLength()); + } + } + + private void drawCutQuad(GuiRenderer renderer, ReadableDimension size, int startFrameTime, int endFrameTime) { + int visibleWidth = size.getWidth() - BORDER_LEFT - BORDER_RIGHT; + int startTime = getOffset(); + int visibleTime = (int) (getZoom() * getLength()); + int endTime = getOffset() + visibleTime; + + if (startFrameTime >= endTime || endFrameTime <= startTime) { + return; // Segment out of display range + } + + double relativeStart = startFrameTime - startTime; + double relativeEnd = endFrameTime - startTime; + int startX = BORDER_LEFT + Math.max(0, (int) (relativeStart / visibleTime * visibleWidth) + MARKER_SIZE / 2 + 1); + int endX = BORDER_LEFT + Math.min(visibleWidth, (int) (relativeEnd / visibleTime * visibleWidth) - MARKER_SIZE / 2); + if (startX < endX) { + renderer.drawRect(startX + 1, size.getHeight() - BORDER_BOTTOM - MARKER_SIZE, + endX - startX - 2, MARKER_SIZE - 2, Color.RED); + } + } + } +} diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java index a71da532..c804d575 100644 --- a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; public class MarkerProcessor { @@ -49,7 +50,7 @@ public class MarkerProcessor { } } - public static void apply(Path path) throws IOException { + public static void apply(Path path, Consumer progress) throws IOException { if (!hasWork(path)) { return; } @@ -71,6 +72,7 @@ public class MarkerProcessor { Iterator markerIterator = markers.iterator(); boolean anySplit = markers.stream().anyMatch(m -> m.getName().equals(MARKER_NAME_SPLIT)); + int inputDuration = inputReplayFile.getMetaData().getDuration(); ReplayInputStream replayInputStream = inputReplayFile.getPacketData(studio, true); int timeOffset = 0; SquashFilter cutFilter = null; @@ -142,6 +144,7 @@ public class MarkerProcessor { duration = nextPacket.getTime() - timeOffset; } nextPacket = replayInputStream.readPacket(); + progress.accept((float) nextPacket.getTime() / (float) inputDuration); } } diff --git a/src/main/java/com/replaymod/editor/handler/GuiHandler.java b/src/main/java/com/replaymod/editor/handler/GuiHandler.java index 0a673790..20e30061 100644 --- a/src/main/java/com/replaymod/editor/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/editor/handler/GuiHandler.java @@ -1,17 +1,19 @@ package com.replaymod.editor.handler; +import com.replaymod.core.utils.Utils; import com.replaymod.editor.ReplayModEditor; -import com.replaymod.editor.gui.GuiReplayEditor; +import com.replaymod.editor.gui.GuiEditReplay; +import com.replaymod.replay.gui.screen.GuiReplayViewer; +import de.johni0702.minecraft.gui.container.AbstractGuiScreen; +import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiScreen; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiMainMenu; -import net.minecraft.client.resources.I18n; +import de.johni0702.minecraft.gui.element.GuiElement; +import net.minecraft.crash.CrashReport; import net.minecraftforge.client.event.GuiScreenEvent; //#if MC>=10800 //#if MC>=11300 import net.minecraftforge.eventbus.api.SubscribeEvent; -import java.util.ArrayList; //#else //$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; //#endif @@ -19,48 +21,42 @@ import java.util.ArrayList; //$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; //#endif +import java.io.IOException; + import static com.replaymod.core.versions.MCVer.*; public class GuiHandler { - private static final int BUTTON_REPLAY_EDITOR = 17890237; - - private final ReplayModEditor mod; - - public GuiHandler(ReplayModEditor mod) { - this.mod = mod; - } - public void register() { FML_BUS.register(this); FORGE_BUS.register(this); } @SubscribeEvent - public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) { - if (!(getGui(event) instanceof GuiMainMenu)) { + public void injectIntoReplayViewer(GuiScreenEvent.InitGuiEvent.Post event) { + AbstractGuiScreen guiScreen = GuiScreen.from(getGui(event)); + if (!(guiScreen instanceof GuiReplayViewer)) { return; } - - GuiButton button = new GuiButton(BUTTON_REPLAY_EDITOR, getGui(event).width / 2 + 2, - getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayeditor")) { - //#if MC>=11300 - @Override - public void onClick(double mouseX, double mouseY) { - onButton(new GuiScreenEvent.ActionPerformedEvent.Pre(getGui(event), this, new ArrayList<>())); - } - //#endif - }; - button.width = button.width / 2 - 2; - addButton(event, button); - } - - @SubscribeEvent - public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) { - if(!getButton(event).enabled) return; - - if (getGui(event) instanceof GuiMainMenu) { - if (getButton(event).id == BUTTON_REPLAY_EDITOR) { - new GuiReplayEditor(GuiScreen.wrap(getGui(event)), mod.getCore()).display(); + final GuiReplayViewer replayViewer = (GuiReplayViewer) guiScreen; + // Inject Edit button + for (GuiElement element : replayViewer.replayButtonPanel.getChildren()) { + if (element instanceof GuiPanel && (((GuiPanel) element).getChildren().isEmpty())) { + new de.johni0702.minecraft.gui.element.GuiButton((GuiPanel) element).onClick(new Runnable() { + @Override + public void run() { + try { + new GuiEditReplay(replayViewer, replayViewer.list.getSelected().file.toPath()) { + @Override + protected void close() { + super.close(); + replayViewer.list.load(); + } + }.open(); + } catch (IOException e) { + Utils.error(ReplayModEditor.LOGGER, replayViewer, CrashReport.makeCrashReport(e, "Opening replay editor"), () -> {}); + } + } + }).setSize(73, 20).setI18nLabel("replaymod.gui.replayeditor").setDisabled(); } } } diff --git a/src/main/java/com/replaymod/online/handler/GuiHandler.java b/src/main/java/com/replaymod/online/handler/GuiHandler.java index 7ac54ca5..bbba6549 100644 --- a/src/main/java/com/replaymod/online/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/online/handler/GuiHandler.java @@ -49,8 +49,12 @@ public class GuiHandler { return; } - GuiButton button = new GuiButton(BUTTON_REPLAY_CENTER, getGui(event).width / 2 - 100, - getGui(event).height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replaycenter")) { + GuiButton button = new GuiButton( + BUTTON_REPLAY_CENTER, + getGui(event).width / 2 + 2, + getGui(event).height / 4 + 10 + 4 * 24, + I18n.format("replaymod.gui.replaycenter") + ) { //#if MC>=11300 @Override public void onClick(double mouseX, double mouseY) { @@ -58,6 +62,7 @@ public class GuiHandler { } //#endif }; + button.width = button.width / 2 - 2; addButton(event, button); } diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index 44e9851f..dc3d8371 100755 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -210,7 +210,7 @@ public class PacketListener extends ChannelInboundHandlerAdapter { replayFile.close(); if (core.getSettingsRegistry().get(Setting.AUTO_POST_PROCESS)) { - MarkerProcessor.apply(outputPath); + MarkerProcessor.apply(outputPath, progress -> {}); } } catch (IOException e) { logger.error("Saving replay file:", e); diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index e240b52d..b3cb2fc7 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -112,7 +112,7 @@ public class ReplayHandler { */ private boolean suppressCameraMovements; - private final List markers; + private Set markers; private final GuiReplayOverlay overlay; @@ -135,7 +135,7 @@ public class ReplayHandler { FML_BUS.post(new ReplayOpenEvent.Pre(this)); - markers = new ArrayList<>(replayFile.getMarkers().or(Collections.emptySet())); + markers = replayFile.getMarkers().or(Collections.emptySet()); fullReplaySender = new FullReplaySender(this, replayFile, false); //#if MC>=10904 @@ -432,26 +432,6 @@ public class ReplayHandler { this.suppressCameraMovements = suppressCameraMovements; } - /** - * Returns all markers. - * When changed, {@link #saveMarkers()} should be called to save the changes. - * @return Collection of markers in no particular order - */ - public Collection getMarkers() { - return markers; - } - - /** - * Saves all markers. - */ - public void saveMarkers() { - try { - replayFile.writeMarkers(new HashSet<>(markers)); - } catch (IOException e) { - e.printStackTrace(); - } - } - /** * Spectate the specified entity. * When the entity is {@code null} or the camera entity, the camera becomes the view entity. diff --git a/src/main/java/com/replaymod/replay/ReplayModReplay.java b/src/main/java/com/replaymod/replay/ReplayModReplay.java index 077b8f20..4ece897f 100644 --- a/src/main/java/com/replaymod/replay/ReplayModReplay.java +++ b/src/main/java/com/replaymod/replay/ReplayModReplay.java @@ -14,7 +14,6 @@ import com.replaymod.replay.camera.CameraControllerRegistry; import com.replaymod.replay.camera.CameraEntity; import com.replaymod.replay.camera.ClassicCameraController; import com.replaymod.replay.camera.VanillaCameraController; -import com.replaymod.replay.gui.overlay.GuiMarkerTimeline; import com.replaymod.replay.gui.screen.GuiModCompatWarning; import com.replaymod.replay.handler.GuiHandler; import com.replaymod.replaystudio.data.Marker; @@ -75,24 +74,12 @@ public class ReplayModReplay implements Module { marker.setYaw(camera.rotationYaw); marker.setPitch(camera.rotationPitch); marker.setRoll(camera.roll); - replayHandler.getMarkers().add(marker); - replayHandler.saveMarkers(); + replayHandler.getOverlay().timeline.addMarker(marker); } } } }); - registry.registerRaw(Keyboard.KEY_DELETE, () -> { - if (replayHandler != null) { - GuiMarkerTimeline timeline = replayHandler.getOverlay().timeline; - if (timeline.getSelectedMarker() != null) { - replayHandler.getMarkers().remove(timeline.getSelectedMarker()); - replayHandler.saveMarkers(); - timeline.setSelectedMarker(null); - } - } - }); - registry.registerKeyBinding("replaymod.input.thumbnail", Keyboard.KEY_N, new Runnable() { @Override public void run() { diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java index 3d797cf6..4b2c9f04 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java @@ -16,6 +16,8 @@ import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; //#if MC>=11300 import com.replaymod.core.versions.MCVer.Keyboard; + +import java.util.function.Consumer; //#else //$$ import org.lwjgl.input.Keyboard; //#endif @@ -25,8 +27,7 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup imp return new GuiNumberField().setSize(150, 20).setValidateOnFocusChange(true); } - private final ReplayHandler replayHandler; - private final Marker marker; + private final Consumer onSave; public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.editkeyframe.title.marker"); @@ -65,6 +66,7 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup imp public final GuiButton saveButton = new GuiButton().onClick(new Runnable() { @Override public void run() { + Marker marker = new Marker(); marker.setName(Strings.emptyToNull(nameField.getText())); marker.setTime(timeField.getInteger()); marker.setX(xField.getDouble()); @@ -73,7 +75,7 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup imp marker.setYaw(yawField.getFloat()); marker.setPitch(pitchField.getFloat()); marker.setRoll(rollField.getFloat()); - replayHandler.saveMarkers(); + onSave.accept(marker); close(); } }).setSize(150, 20).setI18nLabel("replaymod.gui.save"); @@ -89,10 +91,9 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup imp .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(7)) .addElements(new HorizontalLayout.Data(0.5), saveButton, cancelButton); - public GuiEditMarkerPopup(ReplayHandler replayHandler, GuiContainer container, Marker marker) { + public GuiEditMarkerPopup(GuiContainer container, Marker marker, Consumer onSave) { super(container); - this.replayHandler = replayHandler; - this.marker = marker; + this.onSave = onSave; setBackgroundColor(Colors.DARK_TRANSPARENT); diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java index d48343c0..0cf96d05 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiMarkerTimeline.java @@ -2,27 +2,46 @@ package com.replaymod.replay.gui.overlay; import com.replaymod.core.ReplayMod; import com.replaymod.replay.ReplayHandler; +import com.replaymod.replay.ReplayModReplay; import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.util.Location; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; import de.johni0702.minecraft.gui.element.advanced.AbstractGuiTimeline; import de.johni0702.minecraft.gui.function.Draggable; +import de.johni0702.minecraft.gui.function.Typeable; import de.johni0702.minecraft.gui.utils.lwjgl.Point; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import net.minecraft.client.resources.I18n; +//#if MC>=11300 +import com.replaymod.core.versions.MCVer.Keyboard; +//#else +//$$ import org.lwjgl.input.Keyboard; +//#endif + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + import static de.johni0702.minecraft.gui.utils.Utils.clamp; -public class GuiMarkerTimeline extends AbstractGuiTimeline implements Draggable { +public class GuiMarkerTimeline extends AbstractGuiTimeline implements Draggable, Typeable { protected static final int TEXTURE_MARKER_X = 109; protected static final int TEXTURE_MARKER_Y = 20; protected static final int TEXTURE_MARKER_SELECTED_X = 114; protected static final int TEXTURE_MARKER_SELECTED_Y = 20; protected static final int MARKER_SIZE = 5; + @Nullable private final ReplayHandler replayHandler; + private final Consumer> saveMarkers; + protected Set markers; private ReadableDimension lastSize; @@ -32,8 +51,27 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline im private boolean dragging; private long lastClickTime; - public GuiMarkerTimeline(ReplayHandler replayHandler) { + public GuiMarkerTimeline(@Nonnull ReplayHandler replayHandler) { this.replayHandler = replayHandler; + try { + this.markers = replayHandler.getReplayFile().getMarkers().or(HashSet::new); + } catch (IOException e) { + ReplayModReplay.LOGGER.error("Failed to get markers from replay", e); + this.markers = new HashSet<>(); + } + this.saveMarkers = (markers) -> { + try { + replayHandler.getReplayFile().writeMarkers(markers); + } catch (IOException e) { + ReplayModReplay.LOGGER.error("Failed to save markers to replay", e); + } + }; + } + + public GuiMarkerTimeline(Set markers, Consumer> saveMarkers) { + this.replayHandler = null; + this.markers = markers; + this.saveMarkers = saveMarkers; } @Override @@ -52,7 +90,7 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline im protected void drawMarkers(GuiRenderer renderer, ReadableDimension size) { renderer.bindTexture(ReplayMod.TEXTURE); - for (Marker marker : replayHandler.getMarkers()) { + for (Marker marker : markers) { drawMarker(renderer, size, marker); } } @@ -63,7 +101,10 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline im double positionInVisible = markerPos - getOffset(); double fractionOfVisible = positionInVisible / visibleLength; int markerX = (int) (BORDER_LEFT + fractionOfVisible * (size.getWidth() - BORDER_LEFT - BORDER_RIGHT)); + drawMarker(renderer, size, marker, markerX); + } + protected void drawMarker(GuiRenderer renderer, ReadableDimension size, Marker marker, int markerX) { int textureX, textureY; if (marker.equals(selectedMarker)) { textureX = TEXTURE_MARKER_SELECTED_X; @@ -99,7 +140,7 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline im int visibleLength = (int) (getLength() * getZoom()); int contentWidth = lastSize.getWidth() - BORDER_LEFT - BORDER_RIGHT; - for (Marker marker : replayHandler.getMarkers()) { + for (Marker marker : markers) { int markerPos = clamp(marker.getTime(), getOffset(), getOffset() + visibleLength); double positionInVisible = markerPos - getOffset(); double fractionOfVisible = positionInVisible / visibleLength; @@ -122,16 +163,24 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline im draggingStartX = position.getX(); draggingTimeDelta = marker.getTime() - getTimeAt(position.getX(), position.getY()); } else { // Double click - new GuiEditMarkerPopup(replayHandler, getContainer(), marker).open(); + new GuiEditMarkerPopup(getContainer(), marker, (updatedMarker) -> { + markers.remove(marker); + markers.add(updatedMarker); + saveMarkers.accept(markers); + }).open(); } lastClickTime = now; } else if (button == 1) { // Right click selectedMarker = null; - replayHandler.setTargetPosition(new Location( - marker.getX(), marker.getY(), marker.getZ(), - marker.getPitch(), marker.getYaw() - )); - replayHandler.doJump(marker.getTime(), false); + if (replayHandler != null) { + replayHandler.setTargetPosition(new Location( + marker.getX(), marker.getY(), marker.getZ(), + marker.getPitch(), marker.getYaw() + )); + replayHandler.doJump(marker.getTime(), false); + } else { + setCursorPosition(marker.getTime()); + } } return true; } else { @@ -164,7 +213,7 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline im mouseDrag(position, button, 0); if (dragging) { dragging = false; - replayHandler.saveMarkers(); + saveMarkers.accept(markers); return true; } } @@ -187,4 +236,19 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline im public Marker getSelectedMarker() { return selectedMarker; } + + @Override + public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) { + if (keyCode == Keyboard.KEY_DELETE && selectedMarker != null) { + markers.remove(selectedMarker); + saveMarkers.accept(markers); + return true; + } + return false; + } + + public void addMarker(Marker marker) { + markers.add(marker); + saveMarkers.accept(markers); + } } diff --git a/src/main/java/com/replaymod/replay/handler/GuiHandler.java b/src/main/java/com/replaymod/replay/handler/GuiHandler.java index 4dbbb260..1bc1bc28 100644 --- a/src/main/java/com/replaymod/replay/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/replay/handler/GuiHandler.java @@ -153,7 +153,7 @@ public class GuiHandler { return; } GuiButton button = new GuiButton(BUTTON_REPLAY_VIEWER, getGui(event).width / 2 - 100, - getGui(event).height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayviewer")) { + getGui(event).height / 4 + 10 + 4 * 24, I18n.format("replaymod.gui.replayviewer")) { //#if MC>=11300 @Override public void onClick(double mouseX, double mouseY) { diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 967405aa..fdce012f 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 967405aafad5e7613c3eb9760d8eab03ad4e1567 +Subproject commit fdce012f2cc2126596506e6d38c4d7f69166559f From 1d88ca044d9bbe6b3cf79f51192a61ecc6e271e1 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 12 Mar 2019 20:10:42 +0100 Subject: [PATCH 59/65] Add "Edit" button to replay viewer gui --- .../replaymod/editor/handler/GuiHandler.java | 30 +++++++------------ .../replaymod/online/handler/GuiHandler.java | 23 +++++--------- .../replay/gui/screen/GuiReplayViewer.java | 25 ++++++++++------ src/main/resources/assets/replaymod/lang | 2 +- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/replaymod/editor/handler/GuiHandler.java b/src/main/java/com/replaymod/editor/handler/GuiHandler.java index 20e30061..88aa69cc 100644 --- a/src/main/java/com/replaymod/editor/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/editor/handler/GuiHandler.java @@ -5,9 +5,8 @@ import com.replaymod.editor.ReplayModEditor; import com.replaymod.editor.gui.GuiEditReplay; import com.replaymod.replay.gui.screen.GuiReplayViewer; import de.johni0702.minecraft.gui.container.AbstractGuiScreen; -import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiScreen; -import de.johni0702.minecraft.gui.element.GuiElement; +import de.johni0702.minecraft.gui.element.GuiButton; import net.minecraft.crash.CrashReport; import net.minecraftforge.client.event.GuiScreenEvent; @@ -39,25 +38,18 @@ public class GuiHandler { } final GuiReplayViewer replayViewer = (GuiReplayViewer) guiScreen; // Inject Edit button - for (GuiElement element : replayViewer.replayButtonPanel.getChildren()) { - if (element instanceof GuiPanel && (((GuiPanel) element).getChildren().isEmpty())) { - new de.johni0702.minecraft.gui.element.GuiButton((GuiPanel) element).onClick(new Runnable() { + replayViewer.replaySpecificButtons.add(new GuiButton(replayViewer.editorButton).onClick(() -> { + try { + new GuiEditReplay(replayViewer, replayViewer.list.getSelected().file.toPath()) { @Override - public void run() { - try { - new GuiEditReplay(replayViewer, replayViewer.list.getSelected().file.toPath()) { - @Override - protected void close() { - super.close(); - replayViewer.list.load(); - } - }.open(); - } catch (IOException e) { - Utils.error(ReplayModEditor.LOGGER, replayViewer, CrashReport.makeCrashReport(e, "Opening replay editor"), () -> {}); - } + protected void close() { + super.close(); + replayViewer.list.load(); } - }).setSize(73, 20).setI18nLabel("replaymod.gui.replayeditor").setDisabled(); + }.open(); + } catch (IOException e) { + Utils.error(ReplayModEditor.LOGGER, replayViewer, CrashReport.makeCrashReport(e, "Opening replay editor"), () -> {}); } - } + }).setSize(73, 20).setI18nLabel("replaymod.gui.edit").setDisabled()); } } diff --git a/src/main/java/com/replaymod/online/handler/GuiHandler.java b/src/main/java/com/replaymod/online/handler/GuiHandler.java index bbba6549..165a8bbf 100644 --- a/src/main/java/com/replaymod/online/handler/GuiHandler.java +++ b/src/main/java/com/replaymod/online/handler/GuiHandler.java @@ -74,22 +74,15 @@ public class GuiHandler { } final GuiReplayViewer replayViewer = (GuiReplayViewer) guiScreen; // Inject Upload button - for (GuiElement element : replayViewer.replayButtonPanel.getChildren()) { - if (element instanceof GuiPanel && (((GuiPanel) element).getChildren().isEmpty())) { - new de.johni0702.minecraft.gui.element.GuiButton((GuiPanel) element).onClick(new Runnable() { - @Override - public void run() { - File replayFile = replayViewer.list.getSelected().file; - GuiUploadReplay uploadGui = new GuiUploadReplay(replayViewer, mod, replayFile); - if (mod.isLoggedIn()) { - uploadGui.display(); - } else { - new GuiLoginPrompt(mod.getApiClient(), replayViewer, uploadGui, true); - } - } - }).setSize(73, 20).setI18nLabel("replaymod.gui.upload").setDisabled(); + replayViewer.replaySpecificButtons.add(new de.johni0702.minecraft.gui.element.GuiButton(replayViewer.uploadButton).onClick(() -> { + File replayFile = replayViewer.list.getSelected().file; + GuiUploadReplay uploadGui = new GuiUploadReplay(replayViewer, mod, replayFile); + if (mod.isLoggedIn()) { + uploadGui.display(); + } else { + new GuiLoginPrompt(mod.getApiClient(), replayViewer, uploadGui, true); } - } + }).setSize(73, 20).setI18nLabel("replaymod.gui.upload").setDisabled()); } @SubscribeEvent diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index c24b4607..7243e9d1 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -55,7 +55,10 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; +import java.util.List; import static com.replaymod.replay.ReplayModReplay.LOGGER; import static com.replaymod.core.versions.MCVer.*; @@ -66,7 +69,7 @@ public class GuiReplayViewer extends GuiScreen { public final GuiReplayList list = new GuiReplayList(this).onSelectionChanged(new Runnable() { @Override public void run() { - replayButtonPanel.forEach(IGuiButton.class).setEnabled(list.getSelected() != null); + replaySpecificButtons.forEach(b -> b.setEnabled(list.getSelected() != null)); if (list.getSelected() != null && list.getSelected().incompatible) { loadButton.setDisabled(); } @@ -88,7 +91,7 @@ public class GuiReplayViewer extends GuiScreen { e.printStackTrace(); } } - }).setSize(73, 20).setI18nLabel("replaymod.gui.load").setDisabled(); + }).setSize(150, 20).setI18nLabel("replaymod.gui.load").setDisabled(); public final GuiButton folderButton = new GuiButton().onClick(new Runnable() { @Override @@ -199,13 +202,17 @@ public class GuiReplayViewer extends GuiScreen { } }).setSize(73, 20).setI18nLabel("replaymod.gui.cancel"); - public final GuiPanel replayButtonPanel = new GuiPanel().setLayout(new GridLayout().setSpacingX(5).setSpacingY(5) - .setColumns(2)).addElements(null, loadButton, new GuiPanel() /* Upload */, renameButton, deleteButton); - public final GuiPanel generalButtonPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(5)) - .addElements(null, folderButton, new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)) - .addElements(null, settingsButton, cancelButton)); - public final GuiPanel buttonPanel = new GuiPanel(this).setLayout(new HorizontalLayout().setSpacing(6)) - .addElements(null, replayButtonPanel, generalButtonPanel); + public final List replaySpecificButtons = new ArrayList<>(); + { replaySpecificButtons.addAll(Arrays.asList(loadButton, renameButton, deleteButton)); } + public final GuiPanel uploadButton = new GuiPanel(); + public final GuiPanel editorButton = new GuiPanel(); + + public final GuiPanel upperButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)) + .addElements(null, loadButton, editorButton, uploadButton); + public final GuiPanel lowerButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)) + .addElements(null, renameButton, deleteButton, settingsButton, cancelButton); + public final GuiPanel buttonPanel = new GuiPanel(this).setLayout(new VerticalLayout().setSpacing(5)) + .addElements(null, upperButtonPanel, lowerButtonPanel); public GuiReplayViewer(ReplayModReplay mod) { this.mod = mod; diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index fdce012f..37de62cf 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit fdce012f2cc2126596506e6d38c4d7f69166559f +Subproject commit 37de62cf8edae480b2a78cb6f5ed307aec351a69 From 681e803d44907ed9db89be3ef1bbaebcbd21abbd Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 13 Mar 2019 11:46:12 +0100 Subject: [PATCH 60/65] Update ReplayStudio for custom entity attribs and binary tracker cache --- versions/common.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions/common.gradle b/versions/common.gradle index 343feb07..33da2fe2 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -181,7 +181,7 @@ dependencies { def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' - shade "com.github.ReplayMod.ReplayStudio:$studioVersion:e0562a30", shadeExclusions + shade "com.github.ReplayMod.ReplayStudio:$studioVersion:155c0f6", shadeExclusions def jGuiVersion = project.name if (['1.10.2', '1.11', '1.11.2'].contains(jGuiVersion)) jGuiVersion = '1.9.4' From 1db94b78717b0a6d3fedfc60c74256294e576f09 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 13 Mar 2019 11:50:18 +0100 Subject: [PATCH 61/65] Fix timestamp of squashed packets in MarkerProcessor --- src/main/java/com/replaymod/editor/gui/MarkerProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java index c804d575..9224d4e5 100644 --- a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -98,7 +98,7 @@ public class MarkerProcessor { timeOffset ); for (PacketData packet : packets) { - replayOutputStream.write(packet.getTime() - timeOffset, packet.getPacket()); + replayOutputStream.write(0, packet.getPacket()); } } @@ -116,7 +116,7 @@ public class MarkerProcessor { nextMarker.getTime() ); for (PacketData packet : packets) { - replayOutputStream.write(packet.getTime() - timeOffset, packet.getPacket()); + replayOutputStream.write(nextMarker.getTime() - timeOffset, packet.getPacket()); } cutFilter = null; } From 2d7696b09e14e326676c9a8efcd536a67bb4ad7d Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 13 Mar 2019 12:06:35 +0100 Subject: [PATCH 62/65] Fix NPE in MarkerProcessor when done --- src/main/java/com/replaymod/editor/gui/MarkerProcessor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java index 9224d4e5..12efe758 100644 --- a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -144,7 +144,11 @@ public class MarkerProcessor { duration = nextPacket.getTime() - timeOffset; } nextPacket = replayInputStream.readPacket(); - progress.accept((float) nextPacket.getTime() / (float) inputDuration); + if (nextPacket != null) { + progress.accept((float) nextPacket.getTime() / (float) inputDuration); + } else { + progress.accept(1f); + } } } From d7f2cba2140e23b275b3cae54d77c4b405b20b7d Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 13 Mar 2019 13:55:28 +0100 Subject: [PATCH 63/65] Update ReplayStudio to fix SquashFilter as used by MarkerProcessor --- versions/common.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions/common.gradle b/versions/common.gradle index 33da2fe2..d17913ac 100644 --- a/versions/common.gradle +++ b/versions/common.gradle @@ -181,7 +181,7 @@ dependencies { def studioVersion = mcVersionStr if (studioVersion == '1.8.9') studioVersion = '1.8' - shade "com.github.ReplayMod.ReplayStudio:$studioVersion:155c0f6", shadeExclusions + shade "com.github.ReplayMod.ReplayStudio:$studioVersion:c41d016", shadeExclusions def jGuiVersion = project.name if (['1.10.2', '1.11', '1.11.2'].contains(jGuiVersion)) jGuiVersion = '1.9.4' From 27efc3c717386b56eef17635e369b2fbd8102238 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 13 Mar 2019 14:52:11 +0100 Subject: [PATCH 64/65] Fix pre-1.13 imports --- jGui | 2 +- .../com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/jGui b/jGui index 63aec9fd..79b0aaf3 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 63aec9fd9a7acf4dad8b128ee86e70ea05fa4f5c +Subproject commit 79b0aaf3e9b5c70739c30a9ce1af9efa8adb26dc diff --git a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java index 4b2c9f04..12332b05 100644 --- a/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java +++ b/src/main/java/com/replaymod/replay/gui/overlay/GuiEditMarkerPopup.java @@ -1,7 +1,6 @@ package com.replaymod.replay.gui.overlay; import com.google.common.base.Strings; -import com.replaymod.replay.ReplayHandler; import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.element.*; @@ -16,12 +15,12 @@ import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; //#if MC>=11300 import com.replaymod.core.versions.MCVer.Keyboard; - -import java.util.function.Consumer; //#else //$$ import org.lwjgl.input.Keyboard; //#endif +import java.util.function.Consumer; + public class GuiEditMarkerPopup extends AbstractGuiPopup implements Typeable { private static GuiNumberField newGuiNumberField() { return new GuiNumberField().setSize(150, 20).setValidateOnFocusChange(true); From 443c1ca2af93846e68971728b6051a51a40d3c61 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 13 Mar 2019 15:27:53 +0100 Subject: [PATCH 65/65] Render to BGRA instead of RGB format for better performance on most GPUs --- .../com/replaymod/render/RenderSettings.java | 2 +- .../MultiFramePboOpenGlFrameCapturer.java | 21 +++++++------------ .../render/capturer/OpenGlFrameCapturer.java | 17 +++++---------- .../SimplePboOpenGlFrameCapturer.java | 17 +++++---------- .../com/replaymod/render/frame/RGBFrame.java | 4 ++-- .../render/processor/CubicToRGBProcessor.java | 2 +- .../EquirectangularToRGBProcessor.java | 6 +++--- .../render/processor/ODSToRGBProcessor.java | 2 +- .../processor/OpenGlToRGBProcessor.java | 2 +- .../processor/StereoscopicToRGBProcessor.java | 2 +- .../com/replaymod/render/utils/Utils.java | 6 +++--- 11 files changed, 30 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index 917fc9bb..a675fecd 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -72,7 +72,7 @@ public class RenderSettings { } public String getValue() { - return "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - %FILTERS%" + preset; + return "-y -f rawvideo -pix_fmt bgra -s %WIDTH%x%HEIGHT% -r %FPS% -i - %FILTERS%" + preset; } public String getFileExtension() { diff --git a/src/main/java/com/replaymod/render/capturer/MultiFramePboOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/MultiFramePboOpenGlFrameCapturer.java index 55eccf01..2af60ffe 100644 --- a/src/main/java/com/replaymod/render/capturer/MultiFramePboOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/MultiFramePboOpenGlFrameCapturer.java @@ -4,8 +4,8 @@ import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.rendering.Frame; import com.replaymod.render.utils.ByteBufferPool; import com.replaymod.render.utils.PixelBufferObject; -import net.minecraft.client.renderer.OpenGlHelper; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; import java.io.IOException; import java.nio.ByteBuffer; @@ -19,7 +19,7 @@ public abstract class MultiFramePboOpenGlFrameCapturer