diff --git a/build.gradle b/build.gradle index 7698397..8da6471 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,9 @@ configurations { minecraft { version = "1.7.10-10.13.4.1614-1.7.10" runDir = "run" + + replaceIn "net/anvilcraft/anvillib/AnvilLib.java" + replace "{VERSION}", project.version } repositories { @@ -55,6 +58,10 @@ jar { duplicatesStrategy = 'exclude' exclude 'LICENSE.txt', 'META-INF/MANIFSET.MF', 'META-INF/maven/**', 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/services/*.Processor', 'META-INF/versions/**' } + + manifest { + attributes "FMLAT": "anvillib_at.cfg" + } } processResources { @@ -102,4 +109,4 @@ publishing { mavenLocal() } } -} \ No newline at end of file +} diff --git a/src/main/java/net/anvilcraft/anvillib/AnvilLib.java b/src/main/java/net/anvilcraft/anvillib/AnvilLib.java new file mode 100644 index 0000000..59f19e5 --- /dev/null +++ b/src/main/java/net/anvilcraft/anvillib/AnvilLib.java @@ -0,0 +1,63 @@ +package net.anvilcraft.anvillib; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.Mod.EventHandler; +import cpw.mods.fml.common.SidedProxy; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerStoppedEvent; +import cpw.mods.fml.common.network.NetworkRegistry; +import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import cpw.mods.fml.relauncher.Side; +import net.anvilcraft.anvillib.network.PacketUpdateUserCache; +import net.anvilcraft.anvillib.proxy.CommonProxy; +import net.anvilcraft.anvillib.usercache.UserCache; +import net.anvilcraft.anvillib.usercache.UserCacheEventHandler; +import net.minecraftforge.common.MinecraftForge; + +@Mod(modid = "anvillib", version = "{VERSION}", name = "AnvilLib") +public class AnvilLib { + public static final Logger LOGGER = LogManager.getLogger("AnvilLib"); + + @SidedProxy( + modId = "anvillib", + serverSide = "net.anvilcraft.anvillib.proxy.CommonProxy", + clientSide = "net.anvilcraft.anvillib.proxy.ClientProxy" + ) + public static CommonProxy proxy; + + public static SimpleNetworkWrapper channel; + + @EventHandler + public static void preInit(FMLPreInitializationEvent ev) { + proxy.loadUserCache(UserCache.INSTANCE); + UserCacheEventHandler uceh = new UserCacheEventHandler(); + MinecraftForge.EVENT_BUS.register(uceh); + FMLCommonHandler.instance().bus().register(uceh); + + Runtime.getRuntime().addShutdownHook( + new Thread(() -> proxy.saveUserCache(UserCache.INSTANCE)) + ); + + channel = NetworkRegistry.INSTANCE.newSimpleChannel("anvillib"); + int pktid = 0; + channel.registerMessage( + PacketUpdateUserCache.Handler.class, + PacketUpdateUserCache.class, + pktid++, + Side.CLIENT + ); + } + + @EventHandler + public static void init(FMLInitializationEvent ev) {} + + @EventHandler + public static void shutdown(FMLServerStoppedEvent ev) { + proxy.saveUserCache(UserCache.INSTANCE); + } +} diff --git a/src/main/java/net/anvilcraft/anvillib/network/PacketUpdateUserCache.java b/src/main/java/net/anvilcraft/anvillib/network/PacketUpdateUserCache.java new file mode 100644 index 0000000..f4c7291 --- /dev/null +++ b/src/main/java/net/anvilcraft/anvillib/network/PacketUpdateUserCache.java @@ -0,0 +1,58 @@ +package net.anvilcraft.anvillib.network; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import io.netty.buffer.ByteBuf; +import net.anvilcraft.anvillib.usercache.UserCache; + +public class PacketUpdateUserCache implements IMessage { + public Map entries; + + public PacketUpdateUserCache(Map entries) { + this.entries = entries; + } + + public PacketUpdateUserCache() {} + + @Override + public void toBytes(ByteBuf buf) { + for (Entry ent : this.entries.entrySet()) { + buf.writeLong(ent.getKey().getMostSignificantBits()); + buf.writeLong(ent.getKey().getLeastSignificantBits()); + + buf.writeInt(ent.getValue().length()); + buf.writeBytes(ent.getValue().getBytes()); + } + } + + @Override + public void fromBytes(ByteBuf buf) { + this.entries = new HashMap<>(); + + while (buf.readableBytes() > 0) { + UUID id = new UUID(buf.readLong(), buf.readLong()); + + byte[] nameBytes = new byte[buf.readInt()]; + buf.readBytes(nameBytes); + + String name = new String(nameBytes); + + this.entries.put(id, name); + } + } + + public static class Handler + implements IMessageHandler { + @Override + public IMessage onMessage(PacketUpdateUserCache pkt, MessageContext arg1) { + UserCache.INSTANCE.users.putAll(pkt.entries); + return null; + } + } +} diff --git a/src/main/java/net/anvilcraft/anvillib/proxy/ClientProxy.java b/src/main/java/net/anvilcraft/anvillib/proxy/ClientProxy.java new file mode 100644 index 0000000..869db40 --- /dev/null +++ b/src/main/java/net/anvilcraft/anvillib/proxy/ClientProxy.java @@ -0,0 +1,16 @@ +package net.anvilcraft.anvillib.proxy; + +import net.anvilcraft.anvillib.usercache.ClientCacheManager; +import net.anvilcraft.anvillib.usercache.UserCache; + +public class ClientProxy extends CommonProxy { + @Override + public void loadUserCache(UserCache cache) { + ClientCacheManager.loadCacheInto(cache); + } + + @Override + public void saveUserCache(UserCache cache) { + ClientCacheManager.serializeCache(cache); + } +} diff --git a/src/main/java/net/anvilcraft/anvillib/proxy/CommonProxy.java b/src/main/java/net/anvilcraft/anvillib/proxy/CommonProxy.java new file mode 100644 index 0000000..1986504 --- /dev/null +++ b/src/main/java/net/anvilcraft/anvillib/proxy/CommonProxy.java @@ -0,0 +1,9 @@ +package net.anvilcraft.anvillib.proxy; + +import net.anvilcraft.anvillib.usercache.UserCache; + +public class CommonProxy { + public void loadUserCache(UserCache cache) {} + + public void saveUserCache(UserCache cache) {} +} diff --git a/src/main/java/net/anvilcraft/anvillib/usercache/ClientCacheManager.java b/src/main/java/net/anvilcraft/anvillib/usercache/ClientCacheManager.java new file mode 100644 index 0000000..7148bf2 --- /dev/null +++ b/src/main/java/net/anvilcraft/anvillib/usercache/ClientCacheManager.java @@ -0,0 +1,92 @@ +package net.anvilcraft.anvillib.usercache; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Map.Entry; +import java.util.UUID; + +import org.apache.commons.lang3.tuple.Pair; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.anvilcraft.anvillib.AnvilLib; + +/** + * This class loads the client's serialized user cache and adds it to the cache instance. + */ +@SideOnly(Side.CLIENT) +public class ClientCacheManager { + public static final String FILE_PATH = "anvillib_usercache"; + + public static void loadCacheInto(UserCache cache) { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(FILE_PATH)); + + int linen = 0; + String line; + while ((line = reader.readLine()) != null) { + linen++; + Pair parsed = parseLine(line); + if (parsed == null) { + AnvilLib.LOGGER.warn( + "Usercache contains invalid line @ {}, skipping", linen + ); + continue; + } + + cache.addEntry(parsed.getLeft(), parsed.getRight()); + } + } catch (FileNotFoundException e) { + AnvilLib.LOGGER.warn("Usercache file does not exist."); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + if (reader != null) + reader.close(); + } catch (IOException alec) {} + } + } + + private static Pair parseLine(String line) { + String[] splits = line.split("=", 2); + if (splits.length != 2) + return null; + + UUID id; + try { + id = UUID.fromString(splits[0]); + } catch (IllegalArgumentException e) { + return null; + } + + return Pair.of(id, splits[1]); + } + + public static void serializeCache(UserCache cache) { + AnvilLib.LOGGER.debug("Saving usercache"); + File outfile = new File(FILE_PATH); + Writer writer = null; + try { + outfile.createNewFile(); + writer = new BufferedWriter(new FileWriter(outfile)); + for (Entry ent : cache.users.entrySet()) { + writer.append(ent.getKey().toString() + "=" + ent.getValue() + "\n"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + if (writer != null) + writer.close(); + } catch (IOException alec) {} + } + } +} diff --git a/src/main/java/net/anvilcraft/anvillib/usercache/UserCache.java b/src/main/java/net/anvilcraft/anvillib/usercache/UserCache.java new file mode 100644 index 0000000..86bb592 --- /dev/null +++ b/src/main/java/net/anvilcraft/anvillib/usercache/UserCache.java @@ -0,0 +1,46 @@ +package net.anvilcraft.anvillib.usercache; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import net.minecraft.entity.player.EntityPlayer; + +/** + * A singleton data structure that serves as a lookup table from user UUIDs to their usernames. + * It is serialized on the client and in worlds and is synched to the client upon joining a server. + */ +public class UserCache { + public static final UserCache INSTANCE = new UserCache(); + public Map users = new HashMap<>(); + + /** + * Add a new entry to the usercache. + * + * @param id The UUID of the player. + * @param name The name of the player. + */ + public void addEntry(UUID id, String name) { + this.users.put(id, name); + } + + /** + * Add a player to the usercache. + * + * @param player Player Entity to add. + */ + public void addEntry(EntityPlayer player) { + this.addEntry(player.getUniqueID(), player.getCommandSenderName()); + } + + /** + * Gets a username given the user's UUID. + * + * @param id The UUID of the player. + * + * @return The player's Username or null if it is not known. + */ + public String getCached(UUID id) { + return this.users.get(id); + } +} diff --git a/src/main/java/net/anvilcraft/anvillib/usercache/UserCacheEventHandler.java b/src/main/java/net/anvilcraft/anvillib/usercache/UserCacheEventHandler.java new file mode 100644 index 0000000..da28f48 --- /dev/null +++ b/src/main/java/net/anvilcraft/anvillib/usercache/UserCacheEventHandler.java @@ -0,0 +1,91 @@ +package net.anvilcraft.anvillib.usercache; + +import java.io.File; +import java.io.FileInputStream; +import java.util.List; +import java.util.UUID; + +import com.google.common.collect.ImmutableMap; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import net.anvilcraft.anvillib.AnvilLib; +import net.anvilcraft.anvillib.network.PacketUpdateUserCache; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.storage.SaveHandler; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.world.WorldEvent; + +public class UserCacheEventHandler { + @SubscribeEvent + public void onWorldSave(WorldEvent.Save ev) { + AnvilLib.proxy.saveUserCache(UserCache.INSTANCE); + } + + @SubscribeEvent + public void onPlayerSave(PlayerEvent.SaveToFile ev) { + ev.entityPlayer.getEntityData().setString( + "anvillib:name", ev.entityPlayer.getCommandSenderName() + ); + } + + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load ev) { + if (ev.world.provider.dimensionId != 0 + || !(ev.world.getSaveHandler() instanceof SaveHandler)) + return; + + int loaded = 0; + SaveHandler sh = (SaveHandler) ev.world.getSaveHandler(); + for (String playerdat : sh.getAvailablePlayerDat()) { + try { + File f = new File(sh.playersDirectory, playerdat + ".dat"); + + UUID id = UUID.fromString(playerdat); + NBTTagCompound tag + = CompressedStreamTools.readCompressed(new FileInputStream(f)); + + String name = tag.getCompoundTag("ForgeData").getString("anvillib:name"); + if (!name.isEmpty()) { + UserCache.INSTANCE.addEntry(id, name); + loaded++; + } + } catch (Exception e) { + continue; + } + } + + AnvilLib.LOGGER.info("Loaded {} usercache entries from player dat", loaded); + } + + @SubscribeEvent + @SuppressWarnings("unchecked") + public void onPlayerJoin(PlayerLoggedInEvent ev) { + UserCache.INSTANCE.addEntry(ev.player); + + if (!(ev.player instanceof EntityPlayerMP) + || ((EntityPlayerMP) ev.player).getPlayerIP().equals("local")) + return; + + AnvilLib.channel.sendTo( + new PacketUpdateUserCache(UserCache.INSTANCE.users), + (EntityPlayerMP) ev.player + ); + + IMessage updatePkt = new PacketUpdateUserCache( + ImmutableMap.of(ev.player.getUniqueID(), ev.player.getCommandSenderName()) + ); + for (EntityPlayerMP pl : (List) MinecraftServer.getServer() + .getConfigurationManager() + .playerEntityList) { + if (pl == ev.player) + continue; + + AnvilLib.channel.sendTo(updatePkt, pl); + } + } +} diff --git a/src/main/resources/META-INF/anvillib_at.cfg b/src/main/resources/META-INF/anvillib_at.cfg new file mode 100644 index 0000000..d8c99a3 --- /dev/null +++ b/src/main/resources/META-INF/anvillib_at.cfg @@ -0,0 +1 @@ +public net.minecraft.world.storage.SaveHandler field_75771_c # playersDirectory