From d5a8b5e18d2749f848bc0113ba3f42112e65c285 Mon Sep 17 00:00:00 2001
From: Unknown <lemadec.fr@gmail.com>
Date: Sat, 31 Aug 2019 13:52:43 +0200
Subject: [PATCH] Improved world border usability

- use action bar to reduce chat spamming
- inform player when cancelling nether portal opening
- render the celestia object border using vanilla renderer
---
 .../cr0s/warpdrive/core/ClassTransformer.java |  67 +++++++++++
 .../cr0s/warpdrive/data/CelestialBorder.java  | 113 ++++++++++++++++++
 .../cr0s/warpdrive/data/CelestialObject.java  |   9 ++
 .../data/CelestialObjectManager.java          |  36 ++++++
 .../cr0s/warpdrive/event/LivingHandler.java   |  12 +-
 .../assets/warpdrive/lang/de_de.lang          |   1 +
 .../assets/warpdrive/lang/en_us.lang          |   1 +
 .../assets/warpdrive/lang/fr_fr.lang          |   7 +-
 .../assets/warpdrive/lang/nl_nl.lang          |   1 +
 .../assets/warpdrive/lang/ru_ru.lang          |   1 +
 .../assets/warpdrive/lang/zh_cn.lang          |   1 +
 .../assets/warpdrive/lang/zh_tw.lang          |   1 +
 12 files changed, 241 insertions(+), 9 deletions(-)
 create mode 100644 src/main/java/cr0s/warpdrive/data/CelestialBorder.java

diff --git a/src/main/java/cr0s/warpdrive/core/ClassTransformer.java b/src/main/java/cr0s/warpdrive/core/ClassTransformer.java
index 075f5bc0..d7f6fb23 100644
--- a/src/main/java/cr0s/warpdrive/core/ClassTransformer.java
+++ b/src/main/java/cr0s/warpdrive/core/ClassTransformer.java
@@ -36,6 +36,7 @@ public class ClassTransformer implements net.minecraft.launchwrapper.IClassTrans
 	
 	private static final String GRAVITY_MANAGER_CLASS = "cr0s/warpdrive/data/GravityManager";
 	private static final String CLOAK_MANAGER_CLASS = "cr0s/warpdrive/data/CloakManager";
+	private static final String CELESTIAL_OBJECT_MANAGER_CLASS = "cr0s/warpdrive/data/CelestialObjectManager";
 	
 	private static final boolean debugLog = false;
 	private static final String ASM_DUMP_BEFORE = "asm/warpdrive.before";
@@ -69,6 +70,12 @@ public class ClassTransformer implements net.minecraft.launchwrapper.IClassTrans
 		nodeMap.put("ForgeHooks.class", "ForgeHooks");
 		nodeMap.put("loadAdvancements.name", "lambda$loadAdvancements$0");
 		nodeMap.put("loadAdvancements.desc", "(Lnet/minecraftforge/fml/common/ModContainer;Ljava/util/Map;Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/lang/Boolean;");
+		
+		nodeMap.put("RenderGlobal.class", "buw");
+		nodeMap.put("renderWorldBorder.name", "func_180449_a");
+		nodeMap.put("renderWorldBorder.desc", "(Lnet/minecraft/entity/Entity;F)V");
+		nodeMap.put("getWorldBorder.name", "func_175723_af");
+		nodeMap.put("getWorldBorder.desc", "()Lnet/minecraft/world/border/WorldBorder;");
 	}
 	
 	@Override
@@ -119,6 +126,10 @@ public class ClassTransformer implements net.minecraft.launchwrapper.IClassTrans
 			bytesNew = transformMinecraftForgeHooks(bytesOld);
 			break;
 			
+		case "net.minecraft.client.renderer.RenderGlobal":
+			bytesNew = transformMinecraftRenderGlobal(bytesOld);
+			break;
+			
 		default:
 			bytesNew = null;
 		}
@@ -743,6 +754,62 @@ public class ClassTransformer implements net.minecraft.launchwrapper.IClassTrans
 		return bytesNew;
 	}
 	
+	private byte[] transformMinecraftRenderGlobal(@Nonnull final byte[] bytes) {
+		final ClassNode classNode = new ClassNode();
+		final ClassReader classReader = new ClassReader(bytes);
+		classReader.accept(classNode, 0);
+		
+		final int countExpected = 1;
+		int countTransformed = 0;
+		for (final MethodNode methodNode : classNode.methods) {
+			// if (debugLog) { FMLLoadingPlugin.logger.info(String.format("- Method %s %s", methodNode.name, methodNode.desc)); }
+			
+			if ( (methodNode.name.equals(nodeMap.get("renderWorldBorder.name")) || methodNode.name.equals("renderWorldBorder"))
+			  && methodNode.desc.equals(nodeMap.get("renderWorldBorder.desc")) ) {
+				FMLLoadingPlugin.logger.debug(String.format("Found method to transform: %s %s",
+				                                            methodNode.name, methodNode.desc));
+				
+				int indexInstruction = 0;
+				
+				while (indexInstruction < methodNode.instructions.size()) {
+					final AbstractInsnNode abstractNode = methodNode.instructions.get(indexInstruction);
+					if (debugLog) { decompile(abstractNode); }
+					
+					// change    WorldBorder worldborder = this.field_72769_h.func_175723_af();
+					// into      WorldBorder worldborder = CelestiaObjectManager.getWorldBorder(world);
+					if (abstractNode instanceof MethodInsnNode) {
+						final MethodInsnNode nodeAt = (MethodInsnNode) abstractNode;
+						
+						if (nodeAt.name.equals(nodeMap.get("getWorldBorder.name")) || nodeAt.name.equals("getWorldBorder")) {
+							final MethodInsnNode overwriteNode = new MethodInsnNode(
+									Opcodes.INVOKESTATIC,
+									CELESTIAL_OBJECT_MANAGER_CLASS,
+									"World_getWorldBorder",
+									"(Lnet/minecraft/world/World;)Lnet/minecraft/world/border/WorldBorder;",
+									false);
+							methodNode.instructions.set(nodeAt, overwriteNode);
+							if (debugLog) { FMLLoadingPlugin.logger.info(String.format("Injecting into %s.%s %s", classNode.name, methodNode.name, methodNode.desc)); }
+							countTransformed++;
+						}
+					}
+					
+					indexInstruction++;
+				}
+			}
+		}
+		
+		if (countTransformed != countExpected) {
+			FMLLoadingPlugin.logger.error(String.format("Transformation failed for %s (%d/%d), aborting...", classNode.name, countTransformed, countExpected));
+			return bytes;
+		}
+		
+		final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); // | ClassWriter.COMPUTE_FRAMES);
+		classNode.accept(writer);
+		final byte[] bytesNew = writer.toByteArray();
+		FMLLoadingPlugin.logger.info(String.format("Successful injection in %s", classNode.name));
+		return bytesNew;
+	}
+	
 	private void removeInstruction(@Nonnull final MethodNode methodNode, final int indexInstruction) {
 		final AbstractInsnNode abstractNodeToRemove = methodNode.instructions.get(indexInstruction);
 		if (debugLog) {
diff --git a/src/main/java/cr0s/warpdrive/data/CelestialBorder.java b/src/main/java/cr0s/warpdrive/data/CelestialBorder.java
new file mode 100644
index 00000000..27d4182d
--- /dev/null
+++ b/src/main/java/cr0s/warpdrive/data/CelestialBorder.java
@@ -0,0 +1,113 @@
+package cr0s.warpdrive.data;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.world.border.EnumBorderStatus;
+import net.minecraft.world.border.WorldBorder;
+
+
+/**
+ * An overloaded world border to allow rectangular shapes.
+ *
+ * @author LemADEC
+ */
+public class CelestialBorder extends WorldBorder {
+	
+	public int centerX, centerZ;
+	public int radiusX, radiusZ;
+	
+	public CelestialBorder(final int centerX, final int centerZ,
+	                       final int radiusX, final int radiusZ) {
+		this.centerX = centerX;
+		this.centerZ = centerZ;
+		this.radiusX = radiusX;
+		this.radiusZ = radiusZ;
+	}
+	
+	@Nonnull
+	@Override
+	public EnumBorderStatus getStatus() {
+		return EnumBorderStatus.STATIONARY;
+	}
+	
+	@Override
+	public double minX() {
+		return centerX - radiusX;
+	}
+	
+	@Override
+	public double minZ() {
+		return centerZ - radiusZ;
+	}
+	
+	@Override
+	public double maxX() {
+		return centerX + radiusX;
+	}
+	
+	@Override
+	public double maxZ() {
+		return centerZ + radiusZ;
+	}
+	
+	@Override
+	public double getCenterX()
+	{
+		return centerX;
+	}
+	
+	@Override
+	public double getCenterZ()
+	{
+		return centerZ;
+	}
+	
+	@Override
+	public void setCenter(final double x, final double z) {
+		assert false;
+	}
+	
+	@Override
+	public double getDiameter() {
+		assert false;
+		return super.getDiameter();
+	}
+	
+	@Override
+	public long getTimeUntilTarget() {
+		assert false;
+		return 0L;
+	}
+	
+	@Override
+	public double getTargetSize() {
+		return 2 * Math.max(radiusX, radiusZ);
+	}
+	
+	@Override
+	public void setTransition(final double newSize) {
+		super.setTransition(newSize);
+	}
+	
+	@Override
+	public void setTransition(final double oldSize, final double newSize, final long time) {
+		super.setTransition(oldSize, newSize, time);
+	}
+	
+	@Override
+	public void setSize(final int size) {
+		// no operation
+	}
+	
+	@Override
+	public int getSize() {
+		return 2 * Math.max(radiusX, radiusZ);
+	}
+	
+	@Override
+	public String toString() {
+		return String.format("CelestialBorder [@ %d %d Border(%d %d)]",
+		                     centerX, centerZ,
+		                     2 * radiusX, 2 * radiusZ );
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/cr0s/warpdrive/data/CelestialObject.java b/src/main/java/cr0s/warpdrive/data/CelestialObject.java
index 497087c0..2e61cd30 100644
--- a/src/main/java/cr0s/warpdrive/data/CelestialObject.java
+++ b/src/main/java/cr0s/warpdrive/data/CelestialObject.java
@@ -24,6 +24,7 @@ import net.minecraft.nbt.NBTTagList;
 import net.minecraft.util.IStringSerializable;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.math.AxisAlignedBB;
+import net.minecraft.world.border.WorldBorder;
 
 import net.minecraftforge.common.util.Constants.NBT;
 
@@ -81,6 +82,7 @@ public class CelestialObject implements Cloneable, IStringSerializable, ICelesti
 	public CelestialObject parent;
 	private boolean isHyperspace;
 	
+	private WorldBorder cache_worldBorder;
 	private AxisAlignedBB cache_aabbWorldBorder;
 	private AxisAlignedBB cache_aabbAreaToReachParent;
 	private AxisAlignedBB cache_aabbAreaInParent;
@@ -485,6 +487,13 @@ public class CelestialObject implements Cloneable, IStringSerializable, ICelesti
 		return new VectorI(dimensionCenterX - parentCenterX, 0, dimensionCenterZ - parentCenterZ);
 	}
 	
+	public WorldBorder getWorldBorder() {
+		if (cache_worldBorder == null) {
+			cache_worldBorder = new CelestialBorder(dimensionCenterX, dimensionCenterZ, borderRadiusX, borderRadiusZ);
+		}
+		return cache_worldBorder;
+	}
+	
 	@Override
 	public AxisAlignedBB getWorldBorderArea() {
 		if (cache_aabbWorldBorder == null) {
diff --git a/src/main/java/cr0s/warpdrive/data/CelestialObjectManager.java b/src/main/java/cr0s/warpdrive/data/CelestialObjectManager.java
index 275adbb5..f9cd60bd 100644
--- a/src/main/java/cr0s/warpdrive/data/CelestialObjectManager.java
+++ b/src/main/java/cr0s/warpdrive/data/CelestialObjectManager.java
@@ -2,11 +2,14 @@ package cr0s.warpdrive.data;
 
 import cr0s.warpdrive.Commons;
 import cr0s.warpdrive.WarpDrive;
+import cr0s.warpdrive.api.WarpDriveText;
 import cr0s.warpdrive.config.InvalidXmlException;
 import cr0s.warpdrive.config.XmlFileManager;
 
 import javax.annotation.Nonnull;
 
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.util.math.AxisAlignedBB;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
@@ -22,6 +25,7 @@ import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.nbt.NBTTagList;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
+import net.minecraft.world.border.WorldBorder;
 
 import net.minecraftforge.common.DimensionManager;
 
@@ -172,6 +176,14 @@ public class CelestialObjectManager extends XmlFileManager {
 		final CelestialObject celestialObjectPortal = get(world, blockPos.getX(), blockPos.getZ());
 		if (celestialObjectPortal != null) {
 			if (!celestialObjectPortal.isInsideBorder(blockPos.getX(), blockPos.getZ()) ) {
+				final EntityPlayer entityPlayer = world.getClosestPlayer(blockPos.getX(), blockPos.getY(), blockPos.getZ(), 10.0D, false);
+				if (entityPlayer != null) {
+					entityPlayer.sendStatusMessage(
+							new WarpDriveText(Commons.getStyleWarning(), "warpdrive.world_border.portal_denied"), true);
+				}
+				WarpDrive.logger.info(String.format("Nether portal opening cancelled %s for player %s: portal entry is outside the world border",
+				                                    entityPlayer == null ? "-null-" : entityPlayer.getName(),
+				                                    Commons.format(world, blockPos) ));
 				return false;
 			}
 			
@@ -187,6 +199,14 @@ public class CelestialObjectManager extends XmlFileManager {
 			final int zExit = (int) Math.floor(blockPos.getZ() * factor);
 			if ( Math.abs(xExit - celestialObjectExit.dimensionCenterX) > celestialObjectExit.borderRadiusX
 			  || Math.abs(zExit - celestialObjectExit.dimensionCenterZ) > celestialObjectExit.borderRadiusZ ) {
+				final EntityPlayer entityPlayer = world.getClosestPlayer(blockPos.getX(), blockPos.getY(), blockPos.getZ(), 10.0D, false);
+				if (entityPlayer != null) {
+					entityPlayer.sendStatusMessage(
+							new WarpDriveText(Commons.getStyleWarning(), "warpdrive.world_border.portal_denied"), true);
+				}
+				WarpDrive.logger.info(String.format("Nether portal opening cancelled for player %s %s: portal exit is outside the world border",
+				                                    entityPlayer == null ? "-null-" : entityPlayer.getName(),
+				                                    Commons.format(world, blockPos) ));
 				return false;
 			}
 		}
@@ -218,6 +238,22 @@ public class CelestialObjectManager extends XmlFileManager {
 		return CLIENT.celestialObjects;
 	}
 	
+	@SuppressWarnings("unused") // Core mod
+	@SideOnly(Side.CLIENT)
+	public static WorldBorder World_getWorldBorder(@Nonnull final World world) {
+		final WorldBorder worldBorder = world.getWorldBorder();
+		final EntityPlayer entityPlayer = Minecraft.getMinecraft().player;
+		if ( entityPlayer == null
+		  || entityPlayer.world != world ) {
+			return worldBorder;
+		}
+		final CelestialObject celestialObject = get(world, (int) entityPlayer.posX, (int) entityPlayer.posZ);
+		if (celestialObject == null) {
+			return worldBorder;
+		}
+		return celestialObject.getWorldBorder();
+	}
+	
 	// *** non-static methods ***
 	
 	private void addOrUpdateInRegistry(@Nonnull final CelestialObject celestialObject, final boolean isUpdating) {
diff --git a/src/main/java/cr0s/warpdrive/event/LivingHandler.java b/src/main/java/cr0s/warpdrive/event/LivingHandler.java
index 21640468..2504bc12 100644
--- a/src/main/java/cr0s/warpdrive/event/LivingHandler.java
+++ b/src/main/java/cr0s/warpdrive/event/LivingHandler.java
@@ -112,17 +112,17 @@ public class LivingHandler {
 			if ( Math.abs(distanceSquared) <= BORDER_WARNING_RANGE_BLOCKS_SQUARED
 			  && entityLivingBase instanceof EntityPlayer
 			  && entityLivingBase.ticksExisted % 40 == 0) {
-				Commons.addChatMessage( entityLivingBase,
-					new WarpDriveText(Commons.getStyleWarning(), "warpdrive.world_border.in_range",
-					                  (int) Math.sqrt(Math.abs(distanceSquared))) );
+				((EntityPlayer) entityLivingBase).sendStatusMessage(
+						new WarpDriveText(Commons.getStyleWarning(), "warpdrive.world_border.in_range",
+						                  (int) Math.sqrt(Math.abs(distanceSquared))), true );
 			}
 		} else {
 			if (entityLivingBase instanceof EntityPlayerMP) {
 				if (((EntityPlayerMP) entityLivingBase).capabilities.isCreativeMode) {
 					if (entityLivingBase.ticksExisted % 100 == 0) {
-						Commons.addChatMessage( entityLivingBase,
-							new WarpDriveText(Commons.getStyleWarning(), "warpdrive.world_border.outside",
-							                  (int) Math.sqrt(Math.abs(distanceSquared))) );
+						((EntityPlayer) entityLivingBase).sendStatusMessage(
+								new WarpDriveText(Commons.getStyleWarning(), "warpdrive.world_border.outside",
+								                  (int) Math.sqrt(Math.abs(distanceSquared))), true );
 					}
 					return;
 				}
diff --git a/src/main/resources/assets/warpdrive/lang/de_de.lang b/src/main/resources/assets/warpdrive/lang/de_de.lang
index 17be426f..37c1057c 100644
--- a/src/main/resources/assets/warpdrive/lang/de_de.lang
+++ b/src/main/resources/assets/warpdrive/lang/de_de.lang
@@ -980,4 +980,5 @@ warpdrive.transporter_signature.set=Transporter room is now linked to %1$s.
 
 warpdrive.world_border.in_range=Proximity alert: world border is only %1$d m away!
 warpdrive.world_border.outside=You're %1$d m outside the world border...
+warpdrive.world_border.portal_denied=Portal opening cancelled to prevent a certain death!
 warpdrive.world_border.reached=You've reached the world border...
diff --git a/src/main/resources/assets/warpdrive/lang/en_us.lang b/src/main/resources/assets/warpdrive/lang/en_us.lang
index 79e477b7..3d1b75b9 100644
--- a/src/main/resources/assets/warpdrive/lang/en_us.lang
+++ b/src/main/resources/assets/warpdrive/lang/en_us.lang
@@ -980,4 +980,5 @@ warpdrive.transporter_signature.set=Transporter room is now linked to %1$s.
 
 warpdrive.world_border.in_range=Proximity alert: world border is only %1$d m away!
 warpdrive.world_border.outside=You're %1$d m outside the world border...
+warpdrive.world_border.portal_denied=Portal opening cancelled to prevent a certain death!
 warpdrive.world_border.reached=You've reached the world border...
diff --git a/src/main/resources/assets/warpdrive/lang/fr_fr.lang b/src/main/resources/assets/warpdrive/lang/fr_fr.lang
index 60af29e7..7d481712 100644
--- a/src/main/resources/assets/warpdrive/lang/fr_fr.lang
+++ b/src/main/resources/assets/warpdrive/lang/fr_fr.lang
@@ -978,6 +978,7 @@ warpdrive.transporter_signature.set_self=Transporter room can't target itself!
 warpdrive.transporter_signature.set_same=Transporter room is already linked to %1$s.
 warpdrive.transporter_signature.set=Transporter room is now linked to %1$s.
 
-warpdrive.world_border.in_range=Proximity alert: world border is only %1$d m away!
-warpdrive.world_border.outside=You're %1$d m outside the world border...
-warpdrive.world_border.reached=You've reached the world border...
+warpdrive.world_border.in_range=Alerte de proximité: frontière du monde à %1$d m!
+warpdrive.world_border.outside=Tu es %1$d m au-delà de la frontière du monde...
+warpdrive.world_border.portal_denied=Ouverture du portail annulée pour éviter une mort certaine!
+warpdrive.world_border.reached=Tu as atteint la frontière du monde...
diff --git a/src/main/resources/assets/warpdrive/lang/nl_nl.lang b/src/main/resources/assets/warpdrive/lang/nl_nl.lang
index e8a707b9..c5c267c4 100644
--- a/src/main/resources/assets/warpdrive/lang/nl_nl.lang
+++ b/src/main/resources/assets/warpdrive/lang/nl_nl.lang
@@ -979,4 +979,5 @@ warpdrive.transporter_signature.set=Transporter room is now linked to %1$s.
 
 warpdrive.world_border.in_range=Proximity alert: world border is only %1$d m away!
 warpdrive.world_border.outside=You're %1$d m outside the world border...
+warpdrive.world_border.portal_denied=Portal opening cancelled to prevent a certain death!
 warpdrive.world_border.reached=You've reached the world border...
diff --git a/src/main/resources/assets/warpdrive/lang/ru_ru.lang b/src/main/resources/assets/warpdrive/lang/ru_ru.lang
index ead35727..352cf00d 100644
--- a/src/main/resources/assets/warpdrive/lang/ru_ru.lang
+++ b/src/main/resources/assets/warpdrive/lang/ru_ru.lang
@@ -980,4 +980,5 @@ warpdrive.transporter_signature.set=Transporter room is now linked to %1$s.
 
 warpdrive.world_border.in_range=Proximity alert: world border is only %1$d m away!
 warpdrive.world_border.outside=You're %1$d m outside the world border...
+warpdrive.world_border.portal_denied=Portal opening cancelled to prevent a certain death!
 warpdrive.world_border.reached=You've reached the world border...
diff --git a/src/main/resources/assets/warpdrive/lang/zh_cn.lang b/src/main/resources/assets/warpdrive/lang/zh_cn.lang
index 61d77057..d6f3b19f 100644
--- a/src/main/resources/assets/warpdrive/lang/zh_cn.lang
+++ b/src/main/resources/assets/warpdrive/lang/zh_cn.lang
@@ -985,4 +985,5 @@ warpdrive.transporter_signature.set=Transporter room is now linked to %1$s.
 
 warpdrive.world_border.in_range=Proximity alert: world border is only %1$d m away!
 warpdrive.world_border.outside=You're %1$d m outside the world border...
+warpdrive.world_border.portal_denied=Portal opening cancelled to prevent a certain death!
 warpdrive.world_border.reached=You've reached the world border...
diff --git a/src/main/resources/assets/warpdrive/lang/zh_tw.lang b/src/main/resources/assets/warpdrive/lang/zh_tw.lang
index 98f889c7..e615c64d 100644
--- a/src/main/resources/assets/warpdrive/lang/zh_tw.lang
+++ b/src/main/resources/assets/warpdrive/lang/zh_tw.lang
@@ -979,4 +979,5 @@ warpdrive.transporter_signature.set=Transporter room is now linked to %1$s.
 
 warpdrive.world_border.in_range=Proximity alert: world border is only %1$d m away!
 warpdrive.world_border.outside=You're %1$d m outside the world border...
+warpdrive.world_border.portal_denied=Portal opening cancelled to prevent a certain death!
 warpdrive.world_border.reached=You've reached the world border...