diff --git a/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java b/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java index cbcb9ebb..56e20ccc 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java +++ b/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java @@ -24,6 +24,7 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.profiler.Profiler; import net.minecraft.util.ChatComponentText; +import net.minecraft.util.IChatComponent; import net.minecraft.util.IIcon; import net.minecraft.util.Vec3; import net.minecraft.world.World; @@ -69,6 +70,10 @@ public class ClientProxy extends CommonProxy { * counter keeping track of how many renderThrough hint particle we have. enable optimization if no renderThrough */ private static int renderThrough; + /** + * A throttle map for local player. Integrated server will use the throttle map in super class instead. + */ + private static final Map localThrottleMap = new HashMap<>(); @Override public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) { @@ -152,6 +157,19 @@ public class ClientProxy extends CommonProxy { return true; } + @Override + public void addThrottledChat( + Object throttleKey, + EntityPlayer player, + IChatComponent text, + short intervalRequired, + boolean forceUpdateLastSend) { + if (player instanceof EntityPlayerMP) + super.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend); + else if (player != null) + addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, localThrottleMap); + } + static void removeGroup(HintGroup group) { for (HintParticleInfo hintParticleInfo : group.getHints()) { allHints.remove(hintParticleInfo); @@ -423,10 +441,13 @@ public class ClientProxy extends CommonProxy { @SubscribeEvent public void onWorldLoad(WorldEvent.Load e) { if (e.world.isRemote) { + // flush hints. we are in a different world now. allHintsForRender.clear(); allGroups.clear(); lastPlayerPos.yCoord = -1e30; renderThrough = 0; + // clear throttles. hopefully a world switch is enough long as a cool down. + localThrottleMap.clear(); } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java b/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java index c60450a5..5ecc274a 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java +++ b/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java @@ -3,11 +3,15 @@ package com.gtnewhorizon.structurelib; import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage; import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage; import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.server.MinecraftServer; +import net.minecraft.util.IChatComponent; import net.minecraft.util.IIcon; import net.minecraft.world.World; @@ -61,4 +65,38 @@ public class CommonProxy { return false; } } + + private final Map> throttleMap = new WeakHashMap<>(); + + public void addThrottledChat( + Object throttleKey, + EntityPlayer player, + IChatComponent text, + short intervalRequired, + boolean forceUpdateLastSend) { + if (player instanceof EntityPlayerMP) { + Map submap = throttleMap.computeIfAbsent((EntityPlayerMP) player, p -> new HashMap<>()); + addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, submap); + } + } + + protected static void addThrottledChat( + Object throttleKey, + EntityPlayer player, + IChatComponent text, + short intervalRequired, + boolean forceUpdateLastSend, + Map submap) { + long now = System.currentTimeMillis(); + Long old; + if (forceUpdateLastSend) { + old = submap.put(throttleKey, now); + } else { + old = submap.get(throttleKey); + } + if (old == null || now - old >= intervalRequired) { + player.addChatComponentMessage(text); + if (!forceUpdateLastSend) submap.put(throttleKey, now); + } + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java index 3e506eaa..1c28f51d 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java @@ -13,6 +13,7 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; import net.minecraft.util.IIcon; import net.minecraft.world.World; @@ -225,4 +226,37 @@ public class StructureLibAPI { Block block = w.getBlock(x, y, z); return block.isAir(w, x, y, z) || block.isReplaceable(w, x, y, z); } + + /** + * Send chat to player, but throttled. + * @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)} + * to handle value equality. + * @param player player to send chat to + * @param text chat to send + * @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond. + * we purposefully chose to not use a bigger data type to limit how long this interval can be + */ + public static void addThrottledChat( + Object throttleKey, EntityPlayer player, IChatComponent text, short intervalRequired) { + addThrottledChat(throttleKey, player, text, intervalRequired, false); + } + + /** + * Send chat to player, but throttled. + * @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)} + * to handle value equality. + * @param player player to send chat to + * @param text chat to send + * @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond. + * we purposefully chose to not use a bigger data type to limit how long this interval can be + * @param forceUpdateLastSend if true, always update the last send time, even if the message isn't actually sent. + */ + public static void addThrottledChat( + Object throttleKey, + EntityPlayer player, + IChatComponent text, + short intervalRequired, + boolean forceUpdateLastSend) { + proxy.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/ChatThrottleKey.java b/src/main/java/com/gtnewhorizon/structurelib/structure/ChatThrottleKey.java new file mode 100644 index 00000000..751d7e7e --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/ChatThrottleKey.java @@ -0,0 +1,30 @@ +package com.gtnewhorizon.structurelib.structure; + +class ChatThrottleKey { + + static class NoExplicitChannel { + private static final int hashOffset = NoExplicitChannel.class.hashCode(); + private final String subchannel; + + public NoExplicitChannel(String subchannel) { + this.subchannel = subchannel; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NoExplicitChannel)) return false; + + NoExplicitChannel that = (NoExplicitChannel) o; + + return subchannel.equals(that.subchannel); + } + + @Override + public int hashCode() { + // as this one will be placed within a map along with instances of different classes, it'd be best to add + // an offset based off class type to avoid collision + return subchannel.hashCode() + hashOffset; + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index bceacd0f..b99a4ad7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -2706,12 +2706,18 @@ public class StructureUtility { } private void warnNoExplicitSubChannel(EntityPlayer currentPlayer) { - currentPlayer.addChatComponentMessage( - new ChatComponentTranslation("structurelib.autoplace.warning.no_explicit_channel", channel)); + // throttle this warning a bit. + // I'm sure we can finish a check/autoplace round within 100 milliseconds. + StructureLibAPI.addThrottledChat( + new ChatThrottleKey.NoExplicitChannel(channel), + currentPlayer, + new ChatComponentTranslation("structurelib.autoplace.warning.no_explicit_channel", channel), + (short) 100); } public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { // I hope a CREATIVE player know what he is doing... + // no warning for yah return backing.placeBlock(t, world, x, y, z, ChannelDataAccessor.withChannel(trigger, channel)); }