From 2399aba5eafddb9ee0801ea429503b6baed890cf Mon Sep 17 00:00:00 2001 From: tterrag Date: Sun, 29 Nov 2015 23:40:51 -0500 Subject: [PATCH] Somewhat experimental: Add time of day on server to server list GUI --- .../gui/serverlist/PacketServerTime.java | 70 +++++ .../gui/serverlist/ServerListHandler.java | 271 ++++++++++++++++++ .../gui/serverlist/TimeServerPinger.java | 128 +++++++++ 3 files changed, 469 insertions(+) create mode 100644 src/main/java/com/enderio/core/client/gui/serverlist/PacketServerTime.java create mode 100644 src/main/java/com/enderio/core/client/gui/serverlist/ServerListHandler.java create mode 100644 src/main/java/com/enderio/core/client/gui/serverlist/TimeServerPinger.java diff --git a/src/main/java/com/enderio/core/client/gui/serverlist/PacketServerTime.java b/src/main/java/com/enderio/core/client/gui/serverlist/PacketServerTime.java new file mode 100644 index 0000000..91caa2b --- /dev/null +++ b/src/main/java/com/enderio/core/client/gui/serverlist/PacketServerTime.java @@ -0,0 +1,70 @@ +package com.enderio.core.client.gui.serverlist; + +import java.io.IOException; +import java.lang.reflect.Field; + +import net.minecraft.network.INetHandler; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.Packet; +import net.minecraft.network.PacketBuffer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.NetHandlerStatusServer; + +import com.enderio.core.client.gui.serverlist.TimeServerPinger.NetHandlerTime; +import com.google.common.base.Throwables; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.network.ByteBufUtils; +import cpw.mods.fml.relauncher.ReflectionHelper; + +public class PacketServerTime extends Packet { + + private long time; + private String serverIP; + + public PacketServerTime() { + this(""); + } + + public PacketServerTime(String serverIP) { + this(0, serverIP); + } + + private PacketServerTime(long time, String serverIP) { + this.time = time; + this.serverIP = serverIP; + } + + @Override + public void readPacketData(PacketBuffer buf) throws IOException { + this.time = buf.readLong(); + this.serverIP = ByteBufUtils.readUTF8String(buf); + } + + @Override + public void writePacketData(PacketBuffer buf) throws IOException { + buf.writeLong(getTime()); + ByteBufUtils.writeUTF8String(buf, serverIP); + } + + private static final Field _networkManager = ReflectionHelper.findField(NetHandlerStatusServer.class, "field_147313_b", "networkManager"); + + @Override + public void processPacket(INetHandler net) { + if (FMLCommonHandler.instance().getEffectiveSide().isServer()) { + try { + MinecraftServer serv = MinecraftServer.getServer(); + NetworkManager resp = (NetworkManager) _networkManager.get(net); + resp.scheduleOutboundPacket(new PacketServerTime(serv.getEntityWorld().getWorldTime(), serverIP)); + } catch (Exception e) { + Throwables.propagate(e); + } + } else { + ((NetHandlerTime) net).handleTime(this); + } + } + + public long getTime() { + return time; + } +} diff --git a/src/main/java/com/enderio/core/client/gui/serverlist/ServerListHandler.java b/src/main/java/com/enderio/core/client/gui/serverlist/ServerListHandler.java new file mode 100644 index 0000000..55c9a10 --- /dev/null +++ b/src/main/java/com/enderio/core/client/gui/serverlist/ServerListHandler.java @@ -0,0 +1,271 @@ +package com.enderio.core.client.gui.serverlist; + +import gnu.trove.map.hash.TObjectLongHashMap; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiListExtended.IGuiListEntry; +import net.minecraft.client.gui.GuiMultiplayer; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.ServerListEntryNormal; +import net.minecraft.client.gui.ServerSelectionList; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureClock; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.init.Items; +import net.minecraft.network.EnumConnectionState; +import net.minecraft.world.WorldProvider; +import net.minecraftforge.client.event.GuiScreenEvent.DrawScreenEvent; +import net.minecraftforge.client.event.GuiScreenEvent.InitGuiEvent; +import net.minecraftforge.common.DimensionManager; + +import org.lwjgl.opengl.GL11; + +import com.enderio.core.client.handlers.ClientHandler; +import com.enderio.core.client.render.RenderUtil; +import com.enderio.core.common.Handlers.Handler; +import com.enderio.core.common.Handlers.Handler.HandlerSide; +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent; +import cpw.mods.fml.relauncher.ReflectionHelper; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SuppressWarnings("unchecked") +@Handler(side = HandlerSide.COMMON) +public class ServerListHandler { + + private static class TextureClockCustom extends TextureClock { + private static final Field field_94239_h = ReflectionHelper.findField(TextureClock.class, "field_94239_h"); + private static final Field field_94240_i = ReflectionHelper.findField(TextureClock.class, "field_94240_i"); + private static final Field _framesTextureData = ReflectionHelper.findField(TextureAtlasSprite.class, "field_110976_a", "framesTextureData"); + + private static final WorldProvider provider = DimensionManager.createProviderFor(0); + + private String serverIP; + + public TextureClockCustom(String serverIP) throws IllegalArgumentException, IllegalAccessException { + super("clock-custom"); + this.serverIP = serverIP; + this.framesTextureData = (List) _framesTextureData.get(Items.clock.getIconFromDamage(0)); + } + + @Override + public void updateAnimation() { + if (!this.framesTextureData.isEmpty()) { + try { + float f = provider.calculateCelestialAngle(timeCache.get(serverIP), 1.0F); + double angle = (double) f; + + if (!provider.isSurfaceWorld()) { + angle = Math.random(); + } + + double d1; + + for (d1 = angle - field_94239_h.getDouble(this); d1 < -0.5D; ++d1) { + ; + } + + while (d1 >= 0.5D) { + --d1; + } + + if (d1 < -1.0D) { + d1 = -1.0D; + } + + if (d1 > 1.0D) { + d1 = 1.0D; + } + + field_94240_i.setDouble(this, field_94240_i.getDouble(this) + d1 * 0.1D); + field_94240_i.setDouble(this, field_94240_i.getDouble(this) * 0.8D); + field_94239_h.setDouble(this, field_94239_h.getDouble(this) + field_94240_i.getDouble(this)); + int i; + + for (i = (int) ((field_94239_h.getDouble(this) + 1.0D) * (double) this.framesTextureData.size()) % this.framesTextureData.size(); i < 0; i = (i + this.framesTextureData + .size()) % this.framesTextureData.size()) { + ; + } + + if (i != this.frameCounter) { + this.frameCounter = i; + TextureUtil.uploadTextureMipmap((int[][]) this.framesTextureData.get(this.frameCounter), this.width, this.height, this.originX, this.originY, + false, false); + // For some reason this can spam a GL error, it is harmless from what I can see, so we'll just throw it away + GL11.glGetError(); + } + } catch (Exception e) { + Throwables.propagate(e); + } + } + } + } + + static { + EnumConnectionState.STATUS.func_150753_a().put(42, PacketServerTime.class); + EnumConnectionState.STATUS.func_150755_b().put(42, PacketServerTime.class); + try { + ((Map, EnumConnectionState>) ReflectionHelper.findField(EnumConnectionState.class, "field_150761_f", "STATES_BY_CLASS").get(null)).put( + PacketServerTime.class, EnumConnectionState.STATUS); + } catch (Exception e) { + Throwables.propagate(e); + } + } + + private static Field _serverList; + private static Method _getSize; + private static Method _drawHoveringText; + + private static TimeServerPinger pinger; + private static Set capturedIps = Sets.newHashSet(); + + @SideOnly(Side.CLIENT) + private static GuiMultiplayer openGui; + @SideOnly(Side.CLIENT) + private static ServerSelectionList serverList; + + static TObjectLongHashMap timeCache = new TObjectLongHashMap(); + + private static Map textures = Maps.newHashMap(); + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onDrawGui(DrawScreenEvent.Post event) { + if (openGui != null) { + if (serverList != null) { + int x = serverList.left + (serverList.width / 2) + (serverList.getListWidth() / 2) - 32; + int y = serverList.top + 13 - serverList.getAmountScrolled(); +// System.out.println(timeCache); + try { + int size = (Integer) _getSize.invoke(serverList); + for (int i = 0; i < size; i++) { + IGuiListEntry entry = serverList.getListEntry(i); + if (entry instanceof ServerListEntryNormal) { + ServerData data = ((ServerListEntryNormal) entry).func_148296_a(); + RenderUtil.bindItemTexture(); + TextureAtlasSprite sprite = textures.get(data.serverIP); + + if (sprite != null) { + Tessellator tess = Tessellator.instance; + + GL11.glPushMatrix(); + GL11.glColor4f(1, 1, 1, 1); + GL11.glTranslated(0, 0, -500); + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDepthMask(true); + tess.startDrawingQuads(); + tess.addVertexWithUV(x, y, 0, sprite.getMinU(), sprite.getMinV()); + tess.addVertexWithUV(x, y + 16, 0, sprite.getMinU(), sprite.getMaxV()); + tess.addVertexWithUV(x + 16, y + 16, 0, sprite.getMaxU(), sprite.getMaxV()); + tess.addVertexWithUV(x + 16, y, 0, sprite.getMaxU(), sprite.getMinV()); + tess.draw(); + GL11.glPopMatrix(); + + if (event.mouseX >= x && event.mouseX <= x + 16 && event.mouseY >= y && event.mouseY <= y + 16) { + long time = timeCache.get(data.serverIP); + long hour = (time / 1000 + 6) % 24; + long minute = (time % 1000) * 60 / 1000; + String ampm = "AM"; + if (hour >= 12) { + hour -= 12; + ampm = "PM"; + } + if (hour == 0) { + hour += 12; + } + String tt = String.format(Locale.ENGLISH, "%02d:%02d %s", hour, minute, ampm); + _drawHoveringText.invoke(event.gui, Lists.newArrayList(tt), event.mouseX, event.mouseY, Minecraft.getMinecraft().fontRenderer); + } + } + + y += serverList.getSlotHeight(); + } + } + } catch (Exception e) { + Throwables.propagate(e); + } + } + if (ClientHandler.getTicksElapsed() % 100 == 0) { + openConnections(openGui); + } + } + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onGuiOpen(InitGuiEvent.Post event) { + if (_serverList == null) { + _serverList = ReflectionHelper.findField(GuiMultiplayer.class, "field_146803_h", "serverListSelector"); + _getSize = ReflectionHelper.findMethod(ServerSelectionList.class, null, new String[] { "func_148127_b", "getSize" }); + _drawHoveringText = ReflectionHelper.findMethod(GuiScreen.class, null, new String[] { "func_146283_a", "drawHoveringText" }, List.class, int.class, int.class, FontRenderer.class); + } + if (pinger != null) { + pinger.close(); + } + if (event.gui instanceof GuiMultiplayer) { + openGui = (GuiMultiplayer) event.gui; + pinger = new TimeServerPinger(); + capturedIps.clear(); + openConnections(openGui); + } + } + + private void openConnections(GuiMultiplayer gui) { + try { + serverList = (ServerSelectionList) _serverList.get(gui); + int size = (Integer) _getSize.invoke(serverList); + for (int i = 0; i < size; i++) { + IGuiListEntry entry = serverList.getListEntry(i); + if (entry instanceof ServerListEntryNormal) { + ServerData data = ((ServerListEntryNormal) entry).func_148296_a(); + if (!capturedIps.contains(data.serverIP)) { + capturedIps.add(data.serverIP); + pinger.addServer(data); + if (!textures.containsKey(data.serverIP)) { + TextureClockCustom sprite = new TextureClockCustom(data.serverIP); + sprite.copyFrom((TextureAtlasSprite) Items.clock.getIconFromDamage(0)); + textures.put(data.serverIP, sprite); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onClientTick(ClientTickEvent event) { + if (Minecraft.getMinecraft().currentScreen != openGui) { + openGui = null; + } else if (openGui != null) { + pinger.update(ClientHandler.getTicksElapsed() % 20 == 0); + for (TextureClockCustom tex : textures.values()) { + tex.updateAnimation(); + } + } + + if (openGui == null && pinger != null) { + pinger.close(); + pinger = null; + } + } +} diff --git a/src/main/java/com/enderio/core/client/gui/serverlist/TimeServerPinger.java b/src/main/java/com/enderio/core/client/gui/serverlist/TimeServerPinger.java new file mode 100644 index 0000000..1c28717 --- /dev/null +++ b/src/main/java/com/enderio/core/client/gui/serverlist/TimeServerPinger.java @@ -0,0 +1,128 @@ +package com.enderio.core.client.gui.serverlist; + +import java.io.Closeable; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import net.minecraft.client.multiplayer.ServerAddress; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.network.EnumConnectionState; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.handshake.client.C00Handshake; +import net.minecraft.network.status.INetHandlerStatusClient; +import net.minecraft.network.status.server.S00PacketServerInfo; +import net.minecraft.network.status.server.S01PacketPong; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.IChatComponent; + +import org.apache.logging.log4j.Logger; + +import com.enderio.core.EnderCore; +import com.google.common.collect.Maps; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TimeServerPinger implements Closeable { + + public static class NetHandlerTime implements INetHandlerStatusClient { + + private NetworkManager manager; + private ServerData data; + private boolean done; + + public NetHandlerTime(NetworkManager manager, ServerData data) { + this.manager = manager; + this.data = data; + } + + public void onDisconnect(IChatComponent p_147231_1_) { + if (!done) { + logger.error("Can\'t ping " + data.serverIP + " for time info" + ": " + p_147231_1_.getUnformattedText()); + } + } + + public void handleTime(PacketServerTime packet) { + ServerListHandler.timeCache.put(data.serverIP, packet.getTime()); + done = true; + manager.closeChannel(new ChatComponentText("Finished")); + } + + @Override + public void handlePong(S01PacketPong packet) { + // NO-OP + } + + @Override + public void handleServerInfo(S00PacketServerInfo p_147397_1_) { + // NO-OP + } + + public void onConnectionStateTransition(EnumConnectionState p_147232_1_, EnumConnectionState p_147232_2_) { + if (p_147232_2_ != EnumConnectionState.STATUS) { + throw new UnsupportedOperationException("Unexpected change in protocol to " + p_147232_2_); + } + } + + public void onNetworkTick() { + } + } + + private static final Logger logger = EnderCore.logger; + private final Map managers = Collections.synchronizedMap(Maps. newHashMap()); + + public void addServer(final ServerData data) throws UnknownHostException { + ServerAddress serveraddress = ServerAddress.func_78860_a(data.serverIP); + final NetworkManager networkmanager = NetworkManager.provideLanClient(InetAddress.getByName(serveraddress.getIP()), serveraddress.getPort()); + this.managers.put(networkmanager, data); + + networkmanager.setNetHandler(new NetHandlerTime(networkmanager, data)); + + try { + networkmanager.scheduleOutboundPacket(new C00Handshake(5, serveraddress.getIP(), serveraddress.getPort(), EnumConnectionState.STATUS)); + networkmanager.scheduleOutboundPacket(new PacketServerTime(data.serverIP)); + } catch (Throwable throwable) { + logger.error(throwable); + } + } + + public void update(boolean sendPacket) { + synchronized (this.managers) { + Iterator iterator = this.managers.keySet().iterator(); + + while (iterator.hasNext()) { + NetworkManager manager = iterator.next(); + + if (manager.isChannelOpen()) { + manager.processReceivedPackets(); + } else { + iterator.remove(); + + if (manager.getExitMessage() != null) { + manager.getNetHandler().onDisconnect(manager.getExitMessage()); + } + } + } + } + } + + @Override + public void close() { + synchronized (this.managers) { + Iterator iterator = this.managers.keySet().iterator(); + + while (iterator.hasNext()) { + NetworkManager networkmanager = (NetworkManager) iterator.next(); + + if (networkmanager.isChannelOpen()) { + iterator.remove(); + networkmanager.closeChannel(new ChatComponentText("Cancelled")); + } + } + } + } +} \ No newline at end of file