Merge branch 1.9.4 into 1.10.2
0aed9c3Merge branch 1.8.9 into 1.9.4f36ebf1Merge branch 1.8 into 1.8.908170b3Downgrade FG to 2.1 because FG 2.2 is 1.9+ only41d2547Update translations00e999fFix replay not being closed when opened via URI scheme handler (fixes #92)767ea29Fix book gui not being suppressed during replay (fixes #90)5b04edbHide ReplayMod app entry from menus on Linux6aff99dFix livelock during netty write to embedded channel (fixes #85)703805fFix infinite loop in mc.scheduledTasks (fixes #86)7a4440cUpdate ReplayStudio0b9c56cFix resource packs not working after first time jumping back in time933ee5f[Compat] Fix camera entity with BetterSprinting prior to 2.0.0 (fixes #78)83b1090Fix hotbar being visible while spectating player (fixes #83)5c73117[Compat] Fix invisible entities with Orange's 1.7 Animations (fixes #78)4704b29Replace the RenderPlayer hook (used subclassing) with a mixin (fixes #79)0c226b0Fix path at end of replay resulting in constant reloading (fixes #56)1b9b13eRename render success sound to all lowercase (required for 1.11) (fixes #66)c2000b3Fix only front-facing chunks visible for ODS rendering (fixes #67)aafeeccFix initial login gui not closing on success (fixes #68)9292addUse percent-encoding for replay file names (fixes #71)8499c0fFix name of invis armor stand with CustomNameVisible not rendering (fixes #72)b9ea572Try to handle invalid ffmpeg arguments more gracefully (fixes #77)a2f8c88Fix compression packets being recorded (fixes #80)19629c3Replace usages of System.out with loggerfe1d9b8Stop Forge from allowing the mod to load on other MC versions (fixes #82)1a1e96cFix server with RM installed refusing clients without RM (fixes #76)35eb9caReplace usage of FMLLog with the mod logger
This commit is contained in:
@@ -124,6 +124,8 @@ public class ReplayHandler {
|
||||
|
||||
ReplayModReplay.instance.replayHandler = null;
|
||||
|
||||
mc.displayGuiScreen(null);
|
||||
|
||||
MinecraftForge.EVENT_BUS.post(new ReplayCloseEvent.Post(this));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.Optional;
|
||||
@Mod(modid = ReplayModReplay.MOD_ID,
|
||||
version = "@MOD_VERSION@",
|
||||
acceptedMinecraftVersions = "@MC_VERSION@",
|
||||
acceptableRemoteVersions = "*",
|
||||
useMetadata = true)
|
||||
public class ReplayModReplay {
|
||||
public static final String MOD_ID = "replaymod-replay";
|
||||
@@ -46,7 +47,7 @@ public class ReplayModReplay {
|
||||
|
||||
private final CameraControllerRegistry cameraControllerRegistry = new CameraControllerRegistry();
|
||||
|
||||
private Logger logger;
|
||||
public static Logger LOGGER;
|
||||
|
||||
protected ReplayHandler replayHandler;
|
||||
|
||||
@@ -56,7 +57,7 @@ public class ReplayModReplay {
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
logger = event.getModLog();
|
||||
LOGGER = event.getModLog();
|
||||
core = ReplayMod.instance;
|
||||
|
||||
core.getSettingsRegistry().register(Setting.class);
|
||||
@@ -180,6 +181,9 @@ public class ReplayModReplay {
|
||||
}
|
||||
|
||||
public void startReplay(ReplayFile replayFile, boolean checkModCompat) throws IOException {
|
||||
if (replayHandler != null) {
|
||||
replayHandler.endReplay();
|
||||
}
|
||||
if (checkModCompat) {
|
||||
ModCompat.ModInfoDifference modDifference = new ModCompat.ModInfoDifference(replayFile.getModInfo());
|
||||
if (!modDifference.getMissing().isEmpty() || !modDifference.getDiffering().isEmpty()) {
|
||||
@@ -195,7 +199,7 @@ public class ReplayModReplay {
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
return LOGGER;
|
||||
}
|
||||
|
||||
public CameraControllerRegistry getCameraControllerRegistry() {
|
||||
|
||||
@@ -2,15 +2,16 @@ package com.replaymod.replay;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.core.utils.Restrictions;
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
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.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityOtherPlayerMP;
|
||||
import net.minecraft.client.gui.GuiDownloadTerrain;
|
||||
@@ -35,7 +36,6 @@ import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Sends replay packets to netty channels.
|
||||
@@ -43,7 +43,7 @@ import java.util.concurrent.Callable;
|
||||
* the replay restart from the beginning.
|
||||
*/
|
||||
@Sharable
|
||||
public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
public class ReplaySender extends ChannelDuplexHandler {
|
||||
/**
|
||||
* These packets are ignored completely during replay.
|
||||
*/
|
||||
@@ -242,6 +242,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
try {
|
||||
channelInactive(ctx);
|
||||
ctx.channel().pipeline().close();
|
||||
FileUtils.deleteDirectory(tempResourcePackFolder);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -383,6 +384,13 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
|
||||
if(BAD_PACKETS.contains(p.getClass())) return null;
|
||||
|
||||
if (p instanceof SPacketCustomPayload) {
|
||||
SPacketCustomPayload packet = (SPacketCustomPayload) p;
|
||||
if ("MC|BOpen".equals(packet.getChannelName())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if(p instanceof SPacketResourcePackSend) {
|
||||
SPacketResourcePackSend packet = (SPacketResourcePackSend) p;
|
||||
String url = packet.getURL();
|
||||
@@ -456,22 +464,19 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
new Callable<Void>() {
|
||||
new Runnable() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Void call() {
|
||||
public void run() {
|
||||
if (mc.theWorld == null || !mc.isCallingFromMinecraftThread()) {
|
||||
synchronized(mc.scheduledTasks) {
|
||||
mc.scheduledTasks.add(ListenableFutureTask.create(this));
|
||||
}
|
||||
return null;
|
||||
ReplayMod.instance.runLater(this);
|
||||
return;
|
||||
}
|
||||
|
||||
CameraEntity cent = replayHandler.getCameraEntity();
|
||||
cent.setCameraPosition(ppl.getX(), ppl.getY(), ppl.getZ());
|
||||
return null;
|
||||
}
|
||||
}.call();
|
||||
}.run();
|
||||
}
|
||||
|
||||
if(p instanceof SPacketChangeGameState) {
|
||||
@@ -508,9 +513,22 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
FileUtils.deleteDirectory(tempResourcePackFolder);
|
||||
super.channelInactive(ctx);
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -738,6 +756,9 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.replaymod.core.events.SettingsChangedEvent;
|
||||
import com.replaymod.core.utils.Utils;
|
||||
import com.replaymod.replay.ReplayModReplay;
|
||||
import com.replaymod.replay.Setting;
|
||||
import com.replaymod.replay.events.ReplayChatMessageEvent;
|
||||
import com.replaymod.replaystudio.util.Location;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -24,6 +25,7 @@ import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.event.EntityViewRenderEvent;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
@@ -384,6 +386,12 @@ public class CameraEntity extends EntityPlayerSP {
|
||||
&& (e instanceof EntityPlayer || e instanceof EntityLiving || e instanceof EntityItemFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChatMessage(ITextComponent message) {
|
||||
if (MinecraftForge.EVENT_BUS.post(new ReplayChatMessageEvent(this))) return;
|
||||
super.addChatMessage(message);
|
||||
}
|
||||
|
||||
private class EventHandler {
|
||||
@SubscribeEvent
|
||||
public void onPreClientTick(TickEvent.ClientTickEvent event) {
|
||||
@@ -460,5 +468,45 @@ public class CameraEntity extends EntityPlayerSP {
|
||||
event.setRoll(roll);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean heldItemTooltipsWasTrue;
|
||||
|
||||
@SubscribeEvent
|
||||
public void preRenderGameOverlay(RenderGameOverlayEvent.Pre event) {
|
||||
switch (event.getType()) {
|
||||
case ALL:
|
||||
heldItemTooltipsWasTrue = mc.gameSettings.heldItemTooltips;
|
||||
mc.gameSettings.heldItemTooltips = false;
|
||||
break;
|
||||
case ARMOR:
|
||||
case HEALTH:
|
||||
case FOOD:
|
||||
case AIR:
|
||||
case HOTBAR:
|
||||
case EXPERIENCE:
|
||||
case HEALTHMOUNT:
|
||||
case JUMPBAR:
|
||||
case POTION_ICONS:
|
||||
event.setCanceled(true);
|
||||
break;
|
||||
case HELMET:
|
||||
case PORTAL:
|
||||
case CROSSHAIRS:
|
||||
case BOSSHEALTH:
|
||||
case BOSSINFO:
|
||||
case SUBTITLES:
|
||||
case TEXT:
|
||||
case CHAT:
|
||||
case PLAYER_LIST:
|
||||
case DEBUG:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void postRenderGameOverlay(RenderGameOverlayEvent.Post event) {
|
||||
if (event.getType() != RenderGameOverlayEvent.ElementType.ALL) return;
|
||||
mc.gameSettings.heldItemTooltips = heldItemTooltipsWasTrue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.replaymod.replay.events;
|
||||
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraftforge.fml.common.eventhandler.Cancelable;
|
||||
import net.minecraftforge.fml.common.eventhandler.Event;
|
||||
|
||||
@Cancelable
|
||||
@RequiredArgsConstructor
|
||||
public class ReplayChatMessageEvent extends Event {
|
||||
@Getter
|
||||
private final CameraEntity cameraEntity;
|
||||
}
|
||||
@@ -28,9 +28,7 @@ import de.johni0702.minecraft.gui.utils.Consumer;
|
||||
import net.minecraft.client.gui.GuiErrorScreen;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOCase;
|
||||
import org.apache.commons.io.filefilter.SuffixFileFilter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -47,6 +45,8 @@ import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.replaymod.replay.ReplayModReplay.LOGGER;
|
||||
|
||||
public class GuiReplayViewer extends GuiScreen {
|
||||
private final ReplayModReplay mod;
|
||||
|
||||
@@ -91,7 +91,7 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
obj.consume(() -> new GuiReplayEntry(file, metaData, theThumb));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FMLLog.getLogger().error("Could not load Replay File " + file.getName(), e);
|
||||
LOGGER.error("Could not load Replay File {}", file.getName(), e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -155,7 +155,7 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
@Override
|
||||
public void run() {
|
||||
final File file = list.getSelected().file;
|
||||
String name = FilenameUtils.getBaseName(file.getName());
|
||||
String name = Utils.fileNameToReplayName(file.getName());
|
||||
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name);
|
||||
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this,
|
||||
new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK),
|
||||
@@ -171,16 +171,16 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
}
|
||||
}).onTextChanged(obj -> {
|
||||
popup.getYesButton().setEnabled(!nameField.getText().isEmpty()
|
||||
&& !new File(file.getParentFile(), nameField.getText() + ".mcpr").exists());
|
||||
&& !new File(file.getParentFile(), Utils.replayNameToFileName(nameField.getText())).exists());
|
||||
});
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean delete) {
|
||||
if (delete) {
|
||||
// Sanitize their input
|
||||
String name = nameField.getText().trim().replace("[^a-zA-Z0-9\\.\\- ]", "_");
|
||||
String name = nameField.getText().trim();
|
||||
// This file is what they want
|
||||
File targetFile = new File(file.getParentFile(), name + ".mcpr");
|
||||
File targetFile = new File(file.getParentFile(), Utils.replayNameToFileName(name));
|
||||
try {
|
||||
// Finally, try to move it
|
||||
FileUtils.moveFile(file, targetFile);
|
||||
@@ -302,7 +302,7 @@ public class GuiReplayViewer extends GuiScreen {
|
||||
public GuiReplayEntry(File file, ReplayMetaData metaData, BufferedImage thumbImage) {
|
||||
this.file = file;
|
||||
|
||||
name.setText(ChatFormatting.UNDERLINE + FilenameUtils.getBaseName(file.getName()));
|
||||
name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(file.getName()));
|
||||
if (Strings.isEmpty(metaData.getServerName())) {
|
||||
server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED);
|
||||
} else {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.replaymod.replay.mixin;
|
||||
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.RenderArmorStand;
|
||||
import net.minecraft.entity.item.EntityArmorStand;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(RenderArmorStand.class)
|
||||
public abstract class MixinRenderArmorStand {
|
||||
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
|
||||
private void replayModReplay_canRenderInvisibleName(EntityArmorStand entity, CallbackInfoReturnable<Boolean> ci) {
|
||||
EntityPlayer thePlayer = Minecraft.getMinecraft().thePlayer;
|
||||
if (thePlayer instanceof CameraEntity && entity.isInvisible()) {
|
||||
ci.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user