{"action":"created","comment":{"url":"https://api.github.com/repos/runelite/runelite/pulls/comments/196404312","pull_request_review_id":129948262,"id":196404312,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5NjQwNDMxMg==","diff_hunk":"@@ -0,0 +1,1365 @@\n+/*\n+ * Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions are met:\n+ *\n+ * 1. Redistributions of source code must retain the above copyright notice, this\n+ *    list of conditions and the following disclaimer.\n+ * 2. Redistributions in binary form must reproduce the above copyright notice,\n+ *    this list of conditions and the following disclaimer in the documentation\n+ *    and/or other materials provided with the distribution.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+package net.runelite.client.plugins.bosslogger;\n+\n+import com.google.common.eventbus.Subscribe;\n+import com.google.inject.Provides;\n+\n+import java.awt.Color;\n+import java.awt.image.BufferedImage;\n+import java.io.BufferedReader;\n+import java.io.BufferedWriter;\n+import java.io.File;\n+import java.io.FileNotFoundException;\n+import java.io.FileReader;\n+import java.io.FileWriter;\n+import java.io.IOException;\n+import java.util.ArrayList;\n+import java.util.HashMap;\n+import java.util.HashSet;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Set;\n+import java.util.concurrent.Executors;\n+import java.util.concurrent.ScheduledExecutorService;\n+import java.util.concurrent.TimeUnit;\n+import java.util.regex.Matcher;\n+import java.util.regex.Pattern;\n+import java.util.stream.Collectors;\n+import javax.imageio.ImageIO;\n+import javax.inject.Inject;\n+import javax.swing.SwingUtilities;\n+\n+import lombok.extern.slf4j.Slf4j;\n+import net.runelite.api.Actor;\n+import net.runelite.api.ChatMessageType;\n+import net.runelite.api.Client;\n+import net.runelite.api.Constants;\n+import net.runelite.api.InventoryID;\n+import net.runelite.api.Item;\n+import net.runelite.api.ItemComposition;\n+import net.runelite.api.ItemContainer;\n+import net.runelite.api.ItemID;\n+import net.runelite.api.ItemLayer;\n+import net.runelite.api.NPC;\n+import net.runelite.api.NPCComposition;\n+import net.runelite.api.Node;\n+import net.runelite.api.NpcID;\n+import net.runelite.api.Tile;\n+import net.runelite.api.coords.WorldPoint;\n+import net.runelite.api.events.ActorDespawned;\n+import net.runelite.api.events.ChatMessage;\n+import net.runelite.api.events.ConfigChanged;\n+import net.runelite.api.events.GameTick;\n+import net.runelite.api.events.ItemLayerChanged;\n+import net.runelite.api.events.WidgetLoaded;\n+import net.runelite.api.widgets.Widget;\n+import net.runelite.api.widgets.WidgetID;\n+import net.runelite.api.widgets.WidgetInfo;\n+import net.runelite.client.Notifier;\n+import net.runelite.client.chat.ChatMessageBuilder;\n+import net.runelite.client.chat.ChatMessageManager;\n+import net.runelite.client.chat.QueuedMessage;\n+import net.runelite.client.config.ConfigManager;\n+import net.runelite.client.game.ItemManager;\n+import net.runelite.client.plugins.Plugin;\n+import net.runelite.client.plugins.PluginDescriptor;\n+import net.runelite.client.ui.NavigationButton;\n+import net.runelite.client.ui.PluginToolbar;\n+import net.runelite.client.util.Text;\n+import net.runelite.http.api.RuneLiteAPI;\n+\n+import static net.runelite.client.RuneLite.LOOTS_DIR;\n+\n+@PluginDescriptor(\n+\tname = \"Boss Logger\"\n+)\n+\n+@Slf4j\n+public class BossLoggerPlugin extends Plugin\n+{\n+\t@Inject\n+\tprivate Client client;\n+\n+\t@Inject\n+\tprivate ItemManager itemManager;\n+\n+\t@Inject\n+\tprivate BossLoggerConfig bossLoggerConfig;\n+\n+\t@Inject\n+\tprivate Notifier notifier;\n+\n+\t@Inject\n+\tprivate ChatMessageManager chatMessageManager;\n+\n+\t@Inject\n+\tprivate PluginToolbar pluginToolbar;\n+\n+\tprivate File playerFolder;\t// Where we are storing files\n+\tprivate static final Pattern NUMBER_PATTERN = Pattern.compile(\"([0-9]+)\");\n+\tprivate static final Pattern BOSS_NAME_PATTERN = Pattern.compile(\"Your (.*) kill count is:\");\n+\tprivate static final Pattern PET_RECEIVED_PATTERN = Pattern.compile(\"You have a funny feeling like \");\n+\tprivate static final Pattern PET_RECEIVED_INVENTORY_PATTERN = Pattern.compile(\"You feel something weird sneaking into your backpack.\");\n+\tprivate String messageColor = \"\"; // in-game chat message color\n+\n+\tprivate BossLoggerPanel panel;\n+\tprivate NavigationButton navButton;\n+\n+\t// Mapping Variables\n+\tprivate Map<String, Boolean> recordingMap = new HashMap<>(); \t\t\t// Store config recording value by tab boss name\n+\tprivate Map<String, ArrayList<LootEntry>> lootMap = new HashMap<>();\t// Store loot entries by boss name\n+\tprivate Map<String, Integer> killcountMap = new HashMap<>(); \t\t\t// Store boss kill count by name\n+\tprivate Map<String, String> filenameMap = new HashMap<>(); \t\t\t\t// Stores filename for each boss name\n+\n+\t// Loot Finding Variables\n+\tprivate Set<Tile> changedItemLayerTiles = new HashSet<Tile>();\t\t\t\t// Stores tiles that have had ItemLayer changes\n+\tprivate WorldPoint[] deathLocations;\t\t\t\t\t\t\t\t\t\t// Stores NPC Death Worldpoints\n+\tprivate Map<WorldPoint, Map<Integer, Integer>> itemArray = new HashMap<>();\t// Stores item map for a specific WorldPoint\n+\tprivate Map<Actor, Actor> actors = new HashMap<>();\t\t\t\t\t\t\t// Stores Actors we've interacted with to check for their deaths\n+\n+\t// Variables for handling NPC death\n+\tprivate String deathName;\t\t\t\t// NPC Name\n+\tprivate String lastBossKilled;\t\t\t// NPC Name, saved for potential pet logging issues\n+\tprivate int deathSize;\t\t\t\t\t// NPC Size\n+\tprivate int deathID;\t\t\t\t\t// NPC ID\n+\tprivate WorldPoint deathSpot;\t\t\t// NPC Death WorldPoint\n+\tprivate WorldPoint playerLocation;\t\t// Players WorldPoint at time of NPC death animation\n+\tprivate boolean gotPet = false;\t\t\t// Got the pet chat message?\n+\n+\t// Watching flags (actor death/changed item layer)\n+\tprivate boolean watching = false;\t\t\t\t// Watching for ActorDespawn?\n+\tprivate boolean watchingItemLayers = false;\t\t// Watching for ItemLayerChanged?\n+\n+\t@Provides\n+\tBossLoggerConfig provideConfig(ConfigManager configManager)\n+\t{\n+\t\treturn configManager.getConfig(BossLoggerConfig.class);\n+\t}\n+\n+\t@Override\n+\tprotected void startUp()\n+\t{\n+\t\tif (bossLoggerConfig.showLootTotals())\n+\t\t{\n+\t\t\t// Waits 2 seconds, helps ensure itemManager is loaded\n+\t\t\t// Client cache loading is async, plugins can be loaded before it is finished\n+\t\t\tScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);\n+\t\t\tscheduler.schedule(() -> SwingUtilities.invokeLater(this::createPanel), 2, TimeUnit.SECONDS);\n+\t\t}\n+\t\tinit();\n+\n+\t\t// Ensure Loot Directory has been created\n+\t\tLOOTS_DIR.mkdir();\n+\t}\n+\n+\t@Override\n+\tprotected void shutDown() throws Exception\n+\t{\n+\t\tremovePanel();\n+\t}\n+\n+\t// Check for loot that is rewarded via interfaces\n+\t@Subscribe\n+\tpublic void onWidgetLoaded(WidgetLoaded event)\n+\t{\n+\t\t// Barrows Chests\n+\t\tif (event.getGroupId() == WidgetID.BARROWS_REWARD_GROUP_ID && bossLoggerConfig.recordBarrowsChest())\n+\t\t{\n+\t\t\tItemContainer rewardContainer = client.getItemContainer(InventoryID.REWARD_CHEST);\n+\t\t\tif (rewardContainer == null)\n+\t\t\t{\n+\t\t\t\tBossLoggedAlert(\"Couldn't find Barrows Chest Loot\");\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t\tint kc = killcountMap.get(\"BARROWS\");\n+\t\t\tLootEntry entry = createLootEntry(kc, rewardContainer);\n+\t\t\taddLootEntry(\"Barrows\", entry);\n+\t\t\tBossLoggedAlert(\"Barrows Chest added to log.\");\n+\t\t}\n+\n+\t\t// Raids Chest\n+\t\tif (event.getGroupId() == WidgetID.RAIDS_REWARD_GROUP_ID && bossLoggerConfig.recordRaidsChest())\n+\t\t{\n+\t\t\tItemContainer rewardContainer = client.getItemContainer(InventoryID.CHAMBERS_OF_XERIC_CHEST);\n+\t\t\tif (rewardContainer == null)\n+\t\t\t{\n+\t\t\t\tBossLoggedAlert(\"Couldn't find Raids Chest Loot\");\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t\tint kc = killcountMap.get(\"RAIDS\");\n+\t\t\tLootEntry entry = createLootEntry(kc, rewardContainer);\n+\t\t\tif (gotPet)\n+\t\t\t{\n+\t\t\t\tentry.drops.add(handlePet(\"Raids\"));\n+\t\t\t}\n+\t\t\taddLootEntry(\"Raids\", entry);\n+\t\t\tBossLoggedAlert(\"Raids Chest Loot added to log.\");\n+\t\t}\n+\n+\t\t// Theater of Blood Chest (Raids 2)\n+\t\tif (event.getGroupId() == WidgetID.THEATER_OF_BLOOD_GROUP_ID && bossLoggerConfig.recordTobChest())\n+\t\t{\n+\t\t\tItemContainer rewardContainer = client.getItemContainer(InventoryID.THEATER_OF_BLOOD_CHEST);\n+\t\t\tif (rewardContainer == null)\n+\t\t\t{\n+\t\t\t\tBossLoggedAlert(\"Couldn't find Theater of Blood Chest Loot\");\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t\tint kc = killcountMap.get(\"RAIDS 2\");\n+\t\t\tLootEntry entry = createLootEntry(kc, rewardContainer);\n+\t\t\tif (gotPet)\n+\t\t\t{\n+\t\t\t\tentry.drops.add(handlePet(\"Raids 2\"));\n+\t\t\t}\n+\t\t\taddLootEntry(\"Raids 2\", entry);\n+\t\t\tBossLoggedAlert(\"Theater of Blood Chest Loot added to log.\");\n+\t\t}\n+\n+\n+\t\t// Received unsired loot?\n+\t\tif (event.getGroupId() == WidgetID.DIALOG_SPRITE_GROUP_ID)\n+\t\t{\n+\t\t\tWidget sprite = client.getWidget(WidgetInfo.DIALOG_SPRITE);\n+\t\t\tint itemID = sprite.getItemId();\n+\t\t\tswitch (itemID)\n+\t\t\t{\n+\t\t\t\tcase ItemID.BLUDGEON_CLAW:\n+\t\t\t\tcase ItemID.BLUDGEON_SPINE:\n+\t\t\t\tcase ItemID.BLUDGEON_AXON:\n+\t\t\t\tcase ItemID.ABYSSAL_DAGGER:\n+\t\t\t\tcase ItemID.ABYSSAL_WHIP:\n+\t\t\t\tcase ItemID.ABYSSAL_ORPHAN:\n+\t\t\t\tcase ItemID.ABYSSAL_HEAD:\n+\t\t\t\t\tbreak;\n+\t\t\t\tdefault:\n+\t\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\t\treceivedUnsiredLoot(itemID);\n+\t\t}\n+\t}\n+\n+\t//\n+\t// Recreate the `onActorDeath` event\n+\t//\n+\n+\t@Subscribe\n+\tpublic void onGameTick(GameTick event)\n+\t{\n+\t\tcheckInteractingNpcs();\n+\t\tcheckDeadActors();\n+\t}\n+\n+\t// Find the NPC the player is interacting with\n+\tprivate void checkInteractingNpcs()\n+\t{\n+\t\tActor interacting = client.getLocalPlayer().getInteracting();\n+\n+\t\tif (interacting instanceof NPC)\n+\t\t{\n+\t\t\t// Only add if the NPC is interacting us\n+\t\t\tActor target = interacting.getInteracting();\n+\t\t\tif (target != null && target.getName().equals(client.getLocalPlayer().getName()))\n+\t\t\t{\n+\n+\t\t\t\tString name = interacting.getName();\n+\t\t\t\tBoolean flag = recordingMap.get(name.toUpperCase());\n+\t\t\t\t// Special Cases\n+\t\t\t\tif (name.equals(\"Dusk\"))\n+\t\t\t\t{\n+\t\t\t\t\tflag = recordingMap.get(\"GROTESQUE GUARDIANS\");\n+\t\t\t\t}\n+\t\t\t\tif (flag != null && flag)\n+\t\t\t\t{\n+\t\t\t\t\tactors.putIfAbsent(interacting, interacting);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t// Did any of the NPCs we interacted with die?\n+\tprivate void checkDeadActors()\n+\t{\n+\t\tfor (Map.Entry<Actor, Actor> entry : actors.entrySet())\n+\t\t{\n+\t\t\tActor actor = entry.getKey();\n+\t\t\tif (actor.getHealthRatio() == 0)\n+\t\t\t{\n+\t\t\t\tonActorDeath(actor);\n+\t\t\t\tactors.remove(entry.getKey());\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t// Use to be a subscribe event but was removed on 5/27/2018. Recreated above.\n+\tprivate void onActorDeath(Actor actor)\n+\t{\n+\t\t// Are kills for this Boss being recorded?\n+\t\tBoolean flag = recordingMap.get(actor.getName().toUpperCase());\n+\t\t// Grotesque Guardians Handling\n+\t\tif (actor.getName().equals(\"Dusk\"))\n+\t\t{\n+\t\t\tflag = recordingMap.get(\"GROTESQUE GUARDIANS\");\n+\t\t}\n+\t\t// Can't find NPC in recording map or should not be recording the npc loot\n+\t\tif (flag == null || !flag)\n+\t\t\treturn;\n+\n+\t\t// Yes they are\n+\t\tNPC npc = (NPC) actor;\n+\t\tNPCComposition comp = npc.getComposition();\n+\t\t// Record Death info in global variables\n+\t\tdeathSize = comp.getSize();\t\t\t\t\t\t\t\t\t\t// NPC Size\n+\t\tdeathSpot = actor.getWorldLocation();   \t\t\t\t\t\t// Death Location\n+\t\tplayerLocation = client.getLocalPlayer().getWorldLocation(); \t// Player Location on NPC Death\n+\t\tdeathName = actor.getName();\t\t\t\t\t\t\t\t\t// NPC Name\n+\t\tdeathID = npc.getId();\t\t\t\t\t\t\t\t\t\t\t// NPC ID\n+\t\tlastBossKilled = actor.getName();\t\t\t\t\t\t\t\t// NPC Name (stored for pets)\n+\n+\t\t// Get possible death locations\n+\t\tdeathLocations = getExpectedLootPoints(npc, deathSpot);\n+\t\t// Loop over each death location and store its current item contents\n+\t\tfor (WorldPoint point : deathLocations)\n+\t\t{\n+\t\t\tif (point != null)\n+\t\t\t{\n+\t\t\t\tTile tile = getLootTile(point);\n+\t\t\t\tMap<Integer, Integer> items = createItemMap(tile.getItemLayer());\n+\t\t\t\tif (items.size() > 0)\n+\t\t\t\t\titemArray.put(point, items);\n+\t\t\t}\n+\t\t}\n+\n+\t\t// Start watching for NPC Despawn\n+\t\twatching = true;\n+\t\tif (deathName.equals(\"Zulrah\"))\n+\t\t\twatchingItemLayers = true;\n+\t}\n+\n+\t@Subscribe\n+\tpublic void onActorDespawn(ActorDespawned despawned)\n+\t{\n+\t\tActor actor = actors.get(despawned.getActor());\n+\t\tif (actor != null)\n+\t\t{\n+\t\t\tactors.remove(actor);\n+\t\t}\n+\n+\t\tif (watching)\n+\t\t{\n+\t\t\tActor npc = despawned.getActor();\n+\t\t\tif (npc.getName().equals(deathName) && npc.getWorldLocation().equals(deathSpot))\n+\t\t\t{\n+\t\t\t\t// Correct Boss Despawned\n+\t\t\t\twatching = false;\n+\t\t\t\t// Find the drops from the correct tile and return them in the correct format\n+\t\t\t\tArrayList<DropEntry> drops = createDropEntryArray((NPC) npc);\n+\t\t\t\t// Specific use case(s)\n+\t\t\t\tString npcName = npc.getName();\n+\t\t\t\tif (npcName.equals(\"Dusk\"))\n+\t\t\t\t\tnpcName = \"Grotesque Guardians\";\n+\n+\t\t\t\tif (drops != null)\n+\t\t\t\t{\n+\t\t\t\t\t// Add LootEntry by Boss Name\n+\t\t\t\t\tAddBossLootEntry(npcName, drops);\n+\t\t\t\t}\n+\t\t\t\telse\n+\t\t\t\t{\n+\t\t\t\t\tlog.debug(\"Error creating DropEntry array\");\n+\t\t\t\t}\n+\n+\t\t\t\t// Reset Variables\n+\t\t\t\tdeathSize = -1;\t\t\t// NPC Size\n+\t\t\t\tdeathSpot = null;\t\t// NPC Death WP\n+\t\t\t\tplayerLocation = null;\t// Player WP on NPC Death\n+\t\t\t\tdeathName = null;\t\t// NPC Name\n+\t\t\t\tdeathID = -1;\t\t\t// NPC ID\n+\n+\t\t\t\t// Clear maps used for creating the DropEntry array\n+\t\t\t\tchangedItemLayerTiles.clear();\t// Changed Tiles\n+\t\t\t\tdeathLocations = null;\t\t\t// Possible Loot Tiles\n+\t\t\t\titemArray.clear();\t\t\t\t// Items on Possible Loot Tiles\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t@Subscribe\n+\tpublic void onItemLayerChanged(ItemLayerChanged event)\n+\t{\n+\t\tif (watchingItemLayers)\n+\t\t{\n+\t\t\tchangedItemLayerTiles.add(event.getTile());\n+\t\t}\n+\t}\n+\n+\t@Subscribe\n+\tpublic void onConfigChanged(ConfigChanged event)\n+\t{\n+\t\t// Only update if our plugin config was changed\n+\t\tif (!event.getGroup().equals(\"bosslogger\"))\n+\t\t{\n+\t\t\treturn;\n+\t\t}\n+\n+\t\thandleConfigChanged(event.getKey());\n+\t}\n+\n+\t// Searches chat messages for kill count value or pet drop\n+\t@Subscribe\n+\tpublic void onChatMessage(ChatMessage event)\n+\t{\n+\t\tif (event.getType() != ChatMessageType.SERVER && event.getType() != ChatMessageType.FILTERED)\n+\t\t{\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tString chatMessage = event.getMessage();\n+\n+\t\t// Barrows KC\n+\t\tif (chatMessage.startsWith(\"Your Barrows chest count is\"))\n+\t\t{\n+\t\t\tMatcher m = NUMBER_PATTERN.matcher(Text.removeTags(chatMessage));\n+\t\t\tif (m.find())\n+\t\t\t{\n+\t\t\t\tkillcountMap.put(\"BARROWS\", Integer.valueOf(m.group()));\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t}\n+\n+\t\t// Raids KC\n+\t\tif (chatMessage.startsWith(\"Your completed Chambers of Xeric count is:\"))\n+\t\t{\n+\t\t\tMatcher m = NUMBER_PATTERN.matcher(Text.removeTags(chatMessage));\n+\t\t\tif (m.find())\n+\t\t\t{\n+\t\t\t\tkillcountMap.put(\"RAIDS\", Integer.valueOf(m.group()));\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t}\n+\n+\t\t// Raids KC\n+\t\tif (chatMessage.startsWith(\"Your completed Theatre of Blood count is:\"))\n+\t\t{\n+\t\t\tMatcher m = NUMBER_PATTERN.matcher(Text.removeTags(chatMessage));\n+\t\t\tif (m.find())\n+\t\t\t{\n+\t\t\t\tkillcountMap.put(\"RAIDS 2\", Integer.valueOf(m.group()));\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t}\n+\n+\t\t// Handle all other boss\n+\t\tMatcher boss = BOSS_NAME_PATTERN.matcher(Text.removeTags(chatMessage));\n+\t\tif (boss.find())\n+\t\t{\n+\t\t\tString bossName = boss.group(1);\n+\t\t\tMatcher m = NUMBER_PATTERN.matcher(Text.removeTags(chatMessage));\n+\t\t\tif (!m.find())\n+\t\t\t\treturn;\n+\t\t\tint KC = Integer.valueOf(m.group());\n+\t\t\tkillcountMap.put(bossName.toUpperCase(), KC);\n+\t\t}\n+\n+\t\t// Pet Drop\n+\t\tMatcher pet1 = PET_RECEIVED_PATTERN.matcher(Text.removeTags(chatMessage));\n+\t\tMatcher pet2 = PET_RECEIVED_INVENTORY_PATTERN.matcher(Text.removeTags(chatMessage));\n+\t\tif (pet1.find() || pet2.find())\n+\t\t{\n+\t\t\tgotPet = true;\n+\t\t}\n+\t}\n+\n+\tprivate void resetStoredData()\n+\t{\n+\t\tfor (Tab tab : Tab.values())\n+\t\t{\n+\t\t\tlootMap.put(tab.getBossName().toUpperCase(), new ArrayList<>());\n+\t\t}\n+\t}\n+\n+\tprivate void updateTabData()\n+\t{\n+\t\t// Do nothing is panel isn't enabled\n+\t\tif (!bossLoggerConfig.showLootTotals())\n+\t\t{\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tfor (Tab tab : Tab.values())\n+\t\t{\n+\t\t\t// Only update tabs if the tabs are being shown.\n+\t\t\tif (isBeingRecorded(tab.getName()))\n+\t\t\t{\n+\t\t\t\tpanel.updateTab(tab.getName());\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tprivate void init()\n+\t{\n+\t\t// Create maps for easy management of certain features\n+\t\tMap<String, Boolean> mapRecording = new HashMap<>();\n+\t\tMap<String, ArrayList<LootEntry>> mapLoot = new HashMap<>();\n+\t\tMap<String, Integer> mapKillcount = new HashMap<>();\n+\t\tMap<String, String> mapFilename = new HashMap<>();\n+\t\tfor (Tab tab : Tab.values())\n+\t\t{\n+\t\t\tString bossName = tab.getBossName().toUpperCase();\n+\t\t\t// Is Boss being recorded?\n+\t\t\tmapRecording.put(bossName, isBeingRecorded(tab.getName()));\n+\t\t\t// Loot Entries by Tab Name\n+\t\t\tArrayList<LootEntry> array = new ArrayList<LootEntry>();","path":"runelite-client/src/main/java/net/runelite/client/plugins/bosslogger/BossLoggerPlugin.java","position":535,"original_position":535,"commit_id":"e48afaf8080aedcd333a57d526cbfc164e71c519","original_commit_id":"e48afaf8080aedcd333a57d526cbfc164e71c519","user":{"login":"NathenSample","id":9803552,"node_id":"MDQ6VXNlcjk4MDM1NTI=","avatar_url":"https://avatars1.githubusercontent.com/u/9803552?v=4","gravatar_id":"","url":"https://api.github.com/users/NathenSample","html_url":"https://github.com/NathenSample","followers_url":"https://api.github.com/users/NathenSample/followers","following_url":"https://api.github.com/users/NathenSample/following{/other_user}","gists_url":"https://api.github.com/users/NathenSample/gists{/gist_id}","starred_url":"https://api.github.com/users/NathenSample/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NathenSample/subscriptions","organizations_url":"https://api.github.com/users/NathenSample/orgs","repos_url":"https://api.github.com/users/NathenSample/repos","events_url":"https://api.github.com/users/NathenSample/events{/privacy}","received_events_url":"https://api.github.com/users/NathenSample/received_events","type":"User","site_admin":false},"body":"Nitpicking but declare the interface, provide an implementation\r\n\r\n`List<LootEntry> array = new ArrayList<LootEntry>();`","created_at":"2018-06-19T12:37:48Z","updated_at":"2018-06-19T12:37:49Z","html_url":"https://github.com/runelite/runelite/pull/3545#discussion_r196404312","pull_request_url":"https://api.github.com/repos/runelite/runelite/pulls/3545","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/runelite/runelite/pulls/comments/196404312"},"html":{"href":"https://github.com/runelite/runelite/pull/3545#discussion_r196404312"},"pull_request":{"href":"https://api.github.com/repos/runelite/runelite/pulls/3545"}}},"pull_request":{"url":"https://api.github.com/repos/runelite/runelite/pulls/3545","id":191903229,"node_id":"MDExOlB1bGxSZXF1ZXN0MTkxOTAzMjI5","html_url":"https://github.com/runelite/runelite/pull/3545","diff_url":"https://github.com/runelite/runelite/pull/3545.diff","patch_url":"https://github.com/runelite/runelite/pull/3545.patch","issue_url":"https://api.github.com/repos/runelite/runelite/issues/3545","number":3545,"state":"open","locked":false,"title":"Boss Logger Plugin","user":{"login":"TheStonedTurtle","id":29030969,"node_id":"MDQ6VXNlcjI5MDMwOTY5","avatar_url":"https://avatars0.githubusercontent.com/u/29030969?v=4","gravatar_id":"","url":"https://api.github.com/users/TheStonedTurtle","html_url":"https://github.com/TheStonedTurtle","followers_url":"https://api.github.com/users/TheStonedTurtle/followers","following_url":"https://api.github.com/users/TheStonedTurtle/following{/other_user}","gists_url":"https://api.github.com/users/TheStonedTurtle/gists{/gist_id}","starred_url":"https://api.github.com/users/TheStonedTurtle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TheStonedTurtle/subscriptions","organizations_url":"https://api.github.com/users/TheStonedTurtle/orgs","repos_url":"https://api.github.com/users/TheStonedTurtle/repos","events_url":"https://api.github.com/users/TheStonedTurtle/events{/privacy}","received_events_url":"https://api.github.com/users/TheStonedTurtle/received_events","type":"User","site_admin":false},"body":"Records loot received from boss monsters in a `.log` file inside the .runelite directory.\r\n\r\n* Tracks loot received from the boss whether you looted it or not.\r\n* Has a panel showing all loot and the price of each item, see picture(s) below.\r\n* Tracks Unique Items from each boss within panel, see picture(s) below.\r\n* In-game and system notification alerts with toggle options\r\n* Records the following bosses:\r\n\t* Armadyl \r\n\t* Abyssal Sire\r\n\t* Bandos - TESTED\r\n\t* Barrows Chests - TESTED\r\n\t* Callisto\r\n\t* Cerberus - TESTED\r\n\t* Chaos Elemental - TESTED\r\n\t* Chaos Fanatic\r\n\t* Corporeal Beast\r\n\t* Crazy Archaeologist - TESTED\r\n\t* Dag Rex\r\n\t* Dag Prime\r\n\t* Dag Supreme\r\n\t* Giant Mole - TESTED\r\n\t* Grotesque Guardians - TESTED\r\n\t* Kalphite Queen - TESTED\r\n\t* King black dragon - TESTED\r\n\t* Kraken\r\n\t* Saradomin - TESTED\r\n\t* Scorpia - TESTED\r\n\t* Skotizo - TESTED\r\n\t* Raids\r\n\t* Thermonuclear Smoke Devil - TESTED\r\n\t* Vet'ion\r\n\t* Venenatis\r\n\t* Vorkath - TESTED\r\n\t* Zammy - TESTED\r\n\t* Zulrah - TESTED\r\n\r\nI still need to test some bosses and the tracking system for exchanging sires at the pool. The list above has the currently tested bosses, if anyone could help with testing that would be greatly appreciated.\r\n\r\nThe goal is to eventually use the functionality from @WooxSolo's droplogger for finding the correct loot tile but I have a feeling that it may be implemented as part of the API. Because of this some of the loot finding code has been pulled from Woox's plugin.\r\n\r\nPanel Gif:\r\n![2018-05-24_03-56-20](https://user-images.githubusercontent.com/29030969/40814228-ddd6a25c-64f3-11e8-95b8-258e9916d43e.gif)\r\n\r\nPanel with some uniques received:\r\n![javaw_2018-05-31_16-48-45](https://user-images.githubusercontent.com/29030969/40814213-c2a51edc-64f3-11e8-9383-0fa8b7214416.png)\r\n\r\nExample In-game message:\r\n![java_2018-05-28_13-23-01](https://user-images.githubusercontent.com/29030969/40814260-0b449d02-64f4-11e8-8ffe-105501e9c60d.png)\r\n\r\n\r\nNOTES: \r\n* This data is stored in a folder with the same name as your ingame username. When you change your username you will need to manually update this folder or a new folder will be created.\r\n* Some of the bosses were tested prior to the removal of `onActorDeath` but i believe my work around will not affect the loot tile tracking system.\r\n* This is my firt time programming in Java so there is probably some code simplifications/optimizations that can be made.","created_at":"2018-06-01T00:00:24Z","updated_at":"2018-06-19T12:37:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"381918d5f8dadee62969e7aa562b03aa03405165","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":771846940,"node_id":"MDU6TGFiZWw3NzE4NDY5NDA=","url":"https://api.github.com/repos/runelite/runelite/labels/feature-request","name":"feature-request","color":"fbca04","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/runelite/runelite/pulls/3545/commits","review_comments_url":"https://api.github.com/repos/runelite/runelite/pulls/3545/comments","review_comment_url":"https://api.github.com/repos/runelite/runelite/pulls/comments{/number}","comments_url":"https://api.github.com/repos/runelite/runelite/issues/3545/comments","statuses_url":"https://api.github.com/repos/runelite/runelite/statuses/e48afaf8080aedcd333a57d526cbfc164e71c519","head":{"label":"TheStonedTurtle:master","ref":"master","sha":"e48afaf8080aedcd333a57d526cbfc164e71c519","user":{"login":"TheStonedTurtle","id":29030969,"node_id":"MDQ6VXNlcjI5MDMwOTY5","avatar_url":"https://avatars0.githubusercontent.com/u/29030969?v=4","gravatar_id":"","url":"https://api.github.com/users/TheStonedTurtle","html_url":"https://github.com/TheStonedTurtle","followers_url":"https://api.github.com/users/TheStonedTurtle/followers","following_url":"https://api.github.com/users/TheStonedTurtle/following{/other_user}","gists_url":"https://api.github.com/users/TheStonedTurtle/gists{/gist_id}","starred_url":"https://api.github.com/users/TheStonedTurtle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TheStonedTurtle/subscriptions","organizations_url":"https://api.github.com/users/TheStonedTurtle/orgs","repos_url":"https://api.github.com/users/TheStonedTurtle/repos","events_url":"https://api.github.com/users/TheStonedTurtle/events{/privacy}","received_events_url":"https://api.github.com/users/TheStonedTurtle/received_events","type":"User","site_admin":false},"repo":{"id":133862633,"node_id":"MDEwOlJlcG9zaXRvcnkxMzM4NjI2MzM=","name":"runelite","full_name":"TheStonedTurtle/runelite","owner":{"login":"TheStonedTurtle","id":29030969,"node_id":"MDQ6VXNlcjI5MDMwOTY5","avatar_url":"https://avatars0.githubusercontent.com/u/29030969?v=4","gravatar_id":"","url":"https://api.github.com/users/TheStonedTurtle","html_url":"https://github.com/TheStonedTurtle","followers_url":"https://api.github.com/users/TheStonedTurtle/followers","following_url":"https://api.github.com/users/TheStonedTurtle/following{/other_user}","gists_url":"https://api.github.com/users/TheStonedTurtle/gists{/gist_id}","starred_url":"https://api.github.com/users/TheStonedTurtle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TheStonedTurtle/subscriptions","organizations_url":"https://api.github.com/users/TheStonedTurtle/orgs","repos_url":"https://api.github.com/users/TheStonedTurtle/repos","events_url":"https://api.github.com/users/TheStonedTurtle/events{/privacy}","received_events_url":"https://api.github.com/users/TheStonedTurtle/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/TheStonedTurtle/runelite","description":"Open source Old School RuneScape client","fork":true,"url":"https://api.github.com/repos/TheStonedTurtle/runelite","forks_url":"https://api.github.com/repos/TheStonedTurtle/runelite/forks","keys_url":"https://api.github.com/repos/TheStonedTurtle/runelite/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TheStonedTurtle/runelite/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TheStonedTurtle/runelite/teams","hooks_url":"https://api.github.com/repos/TheStonedTurtle/runelite/hooks","issue_events_url":"https://api.github.com/repos/TheStonedTurtle/runelite/issues/events{/number}","events_url":"https://api.github.com/repos/TheStonedTurtle/runelite/events","assignees_url":"https://api.github.com/repos/TheStonedTurtle/runelite/assignees{/user}","branches_url":"https://api.github.com/repos/TheStonedTurtle/runelite/branches{/branch}","tags_url":"https://api.github.com/repos/TheStonedTurtle/runelite/tags","blobs_url":"https://api.github.com/repos/TheStonedTurtle/runelite/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TheStonedTurtle/runelite/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TheStonedTurtle/runelite/git/refs{/sha}","trees_url":"https://api.github.com/repos/TheStonedTurtle/runelite/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TheStonedTurtle/runelite/statuses/{sha}","languages_url":"https://api.github.com/repos/TheStonedTurtle/runelite/languages","stargazers_url":"https://api.github.com/repos/TheStonedTurtle/runelite/stargazers","contributors_url":"https://api.github.com/repos/TheStonedTurtle/runelite/contributors","subscribers_url":"https://api.github.com/repos/TheStonedTurtle/runelite/subscribers","subscription_url":"https://api.github.com/repos/TheStonedTurtle/runelite/subscription","commits_url":"https://api.github.com/repos/TheStonedTurtle/runelite/commits{/sha}","git_commits_url":"https://api.github.com/repos/TheStonedTurtle/runelite/git/commits{/sha}","comments_url":"https://api.github.com/repos/TheStonedTurtle/runelite/comments{/number}","issue_comment_url":"https://api.github.com/repos/TheStonedTurtle/runelite/issues/comments{/number}","contents_url":"https://api.github.com/repos/TheStonedTurtle/runelite/contents/{+path}","compare_url":"https://api.github.com/repos/TheStonedTurtle/runelite/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TheStonedTurtle/runelite/merges","archive_url":"https://api.github.com/repos/TheStonedTurtle/runelite/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TheStonedTurtle/runelite/downloads","issues_url":"https://api.github.com/repos/TheStonedTurtle/runelite/issues{/number}","pulls_url":"https://api.github.com/repos/TheStonedTurtle/runelite/pulls{/number}","milestones_url":"https://api.github.com/repos/TheStonedTurtle/runelite/milestones{/number}","notifications_url":"https://api.github.com/repos/TheStonedTurtle/runelite/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TheStonedTurtle/runelite/labels{/name}","releases_url":"https://api.github.com/repos/TheStonedTurtle/runelite/releases{/id}","deployments_url":"https://api.github.com/repos/TheStonedTurtle/runelite/deployments","created_at":"2018-05-17T20:01:51Z","updated_at":"2018-06-18T13:55:17Z","pushed_at":"2018-06-19T12:07:16Z","git_url":"git://github.com/TheStonedTurtle/runelite.git","ssh_url":"git@github.com:TheStonedTurtle/runelite.git","clone_url":"https://github.com/TheStonedTurtle/runelite.git","svn_url":"https://github.com/TheStonedTurtle/runelite","homepage":"https://runelite.net","size":16520,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"bsd-2-clause","name":"BSD 2-Clause \"Simplified\" License","spdx_id":"BSD-2-Clause","url":"https://api.github.com/licenses/bsd-2-clause","node_id":"MDc6TGljZW5zZTQ="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"real-master"}},"base":{"label":"runelite:master","ref":"master","sha":"3b17c3e892abd442e210df906ba111111b6b218b","user":{"login":"runelite","id":13814776,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzODE0Nzc2","avatar_url":"https://avatars1.githubusercontent.com/u/13814776?v=4","gravatar_id":"","url":"https://api.github.com/users/runelite","html_url":"https://github.com/runelite","followers_url":"https://api.github.com/users/runelite/followers","following_url":"https://api.github.com/users/runelite/following{/other_user}","gists_url":"https://api.github.com/users/runelite/gists{/gist_id}","starred_url":"https://api.github.com/users/runelite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/runelite/subscriptions","organizations_url":"https://api.github.com/users/runelite/orgs","repos_url":"https://api.github.com/users/runelite/repos","events_url":"https://api.github.com/users/runelite/events{/privacy}","received_events_url":"https://api.github.com/users/runelite/received_events","type":"Organization","site_admin":false},"repo":{"id":57461213,"node_id":"MDEwOlJlcG9zaXRvcnk1NzQ2MTIxMw==","name":"runelite","full_name":"runelite/runelite","owner":{"login":"runelite","id":13814776,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzODE0Nzc2","avatar_url":"https://avatars1.githubusercontent.com/u/13814776?v=4","gravatar_id":"","url":"https://api.github.com/users/runelite","html_url":"https://github.com/runelite","followers_url":"https://api.github.com/users/runelite/followers","following_url":"https://api.github.com/users/runelite/following{/other_user}","gists_url":"https://api.github.com/users/runelite/gists{/gist_id}","starred_url":"https://api.github.com/users/runelite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/runelite/subscriptions","organizations_url":"https://api.github.com/users/runelite/orgs","repos_url":"https://api.github.com/users/runelite/repos","events_url":"https://api.github.com/users/runelite/events{/privacy}","received_events_url":"https://api.github.com/users/runelite/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/runelite/runelite","description":"Open source Old School RuneScape client","fork":false,"url":"https://api.github.com/repos/runelite/runelite","forks_url":"https://api.github.com/repos/runelite/runelite/forks","keys_url":"https://api.github.com/repos/runelite/runelite/keys{/key_id}","collaborators_url":"https://api.github.com/repos/runelite/runelite/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/runelite/runelite/teams","hooks_url":"https://api.github.com/repos/runelite/runelite/hooks","issue_events_url":"https://api.github.com/repos/runelite/runelite/issues/events{/number}","events_url":"https://api.github.com/repos/runelite/runelite/events","assignees_url":"https://api.github.com/repos/runelite/runelite/assignees{/user}","branches_url":"https://api.github.com/repos/runelite/runelite/branches{/branch}","tags_url":"https://api.github.com/repos/runelite/runelite/tags","blobs_url":"https://api.github.com/repos/runelite/runelite/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/runelite/runelite/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/runelite/runelite/git/refs{/sha}","trees_url":"https://api.github.com/repos/runelite/runelite/git/trees{/sha}","statuses_url":"https://api.github.com/repos/runelite/runelite/statuses/{sha}","languages_url":"https://api.github.com/repos/runelite/runelite/languages","stargazers_url":"https://api.github.com/repos/runelite/runelite/stargazers","contributors_url":"https://api.github.com/repos/runelite/runelite/contributors","subscribers_url":"https://api.github.com/repos/runelite/runelite/subscribers","subscription_url":"https://api.github.com/repos/runelite/runelite/subscription","commits_url":"https://api.github.com/repos/runelite/runelite/commits{/sha}","git_commits_url":"https://api.github.com/repos/runelite/runelite/git/commits{/sha}","comments_url":"https://api.github.com/repos/runelite/runelite/comments{/number}","issue_comment_url":"https://api.github.com/repos/runelite/runelite/issues/comments{/number}","contents_url":"https://api.github.com/repos/runelite/runelite/contents/{+path}","compare_url":"https://api.github.com/repos/runelite/runelite/compare/{base}...{head}","merges_url":"https://api.github.com/repos/runelite/runelite/merges","archive_url":"https://api.github.com/repos/runelite/runelite/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/runelite/runelite/downloads","issues_url":"https://api.github.com/repos/runelite/runelite/issues{/number}","pulls_url":"https://api.github.com/repos/runelite/runelite/pulls{/number}","milestones_url":"https://api.github.com/repos/runelite/runelite/milestones{/number}","notifications_url":"https://api.github.com/repos/runelite/runelite/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/runelite/runelite/labels{/name}","releases_url":"https://api.github.com/repos/runelite/runelite/releases{/id}","deployments_url":"https://api.github.com/repos/runelite/runelite/deployments","created_at":"2016-04-30T20:48:02Z","updated_at":"2018-06-19T10:04:28Z","pushed_at":"2018-06-19T12:07:18Z","git_url":"git://github.com/runelite/runelite.git","ssh_url":"git@github.com:runelite/runelite.git","clone_url":"https://github.com/runelite/runelite.git","svn_url":"https://github.com/runelite/runelite","homepage":"https://runelite.net","size":16468,"stargazers_count":987,"watchers_count":987,"language":"Java","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1301,"mirror_url":null,"archived":false,"open_issues_count":1083,"license":{"key":"bsd-2-clause","name":"BSD 2-Clause \"Simplified\" License","spdx_id":"BSD-2-Clause","url":"https://api.github.com/licenses/bsd-2-clause","node_id":"MDc6TGljZW5zZTQ="},"forks":1301,"open_issues":1083,"watchers":987,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/runelite/runelite/pulls/3545"},"html":{"href":"https://github.com/runelite/runelite/pull/3545"},"issue":{"href":"https://api.github.com/repos/runelite/runelite/issues/3545"},"comments":{"href":"https://api.github.com/repos/runelite/runelite/issues/3545/comments"},"review_comments":{"href":"https://api.github.com/repos/runelite/runelite/pulls/3545/comments"},"review_comment":{"href":"https://api.github.com/repos/runelite/runelite/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/runelite/runelite/pulls/3545/commits"},"statuses":{"href":"https://api.github.com/repos/runelite/runelite/statuses/e48afaf8080aedcd333a57d526cbfc164e71c519"}},"author_association":"NONE"}}
{"actor":{"display_login":"NathenSample"}}