diff --git a/dependencies.gradle b/dependencies.gradle index ed2bf1b..1a44f0d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -34,9 +34,9 @@ * For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph */ dependencies { - implementation("com.github.GTNewHorizons:NotEnoughItems:2.7.77-GTNH:dev") + compileOnly("com.github.GTNewHorizons:NotEnoughItems:2.7.77-GTNH:dev") implementation("com.github.GTNewHorizons:GTNHLib:0.6.39:dev") - implementation("com.github.GTNewHorizons:BetterQuesting:3.7.11-GTNH:dev") + compileOnly("com.github.GTNewHorizons:BetterQuesting:3.7.11-GTNH:dev") implementation("com.github.GTNewHorizons:ModularUI2:2.2.16-1.7.10:dev") } diff --git a/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java index 873cb84..39e4555 100644 --- a/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java +++ b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/BqAdapter.java @@ -8,8 +8,14 @@ import javax.annotation.Nullable; +import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.network.handlers.NetAvailableTradeSync; import com.cubefury.vendingmachine.trade.TradeDatabase; import com.cubefury.vendingmachine.trade.TradeGroup; +import com.google.common.collect.ImmutableMap; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; public class BqAdapter { @@ -17,6 +23,8 @@ public class BqAdapter { private final Map> questUpdateTriggers = new HashMap<>(); + // cache of quests that player has completed, for NEI integration not having + // to look it up so much private final Map> playerSatisfiedCache = new HashMap<>(); private BqAdapter() {} @@ -37,37 +45,95 @@ public void addQuestTrigger(UUID quest, TradeGroup tg) { .add(tg); } + public Map> getPlayerSatisfiedCache() { + synchronized (playerSatisfiedCache) { + return ImmutableMap.copyOf(playerSatisfiedCache); + } + } + + @SideOnly(Side.CLIENT) + public void setPlayerSatisifedCache(Map> newCache) { + // Player -> Set + synchronized (playerSatisfiedCache) { + playerSatisfiedCache.clear(); + playerSatisfiedCache.putAll(newCache); + } + } + public void setQuestFinished(UUID player, UUID quest) { + if (!questUpdateTriggers.containsKey(quest)) { + return; + } for (TradeGroup tradeGroup : questUpdateTriggers.get(quest)) { tradeGroup.addSatisfiedCondition(player, new BqCondition(quest)); } - playerSatisfiedCache.computeIfAbsent(player, k -> new HashSet<>()); - playerSatisfiedCache.get(player) - .add(quest); + synchronized (playerSatisfiedCache) { + playerSatisfiedCache.computeIfAbsent(player, k -> new HashSet<>()); + playerSatisfiedCache.get(player) + .add(quest); + } + syncAvailableTradesFromServer(); } public void setQuestUnfinished(UUID player, UUID quest) { for (TradeGroup tradeGroup : questUpdateTriggers.get(quest)) { tradeGroup.removeSatisfiedCondition(player, new BqCondition(quest)); - if (playerSatisfiedCache.get(player) != null) { - playerSatisfiedCache.get(player) - .remove(quest); + synchronized (playerSatisfiedCache) { + if (playerSatisfiedCache.get(player) != null) { + playerSatisfiedCache.get(player) + .remove(quest); + } } } + syncAvailableTradesFromServer(); } public void resetQuests(UUID player) { TradeDatabase.INSTANCE.removeAllSatisfiedBqConditions(player); - if (player == null) { - playerSatisfiedCache.clear(); - } else { - playerSatisfiedCache.remove(player); + synchronized (playerSatisfiedCache) { + if (player == null) { + playerSatisfiedCache.clear(); + } else { + playerSatisfiedCache.remove(player); + } + } + syncAvailableTradesFromServer(); + } + + public void syncAvailableTradesFromServer() { + // We have to sync these trades even though the trades are only pulled usually during VM GUI opening, + // cuz someone's teammate might finish the quest + if (VendingMachine.proxy.isClient()) { + NetAvailableTradeSync.requestSync(); } } public boolean checkPlayerCompletedQuest(UUID player, UUID quest) { - return playerSatisfiedCache.get(player) != null && playerSatisfiedCache.get(player) - .contains(quest); + synchronized (playerSatisfiedCache) { + return playerSatisfiedCache.get(player) != null && playerSatisfiedCache.get(player) + .contains(quest); + } + } + + @SideOnly(Side.CLIENT) + public Set getTrades(UUID quest) { + Set output = new HashSet<>(); + if (questUpdateTriggers.get(quest) == null) { + return output; + } + + // Cannot use TradeManager.availableTrades since it is only updated + // when Vending Machine GUI is open + for (TradeGroup tradeGroup : questUpdateTriggers.get(quest)) { + output.add(tradeGroup.getId()); + } + return output; + } + + @SideOnly(Side.CLIENT) + public boolean questHasTrades(UUID quest) { + return questUpdateTriggers.get(quest) != null && !questUpdateTriggers.get(quest) + .isEmpty(); } } diff --git a/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/gui/BqTradeGroup.java b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/gui/BqTradeGroup.java new file mode 100644 index 0000000..fcdf3cc --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/integration/betterquesting/gui/BqTradeGroup.java @@ -0,0 +1,48 @@ +package com.cubefury.vendingmachine.integration.betterquesting.gui; + +import java.util.UUID; + +import com.cubefury.vendingmachine.trade.Trade; +import com.cubefury.vendingmachine.trade.TradeDatabase; +import com.cubefury.vendingmachine.trade.TradeGroup; + +import betterquesting.api2.client.gui.misc.GuiAlign; +import betterquesting.api2.client.gui.misc.GuiTransform; +import betterquesting.api2.client.gui.misc.IGuiRect; +import betterquesting.api2.client.gui.panels.CanvasEmpty; +import betterquesting.api2.client.gui.panels.IGuiPanel; +import betterquesting.api2.client.gui.panels.lists.CanvasScrolling; + +public class BqTradeGroup { + + public static int addTradePanel(CanvasScrolling csReward, IGuiRect rectReward, UUID tradeGroup, int yOffset) { + TradeGroup tg = TradeDatabase.INSTANCE.getTradeGroups() + .get(tradeGroup); + if (tg == null || tg.getTrades() == null) { + return yOffset; + } + for (Trade trade : tg.getTrades()) { + IGuiPanel tradeGui = trade.getTradeGui( + new GuiTransform(GuiAlign.FULL_BOX, 0, 0, rectReward.getWidth(), rectReward.getHeight(), 111)); + if (tradeGui != null) { + tradeGui.initPanel(); + CanvasEmpty tempCanvas = new CanvasEmpty( + new GuiTransform( + GuiAlign.TOP_LEFT, + 0, + yOffset, + rectReward.getWidth(), + tradeGui.getTransform() + .getHeight() + - tradeGui.getTransform() + .getY(), + 1)); + csReward.addPanel(tempCanvas); + tempCanvas.addPanel(tradeGui); + yOffset += tempCanvas.getTransform() + .getHeight(); + } + } + return yOffset; + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java b/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java index e688611..bb0e3d8 100644 --- a/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java +++ b/src/main/java/com/cubefury/vendingmachine/integration/nei/NeiRecipeHandler.java @@ -45,6 +45,7 @@ import codechicken.nei.PositionedStack; import codechicken.nei.recipe.GuiRecipe; import codechicken.nei.recipe.TemplateRecipeHandler; +import cpw.mods.fml.common.Optional; public class NeiRecipeHandler extends TemplateRecipeHandler { @@ -62,8 +63,6 @@ public class NeiRecipeHandler extends TemplateRecipeHandler { private Rectangle lastHoveredTextArea = null; private UUID lastHoveredQuestId = null; - private UUID lastQuestId = null; - private UUID getCurrentPlayerUUID() { if (currentPlayerId == null) { currentPlayerId = NameCache.INSTANCE.getUUIDFromPlayer(Minecraft.getMinecraft().thePlayer); @@ -194,9 +193,6 @@ public boolean isMouseOverBqCondition(int recipeIndex, int curY, UUID questId, S } public boolean isMouseOnLastHovered(GuiRecipe gui, int recipeIndex) { - VendingMachine.LOG.info("{} {}", recipeIndex, lastHoveredRecipeIndex); - VendingMachine.LOG.info(lastHoveredQuestId); - VendingMachine.LOG.info(lastHoveredTextArea); if (lastHoveredTextArea == null || lastHoveredQuestId == null || lastHoveredRecipeIndex != recipeIndex) { return false; } @@ -206,43 +202,46 @@ public boolean isMouseOnLastHovered(GuiRecipe gui, int recipeIndex) { Point offset = gui.getRecipePosition(recipeIndex); Point pos = GuiDraw.getMousePosition(); Point relMousePos = new Point(pos.x - guiLeft - offset.x, pos.y - guiTop - offset.y); - VendingMachine.LOG.info("relmousepos {}", relMousePos); return lastHoveredTextArea.contains(relMousePos); } @Override public boolean mouseClicked(GuiRecipe gui, int button, int recipeIndex) { if (super.mouseClicked(gui, button, recipeIndex)) return true; - - if (isMouseOnLastHovered(gui, recipeIndex)) { + if (VendingMachine.isBqLoaded && isMouseOnLastHovered(gui, recipeIndex)) { // prepare "Back" behavior - GuiScreen parentScreen; - if (GuiHome.bookmark instanceof GuiQuest && BQ_Settings.useBookmark) { - // back to GuiQuestLines - parentScreen = ((GuiScreenCanvas) GuiHome.bookmark).parent; - } else if (GuiHome.bookmark instanceof GuiScreenCanvas && BQ_Settings.useBookmark) { - // for example, GuiQuestLines.parent is GuiHome - // going back to home screen is not good - parentScreen = GuiHome.bookmark; - } else { - // init quest screen - parentScreen = ThemeRegistry.INSTANCE.getGui(PresetGUIs.HOME, GArgsNone.NONE); - if (BQ_Settings.useBookmark && BQ_Settings.skipHome) { - parentScreen = new GuiQuestLines(parentScreen); - } - } - GuiQuest toDisplay = new GuiQuest(parentScreen, lastHoveredQuestId); - toDisplay.setPreviousScreen(Minecraft.getMinecraft().currentScreen); - Minecraft.getMinecraft() - .displayGuiScreen(toDisplay); - if (BQ_Settings.useBookmark) { - GuiHome.bookmark = toDisplay; - } + processBqGui(); return true; } return false; } + @Optional.Method(modid = "betterquesting") + public void processBqGui() { + GuiScreen parentScreen; + if (GuiHome.bookmark instanceof GuiQuest && BQ_Settings.useBookmark) { + // back to GuiQuestLines + parentScreen = ((GuiScreenCanvas) GuiHome.bookmark).parent; + } else if (GuiHome.bookmark instanceof GuiScreenCanvas && BQ_Settings.useBookmark) { + // for example, GuiQuestLines.parent is GuiHome + // going back to home screen is not good + parentScreen = GuiHome.bookmark; + } else { + // init quest screen + parentScreen = ThemeRegistry.INSTANCE.getGui(PresetGUIs.HOME, GArgsNone.NONE); + if (BQ_Settings.useBookmark && BQ_Settings.skipHome) { + parentScreen = new GuiQuestLines(parentScreen); + } + } + GuiQuest toDisplay = new GuiQuest(parentScreen, lastHoveredQuestId); + toDisplay.setPreviousScreen(Minecraft.getMinecraft().currentScreen); + Minecraft.getMinecraft() + .displayGuiScreen(toDisplay); + if (BQ_Settings.useBookmark) { + GuiHome.bookmark = toDisplay; + } + } + @Override public void drawExtras(int recipeIndex) { CachedTradeRecipe recipe = (CachedTradeRecipe) this.arecipes.get(recipeIndex); @@ -274,7 +273,7 @@ public void drawExtras(int recipeIndex) { isMouseOverBqCondition(recipeIndex, y, questId, unformatted.toString()) ? UNDERLINE : ""); requirementString.append(unformatted); } - color = BqAdapter.INSTANCE.checkPlayerCompletedQuest(currentPlayerId, questId) + color = BqAdapter.INSTANCE.checkPlayerCompletedQuest(getCurrentPlayerUUID(), questId) ? textColorConditionSatisfied : textColorConditionUnsatisfied; } else { diff --git a/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java b/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java index 750776f..2d298f3 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java +++ b/src/main/java/com/cubefury/vendingmachine/network/PacketTypeRegistry.java @@ -15,6 +15,7 @@ import com.cubefury.vendingmachine.network.handlers.NetAvailableTradeSync; import com.cubefury.vendingmachine.network.handlers.NetBulkSync; import com.cubefury.vendingmachine.network.handlers.NetNameSync; +import com.cubefury.vendingmachine.network.handlers.NetSatisfiedQuestSync; import com.cubefury.vendingmachine.network.handlers.NetTradeDbSync; import com.cubefury.vendingmachine.network.handlers.NetTradeOutputSync; import com.cubefury.vendingmachine.network.handlers.NetTradeStateSync; @@ -31,6 +32,7 @@ public void init() { NetTradeStateSync.registerHandler(); NetTradeOutputSync.registerHandler(); NetAvailableTradeSync.registerHandler(); + NetSatisfiedQuestSync.registerHandler(); NetNameSync.registerHandler(); NetBulkSync.registerHandler(); diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetAvailableTradeSync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetAvailableTradeSync.java index 0e9ba9a..7f84010 100644 --- a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetAvailableTradeSync.java +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetAvailableTradeSync.java @@ -59,7 +59,7 @@ public static void onServer(Tuple2 message) { @SideOnly(Side.CLIENT) public static void onClient(NBTTagCompound message) { - if ( // Don't sync in SP or LAN + if ( // Don't sync in SP or LAN - will delete other player's data Minecraft.getMinecraft() .isIntegratedServerRunning() ) { diff --git a/src/main/java/com/cubefury/vendingmachine/network/handlers/NetSatisfiedQuestSync.java b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetSatisfiedQuestSync.java new file mode 100644 index 0000000..dc1a561 --- /dev/null +++ b/src/main/java/com/cubefury/vendingmachine/network/handlers/NetSatisfiedQuestSync.java @@ -0,0 +1,85 @@ +package com.cubefury.vendingmachine.network.handlers; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.util.Constants; + +import com.cubefury.vendingmachine.VendingMachine; +import com.cubefury.vendingmachine.api.network.UnserializedPacket; +import com.cubefury.vendingmachine.integration.betterquesting.BqAdapter; +import com.cubefury.vendingmachine.network.PacketSender; +import com.cubefury.vendingmachine.network.PacketTypeRegistry; +import com.cubefury.vendingmachine.storage.NameCache; +import com.cubefury.vendingmachine.util.NBTConverter; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class NetSatisfiedQuestSync { + + private static final ResourceLocation ID_NAME = new ResourceLocation("vendingmachine:questdone_sync"); + + public static void registerHandler() { + if (VendingMachine.proxy.isClient()) { + PacketTypeRegistry.INSTANCE.registerClientHandler(ID_NAME, NetSatisfiedQuestSync::onClient); + } + } + + public static void sendSync() { + MinecraftServer server = FMLCommonHandler.instance() + .getMinecraftServerInstance(); + Map> cache = BqAdapter.INSTANCE.getPlayerSatisfiedCache(); + if (server != null) { // Sync to all connected players + List players = server.getConfigurationManager().playerEntityList; + + for (EntityPlayerMP player : players) { + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(player); + NBTTagCompound payload = new NBTTagCompound(); + NBTTagList questList = new NBTTagList(); + // have to do cache null check here as it may not be initialized + if (cache != null && cache.get(playerId) != null) { + for (UUID quest : cache.get(playerId)) { + NBTTagCompound questInfo = new NBTTagCompound(); + NBTConverter.UuidValueType.QUEST.writeId(quest); + questList.appendTag(questInfo); + } + } + payload.setTag("questData", questList); + PacketSender.INSTANCE.sendToPlayers(new UnserializedPacket(ID_NAME, payload), player); + } + } + } + + @SideOnly(Side.CLIENT) + public static void onClient(NBTTagCompound message) { + if ( + Minecraft.getMinecraft() + .isIntegratedServerRunning() + ) { + return; + } + + UUID playerId = NameCache.INSTANCE.getUUIDFromPlayer(Minecraft.getMinecraft().thePlayer); + + Map> newCache = new HashMap<>(); + newCache.put(playerId, new HashSet<>()); + NBTTagList questList = message.getTagList("questData", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < questList.tagCount(); i++) { + newCache.get(playerId) + .add(NBTConverter.UuidValueType.QUEST.readId(questList.getCompoundTagAt(i))); + } + BqAdapter.INSTANCE.setPlayerSatisifedCache(newCache); + } +} diff --git a/src/main/java/com/cubefury/vendingmachine/trade/Trade.java b/src/main/java/com/cubefury/vendingmachine/trade/Trade.java index a945e3f..b0c2d51 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/Trade.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/Trade.java @@ -55,7 +55,7 @@ public void readFromNBT(NBTTagCompound nbt) { } } - @Optional.Method(modid = "NotEnoughItems") + @Optional.Method(modid = "betterquesting") @SideOnly(Side.CLIENT) public IGuiPanel getTradeGui(IGuiRect rect) { return new PanelQBTrade(rect, this); diff --git a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java index b0e08fa..030fd7a 100644 --- a/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java +++ b/src/main/java/com/cubefury/vendingmachine/trade/TradeGroup.java @@ -29,6 +29,8 @@ public class TradeGroup { private final Set requirementSet = new HashSet<>(); // List of completed conditions for each player + // This is only updated server-side, since players only need to know what trades + // they have and their status. private final Map> playerDone = new HashMap<>(); // List of players with trade history @@ -44,25 +46,9 @@ public String toString() { return this.id.toString(); } - // Check if trade would be available upon adding this condition - // Used for questbook trade GUI display - public boolean isAvailableUponSatisfied(UUID player, ICondition c) { - Set tmp = new HashSet<>(); - synchronized (playerDone) { - if (playerDone.containsKey(player) && playerDone.get(player) == null) { - tmp.addAll(playerDone.get(player)); - } - } - tmp.add(c); - return tmp.equals(requirementSet); - - } - public void addSatisfiedCondition(UUID player, ICondition c) { synchronized (playerDone) { - if (!playerDone.containsKey(player) || playerDone.get(player) == null) { - playerDone.put(player, new HashSet<>()); - } + playerDone.computeIfAbsent(player, k -> new HashSet<>()); playerDone.get(player) .add(c); if ( @@ -141,7 +127,6 @@ public void setTradeState(UUID player, TradeHistory history) { } public boolean attemptExecuteTrade(UUID player) { - List availableTrades = TradeManager.INSTANCE.getTrades(player); for (TradeGroupWrapper trade : availableTrades) { if (trade == null) { // shouldn't happen